@@ -19,8 +19,19 @@ Figure and Axes creation / management
1919
2020The relative width and height of columns and rows in `~.Figure.subplots ` and
2121`~.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]))
2435
2536Constrained layout is no longer considered experimental
2637-------------------------------------------------------
@@ -41,11 +52,31 @@ Compressed layout for fixed-aspect ratio Axes
4152---------------------------------------------
4253
4354Simple 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.
4980
5081Layout engines may now be removed
5182---------------------------------
@@ -64,6 +95,16 @@ with the previous layout engine.
6495*axes_class * keyword arguments, so that subclasses of `matplotlib.axes.Axes `
6596may be returned.
6697
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+
67108WebP is now a supported output format
68109-------------------------------------
69110
@@ -127,37 +168,72 @@ controlling the widths of the caps in box and whisker plots.
127168
128169 x = np.linspace(-7, 7, 140)
129170 x = np.hstack([-25, x, 25])
171+ capwidths = [0.01, 0.2]
172+
130173 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=}')
132176
133177Easier labelling of bars in bar plot
134178------------------------------------
135179
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.)
138185
139- .. code-block :: python
186+ .. plot ::
187+ :include-source: true
140188
141189 x = ["a", "b", "c"]
142190 y = [10, 20, 15]
191+ color = ['C0', 'C1', 'C2']
143192
144193 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()
147196
148197New style format string for colorbar ticks
149198------------------------------------------
150199
151200The *format * argument of `~.Figure.colorbar ` (and other colorbar methods) now
152201accepts ``{} ``-style format strings.
153202
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+
154209 Linestyles for negative contours may be set individually
155210--------------------------------------------------------
156211
157212The line style of negative contours may be set by passing the
158213*negative_linestyles * argument to `.Axes.contour `. Previously, this style could
159214only be set globally via :rc: `contour.negative_linestyles `.
160215
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+
161237ContourPy used for quad contour calculations
162238--------------------------------------------
163239
@@ -193,6 +269,19 @@ The *markerfacecoloralt* parameter is now passed to the line plotter from
193269passed to `.Line2D `, rather than claiming that all keyword arguments are passed
194270on.
195271
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+
196285``streamplot `` can disable streamline breaks
197286--------------------------------------------
198287
@@ -266,7 +355,40 @@ Rectangle patch rotation point
266355------------------------------
267356
268357The 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')
270392
271393Colors and colormaps
272394====================
@@ -317,7 +439,7 @@ It is now possible to set or get minor ticks using `.pyplot.xticks` and
317439 plt.figure()
318440 plt.plot([1, 2, 3, 3.5], [2, 1, 0, -0.5])
319441 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 ],
321443 [r"$\s qrt{2}$", r"$\f rac{5}{2}$", r"$\p i$"], minor=True)
322444
323445Legends
@@ -330,6 +452,14 @@ Legend can control alignment of title and handles
330452the keyword argument *alignment *. You can also use `.Legend.set_alignment ` to
331453control the alignment on existing Legends.
332454
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+
333463*ncol * keyword argument to ``legend `` renamed to *ncols *
334464--------------------------------------------------------
335465
@@ -358,15 +488,40 @@ rotation).
358488.. plot ::
359489 :include-source: true
360490
361- from matplotlib.markers import MarkerStyle
491+ from matplotlib.markers import CapStyle, JoinStyle, MarkerStyle
362492 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])):
366523 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))
370525
371526Fonts and Text
372527==============
@@ -382,10 +537,10 @@ them in order to locate a required glyph.
382537 :alt: The phrase "There are 几个汉字 in between!" rendered in various fonts.
383538 :include-source: True
384539
385- text = "There are 几个汉字 in between!"
386-
387540 plt.rcParams["font.size"] = 20
388541 fig = plt.figure(figsize=(4.75, 1.85))
542+
543+ text = "There are 几个汉字 in between!"
389544 fig.text(0.05, 0.85, text, family=["WenQuanYi Zen Hei"])
390545 fig.text(0.05, 0.65, text, family=["Noto Sans CJK JP"])
391546 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
434589weight can be set separately from the figure title using :rc: `figure.labelsize `
435590and :rc: `figure.labelweight `.
436591
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+
437611Note that if you have changed :rc: `figure.titlesize ` or
438612:rc: `figure.titleweight `, you must now also change the introduced parameters
439613for a result consistent with past behaviour.
@@ -487,14 +661,15 @@ The focal length can be calculated from a desired FOV via the equation:
487661
488662 from mpl_toolkits.mplot3d import axes3d
489663
490- fig, axs = plt.subplots(1, 3, subplot_kw={'projection': '3d'})
491664 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]):
494670 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=}")
498673
4996743D plots gained a 3rd "roll" viewing angle
500675------------------------------------------
@@ -510,9 +685,11 @@ existing 3D plots.
510685 :include-source: true
511686
512687 from mpl_toolkits.mplot3d import axes3d
513- fig = plt.figure()
514- ax = fig.add_subplot(projection='3d')
688+
515689 X, Y, Z = axes3d.get_test_data(0.05)
690+
691+ fig, ax = plt.subplots(subplot_kw={'projection': '3d'})
692+
516693 ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
517694 ax.view_init(elev=0, azim=0, roll=30)
518695 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',
528705
529706 from itertools import combinations, product
530707
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'})
533714
534715 # Draw rectangular cuboid with side lengths [1, 1, 5]
535716 r = [0, 1]
536717 scale = np.array([1, 1, 5])
537718 pts = combinations(np.array(list(product(r, r, r))), 2)
538719 for start, end in pts:
539720 if np.sum(np.abs(start - end)) == r[1] - r[0]:
540- for ax in axs:
721+ for ax in axs.values() :
541722 ax.plot3D(*zip(start*scale, end*scale), color='C0')
542723
543724 # Set the aspect ratios
544- for i , ax in enumerate( axs):
725+ for aspect , ax in axs.items( ):
545726 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})')
550729
551730Interactive tool improvements
552731=============================
0 commit comments