What's new in Matplotlib 3.9.0 (May 15, 2024)#
For a list of all of the issues and pull requests since the last revision, see the GitHub statistics for 3.9.2 (Aug 12, 2024).
Plotting and Annotation improvements#
Axes.inset_axes
is no longer experimental#
Axes.inset_axes
is considered stable for use.
Legend support for Boxplot#
Boxplots now support a label parameter to create legend entries. Legend labels can be
passed as a list of strings to label multiple boxes in a single Axes.boxplot
call:
np.random.seed(19680801)
fruit_weights = [
np.random.normal(130, 10, size=100),
np.random.normal(125, 20, size=100),
np.random.normal(120, 30, size=100),
]
labels = ['peaches', 'oranges', 'tomatoes']
colors = ['peachpuff', 'orange', 'tomato']
fig, ax = plt.subplots()
ax.set_ylabel('fruit weight (g)')
bplot = ax.boxplot(fruit_weights,
patch_artist=True, # fill with color
label=labels)
# fill with colors
for patch, color in zip(bplot['boxes'], colors):
patch.set_facecolor(color)
ax.set_xticks([])
ax.legend()
(Source code
, 2x.png
, png
)
Or as a single string to each individual Axes.boxplot
:
fig, ax = plt.subplots()
data_A = np.random.random((100, 3))
data_B = np.random.random((100, 3)) + 0.2
pos = np.arange(3)
ax.boxplot(data_A, positions=pos - 0.2, patch_artist=True, label='Box A',
boxprops={'facecolor': 'steelblue'})
ax.boxplot(data_B, positions=pos + 0.2, patch_artist=True, label='Box B',
boxprops={'facecolor': 'lightblue'})
ax.legend()
(Source code
, 2x.png
, png
)
Percent sign in pie labels auto-escaped with usetex=True
#
It is common, with Axes.pie
, to specify labels that include a percent sign (%
),
which denotes a comment for LaTeX. When enabling LaTeX with rcParams["text.usetex"]
(default: False
) or passing
textprops={"usetex": True}
, this used to cause the percent sign to disappear.
Now, the percent sign is automatically escaped (by adding a preceding backslash) so that
it appears regardless of the usetex
setting. If you have pre-escaped the percent
sign, this will be detected, and remain as is.
hatch
parameter for stackplot#
The stackplot
hatch parameter now accepts a list of strings describing
hatching styles that will be applied sequentially to the layers in the stack:
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10,5))
cols = 10
rows = 4
data = (
np.reshape(np.arange(0, cols, 1), (1, -1)) ** 2
+ np.reshape(np.arange(0, rows), (-1, 1))
+ np.random.random((rows, cols))*5
)
x = range(data.shape[1])
ax1.stackplot(x, data, hatch="x")
ax2.stackplot(x, data, hatch=["//","\\","x","o"])
ax1.set_title("hatch='x'")
ax2.set_title("hatch=['//','\\\\','x','o']")
plt.show()
(Source code
, 2x.png
, png
)
Add option to plot only one half of violin plot#
Setting the parameter side to 'low' or 'high' allows to only plot one half of the
Axes.violinplot
.
# Fake data with reproducible random state.
np.random.seed(19680801)
data = np.random.normal(0, 8, size=100)
fig, ax = plt.subplots()
ax.violinplot(data, [0], showmeans=True, showextrema=True)
ax.violinplot(data, [1], showmeans=True, showextrema=True, side='low')
ax.violinplot(data, [2], showmeans=True, showextrema=True, side='high')
ax.set_title('Violin Sides Example')
ax.set_xticks([0, 1, 2], ['Default', 'side="low"', 'side="high"'])
ax.set_yticklabels([])
(Source code
, 2x.png
, png
)
axhline
and axhspan
on polar axes#
... now draw circles and circular arcs (axhline
) or annuli and wedges
(axhspan
).
fig = plt.figure()
ax = fig.add_subplot(projection="polar")
ax.set_rlim(0, 1.2)
ax.axhline(1, c="C0", alpha=.5)
ax.axhspan(.8, .9, fc="C1", alpha=.5)
ax.axhspan(.6, .7, .8, .9, fc="C2", alpha=.5)
(Source code
, 2x.png
, png
)
Subplot titles can now be automatically aligned#
Subplot axes titles can be misaligned vertically if tick labels or xlabels are placed at
the top of one subplot. The new align_titles
method on the Figure
class
will now align the titles vertically.
fig, axs = plt.subplots(1, 2, layout='constrained')
axs[0].plot(np.arange(0, 1e6, 1000))
axs[0].set_title('Title 0')
axs[0].set_xlabel('XLabel 0')
axs[1].plot(np.arange(1, 0, -0.1) * 2000, np.arange(1, 0, -0.1))
axs[1].set_title('Title 1')
axs[1].set_xlabel('XLabel 1')
axs[1].xaxis.tick_top()
axs[1].tick_params(axis='x', rotation=55)
(Source code
, 2x.png
, png
)
fig, axs = plt.subplots(1, 2, layout='constrained')
axs[0].plot(np.arange(0, 1e6, 1000))
axs[0].set_title('Title 0')
axs[0].set_xlabel('XLabel 0')
axs[1].plot(np.arange(1, 0, -0.1) * 2000, np.arange(1, 0, -0.1))
axs[1].set_title('Title 1')
axs[1].set_xlabel('XLabel 1')
axs[1].xaxis.tick_top()
axs[1].tick_params(axis='x', rotation=55)
fig.align_labels()
fig.align_titles()
(Source code
, 2x.png
, png
)
axisartist
can now be used together with standard Formatters
#
... instead of being limited to axisartist-specific ones.
Toggle minorticks on Axis#
Minor ticks on an Axis
can be displayed or removed using
minorticks_on
and minorticks_off
; e.g.,
ax.xaxis.minorticks_on()
. See also minorticks_on
.
StrMethodFormatter
now respects axes.unicode_minus
#
When formatting negative values, StrMethodFormatter
will now use unicode minus signs
if rcParams["axes.unicode_minus"]
(default: True
) is set.
>>> from matplotlib.ticker import StrMethodFormatter
>>> with plt.rc_context({'axes.unicode_minus': False}):
... formatter = StrMethodFormatter('{x}')
... print(formatter.format_data(-10))
-10
>>> with plt.rc_context({'axes.unicode_minus': True}):
... formatter = StrMethodFormatter('{x}')
... print(formatter.format_data(-10))
−10
Figure, Axes, and Legend Layout#
Subfigures now have controllable zorders#
Previously, setting the zorder of a subfigure had no effect, and those were plotted on top of any figure-level artists (i.e for example on top of fig-level legends). Now, subfigures behave like any other artists, and their zorder can be controlled, with default a zorder of 0.
x = np.linspace(1, 10, 10)
y1, y2 = x, -x
fig = plt.figure(constrained_layout=True)
subfigs = fig.subfigures(nrows=1, ncols=2)
for subfig in subfigs:
axarr = subfig.subplots(2, 1)
for ax in axarr.flatten():
(l1,) = ax.plot(x, y1, label="line1")
(l2,) = ax.plot(x, y2, label="line2")
subfigs[0].set_zorder(6)
l = fig.legend(handles=[l1, l2], loc="upper center", ncol=2)
(Source code
, 2x.png
, png
)
Getters for xmargin, ymargin and zmargin#
Axes.get_xmargin
, Axes.get_ymargin
and Axes3D.get_zmargin
methods have been
added to return the margin values set by Axes.set_xmargin
, Axes.set_ymargin
and
Axes3D.set_zmargin
, respectively.
Mathtext improvements#
mathtext
documentation improvements#
The documentation is updated to take information directly from the parser. This means that (almost) all supported symbols, operators, etc. are shown at Writing mathematical expressions.
mathtext
spacing corrections#
As consequence of the updated documentation, the spacing on a number of relational and operator symbols were correctly classified and therefore will be spaced properly.
Widget Improvements#
3D plotting improvements#
Setting 3D axis limits now set the limits exactly#
Previously, setting the limits of a 3D axis would always add a small margin to the
limits. Limits are now set exactly by default. The newly introduced rcparam
axes3d.automargin
can be used to revert to the old behavior where margin is
automatically added.
fig, axs = plt.subplots(1, 2, subplot_kw={'projection': '3d'})
plt.rcParams['axes3d.automargin'] = True
axs[0].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='Old Behavior')
plt.rcParams['axes3d.automargin'] = False # the default in 3.9.0
axs[1].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='New Behavior')
(Source code
, 2x.png
, png
)
Other improvements#
BackendRegistry#
New BackendRegistry
class is the single source of
truth for available backends. The singleton instance is
matplotlib.backends.backend_registry
. It is used internally by Matplotlib, and also
IPython (and therefore Jupyter) starting with IPython 8.24.0.
There are three sources of backends: built-in (source code is within the Matplotlib
repository), explicit module://some.backend
syntax (backend is obtained by loading
the module), or via an entry point (self-registering backend in an external package).
To obtain a list of all registered backends use:
>>> from matplotlib.backends import backend_registry
>>> backend_registry.list_all()
Add widths
, heights
and angles
setter to EllipseCollection
#
The widths
, heights
and angles
values of the
EllipseCollection
can now be changed after the collection has
been created.
from matplotlib.collections import EllipseCollection
rng = np.random.default_rng(0)
widths = (2, )
heights = (3, )
angles = (45, )
offsets = rng.random((10, 2)) * 10
fig, ax = plt.subplots()
ec = EllipseCollection(
widths=widths,
heights=heights,
angles=angles,
offsets=offsets,
units='x',
offset_transform=ax.transData,
)
ax.add_collection(ec)
ax.set_xlim(-2, 12)
ax.set_ylim(-2, 12)
new_widths = rng.random((10, 2)) * 2
new_heights = rng.random((10, 2)) * 3
new_angles = rng.random((10, 2)) * 180
ec.set(widths=new_widths, heights=new_heights, angles=new_angles)
(Source code
, 2x.png
, png
)
image.interpolation_stage
rcParam#
This new rcParam controls whether image interpolation occurs in "data" space or in "rgba" space.
Arrow patch position is now modifiable#
A setter method has been added that allows updating the position of the patches.Arrow
object without requiring a full re-draw.
from matplotlib import animation
from matplotlib.patches import Arrow
fig, ax = plt.subplots()
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
a = Arrow(2, 0, 0, 10)
ax.add_patch(a)
# code for modifying the arrow
def update(i):
a.set_data(x=.5, dx=i, dy=6, width=2)
ani = animation.FuncAnimation(fig, update, frames=15, interval=90, blit=False)
plt.show()
(Source code
, 2x.png
, png
)
NonUniformImage now has mouseover support#
When mousing over a NonUniformImage
, the data values are now
displayed.