绘图基础与环境配置

Matplotlib 简介与安装#

Matplotlib Logo

在 Python 的科学计算生态中,Matplotlib 是毫无争议的 2D 绘图基石。它的最大特点是极高的底层定制能力严谨的学术规范性。无论是控制图表的每一个像素细节,还是输出支持无损放大、可嵌入 LaTeX 公式的矢量图(如 PDF、SVG),Matplotlib 都能完美满足工程报告和中外核心期刊的出版要求。

在正式编写代码前,我们需要确保该库已安装。如果你使用的是标准的 Python 虚拟环境,可以直接通过终端执行以下命令进行安装:

pip install matplotlib

绘制第一幅图#

为了直观体验绘图流程,我们直接来绘制一幅相对完整的工程曲线图(假设这是某工作面 6 个小时内的瓦斯浓度变化趋势)。

在此过程中,我们会遇到国内开发者必然会碰到的“中文乱码”问题。Matplotlib 默认的英文字库不支持汉字,若不加设置,图表中的中文会变成方块。我们在代码开头使用两行全局配置即可彻底解决此问题。

请在你的编辑器中输入并运行以下代码:

import matplotlib.pyplot as plt

# 1. 指定全局使用衬线体(serif)
plt.rcParams["font.family"] = "serif"

# 2. 跨平台中文字体防御列表(你的杰作!)
plt.rcParams["font.serif"] = [
    "FandolSong",
    "SimSun",  # Windows
    "Songti SC",  # macOS
    "Noto Serif SC",  # Linux
]

# 3. 解决负号显示问题
plt.rcParams["axes.unicode_minus"] = False

# 4. 指定数学模式(MathText)使用类 Times New Roman 风格
plt.rcParams["mathtext.fontset"] = "stix"


# 2. 准备实验数据
depth = [255.4, 312.9, 361.3, 408.7, 466.8, 520.5]  # X轴数据:时间 (s)
max_horizontal_stress = [20.1, 25.2, 33.2, 38.8, 45.2, 48.2]  # Y轴数据:瓦斯浓度 (%)

# 3. 创建画布与坐标系
fig, ax = plt.subplots()

# 4. 绘制折线图,并设置线条样式和图例标签
ax.plot(
    depth, max_horizontal_stress, marker="o", color="red", label="实测最大水平应力"
)

# 5. 完善图表元素
ax.set_title("某矿最大水平应力随深度的变化关系")  # 图表标题
ax.set_xlabel(r"深度 $h$ ($\mathrm{m}$)")
ax.set_ylabel(r"最大水平应力 $\sigma_{max}$ ($\mathrm{MPa}$)")
ax.grid(True, linestyle="--")  # 开启虚线网格
ax.legend()  # 显示图例

# 6. 展示图表
plt.show()

运行后,你将看到一个带有坐标轴、数据点、网格线、中文标题和图例的完整折线图。这几行代码构成了后续所有复杂数据可视化的骨架。

Matplotlib 绘图基本概念#

在查阅官方文档或报错信息时,你经常会遇到各种图表元素的英文术语。Matplotlib 对图形结构的划分非常严密,厘清这些概念对于后续的精细化排版(如微调刻度、修改边框颜色)至关重要。

以下是核心图表元素的对应关系与原理解析:

