Hey all,
I recently started using the Plotly Resampler as I am working with data with over 38 million datapoints and, in my initial tests, it works amazingly well! As I was trying to implement more and more functionality into my Dash app, however, I could not figure out how to update the y data (multiplying it by a scaling factor) of my traces using dash.Patch() as I had prior. More specifically, while the initial changing of the figure using dash.Patch() worked well, when I pan or zoom, the figure reverts back to the original. I also tried updating the hf_data of the stored FigureResampler object with no success. The reduced code example I adapted from a code example provided on the Plotly Resampler git below demonstrates the problem.
Is there an official way to update the data of a figure used with the FigureResampler that I am missing, or do I have to fully re-initialize the respective figure every time? Interestingly, I found that when I update other βdataβ components such as patched_fig['data'][1]['marker']['color'] = 'black'
, the changes do persist after a relayout event such as panning/zooming (see the commented-out line).
Thank you in advance for any input!
import numpy as np
import plotly.graph_objects as go
from dash import Input, Output, State, callback_context, dcc, html, no_update, Patch
from dash_extensions.enrich import DashProxy, Serverside, ServersideOutputTransform
from plotly_resampler import FigureResampler
from plotly_resampler.aggregation import MinMaxLTTB
# Data that will be used for the plotly-resampler figures
x = np.arange(2_000_000)
noisy_sin = (3 + np.sin(x / 200) + np.random.randn(len(x)) / 10) * x / 1_000
# --------------------------------------Globals ---------------------------------------
app = DashProxy(__name__, transforms=[ServersideOutputTransform()])
app.layout = html.Div(
[
html.H1("plotly-resampler + dash-extensions", style={"textAlign": "center"}),
html.Button("plot chart", id="plot-button", n_clicks=0),
html.Button("change data", id="change-button", n_clicks=0),
html.Hr(),
# The graph object - which we will empower with plotly-resampler
dcc.Graph(id="graph-id"),
# Note: we also add a dcc.Store component, which will be used to link the
# server side cached FigureResampler object
dcc.Loading(dcc.Store(id="store")),
]
)
# ------------------------------------ DASH logic -------------------------------------
# The callback used to construct and store the FigureResampler on the serverside
@app.callback(
[Output("graph-id", "figure"), Output("store", "data")],
Input("plot-button", "n_clicks"),
prevent_initial_call=True,
)
def plot_graph(n_clicks):
ctx = callback_context
if len(ctx.triggered) and "plot-button" in ctx.triggered[0]["prop_id"]:
fig: FigureResampler = FigureResampler(
go.Figure(), default_downsampler=MinMaxLTTB(parallel=True)
)
# Figure construction logic
fig.add_trace(go.Scattergl(name="log"), hf_x=x, hf_y=noisy_sin * 0.9999995**x)
fig.add_trace(go.Scattergl(name="exp"), hf_x=x, hf_y=noisy_sin * 1.000002**x)
return fig, Serverside(fig)
else:
return no_update
# The plotly-resampler callback to update the graph after a relayout event (= zoom/pan)
# As we use the figure again as output, we need to set: allow_duplicate=True
@app.callback(
Output("graph-id", "figure", allow_duplicate=True),
Input("graph-id", "relayoutData"),
State("store", "data"), # The server side cached FigureResampler per session
prevent_initial_call=True,
memoize=True,
)
def update_fig(relayoutdata: dict, fig: FigureResampler):
if fig is None:
return no_update
return fig.construct_update_data_patch(relayoutdata)
# # Callback for button to change trace data
@app.callback(
Output("graph-id", "figure", allow_duplicate=True), # Output("store", "data", allow_duplicate=True)],
Input("change-button", "n_clicks"),
# State("store", "data"),
prevent_initial_call=True,
)
def change_fig(n_clicks): # , fig):
ctx = callback_context
if len(ctx.triggered) and "change-button" in ctx.triggered[0]["prop_id"]:
patched_fig = Patch()
patched_fig['data'][1]['y'] *=2
# fig.hf_data[1]['y'] *= 2
# patched_fig['data'][1]['marker']['color'] = 'black'
return patched_fig # , Serverside(fig)
else:
return no_update
# --------------------------------- Running the app ---------------------------------
if __name__ == "__main__":
app.run_server(debug=True, port=9024)