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

Appearance settings

Commit d1b13d9

Browse filesBrowse files
committed
Merge pull request matplotlib#1664 from dopplershift/skew
Support for skewed transforms.
2 parents 37dc468 + f3ba859 commit d1b13d9
Copy full SHA for d1b13d9

File tree

Expand file treeCollapse file tree

14 files changed

+6213
-28
lines changed
Filter options
Expand file treeCollapse file tree

14 files changed

+6213
-28
lines changed

‎doc/api/api_changes.rst

Copy file name to clipboardExpand all lines: doc/api/api_changes.rst
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ original location:
144144
tuple-like if separate axis padding is required.
145145
The original behavior is preserved.
146146

147+
* Added support for skewed transforms to `matplotlib.transforms.Affine2D`,
148+
which can be created using the `skew` and `skew_deg` methods.
149+
147150

148151
.. _changes_in_1_3:
149152

‎doc/users/whats_new.rst

Copy file name to clipboardExpand all lines: doc/users/whats_new.rst
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@ be a tuple if separate horizontal/vertical padding is needed.
121121
This is supposed to be very helpful when you have a labelled legend next to
122122
every subplot and you need to make some space for legend's labels.
123123

124+
Support for skewed transformations
125+
``````````````````````````````````
126+
The :class:`~matplotlib.transforms.Affine2D` gained additional methods
127+
`skew` and `skew_deg` to create skewed transformations. Additionally,
128+
matplotlib internals were cleaned up to support using such transforms in
129+
:class:`~matplotlib.Axes`. This transform is important for some plot types,
130+
specifically the Skew-T used in meteorology.
131+
132+
.. plot:: mpl_examples/api/skewt.py
133+
134+
124135
Date handling
125136
-------------
126137

‎examples/api/skewt.py