Figure (图形)
它是最顶层的容器,相当于一整张“画板”或一个应用窗口。一个 Figure 内部可以包含一个或多个具体的坐标系(Axes)。
Axes (坐标系)
这是图表真正的“绘图区域”。一幅完整的曲线图(包含数据、标签、边框等)就是一个 Axes。注意:Axes 绝不是坐标轴的意思,它是承载所有视觉元素的实体对象。
Axis (坐标轴)
特指构成坐标系的数轴,分为水平的 x 轴和垂直的 y 轴。它们负责控制数据的数值范围(Limits)并生成刻度。
Title (标题)
位于坐标系正上方的说明性文本,用于概括该图表的核心内容。
Legend (图例)
当图表中包含多组数据时,用于标识不同颜色线条或标记代表什么具体物理量的说明框。
Line (线条)
在折线图中,连接各个数据点的线段。可通过参数调整颜色、粗细和线型(如实线、虚线)。
Markers (标记)
在图表中用来标出具体数据点位置的符号,例如上文代码中的 marker='o'(圆点),常见的还有星号、方块、三角形等。
Tick (刻度)
坐标轴上的短小标记线,用于指示具体的数值位置。分为主刻度(Major Tick)和次刻度(Minor Tick)。
Tick label (刻度标签)
紧挨着刻度旁边的文字或数字,用于显示该刻度代表的具体数值。
x-label (x 轴标签)
说明水平坐标轴物理意义的文本(如“时间 (h)”)。同理,y 轴对应 y-label。
Grid (网格线)
从坐标轴刻度延伸出来的背景辅助线,便于读图时快速估算数据点的值。
Spine (边框)
包围整个绘图区域(Axes)的四条实体线(上、下、左、右)。默认情况下,坐标轴与底边和左边的边框重合。

编码风格:为什么必须选择“面向对象”#

如果你在网上搜索 Matplotlib 的教程,会发现同样是画折线图,存在两种截然不同的代码风格:

风格一:状态机接口(Pyplot 风格)

plt.plot(x, y)
plt.title("标题")

这种风格模仿了老式的 MATLAB 语法,代码简短,适合画极简的草图。但它的致命缺点是代码内部状态不可控,当你要画多张图或进行复杂排版时,经常会把标题和曲线画错位置。

风格二:面向对象接口(OOP 风格,本书唯一推荐)

fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title("标题")

正如我们在绘制第一幅图时展示的那样,本书将严格采用面向对象的编码风格

在此风格下,我们首先显式地创建画布 (fig) 和坐标系 (ax) 两个对象。随后所有的绘制动作(ax.plot)、标题设置(ax.set_title)都是在对特定的 ax 对象发出指令。

这种风格完美契合了我们的计算思维:在代码世界中,图表不再是屏幕上随意涂抹的像素,而是一个有着清晰层级结构的工程对象。一旦你习惯了 fig, ax 的起手式,哪怕面对几十个子图叠加的复杂工程数据面板,你的代码逻辑依然能保持高度的清晰与严谨。

Matplotlib 是一个用于在 Python 中创建静态、动画和交互式可视化的综合库。

  • 创建达到可出版质量的图形;
  • 制作可交互式缩放、平移和更新的图形;
  • 自定义视觉样式和布局;
  • 导出为多种文件格式;
  • 嵌入到 JupyterLab 和图形用户界面;
  • 使用基于Matplotlib 构建的丰富的第三方包。

安装 Matplotlib#

使用 pip 安装:

    pip install matplotlib

使用 conda 安装:

    conda install -c conda-forge matplotlib

绘制第一幅图#

Matplotlib 在 Figure 上绘制数据,每个 Figure 可能包含一个或多个 AxesAxes 是一个可以用 x-y 坐标(或极坐标图形中的 theta-r,3D 图形中的 x-y-z,等等)来指定点的区域。创建一个带 AxesFigure 的最简单方法是使用 pyplot.subplots。然后可以使用 Axes.plotAxes 上绘制数据:


    import matplotlib.pyplot as plt
    import numpy as np

    x = np.linspace(0, 2 * np.pi, 200)
    y = np.sin(x)

    fig, ax = plt.subplots()  # 创建一个包含一个 Axes 的 Figure 对象.
    ax.plot(x, y)  # 在 Axes 上绘制数据.
    plt.show()  # 显示此 Figure 对象.

Figure 的组成#

Figure#

代表整个图形。Figure 追踪所有子 Axes、一组“特殊的”Artist(标题、图例、色标等),甚至嵌套的子图形。

