Tooltip and labels in pydeck_chart

I am making my first steps with pydeck_chart. Unfortunately, I cannot get the tooltips to work. I am using the example from the Streamlit doc with my own data.

tooltip = {
                    "html": "<b>Value:</b> {value} <br/>",
                    "style": {
                        "backgroundColor": "steelblue",
                        "color": "white"}
                }

the columns are showing a hover over effect but no text is displayed. I believe is to set the right text for the {value} expression. value is a column in my dataframe holding the value to be displayed. What am I doing wrong?
Also, I would like to label some of the points, is it possible to add a label layer, does anyone have an example for this? thx!

Hi @godot63

There are issues with the implementation of PyDeck in Streamlit. For example tooltips are not supported.

Please see https://github.com/streamlit/streamlit/issues/984. Please upvote if you think its important. (I do :-))

1 Like

thanks Marc,
I did up-vote the issue. What I do not understand in my case: when I run the tutorial example, then the tooltips work just fine, except that the tooltip box is behind the data points, so in some cases you cannot read it. Just not with my own dataframe data. You also mention that nun url data is not supported. Is this just the issue with data including NAN values? I experience this too, however if I filter NAN value out, I can display my dataframe. My main issue currently is, that I want my data to plot with a blue-green gradient color. I calculate the color column using a lamda function

df[β€œcolor”] = df.apply (lambda row: tools.color_gradient(row, min_val, max_val), axis=1)
the column is filled with the correct rgb values where color_gradient returns a list [r,g,b]

I then set the getFillColor to the color column: getFillColor = β€˜[color]’

The displayed colors are however all red and orange, seems like a default color. I had started using getColorR, getColorG, getColorB and that worked fine, but I don’t know how to update all three columns using a lambda function. I was trying to reproduce your power plant example, but it is a bit different in that it uses lookup colors for categories, whereas I try to calculate a value within a continuous gradient.

@godot63

Try pasting a minimum runnable code example then I or others might be able to help.

Here is what I tried, projected on the demo project. During my trials, sometimes colors did appear, however after a reload of the page, without changes to the code, the color was gone again. I would like to have a column named β€˜color’ filled with a string or list of RGB colors that can be set in the layer object using the setColor command. I left the various trials in the code as comments.

import streamlit as st
import pandas as pd
import numpy as np
import pydeck as pdk

df = pd.DataFrame(
    np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4],
    columns=['lat', 'lon'])
df['color_r'] = 0
df['color_g'] = 0
df['color_b'] = 255
df['color'] = '[255,0,0]'

st.pydeck_chart(pdk.Deck(
    map_style='mapbox://styles/mapbox/light-v9',
    initial_view_state=pdk.ViewState(
        latitude=37.76,
        longitude=-122.4,
        zoom=11,
        pitch=50,
    ),
    layers=[
        pdk.Layer(
            'ScatterplotLayer',
            data=df,
            get_position='[lon, lat]',
            # original, works
            # get_color='[200, 30, 0, 160]',

            # GetColorR/G/B, does not work:
            # getColorR=255,
            # getColorG=0,
            # getColorB=0,
            
            # getting colors from columns, does not work:
            # getColorR='[color_r]',
            # getColorG='[color_g]',
            # getColorB='[color_b]',
            
            # getting colors from single column, this is what I actually would like to do, does not work
            # getColor='[color]'

            # or maybe like this? does not work either
            # getColor='[color]'.split(','),

            get_radius=200,
        ),
    ],
))

Try something like get_fill_color="[color_r, color_g, color_b, color_a]".

This example from awesome-streamlit.org.org should work

"""This app demonstrates the use of the awesome [deck.gl]() framework for visual
exploratory data analysis of large datasets.

Deck.gl is now (as of Streamlit v. 0.53) supported via the
[`st.pydeck_chart`](https://docs.streamlit.io/api.html?highlight=pydeck#streamlit.pydeck_chart)
function.

We use data from the
[Global Power Plant Database](http://datasets.wri.org/dataset/globalpowerplantdatabase) to
illustrate the locations, fuel types and capacities of the worlds power plants.
"""


import pathlib

import pandas as pd
import pydeck as pdk
import streamlit as st

POWER_PLANT_PATH = (
    pathlib.Path.cwd() / "gallery/global_power_plant_database/global_power_plant_database.csv"
)

POWER_PLANT_URL = (
    "https://raw.githubusercontent.com/MarcSkovMadsen/awesome-streamlit/master/"
    "gallery/global_power_plant_database/global_power_plant_database.csv"
)

LATITUDE_COLUMN = "latitude"
LONGITUDE_COLUMN = "longitude"

LOCATIONS = {
    "Orsted Copenhagen HQ": {"latitude": 55.676098, "longitude": 12.568337},
    "Orsted Boston": {"latitude": 2.361145, "longitude": -71.057083},
}
ORSTED_CPH_HQ = LOCATIONS["Orsted Copenhagen HQ"]

FUEL_COLORS = {
    "Oil": "black",
    "Solar": "green",
    "Gas": "black",
    "Other": "gray",
    "Hydro": "blue",
    "Coal": "black",
    "Petcoke": "black",
    "Biomass": "green",
    "Waste": "green",
    "Cogeneration": "gray",
    "Storage": "orange",
    "Wind": "green",
}

COLORS_R = {"black": 0, "green": 0, "blue": 0, "orange": 255, "gray": 128}

COLORS_G = {"black": 0, "green": 128, "blue": 0, "orange": 165, "gray": 128}

