【问题标题】:Plotly-Dash: How to improve plot responsiveness to slider changesPlotly-Dash:如何提高对滑块变化的绘图响应能力
【发布时间】:2021-04-27 11:42:20
【问题描述】:

我正在开发一种工具,通过 Dash 滑块修改这些参数来可视化一组参数对数学函数的影响。我正在使用一些 Dash 教程示例中的方法,这些示例使用回调来替换图形。

这可行,但绘图对滑块变化的响应不如内置操作(例如通过拖动旋转绘图)。当图中的元素很多时尤其如此。

是否有不同的方法可以提高响应能力?例如,有没有办法只针对发生变化的绘图元素而不是替换整个图形?

这是一个最小的工作示例,由一个静态圆(有许多样本)和一个我们通过滑块旋转的线段组成。线段的旋转很不稳定。

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
from dash.dependencies import Input, Output, State
import numpy as np

app = dash.Dash(__name__)

# plot a circle
t = np.linspace(0, 2*np.pi, 10000)  # intentionally use many points to exaggerate the issue
x = np.cos(t)
y = np.sin(t)
z = np.zeros_like(t)
marker_size = 4
fig = go.Figure()
fig.add_trace(go.Scatter3d(x=x, y=y, z=z, mode='lines'))
fig.add_trace(go.Scatter3d(
    x=[0.0, 0.0], y=[0.0, 0.0], z=[0.0, 0.0],
    marker=go.scatter3d.Marker(size=marker_size),
    line=dict(width=3.0),
    showlegend=False,
))

fig.update_layout(
    uirevision='constant',
    autosize=False,
    width=900,
    height=900,
    scene=dict(
        xaxis=dict(range=[-1, 1]),
        yaxis=dict(range=[-1, 1]),
        zaxis=dict(range=[-1, 1.0]),
        aspectratio=dict(x=2, y=2, z=2),
    ),
)

app.layout = html.Div(children=[
    dcc.Graph(
        id='example-graph',
        figure=fig
    ),
    html.Div(
        [
            dcc.Slider(
                id='slider-phi',
                min=0.0,
                max=360.0,
                step=1.0,
                value=0.0,
                marks={0: '0', 180: '180', 360: '360'},
                updatemode='drag',
            ),
        ],
        style=dict(width='50%'),
    ),
    html.Div(children='', id='output-box'),
])

@app.callback(
    Output('example-graph', 'figure'),
    [Input('slider-phi', 'value')],
    [State('example-graph', 'figure')]
)
def display_structure(phi, myfig):
    myfig['data'][1]['x'][1] = np.cos(np.radians(phi))
    myfig['data'][1]['y'][1] = np.sin(np.radians(phi))
    myfig['data'][1]['z'][1] = 0

    return myfig

if __name__ == '__main__':
    app.run_server(debug=True)

【问题讨论】:

  • 我认为出现这种情况的原因是由于实时绘制需要大量的计算。我通过将图形的大小减半并使用步长值 10 来检查操作。拖动滑块也会随之而来。

标签: python plotly plotly-dash


【解决方案1】:

缺乏响应可归因于两个关键因素。第一个是,正如您所注意到的,每次都会更新整个图形,而不仅仅是所需的轨迹。您可以通过定位extendData 属性而不是figure 属性来避免这种情况,

@app.callback(Output('example-graph', 'extendData'), [Input('slider-phi', 'value')])
def update_data(phi):
    # tuple is (dict of new data, target trace index, number of points to keep)
    return dict(x=[[0, np.cos(np.radians(phi))]], y=[[0, np.sin(np.radians(phi))]]), [1], 2

第二个因素是回调是在服务器端而不是客户端执行的,即每次移动滑块时,客户端和服务器之间都会交换请求。为避免这种情况,您可以通过将回调转换为 clientside callback 来将更新移动到客户端,

