matplotlib绘图经验

Author : zbzhen,        Modified : Fri Nov 1 01:13:45 2024

目标:

和LaTex一样, 绘图也不难, 难的是找到一个顺手且实用的模板

推荐用保存mp4文件的方法

1. 支持中文和保存为矢量图

中文支持, 只需加上代码

plt.rcParams["font.sans-serif"]=["SimHei"]
plt.rcParams["axes.unicode_minus"]=False

矢量图推荐用pdf格式或svg格式, 建议在保存代码之前加上fig.tight_layout()

1.1. 简单例子

import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(10,8), dpi=72,facecolor="w")
ax = plt.subplot(111)
x = np.linspace(-np.pi, np.pi, 13)
plt.plot(x, np.sin(x)+0.5, "v", label="polyline", linestyle='dashed')
x = np.linspace(-np.pi, np.pi, 100)
plt.gca().set_aspect('equal',adjustable='box') # 设置坐标轴比例为1:1
plt.plot(x, np.sin(x)+0.5, label="curve: $\sin(x)+\\dfrac{1}{2}$")
plt.legend()
fig.tight_layout()
plt.savefig("test0.svg")
plt.show()

1.2. 高端例子

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager as mft
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
plt.rcParams["font.sans-serif"]=["SimHei"] #支持中文
plt.rcParams["axes.unicode_minus"]=False   #负号变短

fig = plt.figure(figsize=(10,8), dpi=72,facecolor="w")
ax = plt.subplot(111)

x = np.linspace(-np.pi, np.pi, 13)
plt.plot(x, np.sin(x)+0.5, "v", label="polyline", linestyle='dashed')
x = np.linspace(-np.pi, np.pi, 100)
plt.plot(x, np.sin(x)+0.5, label="curve: $\sin(x)+\\dfrac{1}{2}$")

### 设置坐标范围
# plt.xlim(-3.3, 3.3)
# plt.ylim(-0.6, 1.6)

# ax.set_xticks(np.pi/2*np.array([-2,-1,0,1,2])) # 也可以指定x轴主刻度
xmajorLocator = MultipleLocator(np.pi/2) #x轴主刻度
ax.xaxis.set_major_locator(xmajorLocator)
xminorLocator   = MultipleLocator(np.pi/8) #x轴次刻度
ax.xaxis.set_minor_locator(xminorLocator)

# 刻度字体
fontz = 20 
for tick in ax.yaxis.get_major_ticks():
    tick.label1.set_fontsize(fontz)
for tick in ax.xaxis.get_major_ticks():
    tick.label1.set_fontsize(fontz)

#设置x轴标签文本的格式
xmajorFormatter = FormatStrFormatter('%0.2f') 
ax.xaxis.set_major_formatter(xmajorFormatter)

plt.title("Title", fontsize=fontz)
plt.grid()
# ax.xaxis.grid(True, which='minor') #x坐标轴的网格使用主刻度
# ax.yaxis.grid(True, which='major') #y坐标轴的网格使用次刻度
plt.legend(prop = mft.FontProperties(size=fontz))
plt.xlabel("$x$", fontsize = fontz)
plt.ylabel("$y$", fontsize = fontz)
fig.tight_layout()
plt.savefig("test1.svg")
plt.show()

1.2. 高端例子(简化版)

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager as mft
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
# from pylab import mpl
# mpl.style.use('classic')
config = {
    "font.family":'serif',
    "font.size": 20,
    "font.sans-serif": "SimHei",
    # "mathtext.fontset":'cm', 
    "font.serif":'SimSun'
}
plt.rcParams.update(config)

fig = plt.figure(figsize=(10,8), dpi=72,facecolor="w")
ax = plt.subplot(111)

x = np.linspace(-np.pi, np.pi, 13)
plt.plot(x, np.sin(x)+0.5, "v", label="polyline", linestyle='dashed')
x = np.linspace(-np.pi, np.pi, 100)
plt.plot(x, np.sin(x)+0.5, label="curve: $\sin(x)+\\dfrac{1}{2}$")

plt.xticks(np.pi/2*np.array([-2,-1,0,1,2]), ["$-\pi$", "$-\\dfrac{\pi}{2}$", "$0$", "$\\dfrac{\pi}{2}$", "$\pi$"]) 

ran = np.arange(5)*0.5-0.5
plt.yticks(ran) 
ymajorLocator = MultipleLocator(0.5) 
ax.yaxis.set_major_locator(ymajorLocator)
plt.yticks(fontsize=20)
plt.title("Title")
plt.grid()
plt.legend(loc='upper left')
plt.xlabel("$x$")
plt.ylabel("$y$")
fig.tight_layout()
plt.savefig("test2.svg")
plt.show()

2. 绘制动图

2.1. 一些常见的动图方案

2.1.1. 方法一

该方法只能在本地运行, 并且不能用于jupyter

import numpy as np
import matplotlib.pyplot as plt
def f(x, y):
    return np.sin(x) + np.cos(y)
