From e8d3640c260d7b0e8349e062deedbacf400a34e9 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 6 Jun 2019 15:23:32 +0200 Subject: [PATCH] Improve(?) implementation of secondary_axis. Currently, secondary_xaxis is implemented by adding a child axes with a physical height set to zero and y position set accordingly relative to its parent axes (using the axes_locator mechanism). This patch changes it so that the child axes' extents actually *matches* the parent axes, and instead positioning the spines using Spine.set_position. It also makes sure that the secondary axes patch is invisible and that the bounds of the "orthogonal" axis (the one that is not shown) matches the parent bounds. By doing so, it becomes possible to plot data directly on the secondary axis as well; e.g. the following now works: ``` from matplotlib import pyplot as plt from matplotlib.axes import Axes fig, ax0 = plt.subplots() ax1 = ax0.secondary_xaxis( "top", functions=(lambda x: x**3, lambda x: x**(1/3))) Axes.plot(ax1, [.25, .5, .75], [.25, .5, .75], ".-") plt.show() ``` (We have to use Axes.plot here instead of ax1.plot just because SecondaryAxes inherits from _AxesBase, not from Axes, but that doesn't really matter.) Another advantage is that one can now use secondary_axis as a replacement for SubplotZero, a relatively obscure feature of mpl_toolkits that is only showcased in 3 examples: https://matplotlib.org/gallery/axisartist/simple_axisline.html https://matplotlib.org/gallery/axisartist/simple_axisline2.html https://matplotlib.org/gallery/axisartist/demo_axisline_style.html whose goal is just to draw a spine at x=0 or y=0. simple_axisline2 just moves its main spine to y=0, so we can implement that directly with Spine.set_position (see also simple_axisartist1). simple_axisline adds additional spines, and I added an equivalent implementation using secondary_axis, which is fairly transparent. (This example, as well as test_secondary_xy, show why the axes patch must be made invisible: otherwise, the patch of later secondary axes would be drawn over other secondary axes.) demo_axisline_style doesn't showcase anything that's specifically related to SubplotZero so I just rewrote it without SubplotZero. If we agree that secondary_axis is a suitable replacement, SubplotZero could ultimately be deprecated (in a later PR). Minor points: Also delete `SecondaryAxis._loc`, which is set in a single place and never used. --- examples/axisartist/demo_axisline_style.py | 13 ++++------ examples/axisartist/simple_axisartist1.py | 9 ++++--- examples/axisartist/simple_axisline.py | 21 ++++++++++++++++ examples/axisartist/simple_axisline2.py | 11 +++++---- lib/matplotlib/axes/_secondary_axes.py | 28 ++++++++-------------- 5 files changed, 48 insertions(+), 34 deletions(-) diff --git a/examples/axisartist/demo_axisline_style.py b/examples/axisartist/demo_axisline_style.py index e3159c9c054f..7196bd659fb0 100644 --- a/examples/axisartist/demo_axisline_style.py +++ b/examples/axisartist/demo_axisline_style.py @@ -11,24 +11,19 @@ example. """ -from mpl_toolkits.axisartist.axislines import SubplotZero +from mpl_toolkits.axisartist.axislines import Subplot import matplotlib.pyplot as plt import numpy as np fig = plt.figure() -ax = SubplotZero(fig, 111) +ax = Subplot(fig, 111) fig.add_subplot(ax) -for direction in ["xzero", "yzero"]: - # adds arrows at the ends of each axis +for direction in ["bottom", "left"]: # Add arrows for bottom and left axes. ax.axis[direction].set_axisline_style("-|>") - # adds X and Y-axis from the origin - ax.axis[direction].set_visible(True) - -for direction in ["left", "right", "bottom", "top"]: - # hides borders +for direction in ["top", "right"]: # Hide top and right axes. ax.axis[direction].set_visible(False) x = np.linspace(-0.5, 1., 100) diff --git a/examples/axisartist/simple_axisartist1.py b/examples/axisartist/simple_axisartist1.py index 3c702c347425..13becddc2cc9 100644 --- a/examples/axisartist/simple_axisartist1.py +++ b/examples/axisartist/simple_axisartist1.py @@ -1,9 +1,12 @@ """ -================== -Simple Axisartist1 -================== +======================================== +Using axisartist to place an axis at y=0 +======================================== +Note that the following example can also be implemented without mpl_toolkits; +see :doc:`/gallery/ticks_and_spines/spine_placement_demo`. """ + import matplotlib.pyplot as plt from mpl_toolkits import axisartist diff --git a/examples/axisartist/simple_axisline.py b/examples/axisartist/simple_axisline.py index 1d48d9f3ff8a..51572f98cfea 100644 --- a/examples/axisartist/simple_axisline.py +++ b/examples/axisartist/simple_axisline.py @@ -34,4 +34,25 @@ ax.axis["right2"].label.set_text("Label Y2") ax.plot([-2, 3, 2]) + +############################################################################### +# Or, without axisartist, one can use secondary axes to add the additional +# axes: + +fig, ax = plt.subplots() +fig.subplots_adjust(right=0.85) + +ax.spines["top"].set_visible(False) +ax.spines["right"].set_visible(False) + +ax1 = ax.secondary_xaxis(0) +ax1.spines["bottom"].set_position(("data", 0)) +ax1.set_xlabel("Axis Zero") + +ax2 = ax.secondary_yaxis(1) +ax2.spines["right"].set_position(("outward", 20)) +ax2.set_ylabel("Label Y2") + +ax.plot([-2, 3, 2]) + plt.show() diff --git a/examples/axisartist/simple_axisline2.py b/examples/axisartist/simple_axisline2.py index c0523f33da54..a0cb8e3d61e5 100644 --- a/examples/axisartist/simple_axisline2.py +++ b/examples/axisartist/simple_axisline2.py @@ -1,14 +1,17 @@ """ -================ -Simple Axisline2 -================ +=================== +SubplotZero example +=================== +Note that the following example can also be implemented without mpl_toolkits; +see :doc:`/gallery/ticks_and_spines/spine_placement_demo`. """ + import matplotlib.pyplot as plt from mpl_toolkits.axisartist.axislines import SubplotZero import numpy as np -fig = plt.figure(figsize=(4, 3)) +fig = plt.figure() # a subplot with two additional axis, "xzero" and "yzero". "xzero" is # y=0 line, and "yzero" is x=0 line. diff --git a/lib/matplotlib/axes/_secondary_axes.py b/lib/matplotlib/axes/_secondary_axes.py index cfa5bc676d31..91c21010bdd1 100644 --- a/lib/matplotlib/axes/_secondary_axes.py +++ b/lib/matplotlib/axes/_secondary_axes.py @@ -40,6 +40,10 @@ def __init__(self, parent, orientation, location, functions, **kwargs): self._layoutbox = None self._poslayoutbox = None + # Make this axes always follow the parent axes' position. + self.set_axes_locator( + _axes._InsetLocator([0, 0, 1, 1], self._parent.transAxes)) + self.set_location(location) self.set_functions(functions) @@ -52,6 +56,7 @@ def __init__(self, parent, orientation, location, functions, **kwargs): otheraxis.set_major_locator(mticker.NullLocator()) otheraxis.set_ticks_position('none') + self.patch.set_visible(False) for st in self._otherstrings: self.spines[st].set_visible(False) for st in self._locstrings: @@ -95,8 +100,7 @@ def set_location(self, location): parent axes to put the new axes, 0.0 being the bottom (or left) and 1.0 being the top (or right). """ - - # This puts the rectangle into figure-relative coordinates. + # Put the spine into axes-relative coordinates. if isinstance(location, str): if location in ['top', 'right']: self._pos = 1. @@ -108,22 +112,8 @@ def set_location(self, location): f"{self._locstrings[1]!r}, or a float, not {location!r}") else: self._pos = location - self._loc = location - - if self._orientation == 'x': - # An x-secondary axes is like an inset axes from x = 0 to x = 1 and - # from y = pos to y = pos + eps, in the parent's transAxes coords. - bounds = [0, self._pos, 1., 1e-10] - else: - bounds = [self._pos, 0, 1e-10, 1] - - secondary_locator = _axes._InsetLocator(bounds, self._parent.transAxes) - - # this locator lets the axes move in the parent axes coordinates. - # so it never needs to know where the parent is explicitly in - # figure coordinates. - # it gets called in `ax.apply_aspect() (of all places) - self.set_axes_locator(secondary_locator) + for loc in self._locstrings: + self.spines[loc].set_position(('axes', self._pos)) def apply_aspect(self, position=None): # docstring inherited. @@ -235,9 +225,11 @@ def _set_lims(self): if self._orientation == 'x': lims = self._parent.get_xlim() set_lim = self.set_xlim + self.set_ylim(self._parent.get_ylim()) if self._orientation == 'y': lims = self._parent.get_ylim() set_lim = self.set_ylim + self.set_xlim(self._parent.get_xlim()) order = lims[0] < lims[1] lims = self._functions[0](np.array(lims)) neworder = lims[0] < lims[1]