How to get figure object from dcc.Graph instead of dict

I need to update a figure on some event (button click in my example). This depends on the state of the figure that I want to update.
For this I’d need to return the full figure object from dcc.Graph instead of a dictionary (???).
Specifically I want to add traces to the figure
What I tried:

  • fig.add_trace(...). Doesn’t work since fig is a dict and not a figure object

  • fig['data'].append(...) since fig[‘data’] contains an immutable tuple and not a list

  • old = fig['data'], new=old + tuple([go.Scatter(...)]), fig['data'] = new Doesn’t work for reasons I don’t understand

minimal example to show what I mean:

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

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
df = pd.read_csv('https://gist.githubusercontent.com/chriddyp/5d1ea79569ed194d432e56108a04d188/raw/a9f9e8076b837d541398e999dcbac2b2826a81f8/gdp-life-exp-2007.csv')


fig = px.scatter(df, x="gdp per capita", y="life expectancy",
                     size="population", color="continent", hover_name="country",
                     log_x=True, size_max=60)


app.layout = html.Div([
    dcc.Graph(id='graph', figure=fig),
    html.Button('click', id='button')])


@app.callback(Output('graph', 'figure'),
              Input('button', 'n_click'),
              State('graph', 'figure'))
def update_fig(button_pressed, fig):
    # stuff i want to do with fig:
    # fig.add_trace(...)
    # doesn't work
    return fig


if __name__ == '__main__':
    app.run_server(debug=True)

Hi @luggie

One way to do this is to add:

import plotly.graph_objects as go

Then in your callback start with:

fig=go.Figure(fig)

This will convert the dictionary to a figure object and you can update it using things like fig.add_trace()

And this is probably just a typo, but:

Input('button', 'n_click'),

should be:

Input('button', 'n_clicks'),

I hope this helps :slight_smile:

2 Likes

Thanks! That was what I was looking for

Hi, I got the same issue and your response is really useful !
However, it doesnt work with subplots. If I do it with a subplots object, the fig = go.Figure(subplots_fig) works but then I cannot add traces because the fig is a Figure() object and cannot receive ‘row’ and ‘col’ arguments.

Here is the error I get :

Exception: In order to reference traces by row and column, you must first use plotly.tools.make_subplots to create the figure with a subplot grid.

My question is : when the subplots havebeen converted to a dict by dash dcc.Graph, is it possible to then convert the dict to a subplots figure again in a callback?

Hey @Andy, could you create an example for that? I’m not sure I understand the issue.

Hi, Thanks for your answer, sorry for not being clear.

Here is an example of my problem:

from dash import Dash, dcc, html, Input, Output, State, callback, dash_table, ctx, no_update
import plotly
import plotly.graph_objects as go
from plotly.subplots import make_subplots

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = Dash(__name__, external_stylesheets=external_stylesheets)

#creation of the subplots figure
subplot_fig = make_subplots(rows=2, cols=1,
        specs=[[{}],
                [{}]],
        subplot_titles=("First plot","Second Plot"))
subplot_fig.add_trace(go.Scatter(x=[1,2,3,4], y=[1,1,1,1]), row=1, col=1)
subplot_fig.add_trace(go.Scatter(x=[1,2,3,4], y=[2,2,2,2]), row=2, col=1)

# Creation of the app layout
app.layout = html.Div(children=[
    html.Div(className='row', children = dcc.Graph(id = 'graph', figure = subplot_fig)),
    html.Div(className='row', children = html.Button(id = 'button', children='Add trace'))
])

# Callback to add a trace to a 
@callback(
    Output('graph', 'figure'),
    Input('button', 'n_clicks'),
    State('graph', 'figure'),
    prevent_initial_call=True
)
def add_trace(n_clicks, figure):
    fig = go.Figure(figure)
    fig.add_trace(go.Scatter(x=[1,2,3,4], y=[3,3,3,3]), row=1, col=1) # Here is the problem, fig is no more a subplot.
    return fig


if __name__ == '__main__':
    app.run(debug=True)

