Skip to content

Commit e8d3640

Browse files
committed
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.
1 parent 019a752 commit e8d3640

File tree

5 files changed

+48
-34
lines changed

5 files changed

+48
-34
lines changed

examples/axisartist/demo_axisline_style.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,19 @@
1111
example.
1212
"""
1313

14-
from mpl_toolkits.axisartist.axislines import SubplotZero
14+
from mpl_toolkits.axisartist.axislines import Subplot
1515
import matplotlib.pyplot as plt
1616
import numpy as np
1717

1818

1919
fig = plt.figure()
20-
ax = SubplotZero(fig, 111)
20+
ax = Subplot(fig, 111)
2121
fig.add_subplot(ax)
2222

23-
for direction in ["xzero", "yzero"]:
24-
# adds arrows at the ends of each axis
23+
for direction in ["bottom", "left"]: # Add arrows for bottom and left axes.
2524
ax.axis[direction].set_axisline_style("-|>")
2625

27-
# adds X and Y-axis from the origin
28-
ax.axis[direction].set_visible(True)
29-
30-
for direction in ["left", "right", "bottom", "top"]:
31-
# hides borders
26+
for direction in ["top", "right"]: # Hide top and right axes.
3227
ax.axis[direction].set_visible(False)
3328

3429
x = np.linspace(-0.5, 1., 100)

examples/axisartist/simple_axisartist1.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""
2-
==================
3-
Simple Axisartist1
4-
==================
2+
========================================
3+
Using axisartist to place an axis at y=0
4+
========================================
55
6+
Note that the following example can also be implemented without mpl_toolkits;
7+
see :doc:`/gallery/ticks_and_spines/spine_placement_demo`.
68
"""
9+
710
import matplotlib.pyplot as plt
811
from mpl_toolkits import axisartist
912

examples/axisartist/simple_axisline.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,25 @@
3434
ax.axis["right2"].label.set_text("Label Y2")
3535

3636
ax.plot([-2, 3, 2])
37+
38+
###############################################################################
39+
# Or, without axisartist, one can use secondary axes to add the additional
40+
# axes:
41+
42+
fig, ax = plt.subplots()
43+
fig.subplots_adjust(right=0.85)
44+
45+
ax.spines["top"].set_visible(False)
46+
ax.spines["right"].set_visible(False)
47+
48+
ax1 = ax.secondary_xaxis(0)
49+
ax1.spines["bottom"].set_position(("data", 0))
50+
ax1.set_xlabel("Axis Zero")
51+
52+
ax2 = ax.secondary_yaxis(1)
53+
ax2.spines["right"].set_position(("outward", 20))
54+
ax2.set_ylabel("Label Y2")
55+
56+
ax.plot([-2, 3, 2])
57+
3758
plt.show()

examples/axisartist/simple_axisline2.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
"""
2-
================
3-
Simple Axisline2
4-
================
2+
===================
3+
SubplotZero example
4+
===================
55
6+
Note that the following example can also be implemented without mpl_toolkits;
7+
see :doc:`/gallery/ticks_and_spines/spine_placement_demo`.
68
"""
9+
710
import matplotlib.pyplot as plt
811
from mpl_toolkits.axisartist.axislines import SubplotZero
912
import numpy as np
1013

11-
fig = plt.figure(figsize=(4, 3))
14+
fig = plt.figure()
1215

1316
# a subplot with two additional axis, "xzero" and "yzero". "xzero" is
1417
# y=0 line, and "yzero" is x=0 line.

lib/matplotlib/axes/_secondary_axes.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ def __init__(self, parent, orientation, location, functions, **kwargs):
4040
self._layoutbox = None
4141
self._poslayoutbox = None
4242

43+
# Make this axes always follow the parent axes' position.
44+
self.set_axes_locator(
45+
_axes._InsetLocator([0, 0, 1, 1], self._parent.transAxes))
46+
4347
self.set_location(location)
4448
self.set_functions(functions)
4549

@@ -52,6 +56,7 @@ def __init__(self, parent, orientation, location, functions, **kwargs):
5256
otheraxis.set_major_locator(mticker.NullLocator())
5357
otheraxis.set_ticks_position('none')
5458

59+
self.patch.set_visible(False)
5560
for st in self._otherstrings:
5661
self.spines[st].set_visible(False)
5762
for st in self._locstrings:
@@ -95,8 +100,7 @@ def set_location(self, location):
95100
parent axes to put the new axes, 0.0 being the bottom (or left)
96101
and 1.0 being the top (or right).
97102
"""
98-
99-
# This puts the rectangle into figure-relative coordinates.
103+
# Put the spine into axes-relative coordinates.
100104
if isinstance(location, str):
101105
if location in ['top', 'right']:
102106
self._pos = 1.
@@ -108,22 +112,8 @@ def set_location(self, location):
108112
f"{self._locstrings[1]!r}, or a float, not {location!r}")
109113
else:
110114
self._pos = location
111-
self._loc = location
112-
113-
if self._orientation == 'x':
114-
# An x-secondary axes is like an inset axes from x = 0 to x = 1 and
115-
# from y = pos to y = pos + eps, in the parent's transAxes coords.
116-
bounds = [0, self._pos, 1., 1e-10]
117-
else:
118-
bounds = [self._pos, 0, 1e-10, 1]
119-
120-
secondary_locator = _axes._InsetLocator(bounds, self._parent.transAxes)
121-
122-
# this locator lets the axes move in the parent axes coordinates.
123-
# so it never needs to know where the parent is explicitly in
124-
# figure coordinates.
125-
# it gets called in `ax.apply_aspect() (of all places)
126-
self.set_axes_locator(secondary_locator)
115+
for loc in self._locstrings:
116+
self.spines[loc].set_position(('axes', self._pos))
127117

128118
def apply_aspect(self, position=None):
129119
# docstring inherited.
@@ -235,9 +225,11 @@ def _set_lims(self):
235225
if self._orientation == 'x':
236226
lims = self._parent.get_xlim()
237227
set_lim = self.set_xlim
228+
self.set_ylim(self._parent.get_ylim())
238229
if self._orientation == 'y':
239230
lims = self._parent.get_ylim()
240231
set_lim = self.set_ylim
232+
self.set_xlim(self._parent.get_xlim())
241233
order = lims[0] < lims[1]
242234
lims = self._functions[0](np.array(lims))
243235
neworder = lims[0] < lims[1]

0 commit comments

Comments
 (0)