Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitdf9e346

Browse files
authored
Merge pull request#11859 from jklymak/enh-secondary-axes
ENH: add secondary x/y axis
2 parents6b68d86 +85c1b99 commitdf9e346

File tree

14 files changed

+845
-24
lines changed

14 files changed

+845
-24
lines changed

‎.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ per-file-ignores =
224224
examples/subplots_axes_and_figures/axes_zoom_effect.py: E402
225225
examples/subplots_axes_and_figures/demo_constrained_layout.py: E402
226226
examples/subplots_axes_and_figures/demo_tight_layout.py: E402
227+
examples/subplots_axes_and_figures/secondary_axis.py: E402
227228
examples/subplots_axes_and_figures/two_scales.py: E402
228229
examples/subplots_axes_and_figures/zoom_inset_axes.py: E402
229230
examples/tests/backend_driver_sgskip.py: E402, E501

‎doc/api/axes_api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ Text and Annotations
188188
Axes.inset_axes
189189
Axes.indicate_inset
190190
Axes.indicate_inset_zoom
191+
Axes.secondary_xaxis
192+
Axes.secondary_yaxis
191193

192194

193195
Fields
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
:orphan:
2+
3+
Secondary x/y Axis support
4+
--------------------------
5+
6+
A new method provides the ability to add a second axis to an existing
7+
axes via `.Axes.secondary_xaxis` and `.Axes.secondary_yaxis`. See
8+
:doc:`/gallery/subplots_axes_and_figures/secondary_axis` for examples.
9+
10+
..plot::
11+
12+
import matplotlib.pyplot as plt
13+
14+
fig, ax = plt.subplots(figsize=(5, 3))
15+
ax.plot(range(360))
16+
ax.secondary_xaxis('top', functions=(np.deg2rad, np.rad2deg))
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
"""
2+
==============
3+
Secondary Axis
4+
==============
5+
6+
Sometimes we want as secondary axis on a plot, for instance to convert
7+
radians to degrees on the same plot. We can do this by making a child
8+
axes with only one axis visible via `.Axes.axes.secondary_xaxis` and
9+
`.Axes.axes.secondary_yaxis`. This secondary axis can have a different scale
10+
than the main axis by providing both a forward and an inverse conversion
11+
function in a tuple to the ``functions`` kwarg:
12+
"""
13+
14+
importmatplotlib.pyplotasplt
15+
importnumpyasnp
16+
importdatetime
17+
importmatplotlib.datesasmdates
18+
frommatplotlib.transformsimportTransform
19+
frommatplotlib.tickerimport (
20+
AutoLocator,AutoMinorLocator)
21+
22+
fig,ax=plt.subplots(constrained_layout=True)
23+
x=np.arange(0,360,1)
24+
y=np.sin(2*x*np.pi/180)
25+
ax.plot(x,y)
26+
ax.set_xlabel('angle [degrees]')
27+
ax.set_ylabel('signal')
28+
ax.set_title('Sine wave')
29+
30+
31+
defdeg2rad(x):
32+
returnx*np.pi/180
33+
34+
35+
defrad2deg(x):
36+
returnx*180/np.pi
37+
38+
secax=ax.secondary_xaxis('top',functions=(deg2rad,rad2deg))
39+
secax.set_xlabel('angle [rad]')
40+
plt.show()
41+
42+
###########################################################################
43+
# Here is the case of converting from wavenumber to wavelength in a
44+
# log-log scale.
45+
#
46+
# .. note ::
47+
#
48+
# In this case, the xscale of the parent is logarithmic, so the child is
49+
# made logarithmic as well.
50+
51+
fig,ax=plt.subplots(constrained_layout=True)
52+
x=np.arange(0.02,1,0.02)
53+
np.random.seed(19680801)
54+
y=np.random.randn(len(x))**2
55+
ax.loglog(x,y)
56+
ax.set_xlabel('f [Hz]')
57+
ax.set_ylabel('PSD')
58+
ax.set_title('Random spectrum')
59+
60+
61+
defforward(x):
62+
return1/x
63+
64+
65+
definverse(x):
66+
return1/x
67+
68+
secax=ax.secondary_xaxis('top',functions=(forward,inverse))
69+
secax.set_xlabel('period [s]')
70+
plt.show()
71+
72+
###########################################################################
73+
# Sometime we want to relate the axes in a transform that is ad-hoc from
74+
# the data, and is derived empirically. In that case we can set the
75+
# forward and inverse transforms functions to be linear interpolations from the
76+
# one data set to the other.
77+
78+
fig,ax=plt.subplots(constrained_layout=True)
79+
xdata=np.arange(1,11,0.4)
80+
ydata=np.random.randn(len(xdata))
81+
ax.plot(xdata,ydata,label='Plotted data')
82+
83+
xold=np.arange(0,11,0.2)
84+
# fake data set relating x co-ordinate to another data-derived co-ordinate.
85+
# xnew must be monotonic, so we sort...
86+
xnew=np.sort(10*np.exp(-xold/4)+np.random.randn(len(xold))/3)
87+
88+
ax.plot(xold[3:],xnew[3:],label='Transform data')
89+
ax.set_xlabel('X [m]')
90+
ax.legend()
91+
92+
93+
defforward(x):
94+
returnnp.interp(x,xold,xnew)
95+
96+
97+
definverse(x):
98+
returnnp.interp(x,xnew,xold)
99+
100+
secax=ax.secondary_xaxis('top',functions=(forward,inverse))
101+
secax.xaxis.set_minor_locator(AutoMinorLocator())
102+
secax.set_xlabel('$X_{other}$')
103+
104+
plt.show()
105+
106+
###########################################################################
107+
# A final example translates np.datetime64 to yearday on the x axis and
108+
# from Celsius to Farenheit on the y axis:
109+
110+
111+
dates= [datetime.datetime(2018,1,1)+datetime.timedelta(hours=k*6)
112+
forkinrange(240)]
113+
temperature=np.random.randn(len(dates))
114+
fig,ax=plt.subplots(constrained_layout=True)
115+
116+
ax.plot(dates,temperature)
117+
ax.set_ylabel(r'$T\ [^oC]$')
118+
plt.xticks(rotation=70)
119+
120+
121+
defdate2yday(x):
122+
"""
123+
x is in matplotlib datenums, so they are floats.
124+
"""
125+
y=x-mdates.date2num(datetime.datetime(2018,1,1))
126+
returny
127+
128+
129+
defyday2date(x):
130+
"""
131+
return a matplotlib datenum (x is days since start of year)
132+
"""
133+
y=x+mdates.date2num(datetime.datetime(2018,1,1))
134+
returny
135+
136+
secaxx=ax.secondary_xaxis('top',functions=(date2yday,yday2date))
137+
secaxx.set_xlabel('yday [2018]')
138+
139+
140+
defCtoF(x):
141+
returnx*1.8+32
142+
143+
144+
defFtoC(x):
145+
return (x-32)/1.8
146+
147+
secaxy=ax.secondary_yaxis('right',functions=(CtoF,FtoC))
148+
secaxy.set_ylabel(r'$T\ [^oF]$')
149+
150+
plt.show()
151+
152+
#############################################################################
153+
#
154+
# ------------
155+
#
156+
# References
157+
# """"""""""
158+
#
159+
# The use of the following functions and methods is shown in this example:
160+
161+
importmatplotlib
162+
163+
matplotlib.axes.Axes.secondary_xaxis
164+
matplotlib.axes.Axes.secondary_yaxis

