MRE below, I noticed that the error can be avoided if I manually set widths in advance though this is not a preferred way.
import dash
from dash import dcc, html, Input, Output, State, callback
import plotly.graph_objs as go
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.express as px
# Sample data
df = pd.DataFrame({
'X': [1, 2, 3] * 3,
'Y': [4, 5, 6, 7, 8, 9, 10, 11, 12],
'Series': ['Series 1'] * 3 + ['Series 2'] * 3 + ['Series 3'] * 3
})
# Create the plot with Plotly Express
fig = px.line(df, x='X', y='Y', color='Series', line_shape='linear')
# Initialize the Dash app
app = dash.Dash(__name__)
# Define the app layout
app.layout = html.Div([
dcc.Graph(
id='graph_analysis',
figure=fig
),
dcc.Store(id='selected-series-index'),
dcc.Input(id='input-data-series'),
dcc.Dropdown(id='line-width-dropdown', options=[{'label': i, 'value': i} for i in range(1, 11)]),
dbc.Offcanvas(id='offcanvas2')
])
@callback(
Output("selected-series-index", "data"),
Output("input-data-series", "value"),
Output('line-width-dropdown', 'value'),
Output("offcanvas2", "is_open"),
Input("graph_analysis", "clickData"),
State("graph_analysis", "figure"),
State("offcanvas2", "is_open"),
prevent_initial_call=True
)
def update_selected_series_and_name_input(clickData, fig, is_open):
if clickData:
clicked_series = clickData['points'][0]['curveNumber']
series_name = fig['data'][clicked_series]['name']
current_width = fig['data'][clicked_series]['line']['width']
return clicked_series, series_name, current_width, True if not is_open else dash.no_update
return None, '', '', dash.no_update
# Run the app
if __name__ == '__main__':
app.run_server(debug=True)
In my actual app I am using this code function for the actual plot:
def line(error_y_mode=None, **kwargs):
"""Extension of `plotly.express.line` to use error bands."""
ERROR_MODES = {'bar','band','bars','bands', None}
if error_y_mode not in ERROR_MODES:
raise ValueError(f"'error_y_mode' must be one of {ERROR_MODES}, received {repr(error_y_mode)}.")
if error_y_mode in {'bar','bars', None}:
fig = px.line(**kwargs)
elif error_y_mode in {'band','bands'}:
if 'error_y' not in kwargs:
raise ValueError(f"If you provide argument 'error_y_mode' you must also provide 'error_y'.")
figure_with_error_bars = px.line(**kwargs)
fig = px.line(**{arg: val for arg,val in kwargs.items() if arg != 'error_y'})
for data in figure_with_error_bars.data:
x = list(data['x'])
y_upper = list(data['y'] + data['error_y']['array'])
y_lower = list(data['y'] - data['error_y']['array'] if data['error_y']['arrayminus'] is None else data['y'] - data['error_y']['arrayminus'])
color = f"rgba({tuple(int(data['line']['color'].lstrip('#')[i:i+2], 16) for i in (0, 2, 4))},.3)".replace('((','(').replace('),',',').replace(' ','')
fig.add_trace(
go.Scatter(
x = x+x[::-1],
y = y_upper+y_lower[::-1],
fill = 'toself',
fillcolor = color,
line = dict(
color = 'rgba(255,255,255,0)'
),
hoverinfo = "skip",
showlegend = False,
legendgroup = data['legendgroup'],
xaxis = data['xaxis'],
yaxis = data['yaxis'],
)
)
# Reorder data as said here: https://stackoverflow.com/a/66854398/8849755
reordered_data = []
for i in range(int(len(fig.data)/2)):
reordered_data.append(fig.data[i+int(len(fig.data)/2)])
reordered_data.append(fig.data[i])
fig.data = tuple(reordered_data)
return fig
I guess using plotly express might be a problem here?