pythonplotlybar-chartchoropleth

How to Add Value Labels to 3D-like Bar Chart on a Basemap in Python_


I'm working on a Python project where I'm plotting a bar chart on top of a basemap. My original goal is to create a visualization that mimics a 3D bar chart by plotting vertical bars at specific geographical coordinates to represent values.

So far, I’ve managed to get the bars to plot correctly, but I’m struggling to add value labels (i.e., numeric labels on top of each bar) so the plot is more informative.

Here’s a simplified version of my current code. It generates the plot, but the labels are missing:

  import plotly.graph_objects as go


fig = go.Figure()


fig.add_trace(go.Choroplethmapbox(
    geojson=nys_hsa.geometry.__geo_interface__,
    locations=nys_hsa.index,
    z=[0]*len(nys_hsa),  # dummy values
    colorscale=[[0, "white"], [1, "white"]],
    showscale=False,
    marker_opacity=0.2,
    marker_line_width=1,
    marker_line_color='black',
    hoverinfo="skip",  # no hover needed for the base map
    name="HSA Zones"
))


bar_offset = 0.05
bar_colors = {'Major': 'red', 'Moderate': 'orange', 'Minor': 'green'}
bar_height_scale = 0.05  # converts percentage to degrees latitude
bar_line_width = 10       # thicker bars for clarity


legend_flags = {'Major': False, 'Moderate': False, 'Minor': False}


for _, row in nys_hsa.iterrows():
    lon = row['lon']
    lat = row['lat']

    for i, category in enumerate(['Major', 'Moderate', 'Minor']):
        offset = (i - 1) * bar_offset
        value = row[category]
        bar_height = value * bar_height_scale

        # Draw vertical bar
        fig.add_trace(go.Scattermapbox(
            mode="lines",
            lon=[lon + offset, lon + offset],
            lat=[lat, lat + bar_height],
            line=dict(width=bar_line_width, color=bar_colors[category]),
            hoverinfo="text",
            text=f"{row['RegionName']}<br>{category}: {value:.2f}%",
            showlegend=not legend_flags[category],
            name=category
        ))
        legend_flags[category] = True

                
        fig.add_trace(go.Scattermapbox(
            mode="markers+text",
            lon=[lon + offset],
            lat=[lat + bar_height + 0.015],  # slightly above bar tip
            text=[f"{value:.1f}%"],
            textposition="top center",
            textfont=dict(size=12, color=bar_colors[category], family="Arial"),
            marker=dict(size=1, color=bar_colors[category]),  # invisible marker to anchor text
            showlegend=False,
            hoverinfo="skip"
))



fig.update_layout(
    title="New York State HSA Zones with Risk Bar Charts",
    mapbox_style="carto-positron",
    mapbox_zoom=5.5,
    mapbox_center={"lat": nys_hsa['lat'].mean(), "lon": nys_hsa['lon'].mean()},
    height=800,
    margin=dict(l=0, r=0, b=0, t=50),
    legend=dict(
        title="Risk Level",
        orientation="v",
        yanchor="top",
        y=0.95,
        xanchor="right",
        x=1.02,
        bgcolor="rgba(255,255,255,0.8)",
        bordercolor="black",
        borderwidth=1,
        itemsizing="trace"
    ),
    showlegend=True
)

fig.show()

This is the current plot image enter image description here

Sample data can be found here


Solution

  • I don't know why, but it looks like passing textposition="center" to the go.Scattermapbox trace is causing plotly to not render the text, even though this should be a supported argument according to the documentation. You also don't need to create an invisible marker, you can just use mode="text":

    fig.add_trace(go.Scattermapbox(
        mode="text",
        lon=[lon + offset],
        lat=[lat + bar_height + 0.10],
        text=[f"{value:.1f}%"],
        textfont=dict(size=12, color="black"),
        showlegend=False,
        hoverinfo="skip",
    ))
    

    enter image description here