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

Hi, I am trying to set range of xaxis of a graph in callback function. From my understanding, the only way to do is by returning the figure property:


@app.callback(
    Output(component_id='graph_id', component_property='figure'),
    [Input(component_id='btn_custom_x_range', component_property='n_clicks')]
)
def set_custom_range(clicks):
    return {
        "data": data,
        "layout":
            {"xaxis": {
                "range": [1, 2]
            }
            ...
            }
        ...
    }

My concern is, that this way I am sending the whole data object back to the client again. I would like to avoid this, as the data collection is quite huge.

Is there a way, how to update the xaxis.range without resending all the data?

5 Likes

Hi,

Maybe this will helpā€¦
Jimmybow wrote a custom component you can install, to do just this. You can find it at
https://github.com/jimmybow/mydcc look under ā€œ3. mydcc.Relayoutā€

2 Likes

This is correct. Right now, there is not a way to update a nested attribute of a property.

Ideally, Iā€™d like to support this more generally through two different solutions:

  1. Allow the developer to update nested properties in callbacks with something like
@app.callback(Output('my-graph', 'figure.layout.range'))
  1. Provide a way to tell which input was fired and use that in conjunction with Plotly.react to provide incremental updates to the figure, see https://github.com/plotly/plotly.js/pull/2341#issuecomment-364257543

For now, you can use jimmybowā€™s dcc package or youā€™ll just have to return the entire figure each time.

5 Likes

Very interesting topic!
I have quite a few issues with slow response in Dash when updating minor figure layout elements or using events, probably caused by re-loading the entire data on each callback.

I have looked a bit into ā€œbuild your own dash componentā€, thinking that some of these tasks could be transferred ā€œbackā€ to the faster JS side (such as updating a color, height, get hover information etc) without invoking a ā€œcostlyā€ Dash callback (and leave those for more data related, Python-based stuff).

Now I understand (sorry, Iā€™m new to React as well as relatively new to JSā€¦) that I could build sort of custom-made graph components using Plotly.js and Plotly.newPlot(); (as done here: https://academy.plot.ly/react/3-with-plotly/) and work for example with Plotly.restyle or Plotly.relayout (as shown here: https://plot.ly/javascript/plotlyjs-function-reference/#plotlyrestyle).

However - here comes Plotly.react (as @chriddyp posted: https://github.com/plotly/plotly.js/pull/2341#issuecomment-364257543), which sounds to be faster and may work better/cleaner(?) together with React. BUT on the main plotly.react page it says that ā€œThis component currently creates a new plot every time the input changesā€ (https://github.com/plotly/react-plotly.js/)

So, my question is - what do you think is the way forward? After all Plotly.js is the base library, which ultimately should allow most customisation - yet Plotly.react seems really attractive. In terms of building a custom-made Dash component, what would be the most sensible approach?

Thanks for shedding some light onto my current Plotly confusionā€¦!

1 Like

I am also interested in what will be the way forward here. Right now in my app i have a figure where I am animating a moving vertical line along the time axis. I am returning the entire figure each time, which works ok as long as the figure stays simple. However as figure get more complex the animation delay gets longer and longer.

Also mydcc.relayout works well for a simple example, but when I tried to use it in my case i get into some sort of infinite loop of callbacks, because I also i have callbacks that listen to chart relayout data and then update the chart to zoom in/out. Which leaves me not sure if the issue is with my callbacks or with mydcc.relayout and I donā€™t know to debug it.

this also seems ideal to me:

Allow the developer to update nested properties in callbacks with something like
@app.callback(Output(ā€˜my-graphā€™, ā€˜figure.layout.rangeā€™))

For now it seems like I am stuck with ā€œreturning the entire figure each timeā€, until solution 1 or 2 is created (not soon?) or I learn more React/JS and custom components and can make some sort of custom hack.

1 Like

Is there any update on this issue?

Your first soluction would be perfect.

Thanks!

2 Likes

No updates. This is actually quite a bit project and weā€™d likely need commercial sponsorship of the feature in order to move it forward

Sorry I can not help with that. I donĀ“t belong to any company.

1 Like

I believe this is now possible to some extent thanks to State callback.

@app.callback(Output('graph', 'figure'),
             [Input('graph', 'relayoutData')], # this triggers the event
             [State('graph', 'figure')])
def graph_event(select_data,  fig):
    fig['layout'] = [YOUR STUFF]
    return fig
    
3 Likes

This is sorely needed for large plots e.g. choropleths that have megabytes of data to be overlaid on the map, which at the present need to be sent repeatedly when a layout change such as zoom level is made to the figure via callback.

After facing a similar issue it seems like client side callbacks are the best way to step around this issue!

1 Like

Would love to see your solution @sjtrny

Simplified Example

app.py

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, ClientsideFunction

app = dash.Dash(__name__)

fig_data = {
    "data": [{"type": "bar", "x": [1, 2, 3], "y": [1, 3, 2]}],
    "layout": {"title": {"text": ""}}
}

app.layout = html.Div([
    dcc.Store("fig-data", data=fig_data),
    dcc.Dropdown(
        id="city",
        options=[ {'label': z, "value": z} for z in ["Sydney", "Montreal"] ],
        value="Sydney"
    ),
    dcc.Graph(id="graph")
])

app.clientside_callback(
    ClientsideFunction("clientside", "figure"),
    Output(component_id="graph", component_property="figure"),
    [Input("fig-data", "data"), Input("city", "value")],
)

app.run_server(host="0.0.0.0", debug=True)

/assets/clientside.js

if (!window.dash_clientside) {
     window.dash_clientside = {}
 }

window.dash_clientside.clientside = {

    figure: function (fig_dict, title) {

        if (!fig_dict) {
            throw "Figure data not loaded, aborting update."
        }

        // Copy the fig_data so we can modify it
        // Is this required? Not sure if fig_data is passed by reference or value
        fig_dict_copy = {...fig_dict};

        fig_dict_copy["layout"]["title"] = title;

        return fig_dict_copy

    },

}

Choropleth Example

This example is a bit more involved so I put it in a repl.it

1 Like

I am still new to Dash Plotly. I do not know any Javascript. The solution proposed by tracek works (without external script).

This is what I have in my script. I can update the title of the graph. Hopefully this can help anyone who is struggling with this aspect.

@app.callback(
    Output(component_id='my-div', component_property='figure'),
    [Input(component_id='variable', component_property='value')],
    [State('my-div', 'figure')]
)
def update_output_div(input_value, fig):
    fig['layout'] = {"title": input_value}
    return fig
4 Likes

Hi,
Iā€™m wondering how @tracek solution improve performance compared to reloading at each call back. Does changing the state only reload the changed attribute or it reloads the whole figure?
Thank you

2 Likes

Any Update here ? Iā€™m trying to update the annotation text of my Graph , is that possible?

2 Likes

The best workaround right now is to use a clientside callback to add or change the smaller pieces of the figure. See the example here: https://dash.plotly.com/clientside-callbacks

1 Like

Just a minor addition to your great example: You have to make a deep copy of the figure object. Otherwise you might run into issues when adding elements to an existing list, e.g. such as traces.

I was able to completely decouple any figure modification (e.g. adding descriptive shapes, traces, annotations etc.) while keeping the original (large) figure data in a dcc.Store object and not resend it on every update (of the auxiliary data).

1 Like

Would you mind sharing code on your example? I am facing a similar issue.

1 Like

Yes, can you show us your example, please?