Copy file name to clipboard
+252Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# This serves as an intensive exercise of matplotlib's transforms
2+
# and custom projection API. This example produces a so-called
3+
# SkewT-logP diagram, which is a common plot in meteorology for
4+
# displaying vertical profiles of temperature. As far as matplotlib is
5+
# concerned, the complexity comes from having X and Y axes that are
6+
# not orthogonal. This is handled by including a skew component to the
7+
# basic Axes transforms. Additional complexity comes in handling the
8+
# fact that the upper and lower X-axes have different data ranges, which
9+
# necessitates a bunch of custom classes for ticks,spines, and the axis
10+
# to handle this.
11+
12+
from matplotlib.axes import Axes
13+
import matplotlib.transforms as transforms
14+
import matplotlib.axis as maxis
15+
import matplotlib.spines as mspines
16+
import matplotlib.path as mpath
17+
from matplotlib.projections import register_projection
18+
19+
# The sole purpose of this class is to look at the upper, lower, or total
20+
# interval as appropriate and see what parts of the tick to draw, if any.
21+
class SkewXTick(maxis.XTick):
22+
def draw(self, renderer):
23+
if not self.get_visible(): return
24+
renderer.open_group(self.__name__)
25+
26+
lower_interval = self.axes.xaxis.lower_interval
27+
upper_interval = self.axes.xaxis.upper_interval
28+
29+
if self.gridOn and transforms.interval_contains(
30+
self.axes.xaxis.get_view_interval(), self.get_loc()):
31+
self.gridline.draw(renderer)
32+
33+
if transforms.interval_contains(lower_interval, self.get_loc()):
34+
if self.tick1On:
35+
self.tick1line.draw(renderer)
36+
if self.label1On:
37+
self.label1.draw(renderer)
38+
39+
if transforms.interval_contains(upper_interval, self.get_loc()):
40+
if self.tick2On:
41+
self.tick2line.draw(renderer)
42+
if self.label2On:
43+
self.label2.draw(renderer)
44+
45+
renderer.close_group(self.__name__)
46+
47+
48+
# This class exists to provide two separate sets of intervals to the tick,
49+
# as well as create instances of the custom tick
50+
class SkewXAxis(maxis.XAxis):
51+
def __init__(self, *args, **kwargs):
52+
maxis.XAxis.__init__(self, *args, **kwargs)
53+
self.upper_interval = 0.0, 1.0
54+
55+
def _get_tick(self, major):
56+
return SkewXTick(self.axes, 0, '', major=major)
57+
58+
@property
59+
def lower_interval(self):
60+
return self.axes.viewLim.intervalx
61+
62+
def get_view_interval(self):
63+
return self.upper_interval[0], self.axes.viewLim.intervalx[1]
64+
65+
66+
# This class exists to calculate the separate data range of the
67+
# upper X-axis and draw the spine there. It also provides this range
68+
# to the X-axis artist for ticking and gridlines
69+
class SkewSpine(mspines.Spine):
70+
def _adjust_location(self):
71+
trans = self.axes.transDataToAxes.inverted()
72+
if self.spine_type == 'top':
73+
yloc = 1.0
74+
else:
75+
yloc = 0.0
76+
left = trans.transform_point((0.0, yloc))[0]
77+
right = trans.transform_point((1.0, yloc))[0]
78+
79+
pts = self._path.vertices
80+
pts[0, 0] = left
81+
pts[1, 0] = right
82+
self.axis.upper_interval = (left, right)
83+
84+
85+
# This class handles registration of the skew-xaxes as a projection as well
86+
# as setting up the appropriate transformations. It also overrides standard
87+
# spines and axes instances as appropriate.
88+
class SkewXAxes(Axes):
89+
# The projection must specify a name. This will be used be the
90+
# user to select the projection, i.e. ``subplot(111,
91+
# projection='skewx')``.
92+
name = 'skewx'
93+
94+
def _init_axis(self):
95+
#Taken from Axes and modified to use our modified X-axis
96+
self.xaxis = SkewXAxis(self)
97+
self.spines['top'].register_axis(self.xaxis)
98+
self.spines['bottom'].register_axis(self.xaxis)
99+
self.yaxis = maxis.YAxis(self)
100+
self.spines['left'].register_axis(self.yaxis)
101+
self.spines['right'].register_axis(self.yaxis)
102+
103+
def _gen_axes_spines(self):
104+
spines = {'top':SkewSpine.linear_spine(self, 'top'),
105+
'bottom':mspines.Spine.linear_spine(self, 'bottom'),
106+
'left':mspines.Spine.linear_spine(self, 'left'),
107+
'right':mspines.Spine.linear_spine(self, 'right')}
108+
return spines
109+
110+
def _set_lim_and_transforms(self):
111+
"""
112+
This is called once when the plot is created to set up all the
113+
transforms for the data, text and grids.
114+
"""
115+
rot = 30
116+
117+
#Get the standard transform setup from the Axes base class
118+
Axes._set_lim_and_transforms(self)
119+
120+
# Need to put the skew in the middle, after the scale and limits,
121+
# but before the transAxes. This way, the skew is done in Axes
122+
# coordinates thus performing the transform around the proper origin
123+
# We keep the pre-transAxes transform around for other users, like the
124+
# spines for finding bounds
125+
self.transDataToAxes = self.transScale + (self.transLimits +
126+
transforms.Affine2D().skew_deg(rot, 0))
127+
128+
# Create the full transform from Data to Pixels
129+
self.transData = self.transDataToAxes + self.transAxes
130+
131+
# Blended transforms like this need to have the skewing applied using
132+
# both axes, in axes coords like before.
133+
self._xaxis_transform = (transforms.blended_transform_factory(
134+
self.transScale + self.transLimits,
135+
transforms.IdentityTransform()) +
136+
transforms.Affine2D().skew_deg(rot, 0)) + self.transAxes
137+
138+
# Now register the projection with matplotlib so the user can select
139+
# it.
140+
register_projection(SkewXAxes)
141+
142+
if __name__ == '__main__':
143+
# Now make a simple example using the custom projection.
144+
from matplotlib.ticker import ScalarFormatter, MultipleLocator
145+
from matplotlib.collections import LineCollection
146+
import matplotlib.pyplot as plt
147+
from StringIO import StringIO
148+
import numpy as np
149+
150+
#Some examples data
151+
data_txt = '''
152+
978.0 345 7.8 0.8 61 4.16 325 14 282.7 294.6 283.4
153+
971.0 404 7.2 0.2 61 4.01 327 17 282.7 294.2 283.4
154+
946.7 610 5.2 -1.8 61 3.56 335 26 282.8 293.0 283.4
155+
944.0 634 5.0 -2.0 61 3.51 336 27 282.8 292.9 283.4
156+
925.0 798 3.4 -2.6 65 3.43 340 32 282.8 292.7 283.4
157+
911.8 914 2.4 -2.7 69 3.46 345 37 282.9 292.9 283.5
158+
906.0 966 2.0 -2.7 71 3.47 348 39 283.0 293.0 283.6
159+
877.9 1219 0.4 -3.2 77 3.46 0 48 283.9 293.9 284.5
160+
850.0 1478 -1.3 -3.7 84 3.44 0 47 284.8 294.8 285.4
161+
841.0 1563 -1.9 -3.8 87 3.45 358 45 285.0 295.0 285.6
162+
823.0 1736 1.4 -0.7 86 4.44 353 42 290.3 303.3 291.0
163+
813.6 1829 4.5 1.2 80 5.17 350 40 294.5 309.8 295.4
164+
809.0 1875 6.0 2.2 77 5.57 347 39 296.6 313.2 297.6
165+
798.0 1988 7.4 -0.6 57 4.61 340 35 299.2 313.3 300.1
166+
791.0 2061 7.6 -1.4 53 4.39 335 33 300.2 313.6 301.0
167+
783.9 2134 7.0 -1.7 54 4.32 330 31 300.4 313.6 301.2
168+
755.1 2438 4.8 -3.1 57 4.06 300 24 301.2 313.7 301.9
169+
727.3 2743 2.5 -4.4 60 3.81 285 29 301.9 313.8 302.6
170+
700.5 3048 0.2 -5.8 64 3.57 275 31 302.7 313.8 303.3
171+
700.0 3054 0.2 -5.8 64 3.56 280 31 302.7 313.8 303.3
172+
698.0 3077 0.0 -6.0 64 3.52 280 31 302.7 313.7 303.4
173+
687.0 3204 -0.1 -7.1 59 3.28 281 31 304.0 314.3 304.6
174+
648.9 3658 -3.2 -10.9 55 2.59 285 30 305.5 313.8 305.9
175+
631.0 3881 -4.7 -12.7 54 2.29 289 33 306.2 313.6 306.6
176+
600.7 4267 -6.4 -16.7 44 1.73 295 39 308.6 314.3 308.9
177+
592.0 4381 -6.9 -17.9 41 1.59 297 41 309.3 314.6 309.6
178+
577.6 4572 -8.1 -19.6 39 1.41 300 44 310.1 314.9 310.3
179+
555.3 4877 -10.0 -22.3 36 1.16 295 39 311.3 315.3 311.5
180+
536.0 5151 -11.7 -24.7 33 0.97 304 39 312.4 315.8 312.6
181+
533.8 5182 -11.9 -25.0 33 0.95 305 39 312.5 315.8 312.7
182+
500.0 5680 -15.9 -29.9 29 0.64 290 44 313.6 315.9 313.7
183+
472.3 6096 -19.7 -33.4 28 0.49 285 46 314.1 315.8 314.1
184+
453.0 6401 -22.4 -36.0 28 0.39 300 50 314.4 315.8 314.4
185+
400.0 7310 -30.7 -43.7 27 0.20 285 44 315.0 315.8 315.0
186+
399.7 7315 -30.8 -43.8 27 0.20 285 44 315.0 315.8 315.0
187+
387.0 7543 -33.1 -46.1 26 0.16 281 47 314.9 315.5 314.9
188+
382.7 7620 -33.8 -46.8 26 0.15 280 48 315.0 315.6 315.0
189+
342.0 8398 -40.5 -53.5 23 0.08 293 52 316.1 316.4 316.1
190+
320.4 8839 -43.7 -56.7 22 0.06 300 54 317.6 317.8 317.6
191+
318.0 8890 -44.1 -57.1 22 0.05 301 55 317.8 318.0 317.8
192+
310.0 9060 -44.7 -58.7 19 0.04 304 61 319.2 319.4 319.2
193+
306.1 9144 -43.9 -57.9 20 0.05 305 63 321.5 321.7 321.5
194+
305.0 9169 -43.7 -57.7 20 0.05 303 63 322.1 322.4 322.1
195+
300.0 9280 -43.5 -57.5 20 0.05 295 64 323.9 324.2 323.9
196+
292.0 9462 -43.7 -58.7 17 0.05 293 67 326.2 326.4 326.2
197+
276.0 9838 -47.1 -62.1 16 0.03 290 74 326.6 326.7 326.6
198+
264.0 10132 -47.5 -62.5 16 0.03 288 79 330.1 330.3 330.1
199+
251.0 10464 -49.7 -64.7 16 0.03 285 85 331.7 331.8 331.7
200+
250.0 10490 -49.7 -64.7 16 0.03 285 85 332.1 332.2 332.1
201+
247.0 10569 -48.7 -63.7 16 0.03 283 88 334.7 334.8 334.7
202+
244.0 10649 -48.9 -63.9 16 0.03 280 91 335.6 335.7 335.6
203+
243.3 10668 -48.9 -63.9 16 0.03 280 91 335.8 335.9 335.8
204+
220.0 11327 -50.3 -65.3 15 0.03 280 85 343.5 343.6 343.5
205+
212.0 11569 -50.5 -65.5 15 0.03 280 83 346.8 346.9 346.8
206+
210.0 11631 -49.7 -64.7 16 0.03 280 83 349.0 349.1 349.0
207+
200.0 11950 -49.9 -64.9 15 0.03 280 80 353.6 353.7 353.6
208+
194.0 12149 -49.9 -64.9 15 0.03 279 78 356.7 356.8 356.7
209+
183.0 12529 -51.3 -66.3 15 0.03 278 75 360.4 360.5 360.4
210+
164.0 13233 -55.3 -68.3 18 0.02 277 69 365.2 365.3 365.2
211+
152.0 13716 -56.5 -69.5 18 0.02 275 65 371.1 371.2 371.1
212+
150.0 13800 -57.1 -70.1 18 0.02 275 64 371.5 371.6 371.5
213+
136.0 14414 -60.5 -72.5 19 0.02 268 54 376.0 376.1 376.0
214+
132.0 14600 -60.1 -72.1 19 0.02 265 51 380.0 380.1 380.0
215+
131.4 14630 -60.2 -72.2 19 0.02 265 51 380.3 380.4 380.3
216+
128.0 14792 -60.9 -72.9 19 0.02 266 50 381.9 382.0 381.9
217+
125.0 14939 -60.1 -72.1 19 0.02 268 49 385.9 386.0 385.9
218+
119.0 15240 -62.2 -73.8 20 0.01 270 48 387.4 387.5 387.4
219+
112.0 15616 -64.9 -75.9 21 0.01 265 53 389.3 389.3 389.3
220+
108.0 15838 -64.1 -75.1 21 0.01 265 58 394.8 394.9 394.8
221+
107.8 15850 -64.1 -75.1 21 0.01 265 58 395.0 395.1 395.0
222+
105.0 16010 -64.7 -75.7 21 0.01 272 50 396.9 396.9 396.9
223+
103.0 16128 -62.9 -73.9 21 0.02 277 45 402.5 402.6 402.5
224+
100.0 16310 -62.5 -73.5 21 0.02 285 36 406.7 406.8 406.7'''
225+
226+
# Parse the data
227+
sound_data = StringIO(data_txt)
228+
p,h,T,Td = np.loadtxt(sound_data, usecols=range(0,4), unpack=True)
229+
230+
# Create a new figure. The dimensions here give a good aspect ratio
231+
fig = plt.figure(figsize=(6.5875, 6.2125))
232+
ax = fig.add_subplot(111, projection='skewx')
233+
234+
plt.grid(True)
235+
236+
# Plot the data using normal plotting functions, in this case using
237+
# log scaling in Y, as dicatated by the typical meteorological plot
238+
ax.semilogy(T, p, 'r')
239+
ax.semilogy(Td, p, 'g')
240+
241+
# An example of a slanted line at constant X
242+
l = ax.axvline(0, color='b')
243+
244+
# Disables the log-formatting that comes with semilogy
245+
ax.yaxis.set_major_formatter(ScalarFormatter())
246+
ax.set_yticks(np.linspace(100,1000,10))
247+
ax.set_ylim(1050,100)
248+
249+
ax.xaxis.set_major_locator(MultipleLocator(10))
250+
ax.set_xlim(-50,50)
251+
252+
plt.show()

