plotly / plotly/dash

SelectedData on a figure with different legendgroup behaves incorrectly

Open
#2,212 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P3
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:
image

Many points selected:
image

(The part in my code about new_data you can see in the legend has been added after.)

Thanks for any advice :)

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

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.