理解matplotlib繪圖


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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM