Search code examples
pythonplotlyplotly-dashplotly-python

Scatter plot clickdata returns multiple values (choices)


I’m trying to return name of points when clicking on Scatter Plot points but it just return one. How can I do to return multiple value when clicking multiple points. Below is my sample code:

from dash import Dash, html, dcc, Input, Output
import pandas as pd
import plotly.express as px

PCA_table=pd.read_excel('https://github.com/hoatranobita/Jobs/blob/main/PCA_table.xlsx?raw=true')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = Dash(__name__, external_stylesheets=external_stylesheets)

df = pd.read_csv('https://plotly.github.io/datasets/country_indicators.csv')


app.layout = html.Div([
    html.Div([
        html.Div([
            dcc.Dropdown(
                options=PCA_table.columns.unique(),
                value='PC1',
                id='crossfilter-xaxis-column',
            )],style={'width': '49%', 'display': 'inline-block'}),
        html.Div([
            dcc.Dropdown(
                options=PCA_table.columns.unique(),
                value='PC2',
                id='crossfilter-yaxis-column'
            )], style={'width': '49%', 'float': 'right', 'display': 'inline-block'})
    ], style={'padding': '10px 5px'}),
    
    html.Div([
        dcc.Graph(
            id='crossfilter-indicator-scatter',
            clickData={'points': [{'hovertext': '184A1'}]}
        )], style={'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),
    html.Div([
        html.Div(id='x-time-series'),
    ], style={'display': 'inline-block', 'width': '49%'}),
])

@app.callback(
    Output('crossfilter-indicator-scatter', 'figure'),
    Input('crossfilter-xaxis-column', 'value'),
    Input('crossfilter-yaxis-column', 'value'))
def update_graph(xaxis_column_name, yaxis_column_name):
    
    fig = px.scatter(PCA_table,x=PCA_table[xaxis_column_name], 
                          y=PCA_table[yaxis_column_name], 
                          color=PCA_table['Labels'],
                          labels=PCA_table['Columns'],
                          hover_name=PCA_table['Columns'])
    fig.update_layout(clickmode='event')
    fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')

    return fig

@app.callback(
    Output('x-time-series', 'children'),
    Input('crossfilter-indicator-scatter', 'clickData'))
def update_y_timeseries(clickData):
    click_name = clickData['points'][0]['hovertext']
    return html.Span(click_name)

if __name__ == '__main__':
    app.run_server(debug=False,port=1111)

As you see, I want to return click_name as multiple values not just one. I’ve read about clickevent but still not get it. Thank you.


Solution

  • Use clickmode='event+select' to enable accumulation and to handle both click and selection events by listening for one single event (selectedData).

    If layout.clickmode = 'event+select', selection data also accumulates (or un-accumulates) selected data if you hold down the shift button while clicking.

    You can also set dragmode='select' if you want to modebar to be ready for selecting points (default mode is 'zoom').

    fig.update_layout(
        clickmode='event+select',
        dragmode='select',
        margin={'l': 40, 'b': 40, 't': 10, 'r': 0},
        hovermode='closest'
    )
    

    Now in the callback, use selectedData instead of clickData :

    @app.callback(
        Output('x-time-series', 'children'),
        Input('crossfilter-indicator-scatter', 'selectedData'),
        prevent_initial_call=True)
    def update_y_timeseries(selectedData):
        selection= [p['hovertext'] for p in selectedData['points']]
        return html.Span(', '.join(selection))