selectedData not showing customdata

I’m trying to use the lasso selection tool to remove points from a scatter plot. Because the scatter plot has multiple traces and subplots belonging to multiple dataframes, I can’t find a way to map the limited selection properties (curveNumber, pointNumber, pointIndex) to the underlying data.

From the documentation, I was planning on using the “customdata” property to add my own index that would determine which data points to remove. However, when I try running the code below from the documentation and select a point, I see the following (notice the absence of “customdata”).

Output from plotly 6.1.2, dash 3.0.3:

{
  "points": [
    {
      "curveNumber": 0,
      "pointNumber": 1,
      "pointIndex": 1,
      "x": 2,
      "y": 2
    }
  ],

Expected output (from documentation):

{
  "points": [
    {
      "curveNumber": 0,
      "pointNumber": 1,
      "pointIndex": 1,
      "x": 2,
      "y": 2,
      "customdata": [
        2
      ]
    }
  ],

Example code:

from dash import Dash, dcc, html, Input, Output, callback

import plotly.express as px

import json
import pandas as pd

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = Dash(__name__, external_stylesheets=external_stylesheets)

styles = {
    'pre': {
        'border': 'thin lightgrey solid',
        'overflowX': 'scroll'
    }
}

df = pd.DataFrame({
    "x": [1,2,1,2],
    "y": [1,2,3,4],
    "customdata": [1,2,3,4],
    "fruit": ["apple", "apple", "orange", "orange"]
})

fig = px.scatter(df, x="x", y="y", color="fruit", custom_data=["customdata"])

fig.update_layout(clickmode='event+select')

fig.update_traces(marker_size=20)

app.layout = html.Div([
    dcc.Graph(
        id='basic-interactions',
        figure=fig
    ),

    html.Div(className='row', children=[
        html.Div([
            dcc.Markdown("""
                **Hover Data**

                Mouse over values in the graph.
            """),
            html.Pre(id='hover-data', style=styles['pre'])
        ], className='three columns'),

        html.Div([
            dcc.Markdown("""
                **Click Data**

                Click on points in the graph.
            """),
            html.Pre(id='click-data', style=styles['pre']),
        ], className='three columns'),

        html.Div([
            dcc.Markdown("""
                **Selection Data**

                Choose the lasso or rectangle tool in the graph's menu
                bar and then select points in the graph.

                Note that if `layout.clickmode = 'event+select'`, selection data also
                accumulates (or un-accumulates) selected data if you hold down the shift
                button while clicking.
            """),
            html.Pre(id='selected-data', style=styles['pre']),
        ], className='three columns'),

        html.Div([
            dcc.Markdown("""
                **Zoom and Relayout Data**

                Click and drag on the graph to zoom or click on the zoom
                buttons in the graph's menu bar.
            """),
            html.Pre(id='relayout-data', style=styles['pre']),
        ], className='three columns')
    ])
])


@callback(
    Output('hover-data', 'children'),
    Input('basic-interactions', 'hoverData'))
def display_hover_data(hoverData):
    return json.dumps(hoverData, indent=2)


@callback(
    Output('click-data', 'children'),
    Input('basic-interactions', 'clickData'))
def display_click_data(clickData):
    return json.dumps(clickData, indent=2)


@callback(
    Output('selected-data', 'children'),
    Input('basic-interactions', 'selectedData'))
def display_selected_data(selectedData):
    return json.dumps(selectedData, indent=2)


@callback(
    Output('relayout-data', 'children'),
    Input('basic-interactions', 'relayoutData'))
def display_relayout_data(relayoutData):
    return json.dumps(relayoutData, indent=2)


if __name__ == '__main__':
    app.run(debug=True)


1 Like

Hello,

{
“points”: [
{
“curveNumber”: 0,
“pointNumber”: 3,
“x”: 10,
“y”: 5
},

]
}
If you don’t see your customdata there, the reason is usually that:

customdata wasn’t actually added to the trace.

You’re looking at an outdated selectedData structure (some versions of Plotly/Dash had bugs with customdata in multi-trace or multi-subplot figures).

Best Regards

1 Like

Hi @john55moore ,

I’ve updated to the newest versions of plotly (6.3.0) and dash (3.2.0) and am still seeing the same behavior.

I’ve also inspected fig[“data”] to confirm that customdata is present in the trace:

  {
    "customdata": {
      "dtype": "i1",
      "bdata": "AQI=",
      "shape": "2, 1",
      "_inputArray": [
        {
          "0": 1
        },
        {
          "0": 2
        }
      ]
    },
1 Like

Hi :slightly_smiling_face:

I just encountered the same issue.

I do think that this issue has been introduced in a recent version of Plotly or Dash. Usually, you could simply do something like

df = pd.DataFrame({
    "x": [1,2,1,2],
    "y": [1,2,3,4],
    "z": ["a","b","c","d"],
})

fig = px.scatter(
    df,
    x="x",
    y="y",
    hover_data=["z"]
)

And when a callback is triggered by “hoverData”, the data in column “z” would appear in the “hoverData” that triggered the callback.

A workaround that I found is to include the figure in the callback as well and to extract the data from the customdata of the figure via the “pointIndex” of the hoverData.

@callback(
    Output("dummy-output", "children"),
    Input("scatter-plot", "hoverData"),
    State("scatter-plot", "figure")
)
def extract_customdata(hoverdata, fig):
    point_idx = hoverdata["points"][0]["pointIndex"]
    customdata = fig["data"][0]["customdata"]["_inputArray"][point_idx]["0"]
    return f"This is the customdata: {customdata}"

I hope this workaround helps you but I’d also like to know if this is intended behaviour or some newly introduced bug.