# Chaco-交互式圖表
Chaco是一個2D的繪圖庫,如果你安裝了Python(x,y)的話,可以在pythonxy的安裝目錄下的找到Chaco的demo程序:
```
import numpy as np
from enthought.chaco.shell import *
x = np.linspace(-2*np.pi, 2*np.pi, 100)
y = np.sin(x)
plot(x, y, "r-")
title("First plot")
ytitle("sin(x)")
show()
```

用Chaco的腳本繪圖方式快速繪制正弦波
plot函數的第三個參數中的"r"指定繪圖的顏色為紅色,"-"指定繪圖的線型為實線。title函數為繪圖添加標題,ytitle為Y軸添加標題,show()函數最終顯示繪圖結果。
腳本繪圖不是Chaco的強項,雖然它的這套腳本繪圖API和Matplotlib的pylab類似,不過它提供的功能卻沒有pylab豐富。Chaco的優勢在于它可以很方便地嵌入到你的應用程序之中,開發出自己獨特的繪圖應用。
## 面向應用繪圖
要將Chaco嵌入到別的應用程序之中,需要做一些額外的工作,因此代碼量比面向腳本繪圖要多,不過同時也更具有靈活性。先來看一個例子:
```
from enthought.traits.api import HasTraits, Instance
from enthought.traits.ui.api import View, Item
from enthought.chaco.api import Plot, ArrayPlotData
from enthought.enable.component_editor import ComponentEditor
from numpy import linspace, sin
class LinePlot(HasTraits):
plot = Instance(Plot)
traits_view = View(
Item('plot',editor=ComponentEditor(), show_label=False),
width=500, height=500, resizable=True, title="Chaco Plot")
def __init__(self):
super(LinePlot, self).__init__()
x = linspace(-14, 14, 100)
y = sin(x) * x**3
plotdata = ArrayPlotData(x=x, y=y)
plot = Plot(plotdata)
plot.plot(("x", "y"), type="line", color="blue")
plot.title = "sin(x) * x^3"
self.plot = plot
if __name__ == "__main__":
LinePlot().configure_traits()
```
上面這段代碼繪制如下的曲線:

