SelectedData on a figure with different legendgroup behaves incorrectly
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 24.4k
- Forks
- 2.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 13
Description
Hello,
Describe your context
dash 2.5.1
dash-bootstrap-components 1.2.0
dash-core-components 2.0.0
dash-datetimepicker 0.0.7
dash-html-components 2.0.0
dash-table 5.0.0
-
if frontend related, tell us your Browser, Version and OS
- Browser : Mozilla Firefox Version 91.12.0esr
Describe the bug
I'm trying to do a selection of points on a graph.
My graph is a 2D graph of a dataset after a clustering.
So, there is a cluster column I use to color my data.
Because of this color parameter, when I select points on my graph,
multiple points are selected outside of the range I select
(points belong to another cluster).
I said "Because of this color parameter" because when I try without this
parameter, I have no problem to select the points I really want to select.
I think this behavior happend because there are different legendgroups
and selectedpoints have they own index on every data made by legendgroups
(legendgroups made different data on a Figure (one by legendgroups)).
Expected behavior
Select only points I select in the first place.
Code I use to display graph and my callback
I tried to use the parameter text or customdata with df.index
but I always have the same problem.
def display_2D(df,x,y,selected_points,selection):
"""
Display 2D graph with x, y axis
"""
fig = px.scatter(df,x,y,color="cluster",
category_orders=category,
color_discrete_sequence=colors,
text=df.index,
)
fig.update_traces(marker_size=5, marker_line_width=0.5, marker_line_color='#FFFFFF',
selectedpoints=selected_points,
#customdata=df.index,
mode='markers',
unselected={'marker': {'opacity': 0.3, 'size': 3}},
)
print(fig)
if selection and selection['range']:
ranges = selection['range']
selection_bounds = {'x0': ranges['x'][0], 'x1': ranges['x'][1],
'y0': ranges['y'][0], 'y1': ranges['y'][1]}
else:
selection_bounds = {'x0': np.min(df[x]), 'x1': np.max(df[x]),
'y0': np.min(df[y]), 'y1': np.max(df[y])}
fig.update_layout(dragmode='select'),
fig.add_shape(dict({'type': 'rect',
'line': { 'width': 1, 'dash': 'dot', 'color': 'darkgrey' } },
**selection_bounds))
return fig
@callback(
Output({"type": "dynamic_output_2D", "index": MATCH}, "figure"),
Input({"type": "dynamic_output_2D", "index": ALL}, 'selectedData'),
Input({"type": "dynamic_output_2D", "index": MATCH}, 'selectedData'),
Input({"type": "dynamic_dropdown_x_2D", "index": MATCH}, "value"),
Input({"type": "dynamic_dropdown_y_2D", "index": MATCH}, "value"),
Input({"type": "dynamic_reload_2D", "index": MATCH}, "n_clicks"),
State('df_visualization','data'))
def display_output_2D(selection_all, selection1, column_x, column_y, _, data_json):
"""
Display 2D graph with new axis
"""
data_df = pd.read_json(data_json,convert_dates=False)
selectedpoints = data_df.index
for selected_data in selection_all:
if selected_data and selected_data['points']:
print(selected_data)
selectedpoints = np.intersect1d(selectedpoints,
[p['text'] for p in selected_data['points']]) #p['customdata'] or p['pointIndex']
return display_2D(data_df, column_x, column_y, selectedpoints, selection1)
Output when I print selected_data
{'points': [{'curveNumber': 0, 'pointNumber': 490, 'pointIndex': 490, 'x': '2021-01-05 11:56', 'y': 194.88, 'text': 6476, 'customdata': 490}, {'curveNumber': 0, 'pointNumber': 491, 'pointIndex': 491, 'x': '2021-01-05 11:57', 'y': 210.38, 'text': 6477, 'customdata': 491}], 'range': {'x': ['2021-01-06 07:55:54.2995', '2021-01-04 09:52:08.8415'], 'y': [190.0347514795131, 213.35874902702997]}}
Output when I print fig (my graph)
Figure({
'data': [{'hovertemplate': ('cluster=-1<br>Date=%{x}<br>met' ... 'r>index=%{text}<extra></extra>'),
'legendgroup': '-1',
'marker': {'color': 'red', 'line': {'color': '#FFFFFF', 'width': 0.5}, 'size': 5, 'symbol': 'circle'},
'mode': 'markers',
'name': '-1',
'selectedpoints': array([6476, 6477]),
'showlegend': True,
'text': array([6.0000e+00, 1.1000e+01, 1.5000e+01, ..., 4.4435e+04, 4.4467e+04,
4.4544e+04]),
'type': 'scattergl',
'unselected': {'marker': {'opacity': 0.3, 'size': 3}},
'x': array([datetime.datetime(2021, 1, 1, 0, 6),
datetime.datetime(2021, 1, 1, 0, 11),
datetime.datetime(2021, 1, 1, 0, 15), ...,
datetime.datetime(2021, 1, 31, 20, 35),
datetime.datetime(2021, 1, 31, 21, 7),
datetime.datetime(2021, 1, 31, 22, 24)], dtype=object),
'xaxis': 'x',
'y': array([296.81, 233.66, 244.06, ..., 153.09, 145.66, 157.25]),
'yaxis': 'y'},
{'hovertemplate': ('cluster=0<br>Date=%{x}<br>metr' ... 'r>index=%{text}<extra></extra>'),
'legendgroup': '0',
'marker': {'color': 'forestgreen',
'line': {'color': '#FFFFFF', 'width': 0.5},
'size': 5,
'symbol': 'circle'},
'mode': 'markers',
'name': '0',
'selectedpoints': array([6476, 6477]),
'showlegend': True,
'text': array([26492., 26493., 26494., ..., 41977., 41978., 42080.]),
'type': 'scattergl',
'unselected': {'marker': {'opacity': 0.3, 'size': 3}},
'x': array([datetime.datetime(2021, 1, 19, 9, 32),
datetime.datetime(2021, 1, 19, 9, 33),
datetime.datetime(2021, 1, 19, 9, 34), ...,
datetime.datetime(2021, 1, 30, 3, 37),
datetime.datetime(2021, 1, 30, 3, 38),
datetime.datetime(2021, 1, 30, 5, 20)], dtype=object),
'xaxis': 'x',
'y': array([173.77, 191.75, 197.2 , ..., 159.83, 159.56, 159.38]),
'yaxis': 'y'},
{'hovertemplate': ('cluster=1<br>Date=%{x}<br>metr' ... 'r>index=%{text}<extra></extra>'),
'legendgroup': '1',
'marker': {'color': 'blueviolet', 'line': {'color': '#FFFFFF', 'width': 0.5}, 'size': 5, 'symbol': 'circle'},
'mode': 'markers',
'name': '1',
'selectedpoints': array([6476, 6477]),
'showlegend': True,
'text': array([0.0000e+00, 1.0000e+00, 2.0000e+00, ..., 4.4541e+04, 4.4542e+04,
4.4543e+04]),
'type': 'scattergl',
'unselected': {'marker': {'opacity': 0.3, 'size': 3}},
'x': array([datetime.datetime(2021, 1, 1, 0, 0),
datetime.datetime(2021, 1, 1, 0, 1),
datetime.datetime(2021, 1, 1, 0, 2), ...,
datetime.datetime(2021, 1, 31, 22, 21),
datetime.datetime(2021, 1, 31, 22, 22),
datetime.datetime(2021, 1, 31, 22, 23)], dtype=object),
'xaxis': 'x',
'y': array([241.85, 244.83, 255.32, ..., 155.91, 156.03, 157.47]),
'yaxis': 'y'}],
'layout': {'legend': {'title': {'text': 'cluster'}, 'tracegroupgap': 0},
'margin': {'t': 60},
'template': '...',
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'Date'}},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'metric1'}}}
})
Screenshots
2 points selected:

Many points selected:

(The part in my code about new_data you can see in the legend has been added after.)
Thanks for any advice :)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the provided display_2D and display_output_2D callback with a Plotly Express scatter split into legendgroups. Inspect how selectedData point indices map to each trace, then verify that a range selection marks only the intended points across traces.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- plotly, python
- Domain
- data-visualization, frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100