-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Open
Labels
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:
- Clicking Reset Axes 🏠 triggers this event: https://github.com/plotly/plotly.js/blob/master/src/components/modebar/buttons.js#L215
- I’ve (unsuccessfully) tried forcing a modebar update by:
- Making some change to the modebar when I create fig2 in the callback
- Modifying the config property of the Graph at the same time I return the figure
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)
logan-hcg