From 269ae8c0fe74780c015a7a3ca876b21d269aa059 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Tue, 11 Aug 2026 09:38:16 +0000 Subject: [PATCH] Convert matplotlib stairs plots to plotly step lines --- plotly/matplotlylib/renderer.py | 36 ++++++++++++++++++++++ plotly/matplotlylib/tests/test_renderer.py | 11 +++++++ 2 files changed, 47 insertions(+) diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index a282c67cec..53705f47ae 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -9,6 +9,7 @@ import warnings +import matplotlib.patches as mpatches import plotly.graph_objs as go from plotly.matplotlylib.mplexporter import Renderer from plotly.matplotlylib import mpltools @@ -599,6 +600,9 @@ def draw_path(self, **props): is_bar = mpltools.is_bar(self.current_mpl_ax.containers, **props) if is_bar: self.current_bars += [props] + elif isinstance(props["mplobj"], mpatches.StepPatch): + self.msg += " Drawing a step path\n" + self._draw_step_path(props) else: self.msg += " This path isn't a bar, not drawing\n" warnings.warn( @@ -606,6 +610,38 @@ def draw_path(self, **props): "of a bar chart. Ignoring." ) + def _draw_step_path(self, props): + """Draw a matplotlib StepPatch as a step line trace.""" + if props["coordinates"] != "data": + self.msg += " Step path is not in data coordinates, not drawing\n" + return + style = props["style"] + x = [] + y = [] + for x0, y0 in props["data"]: + if not x or x0 != x[-1] or y0 != y[-1]: + x.append(x0) + y.append(y0) + if len(x) < 2: + self.msg += " Step path has fewer than 2 points, not drawing\n" + return + self.plotly_fig.add_trace( + go.Scatter( + x=x, + y=y, + mode="lines", + line=go.scatter.Line( + color=mpltools.merge_color_and_opacity( + style["edgecolor"], style["alpha"] + ), + width=style["edgewidth"], + dash=mpltools.convert_dash(style["dasharray"]), + ), + xaxis="x{0}".format(self.axis_ct), + yaxis="y{0}".format(self.axis_ct), + ) + ) + def draw_text(self, **props): """Create an annotation dict for a text obj. diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index f56d830917..136117a0b4 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -199,3 +199,14 @@ def test_filled_path_collection_date_xaxis(): filled = [t for t in plotly_fig.data if t.fill == "toself"] assert len(filled) >= 1 assert all(isinstance(x, str) for x in filled[0].x) + + +def test_stairs_converts_to_step_line(): + fig, ax = plt.subplots() + ax.stairs([0.0, 1.0, 0.0], [0.0, 1.0, 2.0, 3.0]) + plotly_fig = tls.mpl_to_plotly(fig) + assert len(plotly_fig.data) == 1 + trace = plotly_fig.data[0] + assert trace.mode == "lines" + assert tuple(trace.x) == (0.0, 1.0, 1.0, 2.0, 2.0, 3.0) + assert tuple(trace.y) == (0.0, 0.0, 1.0, 1.0, 0.0, 0.0)