Lisp-Stat翻译 —— 第九章 统计绘图窗体
2019獨角獸企業重金招聘Python工程師標準>>>
第九章 統計繪圖窗體
????除了前幾章略述的繪圖窗體原型提供的基本繪圖工具之外,Lisp-Stat里的統計繪圖還需要用來管理數據和將那些數據轉換成屏幕上的圖形的工具集。這些工具由繪圖原型graph-proto提供。更多的專業繪圖工具,比如直方圖和散點圖矩陣,它們都是基于繼承自graph-proto的原型。本章的第一節描述了繪圖原型,第二節略述了更加專用的原型,下一章將展示那些描述如何從這些原型來開發新的繪圖工具類型的例子。
9.1 繪圖原型
????graph-proto原型實現了一個散點圖,該散點圖用來表示在m維空間中的點和線的二維視圖。該視圖是這樣構造的,首先對數據進行中心化和尺度化,然后使用線性變換,比如旋轉變換,最后產生該變換的圖形里的維度的兩個維度的一個散點圖。該原型的:resize和:redraw方法可以保證:當窗體出現或改變大小時,圖形可以得到合適的重畫。鼠標點擊和移動方法支持以下語法:即在第2.5節描述的在選擇模式和刷模式里使用的繪圖方式。該原型也提供了一個基本的菜單用來與圖形交互。
? ? 為了給出該原型提供的機制的詳細的說明,本節使用一個圖形來檢測5.6.2節引入的stack loss數據。
9.1.1 構造一個新的圖形
????graph-proto原型繼承自graph-window-proto原型,graph-proto的:isnew方法需要一個參數,即表示將被視圖化的那個空間的維度的整型值m。stack loss數據由4個變量組成,氣流(Air)、溫度(Temp)、濃度(Conc)和氨損耗(Loss)。視圖化這些數據的圖形可以這樣構造:
> (setf w (send graph-proto :new 4)) #<Object: 141e7e8, prototype = GRAPH-PROTO> 圖形里的變量數目可以使用:num-variables消息來獲取: > (send w :num-variables) 4 但是,一個圖形創建,該值就不能改變。? ? 圖形包含為每個維度描述的標簽字符串,這些字符串可以使用:variable-label消息來設置和獲取。初始情況下,這些字符串是空值:
> (send w :variable-label 0) "" 可以通過使用一個字符串作為其第二個參數的方式來改變它的值: > (send w :variable-label 0 "Air") "Air" 這個消息的方法是矢量化的。表示三個維度的標簽可以通過下式指定: > (send w :variable-label '(1 2 3) (list "Temp." "Conc" "Loss")) ("Temp." "Conc" "Loss") 現在我們可以獲得這4個變量了: > (send w :variable-label '(0 1 2 3)) ("Air" "Temp." "Conc" "Loss") ? ? :graph-proto原型的:isnew方法可以接受graph-window-proto原型的:isnew方法能夠接受的所有關鍵字參數。此外,:variable-label關鍵字可用來指定一個m維度的初始化變量標簽字符串。還有一個關鍵字:scale-type將在接下來的9.1.3節里描述。9.1.2 增加數據可坐標軸
點數據
????一個圖形里可以包含兩類數據:點數據和線數據。點由m維空間里的坐標和一些附加信息組成,這些附加信息比如像用來繪制點的顏色和符號。初始情況下,圖形里不包含點數據:
> (send w :num-points) 0 點數據可以使用:add-points消息來添加,該消息的方法需要一個參數:一個m維的列表,表示將要添加的點的坐標值。下邊的表達式將stack loss數據添加到圖形里: > (def air '(80 80 75 62 62 62 62 62 58 58 58 58 58 58 50 50 50 50 50 56 70)) AIR > (def temp '(27 27 25 24 22 23 24 24 23 18 18 17 18 19 18 18 19 19 29 29 29)) TEMP > (def conc '(89 88 90 87 87 87 93 93 87 80 89 88 82 93 89 86 72 79 80 82 91)) CONC > (def loss '(42 37 37 28 18 18 19 20 15 14 14 13 11 12 8 7 8 8 9 15 15)) LOSS > (send w :add-points (list air temp conc loss)) NIL 數據集里有21個點: > (send w :num-points) 21 :add-points方法會在屏幕上繪制了新的點,除非提供了值為nil的:draw關鍵字。該方法也允許使用:point-label關鍵字的標簽字符串列表。? ? 盡管現在該圖形包含這些數據,在它的窗體上不會顯示任何點。原因是這些數據點在初始情況下,當做在每一個變量的單位間隔組成的一個數據范圍。為了調整圖形視圖化的范圍以適應這些數據,你可以向圖形發送:adjust-to-data消息,你可以使用下邊的表達式發送該消息:
> (send w :adjust-to-data) NIL 或者也可以從圖形的菜單里選擇Rescale Plot菜單項。這個消息的方法將調整圖形視圖化的數據范圍以精確地適應對應的數據的范圍跨度。發送該消息之后,圖形應該顯示了一個散點圖,該散點圖表示出數據集中前兩個變量。結果圖形如圖9.1所示。? ? 當向一個圖形里加入新點的時候,每一個點都會分給一個默認的符號、顏色和標簽,對于第一個點:
> (send w :point-symbol 0) DISK > (send w :point-color 0) NIL > (send w :point-label 0) "0"
圖9.1 stack loss數據集中氣流與溫度變量圖示
????默認符號是一個叫disk的符號。默認顏色是nil,意思是改點使用當前繪圖窗體的顏色來繪制。默認標簽是該點索引的字符串形式。通過向這些消息傳遞第二個參數來為這些屬性指定新值。符號應該取自plot-symbol-symbols函數返回的列表。顏色值應該是nil或者由color-symbols返回的列表的一個值。:point-symbol, :point-color和:point-label消息的方法是矢量化的,因此將所有21個點的符號設置到diamond變量里。這三個消息都不會引起圖形重畫;為了看到屏幕上改變的影響,你不得不向圖形發送一個重畫命令。
? ? 每個點還有一個狀態值,該值可以使用:point-stat消息來設置和獲取。該狀態值可以是invisible, normal, hilited和selected這四個符號中的一個。點數據狀態用做鏈接機制的一部分,它將在9.1.5節詳細描述。
? ? :point-coordinate消息可以用來為某一特定點獲取和設置單一變量坐標的值。該消息的方法需要兩個參數:變量的索引和點數據的索引。因此,第一次觀察到的氣流、溫度、濃度和氨損耗的值是:
> (send w :point-coordinate 0 0) 80.0 > (send w :point-coordinate 1 0) 27.0 > (send w :point-coordinate 2 0) 89.0 > (send w :point-coordinate 3 0) 42.0 :point-coordinate消息的方法也是矢量化的。新的坐標值可以以第三個參數的形式來指定。在強調一次,提供新值的時候,方法不會重畫圖形。 練習 9.1
略。
線數據
????線數據表示從處在m維空間里叫做linestart的點位開始,每個起點都包含額外的信息(比如在繪制線段時使用的寬度和線型信息),還有用來作為線段終點的另一個linestart的索引,nil的下標表示該linestart僅用作在其它地方凱斯的線段的終點。環形定義是是運行的:這不會帶來什么問題,因為繪圖路徑斤通過linestart集合一次。
? ? 當創建一個圖形的時候,它沒有linestart:
> (send w :num-lines) 0? ? stack loss數據事實上是隨著時間收集到的。通過繪制一條從第一個觀察點到第二個觀察點、從第二個到第三個觀察點等等的直線的方式,來表示時間關系,這可能是很有用的。:add-line消息可以增加這樣一個線段序列,它的方法需要一個參數,一個針對linestart的坐標的m維列表的列表。因此,下式將針對stack loss數據向我們的圖形里添加連在一起的線段序列。這些線數據有助于說明,在數據集的前10個觀測量里,氣流和溫度是下降的。
> (send w :add-lines (list air temp conc loss)) NIL? ? :add-lines消息的方法還允許使用:type關鍵字來提供線型,這時直線會會自導圖形上,除非使用了值為nil的:draw關鍵字。
? ? 每一個linestart都有一個寬度、類型和顏色,用來從一個linestart繪制到下一個linestart。對于第一個linestart,對應第一個數據點:
> (send w :linestart-width 0) 1 > (send w :linestart-type 0) SOLID > (send w :linestart-color 0) NIL該方法也是矢量化的,通過提供一個新值作為第二個參數它可以被用來改變linestart屬性的值,數值的改變不會引起任何繪圖行為的發生。
? ? 每個linestart都包含序列里下一個linestart的索引,在繪圖中用來作為一個線段的終點,:add-lines方法將這些linestart連接成序列,因此:
> (send w :linestart-next 0) 1 > (send w :linestart-next 1) 2 序列里的最后一個linestart沒有關于下一個linestart的索引: > (send w :linestart-next 20) NIL 可以通過在線段的開始處為linestart設置下一個線段的值為nil,來移除一個線段。例如: > (send w :linestart-next 7 nil) NIL 上式移除了點號索引為7和8的點之間的線段,為了使該改變可見,你可以向圖形發送:redraw消息。? ? 就想點一樣,你也可以獲取和改變linestart的坐標。第一個linestart的前兩個坐標值可以這樣給出(對應氣流和溫度變量):
> (send w :linestart-coordinate 0 0) 80.0 > (send w :linestart-coordinate 1 0) 27.0 :linestart-coordinate消息的方法是矢量化的,可以用來改變坐標的值。如果坐標改變了,圖形不會重畫。練習 9.2
略。
坐標軸和當前變量
????由graph-proto原型實現的散點圖,使用:x-axis和:y-axis消息,可以用來表示x和y坐標軸。不提供參數,這些消息返回當前坐標軸的狀態。例如:
> (send w :x-axis) (NIL NIL 0) 列表的三個元素表示該坐標軸是否正處于顯示狀態,是否有標簽,它使用的刻度的數目。發送一個值為t的參數將重畫帶坐標軸的圖形。默認地,不使用標簽,使用4為刻度: > (send w :x-axis t) (T NIL 4) 你可以使用可選的第二、三個參數,來指定一個替代的選項。:y-axis方法是相同的。當坐標軸狀態改變時,這兩個方法都向圖形發送:resize和:redraw消息,除非使用了值為nil的:draw關鍵字。為了使用該關鍵字,你需要給出所有這3個可選參數。在加入x、y坐標軸之后,我們的圖形如圖9.2所示。
圖9.2 stack loss數據集中氣流和溫度變量的x坐標與y坐標圖形,連續觀測量由直線相連
? ? :adjust-to-data方法會將被一個圖形視圖化的數據范圍設置到數據范圍,該方法通常不會產生一個效果很好的坐標軸標簽。你也可以使用:range消息為每個變量獲取和改變數據范圍。對于氣流和溫度變量:
> (send w :range 0) (50.0 80.0) > (send w :range 1) (17.0 29.0) 結果列表的元素表示該數據范圍的高低邊界。為了改變一個變量的數據范圍,你需要使用兩個附加值,新的高低邊界。 > (send w :range 1 15 30) (15.0 30.0) 上式將溫度變量的數據范圍設置到區間[15, 30]之間。當數據范圍改變的時候,圖形將重畫,除非使用了值為nil的:draw關鍵字。:range消息的方法是矢量化的。? ? get-nice-range函數可用來幫助找到數據范圍和刻度的一個好的組合。該函數帶3個參數,區間的高、低邊界端點和刻度值的整型數值。它返回一個3值列表,表示包含原始區間的區間端點,和接近指定數值得刻度的數值。新值應該產生合理的坐標軸。例如,對于大約有4刻度值的溫度變量的數據范圍來說:
> (get-nice-range 17 27 4) (16.0 28.0 7) 對于這個變量推薦的設置范圍是[16, 28],表達式如下: > (send w :range 1 16 28) (16.0 28.0) 將y軸設置為使用7作為刻度值: > (send w :y-axis t t 7) (T T 7) 結果坐標軸標記為16, 18, 20, ..., 28.? ? 到目前為止,我們的圖形只顯示了我們的4變量數據集的前兩個。:current-variables消息可用來設置獲取構造該圖形的兩個當前變量。默認的情況是顯示前兩個變量:
> (send w :current-variables) (0 1) 下邊的表達式命令圖形切換到使用其它兩個變量。該方法向圖像發送:redraw消息,除非使用了值為nil的:draw關鍵字。練習9.3
略。
清除繪圖數據
????圖形里當前的數據可以使用:clear, :clear-points和:clear-lines消息來清除。:clear-points消息移除內部數據,并將點數設為0,該消息會重畫圖形,除非使用了值為nil的:draw關鍵字。用來清除linestart數據的:clear-lines消息是相同的。:clear消息會將點數據和線數據一并移除。
? ? 這些消息對于動畫是非常有用的,即顯示在圖形里的快速變化的數據動畫。鑒于之后第9.1.6節里描述的重畫方法使用了雙緩沖技術,該技術會產生一個平滑的動畫效果。
9.1.3 縮放與變換
????graph-proto原型最主要的特征就是其允許對數據進行線性變換的能力,尤其是旋轉變換。變換過程可以分解為兩個階段。第一階段由數據的中心化和縮放組成;第二階段由對已經中心化和縮放過的詩句使用一個變化矩陣組成。然后,將顯示一個散點圖,即該變換的結果的坐標中的兩個數據。
縮放和中心化
????縮放和中心化階段引入了一個新的坐標系統,即縮放坐標系。作為對比,原來指定數據的那個坐標系叫做真實坐標系。可以使用人造的縮放體系,但是有兩個叫定縮放和變縮放的標準的縮放體系對大多數情況來說是足夠用了。這些縮放類型可以通過使用:adjust-to-data方法和:scale-type方法來實現。在處理之前,我們可以私用下邊兩個表達式將坐標軸從圖形中移除:
> (send w :x-axis nil) (NIL NIL 4) > (send w :y-axis nil) (NIL NIL 4) 對于變換后的數據來說,坐標軸是沒有意義的。????當構造一個新的圖形時,它的縮放類型是nil:
> (send w :scale-type) NIL 這意味著沒有進行中心化和縮放,每個維度內的視圖范圍設置成真實坐標系的對應維度里的數據范圍。縮放坐標系里的數據范圍可以通過使用:scale-range消息來獲取: > (send w :range 0) (50.0 80.0)> (send w :scaled-range 0) (50.0 80.0) 因為在初始情況下沒有進行縮放,縮放過的和真實的數據范圍是相等的。? ? 為了檢測這個新的縮放類型,我們可以通過將我們的圖形的縮放類型設置為variable符號來檢測,表達式如下:
> (send w :scale-type 'variable) VARIABLE :scale-type消息的方法將發送:adjust-to-data消息,并重畫圖形,除非使用了值為nil的:draw關鍵字。對于一個使用變化的縮放的圖形,:adjust-to-data方法為每個變量在數據范圍的中心位置進行中心化,在將每個中心化后的變量縮放到[-1, 1]區間上,最后將縮放后的數據范圍設置到[-sqrt(m), sqrt(m)]區間。這將確保數據進行旋轉之后都在縮放范圍之內。在將縮放類型設置成variable之后,圖形的第一個維度的數據范圍和縮放后的數據范圍如下: > (send w :range 0) (34.99999999999999 95.0) > (send w :scaled-range 0) (-2.0 2.0) ? ? 當變量在可比較的尺度內無法量度的時候,可變縮放是合適的。如果變量在可比較的尺度上是可以量度的,通過在每個維度上使用相同的比例系數,我們可能想要在數據內保持一定的角度,這在固定縮放策略里是一體的。如果尺度類型設置為符號fixed,那么:adjust-to-data方法將在數據區間的中間進行數據的中心化,并為每個維度選擇縮放系數為1,然后為每個變量設置其縮放后的區間,該數值為數據中心化的數據最大范圍的sqrt(m)倍。? ? 如果可變縮放和固定縮放體系都不能勝任你的需求,你可以通過定義一個新的:adjudt-to-data方法來定義你自己的縮放體系。該方法可以使用:scale-range方法來設置一個新的縮放范圍,再使用:center和:scale消息來設置或者獲取中心和縮放系數。對于我們的圖形,因為是可變縮放,第一個變量的縮放系數和中心是:
> (send w :scale 0) 15.0 > (send w :center 0) 65.0 當使用:scaled-range消息設置縮放范圍時,原始坐標系內的數據范圍可會調整以適應該變化。:scale, :center和:scaled-range等消息是矢量化的。當使用該方法設置新值時,將會重畫圖形,除非使用了值為nil的:draw關鍵字。? ? 對于定義自定義的縮放體系來說另一個重要的消息是:visible-range消息。該消息的方法接受維度索引作為參數,返回在那個維度里所有可見的點數據和linestart的數據范圍。例如,對于圖形里的第一維度:
> (send w :visible-range 0) (50.0 80.0) ? ? 一個圖形的初始縮放類型可以這樣指定——在graph-proto原型的:isnew方法里指定:scale-type關鍵字。 練習 9.4
略。
變換
????在選定一個縮放體系之后,比如說可變縮放,現在我們可以使用變換了。初始情況下,是沒有變換的:
> (send w :transformation) NIL 我們可以通過向:transformation消息傳遞一個參數,對數據中的每一個點和linestart使用一個轉換矩陣。浙江轉換每一個數據點,這些數據點被視為一個列向量,通過在該列向量左側乘一個變換矩陣完成變換。除非使用了值為nil的:draw關鍵字,否則在給定一個新的變換時:transformation方法將重畫該圖形。舉個例子,如果這樣給定當前變量: > (send w :current-variables) (0 1)那么我們可以使用下邊這個表達式使用一個旋轉變換,該變換使用濃度變量代替氣流變量,氨損失代替溫度。
> (send w :transformation'#2A((0 0 -1 0)(0 0 0 -1)(1 0 0 0)(0 1 0 0))) #2A((0 0 -1 0) (0 0 0 -1) (1 0 0 0) (0 1 0 0)) 下式將返回一個未變換狀態的圖形: > (send w :transformation nil) NIL ? ? 對于變換的使用,一些其它方法也是可用的。為了更容易理解原始圖形中的溫度相對于氣流的點數據,與變換后圖形中的氨損失相對于濃度的點數據,這兩種點數據之間的對應關系,從第一個圖形平滑地旋轉到第二個圖形時有用的。這樣的從第一個散點圖到另一個散點圖的旋轉叫做圖形差值。通過將變換矩陣設置成一系列不同的中間旋轉變換,你可以執行這個旋轉。另一個方法就是使用:apply-transformation消息。該消息的方法接受一個增加的變換矩陣,然后通過使用該增加的變換左乘當前變換來構造一個新的圖形變換。默認地,該方法重畫圖形。因為該重畫使用緩沖區,下式將花費10個步驟將原始圖形平滑地旋轉成為變換后的圖形: > (let* ((c (cos (/ pi 20)))(s (sin (/ pi 20)))(m (+ (* c (identity-matrix 4))(* s '#2A((0 0 -1 0)(0 0 0 -1)(1 0 0 0)(0 1 0 0))))))(dotimes (i 10) (send w :apply-transformation m))) NIL 旋轉后的圖形數據? ? :rotate-2消息可用來使用一個由兩個變量索引定義的平面的二維旋轉變換。該消息需要一個旋轉角度作為第三個參數。例如,下式將當前已經變換后的數據的0維到2維的數據旋轉pi/20角度。通過在你的圖形里將變換重設為nil,我們可以再表達式里使用:rotate-2消息,目的是將溫度對氣流圖形平滑地變換為氨損失對濃度圖形。每步都由兩個二維旋轉組成。圖形僅在第二次旋轉之后重畫。
? ? 可以對:transformation和:apply-transformation方法傳遞一個維度k<m的方陣作為參數。這種情況下改變換將作用到當前數據空間的前k維上。這對于一些原型來說是很有用的,比如說繼承自graph-proto原型的histogram原型。還可以向:apply-transformation消息里傳遞一個與矩陣參數相同維度的序列,這里的矩陣參數是帶;basis關鍵字的。該序列應該包含nil和非nil元素。該變換系統會忽略基里值為nil的對應位置處旋轉矩陣的元素。該特性允許低維旋轉變換可以在高維圖形里有效地使用。
練習 9.5
略。
獲取轉換后的數據
????graph-proto原型提供了用來獲取轉換后的點數據和linestart坐標數據當前值的消息。例如,第一個轉換后的點數據和linestart數據可以這樣給出:
> (send w :point-transformed-coordinate 0 0) -0.6190476190476191 > (send w :point-transformed-coordinate 1 0) -1.0000000000000002 和 > (send w :linestart-transformed-coordinate 0 0) -0.6190476190476191 > (send w :linestart-transformed-coordinate 1 0) -1.00000000000000029.1.4 鼠標事件和鼠標模式
????graph-proto原型的:do-click和:do-motion方法是用來支持這樣的語法的——即使圖形可以在不同的鼠標模式里。新的模式可以添加進來,你可以通過使用一個由Mouse Mode菜單項產生的對話框,或者向圖形發送一個消息,在幾個可用模式之間進行切換。鼠標模式可以使用一個Lisp符號來區別。每個鼠標模式都有一個標簽字符串,用在用來切換模式的那個對話框里,還有一個光標用來提供當前模式、自身點擊和行為動作的可視化暗示。為了自定義一個圖形和一個新的原型,通常不需要覆蓋:do-click和:do-motion方法。添加新的鼠標模式或者覆蓋由標準鼠標模式使用的消息是足夠的。
增加新的鼠標模式
????初始情況下,一個圖形由兩個鼠標模式,選擇模式和刷模式。:mouse-modes返回區別可用模式的符號列表:
> (send w :mouse-modes) (SELECTING BRUSHING) ? ? :add-mouse-mode消息添加一個新的鼠標模式,或者重定義一個現有的模式。該消息需要一個參數,即鼠標模式符號,還接受一些關鍵字參數。這些關鍵字包括用來指定標簽字符串的:title,用來指定光標符號的:cusor。:click和:motion關鍵字可以用來指定消息選擇器,當圖形在新的模式里的時候該消息選擇器用來處理點擊和動作事件。? ? 舉個例子,我們可以向圖形里添加一個新的模式,該圖形僅展示在窗體上點擊的鼠標的坐標。該模式可以這樣添加:
> (send w :add-mouse-mode 'show-coordinates:title "Show Coordinates":click :do-show-coordinates:cursor 'finger) SHOW-COORDINATES 添加了這個模式之后,該圖形就有3個可用的模式了: > (send w :mouse-modes) (SELECTING BRUSHING SHOW-COORDINATES) 當圖形處于show-coordinate模式的時候,光標設成了finger(手指形)。當在該模式里點擊鼠標時,圖形將向:do-click消息發送:do-show-coordinates消息,該消息帶4個參數。因為沒有指定動作,動作事件將被忽略。? ? 在使用該新模式之前,我們需要定義:do-show-coordinates方法。為了避免重畫圖形,我們可以使用XOR繪圖,在按鈕按下的時候繪制一個表示點擊位置的字符串。當按鈕釋放的時候,我們可以再次繪制字符串以移除字符串映像:
> (defmeth w :do-show-coordinates (x y m1 m2)(let ((s (format nil "~s" (list x y)))(mode (send self :draw-mode)))(send self :draw-mode 'xor)(send self :draw-string s x y)(send self :while-button-down #'(lambda (x y) nil))(send self :draw-string s x y)(send self :draw-mode mode))) :DO-SHOW-COORDINATES :while-button-down動作僅僅是等待鼠標按鈕彈起。? ? 現在我們使用圖形菜單提供的對話框切換到新的模式,或者向圖形發送一個參數為模型符號的:mouse-mode消息,向圖形發送一個不帶參數的:mouse-mode消息將返回當前模式的符號:
> (send w :mouse-mode 'show-coordinates) SHOW-COORDINATES > (send w :mouse-mode) SHOW-COORDINATES? ? 我們可以展示縮放后的坐標系統里的當前變量的當前坐標,而不是展示鼠標點擊處的圖上坐標。:canvas-to-scaled消息帶兩個整型參數——代表畫布上的一個點,返回兩個實數——代表縮放后坐標系統的兩個當前變量的對應的點:
> (send w :canvas-to-scaled 100 150) (-0.4 -0.3904382470119522) :canvas-to-real消息試圖轉換回是坐標系,但是它僅在圖形變換為nil時才合適地工作。為了允許你在顯示畫布、縮放和真實坐標系,我們可以定義一個:do-show-coordinates消息來檢查修飾符,在點擊時同時按下shift時顯示實坐標,在alt使用時顯示縮放坐標。如果沒有按下這些擴展修飾符,它僅顯示圖上坐標: > (defmeth w :do-show-coordinates (x y m1 m2)(let* ((xy (cond (m1 (send self :canvas-to-real x y))(m2 (send self :canvas-to-scaled x y))(t (list x y))))(s (format nil "~s" xy))(mode (send self :draw-mode)))(send self :draw-mode 'xor)(send self :draw-string s x y)(send self :while-button-down #'(lambda (x y) nil))(send self :draw-string s x y)(send self :draw-mode mode))) :DO-SHOW-COORDINATES ? ? :scaled-to-canvas和:real-to-canvas消息帶兩個實數參數,表示縮放坐標或實數坐標系中當前變量的坐標,然后返回響應畫布坐標的列表。? ? 為了助于開發新的鼠標模式,一些其它的消息也是可用的。為了說明這些消息中的一些,我們可以構造一些鼠標模式,用于當鼠標在某點附近點擊和按下的時候通過在附近放置一個標簽來標示該點。對于使所有高亮的或選中的點顯示它們的標簽這一標準選項,我們提供的方法是對其的替代物。首先,我們可以通過使用以下表達式移除定義的模式:
(send w :delete-mouse-mode 'show-coordinates) 該操作不會移除與模式有聯系的鼠標點擊或者動作消息。該消息需要每個圖形至少有一個鼠標模式。其次,為了識別各點,我們可以制定一個新模式: (send w :add-mouse-mode 'identify:title "Identify":click :do-identify:cursor 'finger) 為了確定一次點擊足夠接近一個點,新模式的點擊方法需要設置一個容差值。每個圖形都保持一個容差值范圍,該范圍可以通過:click-range消息設置或獲取。該消息返回一個兩個整型數的列表,用像素表示的該容差值的寬度和高度。默認范圍如下: > (send w :click-range) (4 4) 該范圍被標準selection模式采用,當鼠標點擊的時候用來確定選擇的點。可以通過發送帶兩個整型參數的消息來指定一個新的點擊范圍,這兩個整型參數是新范圍的寬度和高度。? ? 使用范圍和鼠標點擊位置的坐標,我們可以下個圖形發送:points-in-rect消息來獲取落在指定矩形區域內的影響的所有點的索引列表。例如:
(send w :points-in-rect 100 150 10 15) 然后,返回位于左上角坐標為(100, 150),寬10像素,高15像素的矩形區域內的影像的所有點的索引列表。如果該該區域內沒有點數據,返回nil。? ? 使用這兩個詳細,我們的:do-identify消息可以定義成下面的樣子:
> (defmeth w :do-identify (x y m1 m2)(let* ((cr (send self :click-range))(p (first (send self :points-in-rect(- x (round (/ (first cr) 2)))(- y (round (/ (second cr) 2)))(first cr)(second cr)))))(if p(let ((mode (send self :draw-mode))(label (send self :point-label p)))(send self :draw-mode 'xor)(send self :draw-string label x y)(send self :while-button-down #'(lambda (x y) nil))(send self :draw-string label x y)(send self :draw-mode mode))))):points-in-rect消息使用中心點為鼠標點擊處的矩形,這里的點擊位置的寬度和高度與點擊范圍內指定的值相等。函數first返回由:points-in-rect消息返回的列表的第一個索引,如果列表為空則返回nil。因此,如果在點擊處附近的發現一個點,變量p是一個整數,如果沒有這樣的一個點,變量p就是nil。
? ? 為了說明另一個消息:drag-point,我們可以通過在圖形里移動數據點來定義一個鼠標模式,用來編輯我們的數據。我們可以這樣設置新的模式:
(send w :add-mouse-mode 'point-moving:title "Point Moving":cursor 'hand:click :do-point-moving) 在圖形里,:drag-point消息用來拖動一個點,該消息帶兩個參數,鼠標點擊的圖上坐標和檢測某點是否接近點擊位置。如果存在這樣的點,將在窗體周圍拖拽出一個灰色的長方形直到鼠標按鍵釋放。在此期間,該點的坐標將改變同時圖形重畫,除非使用了一個值為nil的:draw關鍵字。新的坐標將使用上邊提到的變換消息來計算。對于一個轉換后的圖形,;drag-point消息試圖移動與當前變量相關的平面上的點,并保持正交分量不變。如果變換是正交的,這才會正確地起作用。:drag-point消息的方法返回被拖動的點的索引,如果沒有這樣的足夠接近點擊處的點將返回nil。使用:drag-point,我們可以這樣為:do-point-moving消息定義一個方法: (defmeth w :do-point-moving (x y m1 m2)(let ((p (send self :drag-point x y)))(if p (format t "Point ~d has been moved.~%" p)))) 練習 9.6略。
標準鼠標模式
????初始情況下,由graph-proto原型構造的圖形包括兩個模式:selecting模式和brushing模式。selecting模式使用arrow光標,僅需要一個點擊方法:do-select-click;brushing模式使用brush光標,它的點擊消息和動作消息是:do-brush-click和:do-brush-motion。
? ? 針對selecting和brushing模式的點擊和動作手勢是被用來設計基本的用戶接口以針對這些鼠標模式。實際的選擇和高亮操作是通過兩個其它消息實現的,:unselect-all-points和:adjust-points-in-rect。:unselect-all-points消息不帶參數。:adjust-points-in-rect消息需要5個參數,前4個參數矩形的整型坐標值,左上角的兩個坐標值,還有矩形的寬和高。第5個參數是點狀態符號selected或hilited中的的一個。當發生一個點擊事件時,該圖形處于selecting模式,:do-select-click方法以下邊的方式使用這些消息:
- 如果點擊操作不包括擴展修飾器(即shift或alt鍵),:unselect-all-points消息發向任意當前選中點集中未被選中的點。
- 然后,:adjust-points-in-rect消息被發送,其作用范圍是以點擊點為中心的,由當前點擊范圍指定的寬和高的矩形區域的坐標范圍。第5個參數是selected。這個消息的方法應該會選擇指定矩形范圍內的所有可視點。
- 當鼠標按鍵按下時,一個角固定在鼠標點擊處的虛線矩形將拖拽出來并覆蓋圖形。當鼠標按鍵釋放時,一個帶有最終矩形坐標和符號selected作為參數的:adjust-points-in-rect消息將被發送出去。
? ? 當圖形處于brushing模式時,:do-brush-click方法首先取消對所有點的選擇操作,除非給定了擴展修飾符;然后,該方法將發送一個以當前刷坐標和符號'selected為參數的:adjust-points-in-rect消息。每次鼠標按下并移動的時候,它都會持續發送該消息。:do-brush-motion方法將發送參數為當前刷坐標和符號hilited為參數的消息。當第5個參數是hilited時,該消息的方法有望高亮矩形區域內所有可見的點,然后將矩形外高亮的點取消高亮效果。
? ? 當前刷的位置和大小可以使用:brush消息來獲取。刷由4個整數指定:右下角的畫布坐標x、y,連接鼠標的角,還有刷的寬度和高度。通過發送一個帶四個整數參數(即新坐標)的:brush消息,可以指定新刷的坐標;:resize-brush消息提供了一個對話框來交互地設置新刷的大小,該消息可以通過使用圖形菜單里的Resize Brush菜單項來發送給該圖形。
? ? 使用:unselect-all-points和:adjust-points-in-rectx消息的協議允許開發新的圖形,而不用修改刷模式或選擇模式方法自身。例如,在彩色顯示器的系統上,我們可以為這兩個消息修改這些方法,以確保選中的點著紅色,未選中的點著繪圖色。在發送下邊這條消息以確保使用彩色繪圖之后,
(send w :use-color t) 我們可以這樣為:unselect-all-points定義消息:
(defmeth w :unselect-all-points ()(send self :point-color (iseq (send self :num-points)) nil)(call-next-method)) 該方法將所有點的顏色設置為nil,然后調用從graph-proto原型繼承來的方法。新的:adjust-points-in-rect方法可以這樣定義:
(defmeth w :adjust-points-in-rect (x y w h s)(if (eq s 'selected)(let ((p (send self :points-in-rect x y w h)))(if p (send self :point-color p 'red))))(call-next-method x y w h s)) 變量p包含指定的矩形區域內的點的索引列表,如果該列表不是nil的則金發送:point-color消息。
9.1.5 連接
????圖形里的點可能處于一些不同的狀態,selecting和brushing操作提供了一種容易的方式來改變圖形里點的狀態。為了瀏覽數據集的一些視圖之間的關系,Lisp-Stat繪圖系統允許圖形之間的互連操作。當兩個或更多圖形連接到一起時,系統將試圖保證連接在一起的圖形里的點狀態時相同的。
? ? 默認的Lisp-Stat連接系統基于一個松散的聯合模型,在這個模型里,被連接到一起的不同圖形的點通過他們的索引值彼此關聯。因此,在一個圖形里選擇索引值為3的點,同時也會選中與該圖形連接的圖形里索引值同為3的點。在不存在點的其它屬性匹配的意義下,這個聯合是松散的。在連接的圖形中的點可以有不同的標簽、顏色或者符號。其它的連接模式也是可能的,其中一個可替換的連接模式將在10.6節里說明。
確定哪些圖形可以連接
????確定哪些圖形可以連接到其它圖形上的系統依賴:linked和:links兩個消息。當向圖形發送:links消息時,如果該圖形沒有被連接則返回nil;如果該圖形被連接了,它應該返回一個列表,該類表包含所有連接到該圖形的接收了該消息的其它圖形,圖形自身可能是該列表的一個元素。:linked消息可以以不帶參數的形式發送,用來確定該圖形是否已經被連接,如果已經被連接了,返回t;否則,返回nil。該消息可以接受一個可選參數,該參數為t就是連接該圖形,該參數為nil就是解開連接。
? ? graph-proto原型的:links和linked方法將維護一個包含已連接圖形的單列表,該列表由被連接的圖形的:links消息返回,該列表也可以通過使用linked-plots函數好獲取,當某個圖形被連接時,該圖形的點的狀態傳遞到其它被連接的圖形,就像由:links消息返回的結果確定的一樣。
? ? 因為被連接圖形的確認完全基于這兩個消息,定義一個可代替的策略是相當簡單的。例如,如果我們想確保所有與stack loss data相關的圖形都與另一個數據相連而不是與所有的圖形都相連,我們可以這樣設置一個stack loss圖形的列表:
(setf *stack-plots* nil) 然后,給予每個stack圖形它們自己的:links和:linked方法,它們這樣定義:
(defmeth w :links ()(if (member self *stack-plots*) *stack-plots*))(defmeth w :linked (&optional (link nil set))(when set(setf *stack-plots*(if link(cons self *stack-plots*)(remove self *stack-plots*)))(call-next-method))) 避免為每個新圖形添加這些方法的一個方式是定義一個獨立的stack loss圖形原型。
點狀態和連接
????圖形里的每個點都是四種狀態中的一種,對應的符號為invisible, normal, hilited和selected。graph-proto繪圖方法可以繪制高亮的點和由點的符號的高亮版本選中的點。處于正常狀態的點使用正常符號來繪制,處于invisible狀態的點不被繪制。點的狀態可以通過:point-state消息來確定和繪制。對于處于stack loss數據圖形里的第一個點:
> (send w :point-state 0) NORMAL :point-state消息的方法是矢量化的,所以它可以用來確定圖形里的所有點的狀態,方法如下: > (send w :point-state (iseq 21)) (NORMAL NORMAL NORMAL NORMAL ...) ? ? 為了改變點的狀態,可以向:point-state消息的第二個參數賦值為一個新狀態,下邊的表達式將第一個點的狀態改為selected。當圖形里的一個點的狀態發生了改變,在所有被連接的圖形了對應的點的狀態也會發生改變。另外,會向每個圖形發送一個帶一個參數的:adjust-screen-point方法,該參數是想要改變的那個點的索引值。該方法負責改變屏幕上點的影像。
? ? :adjust-screen-point方法采取的動作即取決于改點的新狀態也取決于改點的上一個狀態。當發送:adjust-screen-point消息的時候,:point-state消息返回的值將表達新的狀態,點的上一個狀態可以通過向:last-point-state消息發送一個參數來獲取,該參數是改點的索引值。
? ? 為了在所有連接在一起的圖形該處最佳的對應關系,:adjust-screen-ponit方法試圖盡可能快地重畫點數據,以表達它們之間的任何狀態變化。對于有些狀態改變來說,這是相當簡單的。如果點的狀態在normal, hilited和selected這些值之間變化的話,那么:adjust-screen-point方法將使用上一章引入的:replace-symbol方法來改變屏幕上顯示的符號。不幸的是,如果點的新狀態是不可見的,不重畫圖形而從屏幕上清晰地移除改點是很困難的。由于每次將一點設置成不可見狀態都要進行重畫操作是很浪費的,所以:adjust-screen-point方法采取了一個間接的方法:它向圖形發送一個標識位來指示是否需要調整以適合窗體,:needs-adjusting消息用來設置和獲取這個標識位的值。在將一個點的狀態設置為不可見后,該標識位的值為t:
> (send w :needs-adjusting) NIL> (send w :point-state 0 'invisible) INVISIBLE> (send w :needs-adjusting) T 在方便的時候,也可以向圖形發送:adjust-screen消息,如果需要的話,該消息的方法將檢查該標志位、重設它,然后重畫圖形。因此,在將一個點的狀態設置為不可見之后,在對所有需要改變狀態的點進行調整之后,:adjust-points-in-rect方法將向所有連接的圖形發送:adjust-screen消息。? ? 作為這些想法的一個簡單的展示,我們可以為我們的圖形定義一個新的:adjust-screen-point方法,該方法將所有高亮的點和選中的點設置為紅色,然后將所有未高亮的點設置為nil:
> (defmeth w :adjust-screen-point (i)(let* ((state (send self :point-state i))(color (if (member state '(selected hilited)) 'red)))(send self :point-color i color))(call-next-method i)) 該方法與早先我們使用的那個為:adjust-points-in-rect方法修改的方法有些不同,之前的方法,僅當在圖形自身內部的點的狀態被刷模式或選擇模式改變的時候才會著色;而現在的方法可以確保即使是在被連接的圖形里由刷模式或選擇模式改變狀態的點,其顏色也會調整。 練習 9.7
略。
高級狀態消息
????對于獲取和改變點的狀態,還有一些其它消息是可用的。:point-showing, :point-hilited和:point-selected消息需要一個點的索引做參數,然后返回t或nil來分別指示點的狀態是否是可見的、高亮的或被選中的;對這些消息的第二個參數賦值為t或nil,將會對其狀態做合適的改變。這3個方法都是矢量化的,這3個方法在返回之前都會發送:adjust-screen消息。
? ? :selection消息返回當前被選中的所有點的列表,如果向該消息傳遞一個索引列表作為它的可選參數的話,該消息將取消所有選中的點,然后選中參數列表里指定的所有可見的點,他在返回前將發送:adjust-screen消息。:points-selected消息的方法與:selection方法是等效的;:points-hilited和:points-showing的方法也是相同的。
? ? :erase-selection方法將所有當前選中的點的狀態改變成不可見狀態,:show-all-points方法將所有點的狀態改為正常狀態;:focus-on-selection方法將所有未選中的方法設置為不可見狀態。這3個方法在返回之前都發送:adjust-screen消息。
? ? 這里有兩個有用的謂詞消息是可用的。:any-points-selected-p消息的方法在圖形里任何一個點被選中時都返回t,否則返回nil。:all-points-showing-p消息在所有點都是可見狀態小返回t,任意一個點為不可見狀態則返回nil。
9.1.6 窗體布局、縮放和重畫
縮放方法
????graph-proto原型的:resize方法基于一定的假設,該假設是關于圖形的布局的。圖形的內容,點數據與線數據的影像被限制在一個叫做內容矩形的矩形里。如果圖形里包含坐標軸,它們將立即在內容矩形的外部被畫出來。由內容和坐標軸組成的整個圖形被一個邊緣包裹起來,這個邊緣被用來束縛圖形,像標準旋轉圖形的旋轉控制,這個邊緣沿著窗體的畫布的左側、上側、右側和下側都有一個固定的像素數的留白。當該圖形變換大小的時候,:resize消息會保持邊緣不變,然后內容矩形的大小以填充畫布的剩余部分。內容矩形的精確的大小和形狀依賴于該空間是否需要x和y坐標軸,坐標軸是否擁有標簽,內容矩形是否保持固定的方位比例。對于一個固定的方位比例,內容多邊形是這樣的一個最大矩形,它是能裝下邊緣的并為坐標軸留下空間的那個最大正方形。如果該圖形允許方位比例改變,內容多邊形將是滿足這個限制的最大的矩形。圖9.3針對一個帶有坐標軸和固定方位比例的圖形的邊緣和內容矩形。
圖9.3?帶有坐標軸和固定方位比例的圖形的邊緣和內容矩形
內容矩形可以通過使用:content-rect消息來設置和獲取。不適用參數的話,該消息將返回內容矩形的坐標的列表(比如說一個包含左上角兩個坐標值、寬度和高度的列表):
> (send w :content-rect) (0 0 250 251) 如果該消息使用四個參數來調用的話,它將會把內容矩形設置成指定的坐標。? ? 圖形的邊緣可以使用:margin消息來設置和獲取。不適用參數調用,該消息返回一個四個整型數表示的列表,代表邊緣距離畫布的上側、頂側、右側和下側的距離的像素值。初始情況下,一個從graph-proto原型創建的圖形的邊緣,其值都是0:
> (send w :margin) (0 0 0 0) 為了設置一個新的邊緣,可以給:margin消息賦予4個參數。? ? 圖形的方位比例可以使用:fixed-aspect消息確定和設置,對于固定的方位比例該結果是一個非空值,對于一個可變的方位比例該結果返回nil。對于我們的例子,方位比例初始情況下是可變的:
> (send w :fixed-aspect) NIL 為了改變方位比例,你可以向該消息發送一個nil和一個非空的參數,以下表達式給予我們的圖形一個固定的方位比例。? ? 如果使用:margin, :fixed-aspect, x-axis 或 :y-axis消息,布局特征的任意一個發生改變的話,那么這些消息的方法將向圖形發送:resize和:redraw消息,除非:draw關鍵字被賦予了值為nil的參數。
? ? 在返回之前,:resize方法將向圖形發送:redraw-overlays消息,這里的疊置(Overlays)將在9.1.7節里描述,它提供方便的方法來實現圖形控制,比如對旋轉圖形的旋轉控制。
重畫方法
????繪圖原型的重畫方法:redraw將重畫過程打斷成幾個階段。首先,它發送:redraw-backgroud消息,該消息的方法將擦除畫布,然后當前圖形設置需要的所有坐標軸。下一步,:redraw消息向圖形發送:redraw-overlays消息。最后,向圖形發送:redraw-content消息,該消息對應的方法負責點數據和線數據自身。:redraw, :redraw-content和:redraw-backgroud消息對應的方法都使用了緩沖區。
? ? 將背景繪制與內容繪制分離的原因是,繪制坐標軸和疊置操作可能很耗時,并且僅有內容改變時這是不需要的。結果,像:adjust-screen和:rotate-2這樣的消息對應的方法僅需要向圖形發送:redraw-content消息以改變圖形的內容。
? ? 開發新圖形時,很少要覆蓋:graph-proto的:redraw方法,通常寫一個新的:redraw-content方法就最夠了。
下層消息
????為了有助于繪圖方法,graph-proto原型轉換系統保持將待轉換的點數據和線數據坐標的整型版本(即轉換后的坐標數據為整型值)。整型數的范圍,即所謂的畫布范圍,與每個變量都有關系。該系統將被變換坐標值從被縮放的比例縮放到畫布的比例。那么如果一個變量的范圍是從0到200,那么在-1.0到1.0范圍內坐標值為0.5的坐標對應的畫布坐標是150。
? ? 變量的畫布范圍可以使用:canvas-range消息進行獲取和設置。對于我們的事例圖形里的第一個變量,它的范圍就是:
> (send w :canvas-range 0) (0 232)對于當前變量的第一個,:resize方法將畫布范圍設置為從0到內容矩形寬度這樣的范圍,對于所有其它變量來說,該范圍被設置到從0到內容多邊形高度這一范圍中。
? ? 點數據和直線中的點數據的畫布坐標可以使用:point-canvas-coordinate和:linestart-canvas-coordinate消息來獲取。這些消息可以向我們在上邊介紹的其它的坐標消息一樣來使用。第一個點的前兩個變量的畫布坐標可以這樣獲取:
> (send w :point-canvas-coordinate 0 0) 80> (send w :point-canvas-coordinate 1 0) 58 ????繪圖系統還會使用一個在內容矩形里的叫做內容原點的點,該原點可以使用:content-origin消息來設置和獲取: > (send w :content-origin) (9 250) :resize方法將該原點放置在內容矩形的左上角稍低點的位置。:redraw-content方法使用畫布坐標作為偏移量。? ? 在這個上下文里使用的一個終極消息是:content-variables,該消息對應的方法與:current-variables方法等價,除非在這樣的情況下——當該變量變化時它沒有重畫圖形。
9.1.7 圖形疊置
????疊置可以被視為一張透明的紙,這張紙在內容繪制之前放置在圖形的背景上,這些紙保持一定的順序,并從下向上繪制。為了不被圖形的內容覆蓋,疊置層通常只在圖形的邊緣里繪制。
? ? 圖形疊置的一個關鍵的特性是它們的攔截或者說是過濾鼠標點擊的能力。graph-proto原型的:do-click方法允許每個疊置層,從頂層逐層向下,來接收或者傳遞一個鼠標點擊事件。如果該點擊事件未被任何疊置層接收,那么它僅能傳遞到當前鼠標模式上。該特性使使用疊置層來實現簡單的圖形控制成為可能,比如讓某個圖形執行特殊的動作。對于旋轉圖形的旋轉操作就是以疊置層實現的。
? ? 圖形疊置層是一個對象,它繼承自graph-overlay-proto原型。通過向圖形發送帶一個參數的:add-overlay消息(該參數是疊置層對象)來向一個圖形里添加疊置層。該操作將新的疊置層置于所有其它疊置層之上;通過向圖形發送帶一個參數的:delete-overlay消息來移除疊置層(該參數是要移除的疊置層對象)。
? ? 當圖形放縮的時候,:resize方法在返回之前向圖形發送:resize-overlays消息,該消息對應的方法向圖形里的每個疊置層發送:resize消息。當圖形重畫時,:redraw方法向圖形發送:redraw-overlays消息,該消息對應的方法向每個疊置層發送:redraw消息,從最底層的疊置層開始一直向上直到疊置堆棧的最頂端。
? ? graph-proto :do-click方法向疊置層發送:do-click消息,該消息帶它接受到的一些參數,該過程從最上邊的疊置層開始,持續穿過其它疊置層,直到返回一個非nil值作為點擊的結果。如果所有的疊置層對點擊動作的反應都返回nil,那么對于當前的鼠標模式該click事件將被傳遞給click方法。
? ? 舉個例子,我們可以向我們的氨氣損耗數據圖形里添加一個疊置層,那個圖形包含一個按鈕的和一個跟著一個標簽的正方形。當在正方形里發生點擊事件時,圖形將接受指令運行一個平滑差值算法,該算法從溫度對氣流的圖形到氨氣損失到濃度的圖形。為了避免圖形縮放中需要重新定位按鈕,我們可以將它放置在圖形的左上角位置。
> (let ((h (+ (send w :text-ascent) (send w :text-descent))))(send w :margin 0 (round (* 1.5 h)) 0 0)) ? ? 上式設置了圖形上邊距,目的是為窗體的字體里的一行文本留下足夠的空間。通過向疊置層原型發送:new-message消息,我們可以構造疊置層對象: > (setf interp-overlay (send graph-overlay-proto :new))接下來,我們可以給予該疊置層一個槽,用來容納描述按鈕容器和標簽字符串的位置的信息。
> (let* ((ascent (send w :text-ascent))(x ascent)(y (round (* 1.5 ascent)))(box ascent))(send interp-overlay :add-slot 'location(list x y box (round (+ x (* 1.5 box)))))) 然后,我們可以為該槽定義一個讀取方法: > (defmeth interp-overlay :location () (slot-value 'location)) ? ? 現在可以定義該疊置層的:redraw方法來獲取位置信息,這里使用:location消息,然后在指定位置繪制一個盒子容器和一個標簽: > (defmeth interp-overlay :redraw ()(let* ((loc (send self :location))(x (first loc))(y (second loc))(box (third loc))(string-x (fourth loc))(graph (send self :graph)))(send graph :frame-rect x (- y box) box box)(send graph :draw-string "Interpolate" string-x y))) 該方法向疊置層發送了:graph消息,目的是某圖形是否含有某疊置層。當某疊置層安裝到一個圖形以后,該疊置層系統將確保該消息返回合適的結果。? ? :do-click消息將返回nil,除非點擊落在按鈕的盒子容器的內部;如果點擊發生在盒子容器里,該方法將向圖形發送:interpolate消息然后返回t,進一步確保點擊事件沒有被做進一步處理:
> (defmeth interp-overlay :do-click (x y m1 m2)(let* ((loc (send self :location))(box (third loc))(left (first loc))(top (- (second loc) box))(right (+ left box))(botton (+ top box))(graph (send self :graph)))(when (and (< left x right) (< top y bottom))(send graph :interpolate)t))) 為了完成這個例子,我們需要為我們的圖形定義一個:interpolate方法。可以基于9.1.3節的平滑旋轉循環,做一個簡單的定義,例如:
圖 9.4 疊置層里帶差值按鈕的煙霧損失數據圖形
> (defmeth w :interpolate ()(send self :transformation nil)(dotimes (i 10)(send self :rotate-2 0 2 (/pi 20) :draw nil)(send self :rotate-2 1 3 (/ pi 20)))) 最后,通過使用下式將疊置層安裝到我們的圖形里: > (send w :add-overlay interp-overlay) 結果見圖9.4,圖形內容部分發生的點擊像以前一樣被處理了,但是在按鈕里的點擊引起圖形運行了差值算法。? ? 待使用疊置層試驗之后,我們可以使用以下表達式來移除該疊置層:
> (send w :delete-overlay interp-overlay) 練習9.8略
練習9.9
略
練習9.10
略
9.1.8 菜單和菜單項
????graph-proto原型的:isnew方法可以通過向圖形對象發送:new-menu消息來添加一個菜單,該消息對應的方法按順序向對象發送:menu-title和:menu-template消息。:menu-title消息返回一個標題字符串:
> (send w :menu-title) "Plot" 在構建新的圖形菜單的時候,該消息可以用來安裝一個新的菜單標題,在給予一個新的原型一個更合適的菜單標題上,這是非常有用的。? ? :menu-template方法預期會返回一個列表,該列表用來構建菜單項,該列表可以包含菜單項對象,這些菜單項對象可以簡單地安裝在菜單里,或者也可能包含一些用來指定標準菜單項的符號。該消息的graph-proto原型對應的方法返回一個符號列表:
> (send w :menu-templates) (LINK SHOWING-LABELS MOUSE REIZE-BRUSH ...)- :new-ment方法為這些符號里的每個符號都構造了一個合適的菜單。下邊的符號可以用來指定標準菜單項:
- color——發送:set-sselection-color消息,該消息會顯示一個對話框,用來改變選中的點的顏色。
- dash——一個處于不可用狀態的分隔符。
- focus-on-selection——發送:focus-on-selection消息。
- link——使用:linked消息切換圖形的鏈接。
- mouse——發送:choose-mouse-mode消息,該消息顯示一個對話框,用來改變鼠標模式。
- option——向圖形發送:set-options消息,該消息顯示一個對話框,用來設置一些選項。
- redraw——發送:redraw消息。
- erase-selection——發送:erase-selection消息。
- rescale——發送:adjust-to-data消息。
- save-image——發送:ask-save-image消息,該消息顯示一個對話框,用來將圖像保存成文件。
- selection——發送:selection-dialog消息。
- show-all——發送:show-all-points消息。
- show-labels——使用:showing-labels消息切換顯示標簽。
- symbol——發送:set-selection-symbol消息,該消息顯示一個對話框,用來改變選中的點的符號。
這些菜單項的一些使用:any-points-selected-p或者:all-points-showing-p來確定他們是否處于可用狀態(使能)。
? ? :new-menu消息還可以被賦予一個可替代的標題字符串參數,該參數用來代替:menu-title消息的結果。菜單項的可選列表可以通過使用:items關鍵字來使用,該列表可以包含上邊列出的符號中的任意多個,目的是指定標準菜單項。例如:
> (send w :new-menu "Stack Loss" :items '(link dash rescale)) 該表達式賦予stack loss圖形一個菜單,它僅包括link項和rescale項,中間用一條直線分開。可替代的方法就是去定義一個新的:menu-template方法,然后發送:new-menu不帶:items關鍵字的消息給它。對于開發一個新的原型是很有用的。9.1.9 雜項消息
????還有一些其它消息在graph-proto原型里是可用的。:showing-labels消息用于設置和獲取標簽選項的狀態,如果該值為非nil值,繪圖方法將把它放置于高點的選中的點數據附近。
? ? 有些方法是專為2維圖形設計的。add-function消息接受一個帶一個實數參數的函數,代表一個區間的高低界限的兩個實數,還有一個指定一定數量點數據的關鍵字參數。該消息對應的方法構造了一個網格,并在該網格上計算函數,然后將對應的線集合添加到圖形上。它也接受關鍵字參數:color和:type,用來指定顏色和線型。該圖形將被設置:redraw-content消息,除非使用了參數為nil的:draw關鍵字。
? ? 對于線性函數,:abline消息帶兩個參數,一個截矩和一個斜率,然后將該直線加到圖形上。該獨立變量的范圍就是x軸的當前范圍。
? ? :adjust-depth-cuing消息主要由旋轉圖形來使用,但是它也可以被其它圖形使用,它將整數維的索引作為參數,對于那些維度使用畫布坐標(它們被視為超出屏幕的坐標),目的是為圖形里的點數據和線數據使用深度標識。通過將點數據的符號改變為符號dot1,dot2,dot3和dot4中的一個,該點數據是深度標識的。符號dot1是為離視點最遠的點使用的,dot4用于里視點最近的點。通過改變線的寬度也可以達到顯得深度標識效果。
9.2 一些標準繪圖原型
????第二章里介紹的那5個標準圖形類型,它們是使用從graph-proto原型繼承來的原型構造的。盡管這些圖形在外觀上相差很大,但是它們僅需要向graph-proto提供的原型,覆蓋和增加少量的方法。
9.2.1 散點圖
????基礎散點圖原型scatterplot-proto用于構造一類圖形,即由plot-points和plot-lines函數構造的圖形。它至少需要二個維度的數據。針對:add-points和:add-lines消息的二維散點圖,允許這樣指定新數據——使用兩個分離的列表代替所需的列表的列表。那樣的話,如果s是一個二維散點圖,x和y是等長度的數值型列表,那么下邊的兩個語句是等價的:
> (send s :add-points x y) > (send s :add-points (list x y)) ? ? 如果圖形的尺度類型是nil的話,:adjust-to-data方法向圖形里添加一個坐標軸,它還會將坐標范圍設置到坐標軸上產生好看的坐標刻度。9.2.2 散點圖矩陣
????散點圖矩陣原型scatmat-proto至少需要2個維度的數據,它使用一個固定的長寬比例,它的內容的原點坐標在內容矩形的左下角,每個變量在畫布上的范圍設置在一定范圍內——子圖的列的左右范圍,從原點開始量度。通過修改:do-click和:do-motion方法,將內容變量設置為包含光標的那個子圖的索引上,這個操作允許很多繼承它的方法可以不經修改就正常工作。
? ? 與:add-lines, :add-points, :adjust-screen-point和:redraw-content消息相對的方法,通過修改它們可以讓矩陣里的所有子圖形合適地繪制。如果圖形的尺度類型是nil,默認地,:redraw-content方法還可以在對角圖里繪制變量標簽和界限。
9.2.3 旋轉圖形
????由spin-plot函數產生的,用來旋轉圖形的原型就是spin-proto,該原型至少需要三個維度的數據,它使用一個固定的橫寬比數據,其默認的尺度類型是variable。:content-variables方法使用3個內容變量索引,前兩個以常規方式使用,第三個變量是深度提示變量的索引。:resize方法將內容的原點放置于內容矩形的中間。
? ? :redraw-content方法在進行標準內容重畫之前調整深度提示。如果圖形要顯示坐標軸,坐標軸可以通過調用:redraw-content方法來重畫,該方法可以通過向圖形對象發送:draw-axes消息來構造。如果尺度類型為nil,:adjust-to-data方法將調整圖形的范圍,以確保所有的點在圍繞原點所做的旋轉過程中均是可見的。:depth-cuing和:showing-axes消息可以用來確定一個圖形時使用深度提示還是顯示坐標軸。通過發送一個值為nil或非nil的參數,這兩個消息可用來圖形對應的狀態。
? ? 旋轉圖形下邊的控件可以以一個疊置層的形式實現,該疊置層由:isnew方法加入。這些按鈕將旋轉的類型設置為pitching、rolling或yawing,調整角度符號,然后在鼠標按下時發送:rotate消息,:do-idle方法也發送該消息。旋轉圖形里的當前角度可以通過使用:angle消息來設置和獲取。角度以度計量。旋轉類型可以通過使用:rotation-type消息設置和獲取,它的值可能是符號pitching, rolling或者yawing中的一個,也可能是一個以漸進式旋轉來使用的旋轉矩陣。如果旋轉類型是一個符號,:rotate方法使用:rotate-2方法(使用合適的變量和角度作為其參數);如果該類型是一個矩陣,:rotate使用:apply-transformation。
? ? 除了添加控件疊置層,針對該原型:isnew方法將圖形的橫寬比設置為固定值,并為疊置層設置一個合適的邊緣。
? ? 針對旋轉圖形原型的:add-function方法是為三維圖形設計的,它需要一些參數:一個兩個參數(x軸限值和y軸限值)的函數,和四個實數數值。:abcplane消息有三個實數參數a,b和c,然后增加線段來顯示函數f(x,y)=a+bx+cy的一段。
9.2.4 直方圖
????直方圖原型是histogram-proto,直方圖在一個m+1維的圖形里顯示m維的點數據,剩余的維度用于圖形的y坐標,第一個維度被扔掉,作為直方圖來顯示,例如,下邊的表達式針對stack loss 數據構造了一個直方圖:
> (setf hs (histogram (list air temp conc loss))) 該直方圖變量的數量是5: > (send hs :num-variables) 5 內容變量是: > (send hs :content-variables) (0 4) 那么,初始情況下該圖形顯示的是數據集里第一個變量——氣流的直方圖。如果對圖形hs使用了變換,它將顯示轉換后的數據的第一維度數據的直方圖。例如,在為們為圖形的尺度類型設置為variable之后: > (send hs :scale-type 'variable) 下邊的表達式將在10步之內旋轉成溫度變量的直方圖。? ? 對于一維數據的直方圖,可以給予:add-points方法一個數值序列而不是一個數值序列的列表;對于一個m維數據的直方圖,:add-points方法需要一個m個序列的列表。那么stack loss數據可以使用下式來安裝:
> (send hs :add-points (list air temp conc loss)) ? ? :add-lines消息相對應的方法(它也可能用于向一個直方圖里添加一個密度線),它需要一個包含m+1個序列的列表。最后一個序列用來作為y坐標軸。例如,如果hp是一個一維的降水量數據的直方圖(該數據在第2.2.1節里),它可以這樣構造: > (setf hp (histogram precipitation)) 那么下邊的表達式將一個正常濃度的圖形添加到直方圖里。? ? 因為:transfomation和:apply-transformation消息相對應的方法可以接受一個維度低于圖形維度矩陣,你也可以使用相同維度的變換作為直方圖里的點數據。
? ? 針對:adjust-screen, :adjust-screen-point, :redraw-content和:adjust-points-in-rect這些消息,直方圖原型提供了它自己的方法。如果尺度類型為nil,:adjust-to-data犯法增加一個x軸,該方法也會重新計算直方圖里的矩形條塊,與:clear-points和:resize相對應的方法也會修改以適應這些矩形條塊。
? ? 有兩個消息提供了對這些矩形條塊的信息訪問能力。:bin-count消息返回每一個矩形條塊的數量的列表;不帶參數的:num-bins消息返回當前用到的矩形條塊的數量,如果給它傳遞一個正整數來調用的話,它將矩形條塊的數量給位指定的數量,當指定了一個行的矩形條塊的數量的時候,同時向圖形對象發送:redraw消息,除非關鍵字參數:draw接受了值為nil的參數。
9.2.5 名稱列表
????最后一個標準繪圖原型是name-list-proto,它的目的是提供一個點標簽的可連接的列表,它不適用任何數值數據。
? ? 名稱列表可以使用一個維度數m=0的數字來構造。該原型的:add-points方法可以接受一個數值來指定添加的點的數量,用來替代坐標值列表的列表。例如,針對stack loss數據的名稱列表可以這樣構造:
> (setf n (send name-list-proto :new 0)) 擁有21個標簽的集合和以使用下式來添加: > (send n :add-points 21) ? ? :add-lines方法被重定義,目的是不要做任何事,因為名稱列表不需要顯示線數據。:adjust-screen, :adjust-screen-point, :redraw-content和:adjust-points-in-rect方法也會被重定義。:isnew方法名稱列表擁有一個垂直滾動條。轉載于:https://my.oschina.net/u/1011760/blog/359393
總結
以上是生活随笔為你收集整理的Lisp-Stat翻译 —— 第九章 统计绘图窗体的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何删除多余系统引导项
- 下一篇: MPI编程简单介绍