@@ -19,8 +19,19 @@ Figure and Axes creation / management
19
19
20
20
The relative width and height of columns and rows in `~.Figure.subplots ` and
21
21
`~.Figure.subplot_mosaic ` can be controlled by passing *height_ratios * and
22
- *width_ratios * keyword arguments to the methods. Previously, this required
23
- passing the ratios in *gridspec_kws * arguments.
22
+ *width_ratios * keyword arguments to the methods:
23
+
24
+ .. plot ::
25
+ :include-source: true
26
+
27
+ fig = plt.figure()
28
+ axs = fig.subplots(3, 1, sharex=True, height_ratios=[3, 1, 1])
29
+
30
+ Previously, this required passing the ratios in *gridspec_kw * arguments::
31
+
32
+ fig = plt.figure()
33
+ axs = fig.subplots(3, 1, sharex=True,
34
+ gridspec_kw=dict(height_ratios=[3, 1, 1]))
24
35
25
36
Constrained layout is no longer considered experimental
26
37
-------------------------------------------------------
@@ -41,11 +52,31 @@ Compressed layout for fixed-aspect ratio Axes
41
52
---------------------------------------------
42
53
43
54
Simple arrangements of Axes with fixed aspect ratios can now be packed together
44
- with ``fig, axs = plt.subplots(2, 3, layout='compressed') ``. With
45
- ``layout='tight' `` or ``'constrained' ``, Axes with a fixed aspect ratio can
46
- leave large gaps between each other. Using the ``layout='compressed' `` layout
47
- reduces the space between the Axes, and adds the extra space to the outer
48
- margins. See :ref: `compressed_layout `.
55
+ with ``fig, axs = plt.subplots(2, 3, layout='compressed') ``.
56
+
57
+ With ``layout='tight' `` or ``'constrained' ``, Axes with a fixed aspect ratio
58
+ can leave large gaps between each other:
59
+
60
+ .. plot ::
61
+
62
+ fig, axs = plt.subplots(2, 2, figsize=(5, 3),
63
+ sharex=True, sharey=True, layout="constrained")
64
+ for ax in axs.flat:
65
+ ax.imshow([[0, 1], [2, 3]])
66
+ fig.suptitle("fixed-aspect plots, layout='constrained'")
67
+
68
+ Using the ``layout='compressed' `` layout reduces the space between the Axes,
69
+ and adds the extra space to the outer margins:
70
+
71
+ .. plot ::
72
+
73
+ fig, axs = plt.subplots(2, 2, figsize=(5, 3),
74
+ sharex=True, sharey=True, layout='compressed')
75
+ for ax in axs.flat:
76
+ ax.imshow([[0, 1], [2, 3]])
77
+ fig.suptitle("fixed-aspect plots, layout='compressed'")
78
+
79
+ See :ref: `compressed_layout ` for further details.
49
80
50
81
Layout engines may now be removed
51
82
---------------------------------
@@ -64,6 +95,16 @@ with the previous layout engine.
64
95
*axes_class * keyword arguments, so that subclasses of `matplotlib.axes.Axes `
65
96
may be returned.
66
97
98
+ .. plot ::
99
+ :include-source: true
100
+
101
+ fig, ax = plt.subplots()
102
+
103
+ ax.plot([0, 2], [1, 2])
104
+
105
+ polar_ax = ax.inset_axes([0.75, 0.25, 0.2, 0.2], projection='polar')
106
+ polar_ax.plot([0, 2], [1, 2])
107
+
67
108
WebP is now a supported output format
68
109
-------------------------------------
69
110
@@ -127,37 +168,72 @@ controlling the widths of the caps in box and whisker plots.
127
168
128
169
x = np.linspace(-7, 7, 140)
129
170
x = np.hstack([-25, x, 25])
171
+ capwidths = [0.01, 0.2]
172
+
130
173
fig, ax = plt.subplots()
131
- ax.boxplot([x, x], notch=True, capwidths=[0.01, 0.2])
174
+ ax.boxplot([x, x], notch=True, capwidths=capwidths)
175
+ ax.set_title(f'{capwidths=}')
132
176
133
177
Easier labelling of bars in bar plot
134
178
------------------------------------
135
179
136
- The *label * argument of `~.Axes.bar ` can now be passed a list of labels for the
137
- bars.
180
+ The *label * argument of `~.Axes.bar ` and `~.Axes.barh ` can now be passed a list
181
+ of labels for the bars. The list must be the same length as *x * and labels the
182
+ individual bars. Repeated labels are not de-duplicated and will cause repeated
183
+ label entries, so this is best used when bars also differ in style (e.g., by
184
+ passing a list to *color *, as below.)
138
185
139
- .. code-block :: python
186
+ .. plot ::
187
+ :include-source: true
140
188
141
189
x = ["a", "b", "c"]
142
190
y = [10, 20, 15]
191
+ color = ['C0', 'C1', 'C2']
143
192
144
193
fig, ax = plt.subplots()
145
- bar_container = ax.barh (x, y, label = x)
146
- [bar.get_label() for bar in bar_container]
194
+ ax.bar (x, y, color=color , label=x)
195
+ ax.legend()
147
196
148
197
New style format string for colorbar ticks
149
198
------------------------------------------
150
199
151
200
The *format * argument of `~.Figure.colorbar ` (and other colorbar methods) now
152
201
accepts ``{} ``-style format strings.
153
202
203
+ .. code-block :: python
204
+
205
+ fig, ax = plt.subplots()
206
+ im = ax.imshow(z)
207
+ fig.colorbar(im, format = ' {x:.2e } ' ) # Instead of '%.2e'
208
+
154
209
Linestyles for negative contours may be set individually
155
210
--------------------------------------------------------
156
211
157
212
The line style of negative contours may be set by passing the
158
213
*negative_linestyles * argument to `.Axes.contour `. Previously, this style could
159
214
only be set globally via :rc: `contour.negative_linestyles `.
160
215
216
+ .. plot ::
217
+ :include-source: true
218
+
219
+ delta = 0.025
220
+ x = np.arange(-3.0, 3.0, delta)
221
+ y = np.arange(-2.0, 2.0, delta)
222
+ X, Y = np.meshgrid(x, y)
223
+ Z1 = np.exp(-X**2 - Y**2)
224
+ Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
225
+ Z = (Z1 - Z2) * 2
226
+
227
+ fig, axs = plt.subplots(1, 2)
228
+
229
+ CS = axs[0].contour(X, Y, Z, 6, colors='k')
230
+ axs[0].clabel(CS, fontsize=9, inline=True)
231
+ axs[0].set_title('Default negative contours')
232
+
233
+ CS = axs[1].contour(X, Y, Z, 6, colors='k', negative_linestyles='dotted')
234
+ axs[1].clabel(CS, fontsize=9, inline=True)
235
+ axs[1].set_title('Dotted negative contours')
236
+
161
237
ContourPy used for quad contour calculations
162
238
--------------------------------------------
163
239
@@ -193,6 +269,19 @@ The *markerfacecoloralt* parameter is now passed to the line plotter from
193
269
passed to `.Line2D `, rather than claiming that all keyword arguments are passed
194
270
on.
195
271
272
+ .. plot ::
273
+ :include-source: true
274
+
275
+ x = np.arange(0.1, 4, 0.5)
276
+ y = np.exp(-x)
277
+
278
+ fig, ax = plt.subplots()
279
+ ax.errorbar(x, y, xerr=0.2, yerr=0.4,
280
+ linestyle=':', color='darkgrey',
281
+ marker='o', markersize=20, fillstyle='left',
282
+ markerfacecolor='tab:blue', markerfacecoloralt='tab:orange',
283
+ markeredgecolor='tab:brown', markeredgewidth=2)
284
+
196
285
``streamplot `` can disable streamline breaks
197
286
--------------------------------------------
198
287
@@ -266,7 +355,40 @@ Rectangle patch rotation point
266
355
------------------------------
267
356
268
357
The rotation point of the `~matplotlib.patches.Rectangle ` can now be set to
269
- 'xy', 'center' or a 2-tuple of numbers.
358
+ 'xy', 'center' or a 2-tuple of numbers using the *rotation_point * argument.
359
+
360
+ .. plot ::
361
+
362
+ fig, ax = plt.subplots()
363
+
364
+ rect = plt.Rectangle((0, 0), 1, 1, facecolor='none', edgecolor='C0')
365
+ ax.add_patch(rect)
366
+ ax.annotate('Unrotated', (1, 0), color='C0',
367
+ horizontalalignment='right', verticalalignment='top',
368
+ xytext=(0, -3), textcoords='offset points')
369
+
370
+ for rotation_point, color in zip(['xy', 'center', (0.75, 0.25)],
371
+ ['C1', 'C2', 'C3']):
372
+ ax.add_patch(
373
+ plt.Rectangle((0, 0), 1, 1, facecolor='none', edgecolor=color,
374
+ angle=45, rotation_point=rotation_point))
375
+
376
+ if rotation_point == 'center':
377
+ point = 0.5, 0.5
378
+ elif rotation_point == 'xy':
379
+ point = 0, 0
380
+ else:
381
+ point = rotation_point
382
+ ax.plot(point[:1], point[1:], color=color, marker='o')
383
+
384
+ label = f'{rotation_point}'
385
+ if label == 'xy':
386
+ label += ' (default)'
387
+ ax.annotate(label, point, color=color,
388
+ xytext=(3, 3), textcoords='offset points')
389
+
390
+ ax.set_aspect(1)
391
+ ax.set_title('rotation_point options')
270
392
271
393
Colors and colormaps
272
394
====================
@@ -317,7 +439,7 @@ It is now possible to set or get minor ticks using `.pyplot.xticks` and
317
439
plt.figure()
318
440
plt.plot([1, 2, 3, 3.5], [2, 1, 0, -0.5])
319
441
plt.xticks([1, 2, 3], ["One", "Zwei", "Trois"])
320
- plt.xticks([1.414 , 2.5, 3.142 ],
442
+ plt.xticks([np.sqrt(2) , 2.5, np.pi ],
321
443
[r"$\s qrt{2}$", r"$\f rac{5}{2}$", r"$\p i$"], minor=True)
322
444
323
445
Legends
@@ -330,6 +452,14 @@ Legend can control alignment of title and handles
330
452
the keyword argument *alignment *. You can also use `.Legend.set_alignment ` to
331
453
control the alignment on existing Legends.
332
454
455
+ .. plot ::
456
+ :include-source: true
457
+
458
+ fig, axs = plt.subplots(3, 1)
459
+ for i, alignment in enumerate(['left', 'center', 'right']):
460
+ axs[i].plot(range(10), label='test')
461
+ axs[i].legend(title=f'{alignment=}', alignment=alignment)
462
+
333
463
*ncol * keyword argument to ``legend `` renamed to *ncols *
334
464
--------------------------------------------------------
335
465
@@ -358,15 +488,40 @@ rotation).
358
488
.. plot ::
359
489
:include-source: true
360
490
361
- from matplotlib.markers import MarkerStyle
491
+ from matplotlib.markers import CapStyle, JoinStyle, MarkerStyle
362
492
from matplotlib.transforms import Affine2D
363
- fig, ax = plt.subplots(figsize=(6, 1))
364
- fig.suptitle('New markers', fontsize=14)
365
- for col, (size, rot) in enumerate(zip([2, 5, 10], [0, 45, 90])):
493
+
494
+ fig, axs = plt.subplots(3, 1, layout='constrained')
495
+ for ax in axs:
496
+ ax.axis('off')
497
+ ax.set_xlim(-0.5, 2.5)
498
+
499
+ axs[0].set_title('Cap styles', fontsize=14)
500
+ for col, cap in enumerate(CapStyle):
501
+ axs[0].plot(col, 0, markersize=32, markeredgewidth=8,
502
+ marker=MarkerStyle('1', capstyle=cap))
503
+ # Show the marker edge for comparison with the cap.
504
+ axs[0].plot(col, 0, markersize=32, markeredgewidth=1,
505
+ markerfacecolor='none', markeredgecolor='lightgrey',
506
+ marker=MarkerStyle('1'))
507
+ axs[0].annotate(cap.name, (col, 0),
508
+ xytext=(20, -5), textcoords='offset points')
509
+
510
+ axs[1].set_title('Join styles', fontsize=14)
511
+ for col, join in enumerate(JoinStyle):
512
+ axs[1].plot(col, 0, markersize=32, markeredgewidth=8,
513
+ marker=MarkerStyle('*', joinstyle=join))
514
+ # Show the marker edge for comparison with the join.
515
+ axs[1].plot(col, 0, markersize=32, markeredgewidth=1,
516
+ markerfacecolor='none', markeredgecolor='lightgrey',
517
+ marker=MarkerStyle('*'))
518
+ axs[1].annotate(join.name, (col, 0),
519
+ xytext=(20, -5), textcoords='offset points')
520
+
521
+ axs[2].set_title('Arbitrary transforms', fontsize=14)
522
+ for col, (size, rot) in enumerate(zip([2, 5, 7], [0, 45, 90])):
366
523
t = Affine2D().rotate_deg(rot).scale(size)
367
- ax.plot(col, 0, marker=MarkerStyle("*", transform=t))
368
- ax.axis("off")
369
- ax.set_xlim(-0.1, 2.4)
524
+ axs[2].plot(col, 0, marker=MarkerStyle('*', transform=t))
370
525
371
526
Fonts and Text
372
527
==============
@@ -382,10 +537,10 @@ them in order to locate a required glyph.
382
537
:alt: The phrase "There are 几个汉字 in between!" rendered in various fonts.
383
538
:include-source: True
384
539
385
- text = "There are 几个汉字 in between!"
386
-
387
540
plt.rcParams["font.size"] = 20
388
541
fig = plt.figure(figsize=(4.75, 1.85))
542
+
543
+ text = "There are 几个汉字 in between!"
389
544
fig.text(0.05, 0.85, text, family=["WenQuanYi Zen Hei"])
390
545
fig.text(0.05, 0.65, text, family=["Noto Sans CJK JP"])
391
546
fig.text(0.05, 0.45, text, family=["DejaVu Sans", "Noto Sans CJK JP"])
@@ -434,6 +589,25 @@ For figure labels, ``Figure.supxlabel`` and ``Figure.supylabel``, the size and
434
589
weight can be set separately from the figure title using :rc: `figure.labelsize `
435
590
and :rc: `figure.labelweight `.
436
591
592
+ .. plot ::
593
+ :include-source: true
594
+
595
+ # Original (previously combined with below) rcParams:
596
+ plt.rcParams['figure.titlesize'] = 64
597
+ plt.rcParams['figure.titleweight'] = 'bold'
598
+
599
+ # New rcParams:
600
+ plt.rcParams['figure.labelsize'] = 32
601
+ plt.rcParams['figure.labelweight'] = 'bold'
602
+
603
+ fig, axs = plt.subplots(2, 2, layout='constrained')
604
+ for ax in axs.flat:
605
+ ax.set(xlabel='xlabel', ylabel='ylabel')
606
+
607
+ fig.suptitle('suptitle')
608
+ fig.supxlabel('supxlabel')
609
+ fig.supylabel('supylabel')
610
+
437
611
Note that if you have changed :rc: `figure.titlesize ` or
438
612
:rc: `figure.titleweight `, you must now also change the introduced parameters
439
613
for a result consistent with past behaviour.
@@ -487,14 +661,15 @@ The focal length can be calculated from a desired FOV via the equation:
487
661
488
662
from mpl_toolkits.mplot3d import axes3d
489
663
490
- fig, axs = plt.subplots(1, 3, subplot_kw={'projection': '3d'})
491
664
X, Y, Z = axes3d.get_test_data(0.05)
492
- focal_lengths = [0.2, 1, np.inf]
493
- for ax, fl in zip(axs, focal_lengths):
665
+
666
+ fig, axs = plt.subplots(1, 3, figsize=(7, 4),
667
+ subplot_kw={'projection': '3d'})
668
+
669
+ for ax, focal_length in zip(axs, [0.2, 1, np.inf]):
494
670
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
495
- ax.set_proj_type('persp', focal_length=fl)
496
- ax.set_title(f"focal_length = {fl}")
497
- fig.set_size_inches(10, 4)
671
+ ax.set_proj_type('persp', focal_length=focal_length)
672
+ ax.set_title(f"{focal_length=}")
498
673
499
674
3D plots gained a 3rd "roll" viewing angle
500
675
------------------------------------------
@@ -510,9 +685,11 @@ existing 3D plots.
510
685
:include-source: true
511
686
512
687
from mpl_toolkits.mplot3d import axes3d
513
- fig = plt.figure()
514
- ax = fig.add_subplot(projection='3d')
688
+
515
689
X, Y, Z = axes3d.get_test_data(0.05)
690
+
691
+ fig, ax = plt.subplots(subplot_kw={'projection': '3d'})
692
+
516
693
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
517
694
ax.view_init(elev=0, azim=0, roll=30)
518
695
ax.set_title('elev=0, azim=0, roll=30')
@@ -528,25 +705,27 @@ Users can set the aspect ratio for the X, Y, Z axes of a 3D plot to be 'equal',
528
705
529
706
from itertools import combinations, product
530
707
531
- aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz')
532
- fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'})
708
+ aspects = [
709
+ ['auto', 'equal', '.'],
710
+ ['equalxy', 'equalyz', 'equalxz'],
711
+ ]
712
+ fig, axs = plt.subplot_mosaic(aspects, figsize=(7, 6),
713
+ subplot_kw={'projection': '3d'})
533
714
534
715
# Draw rectangular cuboid with side lengths [1, 1, 5]
535
716
r = [0, 1]
536
717
scale = np.array([1, 1, 5])
537
718
pts = combinations(np.array(list(product(r, r, r))), 2)
538
719
for start, end in pts:
539
720
if np.sum(np.abs(start - end)) == r[1] - r[0]:
540
- for ax in axs:
721
+ for ax in axs.values() :
541
722
ax.plot3D(*zip(start*scale, end*scale), color='C0')
542
723
543
724
# Set the aspect ratios
544
- for i , ax in enumerate( axs):
725
+ for aspect , ax in axs.items( ):
545
726
ax.set_box_aspect((3, 4, 5))
546
- ax.set_aspect(aspects[i])
547
- ax.set_title(f"set_aspect('{aspects[i]}')")
548
-
549
- fig.set_size_inches(13, 3)
727
+ ax.set_aspect(aspect)
728
+ ax.set_title(f'set_aspect({aspect!r})')
550
729
551
730
Interactive tool improvements
552
731
=============================
0 commit comments