Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5abcae2

Browse files
authored
Merge pull request #25224 from ZachDaChampion/secondary-axes-trans-arg
Allow passing a transformation to secondary_xaxis/_yaxis
2 parents 6287c4c + c1b51aa commit 5abcae2

File tree

7 files changed

+103
-21
lines changed

7 files changed

+103
-21
lines changed

galleries/examples/subplots_axes_and_figures/secondary_axis.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ def rad2deg(x):
4040
secax.set_xlabel('angle [rad]')
4141
plt.show()
4242

43+
# %%
44+
# By default, the secondary axis is drawn in the Axes coordinate space.
45+
# We can also provide a custom transform to place it in a different
46+
# coordinate space. Here we put the axis at Y = 0 in data coordinates.
47+
48+
fig, ax = plt.subplots(layout='constrained')
49+
x = np.arange(0, 10)
50+
np.random.seed(19680801)
51+
y = np.random.randn(len(x))
52+
ax.plot(x, y)
53+
ax.set_xlabel('X')
54+
ax.set_ylabel('Y')
55+
ax.set_title('Random data')
56+
57+
# Pass ax.transData as a transform to place the axis relative to our data
58+
secax = ax.secondary_xaxis(0, transform=ax.transData)
59+
secax.set_xlabel('Axis at Y = 0')
60+
plt.show()
61+
4362
# %%
4463
# Here is the case of converting from wavenumber to wavelength in a
4564
# log-log scale.

lib/matplotlib/axes/_axes.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ def indicate_inset_zoom(self, inset_ax, **kwargs):
551551
return self.indicate_inset(rect, inset_ax, **kwargs)
552552

553553
@_docstring.dedent_interpd
554-
def secondary_xaxis(self, location, *, functions=None, **kwargs):
554+
def secondary_xaxis(self, location, *, functions=None, transform=None, **kwargs):
555555
"""
556556
Add a second x-axis to this `~.axes.Axes`.
557557
@@ -582,18 +582,30 @@ def invert(x):
582582
secax = ax.secondary_xaxis('top', functions=(invert, invert))
583583
secax.set_xlabel('Period [s]')
584584
plt.show()
585+
586+
To add a secondary axis relative to your data, you can pass a transform
587+
to the new axis.
588+
589+
.. plot::
590+
591+
fig, ax = plt.subplots()
592+
ax.plot(range(0, 5), range(-1, 4))
593+
594+
# Pass 'ax.transData' as a transform to place the axis
595+
# relative to your data at y=0
596+
secax = ax.secondary_xaxis(0, transform=ax.transData)
585597
"""
586-
if location in ['top', 'bottom'] or isinstance(location, Real):
587-
secondary_ax = SecondaryAxis(self, 'x', location, functions,
588-
**kwargs)
589-
self.add_child_axes(secondary_ax)
590-
return secondary_ax
591-
else:
598+
if not (location in ['top', 'bottom'] or isinstance(location, Real)):
592599
raise ValueError('secondary_xaxis location must be either '
593600
'a float or "top"/"bottom"')
594601

602+
secondary_ax = SecondaryAxis(self, 'x', location, functions,
603+
transform, **kwargs)
604+
self.add_child_axes(secondary_ax)
605+
return secondary_ax
606+
595607
@_docstring.dedent_interpd
596-
def secondary_yaxis(self, location, *, functions=None, **kwargs):
608+
def secondary_yaxis(self, location, *, functions=None, transform=None, **kwargs):
597609
"""
598610
Add a second y-axis to this `~.axes.Axes`.
599611
@@ -614,16 +626,28 @@ def secondary_yaxis(self, location, *, functions=None, **kwargs):
614626
secax = ax.secondary_yaxis('right', functions=(np.deg2rad,
615627
np.rad2deg))
616628
secax.set_ylabel('radians')
629+
630+
To add a secondary axis relative to your data, you can pass a transform
631+
to the new axis.
632+
633+
.. plot::
634+
635+
fig, ax = plt.subplots()
636+
ax.plot(range(0, 5), range(-1, 4))
637+
638+
# Pass 'ax.transData' as a transform to place the axis
639+
# relative to your data at x=3
640+
secax = ax.secondary_yaxis(3, transform=ax.transData)
617641
"""
618-
if location in ['left', 'right'] or isinstance(location, Real):
619-
secondary_ax = SecondaryAxis(self, 'y', location,
620-
functions, **kwargs)
621-
self.add_child_axes(secondary_ax)
622-
return secondary_ax
623-
else:
642+
if not (location in ['left', 'right'] or isinstance(location, Real)):
624643
raise ValueError('secondary_yaxis location must be either '
625644
'a float or "left"/"right"')
626645

646+
secondary_ax = SecondaryAxis(self, 'y', location, functions,
647+
transform, **kwargs)
648+
self.add_child_axes(secondary_ax)
649+
return secondary_ax
650+
627651
@_docstring.dedent_interpd
628652
def text(self, x, y, s, fontdict=None, **kwargs):
629653
"""

lib/matplotlib/axes/_axes.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class Axes(_AxesBase):
9494
]
9595
| Transform
9696
| None = ...,
97+
transform: Transform | None = ...,
9798
**kwargs
9899
) -> SecondaryAxis: ...
99100
def secondary_yaxis(
@@ -105,6 +106,7 @@ class Axes(_AxesBase):
105106
]
106107
| Transform
107108
| None = ...,
109+
transform: Transform | None = ...,
108110
**kwargs
109111
) -> SecondaryAxis: ...
110112
def text(

lib/matplotlib/axes/_secondary_axes.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import numpy as np
44

5-
from matplotlib import _api, _docstring
5+
from matplotlib import _api, _docstring, transforms
66
import matplotlib.ticker as mticker
77
from matplotlib.axes._base import _AxesBase, _TransformedBoundsLocator
88
from matplotlib.axis import Axis
@@ -14,7 +14,8 @@ class SecondaryAxis(_AxesBase):
1414
General class to hold a Secondary_X/Yaxis.
1515
"""
1616

