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

Commit6cfe2ca

Browse files
committed
ENH: add secondary x/y axis
1 parent922dea2 commit6cfe2ca

File tree

14 files changed

+860
-22
lines changed

14 files changed

+860
-22
lines changed

‎.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ per-file-ignores =
228228
examples/subplots_axes_and_figures/axes_zoom_effect.py: E402
229229
examples/subplots_axes_and_figures/demo_constrained_layout.py: E402
230230
examples/subplots_axes_and_figures/demo_tight_layout.py: E402
231+
examples/subplots_axes_and_figures/secondary_axis.py: E402
231232
examples/subplots_axes_and_figures/two_scales.py: E402
232233
examples/subplots_axes_and_figures/zoom_inset_axes.py: E402
233234
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: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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`.
10+
11+
If we want to label the top of the axes:
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+
secax=ax.secondary_xaxis('top')
30+
plt.show()
31+
32+
###########################################################################
33+
# However, its often useful to label the secondary axis with something
34+
# other than the labels in the main axis. In that case we need to provide
35+
# both a forward and an inverse conversion function in a tuple
36+
# to the ``functions`` kwarg:
37+
38+
fig,ax=plt.subplots(constrained_layout=True)
39+
x=np.arange(0,360,1)
40+
y=np.sin(2*x*np.pi/180)
41+
ax.plot(x,y)
42+
ax.set_xlabel('angle [degrees]')
43+
ax.set_ylabel('signal')
44+
ax.set_title('Sine wave')
45+
46+
47+
defdeg2rad(x):
48+
returnx*np.pi/180
49+
50+
51+
defrad2deg(x):
52+
returnx*180/np.pi
53+
54+
secax=ax.secondary_xaxis('top',functions=(deg2rad,rad2deg))
55+
secax.set_xlabel('angle [rad]')
56+
plt.show()
57+
58+
###########################################################################
59+
# Here is the case of converting from wavenumber to wavelength in a
60+
# log-log scale.
61+
#
62+
# .. note ::
63+
#
64+
# In this case, the xscale of the parent is logarithmic, so the child is
65+
# made logarithmic as well.
66+
67+
fig,ax=plt.subplots(constrained_layout=True)
68+
x=np.arange(0.02,1,0.02)
69+
np.random.seed(19680801)
70+
y=np.random.randn(len(x))**2
71+
ax.loglog(x,y)
72+
ax.set_xlabel('f [Hz]')
73+
ax.set_ylabel('PSD')
74+
ax.set_title('Random spectrum')
75+
76+
77+
defforward(x):
78+
return1/x
79+
80+
81+
definverse(x):
82+
return1/x
83+
84+
secax=ax.secondary_xaxis('top',functions=(forward,inverse))
85+
secax.set_xlabel('period [s]')
86+
plt.show()
87+
88+
###########################################################################
89+
# Sometime we want to relate the axes in a transform that is ad-hoc from
90+
# the data, and is derived empirically. In that case we can set the
91+
# forward and inverse transforms functions to be linear interpolations from the
92+
# one data set to the other.
93+
94+
fig,ax=plt.subplots(constrained_layout=True)
95+
xdata=np.arange(1,11,0.4)
96+
ydata=np.random.randn(len(xdata))
97+
ax.plot(xdata,ydata,label='Plotted data')
98+
99+
xold=np.arange(0,11,0.2)
100+
# fake data set relating x co-ordinate to another data-derived co-ordinate.
101+
xnew=np.sort(10*np.exp(-xold/4)+np.random.randn(len(xold))/3)
102+
103+
ax.plot(xold[3:],xnew[3:],label='Transform data')
104+
ax.set_xlabel('X [m]')
105+
ax.legend()
106+
107+
108+
defforward(x):
109+
returnnp.interp(x,xold,xnew)
110+
111+
112+
definverse(x):
113+
returnnp.interp(x,xnew,xold)
114+
115+
secax=ax.secondary_xaxis('top',functions=(forward,inverse))
116+
secax.xaxis.set_minor_locator(AutoMinorLocator())
117+
secax.set_xlabel('$X_{other}$')
118+
119+
plt.show()
120+
121+
###########################################################################
122+
# A final example translates np.datetime64 to yearday on the x axis and
123+
# from Celsius to Farenheit on the y axis:
124+
125+
126+
dates= [datetime.datetime(2018,1,1)+datetime.timedelta(hours=k*6)
127+
forkinrange(240)]
128+
temperature=np.random.randn(len(dates))
129+
fig,ax=plt.subplots(constrained_layout=True)
130+
131+
ax.plot(dates,temperature)
132+
ax.set_ylabel(r'$T\ [^oC]$')
133+
plt.xticks(rotation=70)
134+
135+
136+
defdate2yday(x):
137+
"""
138+
x is in matplotlib datenums, so they are floats.
139+
"""
140+
y=x-mdates.date2num(datetime.datetime(2018,1,1))
141+
returny
142+
143+
144+
defyday2date(x):
145+
"""
146+
return a matplotlib datenum (x is days since start of year)
147+
"""
148+
y=x+mdates.date2num(datetime.datetime(2018,1,1))
149+
returny
150+
151+
secaxx=ax.secondary_xaxis('top',functions=(date2yday,yday2date))
152+
secaxx.set_xlabel('yday [2018]')
153+
154+
155+
defCtoF(x):
156+
returnx*1.8+32
157+
158+
159+
defFtoC(x):
160+
return (x-32)/1.8
161+
162+
secaxy=ax.secondary_yaxis('right',functions=(CtoF,FtoC))
163+
secaxy.set_ylabel(r'$T\ [^oF]$')
164+
165+
plt.show()
166+
167+
#############################################################################
168+
#
169+
# ------------
170+
#
171+
# References
172+
# """"""""""
173+
#
174+
# The use of the following functions and methods is shown in this example:
175+
176+
importmatplotlib
177+
178+
matplotlib.axes.Axes.secondary_xaxis
179+
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+
ifnotnp.isfinite(bbox.y0+bbox.x0+bbox.y1+bbox.x1):
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: 75 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,80 @@ 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+
Add a secondary axes that shows both wavelength for the main
617+
axes that shows wavenumber.
618+
619+
.. plot::
620+
621+
fig, ax = plt.subplots()
622+
ax.loglog(range(1, 360, 5), range(1, 360, 5))
623+
ax.set_xlabel('frequency [Hz]')
624+
625+
626+
def invert(x):
627+
return 1 / x
628+
629+
secax = ax.secondary_xaxis('top', functions=(invert, invert))
630+
secax.set_xlabel('Period [s]')
631+
plt.show()
632+
633+
634+
"""
635+
if (locationin ['top','bottom']orisinstance(location,Number)):
636+
secondary_ax=SecondaryAxis(self,'x',location,functions,
637+
**kwargs)
638+
self.add_child_axes(secondary_ax)
639+
returnsecondary_ax
640+
else:
641+
raiseValueError('secondary_xaxis location must be either '
642+
'a float or "top"/"bottom"')
643+
644+
defsecondary_yaxis(self,location,*,functions=None,**kwargs):
645+
"""
646+
Add a second y-axis to this axes.
647+
648+
For example if we want to have a second scale for the data plotted on
649+
the yaxis.
650+
651+
%(_secax_docstring)s
652+
653+
Examples
654+
--------
655+
656+
Add a secondary axes that converts from radians to degrees
657+
658+
.. plot::
659+
660+
fig, ax = plt.subplots()
661+
ax.plot(range(1, 360, 5), range(1, 360, 5))
662+
ax.set_ylabel('degrees')
663+
secax = ax.secondary_yaxis('right', functions=(np.deg2rad,
664+
np.rad2deg))
665+
secax.set_ylabel('radians')
666+
667+
"""
668+
iflocationin ['left','right']orisinstance(location,Number):
669+
secondary_ax=SecondaryAxis(self,'y',location,
670+
functions,**kwargs)
671+
self.add_child_axes(secondary_ax)
672+
returnsecondary_ax
673+
else:
674+
raiseValueError('secondary_yaxis location must be either '
675+
'a float or "left"/"right"')
676+
602677
deftext(self,x,y,s,fontdict=None,withdash=False,**kwargs):
603678
"""
604679
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