x = np.linspace(0, 2 * np.pi, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
plt.ion() # Ensure that redrawing is possible
fig, ax = plt.subplots()
im = plt.imshow(f(x, y))
fig.show()
for i in range(20):
    x += np.pi / 15.
    y += np.pi / 20.
    z = f(x, y)
    im.set_array(z)
    tt = (5-len(str(i)))*'0' + str(i)
    plt.title(tt)
    plt.pause(0.1)
    # plt.savefig('./fig/'+tt+'.png')
    fig.canvas.draw()
plt.show()

或者下面的方法, 只能在本地运行, 并且不能用于jupyter

import matplotlib.pyplot as plt
import numpy as np

# 初始化图形和轴对象
fig, ax = plt.subplots()

# 循环次数,例如10次
for i in range(10):
    # 清除之前的图形
    ax.clear()
    
    # 生成新的数据
    x = np.linspace(0, 4 * np.pi, 100)
    y = np.sin(x + i * np.pi / 5)
    
    # 绘制新的数据
    ax.plot(x, y)
    
    # 设置图形的标题
    ax.set_title(f"Frame {i}")
    
    # 显示图形
    plt.pause(0.1)  # 暂停一段时间,以便图形可以更新

# 显示最终的图形
plt.show()

2.1.2. 方法二

该方法只能在本地运行, 在jupyter中会生成很多文件

import matplotlib.pyplot as plt
import numpy as np 

x = np.linspace(0, 10, 100)
y = np.cos(x)
fig = plt.figure()
for p in range(50):
    x += p*0.01
    y=np.cos(x)
    plt.plot(x,y)
    plt.draw()  
    plt.pause(0.1)
    fig.clear()

2.1.3. 方案三

该方法只能在jupyter中跑, 其实显示效果并不好, 并且会清除输出

from matplotlib import pyplot as plt
from IPython.display import clear_output
import numpy as np
for i in range(50):
    clear_output(wait=True)
    y = np.random.random([10,1])
    plt.plot(y)
    plt.show()

2.1.4. 方案四

该方法只能在jupyter中跑, 并非实时更新, 但是最后可以生成一个动态交互图

# https://matplotlib.org/2.0.2/examples/animation/index.html
import matplotlib.animation
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams["animation.html"] = "jshtml"
plt.rcParams['figure.dpi'] = 150  
plt.ioff()
fig, ax = plt.subplots()

x= np.linspace(0,10,100)
def animate(t):
    plt.cla()
    plt.plot(x-t,x)
    plt.xlim(0,10)
matplotlib.animation.FuncAnimation(fig, animate, frames=10)

2.2. 远程动图的绘制

3D动图(尤其是3D交互动图)无论用哪种方案, 都比较麻烦, 主要原因是数据量太大

2.2.1. 保存数据和导入数据画图的例子

该方法可能会适合某些特殊的场合

下面例子里, 保存的数据文件超过1Mb

import numpy as np
import matplotlib.pyplot as plt

################################
### This codes run on remote ###
x = np.linspace(0, 2 * np.pi, 300)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
with open('test.npy', 'wb') as f:
    for i in range(20):
        x += i*np.pi / 150.
        y += i*np.pi / 200.
        z = np.sin(x) + np.cos(y)
        z = np.array(z, dtype=np.float16) # np.float32
        np.save(f, z)
### This codes run on remote ###
################################


################################
### This codes run on local ###
import numpy as np
import matplotlib.pyplot as plt

plt.ion() # Ensure that redrawing is possible
fig, ax = plt.subplots()
f = open('test.npy', 'rb')
z = np.load(f)
dtype = np.float32
z = np.array(z, dtype=dtype)
im = plt.imshow(z)
fig.show()

for i in range(19):
    z = np.load(f)
    z = np.array(z, dtype=dtype)
    im.set_array(z)
    tt = (5-len(str(i)))*'0' + str(i)
    plt.title(tt)
    fig.canvas.draw()
plt.show()

f.close()
### This codes run on local ###
################################

2.2.2. ffmpeg的安装

这里推荐借助免费开源的ffmpeg生成.mp4文件, 文件不到100M

只需下载解压再添加环境变量. 以linux为例:

~/.bashrc文件最后面添加一行代码

PATH=$PATH: 解压后的ffmpeg所在路径

然后命令行输入source ~/.bashrc 生效

2.2.3. 保存mp4文件(推荐)

需要安装ffmpeg, 下面的例子保存的.mp4文件不到40kb

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as manimation
# 万能方法: 清除每次绘制后的fig, 虽然略微慢一点, 但是非常实用
# fps太小的话, 在linux中容易出bug
FFMpegWriter = manimation.writers['ffmpeg']
writer = FFMpegWriter(fps=8) 
fig = plt.figure()
x = np.linspace(0, 2 * np.pi, 300)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
with writer.saving(fig, "writer_test.mp4", 100):
    for i in range(30):
        x += i*np.pi / 150.
        y += i*np.pi / 200.
        z = np.sin(x) + np.cos(y)
        plt.imshow(z)
        plt.title(str(i))
        plt.xlabel(r"x", fontsize = 15)
        plt.ylabel(r"y", fontsize = 15)
        fig.tight_layout()
        writer.grab_frame()
        fig.clear()

上面的方法原理是: 每一帧图都完全重画, 可能会略微影响效率, 但真的非常省事, 所以就不考虑别的方案了.

3. 总结

推荐使用保存mp4文件的方法, 主要优势: 支持本地和服务器, 也支持jupyter, 代码的可读性和可修改性也强, 并且生成的mp4文件非常实用.

缺陷表现在:

总之, 缺陷可以接受, 习惯了就好说


Ref.