Is it possible to update just `layout`, not whole `figure` of Graph in callback?

I’m trying to make a choropleth with ~40k polygons that gets recolored depending on the value of a dropdown menu.
I want to the menu choice to trigger a server-side recomputation of the choropleth values, colors, and legend, then send only that info to the client and not the large amount of geodata again.
Any suggestions?

I haven’t tied with Plotly Choropleth, but it should be possible with the GeJSON component in Dash leaflet. If you color the polygons based on data from the hideout prop (via a custom JS function), you can update this prop only without changing the data prop (which holds the polygons).

Hi @chriddyp , I also wanted to update plots in a Dash applications, and I was tempted to use figure updates as documented at Creating and Updating Figures | Python | Plotly. Do we agree that at the moment I can’t use that in the context of a dash application? Thanks!

I think it depends on what you want to update. Can you elaborate on what you are trying to do, e.g. what properties you are modifying?

Hi, I am reviving this thread a bit. For my usecase, I want to synchronize the x-axis is various plotly figures. Currently, I am using a RangeSlider to define the range of the x-axis. However, as pointed out by previous members, the whole figure is being updated and sent back to the client which takes time. If I understand it correctly, updating the x-axis of a plotly graph only requires an update on a specific parameter in the layout.

I would like to ask for this case:

  1. How do we locate this parameter.
  2. For multipage app, how do we call the clientside_callback?

Thank you!

Hello @matthewdml,

Welcome to the community!

Currently, there is no way to update only the layout from the server without sending the whole figure.

For a clientside callback, to update the layout it would look something like this:

app.clientside_callback(
“”” function (d, fig) {
     if (d) {
         newFig = JSON.parse(JSON.stringify(fig))
         newFig.layout = newLayout
         return newFig
     }
     return window.dash_clientside.no_update
}”””,
Output(‘target’,’figure’),
Input(‘trigger’,’value’),
State(‘target’,’figure’))
2 Likes

Hi @matthewdml ,

@jinnyzor was faster than me, but nevertheless I wanted to share my MRE (basically I’m trying to get better in JS :rofl:)

import dash
from dash import dcc, Input, Output, State
import dash_bootstrap_components as dbc
import plotly.graph_objects as go

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = dbc.Container([
    dbc.Col(
        dbc.Row([
            dcc.Graph(
                id='graph',
                figure=go.Figure(
                    data=go.Scatter(
                        x=[*range(100)],
                        y=[*range(100)],
                        mode='markers'
                    ),
                    layout={'yaxis_range': [0, 20]}
                )
            )
        ]),
    ),
    dbc.Row([
        dbc.Col(
            dcc.RangeSlider(
                id='slider',
                value=[0, 20],
                min=0,
                max=100,
                step=1,
                marks={0: 'min', 100: 'max'},
                className='slider'
            ),
        ),
    ]),

])


# @app.callback(
#     Output('graph', 'figure'),
#     Input('slider', 'value'),
#     State('graph', 'figure'),
#     prevent_initial_call=True
# )
# def update(values, figure):
#     figure.get('layout').get('yaxis').update({'range': values})
#     return figure


app.clientside_callback(
    """
    function(values, figure) {
        newFig = JSON.parse(JSON.stringify(figure))
        newFig['layout']['yaxis']['range']=values
        return newFig
    }
    """,
    Output('graph', 'figure'),
    Input('slider', 'value'),
    State('graph', 'figure'),
    prevent_initial_call=True
)

if __name__ == "__main__":
    app.run(debug=True, port=8055)
1 Like

It is possible using the OperatorTransform. This example should get you started,

2 Likes

@jinnyzor & @AIMPED

Thank you for the code snippets. It took a while but I was finally able to sync several figures in my app. Client-side callback is way faster than the server-side callback that I was previously using.

For future reader who wants to sync several figures, here below is the callback that I eventually used. I am using a slider to assign the range of the xaxis.

clientside_callback(
    """
    function(value, data, figure) {
        updated_fig = JSON.parse(JSON.stringify(figure))
        updated_fig['layout']['xaxis]['range'] = [data[value[0]], data[value[1]]]
        updated_fig['layout']['xaxis]['autorange'] = false
        return updated_fig             
    }
    """,
    Output('fig-id', "figure"),
    Input("date-slider", "value"),
    [State("clientside-store-snapshots", "data"), State('fig-id', "figure")],
    prevent_initial_call=True,
)
2 Likes

Hey @matthewdml,

What happens if you just pust updated_fig['layout']['xaxis]['range'] = data? I believe it is already in list format. :slight_smile:

Glad you got something working.

1 Like