COLORS_B = {"black": 0, "green": 0, "blue": 255, "orange": 0, "gray": 128}


class ViewStateComponent:
    """Component to let the user set the initial view state to for example Copenhagen or Boston"""

    def __init__(self):
        self.latitude = ORSTED_CPH_HQ["latitude"]
        self.longitude = ORSTED_CPH_HQ["longitude"]
        self.zoom = 1
        self.pitch = 40.0

    def edit_view(self):
        """Lets the user edit the attributes"""
        location = st.sidebar.selectbox("Location", options=list(LOCATIONS.keys()), index=0)
        self.latitude = LOCATIONS[location]["latitude"]
        self.longitude = LOCATIONS[location]["longitude"]

        self.zoom = st.sidebar.slider("Zoom", min_value=0, max_value=20, value=self.zoom)
        self.pitch = st.sidebar.slider(
            "Pitch", min_value=0.0, max_value=100.0, value=self.pitch, step=10.0
        )

    @property
    def view_state(self) -> pdk.ViewState:
        """The ViewState according to the attributes

        Returns:
            pdk.ViewState -- [description]
        """
        return pdk.ViewState(
            longitude=self.longitude,
            latitude=self.latitude,
            zoom=self.zoom,
            min_zoom=0,
            max_zoom=15,
            pitch=self.pitch,
            # bearing=-27.36,
        )


class GlobalPowerPlantDatabaseApp:
    """The main app showing the Global Power Plant Database"""

    def __init__(self):
        self.view_state_component = ViewStateComponent()
        self.data = self.get_data()
        self.show_data = False

    @staticmethod
    @st.cache
    def get_data() -> pd.DataFrame:
        """The Global Power Plant data

        Returns:
            pd.DataFrame -- The Global Power Plant data cleaned and transformed
        """
        try:
            data = pd.read_csv(POWER_PLANT_PATH)
        except FileNotFoundError:
            data = pd.read_csv(POWER_PLANT_URL)

        # Clean
        data.primary_fuel = data.primary_fuel.fillna("NA")
        data.capacity_mw = data.capacity_mw.fillna(1)

        # Transform
        data["primary_fuel_color"] = data.primary_fuel.map(FUEL_COLORS)
        data["primary_fuel_color"] = data["primary_fuel_color"].fillna("gray")
        data["color_r"] = data["primary_fuel_color"].map(COLORS_R)
        data["color_g"] = data["primary_fuel_color"].map(COLORS_G)
        data["color_b"] = data["primary_fuel_color"].map(COLORS_B)
        data["color_a"] = 140

        return data[
            [
                "capacity_mw",
                LATITUDE_COLUMN,
                LONGITUDE_COLUMN,
                "primary_fuel_color",
                "color_r",
                "color_g",
                "color_b",
                "color_a",
            ]
        ]

    def _scatter_plotter_layer(self):
        return pdk.Layer(
            "ScatterplotLayer",
            data=self.data,
            get_position=[LONGITUDE_COLUMN, LATITUDE_COLUMN],
            get_fill_color="[color_r, color_g, color_b, color_a]",
            get_radius="capacity_mw*10",
            pickable=True,
            opacity=0.8,
            stroked=False,
            filled=True,
            wireframe=True,
        )

    def _deck(self):
        return pdk.Deck(
            map_style="mapbox://styles/mapbox/light-v9",
            initial_view_state=self.view_state_component.view_state,
            layers=[self._scatter_plotter_layer()],
            tooltip={"html": "<b>Color Value:</b> {primary_fuel}", "style": {"color": "white"}},
        )

    def view(self):
        """Main view of the app"""
        # self.view_state_component.edit_view() # Does not work
        st.write(__doc__)

        st.pydeck_chart(self._deck())

        st.write(
            """The maps shows the power plant

- **location** by latitude, longitude coordinates
- **fuel type** by color and
- **capacity in MW** by bubble size
"""
        )
        st.json(FUEL_COLORS)

        st.write(
            """Unfortunately **tooltips are not supported**. And there are also other issues.
See

- [Issue 984](https://github.com/streamlit/streamlit/issues/984)
- [Issue 985](https://github.com/streamlit/streamlit/issues/985)"""
        )


APP = GlobalPowerPlantDatabaseApp()
APP.view()
1 Like

Thanks Marc. this worked just fine, I just had to tweak it a bit so I get gradient colors instead of discrete color values, but all the heavy lifting was done by you. I had actually started previously with this example but given up too early not sure why.

I used the following solution to add a legend to my maps. I am basically plotting concentration values on a blue to green scale where blue is low and green is high. e.g. for values going from 10 to 20, 10-15 will be in blue shades, >15 - 20 in green. I have not found a legend for pydeck so I came up with the following code that shows 2 colored circles with the corresponding value interval. Is there a more elegant solution for legends?

legend = """
                <style>
                .bdot {{
                height: 15px;
                width: 15px;
                background-color: Blue;
                border-radius: 50%;
                display: inline-block;
                }}
                .gdot {{
                height: 15px;
                width: 15px;
                background-color: #4DFF00;
                border-radius: 50%;
                display: inline-block;
                }}
                </style>
                </head>
                <body>
                <div style="text-align:left">
                <h3>Legend</h3>
                <span class="bdot"></span>  {} - {}<br>
                <span class="gdot"></span>  &#62;{} - {}
                </div>
                </body>
                """.format(round(min_val), round((max_val - min_val) / 2), round((max_val - min_val) / 2), round(max_val))
            st.markdown(legend, unsafe_allow_html=True)
2 Likes