matplotlib是基于Python语言的开源项目,旨在为Python提供一个数据绘图包。Matplotlib 可能是 Python 2D-绘图领域使用最广泛的套件。它能让使用者很轻松地将数据图形化,并且提供多样化的输出格式。
matplotlib使用numpy进行数组运算,并调用一系列其他的Python库来实现硬件交互。matplotlib的核心是一套由对象构成的绘图API。
你需要安装Python, numpy和matplotlib。(可以到python.org下载Python编译器。相关Python包的安装,请参看我的Python小技巧)
matplotlib的官网是: http://matplotlib.org/ 官网有丰富的图例和文档说明。
matplotlib在github的地址为:https://github.com/matplotlib 欢迎有兴趣的开发者fork。
matplotlib是受MATLAB的启发构建的,模仿MATLAB但是不模仿“收费”
1 一个简单的例子
(1) 绘图,画一条直线
import matplotlib.pyplot as plt plt.plot([0, 1], [0, 1]) # plot a line from (0, 0) to (1, 1) plt.title("a strait line") plt.xlabel("x value") plt.ylabel("y value") plt.savefig("demo.jpg") plt.show()
(2) 解析
上面的代码实际上封装了很多,如果我们想简单的快速绘图,那么你只要简单的调用上面的代码。但是作为程序员,怎么能甘心做个调用者,让我们解开matplotlib的核心面纱吧
第一行,plt.plot([0,1],[0,1])源码
# 源码1
def plot(*args, **kwargs): ax = gca() #(1) 获得axes # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() hold = kwargs.pop('hold', None) if hold is not None: ax.hold(hold) try: ret = ax.plot(*args, **kwargs) #(2) 调用ax.plot() draw_if_interactive() finally: ax.hold(washold) return ret
我们看到里面代码(1) 处调用了gca,当获得了axes之后,plot是通过ax进行的,代码(2)
好的,那么继续深入ax=gca(),查看gca()的源码
# 源码2
def gca(**kwargs): """ Get the current :class:`~matplotlib.axes.Axes` instance on the current figure matching the given keyword args, or create one. If the current axes doesn't exist, or isn't a polar one, the appropriate axes will be created and then returned. """ ax = gcf().gca(**kwargs) return ax
好吧,实际上里面还有洞天,继续追踪gcf()的源码
#源码 3
def gcf(): "Get a reference to the current figure." figManager = _pylab_helpers.Gcf.get_active() #(1) 从figure的active 列表中取出最后面的一个,如果列表为空,则返回None if figManager is not None: return figManager.canvas.figure #(2) 直接返回当前active figure列表中最上面的那个figure else: return figure() #(3) 如果没有则创建一个
在这里获得一个figure,如果程序里面有多个figure,则返回正激活的那个(激活是指如果有多个窗口,则是最上面的那个,这个窗口接触鼠标、键盘输入),如果没有则创建。创建的时候默认就将这个figure设为激活的figure。
OK,回溯到源码2。也就是这个gca()是调用figure的gca(),那么继续探索之旅。
#源码4 figure类的gca def gca(self, **kwargs): """ Get the current axes, creating one if necessary The following kwargs are supported for ensuring the returned axes adheres to the given projection etc., and for axes creation if the active axes does not exist: %(Axes)s """ ckey, cax = self._axstack.current_key_axes() #(1) 粗糙axes的是stack # if there exists an axes on the stack see if it maches # the desired axes configuration if cax is not None: # if no kwargs are given just return the current axes # this is a convenience for gca() on axes such as polar etc. if not kwargs: return cax # if the user has specified particular projection detail # then build up a key which can represent this else: # we don't want to modify the original kwargs # so take a copy so that we can do what we like to it kwargs_copy = kwargs.copy() projection_class, _, key = process_projection_requirements( self, **kwargs_copy) # let the returned axes have any gridspec by removing it from # the key ckey = ckey[1:] key = key[1:] # if the cax matches this key then return the axes, otherwise # continue and a new axes will be created if key == ckey and isinstance(cax, projection_class): return cax # no axes found, so create one which spans the figure return self.add_subplot(1, 1, 1, **kwargs)
这里和上面gcf类似,也是看是否有已经存在的,如果存在axes,则直接调用,如果不存在,则是通过figure.add_subplot(1,1,1)创建一个axes。
好了,到这边为止,我们通过查看源码已经深入的接触了figure,axes。实际简单的一句plt.plot() 是通过figure上得axes调用plot()实现的。
因此我们可以自己写
fig1 = plt.figure('fig1') #获得一个figure, 这个figure是接下来所有画在这上面对象的载体。可以理解为边框 fig1_axes_1 = fig1.add_axes([0.1, 0.1, 0.8, 0.8]) fig1_axes_1.plot([0,1],[0,1])
2 理清关系
接下来的内容很多转载自http://www.cnblogs.com/vamei/archive/2013/01/30/2879700.html
我们将上面的直线绘图更改为面向对象式(OO, object-oriented)的,为此,我们引入两个类: Figure和FigureCanvas。(函数式编程也调用了这些类,只是调用的过程被函数调用所遮掩。)
# object-oriented plot from matplotlib.figure import Figure from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas fig = Figure() canvas = FigureCanvas(fig) ax = fig.add_axes([0.1, 0.1, 0.8, 0.8]) line, = ax.plot([0,1], [0,1]) ax.set_title("a straight line (OO)") ax.set_xlabel("x value") ax.set_ylabel("y value") canvas.print_figure('demo.jpg')
上面的例子中,我们至少构建了四个对象: fig, canvas, ax, line。它们分别属于Figure类,FigureCanvas类,Axes类和Line2D类。(使用obj.__class__.__name__来查询对象所属的类)
在深入各个对象之前,我们先来做一个比喻。看下面一个图片:
可以看到,图中有一个房子,房子上有窗户和门,窗户上有条纹,门上有把手,此外图像外还有一只小乌龟。我们所提到的房子,窗户,门,条纹,把手,都可以称其为对象。不同的对象之间有依附关系,比如窗户和门属于房子,而把手属于门。乌龟和房子则是并行的两个对象。此外,整个图像外有一个方框,用来表明可绘图的范围,所有上面提到的元素都依附于该方框。
这就是用面向对象的方式来理解一个图像。事实上,对象是描述图像的最自然的方式,面向对象编程最成功的领域就是在计算机图形方面。
我们先来看什么是Figure和Axes对象。在matplotlib中,整个图像为一个Figure对象。在Figure对象中可以包含一个,或者多个Axes对象。每个Axes对象都是一个拥有自己坐标系统的绘图区域。其逻辑关系如下:
转过头来看直线图。整个图像是fig对象。我们的绘图中只有一个坐标系区域,也就是ax。此外还有以下对象。(括号中表示对象的基本类型)
Title为标题。Axis为坐标轴,Label为坐标轴标注。Tick为刻度线,Tick Label为刻度注释。各个对象之间有下面的对象隶属关系:
(yaxis同样有tick, label和tick label,没有画出)
尽管data是数据绘图的关键部分,也就是数据本身的图形化显示,但是必须和xaxis, yaxis, title一起,才能真正构成一个绘图区域axes。一个单纯的,无法读出刻度的线是没有意义的。xaxis, yaxis, title合起来构成了数据的辅助部分(data guide)。
上面元素又包含有多种图形元素。比如说,我们的data对象是一条线(Line2D)。title, tick label和label都是文本(Text),而tick是由短线(Line 2D)和tick label构成,xaxis由坐标轴的线和tick以及label构成,ax由xaxis, yaxis, title, data构成,ax自身又构成了fig的一部分。上面的每个对象,无论是Line2D, Text还是fig,它们都来自于一个叫做Artist的基类。
OO绘图的原程序还有一个canvas对象。它代表了真正进行绘图的后端(backend)。Artist只是在程序逻辑上的绘图,它必须连接后端绘图程序才能真正在屏幕上绘制出来(或者保存为文件)。我们可以将canvas理解为绘图的物理(或者说硬件)实现。
在OO绘图程序中,我们并没有真正看到title, tick, tick label, xaxis, yaxis对象,而是使用ax.set_*的方法间接设置了这些对象。但这些对象是真实存在的,你可以从上层对象中找到其“真身”。比如,fig.axes[0].xaxis就是我们上面途中的xaxis对象。我们可以通过fig -> axes[0] (也就是ax) -> xaxis的顺序找到它。因此,重复我们刚才已经说过的,一个fig就构成了一个完整的图像。对于每个Artist类的对象,都有findobj()方法,来显示该对象所包含的所有下层对象。
坐标
坐标是计算机绘图的基础。计算机屏幕是由一个个像素点构成的。想要在屏幕上显示图像,计算机必须告诉屏幕每个像素点上显示什么。所以,最贴近硬件的坐标体系是以像素为单位的坐标体系。我们可以通过具体说明像素位置来标明显示器上的某一点。这叫做显示坐标(display coordinate),以像素为单位。
然而,像素坐标不容易被纳入绘图逻辑。相同的程序,在不同的显示器上就要调整像素值,以保证图像不变形。所以一般情况下,还会有图像坐标和数据坐标。
图像坐标将一张图的左下角视为原点,将图像的x方向和y方向总长度都看做1。x方向的0.2就是指20%的图像在x方向的总长,y方向0.8的长度指80%的y方向总长。(0.5, 0.5)是图像的中点,(1, 1)指图像的右上角。比如下面的程序,我们在使用add_axes时,传递的参数中,前两个元素为axes的左下角在fig的图像坐标上的位置,后两个元素指axes在fig的图像坐标上x方向和y方向的长度。fig的图像坐标称为Figure坐标,储存在为fig.transFigure
(类似的,每个axes,比如ax1,有属于自己的图像坐标。它以ax1绘图区域总长作为1,称为Axes坐标。也就是ax1.transAxes。(0.5, 0.5)就表示在Axes的中心。Axes坐标和Figure坐标原理相似,只是所用的基准区域不同。)
from matplotlib.figure import Figure from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas fig = Figure() canvas = FigureCanvas(fig) # first axes ax1 = fig.add_axes([0.1, 0.1, 0.2, 0.2]) line, = ax1.plot([0,1], [0,1]) ax1.set_title("ax1") # second axes ax2 = fig.add_axes([0.4, 0.3, 0.4, 0.5]) sca = ax2.scatter([1,3,5],[2,1,2]) ax2.set_title("ax2") canvas.print_figure('demo.jpg')
我们在绘图,比如使用plot的时候,绘制了两点间的连线。这两点分别为(0, 0)和(1, 1)。(plot中的第一个表为两个x坐标,第二个表为两个y坐标)。这时使用的坐标系为数据坐标系(ax1.transData)。我们可以通过绘出的坐标轴读出数据坐标的位置。
如果绘制的是具体数据,那么数据坐标符合我们的需求。如果绘制的是标题这样的附加信息,那么Axes坐标符合符合我们的需求。如果是整个图像的注解,那么Figure坐标更符合需求。每一个Artist对象都有一个transform属性,用于查询和改变所使用的坐标系统。如果为显示坐标,transform属性为None。
参考资料:
(1) http://www.cnblogs.com/vamei/archive/2013/01/30/2879700.html
(2) http://hyry.dip.jp/tech/book/page/scipy/matplotlib_fast_plot.html
(3) http://liam0205.me/2014/09/11/matplotlib-tutorial-zh-cn/
(4) http://reverland.org/python/2012/09/07/matplotlib-tutorial/
(5) http://www.yeolar.com/note/2011/04/28/matplotlib-tips/