创建一个新 Figure 的最简单方法是使用 pyplot


    fig = plt.figure()  # 一个没有 Axes 的空图形
    fig, ax = plt.subplots()  # 一个带有单个 Axes 的图形
    fig, axs = plt.subplots(2, 2)  # 一个带有 2x2 的 Axes 网格的图形
    # 图形的左侧有一个轴,右侧有两个轴:
    fig, axs = plt.subplot_mosaic([['left', 'right_top'],
                ['left', 'right_bottom']])

为方便起见,经常在创建 Figure 的同时就创建 Axes,但也可以在此后再添加 Axes

Axes#

Axes 是一个绑定在 Figure 上的 Artist,相当于坐标系统。Figure 包含一个用户绘制数据的区域,通常包含 2 个(在 3D 时是 3 个)Axis 对象(注意 AxesAxis 是不同的)以提供刻度线(tick)和刻度标签(tick label),他们可以为 Axes 中的数据提供刻度。每个 Axes 也有一个标题(可通过 set_title() 设置),一个 x 轴标签(x-label,可通过 set_xlabel() 设置),一个 y 轴标签(y-label,可通过 set_ylabel() 设置)。

Axes 类及其成员函数是使用 OOP 接口的主要入口点,他们定义了大多数绘图方法(例如,前面的 ax.plot() 就是个绘图方法)。

Axis#

这些对象设置刻度及其值的范围,并生成刻度线和刻度标签。刻度线的位置由一个 Locator 对象决定,刻度标签字符串通过一个 Formatter 进行格式化。可以通过 LocatorFormatter 组合对刻度进行细微的调节。

Artist#

Figure 上所有可见的一切都可算作 Artist(甚至 FigureAxesAxis 对象也是)。这些包括 Text 对象、Line2D 对象、集合对象、Patch 对象等。当渲染 Figure 时,所有 Artist 都会被绘制到画布上。多数 Artist 都依附 Axes 存在,这些 Artist 不能被多个 Axes 共享,也无法从一个 Axes 移动到另一个。

绘图函数的输入类型#

绘图函数要求的输入类型为 numpy.arraynumpy.ma.masked_array,或是能被传递给 numpy.asarray 的对象。其他类似数组的类,如 pandas 数据对象和 numpy.matrix 可能不会正常工作。一般会在绘图前将这些数据转换为 numpy.array 对象。以下是一个转换 numpy.matrix 的示例:


    b = np.matrix([[1, 2], [3, 4]])
    b_asarray = np.asarray(b)

多数方法也可以解析可寻址对象,如字典(dict)、numpy.recarraypandas.DataFrame。Matplotlib 允许允许提供 data 关键字参数,并进一步通过传递对应于 xy 变量的字符串生成图形。


    np.random.seed(19680801)  # 为随机数生成器取种.
    data = {'a': np.arange(50),
            'c': np.random.randint(0, 50, 50),
            'd': np.random.randn(50)}
    data['b'] = data['a'] + 10 * np.random.randn(50)
    data['d'] = np.abs(data['d']) * 100
    
    fig, ax = plt.subplots(figsize=(5, 2.7), layout='constrained')
    ax.scatter('a', 'b', c='c', s='d', data=data)
    ax.set_xlabel('entry a')
    ax.set_ylabel('entry b')
    plt.show()

编码风格#

① 显式和隐式接口#

可以通过两种方式使用 Matplotlib:

  • 显式的 OO 风格:显式创建 FigureAxes,并调用他们的方法。
  • 隐式的 pyplot 风格:依赖 pyplot 隐式创建和管理 FigureAxes,并使用 pyplot 函数绘图。
  • 彻底不用 pyplot,仅在将 Matplotlib 嵌入到图形用户界面程序时使用。(不再讨论)

隐式的 pyplot 风格更方便使用,但在绘制复杂图形时,更推荐使用显式的 OO 风格。