‎lib/matplotlib/_constrained_layout.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
180180
sup=fig._suptitle
181181
bbox=invTransFig(sup.get_window_extent(renderer=renderer))
182182
height=bbox.y1-bbox.y0
183-
sup._layoutbox.edit_height(height+h_pad)
183+
ifnp.isfinite(height):
184+
sup._layoutbox.edit_height(height+h_pad)
184185

185186
# OK, the above lines up ax._poslayoutbox with ax._layoutbox
186187
# now we need to
@@ -266,10 +267,14 @@ def _make_layout_margins(ax, renderer, h_pad, w_pad):
266267
"""
267268
fig=ax.figure
268269
invTransFig=fig.transFigure.inverted().transform_bbox
269-
270270
pos=ax.get_position(original=True)
271271
tightbbox=ax.get_tightbbox(renderer=renderer)
272272
bbox=invTransFig(tightbbox)
273+
# this can go wrong:
274+
ifnot (np.isfinite(bbox.width)andnp.isfinite(bbox.height)):
275+
# just abort, this is likely a bad set of co-ordinates that
276+
# is transitory...
277+
return
273278
# use stored h_pad if it exists
274279
h_padt=ax._poslayoutbox.h_pad
275280
ifh_padtisNone:
@@ -287,6 +292,8 @@ def _make_layout_margins(ax, renderer, h_pad, w_pad):
287292
_log.debug('left %f', (-bbox.x0+pos.x0+w_pad))
288293
_log.debug('right %f', (bbox.x1-pos.x1+w_pad))
289294
_log.debug('bottom %f', (-bbox.y0+pos.y0+h_padt))
295+
_log.debug('bbox.y0 %f',bbox.y0)
296+
_log.debug('pos.y0 %f',pos.y0)
290297
# Sometimes its possible for the solver to collapse
291298
# rather than expand axes, so they all have zero height
292299
# or width. This stops that... It *should* have been

‎lib/matplotlib/axes/_axes.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
importmatplotlib.triasmtri
3535
frommatplotlib.containerimportBarContainer,ErrorbarContainer,StemContainer
3636
frommatplotlib.axes._baseimport_AxesBase,_process_plot_format
37+
frommatplotlib.axes._secondary_axesimportSecondaryAxis
3738

3839
_log=logging.getLogger(__name__)
3940

@@ -599,6 +600,79 @@ def indicate_inset_zoom(self, inset_ax, **kwargs):
599600

600601
returnrectpatch,connects
601602

603+
@docstring.dedent_interpd
604+
defsecondary_xaxis(self,location,*,functions=None,**kwargs):
605+
"""
606+
Add a second x-axis to this axes.
607+
608+
For example if we want to have a second scale for the data plotted on
609+
the xaxis.
610+
611+
%(_secax_docstring)s
612+
613+
Examples
614+
--------
615+
616+
The main axis shows frequency, and the secondary axis shows period.
617+
618+
.. plot::
619+
620+
fig, ax = plt.subplots()
621+
ax.loglog(range(1, 360, 5), range(1, 360, 5))
622+
ax.set_xlabel('frequency [Hz]')
623+
624+
625+
def invert(x):
626+
return 1 / x
627+
628+
secax = ax.secondary_xaxis('top', functions=(invert, invert))
629+
secax.set_xlabel('Period [s]')
630+
plt.show()
631+
632+
633+
"""
634+
if (locationin ['top','bottom']orisinstance(location,Number)):
635+
secondary_ax=SecondaryAxis(self,'x',location,functions,
636+
**kwargs)
637+
self.add_child_axes(secondary_ax)
638+
returnsecondary_ax
639+
else:
640+
raiseValueError('secondary_xaxis location must be either '
641+
'a float or "top"/"bottom"')
642+
643+
defsecondary_yaxis(self,location,*,functions=None,**kwargs):
644+
"""
645+
Add a second y-axis to this axes.
646+
647+
For example if we want to have a second scale for the data plotted on
648+
the yaxis.
649+
650+
%(_secax_docstring)s
651+
652+
Examples
653+
--------
654+
655+
Add a secondary axes that converts from radians to degrees
656+
657+
.. plot::
658+
659+
fig, ax = plt.subplots()
660+
ax.plot(range(1, 360, 5), range(1, 360, 5))
661+
ax.set_ylabel('degrees')
662+
secax = ax.secondary_yaxis('right', functions=(np.deg2rad,
663+
np.rad2deg))
664+
secax.set_ylabel('radians')
665+
666+
"""
667+
iflocationin ['left','right']orisinstance(location,Number):
668+
secondary_ax=SecondaryAxis(self,'y',location,
669+
functions,**kwargs)
670+
self.add_child_axes(secondary_ax)
671+
returnsecondary_ax
672+
else:
673+
raiseValueError('secondary_yaxis location must be either '
674+
'a float or "left"/"right"')
675+
602676
deftext(self,x,y,s,fontdict=None,withdash=False,**kwargs):
603677
"""
604678
Add text to the axes.