app.clientside_callback(
    """
    function(phi) {
        // tuple is (dict of new data, target trace index, number of points to keep)
        return [{x: [[0, Math.cos(phi/180*Math.PI)]], y:[[0, Math.sin(phi/180*Math.PI)]]}, [1], 2]
    }
    """, Output('example-graph', 'extendData'), [Input('slider-phi', 'value')]
)

这应该会产生合理的响应。这是它在我的笔记本电脑上的样子,

为了完整起见,这里是完整的代码,

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
from dash.dependencies import Input, Output, State
import numpy as np

app = dash.Dash(__name__)

# plot a circle
t = np.linspace(0, 2*np.pi, 10000)  # intentionally use many points to exaggerate the issue
x = np.cos(t)
y = np.sin(t)
z = np.zeros_like(t)
marker_size = 4
fig = go.Figure()
fig.add_trace(go.Scatter3d(x=x, y=y, z=z, mode='lines'))
fig.add_trace(go.Scatter3d(
    x=[0.0, 0.0], y=[0.0, 0.0], z=[0.0, 0.0],
    marker=go.scatter3d.Marker(size=marker_size),
    line=dict(width=3.0),
    showlegend=False,
))

fig.update_layout(
    uirevision='constant',
    autosize=False,
    width=900,
    height=900,
    scene=dict(
        xaxis=dict(range=[-1, 1]),
        yaxis=dict(range=[-1, 1]),
        zaxis=dict(range=[-1, 1.0]),
        aspectratio=dict(x=2, y=2, z=2),
    ),
)

app.layout = html.Div(children=[
    dcc.Graph(
        id='example-graph',
        figure=fig
    ),
    html.Div(
        [
            dcc.Slider(
                id='slider-phi',
                min=0.0,
                max=360.0,
                step=1.0,
                value=0.0,
                marks={0: '0', 180: '180', 360: '360'},
                updatemode='drag',
            ),
        ],
        style=dict(width='50%'),
    ),
    html.Div(children='', id='output-box'),
])

app.clientside_callback(
    """
    function(phi) {
        // tuple is (dict of new data, target trace index, number of points to keep)
        return [{x: [[0, Math.cos(phi/180*Math.PI)]], y:[[0, Math.sin(phi/180*Math.PI)]]}, [1], 2]
    }
    """, Output('example-graph', 'extendData'), [Input('slider-phi', 'value')]
)

if __name__ == '__main__':
    app.run_server(debug=True)

【讨论】:

  • 谢谢!对于无法通过 javascript 计算新数据的不那么琐碎的情况,是否可以使用类似的方法?也许通过在服务器端预先计算整个参数范围的输出,然后发送一个可以通过客户端回调访问的“查找表”?
  • 是的,在这种情况下,您可以使用正常的回调目标将数据发送到客户端,例如Store 组件,然后使用客户端回调更新图形。
  • 这种方法能否适用于不同参数对应的多个滑块,可能适用于不同轨迹中的曲线?似乎 Dash 每个输出只允许一个回调,因此每个滑块都需要使用相同的回调。我不确定如何确定哪个滑块触发了回调,因此需要更新哪些数据。
  • 是的,这应该是可能的。虽然您只能为每个输出分配一个回调,但您可以根据需要向回调添加任意数量的输入(在您的情况下为滑块)。您可以通过 [callback_context][1] 确定触发回调的输入。不过,这与原始问题有点偏离主题,因此扩展讨论可能更适合另一个问题。 [1]:dash.plotly.com/advanced-callbacks
  • 我已经打开了一个单独的问题here 来讨论在同一个客户端回调中处理多个触发器输入。
【解决方案2】:

看起来虽然display_structure在原地修改了myfig,但返回值被视为一个全新的情节,情节将重新渲染。

回调是否有可能不需要返回值?如果您的函数修改了绘图属性,这可能就到位了。

仅供参考:我使用bokeh,但对plotly 了解有限。

【讨论】:

  • 谢谢。这也是我的第一个想法。我尝试将输出更改为虚拟元素(例如,将文本分配给 div),但随后情节根本没有更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-03
  • 2011-07-04
  • 2019-06-07
相关资源
最近更新 更多