Matplotlib 3.10.0 新特性 (2024年12月13日)#

有关自上次修订以来的所有议题和拉取请求列表,请参阅 3.10.0 的 GitHub 统计数据 (2024年12月14日)

无障碍颜色#

全新的更易访问的颜色循环#

增加了一个名为 'petroff10' 的新颜色循环。该循环结合了算法强制的无障碍约束(包括色觉缺陷建模)以及通过众包颜色偏好调查开发的机器学习美学模型。它旨在兼顾普遍的美感与色盲友好性,以便作为通用设计的默认设置。有关详细信息,请参阅 Petroff, M. A.: "Accessible Color Sequences for Data Visualization" 及相关的 SciPy 演讲。样式表 参考 中包含了一个演示。要将此颜色循环加载以替代默认值

import matplotlib.pyplot as plt
plt.style.use('petroff10')

深色模式发散型色图#

新增了三种发散型色图:“berlin”、“managua”和“vanimo”。它们是深色模式发散型色图,中心亮度最低,两端亮度最高。这些色图取自 F. Crameri 的 Scientific colour maps 8.0.1 版本(DOI:https://doi.org/10.5281/zenodo.1243862)。

import numpy as np
import matplotlib.pyplot as plt

vals = np.linspace(-5, 5, 100)
x, y = np.meshgrid(vals, vals)
img = np.sin(x*y)

_, ax = plt.subplots(1, 3)
ax[0].imshow(img, cmap="berlin")
ax[1].imshow(img, cmap="managua")
ax[2].imshow(img, cmap="vanimo")

(源代码, 2x.png, png)

Example figures using "imshow" with dark-mode diverging colormaps on positive and negative data. First panel: "berlin" (blue to red with a black center); second panel: "managua" (orange to cyan with a dark purple center); third panel: "vanimo" (pink to green with a black center).

绘图和标注改进#

contourcontourf 中指定单一颜色#

contourcontourf 以前只接受以字符串形式表示的单一颜色。此限制现已取消,现在可以传入 指定颜色 教程中描述的任何格式的单一颜色。

import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(6, 3))
z = [[0, 1], [1, 2]]

ax1.contour(z, colors=('r', 0.4))
ax2.contour(z, colors=(0.1, 0.2, 0.5))

plt.show()

(源代码, 2x.png, png)

Two-panel example contour plots.  The left panel has all transparent red contours.  The right panel has all dark blue contours.

向量化的 hist 样式参数#

hist 方法的 hatchedgecolorfacecolorlinewidthlinestyle 参数现在实现了向量化。这意味着当输入 x 包含多个数据集时,您可以为每个直方图分别传递参数。

import matplotlib.pyplot as plt
import numpy as np
np.random.seed(19680801)

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(9, 9))

data1 = np.random.poisson(5, 1000)
data2 = np.random.poisson(7, 1000)
data3 = np.random.poisson(10, 1000)

labels = ["Data 1", "Data 2", "Data 3"]

ax1.hist([data1, data2, data3], bins=range(17), histtype="step", stacked=True,
         edgecolor=["red", "green", "blue"], linewidth=[1, 2, 3])
ax1.set_title("Different linewidths")
ax1.legend(labels)

ax2.hist([data1, data2, data3], bins=range(17), histtype="barstacked",
         hatch=["/", ".", "*"])
ax2.set_title("Different hatch patterns")
ax2.legend(labels)

ax3.hist([data1, data2, data3], bins=range(17), histtype="bar", fill=False,
         edgecolor=["red", "green", "blue"], linestyle=["--", "-.", ":"])
ax3.set_title("Different linestyles")
ax3.legend(labels)

ax4.hist([data1, data2, data3], bins=range(17), histtype="barstacked",
         facecolor=["red", "green", "blue"])
ax4.set_title("Different facecolors")
ax4.legend(labels)

plt.show()

(源代码, 2x.png, png)

Four charts, each displaying stacked histograms of three Poisson distributions. Each chart differentiates the histograms using various parameters: top left uses different linewidths, top right uses different hatches, bottom left uses different edgecolors, and bottom right uses different facecolors. Each histogram on the left side also has a different edgecolor.

