diff --git a/CHANGELOG.md b/CHANGELOG.md index cae0a8a502..4f7f0f3d11 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 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 bb62dc36b4..8ddeb25bf4 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -210,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 0b81f1e184..2439a8f193 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -518,6 +518,54 @@ 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_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."""