用Chaco的面向對象的方式繪制曲線
這段代碼看起來似乎挺復雜,其實只要掌握了其基本設計思想,就很容易理解了。首先是許多import語句,為了保持應用程序的名字空間整潔以及讓自動語法檢查工具能幫助我們檢查代碼,這些語句只import了需要的對象。
* HasTraits, Instance:這兩個從traits庫中導入,HasTraits是所有擁有Trait屬性的類的父類,我們自己定義的LinePlot類繼承于它。而Instance用來創建一個Trait屬性,此屬性的值為某個指定的類的實例。
* View, Item:從traits.ui庫導入,View用來創建一個生成用戶界面用的視圖,而Item則用來定義視圖中的元素。
* Plot, ArrayPlotData:從chaco庫中導入,Plot本身是一個描述繪圖的類,它的祖先類中有HasTraits,因此它本身也是一個擁有trait屬性的類,ArrayPlotData是用來統一保存繪圖所用的數據的類。也就是說Plot管理繪圖,而ArrayPlotData則用來管理繪圖所用的數據。
* ComponentEditor:從enable庫中導入,用戶界面視圖中使用ComponentEditor來顯示LinePlot類的plot屬性。如果trait屬性為Int、Str或者Float之類的簡單類型的話,系統能夠自動的幫我們選擇對應的GUI元素來顯示它們。但是系統不知道如何顯示Chaco中定義的Plot這樣的的類型,因此我們必須手工指定采用ComponentEditor來顯示Plot。
* linspace, sin:從numpy庫中導入,linspace用來產生一個等差數列,numpy中的sin函數可以自動對數組中的每個元素進行計算。
接下來的代碼部分:
```
class LinePlot(HasTraits):
plot = Instance(Plot)
```
首先定義一個LinePlot繼承于HasTraits,并且它有一個trait屬性plot為Plot類的實例。然后定義視圖:
```
traits_view = View(
Item('plot',editor=ComponentEditor(), show_label=False),
width=500, height=500, resizable=True, title="Chaco Plot")
```
此視圖在LinePlot類中定義,因此在調用configure_traits的時候就不需要指定視圖了。視圖中有一個元素,它將用來顯示名為plot的屬性的內容,視圖中的元素用Item創建。注意這里使用字符串指定視圖元素所對應的屬性。然后通過關鍵字參數editor指定此視圖元素采用ComponentEditor進行顯示。并且不顯示其標簽(show_label=False)。通過View的關鍵字參數width、height、resizable和title分別指定界面的寬、高、是否可改變大小以及其窗口標題欄的文字。
接下來看構造函數,真正的計算在這里:
```
def __init__(self):
super(LinePlot, self).__init__()
x = linspace(-14, 14, 100)
y = sin(x) * x**3
plotdata = ArrayPlotData(x=x, y=y)
```
在構造函數做其它事情之前,一定要記住調用父類的構造函數,這樣HasTraits的功能才能真正在我們的實例中出現。
接下來和腳本繪圖一樣,計算出繪圖所需的x,y坐標的數值數組。然后將這兩個數組存到一個ArrayPlotData對象中。ArrayPlotData和字典(dict)有些類似,它將一個字符串(數組的名字)和數組本身聯系起來。而真正的繪圖對象plot將通過數組的名字在ArrayPlotData中獲得數組的內容。這樣做就在數據和繪圖對象中形成了一個接口界面,修改ArrayPlotData中的數組的值將會立即反應到與此數據相連的繪圖對象,而多個繪圖對象可以共用ArrayPlotData中的同一數組。
接下來創建繪圖對象plot,并且將我們創建的ArrayPlotData實例傳遞給它,此后plot將在此實例中獲取自己繪圖所需的數據。:
```
plot = Plot(plotdata)
```
Plot類將Chaco中提供的許多真正用來繪圖的對象進行包裝,提供了一個統一的接口用來創建和管理這些繪圖對象。在今后深入學習Chaco的過程中我們將通過分析Plot類的實現來了解Chaco庫的設計思想。
接下來調用plot方法在Plot內部創建真正的繪圖對象(一個曲線圖):
```
plot.plot(("x", "y"), type="line", color="blue")
```
注意我們傳遞給plot方法的是數組的名字而不是數組本身,Plot對象會自動通過數組的名字在ArrayPlotData的實例中找到其對應的數組。
然后設置繪圖的標題,并且把繪圖實例賦值給plot屬性:
```
plot.title = "sin(x) * x^3"
self.plot = plot
```
最后是LinePlot對象的實例化和調用configure_trait顯示繪圖窗口:
```
if __name__ == "__main__":
LinePlot().configure_traits()
```
由于沒有給configure_traits傳遞視圖參數,它將在LinePlot實例中尋找視圖的定義,于是它找到traits_view,并且用此視圖來顯示LinePlot實例的trait屬性。于是plot屬性將如traits_view中定義的一樣,用ComponentEditor顯示。
采用和LinePlot類同樣的模式,我們可以繪制更多的曲線圖:
```
from enthought.traits.api import HasTraits, Instance
from enthought.traits.ui.api import View, Item
from enthought.chaco.api import Plot, ArrayPlotData, Legend
from enthought.enable.component_editor import ComponentEditor
from numpy import linspace, sin, cos
class LinePlot(HasTraits):
plot = Instance(Plot)
traits_view = View(
Item('plot',editor=ComponentEditor(), show_label=False),
width=500, height=500, resizable=True, title="Chaco Plot")
def __init__(self):
super(LinePlot, self).__init__()
x = linspace(-14, 14, 100)
y1 = sin(x) * x**3
y2 = cos(x) * x**3
plotdata = ArrayPlotData(x=x, y1=y1, y2=y2)
plot = Plot(plotdata)
plot.plot(("x", "y1"), type="line", color="blue", name="sin(x) * x**3")
plot.plot(("x", "y2"), type="line", color="red", name="cos(x) * x**3")
plot.plot(("x", "y2"), type="scatter", color="red", marker = "circle",
marker_size = 2, name="cos(x) * x**3 points")
plot.title = "Multiple Curves"
self.plot = plot
legend = Legend(padding=10, align="ur")
legend.plots = plot.plots
plot.overlays.append(legend)
if __name__ == "__main__":
lineplot = LinePlot()
lineplot.configure_traits()
```

