2025, Nov 27 07:00

Make Plotly Scattermapbox bar labels render: drop textposition='center' and use mode='text' for annotations

Labels missing on Plotly Scattermapbox? textposition='center' blocks rendering. Show numeric labels on bar tips by switching to a dedicated mode='text' layer.

Overlaying per-location bars on a map is a neat way to simulate a 3D bar chart in 2D. One practical need for such a plot is to place numeric labels above each bar so viewers don’t have to hover to read values. If you’ve tried this in Plotly with Scattermapbox and your labels mysteriously refuse to render, read on.

Problem overview

The goal is to draw short vertical bars at given coordinates on a basemap and put numeric labels just above each bar’s tip. Bars render as expected, but the labels don’t appear on the map.

Reproducible example

The snippet below sets up a Mapbox basemap, draws vertical bars as line traces, and attempts to add value labels via a second trace at the bar tip.

import plotly.graph_objects as go
canvas = go.Figure()
canvas.add_trace(go.Choroplethmapbox(
    geojson=hsa_df.geometry.__geo_interface__,
    locations=hsa_df.index,
    z=[0] * len(hsa_df),
    colorscale=[[0, "white"], [1, "white"]],
    showscale=False,
    marker_opacity=0.2,
    marker_line_width=1,
    marker_line_color="black",
    hoverinfo="skip",
    name="HSA Zones"
))
offset_step = 0.05
level_color = {"Major": "red", "Moderate": "orange", "Minor": "green"}
height_ratio = 0.05
stroke_px = 10
legend_seen = {"Major": False, "Moderate": False, "Minor": False}
for _, rec in hsa_df.iterrows():
    xlon = rec["lon"]
    ylat = rec["lat"]
    for i, level in enumerate(["Major", "Moderate", "Minor"]):
        shift = (i - 1) * offset_step
        pct = rec[level]
        bar_h = pct * height_ratio
        canvas.add_trace(go.Scattermapbox(
            mode="lines",
            lon=[xlon + shift, xlon + shift],
            lat=[ylat, ylat + bar_h],
            line=dict(width=stroke_px, color=level_color[level]),
            hoverinfo="text",
            text=f"{rec['RegionName']}
{level}: {pct:.2f}%", showlegend=not legend_seen[level], name=level )) legend_seen[level] = True canvas.add_trace(go.Scattermapbox( mode="markers+text", lon=[xlon + shift], lat=[ylat + bar_h + 0.015], text=[f"{pct:.1f}%"], textposition="center", textfont=dict(size=12, color=level_color[level], family="Arial"), marker=dict(size=1, color=level_color[level]), showlegend=False, hoverinfo="skip" )) canvas.update_layout( title="New York State HSA Zones with Risk Bar Charts", mapbox_style="carto-positron", mapbox_zoom=5.5, mapbox_center={"lat": hsa_df["lat"].mean(), "lon": hsa_df["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 ) canvas.show()

What’s actually going wrong

Passing textposition="center" to a go.Scattermapbox trace causes Plotly to skip rendering the text, even though the parameter is documented as supported. The bars render because they are a separate lines trace, but the label layer never appears. There is no need to place an invisible marker to anchor the text either.

Fix

Use a pure text layer and drop textposition entirely. This reliably draws the labels where you provide lon/lat coordinates.

canvas.add_trace(go.Scattermapbox(
    mode="text",
    lon=[xlon + shift],
    lat=[ylat + bar_h + 0.10],
    text=[f"{pct:.1f}%"],
    textfont=dict(size=12, color="black"),
    showlegend=False,
    hoverinfo="skip",
))

This change preserves the same placement logic for the labels. It simply switches from markers+text with textposition to a dedicated text mode that renders as expected.

Why this matters

When you build composite geospatial visuals, consistent text rendering is key for readability and for sharing static exports where interactivity isn’t available. If labels silently fail, users lose the ability to interpret the map at a glance. Keeping the label layer minimal and explicit avoids surprises and simplifies maintenance.

Takeaways

If your Scattermapbox labels refuse to show up, remove textposition="center" and use mode="text" for the label trace. Position labels by lon/lat, style them via textfont, and keep the label layer independent from markers. This small change helps your numeric annotations render reliably on top of Mapbox tiles.