plotly / dash

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

Config attribute of dcc.Graph does not respond to callbacks (is read-only) #2865

Open ndrezn opened 3 years ago

ndrezn commented 3 years ago

When using a callback to control the modebar settings on a chart through the config property of a dcc.Graph, the graph does not update but retains the initial setting.

Note: I have not (yet) tested with other config settings.

Minimal example

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

app = dash.Dash()
server = app.server

app.layout = html.Div(
    [
        dcc.Graph(
            id="line-graph",
            figure=px.line(x=[1, 2, 3], y=[1, 2, 3]),
        ),
        dcc.RadioItems(
            options=[
                {"label": "Show modebar", "value": 1},
                {"label": "Hide modebar", "value": 0},
            ],
            value=1, # Note that if this value is set to 0 then the modebar will stay turned off rather than turned on.
            id="radio",
        ),
        html.Div([html.H3("Current config"), html.P(id="cur-config")]),
    ]
)

@app.callback(
    [Output("line-graph", "config"), Output("cur-config", "children")],
    [Input("radio", "value")],
)
def toggle_modebar(i):
    vals = [False, True]
    config = {"displayModeBar": vals[i]}
    return config, str(config)

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

Screen recording

https://user-images.githubusercontent.com/38958867/107691811-edb77d00-6c79-11eb-8488-c750b635f815.mov

ndrezn commented 3 years ago

One workaround for this is generating a new dcc.Graph in a callback with the same id and using the state of the existing figure to prevent needing to regenerate it.

Screen recording of this (and the expected behaviour)

https://user-images.githubusercontent.com/38958867/107695876-2f96f200-6c7f-11eb-880a-e4c9459a8ca1.mov

Code

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

app = dash.Dash()
server = app.server

app.layout = html.Div(
    [
        html.Div(
            dcc.Graph(id="line-graph"),
            id="graph",
        ),
        dcc.RadioItems(
            options=[
                {"label": "Show modebar", "value": 1},
                {"label": "Hide modebar", "value": 0},
            ],
            value=1,
            id="radio",
        ),
        dcc.RadioItems(
            options=[
                {"label": "Bar", "value": 1},
                {"label": "Line", "value": 0},
            ],
            value=1,
            id="fig-type",
        ),
    ]
)

@app.callback(Output("line-graph", "figure"), [Input("fig-type", "value")])
def create_fig(i):
    figures = [px.line(x=[1, 2, 3], y=[1, 2, 3]), px.bar(x=[1, 2, 3], y=[1, 2, 3])]
    return figures[i]

@app.callback(
    Output("graph", "children"),
    [Input("radio", "value")],
    [State("line-graph", "figure")],
)
def toggle_modebar(i, fig):
    vals = [False, True]
    config = {"displayModeBar": vals[i]}

    return dcc.Graph(id="line-graph", figure=fig if fig else {}, config=config)

if __name__ == "__main__":
    app.run_server(debug=True, port=8050)
luggie commented 4 months ago

is this an intended behavior of dcc.Graph or rather a bug?