Updating a graph using a callback

I am creating a subplot of a maximum of 4 graphs. They are vertically stacked. I would like the user to have the ability to manually change the yaxis ranges of the individual graphs. Here is a callback of the figure creation and then I was making a separate callback to update the graph but I get an error this way.

Any thoughts of how to do this easier or how to change a part of a graph in a callback?

layout = dbc.Container([
          dbc.Row([
              dbc.Button(
                  'Create a View',
                  id='diag-view-btn',
                  n_clicks=0,
                  style={'width': '125px'},
                  color='secondary'
              ),
              dbc.Modal([
                  dbc.ModalHeader(dbc.ModalTitle('Settings'), close_button=True),
                  dbc.ModalBody([
                      html.Div([
                          html.Center([
                              html.H5('Select a Plant'),
                              dcc.Dropdown(
                                  id='diag-plant-drop',
                                  options=np.sort(all_with_areas_names_df.Area_0.unique()),
                                  placeholder='Plant',
                                  style={'width': '300px', 'text-align': 'left'},
                                  clearable=True,
                              )
                          ],
                              style={'margin-top': '10px', 'margin-bottom': '25px'}
                          ),
                      ]),
                      html.Div([
                          dbc.Row([
                              html.Center([
                                  html.H5('Select up to 4 points to display'),
                              ],
                                  style={'margin-bottom': '10px'}
                              )
                          ]),
                          dbc.Row([
                              dbc.Input(
                                  id='search-points',
                                  placeholder='Search',
                                  type='text',
                                  style={'width': '30%', 'margin-left': '5%'}
                              ),
                          ],
                              style={'margin-bottom': '10px'}
                          ),
                          dbc.Row([
                              dbc.Col([
                                  dash_table.DataTable(
                                      id={
                                          'type': 'table',
                                          'index': 'modal-point-table-left'
                                      },
                                      data=[],
                                      columns=[{'id': 'points', 'name': 'Points'}],
                                      style_cell={'text-align': 'left',
                                                  'border': '1px solid rgb(200,200,200)',
                                                  'maxHeight': '8px', 'padding-left': '5px',
                                                  'font-family': 'Arial'},
                                      style_table={'height': '400px', 'overflowY': 'auto',
                                                   'border': '1px solid grey'},
                                      style_data={
                                          'font-size': '12px',
                                          'whiteSpace': 'normal',
                                          'height': '10px',
                                          'lineHeight': '10px',
                                          'color': 'black',
                                          'backgroundColor': 'white',
                                      },
                                  )
                              ],
                                  width=5
                              ),
                              dbc.Col([
                                  html.Div([
                                      dbc.Button([
                                          html.I(
                                              className="fa-solid fa-arrow-right",
                                              style={'font-size': '24px'}
                                          ),
                                      ],
                                          color='secondary',
                                          outline=True,
                                          style={'width': '70px'},
                                          id='add-modal-select',
                                          disabled=False,
                                      ),
                                      dbc.Button([
                                          html.I(
                                              className="fa-solid fa-arrow-left",
                                              style={'font-size': '24px'}
                                          ),
                                      ],
                                          color='secondary',
                                          outline=True,
                                          style={'width': '70px'},
                                          id='remove-modal-select',
                                          disabled=False
                                      )
                                  ],
                                      style={'display': 'flex', 'flex-direction': 'column',
                                             'gap': '100px', 'justify-content': 'space-around',
                                             'align-items': 'center', 'height': '400px'}
                                  )
                              ],
                                  width=2
                              ),
                              dbc.Col([
                                  dash_table.DataTable(
                                      id={
                                          'type': 'table',
                                          'index': 'modal-point-table-right'
                                      },
                                      data=[],
                                      columns=[{'id': 'Selections', 'name': 'Selections'}],
                                      style_cell={'text-align': 'left',
                                                  'border': '1px solid rgb(200,200,200)',
                                                  'maxHeight': '8px', 'padding-left': '5px',
                                                  'font-family': 'Arial'},
                                      style_table={'height': '400px', 'border': '1px solid grey'},
                                      style_data={
                                          'font-size': '12px',
                                          'whiteSpace': 'normal',
                                          'height': '10px',
                                          'lineHeight': '10px',
                                          'color': 'black',
                                          'backgroundColor': 'white',
                                      },
                                  )
                              ],
                                  width=5
                              ),
                          ]),
                          dbc.Row([
                              html.Div(
                                  html.Center(
                                      html.H5('Select a Date Range')
                                  )
                              ),
                              html.Div([
                                  dmc.DatePicker(
                                      id='modal-start-date',
                                      maxDate=dt.today(),
                                      style={'width': '200px'},
                                      clearable=True,
                                      inputFormat='MM-DD-YYYY'
                                  ),
                                  html.P('to'),
                                  dmc.DatePicker(
                                      id='modal-end-date',
                                      maxDate=dt.today(),
                                      style={'width': '200px'},
                                      clearable=True,
                                      inputFormat='MM-DD-YYYY',
                                      value=dt.today()
                                  )
                              ],
                                  style={'display': 'flex', 'justify-content': 'center',
                                         'gap': '20px', 'align-items': 'baseline'}
                              )
                          ],
                              style={'margin-top': '35px'}
                          )
                      ],
                          id='wizard-div',
                      )
                  ],
                      style={'height': '750px'}
                  ),
                  dbc.ModalFooter(
                      dbc.Button(
                          'Create View',
                          id='modal-create-btn',
                          n_clicks=0,
                          style={'width': '125px'},
                          color='secondary'
                      )
                  )
              ],
                  id='view-modal',
                  centered=True,
                  is_open=False,
                  size='xl',
              ),
              dbc.Button(
                  'Save as HTML Plot',
                  id='subplot-html-save',
                  n_clicks=0,
                  style={'display': 'none'},
                  color='success'
              )
          ],
              style={'margin-top': '15px', 'display': 'flex', 'justify-content': 'space-between'}
          ),
          dbc.Row([
              dbc.Col([
                  html.Div([
                    dcc.Graph(
                        figure={},
                        id='subplot',
                        style={'display': 'none'}
                    )
                  ],
                      id='graph-container',
                      style={'height': '700px'}
                  )
              ],
                  width=10
              ),
              dbc.Col([
                  html.Div(
                      id='y-axis-col',
                      children=[],
                      style={'height': '700px'}
                  )
              ],
                  width=2
              ),
          ],
              style={'display': 'flex'}
          )
      ],
          fluid=True,
      )


