You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I am using plotly-resampler to try to "animate" a time-series plot (imagine streaming data, but the data already exists).
Because my dataset is so large, using plotly-resampler makes a lot of sense, since it can plot the entire time-series plot in seconds compared to normal plotly, which takes minutes.
I was thinking that one cool way to simulate/animate a time-series plot would be to automatically pan through a plot, by changing the xaxis range with a dash interval. This would allow someone to pause, zoom out, zoom in, pan forward in time, pan back in time for a given time-series (the panning forward/backward emphasize the need to plot the entire time-series first vs iteratively plotting patches).
I am able to automatically pan through a chart; however, I am running into two problems.
When calling fig.construct_update_data to force the traceupdater to run (as described here), it doesn't appear to be updating all of the traces in all subplots (it looks like it misses the last one). Im not sure if this is a bug or not. It looks like the first element in the returned list from construct_update_data is a layout dict and the next values int the list are the data updates; however, the last subplot's data is missing from the returned data.
I am unable to get the traceupdater to force update, given the new figure's xaxis ranges (or at least, I dont think it is updating). If anyone has any ideas, that would be appreciated. I have tried returning a relayout dict directly from the update_graph callback below and also using the familiar update_fig.
Currently, I am working off of this example:
from plotly import graph_objects as go
import numpy as np
from dash import Input, Output, State, dcc, html, no_update
import dash_bootstrap_components as dbc
from dash_extensions.enrich import (
DashProxy,
ServersideOutput,
ServersideOutputTransform,
)
from trace_updater import TraceUpdater
from plotly_resampler import FigureResampler
x = np.arange(2_000_000)
noisy_sin = (3 + np.sin(x / 200) + np.random.randn(len(x)) / 10) * x / 1_000
noisy_sin_2 = (3 + np.sin(x / 200) + np.random.randn(len(x)) / 10) * x / 1_000
fig = FigureResampler(go.Figure().set_subplots(2, 1, shared_xaxes=True, vertical_spacing=0.02))
fig = fig.add_trace(go.Scattergl(name="orig"), hf_x=x, hf_y=noisy_sin, row=1, col=1)
fig = fig.add_trace(go.Scattergl(name="orig2"), hf_x=x, hf_y=noisy_sin_2, row=2, col=1)
app = DashProxy(
__name__,
transforms=[ServersideOutputTransform()],
external_stylesheets=[dbc.themes.BOOTSTRAP],
)
app.layout = html.Div(
[
dcc.Graph(id="live-update-graph"),
html.Button("Animate chart", id="plot-button", n_clicks=0),
dcc.Store(id='store'),
TraceUpdater(id="trace-updater", gdID="live-update-graph"),
dcc.Interval(
id='interval-component',
interval=1000, # in milliseconds
n_intervals=0,
max_intervals=1000,
disabled=True
)
]
)
@app.callback(
[Output('live-update-graph', 'figure'),
ServersideOutput("store", "data")
],
[State("store", "data"),
State("live-update-graph", "relayoutData"),
Input('interval-component', 'n_intervals'),
Input('plot-button', 'n_clicks'),
],
prevent_initial_call=True,
)
def update_graph(curr_fig, relayout, n, n_clicks):
n_iterations=1000
if curr_fig is None:
curr_fig = fig
idx1, idx2 = 0, len(x) // n_iterations * (n+1)
else:
idx1, idx2 = len(x) // n_iterations * (n-1), len(x) // n_iterations * (n+1)
curr_range = x[idx1:idx2]
# this updates all x axes for traceupdater
for i in range(2):
if i == 0:
relayout['xaxis.range[0]'] = int(curr_range[0])
relayout['xaxis.range[1]'] = int(curr_range[-1])
else:
relayout[f'xaxis{i}.range[0]'] = int(curr_range[0])
relayout[f'xaxis{i}.range[1]'] = int(curr_range[-1])
# this updates the figure itself
curr_fig = curr_fig.update_xaxes({'range': [curr_range[0], curr_range[-1]]})
# force the traceupdater to update
for data in curr_fig.construct_update_data(relayout)[1:]:
curr_fig.data[data.pop("index")].update(data, overwrite=True)
return curr_fig, curr_fig
@app.callback(
Output("interval-component", "disabled"),
[Input("plot-button", "n_clicks")],
[State("interval-component", "disabled")],
prevent_initial_call=True,
)
def toggle_interval(n, disabled):
"""Turn on/off interval on button press"""
if n:
return not disabled
return disabled
@app.callback(
Output("trace-updater", "updateData"),
Input("live-update-graph", "relayoutData"),
State("store", "data"),
prevent_initial_call=True,
)
def update_fig(relayoutdata, fig):
if fig is None:
return no_update
return fig.construct_update_data(relayoutdata)
if __name__ == "__main__":
app.run_server(debug=True)
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hi,
I am using plotly-resampler to try to "animate" a time-series plot (imagine streaming data, but the data already exists).
Because my dataset is so large, using plotly-resampler makes a lot of sense, since it can plot the entire time-series plot in seconds compared to normal plotly, which takes minutes.
I was thinking that one cool way to simulate/animate a time-series plot would be to automatically pan through a plot, by changing the xaxis range with a dash interval. This would allow someone to pause, zoom out, zoom in, pan forward in time, pan back in time for a given time-series (the panning forward/backward emphasize the need to plot the entire time-series first vs iteratively plotting patches).
I am able to automatically pan through a chart; however, I am running into two problems.
fig.construct_update_data
to force the traceupdater to run (as described here), it doesn't appear to be updating all of the traces in all subplots (it looks like it misses the last one). Im not sure if this is a bug or not. It looks like the first element in the returned list fromconstruct_update_data
is a layout dict and the next values int the list are the data updates; however, the last subplot's data is missing from the returned data.update_graph
callback below and also using the familiarupdate_fig
.Currently, I am working off of this example:
Beta Was this translation helpful? Give feedback.
All reactions