显式的 OO 风格:


    x = np.linspace(0, 2, 100)  # 数据采样

    # 注意即便使用 OO 风格,也要使用 `.pyplot.figure` 创建 Figure。
    fig, ax = plt.subplots(figsize=(5, 2.7), layout='constrained')
    ax.plot(x, x, label='linear')  # 在 Axes 上绘制数据
    ax.plot(x, x**2, label='quadratic')  # 在 Axes 上绘制更多数据
    ax.plot(x, x**3, label='cubic')  # ... 更多
    ax.set_xlabel('x label')  # 为 Axes 添加 x 轴标签
    ax.set_ylabel('y label')  # 为 Axes 添加 y 轴标签
    ax.set_title("Simple Plot")  # 为 Axes 添加标题
    ax.legend()  # 添加图例
    plt.show()

隐式的 pyplot 风格:


    x = np.linspace(0, 2, 100)  # 数据采样

    plt.figure(figsize=(5, 2.7), layout='constrained')
    plt.plot(x, x, label='linear')  # 在(隐式的)Axes 上绘制数据
    plt.plot(x, x**2, label='quadratic')  # 其他
    plt.plot(x, x**3, label='cubic')
    plt.xlabel('x label')
    plt.ylabel('y label')
    plt.title("Simple Plot")
    plt.legend()
    plt.show()

② 编制辅助函数#

如果要使用不同数据集多次绘制同一类图形,或更方便地封装 Matplotlib 方法,推荐使用如下形式的签名:


    def my_plotter(ax, data1, data2, param_dict):
        """
        一个生成图形的辅助函数。
        """
        out = ax.plot(data1, data2, **param_dict)
        return out
    
    data1, data2, data3, data4 = np.random.randn(4, 100)  # 生成 4 组随机得数据集
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(5, 2.7))
    my_plotter(ax1, data1, data2, {'marker': 'x'})
    my_plotter(ax2, data3, data4, {'marker': 'o'})
    plt.show()

定义样式#

多数绘图方法具有针对 Artist 的设置样式选项,可以在调用绘图方法时设置这些选项,或通过调用 Artist 的设置器(setter)方法。在如下的图形中,我们在创建图形时手动地设置 Artist 颜色、线宽、线型,并在之后通过 set_linestyle 方法设置第二条线的线型。


    data1, data2 = np.random.randn(2, 100)
    fig, ax = plt.subplots(figsize=(5, 2.7))
    x = np.arange(len(data1))
    ax.plot(x, np.cumsum(data1), color='blue', linewidth=3, linestyle='--')
    l, = ax.plot(x, np.cumsum(data2), color='orange', linewidth=2)
    l.set_linestyle(':')
    plt.show()
颜色

颜色

① 颜色#

Matplotlib 定义了一组非常灵活的颜色,可用于大多数 Artist,详情请见颜色教程。一些 Artist 会接受多个颜色。例如,对于散点图,标记的内部和边界的颜色应该是不同的:


    data1, data2 = np.random.randn(2, 100)
    fig, ax = plt.subplots(figsize=(5, 2.7))
    ax.scatter(data1, data2, s=50, facecolor='C0', edgecolor='k')
    plt.show()

② 线宽、线型和标记尺寸#

线宽的单位是印刷术上的点(1 点 = 1//72 英寸),线宽可用于所有具有笔触线的 Artist。笔触线同样还具有线型。

标记尺寸由所用的方法决定。plot 以点为单位指定标记尺寸,并且通常是标记的直径或宽度。scatter 所指定的标记尺寸大致与标记的可视面积成正比。标记样式

Marker size depends on the method being used. plot specifies markersize in points, and is generally the “diameter” or width of the marker. scatter specifies markersize as approximately proportional to the visual area of the marker. There is an array of markerstyles available as string codes (see markers), or users can define their own MarkerStyle (see Marker reference):


    data1, data2, data3, data4 = np.random.randn(4, 100)  # 生成 4 组随机得数据集
    fig, ax = plt.subplots(figsize=(5, 2.7))
    ax.plot(data1, 'o', label='data1')
    ax.plot(data2, 'd', label='data2')
    ax.plot(data3, 'v', label='data3')
    ax.plot(data4, 's', label='data4')
    ax.legend()
    plt.show()
Matplotlib:数据可视化 画布与坐标轴对象