@app.callback(
    Output('subplot', 'figure'),
    Output('y-axis-col', 'children'),
    Output('subplot', 'style'),
    Output('subplot-html-save', 'style'),
    Input('modal-create-btn', 'n_clicks'),
    State('diag-plant-drop', 'value'),
    State({'type': 'table', 'index': 'modal-point-table-right'}, 'data'),
    State('modal-start-date', 'value'),
    State('modal-end-date', 'value'),
    Input('diag-view-btn', 'n_clicks'),
    State('store', 'data'),
    State('y-axis-col', 'children'),
    prevent_initial_call=True
)
def create_subplot(n1, plant, data, start, end, n2, main_data, yaxis_col):
    all_df = pd.DataFrame(main_data['df'])

    def create_y_axis_range_change(n, total):
        div = html.Div([
            html.Div([
                html.Center(html.P('Change Y-Axis Range'))
            ]),
            html.Div([
                dbc.Input(
                    id=f'yaxis-min-{n}',
                    type='number',
                    style={'width': '80px'},
                    placeholder='Min'
                ),
                dbc.Input(
                    id=f'yaxis-max-{n}',
                    type='number',
                    style={'width': '80px'},
                    placeholder='Max'
                )
            ],
                style={'display': 'flex', 'justify-content': 'center', 'gap': '15px'}
            )
        ],
            style={'display': 'flex', 'flex-direction': 'column', 'justify-content': 'center', 'height': f'{100/total}%'}
        )

        return div

    i = 0
    if ctx.triggered_id == 'modal-create-btn':
        titles = []
        j = 0
        while j < len(data):
            titles.append(f'{plant} - {data[j]["Selections"]}')
            j += 1
        fig = make_subplots(rows=len(data), cols=1, subplot_titles=titles, shared_xaxes=True, vertical_spacing=0.05)

        while i < len(data):
            mask = (all_df['Area_0'] == plant) & (all_df['Short_Description'] == data[i]['Selections'])
            spec_df = all_df[mask]
            query_str = spec_df['Query_Filter_str'].iloc[0]
            graph_data_df = rk.get_rounds_data(query_str)
            date_mask = (graph_data_df['Timestamp'] >= start) & (graph_data_df['Timestamp'] <= end)
            date_cut_df = graph_data_df[date_mask]
            fig.add_trace(go.Scatter(
                x=date_cut_df['Timestamp'],
                y=date_cut_df['Value'],
                mode='lines+markers'
            ), row=(i + 1), col=1)

            l1_min = spec_df['L1_Min'].iloc[0]
            l1_max = spec_df['L1_Max'].iloc[0]
            l2_min = spec_df['L2_Min'].iloc[0]
            l2_max = spec_df['L2_Max'].iloc[0]
            norm_min = spec_df['Norm_Min'].iloc[0]
            norm_max = spec_df['Norm_Max'].iloc[0]

            y = date_cut_df['Value']

            if l2_max > y.max():
                l2_max = y.max()
                if l1_max > y.max():
                    l1_max = y.max()
                    if norm_max > y.max():
                        norm_max = y.max()

            if l2_min < y.min():
                l2_min = y.min()
                if l1_min < y.min():
                    l1_min = y.min()
                    if norm_min < y.min():
                        norm_min = y.min()

            if not math.isnan(l2_min) and not math.isnan(l2_max):
                fig.add_hrect(y0=y.min(), y1=y.max(), fillcolor='#ffcccc', opacity=1, line_width=0, layer='below',
                              row=(i + 1), col=1)

            if math.isnan(norm_min) and math.isnan(norm_max):
                norm_min = 0
                norm_max = 0
            if math.isnan(l1_min) and math.isnan(l1_max):
                l1_min = 0
                l1_max = 0
            if math.isnan(l2_min) and math.isnan(l2_max):
                l2_min = 0
                l2_max = 0

            fig.add_hrect(y0=l2_min, y1=l2_max, fillcolor='#ffe1c2', opacity=1, line_width=0, layer='below',
                          row=(i + 1), col=1)
            fig.add_hrect(y0=l1_min, y1=l1_max, fillcolor='#fffdb8', opacity=1, line_width=0, layer='below',
                          row=(i + 1), col=1)
            fig.add_hrect(y0=norm_min, y1=norm_max, fillcolor='#ccffc2', opacity=1, line_width=0, layer='below',
                          row=(i + 1), col=1)

            fig.update_yaxes(title=f"<span style='font-size: 12px'>{spec_df['Unit'].iloc[0]}</span>", row=(i + 1), col=1)
            yaxis_col.append(create_y_axis_range_change((i + 1), len(data)))

            i += 1

        fig.update_xaxes(range=[start, end])
        fig.update_layout(template='seaborn', height=700, showlegend=False,
                          margin=dict(autoexpand=False,
                                      b=30,
                                      r=30,
                                      l=50,
                                      t=30),
                          xaxis_rangeslider_visible=False
                          )
        return fig, yaxis_col, {'display': 'flex'}, {'width': '175px', 'margin-right': '2%'}

    return {}, [], {'display': 'none'}, {'display': 'none'}