‎lib/matplotlib/axes/_axes.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_axes.py
+6-12Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -720,8 +720,7 @@ def axhline(self, y=0, xmin=0, xmax=1, **kwargs):
720720
yy = self.convert_yunits(y)
721721
scaley = (yy < ymin) or (yy > ymax)
722722

723-
trans = mtransforms.blended_transform_factory(
724-
self.transAxes, self.transData)
723+
trans = self.get_yaxis_transform(which='grid')
725724
l = mlines.Line2D([xmin, xmax], [y, y], transform=trans, **kwargs)
726725
self.add_line(l)
727726
self.autoscale_view(scalex=False, scaley=scaley)
@@ -787,8 +786,7 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
787786
xx = self.convert_xunits(x)
788787
scalex = (xx < xmin) or (xx > xmax)
789788

790-
trans = mtransforms.blended_transform_factory(
791-
self.transData, self.transAxes)
789+
trans = self.get_xaxis_transform(which='grid')
792790
l = mlines.Line2D([x, x], [ymin, ymax], transform=trans, **kwargs)
793791
self.add_line(l)
794792
self.autoscale_view(scalex=scalex, scaley=False)
@@ -833,8 +831,7 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
833831
.. plot:: mpl_examples/pylab_examples/axhspan_demo.py
834832
835833
"""
836-
trans = mtransforms.blended_transform_factory(
837-
self.transAxes, self.transData)
834+
trans = self.get_yaxis_transform(which='grid')
838835

839836
# process the unit information
840837
self._process_unit_info([xmin, xmax], [ymin, ymax], kwargs=kwargs)
@@ -889,8 +886,7 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs):
889886
:meth:`axhspan`
890887
for example plot and source code
891888
"""
892-
trans = mtransforms.blended_transform_factory(
893-
self.transData, self.transAxes)
889+
trans = self.get_xaxis_transform(which='grid')
894890