The problem is that the callback return the figure as a dictionary and go.Figure can convert it to a figure, but in the case of a subplot, the information on row and col is lost and it is then impossible to use “add_trace(…, row=1, col=1)” for instance. You can try the code and you will get the following error : “Exception: In order to reference traces by row and column, you must first use plotly.tools.make_subplots to create the figure with a subplot grid.

I have found that I can recreate the subplot and just add the previous trace datas this way :

fig = make_subplots(rows=3, cols=2,
                    fig = make_subplots(rows=2, cols=1,
                    specs=[[{}],
                             [{}]],
                    subplot_titles=("First plot", "Second Plot"))
            previous_data = go.Figure(figure).data
            for trace in previous_data:
                fig.add_trace(trace)

However this is quite annoying to do in the context of my app. Therefore I wondered if there was a simpler way to just retrieve the subplot object.

Hey @Andy I understand. It’s correct that mmake_subplots “just” creates traces.

I answered this question not too long ago concerning a pretty similar problem:

1 Like

Thanks a lot @AIMPED !! That Patch() function is really something I was looking for. :smile:

However it makes my wonder something else.
Would it be possible to use the Patch() function to add or remove a plot from a subplot?
Like having 2 rows and 1 column, and after a callback update the plot to have 3 rows and 1 column, without creating a new subplot figure with this configuration.

(Idk if I should do another topic to ask this as I have already given context here)

There is also this hack to convert a go.Figure() into something recognisable as subplots.
It works in this MWE, but no guarantees it will work in all situations:

import plotly.graph_objects as go
from plotly.subplots import make_subplots

fig1 = make_subplots(rows=2, cols=1,subplot_titles=("First plot","Second Plot"))
fig1.add_trace(go.Scatter(x=[1,2,3,4], y=[1,1,1,1]), row=1, col=1)
fig1.add_trace(go.Scatter(x=[1,2,3,4], y=[2,2,2,2]), row=2, col=1)

d1 = fig1.to_dict()
fig2 = go.Figure(d1)

subplot_skeleton = make_subplots(rows=2, cols=1)
fig2._grid_ref = subplot_skeleton._grid_ref
fig2.add_trace(go.Scatter(x=[1,2,3,4], y=[1,1.7,1.3,0.8]), row=2, col=1)
fig2.show()
1 Like

Hey @Andy, you could do what you mention, I guess.

Since you are using dash anyways, why don’t you dump the subplots completely and create a separate dcc.Graph() for each trace of the subplot figure and arrange the graphs in your layout accordingly?

This would make the updating much easier. I personally never liked the subplots because of the complexity of it.

Thank you very much !

It is a really useful approach, I will try it with my own app.

Hi @AIMPED ,
I totally agree with you, I would also like not to use subplots, it is way more annoying.
However, the problem is I want the user to be able to construct its own plot by adding multiple traces, tables, changing the titles, legends… and to be able to save the plot constructed either in .png or .html

Therefore I need to use subplots so that I just have to do write_html() in the end to save the figure. I have thought of displaying the elements as multiple dcc.Graph() and only in the end reconstruct everything as one subplot figure to save it, but I cannot find an efficient way do to this as we cannot combine go.Figure() object into a subplot.

If you have other inputs I would be glad to hear it, in any case, thank you very much for your help !

I’m wondering (since the code below also works) if the underlying issue here is simply that fig.to_dict() does not save the _grid_ref property. I can’t see any issues in either plotly.py or plotly.js relating to this, and I also don’t know if there’s some deeper reason why it can’t.

fig1 = make_subplots(rows=2, cols=1,subplot_titles=("First plot","Second Plot"))
fig1.add_trace(go.Scatter(x=[1,2,3,4], y=[1,1,1,1]), row=1, col=1)
fig1.add_trace(go.Scatter(x=[1,2,3,4], y=[2,2,2,2]), row=2, col=1)

d1 = fig1.to_dict()
d1["_grid_ref"] = fig1._grid_ref

fig2 = go.Figure(d1)
fig2.add_trace(go.Scatter(x=[1,2,3,4], y=[1,1.7,1.3,0.8]), row=2, col=1)
fig2.show()