Skip to content

[BUG]: Sunburst marker.colors reassignment changes segment colors #5620

@ym-xie

Description

@ym-xie

Description

A Plotly Express sunburst chart changes color assignment when marker.colors is
reassigned to its current value.

The chart structure and labels stay the same, but the segment colors differ. This
looks like marker.colors is not idempotent for sunburst traces: explicitly assigning
the existing color tuple changes the color resolution path.

Screenshots/Video

Image Image

Steps to reproduce

import io
import json

import plotly
import plotly.express as px
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():
    df = px.data.tips()
    return px.sunburst(
        df,
        path=["sex", "day", "time"],
        values="total_bill",
        color="time",
    )


colors = tuple(make_fig().data[0].marker.colors)

# Case 1: reassign marker.colors before the initial render.
fig1 = make_fig()
fig1.data[0].marker.colors = colors
assert tuple(fig1.data[0].marker.colors) == colors
html1 = pio.to_html(fig1, full_html=True, include_plotlyjs=True)

# Case 2: render first, then reassign the same value with Plotly.restyle.
fig2 = make_fig()
html2 = pio.to_html(fig2, full_html=True, include_plotlyjs=True)
html2 = html2.replace(
    "</body>",
    f"""
<script>
window.addEventListener("DOMContentLoaded", () => {{
    const plotEl = document.getElementsByClassName("plotly-graph-div")[0];
    setTimeout(() => {{
        Plotly.restyle(plotEl, {{"marker.colors": {json.dumps(colors)}}}, [0]);
    }}, 1000);
}});
</script>
</body>""",
)

with sync_playwright() as playwright:
    browser = playwright.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, wait_until="domcontentloaded")
    page.wait_for_timeout(1500)
    img1 = page.screenshot(path="plot1.png")

    page.set_content(html2, wait_until="domcontentloaded")
    page.wait_for_timeout(2000)
    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 (Python-side reassignment before render)")
print("Saved plot2.png (JS-side restyle after render)")
assert diff.getbbox() is not None, "Reproducer did not reproduce: images are identical."
print("Bug reproduced: the two renderings differ.")

Notes

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugsomething broken

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions