使用dash构建可实时刷新图标的数据可视化网页,两种可用的回调模式:
第一种:
方法是直接替换整个figure,示例代码
参考来源:
官方文档参考:
实例1:community.plotly.com/t/3d-surfac…
import random
import plotly.graph_objects as go
import numpy as np
import pandas as pd
import dash_html_components as html
import dash_core_components as dcc
import dash
import plotly.graph_objects as go
import dash
from dash.dependencies import Output, Input
import dash_core_components as dcc
import dash_html_components as html
import plotly
import random
# Read data from a csv
z_data = pd.read_csv(
'https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv')
import numpy as np
mu, sigma = 0, 50
# creating a noise with the same dimension as the dataset (2,2)
noise = np.random.normal(mu, sigma, z_data.shape)
s1 = go.Surface(z=((z_data+noise)/(z_data+noise).max().max()).values)
noise = np.random.normal(mu, sigma, z_data.shape)
s2 = go.Surface(z=((z_data+noise)/(z_data+noise).max().max()).values)
noise = np.random.normal(mu, sigma, z_data.shape)
s3 = go.Surface(z=((z_data+noise)/(z_data+noise).max().max()).values)
noise = np.random.normal(mu, sigma, z_data.shape)
s4 = go.Surface(z=((z_data+noise)/(z_data+noise).max().max()).values)
noise = np.random.normal(mu, sigma, z_data.shape)
s5 = go.Surface(z=((z_data+noise)/(z_data+noise).max().max()).values)
frames = [{"data":[s1]},{"data":[s2]},{"data":[s3]},{"data":[s4]},{"data":[s5]}]
fig = go.Figure(data=[s1],layout=go.Layout(
xaxis=dict(range=[0, 16], autorange=False),
yaxis=dict(range=[0, 13], autorange=False),
hovermode="closest",
title="Start Title"
))
app = dash.Dash(__name__)
app.layout = html.Div(
[
dcc.Graph(id='live-graph'),
dcc.Interval(
id='graph-update',
interval=100
),
]
)
@app.callback(Output('live-graph', 'figure'),
[Input('graph-update', 'n_intervals')])
def update_graph_scatter(input_data):
if input_data is None:
return dash.no_update
return frames[input_data % 5]
if __name__ == '__main__':
app.run_server(debug=True)
实例2:stackoverflow.com/questions/4…
import dash
from dash.dependencies import Output, Event
import dash_core_components as dcc
import dash_html_components as html
from random import random
import plotly
app = dash.Dash(__name__)
app.layout = html.Div(
html.Div([
dcc.Graph(id='live-update-graph-scatter', animate=True),
dcc.Graph(id='live-update-graph-bar'),
dcc.Interval(
id='interval-component',
interval=1*1000
)
])
)
@app.callback(Output('live-update-graph-scatter', 'figure'),
events=[Event('interval-component', 'interval')])
def update_graph_scatter():
traces = list()
for t in range(2):
traces.append(plotly.graph_objs.Scatter(
x=[1, 2, 3, 4, 5],
y=[(t + 1) * random() for i in range(5)],
name='Scatter {}'.format(t),
mode= 'lines+markers'
))
return {'data': traces}
@app.callback(Output('live-update-graph-bar', 'figure'),
events=[Event('interval-component', 'interval')])
def update_graph_bar():
traces = list()
for t in range(2):
traces.append(plotly.graph_objs.Bar(
x=[1, 2, 3, 4, 5],
y=[(t + 1) * random() for i in range(5)],
name='Bar {}'.format(t)
))
layout = plotly.graph_objs.Layout(
barmode='group'
)
return {'data': traces, 'layout': layout}
if __name__ == '__main__':
app.run_server(debug=True)
第二种:
方法是仅更新图标中的数据部分,利用figuire中的extendData这个API
参考来源:
实例1:stackoverflow.com/questions/6…
题主使用更新整张figuire的办法,效率过低
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)
得到建议使用extendData这个API更新,答主同时给出了在服务端和客户端处理的代码
@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
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)
实例2:stackoverflow.com/questions/6…
简单记一个示例的部分代码
@app.callback(
Output('indicators', 'extendData'),
Input('live_interval', 'n_intervals')
)
def update_rates(n_intervals):
#gather data into dict from dataframe
new_data = candles[['time', 'high', 'low', 'open', 'close']].iloc[-1]
new_data['time'] = new_data['time'] + delta*n_intervals
new_data = new_data.to_dict()
#data rename
new_data['x'] = new_data['time']
del new_data['time']
#data reshape
for k in new_data.keys():
new_data[k] = [[new_data[k]]]
return new_data, [1]
实例3:stackoverflow.com/questions/6…
详细讲解
Updating traces of a Graph component without generating a new graph object can be achieved via the extendData property. Here is a small example that appends data each second,
import dash
import dash_html_components as html
import dash_core_components as dcc
import numpy as np
from dash.dependencies import Input, Output
# Example data (a circle).
resolution = 20
t = np.linspace(0, np.pi * 2, resolution)
x, y = np.cos(t), np.sin(t)
# Example app.
figure = dict(data=[{'x': [], 'y': []}], layout=dict(xaxis=dict(range=[-1, 1]), yaxis=dict(range=[-1, 1])))
app = dash.Dash(__name__, update_title=None) # remove "Updating..." from title
app.layout = html.Div([dcc.Graph(id='graph', figure=figure), dcc.Interval(id="interval")])
@app.callback(Output('graph', 'extendData'), [Input('interval', 'n_intervals')])
def update_data(n_intervals):
index = n_intervals % resolution
# tuple is (dict of new data, target trace index, number of points to keep)
return dict(x=[[x[index]]], y=[[y[index]]]), [0], 10
if __name__ == '__main__':
app.run_server()
Depending of the network connection between client and server (at each update, a request is exchanged between client and server), this approach works up to a refresh rate of around 1s.
If you need a higher refresh rate, i would suggest doing the graph update using a client side callback. Adopting the previous example, the code would be along the lines of
import dash
import dash_html_components as html
import dash_core_components as dcc
import numpy as np
from dash.dependencies import Input, Output, State
# Example data (a circle).
resolution = 1000
t = np.linspace(0, np.pi * 2, resolution)
x, y = np.cos(t), np.sin(t)
# Example app.
figure = dict(data=[{'x': [], 'y': []}], layout=dict(xaxis=dict(range=[-1, 1]), yaxis=dict(range=[-1, 1])))
app = dash.Dash(__name__, update_title=None) # remove "Updating..." from title
app.layout = html.Div([
dcc.Graph(id='graph', figure=dict(figure)), dcc.Interval(id="interval", interval=25),
dcc.Store(id='offset', data=0), dcc.Store(id='store', data=dict(x=x, y=y, resolution=resolution)),
])
app.clientside_callback(
"""
function (n_intervals, data, offset) {
offset = offset % data.x.length;
const end = Math.min((offset + 10), data.x.length);
return [[{x: [data.x.slice(offset, end)], y: [data.y.slice(offset, end)]}, [0], 500], end]
}
""",
[Output('graph', 'extendData'), Output('offset', 'data')],
[Input('interval', 'n_intervals')], [State('store', 'data'), State('offset', 'data')]
)
if __name__ == '__main__':
app.run_server()
Client side updates should be fast enough to achieve a smooth update. The gif below shows the above example running with 25 ms refresh rate,
Keep in mind that a client side update is only possible if the data is already present client side, i.e. another mechanism is needed to fetch the data from the server. A possible data flow could be
Use a slow Interval component (e.g. 2 s) to trigger a (normal) callback that fetches a chunk of data from the source and places it in a Store component
Use a fast Interval component (e.g. 25 ms) to trigger a client side callback that streams data from the Store component to the Graph component