How to update only parts of a Plotly figure in a Dash callback?

Hello all. I’m trying to update a Plotly graph in a Dash app. I’d like to update just the figure’s data while preserving the layout. Specifically for my application, I’d like to update the figure data while preserving the axes types (linear, log, etc), but I’ve been unable to do that.

Working minimal code:

#!/usr/env/python3
# -*- coding: utf-8 -*-

import pandas as pd
import plotly.express as px
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

df = pd.DataFrame([[1, 2, 3], [5, 4, 5], [5, 6, 3], [7, 8, 5]],
                  columns=['a', 'b', 'c'])
fig_px = px.line(df, x='a', y='b', color='c')

# building dash app
header = [ html.H1(children='Example') ]
fig = [ dcc.Graph(id='fig', figure=fig_px) ]

opt_radio = [
    dcc.RadioItems(
        id='opt',
        options=[ { 'label': v, 'value': v } for v in range(0,5) ],
        value=3
    )
]

dbg = [
    html.Label('Button Debug:'),
    html.Div(id='dbg_lbl', children=''),
    html.Button('Print Y axis attributes', id='dbg_btn', n_clicks=0)
]

app = dash.Dash('example')
app.title = 'Example'
app.layout = html.Div(children=header + fig + opt_radio + dbg)

# callbacks
@app.callback(
    Output(component_id='fig', component_property='figure'),
    [Input(component_id='opt', component_property='value')]
)
def create_fig(opt):
    # toy dataframe
    df = pd.DataFrame([[opt, 2, 3], [5, 4, 5], [5, 6, 3], [7, 8, 5]],
                      columns=['a', 'b', 'c'])

    # figure creation
    fig = px.line(df, x='a', y='b', color='c', log_y=True)

    log_linear = [{
        'y': 1, 'x': 0,
        'xanchor': 'left', 'yanchor': 'top',
        'type': 'dropdown',
        'buttons': [
            {'label': 'log',
             'method': 'relayout',
             'args': ['yaxis', {'type': 'log'}]
             },
            {'label': 'linear',
             'method': 'relayout',
             'args': ['yaxis', {'type': 'linear'}]
             }
        ]
    }, {
        'y': 0, 'x': 1,
        'xanchor': 'right', 'yanchor': 'bottom',
        'type': 'dropdown', 'direction': 'left',
        'buttons': [
            {'label': 'log',
             'method': 'relayout',
             'args': ['xaxis', {'type': 'log'}]
             },
            {'label': 'linear',
             'method': 'relayout',
             'args': ['xaxis', {'type': 'linear'}]
             }
        ]
    }]

    fig.update_layout(updatemenus=log_linear)
    return fig

@app.callback(
    Output(component_id='dbg_lbl', component_property='children'),
    [Input(component_id='dbg_btn', component_property='n_clicks')]
)
def button_press(n_clicks):
    fig = app.layout.children[1].figure
    return str(fig['layout']['yaxis'])

app.run_server(debug=True)

In this app, if I

  1. change the axis type (say, change the Y axis type from log to linear) through the updatemenu button; then
  2. change the radio button underneath the plot,

the data changes (as it should), but its axes also change back to the default type (in this case, to a loglog plot). I’d like them to stay the same (in this example, I’d like the Y axis type to stay linear as the callback changes the figure’s data).

In this case, simply reaching outside the callback to read the yaxis_type attribute doesn’t work, because updatemenu doesn’t seem to change the figure’s attributes (i.e. the figure yaxis is the same, regardless of the actual figure, as exemplified by the button below the radio buttons).

Can it be done? Do I have to refactor my app somehow? Any help is appreciated!

Versions:

>>> dash.__version__
'1.12.0'
>>> dcc.__version__
'1.10.0'
>>> html.__version__
'1.0.3'
>>> plotly.__version__
'4.8.1'
>>> sys.version
'3.8.2 | packaged by conda-forge | (default, Apr 24 2020, 07:34:03) [MSC v.1916 64 bit (AMD64)]'

Cheers!

Hi @lmnice a solution here would be to change the axis type dropdowns to be Dash dcc.Dropdown objects instead of plotly dropdowns so that you can use their values in a State argument of your callback. The only way you can modify only the traces of a figure is with extendData but this is only for the case where you add new data to existing traces.

Hey @Emmanuelle, thanks for replying. That’s a great idea, thanks! I hadn’t thought of it. It looks like a bit of work to position the dcc.Dropdown s (sort of) neatly along the axes, but it looks like there’s not a lot that I can do besides that.

Cheers!

You can use client side callbacks to partially update figures

Hey @sjtrny, thanks for providing this great solution to my problem!

I’ll try it next if I can’t position @Emmanuelle 's dcc.Dropdowns consistently.

Cheers!