# @app.callback(
#
# )

@app.callback(
    Output('subplot', 'figure'),
    Input('yaxis-min-1', 'value'),
    Input('yaxis-max-1', 'value'),
    Input('yaxis-min-2', 'value'),
    Input('yaxis-max-2', 'value'),
    Input('yaxis-min-3', 'value'),
    Input('yaxis-max-3', 'value'),
    Input('yaxis-min-4', 'value'),
    Input('yaxis-max-4', 'value'),
    State('subplot', 'figure'),
    prevent_initial_call=True
)
def update_yaxes(ymin1, ymax1, ymin2, ymax2, ymin3, ymax3, ymin4, ymax4, fig):
    if ymin1 and ymax1:
        fig.update_yaxes(range=[ymin1, ymax1], row=1, col=1)
    if ymin1 and not ymax1:
        fig.update_yaxes(range=[ymin1, None], row=1, col=1)
    if ymax1 and not ymin1:
        fig.update_yaxes(range=[None, ymax1], row=1, col=1)

    return fig

Update: I’ve changed the callbacks to this. There is a slight change in the first callback for Outputs and creating a button to trigger the change:

@app.callback(
    Output('graph-container', 'children'),
    Output('y-axis-col', 'children'),
    Output('subplot-html-save', 'style'),
    Input('modal-create-btn', 'n_clicks'),
    State('diag-plant-drop', 'value'),
    State({'type': 'table', 'index': 'modal-point-table-right'}, 'data'),
    State('modal-start-date', 'value'),
    State('modal-end-date', 'value'),
    Input('diag-view-btn', 'n_clicks'),
    State('store', 'data'),
    State('y-axis-col', 'children'),
    prevent_initial_call=True
)
def create_subplot(n1, plant, data, start, end, n2, main_data, yaxis_col):
    all_df = pd.DataFrame(main_data['df'])

    def create_y_axis_range_change(n, total):
        div = html.Div([
            html.Div([
                html.Center(html.P('Change Y-Axis Range'))
            ]),
            html.Div([
                dbc.Input(
                    id=f'yaxis-min-{n}',
                    type='number',
                    style={'width': '80px'},
                    placeholder='Min'
                ),
                dbc.Input(
                    id=f'yaxis-max-{n}',
                    type='number',
                    style={'width': '80px'},
                    placeholder='Max'
                )
            ],
                style={'display': 'flex', 'justify-content': 'center', 'gap': '15px'}
            ),
            html.Div([
                html.Center(dbc.Button('Change', id=f'yaxis-{n}', n_clicks=0))
            ])
        ],
            style={'display': 'flex', 'flex-direction': 'column', 'justify-content': 'center',
                   'height': f'{100 / total}%'}
        )

        return div

    i = 0
    if ctx.triggered_id == 'modal-create-btn':
        titles = []
        j = 0
        while j < len(data):
            titles.append(f'{plant} - {data[j]["Selections"]}')
            j += 1
        fig = make_subplots(rows=len(data), cols=1, subplot_titles=titles, shared_xaxes=True, vertical_spacing=0.05)

        while i < len(data):
            mask = (all_df['Area_0'] == plant) & (all_df['Short_Description'] == data[i]['Selections'])
            spec_df = all_df[mask]
            query_str = spec_df['Query_Filter_str'].iloc[0]
            graph_data_df = rk.get_rounds_data(query_str)
            date_mask = (graph_data_df['Timestamp'] >= start) & (graph_data_df['Timestamp'] <= end)
            date_cut_df = graph_data_df[date_mask]
            fig.add_trace(go.Scatter(
                x=date_cut_df['Timestamp'],
                y=date_cut_df['Value'],
                mode='lines+markers',
            ), row=(i + 1), col=1)

            l1_min = spec_df['L1_Min'].iloc[0]
            l1_max = spec_df['L1_Max'].iloc[0]
            l2_min = spec_df['L2_Min'].iloc[0]
            l2_max = spec_df['L2_Max'].iloc[0]
            norm_min = spec_df['Norm_Min'].iloc[0]
            norm_max = spec_df['Norm_Max'].iloc[0]

            y = date_cut_df['Value']

            if l2_max > y.max():
                l2_max = y.max()
                if l1_max > y.max():
                    l1_max = y.max()
                    if norm_max > y.max():
                        norm_max = y.max()

            if l2_min < y.min():
                l2_min = y.min()
                if l1_min < y.min():
                    l1_min = y.min()
                    if norm_min < y.min():
                        norm_min = y.min()

            if not math.isnan(l2_min) and not math.isnan(l2_max):
                fig.add_hrect(y0=y.min(), y1=y.max(), fillcolor='#ffcccc', opacity=1, line_width=0, layer='below',
                              row=(i + 1), col=1)

            if math.isnan(norm_min) and math.isnan(norm_max):
                norm_min = 0
                norm_max = 0
            if math.isnan(l1_min) and math.isnan(l1_max):
                l1_min = 0
                l1_max = 0
            if math.isnan(l2_min) and math.isnan(l2_max):
                l2_min = 0
                l2_max = 0

            fig.add_hrect(y0=l2_min, y1=l2_max, fillcolor='#ffe1c2', opacity=1, line_width=0, layer='below',
                          row=(i + 1), col=1)
            fig.add_hrect(y0=l1_min, y1=l1_max, fillcolor='#fffdb8', opacity=1, line_width=0, layer='below',
                          row=(i + 1), col=1)
            fig.add_hrect(y0=norm_min, y1=norm_max, fillcolor='#ccffc2', opacity=1, line_width=0, layer='below',
                          row=(i + 1), col=1)

            fig.update_yaxes(title=f"<span style='font-size: 12px'>{spec_df['Unit'].iloc[0]}</span>", row=(i + 1),
                             col=1)
            yaxis_col.append(create_y_axis_range_change((i + 1), len(data)))

            i += 1

        fig.update_xaxes(range=[start, end])
        fig.update_layout(template='seaborn', height=700, showlegend=False,
                          margin=dict(autoexpand=False,
                                      b=30,
                                      r=30,
                                      l=50,
                                      t=30),
                          xaxis_rangeslider_visible=False
                          )

        fig_dict = fig.to_dict()
        return dcc.Graph(figure=fig, id='subplot'), yaxis_col, {'width': '175px', 'margin-right': '2%'}

    return {}, [],  {'display': 'none'}


@app.callback(
    Output('subplot', 'figure'),
    State('yaxis-min-1', 'value'),
    State('yaxis-max-1', 'value'),
    Input('yaxis-1', 'n_clicks'),
    State('subplot', 'figure'),
    prevent_initial_call=True
)
def update_yaxes(ymin1, ymax1, n1, fig):
    if ctx.triggered_id == 'yaxis-1':
        fig['layout']['yaxis']['range'][0] = ymin1
        fig['layout']['yaxis']['range'][1] = ymax1
        return fig
    else:
        return fig

When I’m debugging, I can see that it’s changing the range values, however it won’t return and instead jumps to this:

if NoUpdate.is_no_update(output_value):
    raise PreventUpdate

Thoughts as to why?