895891
# process the unit information
896892
self._process_unit_info([xmin, xmax], [ymin, ymax], kwargs=kwargs)
@@ -3949,8 +3945,7 @@ def coarse_bin(x, y, coarse):
39493945
values.append(val)
39503946

39513947
values = np.array(values)
3952-
trans = mtransforms.blended_transform_factory(
3953-
self.transData, self.transAxes)
3948+
trans = self.get_xaxis_transform(which='grid')
39543949

39553950
hbar = mcoll.PolyCollection(verts, transform=trans, edgecolors='face')
39563951

@@ -3979,8 +3974,7 @@ def coarse_bin(x, y, coarse):
39793974

39803975
values = np.array(values)
39813976

3982-
trans = mtransforms.blended_transform_factory(
3983-
self.transAxes, self.transData)
3977+
trans = self.get_yaxis_transform(which='grid')
39843978

39853979
vbar = mcoll.PolyCollection(verts, transform=trans, edgecolors='face')
39863980
vbar.set_array(values)

‎lib/matplotlib/axes/_base.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
+8-6Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,6 +1977,14 @@ def draw(self, renderer=None, inframe=False):
19771977
artists.extend(self.lines)
19781978
artists.extend(self.texts)
19791979
artists.extend(self.artists)
1980+
1981+
# the frame draws the edges around the axes patch -- we
1982+
# decouple these so the patch can be in the background and the
1983+
# frame in the foreground. Do this before drawing the axis
1984+
# objects so that the spine has the opportunity to update them.
1985+
if self.axison and self._frameon:
1986+
artists.extend(six.itervalues(self.spines))
1987+
19801988
if self.axison and not inframe:
19811989
if self._axisbelow:
19821990
self.xaxis.set_zorder(0.5)
@@ -1993,12 +2001,6 @@ def draw(self, renderer=None, inframe=False):
19932001
if self.legend_ is not None:
19942002
artists.append(self.legend_)
19952003

1996-
# the frame draws the edges around the axes patch -- we
1997-
# decouple these so the patch can be in the background and the
1998-
# frame in the foreground.
1999-
if self.axison and self._frameon:
2000-
artists.extend(six.itervalues(self.spines))
2001-
20022004
if self.figure.canvas.is_saving():
20032005
dsu = [(a.zorder, a) for a in artists]
20042006
else:

‎lib/matplotlib/spines.py

Copy file name to clipboardExpand all lines: lib/matplotlib/spines.py
+1-8Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -376,14 +376,7 @@ def set_position(self, position):
376376
self._position = position
377377
self._calc_offset_transform()
378378

379-
t = self.get_spine_transform()
380-
if self.spine_type in ['left', 'right']:
381-
t2 = mtransforms.blended_transform_factory(t,
382-
self.axes.transData)
383-
elif self.spine_type in ['bottom', 'top']:
384-
t2 = mtransforms.blended_transform_factory(self.axes.transData,
385-
t)
386-
self.set_transform(t2)
379+
self.set_transform(self.get_spine_transform())
387380

388381
if self.axis is not None:
389382
self.axis.cla()
Binary file not shown.
Loading

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.