17-
def __init__(self, parent, orientation, location, functions, **kwargs):
17+
def __init__(self, parent, orientation, location, functions, transform=None,
18+
**kwargs):
1819
"""
1920
See `.secondary_xaxis` and `.secondary_yaxis` for the doc string.
2021
While there is no need for this to be private, it should really be
@@ -39,7 +40,7 @@ def __init__(self, parent, orientation, location, functions, **kwargs):
3940
self._parentscale = None
4041
# this gets positioned w/o constrained_layout so exclude:
4142

42-
self.set_location(location)
43+
self.set_location(location, transform)
4344
self.set_functions(functions)
4445

4546
# styling:
@@ -74,7 +75,7 @@ def set_alignment(self, align):
7475
self._axis.set_ticks_position(align)
7576
self._axis.set_label_position(align)
7677

77-
def set_location(self, location):
78+
def set_location(self, location, transform=None):
7879
"""
7980
Set the vertical or horizontal location of the axes in
8081
parent-normalized coordinates.
@@ -87,8 +88,17 @@ def set_location(self, location):
8788
orientation='y'. A float indicates the relative position on the
8889
parent Axes to put the new Axes, 0.0 being the bottom (or left)
8990
and 1.0 being the top (or right).
91+
92+
transform : `.Transform`, optional
93+
Transform for the location to use. Defaults to
94+
the parent's ``transAxes``, so locations are normally relative to
95+
the parent axes.
96+
97+
.. versionadded:: 3.9
9098
"""
9199

100+
_api.check_isinstance((transforms.Transform, None), transform=transform)
101+
92102
# This puts the rectangle into figure-relative coordinates.
93103
if isinstance(location, str):
94104
_api.check_in_list(self._locstrings, location=location)
@@ -106,15 +116,28 @@ def set_location(self, location):
106116
# An x-secondary axes is like an inset axes from x = 0 to x = 1 and
107117
# from y = pos to y = pos + eps, in the parent's transAxes coords.
108118
bounds = [0, self._pos, 1., 1e-10]
119+
120+
# If a transformation is provided, use its y component rather than
121+
# the parent's transAxes. This can be used to place axes in the data
122+
# coords, for instance.
123+
if transform is not None:
124+
transform = transforms.blended_transform_factory(
125+
self._parent.transAxes, transform)
109126
else: # 'y'
110127
bounds = [self._pos, 0, 1e-10, 1]
128+
if transform is not None:
129+
transform = transforms.blended_transform_factory(
130+
transform, self._parent.transAxes) # Use provided x axis
131+
132+
# If no transform is provided, use the parent's transAxes
133+
if transform is None:
134+
transform = self._parent.transAxes
111135

112136
# this locator lets the axes move in the parent axes coordinates.
113137
# so it never needs to know where the parent is explicitly in
114138
# figure coordinates.
115139
# it gets called in ax.apply_aspect() (of all places)
116-
self.set_axes_locator(
117-
_TransformedBoundsLocator(bounds, self._parent.transAxes))
140+
self.set_axes_locator(_TransformedBoundsLocator(bounds, transform))
118141

119142
def apply_aspect(self, position=None):
120143
# docstring inherited.
@@ -278,6 +301,14 @@ def set_color(self, color):
278301
See :doc:`/gallery/subplots_axes_and_figures/secondary_axis`
279302
for examples of making these conversions.
280303
304+
transform : `.Transform`, optional
305+
If specified, *location* will be
306+
placed relative to this transform (in the direction of the axis)
307+
rather than the parent's axis. i.e. a secondary x-axis will
308+
use the provided y transform and the x transform of the parent.
309+
310+
.. versionadded:: 3.9
311+
281312
Returns
282313
-------
283314
ax : axes._secondary_axes.SecondaryAxis

lib/matplotlib/axes/_secondary_axes.pyi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ class SecondaryAxis(_AxesBase):
1818
Callable[[ArrayLike], ArrayLike], Callable[[ArrayLike], ArrayLike]
1919
]
2020
| Transform,
21+
transform: Transform | None = ...,
2122
**kwargs
2223
) -> None: ...
2324
def set_alignment(
2425
self, align: Literal["top", "bottom", "right", "left"]
2526
) -> None: ...
2627
def set_location(
27-
self, location: Literal["top", "bottom", "right", "left"] | float
28+
self,
29+
location: Literal["top", "bottom", "right", "left"] | float,
30+
transform: Transform | None = ...
2831
) -> None: ...
2932
def set_ticks(
3033
self,

lib/matplotlib/tests/test_axes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7549,6 +7549,7 @@ def invert(x):
75497549
secax(0.6, functions=(lambda x: x**2, lambda x: x**(1/2)))
75507550
secax(0.8)
75517551
secax("top" if nn == 0 else "right", functions=_Translation(2))
7552+
secax(6.25, transform=ax.transData)
75527553

75537554

75547555
def test_secondary_fail():
@@ -7560,6 +7561,8 @@ def test_secondary_fail():
75607561
ax.secondary_xaxis('right')
75617562
with pytest.raises(ValueError):
75627563
ax.secondary_yaxis('bottom')
7564+
with pytest.raises(TypeError):
7565+
ax.secondary_xaxis(0.2, transform='error')
75637566

75647567

75657568
def test_secondary_resize():

0 commit comments

Comments
 (0)