4.2Transformations (坐标变换) 第四章:Matplotlib 高级主题领域 4.2 Transformations (坐标变换) 在 Matplotlib 中,坐标变换(Transformations)是控制和定制图形元素位置和形状的关键机制。理解坐标变换对于创建复杂、精细和具有视觉吸引力的图表至关重要。它允许我们将数据坐标映射到屏幕坐标,并在不同的坐标系统中进行操作,从而实现高级的图形定制和布局。 4.2.1 坐标系统 (Coordinate Systems) 在深入坐标变换之前,我们必须先理解 Matplotlib 中存在的不同坐标系统。这些坐标系统构成了变换的基础,理解它们之间的关系是掌握坐标变换的关键。
第四章:Matplotlib 高级主题领域
4.2 Transformations (坐标变换)
在 Matplotlib 中,坐标变换(Transformations)是控制和定制图形元素位置和形状的关键机制。理解坐标变换对于创建复杂、精细和具有视觉吸引力的图表至关重要。它允许我们将数据坐标映射到屏幕坐标,并在不同的坐标系统中进行操作,从而实现高级的图形定制和布局。
4.2.1 坐标系统 (Coordinate Systems)
在深入坐标变换之前,我们必须先理解 Matplotlib 中存在的不同坐标系统。这些坐标系统构成了变换的基础,理解它们之间的关系是掌握坐标变换的关键。Matplotlib 主要使用以下四种坐标系统:
数据坐标 (Data Coordinates): 这是与您的数据直接相关的坐标系统。当您绘制数据点 (x, y) 时,这些 x 和 y 值就位于数据坐标系中。数据坐标系的范围和刻度由您绘制的数据范围和轴的设置决定。
轴坐标 (Axes Coordinates): 轴坐标是相对于单个 Axes 对象(子图)的坐标系统。Axes 对象的左下角为 (0, 0),右上角为 (1, 1)。无论数据范围如何,轴坐标始终在这个范围内。轴坐标常用于在 Axes 对象内部定位元素,例如文本注释、图例和艺术家对象。
图形坐标 (Figure Coordinates): 图形坐标是相对于整个 Figure 对象(画布)的坐标系统。Figure 对象的左下角为 (0, 0),右上角为 (1, 1)。与轴坐标类似,图形坐标的范围也是固定的。图形坐标用于在整个 Figure 对象中定位元素,例如标题、总图例和多个子图之间的相对位置。
显示坐标 (Display Coordinates): 显示坐标是最终将图形渲染到屏幕或输出设备上的像素坐标系统。显示坐标是设备相关的,其单位是像素。Matplotlib 会自动将其他坐标系统转换为显示坐标以便最终绘制。
为了更清晰地展示这些坐标系统之间的关系,我们可以使用 Mermaid 的 graph TD 图来可视化它们之间的层级结构和转换流程:
坐标系统转换流程:
数据坐标到轴坐标 (Data to Axes Transformation): 当您使用 plot()、scatter() 等函数绘制数据时,Matplotlib 首先将数据坐标转换为轴坐标。这个转换过程涉及到轴的缩放、平移和刻度设置。例如,如果您的 X 轴范围设置为 0 到 10,数据点 x=5 将被映射到轴坐标的 x=0.5 位置(假设线性缩放)。
轴坐标到图形坐标 (Axes to Figure Transformation): 轴坐标进一步被转换为图形坐标。这个转换过程取决于 Axes 对象在 Figure 对象中的位置和大小。例如,如果一个 Axes 对象被放置在 Figure 对象的左上角,轴坐标 (0.5, 0.5) 将被映射到图形坐标的 (x, y),其中 x 和 y 的值取决于 Axes 对象相对于 Figure 对象左上角的位置和尺寸。
图形坐标到显示坐标 (Figure to Display Transformation): 最后,图形坐标被转换为显示坐标。这个转换过程涉及到将图形坐标(范围 0 到 1)映射到实际的像素位置。分辨率、画布大小和 DPI 设置都会影响这个转换过程。
理解这些坐标系统及其转换关系至关重要,因为 Matplotlib 的坐标变换机制就是围绕这些坐标系统展开的。在实际应用中,我们经常需要在不同的坐标系统之间进行转换,以便在图表中精确定位和控制各种元素。
4.2.2 变换对象 (Transform Objects)
Matplotlib 使用 变换对象 (Transform Objects) 来执行坐标系统之间的转换。变换对象是一个封装了坐标变换逻辑的类,它接受一个坐标点作为输入,并返回转换后的坐标点。
Matplotlib 中最常用的变换对象是 matplotlib.transforms.Transform 类的子类。其中,最核心和常用的变换对象是 Affine2D 和 CompositeGenericTransform。
Affine2D: Affine2D 类用于表示 仿射变换 (Affine Transformations)。仿射变换是一种线性变换,它可以保持直线和平行线的性质。常见的仿射变换包括:
平移 (Translation): 将对象沿 x 轴和 y 轴移动一定的距离。
缩放 (Scaling): 沿 x 轴和 y 轴按比例放大或缩小对象。
旋转 (Rotation): 围绕一个中心点旋转对象一定的角度。
剪切 (Shear): 沿 x 轴或 y 轴倾斜对象。
Affine2D 对象可以通过矩阵运算来高效地执行这些变换。在 Matplotlib 中,Affine2D 是最常用和最重要的变换对象,因为它足以处理绝大多数的坐标变换需求。
CompositeGenericTransform: CompositeGenericTransform 类用于组合多个变换对象,形成 复合变换 (Composite Transformations)。当需要应用一系列变换时,可以使用 CompositeGenericTransform 将它们组合成一个单一的变换对象,从而简化代码并提高效率。
CompositeGenericTransform 可以接受多个变换对象作为输入,并按照指定的顺序依次应用这些变换。这使得我们可以构建复杂的变换,例如先旋转再平移,或者先缩放再剪切。
4.2.3 仿射变换 Affine2D 详解与实践
Affine2D 类是 Matplotlib 中最核心的变换对象,它基于 3x3 的仿射变换矩阵来实现二维坐标变换。理解 Affine2D 的原理和使用方法是掌握 Matplotlib 坐标变换的关键。
仿射变换矩阵:
一个二维仿射变换可以用一个 3x3 的矩阵表示:
[ a b 0 ] [ c d 0 ] [ tx ty 1 ]
其中:
a, b, c, d 控制线性变换(缩放、旋转、剪切)。
tx, ty 控制平移变换。
当一个二维向量 [x, y, 1] (使用齐次坐标表示)与仿射变换矩阵相乘时,即可得到变换后的向量 [x', y', 1]:
[x'] [ a b 0 ] [x] [y'] = [ c d 0 ] [y] [1 ] [ tx ty 1 ] [1]
展开后得到变换公式:
x' = a*x + b*y + tx y' = c*x + d*y + ty
Affine2D 对象的创建和应用:
Matplotlib 提供了多种便捷的方法来创建 Affine2D 对象,对应于不同的基本仿射变换。
1. 平移 (Translation):
使用 Affine2D.translation(tx, ty) 方法创建平移变换对象。
import matplotlib.pyplot as plt import matplotlib.transforms as transforms # 创建平移变换对象,沿 x 轴平移 20 个单位,沿 y 轴平移 10 个单位 translation = transforms.Affine2D.translation(20, 10) # 示例数据 x = [1, 2, 3, 4, 5] y = [2, 3, 5, 4, 6] fig, ax = plt.subplots(figsize=(8, 6)) # 绘制原始数据点 (蓝色) ax.plot(x, y, 'bo-', label='Original Data') # 应用平移变换后的数据点 (红色) ax.plot(x, y, 'ro-', label='Translated Data', transform=translation + ax.transData) # 注意:transform 参数接受的是变换对象,这里将平移变换与数据坐标变换 ax.transData 组合 ax.legend() ax.set_title('Translation Transformation') plt.grid(True) plt.show()
代码详解:
transforms.Affine2D.translation(20, 10) 创建了一个平移变换对象,表示沿 x 轴平移 20 个数据单位,沿 y 轴平移 10 个数据单位。
transform=translation + ax.transData 是关键部分。ax.transData 是 Axes 对象的数据坐标变换,它负责将数据坐标转换为轴坐标。我们将平移变换 translation 与 ax.transData 相加,得到一个新的复合变换。这个复合变换先将数据坐标转换为轴坐标,然后再在轴坐标系中应用平移变换。
绘制红色线条时,transform 参数指定了应用这个复合变换。因此,红色线条的数据点会先经过数据坐标到轴坐标的转换,然后再在轴坐标系中平移 (20, 10) 个数据单位(注意,这里平移的单位是数据坐标单位,因为平移变换是在数据坐标变换之后应用的)。
2. 缩放 (Scaling):
使用 Affine2D.scale(sx, sy) 方法创建缩放变换对象。
import matplotlib.pyplot as plt import matplotlib.transforms as transforms # 创建缩放变换对象,x 轴缩放 1.5 倍,y 轴缩放 0.8 倍 scaling = transforms.Affine2D.scale(1.5, 0.8) # 示例数据 x = [1, 2, 3, 4, 5] y = [2, 3, 5, 4, 6] fig, ax = plt.subplots(figsize=(8, 6)) # 绘制原始数据点 (蓝色) ax.plot(x, y, 'bo-', label='Original Data') # 应用缩放变换后的数据点 (红色) ax.plot(x, y, 'ro-', label='Scaled Data', transform=scaling + ax.transData) ax.legend() ax.set_title('Scaling Transformation') plt.grid(True) plt.show()
代码详解:
transforms.Affine2D.scale(1.5, 0.8) 创建了一个缩放变换对象,表示 x 轴方向缩放 1.5 倍,y 轴方向缩放 0.8 倍。
transform=scaling + ax.transData 将缩放变换与数据坐标变换组合,缩放操作在数据坐标系中进行。
3. 旋转 (Rotation):
使用 Affine2D.rotate(angle) 或 Affine2D.rotate_deg(degrees) 方法创建旋转变换对象,角度单位分别为弧度和度。
import matplotlib.pyplot as plt import matplotlib.transforms as transforms import numpy as np # 创建旋转变换对象,逆时针旋转 30 度 rotation = transforms.Affine2D.rotate_deg(30) # 示例数据 (绘制一个矩形) rect_x = [1, 2, 2, 1, 1] rect_y = [1, 1, 3, 3, 1] fig, ax = plt.subplots(figsize=(8, 6)) # 绘制原始矩形 (蓝色) ax.plot(rect_x, rect_y, 'bo-', label='Original Rectangle') # 应用旋转变换后的矩形 (红色) ax.plot(rect_x, rect_y, 'ro-', label='Rotated Rectangle', transform=rotation + ax.transData) # 设置坐标轴范围,以便完整显示旋转后的矩形 ax.set_xlim(-1, 4) ax.set_ylim(-1, 4) ax.set_aspect('equal') # 保持 x 和 y 轴刻度一致,避免图形变形 ax.legend() ax.set_title('Rotation Transformation') plt.grid(True) plt.show()
代码详解:
transforms.Affine2D.rotate_deg(30) 创建了一个旋转变换对象,表示逆时针旋转 30 度。
ax.set_aspect('equal') 非常重要,它确保 x 轴和 y 轴的刻度比例一致,这样旋转才不会导致图形变形。如果不设置 aspect='equal',旋转后的矩形可能会变成一个倾斜的平行四边形。
4. 剪切 (Shear):
使用 Affine2D.shear_x(angle) 或 Affine2D.shear_y(angle) 方法创建剪切变换对象,分别表示沿 x 轴或 y 轴方向进行剪切,角度单位为弧度。
import matplotlib.pyplot as plt import matplotlib.transforms as transforms import numpy as np # 创建 x 轴剪切变换对象,角度为 0.3 弧度 shear_x = transforms.Affine2D.shear_x(0.3) # 示例数据 (绘制一个矩形) rect_x = [1, 2, 2, 1, 1] rect_y = [1, 1, 3, 3, 1] fig, ax = plt.subplots(figsize=(8, 6)) # 绘制原始矩形 (蓝色) ax.plot(rect_x, rect_y, 'bo-', label='Original Rectangle') # 应用 x 轴剪切变换后的矩形 (红色) ax.plot(rect_x, rect_y, 'ro-', label='Sheared Rectangle', transform=shear_x + ax.transData) # 设置坐标轴范围,以便完整显示剪切后的矩形 ax.set_xlim(-1, 4) ax.set_ylim(0, 4) ax.set_aspect('equal') ax.legend() ax.set_title('Shear Transformation (X-axis)') plt.grid(True) plt.show()
代码详解:
transforms.Affine2D.shear_x(0.3) 创建了一个 x 轴剪切变换对象,剪切角度为 0.3 弧度。
x 轴剪切会使 y 坐标保持不变,而 x 坐标会根据 y 坐标的值进行偏移,从而产生倾斜的效果。
5. 组合变换 (Composite Transformations):
可以使用 + 运算符将多个 Affine2D 对象组合成一个复合变换。变换的应用顺序是从左到右。
import matplotlib.pyplot as plt import matplotlib.transforms as transforms import numpy as np # 创建平移变换和旋转变换 translation = transforms.Affine2D.translation(2, 1) rotation = transforms.Affine2D.rotate_deg(45) # 组合变换:先平移,后旋转 composite_transform = translation + rotation # 示例数据 (绘制一个矩形) rect_x = [1, 2, 2, 1, 1] rect_y = [1, 1, 3, 3, 1] fig, ax = plt.subplots(figsize=(8, 6)) # 绘制原始矩形 (蓝色) ax.plot(rect_x, rect_y, 'bo-', label='Original Rectangle') # 应用复合变换后的矩形 (红色) ax.plot(rect_x, rect_y, 'ro-', label='Transformed Rectangle', transform=composite_transform + ax.transData) # 设置坐标轴范围 ax.set_xlim(-2, 5) ax.set_ylim(-2, 5) ax.set_aspect('equal') ax.legend() ax.set_title('Composite Transformation (Translation + Rotation)') plt.grid(True) plt.show()
代码详解:
composite_transform = translation + rotation 将平移变换 translation 和旋转变换 rotation 组合成一个复合变换。注意,变换的应用顺序是先平移,后旋转。
复合变换的应用顺序非常重要,不同的顺序会产生不同的结果。例如,先旋转再平移与先平移再旋转通常是不同的。
4.2.4 复合变换 CompositeGenericTransform 详解与实践
CompositeGenericTransform 类提供了更灵活的方式来创建复合变换,它可以组合任意类型的变换对象,包括自定义的变换函数。
创建 CompositeGenericTransform 对象:
CompositeGenericTransform 的构造函数接受两个参数:
forward_transform: 前向变换函数或变换对象。
inverse_transform: 逆向变换函数或变换对象(可选)。
当 CompositeGenericTransform 应用于坐标点时,它会先应用 forward_transform,然后再应用 inverse_transform 的逆变换。
示例:使用 CompositeGenericTransform 实现非线性变换
import matplotlib.pyplot as plt import matplotlib.transforms as transforms import numpy as np # 自定义非线性变换函数 (例如,平方变换) def square_transform(xy): return xy**2 # 使用 CompositeGenericTransform 创建非线性变换对象 nonlinear_transform = transforms.CompositeGenericTransform(square_transform, square_transform) # 示例数据 x = np.linspace(0, 5, 100) y = x # y = x fig, ax = plt.subplots(figsize=(8, 6)) # 绘制原始数据 (蓝色) ax.plot(x, y, 'b-', label='Original Data (y=x)') # 应用非线性变换后的数据 (红色) ax.plot(x, y, 'r-', label='Nonlinear Transformed Data (y=x^2)', transform=nonlinear_transform + ax.transData) ax.legend() ax.set_title('Nonlinear Transformation using CompositeGenericTransform') plt.grid(True) plt.show()
代码详解:
square_transform(xy) 是一个自定义的非线性变换函数,它将输入的坐标点 xy 的每个分量平方。
nonlinear_transform = transforms.CompositeGenericTransform(square_transform, square_transform) 创建了一个 CompositeGenericTransform 对象,使用 square_transform 作为前向和逆向变换函数(这里逆向变换函数也简单地设置为平方,实际上严格意义上的逆变换是平方根,但这里为了演示目的简化处理)。
transform=nonlinear_transform + ax.transData 将非线性变换与数据坐标变换组合。
4.2.5 应用变换 (Applying Transformations)
在 Matplotlib 中,有多种方式可以应用变换:
transform 参数: 许多绘图函数(如 plot()、scatter()、text() 等)都接受 transform 参数。通过设置 transform 参数,可以将指定的变换应用于该函数绘制的艺术家对象。
ax.plot(x, y, transform=my_transform + ax.transData)
set_transform() 方法: 艺术家对象(Artist Objects)都具有 set_transform() 方法,可以用于设置该艺术家的变换。
line, = ax.plot(x, y) line.set_transform(my_transform + ax.transData)
艺术家对象的内置变换属性: 艺术家对象还具有一些内置的变换属性,例如:
artist.transData: 艺术家对象的数据坐标变换。
artist.transAxes: 艺术家对象的轴坐标变换。
artist.transFigure: 艺术家对象的图形坐标变换。
artist.transOffset: 用于偏移文本或标记的变换。
这些属性可以直接访问和修改艺术家的变换,但通常更常用的是 transform 参数或 set_transform() 方法。
4.2.6 实际应用案例
坐标变换在 Matplotlib 中有广泛的应用,以下是一些常见的实际案例:
创建自定义坐标轴: 可以使用坐标变换来创建非标准的坐标轴,例如对数坐标轴、极坐标轴等。Matplotlib 的 matplotlib.scale 模块就是基于坐标变换来实现各种坐标轴缩放的。
绘制复杂图形: 通过组合各种仿射变换,可以绘制出复杂的几何图形和图案,例如分形图形、万花筒图案等。
数据可视化增强: 可以使用坐标变换来调整数据点的显示位置,例如添加抖动 (jitter) 以避免数据点重叠,或者创建透视效果。
动画效果: 可以通过动态地修改变换对象来实现动画效果,例如平移、旋转或缩放图形元素。
总结
Matplotlib 的坐标变换系统是其高级定制功能的核心。理解坐标系统、变换对象以及如何应用变换,能够帮助您更精细地控制图表的布局和外观,创建更具表现力和信息量的可视化作品。Affine2D 和 CompositeGenericTransform 是最常用的变换工具,掌握它们的使用方法对于深入 Matplotlib 图形定制至关重要。通过灵活运用坐标变换,您可以突破 Matplotlib 的默认设置,实现各种高级和自定义的绘图效果。