From 9044dc564d43d3f098596b0b315dde20379e816c Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Fri, 8 Aug 2025 12:32:00 +0100 Subject: [PATCH 1/3] Set axis linecolor from matplotlib spine colors --- plotly/matplotlylib/renderer.py | 10 ++++++++-- plotly/matplotlylib/tests/test_renderer.py | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index bb62dc36b4..c29714907a 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -199,10 +199,16 @@ def open_axes(self, ax, props): self.plotly_fig["layout"].plot_bgcolor = _export_color(props["axesbg"]) # set defaults in axes xaxis = go.layout.XAxis( - anchor="y{0}".format(self.axis_ct), zeroline=False, ticks="inside" + anchor="y{0}".format(self.axis_ct), + zeroline=False, + ticks="inside", + linecolor=_export_color(ax.spines["bottom"].get_edgecolor()), ) yaxis = go.layout.YAxis( - anchor="x{0}".format(self.axis_ct), zeroline=False, ticks="inside" + anchor="x{0}".format(self.axis_ct), + zeroline=False, + ticks="inside", + linecolor=_export_color(ax.spines["left"].get_edgecolor()), ) # update defaults with things set in mpl mpl_xaxis, mpl_yaxis = mpltools.prep_xy_axis( diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index 0b81f1e184..d1dd51e8de 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -518,6 +518,28 @@ def test_contourf_bands_render(): assert len(filled) > 0 +def test_axis_linecolor_defaults_to_black(): + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.linecolor == "#000000" + assert plotly_fig.layout.yaxis.linecolor == "#000000" + + +def test_custom_axis_linecolors_are_preserved(): + fig, ax = plt.subplots() + ax.spines["bottom"].set_color("red") + ax.spines["left"].set_color("green") + ax.plot([0, 1], [0, 1]) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.linecolor == "#FF0000" + assert plotly_fig.layout.yaxis.linecolor == "#008000" + + def test_filled_path_collection_date_xaxis(): """Filled path collections with date x-values must export date strings, not raw matplotlib date numbers.""" From 1f9fbfa2350e82195fdc499dbfe8693cad494a29 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Thu, 8 Oct 2026 19:01:47 +0000 Subject: [PATCH 2/3] Add changelog entry for axis line color conversion --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cae0a8a502..733a1dcc6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fix `mpl_to_plotly` tick marker mirroring, axis positioning, and trace visibility for twinned and overlaid subplots (such as `twinx` and `twiny`) [[#5310](https://github.com/plotly/plotly.py/pull/5310)], with thanks to @robertoffmoura for the contribution! +- Fix `mpl_to_plotly` losing matplotlib axis line colors by exporting the bottom and left spine colors to the plotly x and y axis `linecolor` [[#5311](https://github.com/plotly/plotly.py/pull/5311)], with thanks to @robertoffmoura for the contribution! - 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! From 7513b831b3713f049f4d04b96ae8bc45347aaa46 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Thu, 8 Oct 2026 19:14:47 +0000 Subject: [PATCH 3/3] Export axis linecolor from the spine on the displayed side --- CHANGELOG.md | 2 +- plotly/matplotlylib/renderer.py | 14 +++++------- plotly/matplotlylib/tests/test_renderer.py | 26 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 733a1dcc6c..4f7f0f3d11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fix `mpl_to_plotly` tick marker mirroring, axis positioning, and trace visibility for twinned and overlaid subplots (such as `twinx` and `twiny`) [[#5310](https://github.com/plotly/plotly.py/pull/5310)], with thanks to @robertoffmoura for the contribution! -- Fix `mpl_to_plotly` losing matplotlib axis line colors by exporting the bottom and left spine colors to the plotly x and y axis `linecolor` [[#5311](https://github.com/plotly/plotly.py/pull/5311)], with thanks to @robertoffmoura for the contribution! +- Fix `mpl_to_plotly` losing matplotlib axis line colors by exporting the color of each axis's displayed spine to the plotly x and y axis `linecolor` [[#5311](https://github.com/plotly/plotly.py/pull/5311)], with thanks to @robertoffmoura for the contribution! - 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! diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index c29714907a..8ddeb25bf4 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -199,16 +199,10 @@ def open_axes(self, ax, props): self.plotly_fig["layout"].plot_bgcolor = _export_color(props["axesbg"]) # set defaults in axes xaxis = go.layout.XAxis( - anchor="y{0}".format(self.axis_ct), - zeroline=False, - ticks="inside", - linecolor=_export_color(ax.spines["bottom"].get_edgecolor()), + anchor="y{0}".format(self.axis_ct), zeroline=False, ticks="inside" ) yaxis = go.layout.YAxis( - anchor="x{0}".format(self.axis_ct), - zeroline=False, - ticks="inside", - linecolor=_export_color(ax.spines["left"].get_edgecolor()), + anchor="x{0}".format(self.axis_ct), zeroline=False, ticks="inside" ) # update defaults with things set in mpl mpl_xaxis, mpl_yaxis = mpltools.prep_xy_axis( @@ -216,6 +210,10 @@ def open_axes(self, ax, props): ) xaxis.update(mpl_xaxis) yaxis.update(mpl_yaxis) + x_spine = "top" if xaxis["side"] == "top" else "bottom" + y_spine = "right" if yaxis["side"] == "right" else "left" + xaxis["linecolor"] = _export_color(ax.spines[x_spine].get_edgecolor()) + yaxis["linecolor"] = _export_color(ax.spines[y_spine].get_edgecolor()) bottom_spine = mpltools.get_spine_visible(ax, "bottom") top_spine = mpltools.get_spine_visible(ax, "top") left_spine = mpltools.get_spine_visible(ax, "left") diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index d1dd51e8de..2439a8f193 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -540,6 +540,32 @@ def test_custom_axis_linecolors_are_preserved(): assert plotly_fig.layout.yaxis.linecolor == "#008000" +def test_axis_linecolor_follows_right_axis_side(): + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + ax2 = ax.twinx() + ax2.plot([0, 1], [1, 0]) + ax2.spines["right"].set_color("red") + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.yaxis2.side == "right" + assert plotly_fig.layout.yaxis2.linecolor == "#FF0000" + + +def test_axis_linecolor_follows_top_axis_side(): + fig, ax = plt.subplots() + ax.spines["bottom"].set_visible(False) + ax.spines["top"].set_color("red") + ax.xaxis.set_ticks_position("top") + ax.plot([0, 1], [0, 1]) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.side == "top" + assert plotly_fig.layout.xaxis.linecolor == "#FF0000" + + def test_filled_path_collection_date_xaxis(): """Filled path collections with date x-values must export date strings, not raw matplotlib date numbers."""