plotly / plotly/dash

Reset axes button in dcc.Graph modebar doesn't apply the range specified in yaxis_range

Open
#3,045 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

P3 sev-3
Dominant language
Python
Stars
24.4k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
13

Description

If the figure is included from the start in a dcc.Graph, clicking Reset axes 🏠 in the modebar triggers this:

{'xaxis.autorange': True, 'xaxis.showspikes': False, 'yaxis.range': 0, 0.1, 'yaxis.showspikes': False}

However, if the dcc.Graph figure property is populated in a callback, Reset Axes 🏠 triggers this (the same effect as clicking Autoscale):

{'xaxis.autorange': True, 'xaxis.showspikes': False, 'yaxis.autorange': True, 'yaxis.showspikes': False} 

even if the figure has a specific yaxis_range=[0,0.1] and we have set yaxis_autorange=False

I believe this is because the modebar is created when the dcc.Graph component is initialized and it’s not updated if the figure changes.

Additional information:

Workaround

Create an empty figure with the desired/eventual layout, for example:

empty_fig = go.Figure(layout=go.Layout(yaxis_range=[0, 0.1])) 

and assign this as the initial value for the figure that you are going to later update with a callback:

dcc.Graph(id='fig2', figure=empty_fig)

Code to replicate the issue

Env
Python 3.10
dash==2.18.1
plotly==5.24.1

from dash import Dash, html, dcc, callback, Input, Output
import pandas as pd
import plotly.graph_objects as go
import random
import plotly
import dash

print(f"Plotly version is {plotly.__version__} and dash version is {dash.__version__}")

def generate_figs():

    N = 50

    well_df = pd.DataFrame(
        {
            "EventTime": [i for i in range(N)],
            "cmax": [random.randint(0, 100) for i in range(N)],
        }
    )

    well_names = [f"Well{i}" for i in range(N)]

    threshold = 1000
    max_y = 0.07

    fig = go.Figure()

    for well_name in well_names:

        fig.add_trace(
            go.Scatter(
                x=well_df["EventTime"],
                y=well_df["cmax"] / threshold,
                name=well_name,
                mode="lines",
            )
        )

    fig.update_layout(
        title="Combined anomaly detection",
        xaxis=dict(title=dict(text="Time")),
        yaxis=dict(range=[0, 0.1], showticklabels=True, autorange=False),
        plot_bgcolor="#ebfaeb",
        legend=dict(y=1.08, x=0.97),
    )

    # create a copy of the figure
    fig2 = go.Figure(fig)

    # your figure
    fig.add_hrect(y0=1.0, y1=max_y, line_width=0, fillcolor="red", opacity=0.2)
    # fig2
    fig2.update_layout(plot_bgcolor="rgba(255,0,0,0.2)")  # red with alpha 0.2
    fig2.add_hrect(y0=0, y1=max_y, line_width=0, fillcolor="#ebfaeb", layer="below")
    return fig, fig2

fig, fig2 = generate_figs()

# WORKAROUND
empty_fig = go.Figure(layout=go.Layout(yaxis_range=[0, 0.1]))

@callback(Output("fig2", "figure"), Input("btn", "n_clicks"), prevent_initial_call=True)
def show_fig(n):
    return fig2

app = Dash(__name__)

app.layout = html.Div(
    [
        html.Button("Click me", id="btn"),
        dcc.Graph(id="fig2"),  # add figure=empty_fig for workaround
        dcc.Markdown(id="out2"),
        dcc.Markdown(id="out2b"),
        dcc.Graph(id="fig2b", figure=fig2),
    ]
)

# callbacks to check the effects on the figure
@callback(
    Output("out2", "children"), Input("fig2", "relayoutData"), prevent_initial_call=True
)
def showchanges2(rl):
    return f"**Relayout:**{rl}"

@callback(
    Output("out2b", "children"),
    Input("fig2b", "relayoutData"),
    prevent_initial_call=True,
)
def showchanges2b(rl):
    return f"**Relayout:**{rl}"

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the callback-populated dcc.Graph case using the supplied Dash example and inspect src/components/modebar/buttons.js at the linked Reset Axes handlers. Compare the relayoutData emitted for an initially configured figure with one populated by a callback. Done means Reset Axes preserves the specified yaxis_range instead of emitting yaxis.autorange: true.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, python
Domain
data-visualization, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.