当你掌握了 Matplotlib 的基础和常用图表后,可能希望创建更具个性和表现力的可视化作品。 本章将带你进入 Matplotlib 的高级自定义世界,包括直接操作艺术家对象、绘制任意形状、应用变换和路径效果, 以及创建复杂的复合图表。这些技巧将让你真正释放 Matplotlib 的全部潜力。
Matplotlib 中的所有绘图元素都是 Artist 的子类,包括 Line2D, Rectangle, Text 等。
通过直接操作这些艺术家对象,可以实现精细控制:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0, 10, 100)
line, = ax.plot(x, np.sin(x))
# 直接修改艺术家属性
line.set_color('red')
line.set_linewidth(3)
line.set_alpha(0.7)
# 添加自定义矩形
rect = plt.Rectangle((2, -0.5), 1, 1, facecolor='lightblue', edgecolor='blue')
ax.add_patch(rect)
# 添加文本
text = ax.text(5, 0.5, '自定义文本', fontsize=12, ha='center')
text.set_bbox(dict(facecolor='yellow', alpha=0.5))
plt.show()
matplotlib.path.Path 可以描述任意矢量路径,用于创建自定义形状:
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
# 定义路径顶点和代码类型
verts = [
(0., 0.), # 起点
(0.5, 1.), # 曲线控制点1
(1., 0.), # 曲线控制点2
(0., 0.5), # 下一个起点
(1., 0.5), # 直线
(0., 1.), # 直线
(0., 0.), # 返回起点
]
codes = [Path.MOVETO,
Path.CURVE3,
Path.CURVE3,
Path.MOVETO,
Path.LINETO,
Path.LINETO,
Path.CLOSEPOLY]
path = Path(verts, codes)
patch = patches.PathPatch(path, facecolor='orange', lw=2)
fig, ax = plt.subplots()
ax.add_patch(patch)
ax.set_xlim(-0.5, 1.5)
ax.set_ylim(-0.2, 1.2)
ax.set_aspect('equal')
plt.title('使用Path自定义形状')
plt.show()
Path 支持 MOVETO、LINETO、CURVE3(二次贝塞尔)、CURVE4(三次贝塞尔)等操作。变换用于在不同坐标系之间转换,如数据坐标、轴坐标、图形坐标。利用变换可以绘制与数据无关的元素:
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
fig, ax = plt.subplots()
ax.plot([0,10], [0,10])
# 创建椭圆,使用轴坐标(相对位置)
ellipse = Ellipse(xy=(0.5, 0.5), width=0.2, height=0.1,
transform=ax.transAxes, facecolor='red', alpha=0.5)
ax.add_patch(ellipse)
# 混合变换:x使用数据坐标,y使用轴坐标
trans = ax.get_xaxis_transform() # x数据,y轴坐标
rect = plt.Rectangle((5, 0.2), 2, 0.3, transform=trans, facecolor='blue', alpha=0.3)
ax.add_patch(rect)
plt.show()
ax.transAxes 使用0-1轴坐标,ax.transData 是默认数据坐标。路径效果可以为线条或文本添加描边、阴影、渐变等特效:
import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects
fig, ax = plt.subplots()
text = ax.text(0.5, 0.5, '带阴影的文本', ha='center', va='center',
fontsize=20, color='white')
text.set_path_effects([
path_effects.Stroke(linewidth=3, foreground='black'),
path_effects.Normal()
])
# 为线条添加阴影效果
line, = ax.plot([0,1], [0,1], linewidth=3, color='red')
line.set_path_effects([
path_effects.SimpleLineShadow(shadow_color='gray', alpha=0.5),
path_effects.Normal()
])
ax.set_xlim(0,1); ax.set_ylim(0,1)
plt.show()
Stroke 创建描边,SimpleLineShadow 添加阴影。通过 ticker 模块可以创建完全自定义的刻度:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
x = np.linspace(0, 100, 100)
y = np.sin(x/10) * x
fig, ax = plt.subplots()
ax.plot(x, y)
# 自定义主刻度定位器:每25一个刻度
ax.xaxis.set_major_locator(ticker.MultipleLocator(25))
# 自定义主刻度格式化器:带单位的标签
ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: f'{int(x)} km'))
# 自定义次刻度定位器:每5一个次刻度
ax.xaxis.set_minor_locator(ticker.MultipleLocator(5))
plt.show()
ConnectionPatch 可以在不同子图之间绘制连接线,常用于关联分析:
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
import numpy as np
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10,4))
x = np.linspace(0, 2*np.pi, 100)
ax1.plot(x, np.sin(x))
ax2.plot(x, np.cos(x))
# 连接ax1中点(π/2,1)到ax2中点(π/2,0)
con = ConnectionPatch(xyA=(np.pi/2, 1), xyB=(np.pi/2, 0),
coordsA="data", coordsB="data",
axesA=ax1, axesB=ax2,
color="red", linewidth=2, arrowstyle="->")
ax2.add_artist(con)
plt.show()
coordsA 和 coordsB 指定坐标类型。在主图中嵌入一个小图用于放大显示局部细节:
from mpl_toolkits.axes_grid1.inset_locator import inset_axes, mark_inset
fig, ax = plt.subplots()
x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x))
# 创建嵌入子图
inset = inset_axes(ax, width="30%", height="30%", loc='upper right')
inset.plot(x, np.sin(x))
inset.set_xlim(2, 4) # 放大局部
inset.set_ylim(0, 1)
inset.set_xticks([]) # 可选隐藏刻度
inset.set_yticks([])
# 标记放大区域
mark_inset(ax, inset, loc1=1, loc2=3, fc="none", ec="gray")
plt.show()
inset_axes 可灵活控制位置和大小。通过创建自定义的图例句柄,可以表示非标准元素:
import matplotlib.pyplot as plt
from matplotlib.patches import Patch, Circle
from matplotlib.lines import Line2D
# 创建自定义图例句柄
legend_elements = [
Line2D([0], [0], color='b', lw=4, label='粗蓝线'),
Line2D([0], [0], marker='o', color='w', markerfacecolor='r', markersize=10, label='红点'),
Patch(facecolor='orange', alpha=0.7, label='橙色区域'),
Circle((0,0), radius=0.1, facecolor='green', label='自定义圆形')
]
plt.figure()
plt.legend(handles=legend_elements, loc='center')
plt.axis('off')
plt.show()
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
import matplotlib.patheffects as path_effects
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import numpy as np
# 创建主图
fig, ax = plt.subplots(figsize=(10,6))
x = np.linspace(0, 10, 200)
y = np.sin(x) * np.exp(-0.1*x)
ax.plot(x, y, 'b-', linewidth=2, label='衰减振荡')
# 添加自定义标注:使用路径画一个星形
star_verts = []
for i in range(5):
angle = i * 2*np.pi/5 - np.pi/2
star_verts.append((0.5*np.cos(angle), 0.5*np.sin(angle)))
angle2 = angle + np.pi/5
star_verts.append((0.25*np.cos(angle2), 0.25*np.sin(angle2)))
star_verts.append(star_verts[0])
star_codes = [Path.MOVETO] + [Path.LINETO]*9 + [Path.CLOSEPOLY]
star_path = Path(star_verts, star_codes)
star_patch = patches.PathPatch(star_path, facecolor='gold', edgecolor='orange', linewidth=2,
transform=ax.transData, zorder=10)
# 放置在峰值点附近
star_patch.set_transform(ax.transData)
# 需要设置位置(通过偏移变换太麻烦,这里简化)
ax.add_patch(star_patch)
# 添加路径效果文本
text = ax.text(5, 0.6, '✨ 关键区域 ✨', ha='center', fontsize=14,
bbox=dict(facecolor='yellow', alpha=0.5))
text.set_path_effects([path_effects.Stroke(linewidth=2, foreground='black'),
path_effects.Normal()])
# 嵌入放大图
inset = inset_axes(ax, width="30%", height="30%", loc='lower left')
inset.plot(x, y)
inset.set_xlim(2, 3)
inset.set_ylim(0, 0.5)
inset.set_title('局部放大', fontsize=9)
# 自定义图例(添加一个代表星形的图例)
from matplotlib.patches import Polygon
star_legend = Polygon(star_verts, closed=True, facecolor='gold', edgecolor='orange')
ax.legend(handles=[ax.get_lines()[0], star_legend], labels=['信号', '峰值点'])
plt.show()
通过本章的高级自定义技巧,你可以突破标准图表的限制,创造出真正独特且富有表现力的可视化作品。不断实践,你将能随心所欲地控制每一个细节。