Dash Plotly 图形线条在某些动画帧中消失
Dash Plotly Graph Lines Disappear in Certain Frames of Animation
我有一个基于 Python 的 Dash 图形“动画”(多帧,显示不同的日期),当帧发生变化时其线条不会显示。我已经使用 Plotly-Dash 几年了,我以前从未 运行 遇到过这个问题,但我在下面有一个可重现的例子。
这是所有帧应该的样子(注意顶部的红线和底部的紫线) :
那是上面的第一帧。右数第二帧看起来像这样,紫色线上方没有红色线。我可以向你保证那里有数据;它只是没有出现!使用 print(tabulate()) 的输出自行检查。
要准备好回答这个问题,请安装以下库:
pip install dash flask plotly pandas colour tabulate
没有 完美 方式来共享相当大的 DataFrame,但共享文本比提供下载更可取 link(感谢@vestland 从他的回答中得到提示).
这是完整的代码,可供您复制和粘贴,并查看“错误”:
from flask import Flask
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
import pandas as pd
from colour import Color
from tabulate import tabulate
# Create the "list_of_dicts" for Pandas
list_of_dicts = [
{
"Unnamed: 0": 1499,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.33,
"up_down": "Downstroke",
"hour": 20,
"load": -241.0,
},
{
"Unnamed: 0": 21615,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.33,
"up_down": "Upstroke",
"hour": 20,
"load": 165.9,
},
{
"Unnamed: 0": 1687,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.73,
"up_down": "Downstroke",
"hour": 20,
"load": -239.0,
},
{
"Unnamed: 0": 21803,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.73,
"up_down": "Upstroke",
"hour": 20,
"load": 147.76,
},
{
"Unnamed: 0": 1875,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.13,
"up_down": "Downstroke",
"hour": 20,
"load": -242.0,
},
{
"Unnamed: 0": 21991,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.13,
"up_down": "Upstroke",
"hour": 20,
"load": 128.0,
},
{
"Unnamed: 0": 2063,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.53,
"up_down": "Downstroke",
"hour": 20,
"load": -244.0,
},
{
"Unnamed: 0": 22179,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.53,
"up_down": "Upstroke",
"hour": 20,
"load": 109.25,
},
{
"Unnamed: 0": 2251,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.94,
"up_down": "Downstroke",
"hour": 20,
"load": -243.0,
},
{
"Unnamed: 0": 22367,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.94,
"up_down": "Upstroke",
"hour": 20,
"load": 92.6206896551724,
},
{
"Unnamed: 0": 1500,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.33,
"up_down": "Downstroke",
"hour": 21,
"load": -245.0,
},
{
"Unnamed: 0": 21616,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.33,
"up_down": "Upstroke",
"hour": 21,
"load": 183.84615384615384,
},
{
"Unnamed: 0": 1688,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.73,
"up_down": "Downstroke",
"hour": 21,
"load": -244.0,
},
{
"Unnamed: 0": 21804,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.73,
"up_down": "Upstroke",
"hour": 21,
"load": 163.5,
},
{
"Unnamed: 0": 1876,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.13,
"up_down": "Downstroke",
"hour": 21,
"load": -244.0,
},
{
"Unnamed: 0": 21992,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.13,
"up_down": "Upstroke",
"hour": 21,
"load": 145.44444444444446,
},
{
"Unnamed: 0": 2064,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.53,
"up_down": "Downstroke",
"hour": 21,
"load": -246.0,
},
{
"Unnamed: 0": 22180,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.53,
"up_down": "Upstroke",
"hour": 21,
"load": 128.21052631578948,
},
{
"Unnamed: 0": 2252,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.94,
"up_down": "Downstroke",
"hour": 21,
"load": -246.0,
},
{
"Unnamed: 0": 22368,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.94,
"up_down": "Upstroke",
"hour": 21,
"load": 110.55555555555556,
},
{
"Unnamed: 0": 1315,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.05,
"up_down": "Downstroke",
"hour": 16,
"load": -202.0,
},
{
"Unnamed: 0": 21431,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.05,
"up_down": "Upstroke",
"hour": 16,
"load": 176.0,
},
{
"Unnamed: 0": 1503,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.45,
"up_down": "Downstroke",
"hour": 16,
"load": -204.0,
},
{
"Unnamed: 0": 21619,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.45,
"up_down": "Upstroke",
"hour": 16,
"load": 166.0,
},
{
"Unnamed: 0": 1691,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.85,
"up_down": "Downstroke",
"hour": 16,
"load": -202.0,
},
{
"Unnamed: 0": 21807,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.85,
"up_down": "Upstroke",
"hour": 16,
"load": 154.0,
},
{
"Unnamed: 0": 1879,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 7.25,
"up_down": "Downstroke",
"hour": 16,
"load": -202.0,
},
{
"Unnamed: 0": 21995,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 7.25,
"up_down": "Upstroke",
"hour": 16,
"load": 142.0,
},
{
"Unnamed: 0": 2067,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 7.66,
"up_down": "Downstroke",
"hour": 16,
"load": -202.0,
},
{
"Unnamed: 0": 22183,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 7.66,
"up_down": "Upstroke",
"hour": 16,
"load": 130.0,
},
]
# Create the DataFrame from the list_of_dicts
df = pd.DataFrame(list_of_dicts)
df = df.sort_values(["timestamp_local", "inches", "up_down"])
# For viewing and verifying DataFrame contents in VS Code:
print(df["timestamp_local"].unique())
print(
tabulate(
df[["timestamp_local", "inches", "up_down", "hour", "load"]],
headers="keys",
tablefmt="psql",
)
)
#############################################################################
# Create the animation in Plotly-Dash
frames = []
slider_steps = []
slider_distinct_days_set = set()
mode = "lines"
marker = dict(
size=5,
opacity=0.5,
)
line = dict(
shape="spline",
smoothing=0.4,
)
# Transition in milliseconds for the animation (default 500)
duration_frame = 1000
duration_transition = 0
duration_transition_slider = 1000
# Docs say redraw not needed for scatterplots, but if it doesn't redraw,
# the annotations stay the same as for the first frame...
redraw = True
easing = "exp-in-out"
ordering = "layout first" # default
mode_animate = "immediate" # default
bootstrap_blue_base = Color("blue")
bootstrap_blue_lum = Color("blue")
bootstrap_blue_lum.luminance = 0.8
bootstrap_red_base = Color("red")
bootstrap_red_lum = Color("red")
bootstrap_red_lum.luminance = 0.8
# Add scatters to the animation by day
for gname_day, gdf_day in df.groupby("timestamp_local_day"):
frame = {"data": [], "name": gname_day, "layout": {}}
hours_in_day = gdf_day["timestamp_local"].nunique()
up_colors = list(bootstrap_red_lum.range_to(bootstrap_red_base, hours_in_day))
down_colors = list(bootstrap_blue_lum.range_to(bootstrap_blue_base, hours_in_day))
for gname_isup, gdf_isup in gdf_day.groupby("up_down"):
i = 0
colors = down_colors if gname_isup == "Downstroke" else up_colors
for label, gdf_ts in gdf_isup.groupby("timestamp_local"):
print(f"{gname_day} {gname_isup} {label} color: {colors[i].hex}")
frame["data"].append(
go.Scatter(
name=label,
mode=mode, # lines or markers
x=gdf_ts["inches"],
y=gdf_ts["load"],
marker=dict(
color=colors[i].hex,
),
line=line,
)
)
i += 1
frames.append(frame)
if gname_day not in slider_distinct_days_set:
slider_distinct_days_set.add(gname_day)
slider_steps.append(
{
"method": "animate",
"label": gname_day, # text label to appear on the slider
"args": [
[gname_day],
{
"mode": mode_animate,
"frame": {"duration": duration_frame, "redraw": redraw},
"transition": {
"duration": duration_transition_slider,
"easing": easing,
},
"ordering": ordering,
},
],
}
)
most_recent_day_available_index = max(0, len(slider_distinct_days_set) - 1)
sliders = [
{
# IMPORTANT: this is the "active" step in the slider, which shows up on load
"active": most_recent_day_available_index,
"pad": {"b": 10, "t": 60},
"len": 0.9,
"x": 0.1,
"xanchor": "left",
"y": 0,
"yanchor": "top",
"steps": slider_steps,
"transition": {"duration": duration_transition_slider},
}
]
updatemenus = [
{
"type": "buttons",
"direction": "left",
"pad": {"r": 10, "t": 70},
"showactive": False,
"x": 0.1,
"xanchor": "right",
"y": 0,
"yanchor": "top",
"buttons": [
{
"label": "Play",
"method": "animate",
"args": [
None,
{
"mode": mode_animate,
"direction": "reverse", # forward or reverse
"fromcurrent": True,
"frame": {"duration": duration_frame, "redraw": redraw},
"transition": {
"duration": duration_transition,
"easing": easing,
},
"ordering": ordering,
},
],
},
{
"label": "Pause",
"method": "animate",
"args": [
[None],
{
"mode": "immediate",
"frame": {"duration": 0, "redraw": redraw},
"transition": {
"duration": 0,
},
},
],
},
],
}
]
fig = go.Figure(
# Make the initial data, before the animation frames start
data=frames[-1]["data"],
frames=frames,
layout=go.Layout(
hovermode="closest",
height=500,
plot_bgcolor="white",
showlegend=False,
font={"family": "Segoe UI", "color": "#717174"},
xaxis=dict(
gridcolor="rgb(238,238,238)",
range=[6, 8],
title="position",
),
yaxis=dict(
gridcolor="rgb(238,238,238)",
range=[-350, 350],
title="Weight",
),
margin=go.layout.Margin(l=0, r=10, b=0, t=0),
sliders=sliders,
updatemenus=updatemenus,
),
)
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
def create_app():
app = Flask(__name__)
dashapp = dash.Dash(__name__, server=app, external_stylesheets=external_stylesheets)
dashapp.layout = html.Div(
[
dcc.Graph(
figure=fig,
)
]
)
return app
app = create_app()
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5000)
我没有足够的代表来发表评论,所以我必须在这里回答。我在 plotly 上有一个这样的例子,实际上是我的一条线直接在另一条线的下面,所以看起来好像它不见了。
当我点击图例中的重叠线时,它发现了缺失的线。在这里可以吗?
Plotly-Dash 动画文档中描述了 problem/bug here:
Animations are designed to work well when each row of input is present across all animation frames, and when categorical values mapped to symbol, color and facet are constant across frames. Animations may be misleading or inconsistent if these constraints are not met.
我的示例有每日帧数,但每一天的小时数数据都不相同。 1月19日有两个小时,而1月20日只有一个小时。
为了解决这个问题,我需要让每一天都有相同数量的每小时“图表”(例如每天 24 个数据图表)。
我有一个基于 Python 的 Dash 图形“动画”(多帧,显示不同的日期),当帧发生变化时其线条不会显示。我已经使用 Plotly-Dash 几年了,我以前从未 运行 遇到过这个问题,但我在下面有一个可重现的例子。
这是所有帧应该的样子(注意顶部的红线和底部的紫线) :
那是上面的第一帧。右数第二帧看起来像这样,紫色线上方没有红色线。我可以向你保证那里有数据;它只是没有出现!使用 print(tabulate()) 的输出自行检查。
要准备好回答这个问题,请安装以下库:
pip install dash flask plotly pandas colour tabulate
没有 完美 方式来共享相当大的 DataFrame,但共享文本比提供下载更可取 link(感谢@vestland 从他的回答中得到提示
这是完整的代码,可供您复制和粘贴,并查看“错误”:
from flask import Flask
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
import pandas as pd
from colour import Color
from tabulate import tabulate
# Create the "list_of_dicts" for Pandas
list_of_dicts = [
{
"Unnamed: 0": 1499,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.33,
"up_down": "Downstroke",
"hour": 20,
"load": -241.0,
},
{
"Unnamed: 0": 21615,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.33,
"up_down": "Upstroke",
"hour": 20,
"load": 165.9,
},
{
"Unnamed: 0": 1687,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.73,
"up_down": "Downstroke",
"hour": 20,
"load": -239.0,
},
{
"Unnamed: 0": 21803,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.73,
"up_down": "Upstroke",
"hour": 20,
"load": 147.76,
},
{
"Unnamed: 0": 1875,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.13,
"up_down": "Downstroke",
"hour": 20,
"load": -242.0,
},
{
"Unnamed: 0": 21991,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.13,
"up_down": "Upstroke",
"hour": 20,
"load": 128.0,
},
{
"Unnamed: 0": 2063,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.53,
"up_down": "Downstroke",
"hour": 20,
"load": -244.0,
},
{
"Unnamed: 0": 22179,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.53,
"up_down": "Upstroke",
"hour": 20,
"load": 109.25,
},
{
"Unnamed: 0": 2251,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.94,
"up_down": "Downstroke",
"hour": 20,
"load": -243.0,
},
{
"Unnamed: 0": 22367,
"timestamp_local": "2021-01-19 20:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.94,
"up_down": "Upstroke",
"hour": 20,
"load": 92.6206896551724,
},
{
"Unnamed: 0": 1500,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.33,
"up_down": "Downstroke",
"hour": 21,
"load": -245.0,
},
{
"Unnamed: 0": 21616,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.33,
"up_down": "Upstroke",
"hour": 21,
"load": 183.84615384615384,
},
{
"Unnamed: 0": 1688,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.73,
"up_down": "Downstroke",
"hour": 21,
"load": -244.0,
},
{
"Unnamed: 0": 21804,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 6.73,
"up_down": "Upstroke",
"hour": 21,
"load": 163.5,
},
{
"Unnamed: 0": 1876,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.13,
"up_down": "Downstroke",
"hour": 21,
"load": -244.0,
},
{
"Unnamed: 0": 21992,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.13,
"up_down": "Upstroke",
"hour": 21,
"load": 145.44444444444446,
},
{
"Unnamed: 0": 2064,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.53,
"up_down": "Downstroke",
"hour": 21,
"load": -246.0,
},
{
"Unnamed: 0": 22180,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.53,
"up_down": "Upstroke",
"hour": 21,
"load": 128.21052631578948,
},
{
"Unnamed: 0": 2252,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.94,
"up_down": "Downstroke",
"hour": 21,
"load": -246.0,
},
{
"Unnamed: 0": 22368,
"timestamp_local": "2021-01-19 21:00:00-07:00",
"timestamp_local_day": "2021-01-19 00:00:00-07:00",
"inches": 7.94,
"up_down": "Upstroke",
"hour": 21,
"load": 110.55555555555556,
},
{
"Unnamed: 0": 1315,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.05,
"up_down": "Downstroke",
"hour": 16,
"load": -202.0,
},
{
"Unnamed: 0": 21431,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.05,
"up_down": "Upstroke",
"hour": 16,
"load": 176.0,
},
{
"Unnamed: 0": 1503,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.45,
"up_down": "Downstroke",
"hour": 16,
"load": -204.0,
},
{
"Unnamed: 0": 21619,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.45,
"up_down": "Upstroke",
"hour": 16,
"load": 166.0,
},
{
"Unnamed: 0": 1691,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.85,
"up_down": "Downstroke",
"hour": 16,
"load": -202.0,
},
{
"Unnamed: 0": 21807,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 6.85,
"up_down": "Upstroke",
"hour": 16,
"load": 154.0,
},
{
"Unnamed: 0": 1879,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 7.25,
"up_down": "Downstroke",
"hour": 16,
"load": -202.0,
},
{
"Unnamed: 0": 21995,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 7.25,
"up_down": "Upstroke",
"hour": 16,
"load": 142.0,
},
{
"Unnamed: 0": 2067,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 7.66,
"up_down": "Downstroke",
"hour": 16,
"load": -202.0,
},
{
"Unnamed: 0": 22183,
"timestamp_local": "2021-01-20 16:00:00-07:00",
"timestamp_local_day": "2021-01-20 00:00:00-07:00",
"inches": 7.66,
"up_down": "Upstroke",
"hour": 16,
"load": 130.0,
},
]
# Create the DataFrame from the list_of_dicts
df = pd.DataFrame(list_of_dicts)
df = df.sort_values(["timestamp_local", "inches", "up_down"])
# For viewing and verifying DataFrame contents in VS Code:
print(df["timestamp_local"].unique())
print(
tabulate(
df[["timestamp_local", "inches", "up_down", "hour", "load"]],
headers="keys",
tablefmt="psql",
)
)
#############################################################################
# Create the animation in Plotly-Dash
frames = []
slider_steps = []
slider_distinct_days_set = set()
mode = "lines"
marker = dict(
size=5,
opacity=0.5,
)
line = dict(
shape="spline",
smoothing=0.4,
)
# Transition in milliseconds for the animation (default 500)
duration_frame = 1000
duration_transition = 0
duration_transition_slider = 1000
# Docs say redraw not needed for scatterplots, but if it doesn't redraw,
# the annotations stay the same as for the first frame...
redraw = True
easing = "exp-in-out"
ordering = "layout first" # default
mode_animate = "immediate" # default
bootstrap_blue_base = Color("blue")
bootstrap_blue_lum = Color("blue")
bootstrap_blue_lum.luminance = 0.8
bootstrap_red_base = Color("red")
bootstrap_red_lum = Color("red")
bootstrap_red_lum.luminance = 0.8
# Add scatters to the animation by day
for gname_day, gdf_day in df.groupby("timestamp_local_day"):
frame = {"data": [], "name": gname_day, "layout": {}}
hours_in_day = gdf_day["timestamp_local"].nunique()
up_colors = list(bootstrap_red_lum.range_to(bootstrap_red_base, hours_in_day))
down_colors = list(bootstrap_blue_lum.range_to(bootstrap_blue_base, hours_in_day))
for gname_isup, gdf_isup in gdf_day.groupby("up_down"):
i = 0
colors = down_colors if gname_isup == "Downstroke" else up_colors
for label, gdf_ts in gdf_isup.groupby("timestamp_local"):
print(f"{gname_day} {gname_isup} {label} color: {colors[i].hex}")
frame["data"].append(
go.Scatter(
name=label,
mode=mode, # lines or markers
x=gdf_ts["inches"],
y=gdf_ts["load"],
marker=dict(
color=colors[i].hex,
),
line=line,
)
)
i += 1
frames.append(frame)
if gname_day not in slider_distinct_days_set:
slider_distinct_days_set.add(gname_day)
slider_steps.append(
{
"method": "animate",
"label": gname_day, # text label to appear on the slider
"args": [
[gname_day],
{
"mode": mode_animate,
"frame": {"duration": duration_frame, "redraw": redraw},
"transition": {
"duration": duration_transition_slider,
"easing": easing,
},
"ordering": ordering,
},
],
}
)
most_recent_day_available_index = max(0, len(slider_distinct_days_set) - 1)
sliders = [
{
# IMPORTANT: this is the "active" step in the slider, which shows up on load
"active": most_recent_day_available_index,
"pad": {"b": 10, "t": 60},
"len": 0.9,
"x": 0.1,
"xanchor": "left",
"y": 0,
"yanchor": "top",
"steps": slider_steps,
"transition": {"duration": duration_transition_slider},
}
]
updatemenus = [
{
"type": "buttons",
"direction": "left",
"pad": {"r": 10, "t": 70},
"showactive": False,
"x": 0.1,
"xanchor": "right",
"y": 0,
"yanchor": "top",
"buttons": [
{
"label": "Play",
"method": "animate",
"args": [
None,
{
"mode": mode_animate,
"direction": "reverse", # forward or reverse
"fromcurrent": True,
"frame": {"duration": duration_frame, "redraw": redraw},
"transition": {
"duration": duration_transition,
"easing": easing,
},
"ordering": ordering,
},
],
},
{
"label": "Pause",
"method": "animate",
"args": [
[None],
{
"mode": "immediate",
"frame": {"duration": 0, "redraw": redraw},
"transition": {
"duration": 0,
},
},
],
},
],
}
]
fig = go.Figure(
# Make the initial data, before the animation frames start
data=frames[-1]["data"],
frames=frames,
layout=go.Layout(
hovermode="closest",
height=500,
plot_bgcolor="white",
showlegend=False,
font={"family": "Segoe UI", "color": "#717174"},
xaxis=dict(
gridcolor="rgb(238,238,238)",
range=[6, 8],
title="position",
),
yaxis=dict(
gridcolor="rgb(238,238,238)",
range=[-350, 350],
title="Weight",
),
margin=go.layout.Margin(l=0, r=10, b=0, t=0),
sliders=sliders,
updatemenus=updatemenus,
),
)
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
def create_app():
app = Flask(__name__)
dashapp = dash.Dash(__name__, server=app, external_stylesheets=external_stylesheets)
dashapp.layout = html.Div(
[
dcc.Graph(
figure=fig,
)
]
)
return app
app = create_app()
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5000)
我没有足够的代表来发表评论,所以我必须在这里回答。我在 plotly 上有一个这样的例子,实际上是我的一条线直接在另一条线的下面,所以看起来好像它不见了。
当我点击图例中的重叠线时,它发现了缺失的线。在这里可以吗?
Plotly-Dash 动画文档中描述了 problem/bug here:
Animations are designed to work well when each row of input is present across all animation frames, and when categorical values mapped to symbol, color and facet are constant across frames. Animations may be misleading or inconsistent if these constraints are not met.
我的示例有每日帧数,但每一天的小时数数据都不相同。 1月19日有两个小时,而1月20日只有一个小时。
为了解决这个问题,我需要让每一天都有相同数量的每小时“图表”(例如每天 24 个数据图表)。