matplotlib绘图经验

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

目标:

  • 支持中文, 并可以保存为矢量图
  • 绘制可控动图, 本地和服务器都能绘制
  • 方案能和jupyter通用

和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. 远程动图的绘制

  • 最容易想到的办法是把绘图数据保存起来, 后续再导入数据绘图, 但是这种方案只能针对于数据量小的情形, 稍微大一点的模型, 数据文件会特别大.

  • 另外一种方案是, 一边跑数据, 一边把数据写入.mp4视频文件中, 这种方案的优势体现在:

    • 可以通过dpi的设置, 控制.mp4视频的清晰度和大小

    • .mp4文件体积相对数据文件的体积要小很多

    • .mp4文件具有可控性, 也很方便观察, 如果用Python开启服务器的http服务, 则可以直接在浏览器中播放服务器中的mp4视频. 这种方法很简单, 只需在服务器的命令行中输入
      python3 -m http.server 8889
      组合快捷键 Ctrl + C 可关闭. 然后在本地浏览器中打开网址 http:10.26.6.81:8889可看视频, 这里的10.26.6.81为内网ip地址, 根据实际情况修改.
      在jupyter中, 可在markdown里插入下面代码, 直接显示视频

      <video  width="600" height="300" autoplay="flase" preload="none" controls="controls" src="http://localhost:8889/writer_test.mp4" >
      </video>
      

      上面scr里的视频地址也可以用mp4绝对路径, 相对路径似乎会出问题

  • 预想方案, 对于超大型数据, 可以考虑用直播的原理, 实时更新视频画面, 感觉没什么必要, 因为要分享和交流, 最后还是得保存.mp4文件. 如果真的是数据量超大, 可以考虑保存多个.mp4文件. 最后用这些视频拼接成一个视频.

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文件非常实用.

缺陷表现在:

  • 不能立马看到更新的图: 可把最大迭代步设置小一点, 然后在服务器上跑大的迭代步
    • 数据更新快, 生成mp4其实也是很快的. 如果更新次数多, 可先把最大迭代步设置小一点, 以便观察, 感觉程序无误, 再把最大迭代步设置大一点放服务器上重新跑, 并不会多耗费时间;
    • 数据更新慢, 如果能实时更新也是要等, 所以这个没什么影响.
  • 需要额外安装ffmpeg: 但凡做动图, 必装ffmpeg, 它的下载安装和配置并不麻烦, 并且只需整一次.

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


Ref.