開發高級 MATLAB 可視化效果通常需要管理多個低層的圖形對象,包含動態更新圖形的應用程式更是如此。這種應用程式可能需要非常耗時的編程。Chart 對象可提供高級應用程式編程接口(API),實現可視化的自定義創建。圖表可為最終用戶提供方便的可視化 API,無需用戶執行低層圖形編程。
本文以包含最佳擬合線的散點圖為主要示例,通過分步指導展示如何使用 MATLAB 面向對象的編程創建和實現自定義圖表,包括:
◆ ◆ ◆ ◆圖表示例
MATLAB 提供幾種圖表,包括 heatmap 圖和 geobubble 圖,前者顯示疊放在彩色網格上的矩陣值,後者可快速在地圖上繪製離散數據點(圖1)。
圖1 heatmap 圖和 geobubble 圖
此外,我們還創建幾種特定於應用程式的圖表(圖2)。您可以從 File Exchange 中隨本文使用的 MATLAB 代碼一起,下載這些圖表。
圖2 可在 File Exchange 中下載自定義圖表:
https://ww2.mathworks.cn/matlabcentral/fileexchange/65857-creating-specialized-charts-with-matlab-object-oriented-programming
創建二維散點圖:函數或圖表?
假設我們要創建包含對應最佳擬合線的二維散點圖(圖 3)。我們可以使用 scatter 函數顯示離散 (x,y) 數據點,並使用 Statisticsand Machine Learning Toolbox™ 中的 fitlm 函數計算最佳擬合線。
圖3 最佳擬合線和底層的分散數據
以上代碼可滿足靜態可視化的需求。但是,如果應用程式要求對數據進行動態修改,我們會遇到幾個難題:
圖4 最佳擬合線在更改散點圖的 XData 後未更新
我們可以通過設計一個圖表 ScatterFit 來解決這些難題。
構建圖表代碼:函數或類?
函數將代碼封裝為可重用單元,用戶無需重複代碼即可創建多個圖表。
請注意,此函數需要輸入兩個數據(x 和 y)。您可以指定圖形父項 f(例如,圖形)作為第一個輸入參數。
scatterfit(x,y) 指定輸入的兩個數據
scatterfit(f,x,y) 指定圖形父項和數據
在第一種情況下,該函數展示自動生成的行為,即將自動創建圖表的圖形。
使用函數創建圖表具有某些缺點:
用類來實現圖表具有代碼封裝及方法可重用性等好處,同時支持對圖表進行修改。
定義圖表類
為了與 MATLAB 圖形對象保持一致,我們將圖表實現為 handle 類,以便在適當位置修改圖表。我們支持在圖表屬性中使用點記法和 get/set 語法。要實現這一目標,我們從預定義 matlab.mixin.SetGet 類(本身是 handle 類)中派生出 ScatterFit 圖表。
因此,任何屬性都將自動支持表 1 中顯示的語法。
表1 圖表屬性的訪問和修改語法
編寫圖表類的構造方法
構造方法是類定義中的一個函數,用於構造圖表對象。首先,將代碼從 scatterfit 函數複製到我們的圖表構造方法之中。隨後,進行以下修改支持所需圖表行為:
請注意,此行為與 plot 和 scatter 等便捷函數的行為不同,後兩者可以自動生成輸入。如果用戶將 Parent 指定為輸入參數,則隨後將在構造方法中對其進行設置。
如果用戶已作為名稱值對輸入參數提供數據屬性,在此處設置這些數據(XData 和 YData)。我們還注意到,此編碼方法可確保用戶在指定名稱值對時出現的任何錯誤都將被圖表的屬性 set 方法發現和解決(稍後討論)。
如可行,我們將使用基元對象創建圖表圖形,因為高級便捷函數在調用時將重置多個現有 axes 屬性(圖2)。但是,這條原則存在例外情況:在 ScatterFit 內部,我們將使用 scatter 函數創建 Scatter 圖形對象,因為它支持對個別標記的尺寸和顏色進行後續更改。
表2 基元和高級圖形函數的示例
封裝圖表數據和圖形
在大多數圖表中,底層圖形包含至少一個 axes 對象及其內容(例如線或面對象)或多個對等的 axes 對象(例如,圖例或彩條)。這些圖表還包含內部數據屬性,以確保公共屬性之間保持一致。我們存儲底層圖形和內部數據為專有圖表屬性。例如,ScatterFit 圖表會維護以下專有屬性:
我們使用命名約定 XData_ 指示該版本是圖表數據的專有內部版本。用戶可見的對應公共數據屬性將命名為 XData。
使用 private 屬性主要有三個目的:
提供可視化API
設計圖表的一個主要原因是提供方便、直觀的 API。我們使用與現有圖形對象屬性一致的名稱為 ScatterFit 圖表提供容易識別的屬性(圖 5)。
圖5 ScatterFit 圖表API
用戶可使用表 1 中所示的語法訪問或修改這些屬性。關聯的圖表圖形會動態更新,以響應屬性的修改。例如,更改圖表的 LineWidth 屬性會更新最佳擬合線的 LineWidth。
我們使用 Dependent 類屬性實現圖表 API。Dependent 屬性的值未進行顯式存儲,而是獲取自類中的其他屬性。在圖表中,Dependent 屬性依賴於低級別圖形等專有屬性或內部數據屬性。
要定義 Dependent 屬性,我們首先使用屬性 Dependent 在 properties 塊中聲明其名稱。這表明該屬性的值依賴於類中的其他屬性。
通過編寫對應的 get 方法,我們還可以指定依賴於其他屬性的屬性。此方法會返回單個輸出參數,即 Dependent 屬性的值。在 ScatterFit 圖表中,XData 屬性(圖表公共接口的一部分)就是底層 XData_ 屬性,它作為圖表的 private 屬性存儲在內部。
我們為每個可配置的圖表屬性編寫 set 方法。此方法將用戶指定的值分配到正確的內部圖表屬性,以在必要時觸發圖形更新。
對於 ScatterFit 圖表,我們支持對數據屬性(XData 和 YData)進行動態修改(包括長度更改)。當用戶設置圖表的(公共)XData 時,我們將根據比較新數據矢量與現有數據的長短,填充或截斷相對的(專有)數據屬性 YData_。請記住,如果用戶在創建圖表時已指定 XData,此 set 方法將被構造方法調用。
我們通過調用不同的 update 方法刷新圖表圖形。此方法包含在 Scatter 對象中設置新數據、重新計算最佳擬合線,以及在對應的 Line 對象中設置新數據所需的代碼。
我們以相同方式為 YData 實現 set 方法,以切換 X/YData 屬性的角色。還會從 set 方法中為 YData 調用 update 方法。
要創建適用於最終用戶的豐富 API,我們會實現一組廣泛的 Dependent 屬性。建議在每個圖表中至少包含表 3 中所示的屬性。
表3. 建議的 Dependent 屬性
請注意,在大多數情況下,這些屬性將直接映射到底層圖表 axes。例如,Parent 屬性的 get 和 set 方法將圖表對象的 Parent 映射到 axes 的 Parent。
我們通過定義額外公共接口屬性啟用對可視化設置的控制,其中每個屬性映射到圖表維護的特定低級別圖形對象。在此類別中,ScatterFit 圖表支持各種線相關屬性,如最佳擬合線相關 LineStyle、LineWidth 和 LineColor。例如,圖表對象的 LineColor 屬性會映射到線對象的 Color 屬性。
此類別中的典型圖表屬性包括:
視圖相關屬性——例如,axes 的 View、XLim 和 Ylim
注釋——例如,axes 的 Xlabel、Ylabel 和 Title
裝飾性屬性——例如,顏色、線寬,樣式、網格、透明效果和明暗度
管理圖表的生命周期
ScatterFit 圖表與其底層 axes 對象密切關聯,該對象作為圖表的 private 屬性之一存儲。要正確管理圖表的生命周期,我們需要確保兩種行為:
MATLAB 中的每個圖形對象都具有 DeleteFcn 屬性——一種在圖形對象超出範圍後被自動調用的回調函數。因此,在圖表構造方法中設置 axes 的 DeleteFcn 滿足第一個要求。
此處,onAxesDeleted 是 private 類方法,僅充當圖表析構方法周圍的包裝程序。如前所述,每個 handle 類在創建時都包含可自定義析構方法。當對象超過範圍後,析構方法將被調用。
通過編寫自定義圖表類的析構方法,我們可滿足第二個要求。在圖表析構時,我們將刪除圖表的 axes。
實現這兩個要求後,圖表對象與其底層 axes 將具有相同的生命周期(圖 6)。
圖6 管理圖表和 axes 生命周期
簡化附加表格的開發
在編寫幾個圖表後,我們能夠輕鬆識別相似性和重複的代碼段。通過在超類中集中放置通用代碼,我們可以加速編寫額外圖表的進程。每個新圖表可派生自此超類,這可使我們專注於實現該特定圖表的細節,減少重複編碼的需求。
我們的超類(即 Chart)具有以下結構:
Chart 派生自 matlab.mixin.SetGet。
Chart 將實現六個核心 Dependent 屬性 Parent、Position、Units、OuterPosition、ActivePositionProperty 和 Visible。
Chart 具有 protected 屬性 Axes(底層對等圖形)。
Chart 構造方法將創建對等的 axes 對象並將 axes 的 DeleteFcn 設置為 protected 方法 onAxesDeleted。此方法進而將刪除圖表對象。
請注意,使用超類 Chart 可能並不適用於所有圖表。例如,維護多個 axes 的圖表需要對上述體系結構進行某些更改。我們可以實現此類圖表,方法是使用 uipanel 替代 axes 對象作為圖表的對等底層圖形,並在面板內部創建多個 axes。
◆ ◆ ◆ ◆在本文中,我們以 ScatterFit 圖表為例介紹實現自定義圖表的設計模式。許多公共可視化任務,尤其是需要動態圖形的任務,可使用適當的圖表執行。設計和創建圖表需要事先投入開發時間和精力,但圖表可以大幅簡化大量可視化工作流。