Author : zbzhen, Modified : Fri Nov 1 01:13:45 2024
目标:
和LaTex一样, 绘图也不难, 难的是找到一个顺手且实用的模板
推荐用保存mp4文件的方法
中文支持, 只需加上代码
plt.rcParams["font.sans-serif"]=["SimHei"] plt.rcParams["axes.unicode_minus"]=False
矢量图推荐用pdf格式或svg格式, 建议在保存代码之前加上fig.tight_layout()
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()
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()
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()
该方法只能在本地运行, 并且不能用于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()
该方法只能在本地运行, 在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()
该方法只能在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()
该方法只能在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)
最容易想到的办法是把绘图数据保存起来, 后续再导入数据绘图, 但是这种方案只能针对于数据量小的情形, 稍微大一点的模型, 数据文件会特别大.
另外一种方案是, 一边跑数据, 一边把数据写入.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交互动图)无论用哪种方案, 都比较麻烦, 主要原因是数据量太大
该方法可能会适合某些特殊的场合
下面例子里, 保存的数据文件超过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 ### ################################
这里推荐借助免费开源的ffmpeg生成.mp4文件, 文件不到100M
只需下载解压再添加环境变量. 以linux为例:
在~/.bashrc文件最后面添加一行代码
PATH=$PATH: 解压后的ffmpeg所在路径
然后命令行输入source ~/.bashrc 生效
需要安装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()
上面的方法原理是: 每一帧图都完全重画, 可能会略微影响效率, 但真的非常省事, 所以就不考虑别的方案了.
推荐使用保存mp4文件的方法, 主要优势: 支持本地和服务器, 也支持jupyter, 代码的可读性和可修改性也强, 并且生成的mp4文件非常实用.
缺陷表现在:
总之, 缺陷可以接受, 习惯了就好说
Ref.