I’m plotting a time series data, including Scatter and Heatmaps, and I’m adding highlights to the graphs.
The way I’m doing it is by getting the selection data and adding shapes, to select multiple ranges.
However, when the plots are huge, and especially for the Heatmaps, it takes a super long time (using go.Heatmap), just to render the shape. Like, first it takes a long time for the selection data to even reach my callback, and after the callback is finished, the graph takes a long time again to render the selection/highlight shape.
One conundrum is that I update multiple other graphs with the same selection shape, so that you can see your selected area across lots of graphs. And I persist the shape when the user wants to add other highlighted selections.
Is there a better way to do this? Like maybe an overlay that is separate from the graph but uses the same x axis values? Would need to make it not interfere with things like hover modes, zooming, etc.
The adding shapes to the graph is ideal in so many ways (don’t have to coordinate relayoutData between an overlay and the graph in zooming, for instance)
But it really is expensive to render in some cases. Like the heat map case.
Hi @MatthewWaller , I imagine the selection data takes a while to get sent to the callback because it includes all of the points/data that’s contained within the selection. If you’re just interested in the range of the selection square then maybe you can achieve the same thing by drawing shapes with the mouse and then listening to the relayoutData to get the coordinates of that shape? E.g like this:
import dash
from dash import dcc, html, Input, Output
import plotly.graph_objects as go
import json
app = dash.Dash(__name__)
# Your Plotly Figure
fig = go.Figure()
text = "Click and drag here <br> to draw a rectangle <br><br> or select another shape <br>in the modebar"
fig.add_annotation(
x=0.5,
y=0.5,
text=text,
xref="paper",
yref="paper",
showarrow=False,
font_size=20
)
fig.add_shape(editable=True, x0=-1, x1=0, y0=2, y1=3, xref='x', yref='y')
fig.update_layout(dragmode='drawrect')
config={'modeBarButtonsToAdd': ['drawline', 'drawopenpath', 'drawclosedpath', 'drawcircle', 'drawrect', 'eraseshape']}
# Dash App Layout
app.layout = html.Div([
dcc.Graph(id='graph-with-shape', figure=fig, config=config),
html.Div(id='relayout-data')
])
# Callback to Display Relayout Data
@app.callback(Output('relayout-data', 'children'),
Input('graph-with-shape', 'relayoutData'))
def display_relayout_data(relayout_data):
if relayout_data is not None:
return html.Div(json.dumps(relayout_data))
else:
return "No relayout data yet."
if __name__ == '__main__':
app.run_server(debug=True)
I’m not 100% sure that I’ve fully understood your use case though so let me know if that’s the case and feel free to post a minimal example that demonstrates the issue.
Hey thanks for this. I think the problem is me serializing the entire heat map and including it as State AND putting it back together as the fig output, so that it can include shapes.
Here is a minimal example
import dash
from dash import dcc, html, Input, Output, State, no_update
import plotly.graph_objects as go
import numpy as np
app = dash.Dash(__name__)
data_array = np.random.rand(512, 3889)
# Your Plotly Figure
heatmap = go.Heatmap(
z=data_array,
)
fig = go.Figure(heatmap)
fig.add_shape(editable=True, x0=-1, x1=0, y0=2, y1=3, xref='x', yref='y')
fig.update_layout(dragmode='drawrect')
config={'modeBarButtonsToAdd': ['drawline', 'drawopenpath', 'drawclosedpath', 'drawcircle', 'drawrect', 'eraseshape']}
# Dash App Layout
app.layout = html.Div([
dcc.Graph(id='graph-with-shape', figure=fig, config=config),
html.Div(id='relayout-data')
])
# Callback to Display Relayout Data
@app.callback(Output('graph-with-shape', 'figure'),
Input('graph-with-shape', 'relayoutData'),
Input('graph-with-shape', 'selectedData'),
State('graph-with-shape', 'figure'),
)
def display_relayout_data(relayout_data,
selecteData,
figure
):
if relayout_data is not None:
fig = go.Figure(figure)
# Add shape corresponding to the relayoutData of the mouse point
# And return the new figure with the data.
return fig
else:
return no_update
if __name__ == '__main__':
app.run_server(debug=True)
You can see how long the dash app is in Updating state in the tab.
You can probably use partial property updates to update the figures so that your app doesn’t need to send the figure across the network. Have you looked into that already?