Skip to content

Commit cfcd9a7

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 db2193c commit cfcd9a7

File tree

5 files changed

+50
-58
lines changed

5 files changed

+50
-58
lines changed

examples/axisartist/demo_axisline_style.py

+4-9
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,19 @@
66
This example shows some configurations for axis style.
77
"""
88

9-
from mpl_toolkits.axisartist.axislines import SubplotZero
9+
from mpl_toolkits.axisartist.axislines import Subplot
1010
import matplotlib.pyplot as plt
1111
import numpy as np
1212

1313

1414
fig = plt.figure()
15-
ax = SubplotZero(fig, 111)
15+
ax = Subplot(fig, 111)
1616
fig.add_subplot(ax)
1717

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

22-
# adds X and Y-axis from the origin
23-
ax.axis[direction].set_visible(True)
24-
25-
for direction in ["left", "right", "bottom", "top"]:
26-
# hides borders
21+
for direction in ["top", "right"]: # Hide top and right axes.
2722
ax.axis[direction].set_visible(False)
2823

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

examples/axisartist/simple_axisartist1.py

+6-3
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
import mpl_toolkits.axisartist as AA
912

examples/axisartist/simple_axisline.py

+21
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,25 @@
3737
ax.axis["right2"].label.set_text("Label Y2")
3838

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

examples/axisartist/simple_axisline2.py

+7-4
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

+12-42
Original file line numberDiff line numberDiff line change
@@ -27,33 +27,6 @@
2727
from matplotlib.scale import Log10Transform
2828

2929

30-
def _make_secondary_locator(rect, parent):
31-
"""
32-
Helper function to locate the secondary axes.
33-
34-
A locator gets used in `Axes.set_aspect` to override the default
35-
locations... It is a function that takes an axes object and
36-
a renderer and tells `set_aspect` where it is to be placed.
37-
38-
This locator make the transform be in axes-relative co-coordinates
39-
because that is how we specify the "location" of the secondary axes.
40-
41-
Here *rect* is a rectangle [l, b, w, h] that specifies the
42-
location for the axes in the transform given by *trans* on the
43-
*parent*.
44-
"""
45-
_rect = mtransforms.Bbox.from_bounds(*rect)
46-
def secondary_locator(ax, renderer):
47-
# delay evaluating transform until draw time because the
48-
# parent transform may have changed (i.e. if window reesized)
49-
bb = mtransforms.TransformedBbox(_rect, parent.transAxes)
50-
tr = parent.figure.transFigure.inverted()
51-
bb = mtransforms.TransformedBbox(bb, tr)
52-
return bb
53-
54-
return secondary_locator
55-
56-
5730
class SecondaryAxis(_AxesBase):
5831
"""
5932
General class to hold a Secondary_X/Yaxis.
@@ -86,6 +59,12 @@ def __init__(self, parent, orientation,
8659
self._layoutbox = None
8760
self._poslayoutbox = None
8861

62+
# Make this axes always follow the parent axes' position.
63+
self.set_axes_locator(
64+
lambda ax, renderer:
65+
self.figure.transFigure.inverted()
66+
.transform_bbox(self._parent.bbox))
67+
8968
self.set_location(location)
9069
self.set_functions(functions)
9170

@@ -98,6 +77,7 @@ def __init__(self, parent, orientation,
9877
otheraxis.set_major_locator(mticker.NullLocator())
9978
otheraxis.set_ticks_position('none')
10079

80+
self.patch.set_visible(False)
10181
for st in self._otherstrings:
10282
self.spines[st].set_visible(False)
10383
for st in self._locstrings:
@@ -161,20 +141,8 @@ def set_location(self, location):
161141
self._locstrings[0], self._locstrings[1]))
162142
else:
163143
self._pos = location
164-
self._loc = location
165-
166-
if self._orientation == 'x':
167-
bounds = [0, self._pos, 1., 1e-10]
168-
else:
169-
bounds = [self._pos, 0, 1e-10, 1]
170-
171-
secondary_locator = _make_secondary_locator(bounds, self._parent)
172-
173-
# this locator lets the axes move in the parent axes coordinates.
174-
# so it never needs to know where the parent is explicitly in
175-
# figure co-ordinates.
176-
# it gets called in `ax.apply_aspect() (of all places)
177-
self.set_axes_locator(secondary_locator)
144+
for loc in self._locstrings:
145+
self.spines[loc].set_position(('axes', self._pos))
178146

179147
def apply_aspect(self, position=None):
180148
self._set_lims()
@@ -290,14 +258,16 @@ def _set_scale(self):
290258
def _set_lims(self):
291259
"""
292260
Set the limits based on parent limits and the convert method
293-
between the parent and this secondary axes
261+
between the parent and this secondary axes.
294262
"""
295263
if self._orientation == 'x':
296264
lims = self._parent.get_xlim()
297265
set_lim = self.set_xlim
266+
self.set_ylim(self._parent.get_ylim())
298267
if self._orientation == 'y':
299268
lims = self._parent.get_ylim()
300269
set_lim = self.set_ylim
270+
self.set_xlim(self._parent.get_xlim())
301271
order = lims[0] < lims[1]
302272
lims = self._functions[0](np.array(lims))
303273
neworder = lims[0] < lims[1]

0 commit comments

Comments
 (0)