繪制多條曲線并且添加圖示
在這個程序中,我們調用了3次plot.plot方法,其中兩次的type="line"繪制曲線,一次是type="scatter"繪制坐標點。繪制坐標點時通過marker和marker_size配置點的形狀和大小。并且我們為每個plot傳遞了一個name參數。
為了繪制圖示(legend),從chaco.api中載入Legend之后,使用如下三行代碼為坐標圖添加圖示::
```
legend = Legend(component=plot, padding=10, align="ur")
legend.plots = plot.plots
plot.overlays.append(legend)
```
其中第一行創建一個Legend對象,并且設置padding和align兩個屬性,padding設置其內容與邊框之間的距離,align設置其在容器中的位置: upper right。
為了讓legend對象知道要顯示什么曲線的圖示,我們需要把曲線對象傳遞給它。三次調用plot繪制的曲線可以通過plot對象plots屬性得到,在iPython中運行完上面的程序之后,輸入lineplot.plot.plots查看plots屬性的值:
```
>>> lineplot.plot.plots
{'cos(x) * x**3 points': [<enthought.chaco.scatterplot.ScatterPlot object at 0x1436B4B0>],
'cos(x) * x**3': [<enthought.chaco.lineplot.LinePlot object at 0x1436B120>],
'sin(x) * x**3': [<enthought.chaco.lineplot.LinePlot object at 0x143140F0>]}
```
我們將plot.plots傳遞給legend.plots,于是legend就知道要顯示哪些曲線的圖示了。
最后我們將legend對象添加到plot.overlays中。整個繪圖區域分為許多層,每一層放置不同的繪圖元素,overlays是最上面的一層,其中放置在屏幕坐標系中的繪圖元素。plot的overlays屬性為一個TraitListObject類的對象,它繼承于list類,具有list的所有能力,因此可以用append方法將繪圖元素添加進overlays層。
### 容器(Container)概述
在Chaco的實現中,Plot類繼承于DataView類,而DataView類繼承于OverlayPlotContainer類,因此Plot本身就是一個容器。可以把OverlayPlotContainer想象成多張透明繪圖紙,我們在多張紙上繪圖,然后通過OverlayPlotContainer容器將這些紙張重疊起來,就組成了最終所繪制的圖。
除了OverlayPlotContainer容器之外,Chaco還提供了下面幾種容器:
* HPlotContainer:內容橫向排列的容器
* VPlotContainer:內容豎向排列的容器
* GridPlotContainer:內容按照網格排列的容器
下面讓我們來看一個用HPlotContainer的例子:
```
from enthought.traits.api import HasTraits, Instance
from enthought.traits.ui.api import View, Item
from enthought.chaco.api import HPlotContainer, ArrayPlotData, Plot
from enthought.enable.component_editor import ComponentEditor
from numpy import linspace, sin
class ContainerExample(HasTraits):
plot = Instance(HPlotContainer)
traits_view = View(Item('plot', editor=ComponentEditor(), show_label=False),
width=1000, height=600, resizable=True, title="Chaco Plot")
def __init__(self):
super(ContainerExample, self).__init__()
x = linspace(-14, 14, 100)
y = sin(x) * x**3
plotdata = ArrayPlotData(x=x, y=y)
scatter = Plot(plotdata)
scatter.plot(("x", "y"), type="scatter", color="blue")
line = Plot(plotdata)
line.plot(("x", "y"), type="line", color="blue")
container = HPlotContainer(scatter, line)
self.plot = container
if __name__ == "__main__":
ContainerExample().configure_traits()
```

用容器繪制兩個子圖
這個程序和前面的例子類似,所不同的是:ContainerExample的plot屬性不是Plot的實例,而是改為HPlotContainer的實例。這樣它就可以橫向排列多個圖了。
在\_\_init\_\_函數中,用創建兩個Plot對象scatter和line,然后創建HPlotContainer對象container,并把兩個plot對象傳遞給它,這樣container就知道要水平排列哪些圖了。
每個容器都有很多屬性可以設置,如果我們在\_\_init\_\_函數最后添加如下幾行程序::
```
scatter.padding_right = 0
line.padding_left = 0
line.y_axis.orientation = "right"
```