InsetIndicator 绘图对象#

indicate_insetindicate_inset_zoom 现在返回 InsetIndicator 的实例,其中包含矩形和连接线补丁。这些补丁现在会自动更新,使得

ax.indicate_inset_zoom(ax_inset)
ax_inset.set_xlim(new_lim)

现在的运行结果与以下代码一致

ax_inset.set_xlim(new_lim)
ax.indicate_inset_zoom(ax_inset)

matplotlib.ticker.EngFormatter 现在可以计算偏移量#

matplotlib.ticker.EngFormatter 现在可以在坐标轴附近显示偏移文本。通过与 matplotlib.ticker.ScalarFormatter 共享的逻辑,它能够确定数据是否需要偏移,并配合适当的 SI 单位前缀及用户提供的 unit 进行显示。

要启用此新行为,只需在实例化 matplotlib.ticker.EngFormatter 时传入 useOffset=True。请参阅示例 带 SI 前缀的偏移量及自然量级顺序

(源代码, 2x.png, png)

修复 ImageGrid 单个颜色条的填充问题#

使用 cbar_mode="single"ImageGrid 不再为 cbar_location 设置为 "left" 或 "bottom" 时在坐标轴和颜色条之间添加 axes_pad。如果需要,可以使用 cbar_pad 添加额外的间距。

ax.table 现在接受 pandas DataFrame#

table 方法现在可以接受 Pandas DataFrame 作为 cellText 参数。

import matplotlib.pyplot as plt
import pandas as pd

data = {
    'Letter': ['A', 'B', 'C'],
    'Number': [100, 200, 300]
}

df = pd.DataFrame(data)
fig, ax = plt.subplots()
table = ax.table(df, loc='center')  # or table = ax.table(cellText=df, loc='center')
ax.axis('off')
plt.show()

子图现在按行优先顺序添加#

Figure.subfigures 现在为保证 API 一致性,按行优先顺序添加。

import matplotlib.pyplot as plt

fig = plt.figure()
subfigs = fig.subfigures(3, 3)
x = np.linspace(0, 10, 100)

for i, sf in enumerate(fig.subfigs):
    ax = sf.subplots()
    ax.plot(x, np.sin(x + i), label=f'Subfigure {i+1}')
    sf.suptitle(f'Subfigure {i+1}')
    ax.set_xticks([])
    ax.set_yticks([])
plt.show()

(源代码, 2x.png, png)

Example of creating 3 by 3 subfigures.

boxplotbxp 的方向参数#

箱线图添加了新的参数 orientation: {"vertical", "horizontal"} 以更改绘图方向。这取代了已弃用的 vert: bool 参数。

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
np.random.seed(19680801)
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]

ax.boxplot(all_data, orientation='horizontal')
plt.show()

(源代码, 2x.png, png)

Example of creating 4 horizontal boxplots.

violinplotviolin 的方向参数#

小提琴图添加了新的参数 orientation: {"vertical", "horizontal"} 以更改绘图方向。这将取代已弃用的 vert: bool 参数。

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
np.random.seed(19680801)
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]

ax.violinplot(all_data, orientation='horizontal')
plt.show()

(源代码, 2x.png, png)

Example of creating 4 horizontal violinplots.

FillBetweenPolyCollection#

新的类 matplotlib.collections.FillBetweenPolyCollection 提供了 set_data 方法,支持例如重采样功能(galleries/event_handling/resample.html)。matplotlib.axes.Axes.fill_between()matplotlib.axes.Axes.fill_betweenx() 现在返回这个新类。

import numpy as np
from matplotlib import pyplot as plt

t = np.linspace(0, 1)

fig, ax = plt.subplots()
coll = ax.fill_between(t, -t**2, t**2)
fig.savefig("before.png")

coll.set_data(t, -t**4, t**4)
fig.savefig("after.png")

matplotlib.colorizer.Colorizer 作为 normcmap 的容器#

