From bb9ca36c768931be5feddae9bc4361a8e21faa62 Mon Sep 17 00:00:00 2001 From: anyingiit <49945850+anyingiit@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:22:49 +0800 Subject: [PATCH] Fix px marginal color with named continuous color scales When color is continuous, Plotly Express colors marginal traces with the first entry of color_continuous_scale. For a named scale such as "Viridis" that entry was the first character of the string ("V"), and for a list of [position, color] pairs it was the pair itself, both of which are invalid marker colors. Normalize the scale with ColorscaleValidator before taking its first color. Closes #2059 --- CHANGELOG.md | 1 + plotly/express/_core.py | 8 +++++- tests/test_optional/test_px/test_marginals.py | 26 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32590d344f0..e9fa22b66c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Fix `convert_dash` mapping dotted lines (`linestyle=':'`) to invalid `'circle'` in Plotly, correctly converting them to `'dot'` and preserving custom dash patterns as pixel lists, [[#5719](https://github.com/plotly/plotly.py/pull/5719)] with thanks to @robertoffmoura for the contribution! - Fix `px.sunburst`, `px.treemap` and `px.icicle` listing sectors in a different order on every run when `path` is used with a Polars DataFrame; sectors now follow their order of first appearance for all dataframe backends [[#5766](https://github.com/plotly/plotly.py/pull/5766)], with thanks to @Irahan2 for the contribution! - Fix `mpl_to_plotly` conversion of matplotlib contour lines and line collections: close contour rings ending with `Z` codes, support dash styles, convert date x-axes, separate disjoint subpaths with `None` separators, hide line collection traces from the legend by default, and group consecutive same-style lines into single traces [[#5770](https://github.com/plotly/plotly.py/pull/5770)], with thanks to @robertoffmoura for the contribution! +- Fix Plotly Express marginal plots (`marginal_x`/`marginal_y`) failing with an invalid `marker.color` (such as `'V'` for `"Viridis"`) when `color` is continuous and `color_continuous_scale` is given as a named color scale or as a list of `[position, color]` pairs; the marginal traces now use the first color of the normalized scale [[#5839](https://github.com/plotly/plotly.py/pull/5839)] ## [7.1.0] - 2026-09-15 diff --git a/plotly/express/_core.py b/plotly/express/_core.py index 9994720b094..62ded03fe30 100644 --- a/plotly/express/_core.py +++ b/plotly/express/_core.py @@ -995,7 +995,13 @@ def make_trace_spec(args, constructor, attrs, trace_patch): ): if "marker" not in trace_spec.trace_patch: trace_spec.trace_patch["marker"] = dict() - first_default_color = args["color_continuous_scale"][0] + # color_continuous_scale may be a named scale (e.g. "Viridis") or a + # list of [position, color] pairs, so normalize it before picking + # the first color + colorscale_validator = ColorscaleValidator("colorscale", "make_figure") + first_default_color = colorscale_validator.validate_coerce( + args["color_continuous_scale"] + )[0][1] trace_spec.trace_patch["marker"]["color"] = first_default_color result.append(trace_spec) diff --git a/tests/test_optional/test_px/test_marginals.py b/tests/test_optional/test_px/test_marginals.py index 759104a254f..d33c5c0cd10 100644 --- a/tests/test_optional/test_px/test_marginals.py +++ b/tests/test_optional/test_px/test_marginals.py @@ -204,3 +204,29 @@ def test_unsupported_marginal_raises_clear_error(): # issue 4654 px.scatter(x=[1, 2, 3], y=[2, 3, 4], marginal_y="density") with pytest.raises(ValueError, match="Supported marginal plot types"): px.histogram(x=[1, 2, 3], marginal="density") + + +@pytest.mark.parametrize( + "color_continuous_scale, expected_color", + [ + ("Viridis", px.colors.sequential.Viridis[0]), + ("magma", px.colors.sequential.Magma[0]), + ("Viridis_r", px.colors.sequential.Viridis[-1]), + (["red", "blue"], "red"), + ([[0, "red"], [1, "blue"]], "red"), + ], +) +@pytest.mark.parametrize("marginal", ["histogram", "box", "violin", "rug"]) +def test_marginal_color_with_continuous_color_scale( + color_continuous_scale, expected_color, marginal +): # issue 2059 + fig = px.scatter( + x=[1, 2, 3], + y=[2, 3, 4], + color=[1, 2, 3], + marginal_x=marginal, + marginal_y=marginal, + color_continuous_scale=color_continuous_scale, + ) + assert fig.data[1].marker.color == expected_color + assert fig.data[2].marker.color == expected_color