修改兩個容器的左右padding值,使它們緊靠在一起
我們看到兩個Plot之間的間距沒有了,他是通過設置容器左圖(scatter)的右邊距為0,右圖(line)的左邊距為0來實現的。 并且將右圖的Y軸坐標設置到了右邊。
### 編輯繪圖屬性
到目前為止所繪制的圖都是靜態的,一旦創建出來就沒有辦法改變其各種顯示屬性了。Chaco庫是建立在Traits庫基礎之上的,我們看到的各種各樣的對象的屬性都是trait屬性,這樣我們可以使用Traits和TraitsUI的強大功能設置對象的各種屬性,下面是一個完整的例子:
```
from enthought.traits.api import HasTraits, Instance, Int, Color
from enthought.traits.ui.api import View, Group, Item
from enthought.enable.component_editor import ComponentEditor
from enthought.chaco.api import marker_trait, Plot, ArrayPlotData
from numpy import linspace, sin
class ScatterPlotTraits(HasTraits):
plot = Instance(Plot)
color = Color("blue")
marker = marker_trait
marker_size = Int(4)
traits_view = View(
Group(Item('color', label="Color"),
Item('marker', label="Marker"),
Item('marker_size', label="Size"),
Item('plot', editor=ComponentEditor(), show_label=False),
orientation = "vertical"),
width=800, height=600, resizable=True, title="Chaco Plot")
def __init__(self):
super(ScatterPlotTraits, self).__init__()
x = linspace(-14, 14, 100)
y = sin(x) * x**3
plotdata = ArrayPlotData(x = x, y = y)
plot = Plot(plotdata)
self.renderer = plot.plot(("x", "y"), type="scatter", color="blue")[0]
self.plot = plot
def _color_changed(self):
self.renderer.color = self.color
def _marker_changed(self):
self.renderer.marker = self.marker
def _marker_size_changed(self):
self.renderer.marker_size = self.marker_size
if __name__ == "__main__":
ScatterPlotTraits().configure_traits()
```
為了觀察trait控件是如何動態地修改繪圖的的各個屬性,我用flash錄制下對控件的操作,請點擊下圖下方的播放按鈕觀看動畫。
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="497" height="357" codebase="http://active.macromedia.com/flash5/cabs/swflash.cab#version=7,0,0,0"><param name="movie" value="img/chaco_intro_07.swf"> <param name="play" value="true"> <param name="loop" value="false"> <param name="wmode" value="transparent"> <param name="quality" value="high"> <embed src="img/chaco_intro_07.swf" width="497" height="357" quality="high" loop="false" wmode="transparent" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash"> </object> 
通過觀察上面的這個動畫,我們發現對顏色、點型和點的大小等屬性的修改立即響應到繪圖的屬性上。下面我們來分析一下這個程序:
ScatterPlotTraits類定義了4個trait屬性,其中一個是我們已經熟知的plot屬性,其余的三個分別為color,marker和marker_size。color是一個Color屬性,marker_size是一個Int屬性,marker比較特別,它是在Chaco的scatter_makers.py中定義的一個Trait屬性,采用字典創建,將一個描述點型的字符串映射到點型對應的類,這樣我們通過界面上的下拉選擇框選擇某個點型名稱時,在程序內部實際上選擇的是其對應的類。
接下來第14行在ScatterPlotTraits類內部定義了一個視圖對象traits_view。它創建4個Item分別與4個trait屬性對應。為了響應trait屬性值的改變事件,我們為類添加了3個事件處理函數_color_changed,_marker_changed和_marker_size_changed。這個三個處理函數通過其名字和trait屬性對應,即名為foo的trait屬性的缺省事件處理函數名為_foo_changed。值得注意的是這三個處理函數是和trait屬性相對應的,而不是界面上的控件。當用戶更改了控件的內容之后,此更改自動反映為trait屬性值的更改,當trait屬性的值更改時,事件處理函數將被運行。這樣,當處理函數運行的時候,trait屬性的值已經是最新的界面上所顯示的值了。因此只需要將此值賦值給曲線繪圖對象(render)的對應的屬性即可。
那么render對象是什么?我們看到它是plot.plot函數的返回值,這個返回值就是圖中所畫的那條曲線。前面我們演示了通過plot.plots獲得過plot中所有的繪圖對象。因此不用render保存此返回值,也可以用plot.plots.values()[0],或者plot.plots["plot0"]來獲取這個繪圖對象。"plot0"是系統自動為我們的曲線所起的名字。
- 用Python做科學計算
- 軟件包的安裝和介紹
- NumPy-快速處理數據
- SciPy-數值計算庫
- matplotlib-繪制精美的圖表
- Traits-為Python添加類型定義
- TraitsUI-輕松制作用戶界面
- Chaco-交互式圖表
- TVTK-三維可視化數據
- Mayavi-更方便的可視化
- Visual-制作3D演示動畫
- OpenCV-圖像處理和計算機視覺
- Traits使用手冊
- 定義Traits
- Trait事件處理
- 設計自己的Trait編輯器
- Visual使用手冊
- 場景窗口
- 聲音的輸入輸出
- 數字信號系統
- FFT演示程序
- 頻域信號處理
- Ctypes和NumPy
- 自適應濾波器和NLMS模擬
- 單擺和雙擺模擬
- 分形與混沌
- 關于本書的編寫
- 最近更新
- 源程序集
- 三角波的FFT演示
- 在traitsUI中使用的matplotlib控件
- CSV文件數據圖形化工具
- NLMS算法的模擬測試
- 三維標量場觀察器
- 頻譜泄漏和hann窗
- FFT卷積的速度比較
- 二次均衡器設計
- 單擺擺動周期的計算
- 雙擺系統的動畫模擬
- 繪制Mandelbrot集合
- 迭代函數系統的分形
- 繪制L-System的分形圖