基于matploblib的面向对象风格的绘图方式
传统的绘图方式,典型如matlab,是面向过程的。尽管思维上更加直接,但灵活性不足。面向对象风格(以下简称OO风格)的绘图方式拥有更多的定制自由度。万物皆对象,这也是我个人偏好OO风格的原因。
Python主要通过调用matplotlib宏包绘图。matplotlib宏包可以通过面向过程调用,类似matlab的方式,对应pylab模块;也可以采用面向对象的方式调用,对应pyplot模块。这里仅介绍如何通过OO风格的调用matplotlib宏包绘图。而且,这里也仅关注绘图的框架,其余大量的绘图细节控制,请参考matplotlib手册(见文末)。 本文使用python3,编辑器则推荐使用spyder 3和jupyter notebook。PyCharm适合大型Python软件工程,笨重且不易上手。 matplotlib适合绘制线图,也可以绘制二维云图。至于三维流场的显示,建议使用ParaView。以下代码都已在linux系统(ubuntu18.04 & manjaro 18)上通过编译测试。 创建画布对象的命令:
创建画布对象后,就可以针对fig这个画布对象操作了。
括号中可以增加对画布的大小、分辨率以及颜色等的控制参数。
比如,需要在画布上创建两个子图对象,排布方式是“两行一列”,可以使用如下的代码:
ax0 = fig.add_subplot(211) # 第一子图对象
ax1 = fig.add_subplot(212) # 第二子图对象
然后,直接操作两个子图对象ax0和ax1绘图即可。
下面是一个简单的示例代码:
import numpy as np # 调用数值计算包
import matplotlib.pyplot as plt # 调用绘图包
x = np.arange(0., 4.*np.pi, 0.1) # 准备数据
y = np.sin(x)
z = np.cos(x)
fig = plt.figure() # 定义一个画布对象fig
ax0 = fig.add_subplot(211) # 在画布对象上定义两个axes子对象
ax1 = fig.add_subplot(212) # 排列方式是2 rows & 1 col
ax0.plot(x,y) # 绘制线图
ax1.plot(x,z)
plt.tight_layout() # 使布局紧凑
如果提示没有安装numpy或者matplotlib宏包,ubuntu系统下可以使用apt安装
sudo apt installpython3-numpy
sudo apt install python3-matplotlib
如果是manjaro系统,可以使用pacman安装。
如下所示:
至此,我们已经展示了如何采用OO风格调用matplotlib宏包绘图:从全局到局部,再到细节,不断的定义对象,画布对象、axes子图对象……
为了改进各子图的细节,还需要进一步基于axes子图对象定义更多的子对象,比如axis坐标轴对象等。图1在细节上还非常粗糙,下面将展示如何控制图片整体布局以及更多细节。plt.show()只能在屏幕上显示效果图片。
若要保存图片,可使用命令:
plt.savefig('./figure01.png', dpi=200)
也可以保存为.jpeg、.eps、.pdf等其它的图片格式。dpi=200是输出图片的分辨率。
ax0 = fig.add_subplot(211)
ax1 = fig.add_subplot(212)
第一节的示例展示就是采用这种方法,这种创建子图对象的方法可以满足大部分的期刊绘图要求。上面的命令意思是将画布分为“两行一列”,第一子图对象位于第1行第1列的位置,第二个子图对象占据第2行第1列的位置。
可以用一行命令同时建立画布对象和子图对象:
这时,ax是一个2×1的数组,可以分别使用ax[0]和ax[1]引用。
这种方式将整个画布分为若干网格单元,然后定义其中的每个子图对象占据的单元位置和数目。相比第一种方式,稍复杂,但更灵活,子图之间也不必须排列整齐。 ax0 = plt.subplot2grid((3,3), (0,0),rowspan=1, colspan=2)
ax1 = plt.subplot2grid((3,3), (0,2), rowspan=3, colspan=1)
ax2 = plt.subplot2grid((3,3), (1,0), rowspan=2, colspan=2)
以第一个子图对象为例,括号中参数表示:将画布分为3×3共9个网格单元。子图对象ax0的位置是(0,0),占据一行两列共2个网格单元。其中位置坐标从画布的左上角算起。
以上两种方式,建立的子图对象之间是隔离的。还有些情况,我们需要子图之间有重叠,比如一张子图是另外一张子图的特写放大。下面的命令可以满足要求
ax0 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax1 = fig.add_axes([0.2, 0.5, 0.4, 0.3])
其中,方括号中,前两个表示子图的坐标,第三和第四个参数数值表示子图的长度和高度。坐标零点位于画布的左下角。 当然还有其它的创建子图对象的方式,比如gridspec,大家可以自行查阅。 如果需要自定义画布内所有目标对象涉及的字体及字体大小、颜色等,命令如下:
matplotlib.rcParams.update({'font.size':15,
'font.family':'STIXGeneral',
'mathtext.fontset':'stix',
'text.usetex':True})
4 最后的输出图
import numpy as np
import matplotlib.pyplot as plt from matplotlib.ticker import FuncFormatter matplotlib.rcParams.update({'font.size':22, 'font.family': 'STIXGeneral', 'mathtext.fontset': 'stix', #fig, ax1 = plt.subplots(1, 1, figsize=(8, 5)) fig = plt.figure(figsize=(8,5)) ax1 = fig.add_axes([0.1, 0.15, 0.775, 0.75]) x = np.arange(0, 2.*np.pi, 0.05) ax1.plot(x, y1, marker='o', markevery=5, color='r', markersize=10, ax1.set_ylabel(r'$y_1 = x^2$') ax1.legend(loc=0, frameon=True) xticks1 = [0, np.pi/3.0, np.pi/3.0*2., np.pi, np.pi/3.0*4.0, np.pi/3.0*5.0, 2.0*np.pi] xticklabels = [0, r'$\pi/3$', r'$2\pi/3$', r'$\pi$', r'$4\pi/3$', r'$5\pi/3$', r'$2\pi$'] ax1.set_xticklabels(xticklabels) ax1.set_title(r'dual $y$-axix') ax1.set_xlim([-0.5, 6.75]) ax1.tick_params('y', which='major', direction='in', length=8, width=2) ax1.tick_params('x', which='major', direction='in', length=8, width=2) ax2.plot(x, y2, marker='d', markevery=5, color='gold', markersize=10, ax2.set_ylabel(r'$y_2 = \sin x$') ax2.legend(loc=0, frameon=True) formatter = FuncFormatter(formatnum) ax2.yaxis.set_major_formatter(formatter) ax2.set_ylim([-1.2, 1.2]) ax2.tick_params('y', which='major', direction='in', length=8, width=2) fig.subplots_adjust(left=0.15, right=0.95, bottom=0.15, top=0.95) fig.savefig('figure02.png', dpi=200) OO风格调用matplotlib宏包绘图的步骤总结为图3: