From 3027f17a867b91083de7e4fbce9521bf2c83ac9b Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Thu, 7 Aug 2025 16:09:58 +0100 Subject: [PATCH 1/2] Fix tick marker mirroring --- plotly/matplotlylib/mpltools.py | 13 ++-- plotly/matplotlylib/renderer.py | 12 +++- plotly/matplotlylib/tests/test_renderer.py | 74 ++++++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/plotly/matplotlylib/mpltools.py b/plotly/matplotlylib/mpltools.py index 0a3206998b..351f7a880f 100644 --- a/plotly/matplotlylib/mpltools.py +++ b/plotly/matplotlylib/mpltools.py @@ -250,15 +250,12 @@ def get_axes_bounds(fig): return (x_min, x_max), (y_min, y_max) -def get_axis_mirror(main_spine, mirror_spine): - if main_spine and mirror_spine: +def get_axis_mirror(main_spine, mirror_spine, main_tick_markers, mirror_tick_markers): + if main_spine and mirror_spine and main_tick_markers and mirror_tick_markers: return "ticks" - elif main_spine and not mirror_spine: - return False - elif not main_spine and mirror_spine: - return False # can't handle this case yet! - else: - return False # nuttin'! + if main_spine and mirror_spine: + return True + return False def get_bar_gap(bar_starts, bar_ends, tol=1e-10): diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index a282c67cec..29c541695b 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -183,8 +183,16 @@ def open_axes(self, ax, props): top_spine = mpltools.get_spine_visible(ax, "top") left_spine = mpltools.get_spine_visible(ax, "left") right_spine = mpltools.get_spine_visible(ax, "right") - xaxis["mirror"] = mpltools.get_axis_mirror(bottom_spine, top_spine) - yaxis["mirror"] = mpltools.get_axis_mirror(left_spine, right_spine) + bottom_tick_markers = ax.xaxis.get_tick_params()["bottom"] + top_tick_markers = ax.xaxis.get_tick_params()["top"] + left_tick_markers = ax.yaxis.get_tick_params()["left"] + right_tick_markers = ax.yaxis.get_tick_params()["right"] + xaxis["mirror"] = mpltools.get_axis_mirror( + bottom_spine, top_spine, bottom_tick_markers, top_tick_markers + ) + yaxis["mirror"] = mpltools.get_axis_mirror( + left_spine, right_spine, left_tick_markers, right_tick_markers + ) xaxis["showline"] = bottom_spine yaxis["showline"] = top_spine diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index f56d830917..e823371b3d 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -199,3 +199,77 @@ 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_axis_mirror_with_spines_and_ticks(): + """Test that mirror=True when both spines and ticks are visible on both sides.""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + # Show all spines + ax.spines["top"].set_visible(True) + ax.spines["bottom"].set_visible(True) + ax.spines["left"].set_visible(True) + ax.spines["right"].set_visible(True) + + # Show ticks on all sides + ax.tick_params(top=True, bottom=True, left=True, right=True) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.mirror == "ticks" + assert plotly_fig.layout.yaxis.mirror == "ticks" + + +def test_axis_mirror_with_ticks_only(): + """Test that mirror=False when spines are not visible on both sides.""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + # Hide opposite spines + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + + # Show ticks on all sides + ax.tick_params(top=True, bottom=True, left=True, right=True) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.mirror == False + assert plotly_fig.layout.yaxis.mirror == False + + +def test_axis_mirror_false_with_one_sided_ticks(): + """Test that mirror=True when ticks are only on one side but spines are + visible on both sides.""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + # Default matplotlib behavior - ticks only on bottom and left + ax.tick_params(top=False, bottom=True, left=True, right=False) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.mirror == True + assert plotly_fig.layout.yaxis.mirror == True + + +def test_axis_mirror_mixed_configurations(): + """Test different configurations for x and y axes.""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + # X-axis: spines and ticks on both sides (mirror="ticks") + ax.spines["top"].set_visible(True) + ax.spines["bottom"].set_visible(True) + ax.tick_params(top=True, bottom=True) + + # Y-axis: spine only on one side (mirror=False) + ax.spines["right"].set_visible(False) + ax.spines["left"].set_visible(True) + ax.tick_params(left=True, right=True) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.mirror == "ticks" + assert plotly_fig.layout.yaxis.mirror == False From c14f3f758af9cb5e36246ca25a4ed48ee4fcef72 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Mon, 10 Aug 2026 14:16:19 +0000 Subject: [PATCH 2/2] Fix y-axis showline and hide tick markers when mpl ticks are hidden --- plotly/matplotlylib/renderer.py | 7 +++- plotly/matplotlylib/tests/test_renderer.py | 43 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index 29c541695b..78b8a9895f 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -194,7 +194,12 @@ def open_axes(self, ax, props): left_spine, right_spine, left_tick_markers, right_tick_markers ) xaxis["showline"] = bottom_spine - yaxis["showline"] = top_spine + yaxis["showline"] = left_spine + # hide tick markers when the mpl main-side tick markers are hidden + if not bottom_tick_markers: + xaxis["ticks"] = "" + if not left_tick_markers: + yaxis["ticks"] = "" # put axes in our figure self.plotly_fig["layout"]["xaxis{0}".format(self.axis_ct)] = xaxis diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index e823371b3d..825b5760ff 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -273,3 +273,46 @@ def test_axis_mirror_mixed_configurations(): assert plotly_fig.layout.xaxis.mirror == "ticks" assert plotly_fig.layout.yaxis.mirror == False + + +def test_axis_showline_tied_to_main_spine(): + """Test that showline follows the main-side spine (bottom for x, left for y).""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + # Hide the mirror-side spines only + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.showline == True + assert plotly_fig.layout.yaxis.showline == True + + +def test_axis_showline_hidden_when_main_spine_hidden(): + """Test that showline is False when the main-side spine is hidden.""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + # Hide the main-side spines but keep the mirror-side ones + ax.spines["bottom"].set_visible(False) + ax.spines["left"].set_visible(False) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.showline == False + assert plotly_fig.layout.yaxis.showline == False + + +def test_ticks_hidden_when_mpl_main_ticks_hidden(): + """Test that tick markers are hidden when the mpl main-side ticks are hidden.""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + ax.tick_params(top=False, bottom=False, left=False, right=False) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.ticks == "" + assert plotly_fig.layout.yaxis.ticks == ""