matplotlib.colorizer.Colorizer 封装了数据到颜色的转换流水线。它使色图重用更加容易,例如跨多个图像使用。现在,支持 normcmap 关键字参数的绘图方法也都接受 colorizer 关键字参数。

在以下示例中,norm 和 cmap 会在多个图上同时发生更改

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np

x = np.linspace(-2, 2, 50)[np.newaxis, :]
y = np.linspace(-2, 2, 50)[:, np.newaxis]
im_0 = 1 * np.exp( - (x**2 + y**2 - x * y))
im_1 = 2 * np.exp( - (x**2 + y**2 + x * y))

colorizer = mpl.colorizer.Colorizer()
fig, axes = plt.subplots(1, 2, figsize=(6, 2))
cim_0 = axes[0].imshow(im_0, colorizer=colorizer)
fig.colorbar(cim_0)
cim_1 = axes[1].imshow(im_1, colorizer=colorizer)
fig.colorbar(cim_1)

colorizer.vmin = 0.5
colorizer.vmax = 2
colorizer.cmap = 'RdBu'

(源代码, 2x.png, png)

Example use of a matplotlib.colorizer.Colorizer object

所有使用数据到颜色流水线的绘图方法,如果没有提供 colorizer,现在都会创建一个 colorizer 对象。它可以被后续的绘图对象重用,从而共享单一的数据到颜色转换流水线。

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np

x = np.linspace(-2, 2, 50)[np.newaxis, :]
y = np.linspace(-2, 2, 50)[:, np.newaxis]
im_0 = 1 * np.exp( - (x**2 + y**2 - x * y))
im_1 = 2 * np.exp( - (x**2 + y**2 + x * y))

fig, axes = plt.subplots(1, 2, figsize=(6, 2))

cim_0 = axes[0].imshow(im_0, cmap='RdBu', vmin=0.5, vmax=2)
fig.colorbar(cim_0)
cim_1 = axes[1].imshow(im_1, colorizer=cim_0.colorizer)
fig.colorbar(cim_1)

cim_1.cmap = 'rainbow'

(源代码, 2x.png, png)

Example of how artists that share a ``colorizer`` have coupled colormaps

3D 绘图改进#

3D 线条之间的填充#

新方法 Axes3D.fill_between 允许使用多边形填充两条 3D 线条之间的表面。

N = 50
theta = np.linspace(0, 2*np.pi, N)

x1 = np.cos(theta)
y1 = np.sin(theta)
z1 = 0.1 * np.sin(6 * theta)

x2 = 0.6 * np.cos(theta)
y2 = 0.6 * np.sin(theta)
z2 = 2  # Note that scalar values work in addition to length N arrays

fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.fill_between(x1, y1, z1, x2, y2, z2,
                alpha=0.5, edgecolor='k')

(源代码, 2x.png, png)

Example of 3D fill_between

使用鼠标旋转 3D 图形#

使用鼠标旋转三维图形变得更加直观。图形现在对鼠标移动的反应保持一致,独立于当前的旋转方向;并且可以控制所有 3 个旋转自由度(方位角、仰角和横滚角)。默认情况下,它使用 Ken Shoemake 的 ARCBALL [1] 的变体。特定的鼠标旋转样式可以通过 rcParams["axes3d.mouserotationstyle"](默认:'arcball')进行设置。另请参阅 鼠标旋转

要恢复原始的鼠标旋转样式,创建一个包含以下内容的 matplotlibrc 文件

axes3d.mouserotationstyle: azel

要尝试各种鼠标旋转样式中的一种

import matplotlib as mpl
mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball'  # 'azel', 'trackball', 'sphere', or 'arcball'

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm

ax = plt.figure().add_subplot(projection='3d')

X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)

surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
                       linewidth=0, antialiased=False)

plt.show()

3D 图形中的数据现在可以动态裁剪到坐标轴视图限制内#

所有 3D 绘图函数现在都支持 axlim_clip 关键字参数,它会将数据裁剪到坐标轴视图限制内,隐藏该边界之外的所有数据。这种裁剪将在平移和缩放时实时动态应用。

