在 Dash 中动态更改绘图范围时,保持 plotly updatemenu 按钮处于活动状态
Keep plotly updatemenu button active when changing plot range dynamically in Dash
我需要一些关于 plotly 中 updatemenu
按钮的建议。我正在动态更改图表的绘图范围,并使用 updatemenu
按钮触发不同的轨迹。但是,只要我移动范围滑块,就会再次显示默认轨迹(并且上一个按钮未被选中或不再处于活动状态)。我希望视图保留在先前选择的(通过 updatemenu
按钮)轨迹上。
我花了大约 8 个小时来修复它,这是我最后的选择 :D 否则,它不会得到修复。
我确实相信这是可能的。我认为这超出了我目前的技能范围。也许有人可以想出一个巧妙的实现方式。
提前致谢:)
我的想法
- 属性
uirevision
- 自定义按钮,修改一个全局变量
- 创建一个额外的回调来监听
update
方法并将其保存在全局变量中
- 在我的回调中生成新图形之前读出图形的状态
- 没有为任何跟踪设置
visible
属性
更新:想法 4 按预期工作,请参阅下面的答案。
对想法的一些评论:
- 我认为这可能是最简单的方法。但是,当我将它设置为常量值时,它只保存了 zoom/selected 数据。它没有保持之前激活的痕迹可见。
- 由于按钮数量的变化,这是迄今为止最耗时的尝试。我能够读出哪个按钮被按下了,但我无法将它保存在一个全局变量中(我知道不推荐这样做,但这里无所谓)。即使
dcc.Store
它也不起作用,因为我没有修改它而改变了值。
- 不知怎么只能听
restyle
和relayout
方法。我无法用这两种方法切换轨迹,因此我没有继续。
- 我尝试将图形作为状态包含在
@ app.callback()
和 dash.State
中。但是,我无法获取当前活动的按钮。
- 我目前的做法是,它在初始化时工作,并且在更改滑块时立即看到一个图表。如果不提供
visible
属性,更改后将看不到任何痕迹。此外,我无法重新创建所需的行为。
最小工作示例(已实施答案)
实施意见
应用程序的当前版本要复杂得多。我在回调中有更多的元素,数据集有点复杂。
由于更改了数据集中的列,重要的是,我在读出列后创建轨迹和更新菜单按钮。固定数量和固定 labels/ids.
会更容易
进口:
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
import numpy as np
import webbrowser
import pandas as pd
申请:
def launch_app(data) -> None:
# Creates the Application
app = dash.Dash(__name__)
# HTML Layout
app.layout = html.Div(children=[
html.Div([
# Graph Container
html.Div(id='graph-container'),
# Range Slider
html.Div(
dcc.RangeSlider(id='range-slider', min=0, vertical=True),
),
], style={"display": "flex", "align-items": "center", "justify-content": "space-evenly"}),
])
# Callback to update graphs when slider changes
@ app.callback(
dash.Output("range-slider", "value"),
dash.Output("graph-container", "children"),
dash.State("graph-container", "children"),
dash.Input("range-slider", "value"),
)
def update(graph_container, slider):
# Setting max
max = len(data["data"]["petal_length"])
# First call to initialize
if slider is None:
end_value = max
slider_value = [0, end_value]
return slider_value, update_graphs(0, end_value)
# Get active Button
active_button = graph_container[0]['props']['figure']['layout']['updatemenus'][0]['active']
# Set values depending on the trigger
start_value = slider[0]
end_value = slider[1]
slider_value = slider
return slider_value, update_graphs(start_value, end_value, active_button)
# Running the app
app.run_server(debug=True, port=8050)
生成新图:
def update_graphs(start: int, end: int, active_button: int = 0) -> list[dcc.Graph]:
"""
Updates the dcc.Graph and returns the updated ones.
Parameters
----------
start : int
lower index which was selected
end : int
upper index which was selected
active_button: int
which button is active of the plotly updatemenu buttons, by default 0
Returns
-------
list[dcc.Graph]
Updated Graph
"""
fig = go.Figure()
# Read out columns automatically
all_columns = [col for col in list(data["data"].head())]
# Generate X-Axis
xvalues = np.arange(0, len(data["data"]["petal_length"]))
# CREATION OF TRACES
for ii, col in enumerate(all_columns):
if ii == active_button:
visible = True
else:
visible = False
fig.add_trace(
go.Scatter(x=xvalues[start: end],
y=data["data"][f"{col}"][start: end],
visible=visible)
)
# Generation of visible array for buttons properties
show_list = []
for ii, val in enumerate(all_columns):
# Initialization
temp = np.zeros(len(all_columns))
temp[ii] = 1
temp = np.array(temp, dtype=bool)
show_list.append(temp)
# CREATION OF BUTTONS
all_buttons = []
for ii, col in enumerate(all_columns):
all_buttons.append(dict(label=f"{col}", method="update", args=[
{"visible": show_list[ii]},
{"yaxis": {'title': f'yaxis {col}'}}
]))
# Update Menu Buttons
fig.update_layout(
updatemenus=[
dict(
type="buttons",
active=active_button,
showactive=True,
buttons=all_buttons,
x=0.0,
y=1.2,
xanchor="left",
yanchor="top",
direction="right",
)
])
return [
dcc.Graph(
id='time',
figure=fig,
)
]
脚本:
if __name__ == "__main__":
# Loading sample Data
iris = pd.read_csv(
'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv')
data = {"id": 20, "data": iris}
# Opens the server
webbrowser.open("http://127.0.0.1:8050/")
# Launches the server
launch_app(data)
您的想法 (4) 将图形(或其父对象)作为状态包含在回调函数中 update
应该可行。您只需找到正确的“活动”属性,然后将活动按钮作为参数传递给您的 update_graphs
函数。
应进行以下更改:
- 添加“graph-container”作为输入。
@ app.callback(
dash.Output("range-slider", "value"),
dash.Output("graph-container", "children"),
dash.Input("graph-container", "children"),
dash.Input("range-slider", "value"),
)
def update(graph_container, slider):
- 获取当前活动按钮的索引并将其传递给
update_graphs
。
active_button = graph_container[1]['props']['figure']['layout']['updatemenus'][0]['active']
return slider_value, update_graphs(start_value, end_value, active_button=active_button)
- 在
update_graphs
中接收active_button索引并使用它。
def update_graphs(start: int, end: int, active_button: int):
# Some stuff
# Update Menu Buttons
fig.update_layout(
updatemenus=[
dict(
# other stuff
active=active_button,
)
])
我需要一些关于 plotly 中 updatemenu
按钮的建议。我正在动态更改图表的绘图范围,并使用 updatemenu
按钮触发不同的轨迹。但是,只要我移动范围滑块,就会再次显示默认轨迹(并且上一个按钮未被选中或不再处于活动状态)。我希望视图保留在先前选择的(通过 updatemenu
按钮)轨迹上。
我花了大约 8 个小时来修复它,这是我最后的选择 :D 否则,它不会得到修复。
我确实相信这是可能的。我认为这超出了我目前的技能范围。也许有人可以想出一个巧妙的实现方式。
提前致谢:)
我的想法
- 属性
uirevision
- 自定义按钮,修改一个全局变量
- 创建一个额外的回调来监听
update
方法并将其保存在全局变量中 - 在我的回调中生成新图形之前读出图形的状态
- 没有为任何跟踪设置
visible
属性
更新:想法 4 按预期工作,请参阅下面的答案。
对想法的一些评论:
- 我认为这可能是最简单的方法。但是,当我将它设置为常量值时,它只保存了 zoom/selected 数据。它没有保持之前激活的痕迹可见。
- 由于按钮数量的变化,这是迄今为止最耗时的尝试。我能够读出哪个按钮被按下了,但我无法将它保存在一个全局变量中(我知道不推荐这样做,但这里无所谓)。即使
dcc.Store
它也不起作用,因为我没有修改它而改变了值。 - 不知怎么只能听
restyle
和relayout
方法。我无法用这两种方法切换轨迹,因此我没有继续。 - 我尝试将图形作为状态包含在
@ app.callback()
和dash.State
中。但是,我无法获取当前活动的按钮。 - 我目前的做法是,它在初始化时工作,并且在更改滑块时立即看到一个图表。如果不提供
visible
属性,更改后将看不到任何痕迹。此外,我无法重新创建所需的行为。
最小工作示例(已实施答案)
实施意见
应用程序的当前版本要复杂得多。我在回调中有更多的元素,数据集有点复杂。
由于更改了数据集中的列,重要的是,我在读出列后创建轨迹和更新菜单按钮。固定数量和固定 labels/ids.
会更容易进口:
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
import numpy as np
import webbrowser
import pandas as pd
申请:
def launch_app(data) -> None:
# Creates the Application
app = dash.Dash(__name__)
# HTML Layout
app.layout = html.Div(children=[
html.Div([
# Graph Container
html.Div(id='graph-container'),
# Range Slider
html.Div(
dcc.RangeSlider(id='range-slider', min=0, vertical=True),
),
], style={"display": "flex", "align-items": "center", "justify-content": "space-evenly"}),
])
# Callback to update graphs when slider changes
@ app.callback(
dash.Output("range-slider", "value"),
dash.Output("graph-container", "children"),
dash.State("graph-container", "children"),
dash.Input("range-slider", "value"),
)
def update(graph_container, slider):
# Setting max
max = len(data["data"]["petal_length"])
# First call to initialize
if slider is None:
end_value = max
slider_value = [0, end_value]
return slider_value, update_graphs(0, end_value)
# Get active Button
active_button = graph_container[0]['props']['figure']['layout']['updatemenus'][0]['active']
# Set values depending on the trigger
start_value = slider[0]
end_value = slider[1]
slider_value = slider
return slider_value, update_graphs(start_value, end_value, active_button)
# Running the app
app.run_server(debug=True, port=8050)
生成新图:
def update_graphs(start: int, end: int, active_button: int = 0) -> list[dcc.Graph]:
"""
Updates the dcc.Graph and returns the updated ones.
Parameters
----------
start : int
lower index which was selected
end : int
upper index which was selected
active_button: int
which button is active of the plotly updatemenu buttons, by default 0
Returns
-------
list[dcc.Graph]
Updated Graph
"""
fig = go.Figure()
# Read out columns automatically
all_columns = [col for col in list(data["data"].head())]
# Generate X-Axis
xvalues = np.arange(0, len(data["data"]["petal_length"]))
# CREATION OF TRACES
for ii, col in enumerate(all_columns):
if ii == active_button:
visible = True
else:
visible = False
fig.add_trace(
go.Scatter(x=xvalues[start: end],
y=data["data"][f"{col}"][start: end],
visible=visible)
)
# Generation of visible array for buttons properties
show_list = []
for ii, val in enumerate(all_columns):
# Initialization
temp = np.zeros(len(all_columns))
temp[ii] = 1
temp = np.array(temp, dtype=bool)
show_list.append(temp)
# CREATION OF BUTTONS
all_buttons = []
for ii, col in enumerate(all_columns):
all_buttons.append(dict(label=f"{col}", method="update", args=[
{"visible": show_list[ii]},
{"yaxis": {'title': f'yaxis {col}'}}
]))
# Update Menu Buttons
fig.update_layout(
updatemenus=[
dict(
type="buttons",
active=active_button,
showactive=True,
buttons=all_buttons,
x=0.0,
y=1.2,
xanchor="left",
yanchor="top",
direction="right",
)
])
return [
dcc.Graph(
id='time',
figure=fig,
)
]
脚本:
if __name__ == "__main__":
# Loading sample Data
iris = pd.read_csv(
'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv')
data = {"id": 20, "data": iris}
# Opens the server
webbrowser.open("http://127.0.0.1:8050/")
# Launches the server
launch_app(data)
您的想法 (4) 将图形(或其父对象)作为状态包含在回调函数中 update
应该可行。您只需找到正确的“活动”属性,然后将活动按钮作为参数传递给您的 update_graphs
函数。
应进行以下更改:
- 添加“graph-container”作为输入。
@ app.callback(
dash.Output("range-slider", "value"),
dash.Output("graph-container", "children"),
dash.Input("graph-container", "children"),
dash.Input("range-slider", "value"),
)
def update(graph_container, slider):
- 获取当前活动按钮的索引并将其传递给
update_graphs
。
active_button = graph_container[1]['props']['figure']['layout']['updatemenus'][0]['active']
return slider_value, update_graphs(start_value, end_value, active_button=active_button)
- 在
update_graphs
中接收active_button索引并使用它。
def update_graphs(start: int, end: int, active_button: int):
# Some stuff
# Update Menu Buttons
fig.update_layout(
updatemenus=[
dict(
# other stuff
active=active_button,
)
])