Closed Jeremy38100 closed 11 months ago
Hey @Jeremy38100,
Thank you for submitting this issue!
Can you provide a minimal reproducible example? I'll gladly have a look at it :)
Cheers, Jeroen
Sure :
import numpy as np
import plotly.graph_objects as go
from dash import Dash, Input, Output, callback_context, dcc, html, no_update
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
app = Dash(__name__)
fig: FigureResampler = FigureResampler()
app.layout = html.Div(
[
html.H1("plotly-resampler global variable", style={"textAlign": "center"}),
html.Button("plot chart", id="plot-button", n_clicks=0),
html.Button("add Annotation", id="plot-annotation", n_clicks=0),
html.Hr(),
dcc.Graph(id="graph-id"),
TraceUpdater(id="trace-updater", gdID="graph-id"),
]
)
@app.callback(
Output("graph-id", "figure"),
Input("plot-button", "n_clicks"),
Input("plot-annotation", "n_clicks"),
prevent_initial_call=True,
)
def plot_graph(n_clicks, n_clicks_annotations):
ctx = callback_context
if len(ctx.triggered) and "plot-button" in ctx.triggered[0]["prop_id"]:
global fig
fig.replace(go.Figure())
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
if len(ctx.triggered) and "plot-annotation" in ctx.triggered[0]["prop_id"]:
x_annotation = 1.5*1000*1000
fig = fig.add_annotation(x=x_annotation, y=80*1000,
text="Text annotation with arrow",
showarrow=True,
arrowhead=1)
return fig
else:
return no_update
fig.register_update_graph_callback(
app=app, graph_id="graph-id", trace_updater_id="trace-updater"
)
if __name__ == "__main__":
app.run_server(debug=True, port=9023)
Zoom closely arour 1.5M then click add annotation, it resets zoom level.
Thx for providing the code snippet, I was able to reproduce the issue.
The underlying problem is that when outputting a "new" figure in the callback - the client-side figure is updated (with the current view being resetted). To circumvent this issue we can trigger the resampling by letting the callback take relayoutData
as input and then manually update the graph - I updated the example for this :arrow_down:
@app.callback(
Output("graph-id", "figure"),
Input("graph-id", "relayoutData"),
Input("plot-button", "n_clicks"),
Input("plot-annotation", "n_clicks"),
prevent_initial_call=True,
)
def plot_graph(relayout_data, n_clicks, n_clicks_annotations):
ctx = callback_context
global fig
if len(ctx.triggered) and "plot-button" in ctx.triggered[0]["prop_id"]:
fig.replace(go.Figure())
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
if len(ctx.triggered) and "plot-annotation" in ctx.triggered[0]["prop_id"]:
x_annotation = 1.5*1000*1000
fig.add_annotation(x=x_annotation, y=80*1000,
text="Text annotation with arrow",
showarrow=True,
arrowhead=1)
## Added the code below to update the graph for the current view
if relayout_data:
update_data = fig.construct_update_data(relayout_data)
if not fig._is_no_update(update_data): # when there is an update
with fig.batch_update():
# First update the layout (first item of update_data)
fig.layout.update(update_data[0])
# Then update the data
for updated_trace in update_data[1:]:
trace_idx = updated_trace.pop("index")
fig.data[trace_idx].update(updated_trace)
return fig
else:
return no_update
My 5 cents regarding this hacky fix:
I'm interested in hearing what you think of this @Jeremy38100
Will close this issue for now as we did not hear back from you @Jeremy38100
Hello,
I implemented a system to add an anotation / shape when the user clicks on the chart and it's dynamically adding / removing a plotly annotations / shape (I'm using dash). I use the
fig.add_annotation
method for instance.The issue is when the user is zoomed in the chart, the add_annotation method reset the sampling as it is at top zoom level and i have to manually click on zoom then unzoom to get the current sampling when the user clicked on the chart