Search code examples
pythonplotlyplotly-python

Plotly: is there a way to save the data of a clicked point in a list?


I have a 2D plotly graph with a hover feature. When you hover over each point, the label (e.g. 'image 2, cluster 1') associated with that point appears. I'd like for label to be appended onto an existing list if I were to click on the point (rather than just hover over it). The reason why is that I'd later like to use the data of this point to perform another task. Is there an example online that demonstrates how to do this-- have looked through the documentation but haven't found something for this yet. Thanks!


Solution

  • The hoverData that is available to you by default, with some sample data, is this:

    {
      "points": [
        {
          "curveNumber": 1,
          "pointNumber": 7,
          "pointIndex": 7,
          "x": 1987,
          "y": 74.32,
          "bbox": {
            "x0": 420.25,
            "x1": 426.25,
            "y0": 256,
            "y1": 262
          }
        }
      ]
    }
    

    I'm not quite sure what you mean by 'label', so I can only assume that it would be the name of a trace or something similar, like in this example from the Plotly docs:

    enter image description here

    But as you can see, that's not readily available in the hoverData dict. This means that you'll have to use this information to reference your figure structure as well, so that you end up with something like this:

    [['New Zealand', 2002, 79.11]]
    

    And that's not a problem as long as you're willing to use Plotly Dash. I've made a complete setup for you that should meet your requirements. In the app in the image below you'll find a figure along with two output fields for strings. The first field shows the info from that last point you've clicked in the figure. On every click, a new element is added to a list named store. The last fields shows the complete information from the same click.

    The answer to your question is, yes, there is a way to save the data of a clicked point in a list. And one way to do so is through the following callback that uses clickdata to reference your figure object, store those references in a list, and append new elements every time you click a new element.

    App

    enter image description here

    Complete code:

    import json
    from textwrap import dedent as d
    import pandas as pd
    import plotly.graph_objects as go
    import numpy as np
    import dash
    from dash import dcc
    import dash_html_components as html
    import plotly.express as px
    from dash.dependencies import Input, Output
    from jupyter_dash import JupyterDash
    import warnings
    
    warnings.simplefilter(action='ignore', category=FutureWarning)
    
    # app info
    app = JupyterDash(__name__)
    styles = {
        'pre': {
            'border': 'thin lightgrey solid',
            'overflowX': 'scroll'
        }
    }
    
    # data
    df = px.data.gapminder().query("continent=='Oceania'")
    
    # plotly figure
    fig = px.line(df, x="year", y="lifeExp", color="country", title="No label selected")
    fig.update_traces(mode="markers+lines")
    
    app.layout = html.Div([
        dcc.Graph(
            id='figure1',
            figure=fig,
        ),
    
        html.Div(className
                 ='row', children=[
            html.Div([
                dcc.Markdown(d("""Hoverdata using figure references""")),
                html.Pre(id='hoverdata2', style=styles['pre']),
            ], className='three columns'),
                     
                         html.Div([
                dcc.Markdown(d("""
                  
                  Full hoverdata
                """)),
                html.Pre(id='hoverdata1', style=styles['pre']),
            ], className='three columns')   
        ]),
        
    ])
    
    # container for clicked points in callbacks
    store = []
    
    @app.callback(
        Output('figure1', 'figure'),
        Output('hoverdata1', 'children'),
        Output('hoverdata2', 'children'),
        [Input('figure1', 'clickData')])
    def display_hover_data(hoverData):
        
        if hoverData is not None:
            traceref = hoverData['points'][0]['curveNumber']
            pointref = hoverData['points'][0]['pointNumber']
            store.append([fig.data[traceref]['name'],
                          fig.data[traceref]['x'][pointref],
                         fig.data[traceref]['y'][pointref]])
            fig.update_layout(title = 'Last label was ' + fig.data[traceref]['name'])
            return fig, json.dumps(hoverData, indent=2), str(store)
        else:
            return fig, 'None selected', 'None selected'
    
    app.run_server(mode='external', port = 7077, dev_tools_ui=True,
              dev_tools_hot_reload =True, threaded=True)