plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.45k stars 2.07k forks source link

plotly figure subplot grid lost after converting back from JSON #2445

Open jamaa opened 1 year ago

jamaa commented 1 year ago

Describe your context

dash==2.8.1
dash-bootstrap-components==1.4.0
dash-core-components==2.0.0
dash-extensions==0.1.13
dash-html-components==2.0.0
dash-leaflet==0.1.23
dash-table==5.0.0
plotly==5.13.1

Describe the bug I am trying to update a plotly figure containing subplots within a dash callback but get the error "Exception: Use plotly.tools.make_subplots to create a subplot grid".

The Figure object is passed as a State to the callback function. Because the figure is passed to the callback function as a dict, I use fig =pio.from_json(json.dumps(fig)) to convert it back to a proper Figure object. However, after conversion, this Figure object does not seem to know about its subplots anymore, raising the following exception whenever I try to reference anything subplot-related, e.g. fig.print_grid() or fig.add_trace([...], row=2, col=1):

Exception: Use plotly.tools.make_subplots to create a subplot grid

Minimal code to reproduce:

fig = plotly.subplots.make_subplots(
    rows=3, 
    cols=1,
)
fig.print_grid()     # this works fine
fig2 = pio.from_json(fig.to_json()) # convert to json and back
fig2.print_grid()   # this raises an Exception

Full example app:

import json
from dash import Dash, html, dcc, Input, Output, State, callback_context
import plotly.graph_objects as go
import plotly.io as pio
import plotly.subplots

app = Dash(
    name=__name__,
    title="Test app",
)

# create the initial empty figure
fig = plotly.subplots.make_subplots(
    rows=3,
    cols=1,
)

# create the layout
app.layout = html.Div(children=[
    html.Button("Test me", id="button"),
    dcc.Graph(id="graph", figure=fig)
])

@app.callback(
    Output("graph", "figure"), 
    Input("button", "n_clicks"), 
    State("graph", "figure"),
    prevent_initial_call=True
)
def update_graph(n_clicks, figure):
    # update the figure
    figure = pio.from_json(json.dumps(figure))

    figure.print_grid() # this raises an Exception

    figure.add_trace(
        go.Scatter(
            x=[0, 1, 2, 3],
            y=[10, 20, 10, 0],
            name=f"line",
        ),
        row=2,
        col=1,
    ) # this also raises an Exception

    return figure

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

Expected behavior I expect to be able to access the subplots of the figure in the callback method.

alexcjohnson commented 1 year ago

Thanks @jamaa - certainly makes sense to want to do this, but I think the problem is make_subplots stores some extra info in the Python objects that don’t correspond to anything in plotly.js, so are not reflected in the JSON we send to the browser. @nicolaskruchten is there anything you can suggest to put that info back, or any way we could potentially infer what it must have been?

jamaa commented 1 year ago

is there any other way to add traces to subplots of an existing figure passed by a callback?

alexcjohnson commented 1 year ago

Under the hood row=r, col=c gets turned into go.Scatter(..., xaxis="x<m>", yaxis="y<n>") - the challenge is just to figure out what <m> and <n> are for a given r and c, and if layout.xaxis<m> and layout.yaxis<n> don't exist they also need to be created with the proper domain set. So if you make sure that all the necessary subplots have something in them from the beginning, all you'd need to do is find the right axis numbers, otherwise it becomes more complicated.

gvwilson commented 3 months ago

Hi - we are tidying up stale issues and PRs in Plotly's public repositories so that we can focus on things that are most important to our community. If this issue is still a concern, please add a comment letting us know what recent version of our software you've checked it with so that I can reopen it and add it to our backlog. (Please note that we will give priority to reports that include a short reproducible example.) If you'd like to submit a PR, we'd be happy to prioritize a review, and if it's a request for tech support, please post in our community forum. Thank you - @gvwilson

jamaa commented 3 months ago

yes, the issue still persists, just tested it using the minimal example above with plotly v5.23.0