I try to synchronize zoom and pan between two graphs in a dashboard (dash + plotly). I obtain strange behavior when I zoom on a graph, the second graph does not update. I need to zoom on the second graph to make both graphs update but not with the same zoom nor the same location on the graphs. Furthermore the shapes of the two graphs change.
Below is the code I am in. I do not see I am doing wrong.
import os from dash import Dash, html, dcc, Input, Output, State import plotly.express as px import numpy as np import rasterio as rio app2 = Dash(__name__) data_folder = r'.data' store = {} for filename in os.listdir(data_folder): if os.path.isfile(os.path.join(data_folder, filename)): band_name = filename.replace('.', '_').split(sep='_')[-2] with rio.open(os.path.join(data_folder, filename)) as dataset: nb_band = dataset.count if nb_band == 1: data = dataset.read(1) else: data = dataset.read(tuple(range(1, nb_band + 1))) if band_name == 'triband': data = np.swapaxes(data, 2, 0) data = np.swapaxes(data, 0, 1) store[band_name] = data.astype(float) else: store[f'B{band_name}'] = data.astype(float) fig1 = px.imshow(store['triband']) fig1.update_xaxes(showticklabels=False, showgrid=False, zeroline=False) fig1.update_yaxes(showticklabels=False, showgrid=False, zeroline=False) fig1.update_layout( margin=dict(l=0, r=0, t=0, b=0), plot_bgcolor='rgba(0, 0, 0, 0)', paper_bgcolor='rgba(0, 0, 0, 0)', ) # Application structure and content app2.layout = html.Div(className='main', children=[ html.H1(children='Hello Dash', style={'padding': 10}), html.Div(children=[ html.Div(children=[ dcc.Graph( id='graph1', figure=fig1, responsive=True ) ], style={'padding': 5, 'flex': 1}), html.Div(children=[ dcc.Graph( id='graph2', figure=fig1, responsive=True ) ], style={'padding': 5, 'flex': 1}) ], style={'display': 'flex', 'flex-direction': 'row'}), ]) @app2.callback(Output('graph2', 'figure'), Input('graph1', 'relayoutData'), State('graph2', 'figure')) def graph_event1(select_data, fig): if select_data is not None: try: fig['layout']['xaxis']['range'] = [select_data['xaxis.range[0]'], select_data['xaxis.range[1]']], fig['layout']['yaxis']['range'] = [select_data['yaxis.range[0]'], select_data['yaxis.range[1]']] except KeyError: pass return fig @app2.callback(Output('graph1', 'figure'), Input('graph2', 'relayoutData'), State('graph1', 'figure')) def graph_event2(select_data, fig): if select_data is not None: try: fig['layout']['xaxis']['range'] = [select_data['xaxis.range[0]'], select_data['xaxis.range[1]']], fig['layout']['yaxis']['range'] = [select_data['yaxis.range[0]'], select_data['yaxis.range[1]']] except KeyError: pass return fig if __name__ == '__main__': app2.run_server(debug=True)
Advertisement
Answer
I found a solution : rather than creating two graphs, I created a graph with several subplots and force zoom and pan between subplots.
fig = make_subplots(rows=1, cols=3, shared_xaxes=True, shared_yaxes=True) fig.add_trace( px.imshow(store['triband']).data[0], row=1, col=1 ) fig.add_trace( px.imshow(index_store['NDVI']).data[0], row=1, col=2 ) fig.add_trace( px.imshow(np.where(index_store['NDVI'] >= np.median(index_store['NDVI']), 0.8 * np.max(index_store['NDVI']), 0.8 * np.min(index_store['NDVI'])) ).data[0], row=1, col=3 ) fig.update_xaxes(matches='x', showticklabels=False, showgrid=False, zeroline=False) fig.update_yaxes(matches='y', showticklabels=False, showgrid=False, zeroline=False)