pythonpandasdataframeplotlynicegui

How do I display DataFrame attribute in NiceGUI with click on Plotly Map?


I've been able to understand how to display long/lat from a map, as well as x/y on a plot, on a label in NiceGUI with a click on a Plotly map with the help of this forum, but now I would like to display attributes from a dataframe object that contains multiple attributes. Sample code is below, dataframe is made up for simplicity.

import pandas as pd
import plotly.graph_objects as go
from nicegui import ui, events

df = pd.DataFrame([
    [12345, -95, 45, 'Cross River'],
    [12346, -94, 43, 'Snake River'],
    [12347, -92, 45, 'Temple River'],
    [12348, -96, 46, 'Gold River'],
    [12349, -95.5, 44.5, '#FT Ty7']],
    columns=['site', 'long', 'lat', 'name']
                   )

fig = go.Figure(go.Scattermap(
    mode='markers',
    lon=df['long'],
    lat=df['lat'],
    )
    )
fig.update_layout(margin=dict(l=0,r=0,t=0,b=0), width=400, showlegend=False,
                  map = {
                      'style': 'satellite-streets',
                      'center': {'lon': sum(df['long']/len(df)), 'lat': sum(df['lat']/len(df)) },
                      'zoom':5})
plot = ui.plotly(fig)

label = ui.label("Click on a point for more information.")

def handle_click(e: events.GenericEventArguments):
    index = e.args
    label.text = f'Name: {name}, Site number: {site}.'
    
plot.on('plotly_click', handle_click)

ui.run()

This code doesn't work to display the attribute "name" from the df, and I would like to include all the attributes in a label as well.


Solution

  • If you print e.args you will see what you can get when you click item on map.

    It is some dictionary with list of points and every point is a dictionary with lat,lon but also pointIndex, pointNumber.

    If you use json.dumps then you can display it in more readable way.

    import json
    print(json.dumps(e.args, indent=2))
    

    example result

    {
      "points": [
        {
          "data": {
            "lat": {
              "dtype": "f8",
              "bdata": "AAAAAACARkAAAAAAAIBFQAAAAAAAgEZAAAAAAAAAR0AAAAAAAEBGQA==",
              "_inputArray": {
                "0": 45,
                "1": 43,
                "2": 45,
                "3": 46,
                "4": 44.5,
                "bdata": "AAAAAACARkAAAAAAAIBFQAAAAAAAgEZAAAAAAAAAR0AAAAAAAEBGQA==",
                "dtype": "f8",
                "shape": "5"
              }
            },
            "lon": {
              "dtype": "f8",
              "bdata": "AAAAAADAV8AAAAAAAIBXwAAAAAAAAFfAAAAAAAAAWMAAAAAAAOBXwA==",
              "_inputArray": {
                "0": -95,
                "1": -94,
                "2": -92,
                "3": -96,
                "4": -95.5,
                "bdata": "AAAAAADAV8AAAAAAAIBXwAAAAAAAAFfAAAAAAAAAWMAAAAAAAOBXwA==",
                "dtype": "f8",
                "shape": "5"
              }
            },
            "mode": "markers",
            "type": "scattermap"
          },
          "curveNumber": 0,
          "pointNumber": 1,
          "pointIndex": 1,
          "lon": -94,
          "lat": 43,
          "bbox": {
            "x0": 237.75555555555556,
            "x1": 239.75555555555556,
            "y0": 347.2957478845692,
            "y1": 349.2957478845692
          }
        }
      ],
      "event": {
        "isTrusted": true
      }
    }
    

    You can get index using

    index = e.args['points'][0]['pointIndex']
    

    and you can get row from dataframe

    data = df.iloc[index]
    

    and you can display it

    label.text = f'Name: {data['name']}, Site number: {data['site']}.'
    

    def handle_click(e: events.GenericEventArguments):
        # use json.dumps to display data with indentations - to make it more readable
        #import json
        #print(json.dumps(e.args, indent=2))
    
        index = e.args['points'][0]['pointIndex']
        data = df.iloc[index]
        label.text = f'Name: {data['name']}, Site number: {data['site']}.'
    

    To make sure you may check if points exists and if it has any item

    def handle_click(e: events.GenericEventArguments):
        # use json.dumps to display data with indentations - to make it more readable
        #import json
        #print(json.dumps(e.args, indent=2))
    
        if 'points' in e.args and len(e.args['points']) > 0 and 'pointIndex' in e.args['points'][0]:
            index = e.args['points'][0]['pointIndex']
            data = df.iloc[index]
            label.text = f'Name: {data['name']}, Site number: {data['site']}.'
        else:
            label.text = 'Wrong point'
    

    Other idea:

    It seems Scattermap() can use customdata= to keep extra information and you can assign full df or only some columns

    fig = go.Figure(go.Scattermap(
        mode='markers',
        lon=df['long'],
        lat=df['lat'],
        customdata=df[['site','name']],
        )
        )
    

    And later you can access it directly in e.args as list without column names

    site, name = e.args['points'][0]['customdata']
    label.text = f'Name: {name}, Site number: {site}.'
    

    You can even display custom data when you hover point on map

    fig = go.Figure(go.Scattermap(
        mode='markers',
        lon=df['long'],
        lat=df['lat'],
        customdata=df[['site','name']],
    
        hovertemplate="Name: %{customdata[1]}, Site number: %{customdata[0]}<extra>lat: %{lat}, lon: %{lon}</extra>"
    
        )
        )
    

    scatttermap with hovertemplate