From f12ed744306d111b2ff82bcb5edda2de6cf0479a Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Mon, 10 Aug 2026 20:45:28 +0000 Subject: [PATCH 1/3] Convert matplotlib step drawstyles to plotly line shapes --- plotly/matplotlylib/mpltools.py | 17 +++++++++++++++++ plotly/matplotlylib/renderer.py | 3 +++ plotly/matplotlylib/tests/test_renderer.py | 16 ++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/plotly/matplotlylib/mpltools.py b/plotly/matplotlylib/mpltools.py index e1198c9792..fbfc77e01e 100644 --- a/plotly/matplotlylib/mpltools.py +++ b/plotly/matplotlylib/mpltools.py @@ -278,6 +278,23 @@ def get_bar_gap(bar_starts, bar_ends, tol=1e-10): return min(max(gap0 / bar_delta, 0.0), 1.0) +DRAWSTYLE_SHAPE_MAP = { + "steps": "vh", + "steps-pre": "vh", + "steps-post": "hv", + "steps-mid": "hvh", +} + + +def convert_drawstyle(drawstyle): + """Convert a matplotlib line drawstyle to a plotly line shape. + + Matplotlib draws steps as vertical/horizontal segments; plotly's + ``line.shape`` expresses the same via "vh", "hv" and "hvh". + """ + return DRAWSTYLE_SHAPE_MAP.get(drawstyle) + + def convert_rgba_array(color_list): clean_color_list = list() for c in color_list: diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index 65bbcfabb1..4c0d7e8b81 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -407,6 +407,9 @@ def draw_marked_line(self, **props): color=color, width=props["linestyle"]["linewidth"], dash=mpltools.convert_dash(props["linestyle"]["dasharray"]), + shape=mpltools.convert_drawstyle( + props["linestyle"]["drawstyle"] + ), ) else: shape = dict( diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index 02dd6cf34a..7cf8cbaf5a 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -37,6 +37,22 @@ def test_no_fake_legend_shapes_with_native_legend(): assert len(plotly_fig.layout.annotations) == 0 +def test_drawstyle_maps_to_line_shape(): + cases = { + "steps-pre": "vh", + "steps": "vh", + "steps-post": "hv", + "steps-mid": "hvh", + } + for drawstyle, shape in cases.items(): + fig, ax = plt.subplots() + ax.plot([0, 1, 2], [0, 1, 0], drawstyle=drawstyle) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.data[0].line.shape == shape + + def test_legend_disabled_when_no_matplotlib_legend(): """Test that legend is not enabled when no matplotlib legend is present.""" fig, ax = plt.subplots() From 0304e152db4ec7b259e885f87447c3d4049650bb Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Sun, 4 Oct 2026 02:26:00 +0100 Subject: [PATCH 2/3] Run ruff format --- plotly/matplotlylib/renderer.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index 4c0d7e8b81..eacb98fc0b 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -407,9 +407,7 @@ def draw_marked_line(self, **props): color=color, width=props["linestyle"]["linewidth"], dash=mpltools.convert_dash(props["linestyle"]["dasharray"]), - shape=mpltools.convert_drawstyle( - props["linestyle"]["drawstyle"] - ), + shape=mpltools.convert_drawstyle(props["linestyle"]["drawstyle"]), ) else: shape = dict( From f902129f18846a6891e0f61a875c9b2cc0cf3715 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Sun, 4 Oct 2026 01:28:09 +0000 Subject: [PATCH 3/3] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea837e3d0b..92ef8247ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fix concurrent first access to lazily initialized graph object properties, which could raise `ValueError("Invalid value")` [[#5691](https://github.com/plotly/plotly.py/pull/5691)], with thanks to @hb1915 for the contribution! - Fix `mpl_to_plotly` crashing on touching bars (such as `plt.hist`) due to floating-point noise producing negative `bargap` values by clamping `bargap` to `[0, 1]` [[#5696](https://github.com/plotly/plotly.py/pull/5696)], with thanks to @robertoffmoura for the contribution! +- Fix `mpl_to_plotly` drawing step plots with diagonal lines instead of vertical/horizontal step segments by mapping matplotlib step drawstyles (`steps-pre`, `steps-post`, `steps-mid`) to plotly line shapes [[#5697](https://github.com/plotly/plotly.py/pull/5697)], 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! ## [7.1.0] - 2026-09-15