请注意,如果线段或 3D 补丁的一个顶点被裁剪,则整个线段或补丁都将被隐藏。无法显示部分线条或补丁以使它们在视图框边界处“平滑”截断是当前渲染器的一个限制。

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
x = np.arange(-5, 5, 0.5)
y = np.arange(-5, 5, 0.5)
X, Y = np.meshgrid(x, y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)

# Note that when a line has one vertex outside the view limits, the entire
# line is hidden. The same is true for 3D patches (not shown).
# In this example, data where x < 0 or z > 0.5 is clipped.
ax.plot_wireframe(X, Y, Z, color='C0')
ax.plot_wireframe(X, Y, Z, color='C1', axlim_clip=True)
ax.set(xlim=(0, 10), ylim=(-5, 5), zlim=(-1, 0.5))
ax.legend(['axlim_clip=False (default)', 'axlim_clip=True'])

(源代码, 2x.png, png)

Example of default behavior (blue) and axlim_clip=True (orange)

对自由线程 CPython 3.13 的初步支持#

Matplotlib 3.10 对 CPython 3.13 的自由线程构建版本提供了初步支持。有关自由线程 Python 的更多详细信息,请参阅 https://py-free-threading.github.ioPEP 703 以及 CPython 3.13 发行说明

对自由线程 Python 的支持并不意味着 Matplotlib 是完全线程安全的。我们预期在单个线程内使用 Figure 是可以正常工作的,尽管输入数据通常会被复制,但在另一个线程中修改用于绘图的数据对象可能会在某些情况下导致不一致。强烈建议不要使用任何全局状态(如 pyplot 模块),因为它们很可能无法持续正常工作。此外,请注意大多数 GUI 工具包期望在主线程上运行,因此在其他线程中进行交互式使用可能受到限制或不被支持。

如果您对自由线程 Python 感兴趣,例如因为您有一个基于多进程的工作流并希望在 Python 线程中运行,我们鼓励您进行测试和尝试。如果您遇到怀疑是由 Matplotlib 引起的问题,请提交议题,并首先检查该错误是否在“常规”非自由线程的 CPython 3.13 构建版本中也会出现。

其他改进#

svg.id rcParam 参数#

rcParams["svg.id"](默认:None)允许您在顶级 <svg> 标签中插入一个 id 属性。

例如 rcParams["svg.id"] = "svg1" 会产生

<svg
    xmlns:xlink="https://w3org.cn/1999/xlink"
    width="50pt" height="50pt"
    viewBox="0 0 50 50"
    xmlns="https://w3org.cn/2000/svg"
    version="1.1"
    id="svg1"
></svg>

如果您想使用 <use> 标签将整个 matplotlib SVG 文件链接到另一个 SVG 文件中,这将非常有用。

<svg>
<use
    width="50" height="50"
    xlink:href="mpl.svg#svg1" id="use1"
    x="0" y="0"
/></svg>

其中 #svg1 指示符现在将指向顶级 <svg> 标签,从而包含整个文件。

默认情况下,不包含 id 标签。

异常处理控制#

当传入无效关键字参数时引发的异常现在将该参数名称作为异常的 name 属性包含在内。这为异常处理提供了更好的控制。

import matplotlib.pyplot as plt

def wobbly_plot(args, **kwargs):
    w = kwargs.pop('wobble_factor', None)

    try:
        plt.plot(args, **kwargs)
    except AttributeError as e:
        raise AttributeError(f'wobbly_plot does not take parameter {e.name}') from e


wobbly_plot([0, 1], wibble_factor=5)
AttributeError: wobbly_plot does not take parameter wibble_factor

提高 Agg 渲染器的图形限制#

使用 Agg 渲染器的图形现在限制在每个方向上 2**23 像素,而不是之前的 2**16。此外,修复了导致绘图对象在水平方向超过 2**15 像素后无法正确渲染的错误。

请注意,如果您使用的是 GUI 后端,它可能有自己的更小限制(这些限制本身可能取决于屏幕尺寸)。

杂项变更#