‎lib/matplotlib/axes/_base.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2500,8 +2500,17 @@ def _update_title_position(self, renderer):
25002500
title.set_position((x,1.0))
25012501
# need to check all our twins too...
25022502
axs=self._twinned_axes.get_siblings(self)
2503-
2504-
top=0# the top of all the axes twinned with this axes...
2503+
# and all the children
2504+
foraxinself.child_axes:
2505+
ifaxisnotNone:
2506+
locator=ax.get_axes_locator()
2507+
iflocator:
2508+
pos=locator(self,renderer)
2509+
ax.apply_aspect(pos)
2510+
else:
2511+
ax.apply_aspect()
2512+
axs=axs+ [ax]
2513+
top=0
25052514
foraxinaxs:
25062515
try:
25072516
if (ax.xaxis.get_label_position()=='top'
@@ -2544,6 +2553,8 @@ def draw(self, renderer=None, inframe=False):
25442553

25452554
# prevent triggering call backs during the draw process
25462555
self._stale=True
2556+
2557+
# loop over self and child axes...
25472558
locator=self.get_axes_locator()
25482559
iflocator:
25492560
pos=locator(self,renderer)
@@ -4315,6 +4326,9 @@ def get_tightbbox(self, renderer, call_axes_locator=True,
43154326
ifbb_yaxis:
43164327
bb.append(bb_yaxis)
43174328

4329+
self._update_title_position(renderer)
4330+
bb.append(self.get_window_extent(renderer))
4331+
43184332
self._update_title_position(renderer)
43194333
ifself.title.get_visible():
43204334
bb.append(self.title.get_window_extent(renderer))

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp