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