1.1 Matplotlib简介与安装
1.1.1 什么是Matplotlib
Matplotlib是Python中最流行的数据可视化库,由John D. Hunter于2003年创建。它提供了一个类似MATLAB的绘图接口,可以生成高质量的静态、动画和交互式可视化图表。
主要特点: - 功能强大且灵活 - 支持多种输出格式 - 高度可定制 - 与NumPy、Pandas等库无缝集成 - 活跃的社区支持
1.1.2 安装Matplotlib
# 使用pip安装
pip install matplotlib
# 使用conda安装
conda install matplotlib
# 安装完整的科学计算环境
pip install matplotlib numpy pandas jupyter
1.1.3 验证安装
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
print(f"Matplotlib版本: {matplotlib.__version__}")
# 简单测试
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.figure(figsize=(8, 6))
plt.plot(x, y)
plt.title('Matplotlib安装测试')
plt.xlabel('X轴')
plt.ylabel('Y轴')
plt.grid(True)
plt.show()
1.2 基本绘图概念
1.2.1 Matplotlib架构
Matplotlib采用分层架构:
import matplotlib.pyplot as plt
import numpy as np
# 1. Backend Layer (后端层)
# 负责实际的图形渲染
# 2. Artist Layer (艺术家层)
# 所有图形元素都是Artist对象
# 3. Scripting Layer (脚本层)
# pyplot接口,最常用的层
class MatplotlibArchitecture:
"""Matplotlib架构演示"""
def __init__(self):
self.backend_info = {
'Agg': '光栅图形',
'SVG': '矢量图形',
'PDF': 'PDF文档',
'PS': 'PostScript',
'Qt5Agg': 'Qt5交互式',
'TkAgg': 'Tkinter交互式'
}
def show_backend_info(self):
"""显示后端信息"""
import matplotlib
print(f"当前后端: {matplotlib.get_backend()}")
print("\n可用后端:")
for backend, desc in self.backend_info.items():
print(f" {backend}: {desc}")
def demonstrate_layers(self):
"""演示不同层的使用"""
# Scripting Layer (pyplot)
plt.figure(figsize=(12, 4))
# 子图1: pyplot接口
plt.subplot(1, 3, 1)
x = np.linspace(0, 2*np.pi, 100)
plt.plot(x, np.sin(x))
plt.title('pyplot接口')
# 子图2: 面向对象接口
plt.subplot(1, 3, 2)
fig, ax = plt.subplots()
ax.plot(x, np.cos(x))
ax.set_title('面向对象接口')
# 子图3: Artist层
plt.subplot(1, 3, 3)
from matplotlib.patches import Circle
fig, ax = plt.subplots()
circle = Circle((0.5, 0.5), 0.3, color='red', alpha=0.5)
ax.add_patch(circle)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.set_title('Artist层')
plt.tight_layout()
plt.show()
# 使用示例
arch = MatplotlibArchitecture()
arch.show_backend_info()
arch.demonstrate_layers()
1.2.2 Figure和Axes
import matplotlib.pyplot as plt
import numpy as np
class FigureAxesDemo:
"""Figure和Axes概念演示"""
def __init__(self):
self.x = np.linspace(0, 10, 100)
self.y1 = np.sin(self.x)
self.y2 = np.cos(self.x)
def basic_concepts(self):
"""基本概念演示"""
# Figure: 整个图形窗口
# Axes: 图形中的一个绘图区域
# 方法1: pyplot接口
plt.figure(figsize=(10, 6))
plt.plot(self.x, self.y1, label='sin(x)')
plt.plot(self.x, self.y2, label='cos(x)')
plt.title('pyplot接口示例')
plt.legend()
plt.show()
# 方法2: 面向对象接口
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(self.x, self.y1, label='sin(x)')
ax.plot(self.x, self.y2, label='cos(x)')
ax.set_title('面向对象接口示例')
ax.legend()
plt.show()
def multiple_subplots(self):
"""多子图演示"""
# 创建2x2的子图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 子图1: 线图
axes[0, 0].plot(self.x, self.y1)
axes[0, 0].set_title('线图')
axes[0, 0].grid(True)
# 子图2: 散点图
axes[0, 1].scatter(self.x[::10], self.y1[::10])
axes[0, 1].set_title('散点图')
axes[0, 1].grid(True)
# 子图3: 柱状图
x_bar = np.arange(5)
y_bar = np.random.rand(5)
axes[1, 0].bar(x_bar, y_bar)
axes[1, 0].set_title('柱状图')
# 子图4: 饼图
sizes = [25, 30, 20, 25]
labels = ['A', 'B', 'C', 'D']
axes[1, 1].pie(sizes, labels=labels, autopct='%1.1f%%')
axes[1, 1].set_title('饼图')
plt.tight_layout()
plt.show()
def subplot_layouts(self):
"""不同的子图布局"""
# 使用GridSpec进行复杂布局
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize=(12, 8))
gs = GridSpec(3, 3, figure=fig)
# 大图占据左上角2x2
ax1 = fig.add_subplot(gs[0:2, 0:2])
ax1.plot(self.x, self.y1)
ax1.set_title('主图')
# 右上角小图
ax2 = fig.add_subplot(gs[0, 2])
ax2.plot(self.x, self.y2)
ax2.set_title('小图1')
# 右中间小图
ax3 = fig.add_subplot(gs[1, 2])
ax3.scatter(self.x[::20], self.y1[::20])
ax3.set_title('小图2')
# 底部横跨整个宽度
ax4 = fig.add_subplot(gs[2, :])
ax4.plot(self.x, self.y1 + self.y2)
ax4.set_title('底部图')
plt.tight_layout()
plt.show()
# 使用示例
demo = FigureAxesDemo()
demo.basic_concepts()
demo.multiple_subplots()
demo.subplot_layouts()
1.3 图形对象模型
1.3.1 Artist对象层次结构
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
class ArtistDemo:
"""Artist对象演示"""
def __init__(self):
self.fig = None
self.ax = None
def artist_hierarchy(self):
"""Artist层次结构演示"""
self.fig, self.ax = plt.subplots(figsize=(10, 8))
# Primitive Artists (基本艺术家)
# Line2D
x = np.linspace(0, 10, 100)
line, = self.ax.plot(x, np.sin(x), 'b-', linewidth=2, label='sin(x)')
# Text
text = self.ax.text(5, 0.5, 'Hello Matplotlib!',
fontsize=14, color='red',
bbox=dict(boxstyle='round', facecolor='yellow'))
# Patch (Rectangle)
rect = patches.Rectangle((2, -0.5), 2, 1,
linewidth=2, edgecolor='green',
facecolor='lightgreen', alpha=0.5)
self.ax.add_patch(rect)
# Patch (Circle)
circle = patches.Circle((7, 0.5), 0.5,
linewidth=2, edgecolor='purple',
facecolor='lavender', alpha=0.7)
self.ax.add_patch(circle)
# Collection (scatter)
x_scatter = np.random.rand(50) * 10
y_scatter = np.random.rand(50) * 2 - 1
scatter = self.ax.scatter(x_scatter, y_scatter,
c=np.random.rand(50),
s=100, alpha=0.6)
# Composite Artists (复合艺术家)
self.ax.set_title('Artist对象层次结构演示', fontsize=16)
self.ax.set_xlabel('X轴', fontsize=12)
self.ax.set_ylabel('Y轴', fontsize=12)
self.ax.legend()
self.ax.grid(True, alpha=0.3)
plt.show()
# 打印Artist信息
self.print_artist_info()
def print_artist_info(self):
"""打印Artist对象信息"""
print("Figure的子Artist:")
for i, child in enumerate(self.fig.get_children()):
print(f" {i}: {type(child).__name__}")
print("\nAxes的子Artist:")
for i, child in enumerate(self.ax.get_children()):
print(f" {i}: {type(child).__name__}")
def custom_artist(self):
"""自定义Artist"""
from matplotlib.artist import Artist
from matplotlib.patches import Polygon
class CustomArtist(Artist):
def __init__(self, ax):
super().__init__()
self.ax = ax
def draw(self, renderer):
# 绘制自定义图形
vertices = np.array([[0.2, 0.2], [0.8, 0.2],
[0.5, 0.8], [0.2, 0.2]])
triangle = Polygon(vertices, closed=True,
facecolor='orange', alpha=0.7)
self.ax.add_patch(triangle)
fig, ax = plt.subplots(figsize=(8, 6))
# 添加自定义Artist
custom = CustomArtist(ax)
ax.add_artist(custom)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.set_title('自定义Artist示例')
plt.show()
# 使用示例
artist_demo = ArtistDemo()
artist_demo.artist_hierarchy()
artist_demo.custom_artist()
1.3.2 属性设置与获取
import matplotlib.pyplot as plt
import numpy as np
class PropertyDemo:
"""属性设置演示"""
def __init__(self):
self.x = np.linspace(0, 10, 100)
self.y = np.sin(self.x)
def property_methods(self):
"""属性设置方法演示"""
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 方法1: 创建时设置
axes[0, 0].plot(self.x, self.y, color='red', linewidth=3,
linestyle='--', marker='o', markersize=5)
axes[0, 0].set_title('创建时设置属性')
# 方法2: 使用set_*方法
line, = axes[0, 1].plot(self.x, self.y)
line.set_color('blue')
line.set_linewidth(2)
line.set_linestyle('-.')
line.set_marker('s')
line.set_markersize(4)
axes[0, 1].set_title('使用set_*方法')
# 方法3: 使用setp函数
line, = axes[1, 0].plot(self.x, self.y)
plt.setp(line, color='green', linewidth=2.5,
linestyle=':', marker='^', markersize=6)
axes[1, 0].set_title('使用setp函数')
# 方法4: 使用update方法
line, = axes[1, 1].plot(self.x, self.y)
line.update({'color': 'purple', 'linewidth': 2,
'linestyle': '-', 'marker': 'D', 'markersize': 5})
axes[1, 1].set_title('使用update方法')
plt.tight_layout()
plt.show()
def get_properties(self):
"""获取属性演示"""
fig, ax = plt.subplots(figsize=(8, 6))
line, = ax.plot(self.x, self.y, 'ro-')
# 获取属性
print("Line2D属性:")
print(f"颜色: {line.get_color()}")
print(f"线宽: {line.get_linewidth()}")
print(f"线型: {line.get_linestyle()}")
print(f"标记: {line.get_marker()}")
print(f"标记大小: {line.get_markersize()}")
# 获取所有属性
print("\n所有可设置的属性:")
for prop in line.properties():
print(f" {prop}")
# 使用getp函数
print(f"\n使用getp获取颜色: {plt.getp(line, 'color')}")
plt.show()
def property_animation(self):
"""属性动画演示"""
import matplotlib.animation as animation
fig, ax = plt.subplots(figsize=(8, 6))
line, = ax.plot(self.x, self.y, 'b-')
ax.set_xlim(0, 10)
ax.set_ylim(-1.5, 1.5)
ax.set_title('属性动画演示')
def animate(frame):
# 改变线条颜色和透明度
alpha = 0.3 + 0.7 * (np.sin(frame * 0.1) + 1) / 2
color = plt.cm.viridis(frame / 100)
line.set_alpha(alpha)
line.set_color(color)
# 改变数据
y_new = np.sin(self.x + frame * 0.1)
line.set_ydata(y_new)
return line,
anim = animation.FuncAnimation(fig, animate, frames=200,
interval=50, blit=True, repeat=True)
plt.show()
return anim
# 使用示例
prop_demo = PropertyDemo()
prop_demo.property_methods()
prop_demo.get_properties()
# anim = prop_demo.property_animation() # 取消注释以运行动画
1.4 第一个图表
1.4.1 简单线图
import matplotlib.pyplot as plt
import numpy as np
class FirstPlotDemo:
"""第一个图表演示"""
def __init__(self):
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
def simple_line_plot(self):
"""简单线图"""
# 生成数据
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
# 创建图表
plt.figure(figsize=(10, 6))
plt.plot(x, y)
# 添加标题和标签
plt.title('我的第一个Matplotlib图表', fontsize=16)
plt.xlabel('X轴 (弧度)', fontsize=12)
plt.ylabel('Y轴 (sin值)', fontsize=12)
# 添加网格
plt.grid(True, alpha=0.3)
# 显示图表
plt.show()
def enhanced_line_plot(self):
"""增强版线图"""
# 生成数据
x = np.linspace(0, 4*np.pi, 200)
y1 = np.sin(x)
y2 = np.cos(x)
y3 = np.sin(x) * np.cos(x)
# 创建图表
plt.figure(figsize=(12, 8))
# 绘制多条线
plt.plot(x, y1, 'b-', linewidth=2, label='sin(x)')
plt.plot(x, y2, 'r--', linewidth=2, label='cos(x)')
plt.plot(x, y3, 'g:', linewidth=3, label='sin(x)cos(x)')
# 美化图表
plt.title('三角函数对比', fontsize=18, fontweight='bold')
plt.xlabel('X轴 (弧度)', fontsize=14)
plt.ylabel('Y轴 (函数值)', fontsize=14)
# 设置坐标轴范围
plt.xlim(0, 4*np.pi)
plt.ylim(-1.2, 1.2)
# 添加图例
plt.legend(fontsize=12, loc='upper right')
# 添加网格
plt.grid(True, alpha=0.3, linestyle='-', linewidth=0.5)
# 添加注释
plt.annotate('最大值', xy=(np.pi/2, 1), xytext=(np.pi/2, 1.3),
arrowprops=dict(arrowstyle='->', color='red'),
fontsize=12, ha='center')
# 调整布局
plt.tight_layout()
# 显示图表
plt.show()
def multiple_plots(self):
"""多个子图"""
# 生成数据
x = np.linspace(0, 10, 100)
# 创建子图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
fig.suptitle('多种图表类型展示', fontsize=16, fontweight='bold')
# 子图1: 线图
axes[0, 0].plot(x, np.sin(x), 'b-', linewidth=2)
axes[0, 0].set_title('线图')
axes[0, 0].set_xlabel('X')
axes[0, 0].set_ylabel('sin(X)')
axes[0, 0].grid(True, alpha=0.3)
# 子图2: 散点图
y_scatter = np.sin(x) + np.random.normal(0, 0.1, len(x))
axes[0, 1].scatter(x, y_scatter, alpha=0.6, s=30)
axes[0, 1].set_title('散点图')
axes[0, 1].set_xlabel('X')
axes[0, 1].set_ylabel('sin(X) + 噪声')
axes[0, 1].grid(True, alpha=0.3)
# 子图3: 柱状图
x_bar = np.arange(1, 6)
y_bar = np.random.rand(5) * 10
axes[1, 0].bar(x_bar, y_bar, color=['red', 'green', 'blue', 'orange', 'purple'])
axes[1, 0].set_title('柱状图')
axes[1, 0].set_xlabel('类别')
axes[1, 0].set_ylabel('数值')
# 子图4: 直方图
data = np.random.normal(0, 1, 1000)
axes[1, 1].hist(data, bins=30, alpha=0.7, color='skyblue', edgecolor='black')
axes[1, 1].set_title('直方图')
axes[1, 1].set_xlabel('数值')
axes[1, 1].set_ylabel('频次')
# 调整子图间距
plt.tight_layout()
# 显示图表
plt.show()
def save_plot(self):
"""保存图表"""
# 生成数据
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
# 创建图表
plt.figure(figsize=(10, 6))
plt.plot(x, y, 'b-', linewidth=2)
plt.title('保存图表示例')
plt.xlabel('X轴')
plt.ylabel('Y轴')
plt.grid(True, alpha=0.3)
# 保存为不同格式
plt.savefig('my_first_plot.png', dpi=300, bbox_inches='tight')
plt.savefig('my_first_plot.pdf', bbox_inches='tight')
plt.savefig('my_first_plot.svg', bbox_inches='tight')
print("图表已保存为:")
print("- my_first_plot.png (高分辨率PNG)")
print("- my_first_plot.pdf (PDF矢量图)")
print("- my_first_plot.svg (SVG矢量图)")
plt.show()
# 使用示例
first_plot = FirstPlotDemo()
first_plot.simple_line_plot()
first_plot.enhanced_line_plot()
first_plot.multiple_plots()
first_plot.save_plot()
1.5 常用配置选项
1.5.1 全局配置
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
class ConfigurationDemo:
"""配置选项演示"""
def __init__(self):
self.x = np.linspace(0, 10, 100)
self.y = np.sin(self.x)
def rcparams_demo(self):
"""rcParams配置演示"""
# 查看当前配置
print("当前图形大小:", plt.rcParams['figure.figsize'])
print("当前字体大小:", plt.rcParams['font.size'])
print("当前线宽:", plt.rcParams['lines.linewidth'])
# 保存原始配置
original_params = plt.rcParams.copy()
# 修改全局配置
plt.rcParams.update({
'figure.figsize': [12, 8],
'font.size': 14,
'lines.linewidth': 2.5,
'lines.markersize': 8,
'axes.grid': True,
'grid.alpha': 0.3,
'axes.spines.top': False,
'axes.spines.right': False,
'font.sans-serif': ['SimHei', 'Arial Unicode MS'],
'axes.unicode_minus': False
})
# 创建图表
plt.figure()
plt.plot(self.x, self.y, 'o-', label='sin(x)')
plt.plot(self.x, np.cos(self.x), 's-', label='cos(x)')
plt.title('使用全局配置的图表')
plt.xlabel('X轴')
plt.ylabel('Y轴')
plt.legend()
plt.show()
# 恢复原始配置
plt.rcParams.update(original_params)
def style_sheets(self):
"""样式表演示"""
# 查看可用样式
print("可用样式:")
for style in plt.style.available:
print(f" {style}")
# 使用不同样式
styles = ['default', 'seaborn-v0_8', 'ggplot', 'bmh']
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
axes = axes.flatten()
for i, style in enumerate(styles):
with plt.style.context(style):
axes[i].plot(self.x, self.y, 'o-', label='sin(x)')
axes[i].plot(self.x, np.cos(self.x), 's-', label='cos(x)')
axes[i].set_title(f'样式: {style}')
axes[i].set_xlabel('X轴')
axes[i].set_ylabel('Y轴')
axes[i].legend()
axes[i].grid(True)
plt.tight_layout()
plt.show()
def custom_style(self):
"""自定义样式"""
# 创建自定义样式字典
custom_style = {
'figure.figsize': [10, 6],
'axes.facecolor': '#f8f9fa',
'axes.edgecolor': '#dee2e6',
'axes.linewidth': 1.2,
'axes.grid': True,
'grid.color': '#e9ecef',
'grid.linestyle': '-',
'grid.linewidth': 0.8,
'grid.alpha': 0.8,
'lines.linewidth': 2.5,
'lines.markersize': 8,
'font.size': 12,
'axes.titlesize': 16,
'axes.labelsize': 14,
'xtick.labelsize': 12,
'ytick.labelsize': 12,
'legend.fontsize': 12,
'axes.spines.top': False,
'axes.spines.right': False,
'axes.spines.left': True,
'axes.spines.bottom': True
}
# 应用自定义样式
with plt.rc_context(custom_style):
plt.figure()
plt.plot(self.x, self.y, 'o-', color='#007bff', label='sin(x)')
plt.plot(self.x, np.cos(self.x), 's-', color='#28a745', label='cos(x)')
plt.title('自定义样式图表')
plt.xlabel('X轴')
plt.ylabel('Y轴')
plt.legend()
plt.show()
def color_configuration(self):
"""颜色配置演示"""
# 颜色循环
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728',
'#9467bd', '#8c564b', '#e377c2', '#7f7f7f']
plt.rcParams['axes.prop_cycle'] = plt.cycler(color=colors)
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
# 子图1: 默认颜色循环
for i in range(8):
y = np.sin(self.x + i * np.pi/4)
axes[0].plot(self.x, y, linewidth=2, label=f'线条{i+1}')
axes[0].set_title('自定义颜色循环')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 子图2: 颜色映射
y_data = [np.sin(self.x + i * np.pi/8) for i in range(5)]
colors_map = plt.cm.viridis(np.linspace(0, 1, 5))
for i, (y, color) in enumerate(zip(y_data, colors_map)):
axes[1].plot(self.x, y, color=color, linewidth=2, label=f'线条{i+1}')
axes[1].set_title('使用颜色映射')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
def backend_configuration(self):
"""后端配置演示"""
import matplotlib
print(f"当前后端: {matplotlib.get_backend()}")
print(f"交互式模式: {matplotlib.is_interactive()}")
# 后端信息
backend_info = {
'Agg': '非交互式,适合服务器环境',
'Qt5Agg': '交互式,基于Qt5',
'TkAgg': '交互式,基于Tkinter',
'notebook': 'Jupyter Notebook内联显示'
}
print("\n常用后端:")
for backend, desc in backend_info.items():
print(f" {backend}: {desc}")
# 设置后端示例(注意:需要在导入pyplot之前设置)
print("\n设置后端示例:")
print("import matplotlib")
print("matplotlib.use('Agg') # 设置为Agg后端")
print("import matplotlib.pyplot as plt")
# 使用示例
config_demo = ConfigurationDemo()
config_demo.rcparams_demo()
config_demo.style_sheets()
config_demo.custom_style()
config_demo.color_configuration()
config_demo.backend_configuration()
1.6 本章总结
1.6.1 学习要点回顾
Matplotlib基础概念
- 分层架构:Backend、Artist、Scripting
- Figure和Axes的关系
- Artist对象模型
绘图接口
- pyplot接口(类MATLAB)
- 面向对象接口(推荐)
- Artist层接口(高级定制)
基本绘图流程
- 创建Figure和Axes
- 绘制图形元素
- 设置属性和样式
- 显示或保存图表
配置管理
- rcParams全局配置
- 样式表系统
- 自定义样式
- 后端选择
1.6.2 实践练习
# 练习1: 创建一个包含多个子图的复合图表
def practice_1():
"""练习:多子图复合图表"""
# TODO: 创建2x3的子图布局
# 包含:线图、散点图、柱状图、直方图、饼图、箱线图
pass
# 练习2: 自定义样式主题
def practice_2():
"""练习:创建自定义样式主题"""
# TODO: 设计一个专业的图表样式
# 包含:颜色方案、字体设置、网格样式等
pass
# 练习3: 属性动态修改
def practice_3():
"""练习:动态修改图表属性"""
# TODO: 创建一个图表,然后动态修改其属性
# 如:颜色、线型、标记等
pass
1.6.3 常见问题
Q: pyplot和面向对象接口有什么区别? A: pyplot接口简单易用,适合快速绘图;面向对象接口更灵活,适合复杂图表和程序化绘图。
Q: 如何选择合适的后端? A: 交互式环境选择Qt5Agg或TkAgg;服务器环境选择Agg;Jupyter Notebook使用inline或notebook。
Q: 中文显示乱码怎么解决?
A: 设置中文字体:plt.rcParams['font.sans-serif'] = ['SimHei']
Q: 如何提高图表质量? A: 使用高DPI、矢量格式、合适的颜色方案和字体大小。
1.6.4 下章预告
下一章我们将学习各种基础图表类型,包括线图、散点图、柱状图等的详细用法和最佳实践。