plotly/plotly.py

[BUG]: marker.colorbar.tickfont.textcase='normal' blanks scattermap on initial render, but Plotly.restyle renders correctly

已关闭

#5,616 创建于 2026年6月7日

 (1 条评论) (0 个反应) (0 位负责人)Python (2,467 个派生)batch import
buggood first issuegood for agent

仓库指标

星标
 (14,684 个星标)
PR 合并指标
 (平均合并 1天 7小时) (30 天内合并 7 个 PR)

描述

Description

When initializing a Plotly Scattermap figure with marker.colorbar.tickfont.textcase='normal', the initial render can fail to draw the OpenStreetMap tiles and map-layer geometry. The figure area is mostly blank, with only right-side numeric tick labels and the OpenStreetMap attribution visible.

However, if the same base figure is rendered first and the same property value is then applied dynamically through Plotly.restyle, the map renders correctly: the OSM tiles appear, the blue filled map layer is visible, and the cyan marker is drawn.

The setting is accepted by plotly.py and emitted as a valid trace property, so this appears to be an initial-render/update-time rendering discrepancy involving Scattermap marker colorbar tick font defaults rather than an invalid Python-side property assignment.

Screenshots/Video

Steps to reproduce

import io
import time

import plotly
import plotly.graph_objects as go
import plotly.io as pio
from PIL import Image, ImageChops
from playwright.sync_api import sync_playwright

print(f"Plotly version: {plotly.__version__}")


def make_fig():
    fig = go.Figure(
        go.Scattermap(
            mode="markers",
            lon=[-73.605],
            lat=[45.51],
            marker={"size": 20, "color": ["cyan"]},
        )
    )

    polygon = [
        [
            [
                [-73.62, 45.50],
                [-73.59, 45.50],
                [-73.59, 45.53],
                [-73.62, 45.53],
                [-73.62, 45.50],
            ]
        ]
    ]

    fig.update_layout(
        map={
            "style": "open-street-map",
            "center": {"lon": -73.6, "lat": 45.5},
            "zoom": 12,
            "layers": [
                {
                    "source": {
                        "type": "FeatureCollection",
                        "features": [
                            {
                                "type": "Feature",
                                "geometry": {
                                    "type": "MultiPolygon",
                                  "coordinates": polygon,
                                },
                            }
                        ],
                    },
                    "type": "fill",
                    "below": "traces",
                    "color": "royalblue",
                }
            ],
        },
        margin={"l": 0, "r": 0, "b": 0, "t": 0},
    )
    return fig


fig = make_fig()

# Screenshot 1 / plot1.png: set the value in Python before the first render.
fig.data[0].marker.colorbar.tickfont.textcase = "normal"
assert fig.data[0].marker.colorbar.tickfont.textcase == "normal"
print("Python-side tickfont JSON:", fig.data[0].marker.colorbar.tickfont.to_plotly_json())
html1 = pio.to_html(fig, full_html=True, include_plotlyjs="cdn")


# Screenshot 2 / plot2.png: render first, then apply the same value with JS.
fig.data[0].marker.colorbar.tickfont.textcase = None
html2 = pio.to_html(fig, full_html=True, include_plotlyjs="cdn")
restyle_script = """
<script>
window.addEventListener("DOMContentLoaded", () => {
    const plotEl = document.getElementsByClassName("plotly-graph-div")[0];
    setTimeout(() => {
        Plotly.restyle(plotEl, {"marker.colorbar.tickfont.textcase": "normal"}, [0]);
    }, 1000);
});
</script>
"""
html2 = html2.replace("</body>", restyle_script + "\n</body>")


with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=True,
        args=["--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu"],
    )
    page = browser.new_page(viewport={"width": 1200, "height": 800})

    page.set_content(html1)
    time.sleep(1.5)
    img1 = page.screenshot(path="plot1.png")

    page.set_content(html2)
    time.sleep(2.0)
    img2 = page.screenshot(path="plot2.png")

    browser.close()

diff = ImageChops.difference(
    Image.open(io.BytesIO(img1)).convert("RGB"),
    Image.open(io.BytesIO(img2)).convert("RGB"),
)

print("Saved plot1.png and plot2.png")
assert diff.getbbox() is not None, "Reproducer did not reproduce: images are identical."

Notes

Add info here that doesn't fit in the other sections.

贡献者指南