1

Good afternoon everyone! :)

What is the problem. I have 4 plots and the challenge is to keep them scaling in sync (i.e. if you zoom in on 1 plot, then the rest of the plots are zoomed in as well, etc.).

Now all I have done is that I can zoom in on one particular graph ('graph1') and the rest are zoomed in with it. This is what it looks like.

@app.callback(
              Output('graph1', 'figure'), Output('graph2', 'figure'), Output('graph3', 'figure'),Output('graph4', 'figure'),
              Input('graph1', 'relayoutData'),
              State('graph1', 'figure'), State('graph2', 'figure'), State('graph3', 'figure'), State('graph4', 'figure')
              )
def zoom_event(relayout_data, *figures):
    outputs = []
    for fig in figures:
        try:

            fig['layout']["xaxis"]["range"] = [relayout_data['xaxis.range[0]'], relayout_data['xaxis.range[1]']]
            fig['layout']["xaxis"]["autorange"] = False
        except (KeyError, TypeError) as e:
            fig['layout']["xaxis"]["autorange"] = True

        outputs.append(fig)
    return outputs

If you try to do something that looks like synchronizing charts, you will get countless errors. I tried to make 2 figures with subplots, and even one figure with 4 subplots, but all in vain.

One of the latter was that outputs accept more inputs than there are outputs. But I haven’t figured it out specifically in this error. I tried to do so.

@app.callback(
Output('graph1', 'figure'), Output('graph2', 'figure'), Output('graph3', 'figure'), Output('graph4', 'figure'),
Input('graph1', 'relayoutData'), Input('graph2', 'relayoutData'), Input('graph3', 'relayoutData'), Input('graph4', 'relayoutData'), 
State('graph1', 'figure'), State('graph2', 'figure'), State('graph3', 'figure'), State('graph4', 'figure')
)

Thank you if you dare to help.

For searches, I recommend going here Dygraphs - synchronous zooming

Here the participant solved a rather similar problem (but it is written in javascript, I will be glad to hear suggestions on how this can be combined)

And also here How to get zoom level in time series data as callback input in Dash

IMPERIUS
  • 13
  • 3
  • Similar question to this one: [Zoom on both graphs via highlighting selection in Dash](https://stackoverflow.com/questions/59606410/zoom-on-both-graphs-via-highlighting-selection-in-dash/67804648#67804648). The main point of the given answer is to read the zoom information applied to one graph and update the others via the range property of update_axes. – bohnpessatti Jul 07 '22 at 13:38

3 Answers3

1

I fought with this for a while and this was the solution I eventually came up with.

In my use case I'm dynamically adding graphs by

dcc.Graph({'type': 'graph', 'index': index_counter})

Linking the zoom behavior

@app.callback(
    Output({'type': 'graph', 'index': ALL}, 'relayoutData'),
    Output({'type': 'graph', 'index': ALL}, 'figure'),
    Input({'type': 'graph', 'index': ALL}, 'relayoutData'),
    State({'type': 'graph', 'index': ALL}, 'figure'))
def LinkedZoom(relayout_data, figure_states):
    unique_data = None
    for data in relayout_data:
      if relayout_data.count(data) == 1:
        unique_data = data
    if unique_data:
      for figure_state in figure_states:
        if unique_data.get('xaxis.autorange'):
          figure_state['layout']['xaxis']['autorange'] = True
          figure_state['layout']['yaxis']['autorange'] = True
        else:
          figure_state['layout']['xaxis']['range'] = [
              unique_data['xaxis.range[0]'], unique_data['xaxis.range[1]']]
          figure_state['layout']['xaxis']['autorange'] = False
          figure_state['layout']['yaxis']['range'] = [
              unique_data['yaxis.range[0]'], unique_data['yaxis.range[1]']]
          figure_state['layout']['yaxis']['autorange'] = False
      return [unique_data] * len(relayout_data), figure_states
    return relayout_data, figure_states

Reference commit: https://github.com/djhedges/exit_speed/commit/863efbf00250c79663dd460f6ab2bd54e051b9d7

djhedges
  • 86
  • 4
  • You can actually just create an empty component (https://dash.plotly.com/dash-html-components/pre), let's call it "relayout-data" and callback #1 associated with the component, which will work if one plot increases, then the callback writes data to "relayout-data", then you need to create callback #2, which will already be responsible for simultaneously changing the scales of four graphs, and which will take the data necessary for this from "relayout-data" using data sharing. But if your suggested solution also works, then thank you very much! I am sure you will help many people in this way. – IMPERIUS Jan 26 '22 at 23:41
  • You can read about data sharing here (https://dash.plotly.com/sharing-data-between-callbacks) – IMPERIUS Jan 26 '22 at 23:42
0

The problem has been resolved.

Unfortunately, I probably cannot show the final code because I will be running it in prod and it may be a trade secret.

But for those who faced a similar problem, I advise you to look at this section.

https://dash.plotly.com/sharing-data-between-callbacks

I wish you success!

IMPERIUS
  • 13
  • 3
0

@djhedges

There are some conditions that also trigger this callback, so I add more condition statements as an improvement of your code :)

for data in relayout_data:  
    if data is not None and relayout_data.count(data) == 1:  
        if 'xaxis.autorange' in list(data.keys()) or 'xaxis.autorange' in list(data.keys()):
            unique_data = data