用MATLAB面向对象编程创建专门的图表
作者:Ken Deeley和David Sampson, MathWorks
发展先进MATLAB®可视化通常涉及管理多个低级图形对象。对于包含动态更新的图形的应用程序尤其如此。这样的应用程序可能需要耗时的编程。
一个图表对象提供用于创建自定义可视化的高级应用程序编程接口(API)。图表不仅为最终用户提供了方便的可视化API;它还消除了用户实现低级图形编程的需要。
MATLAB包含一个面向对象的框架,用于通过以下容器超类开发自定义图表:
matlab.graphics.chartcontainer.ChartContainer
(在R2019b介绍)matlab.ui.componentcontainer.ComponentContainer
(在R2020b介绍)
本文提供了使用该框架创建和实现自定义图表的逐步指南,包括设计模式和最佳实践。通过一个包含最佳拟合线的散点图来说明这些步骤。主题包括:
- 编写标准图表模板
- 写表的
设置
而且更新
方法 - 封装数据和图形
- 为最终用户提供高级API
- 包括交互控制
此外,我们还创建了几个特定于应用程序的图表(图2)。您可以从以下网站下载这些图表以及本文中使用的MATLAB代码文件交换.
上面的代码对于静态可视化已经足够了。然而,如果应用程序要求数据是可动态修改的,那么我们会遇到几个挑战:
- 如果我们替换
XData
或YData
使用与当前数组长度相同的新数组XData
,最佳拟合行没有动态更新(图4)。s.XData = s.XData + 4;
- 的
散射
对象年代
如果其数据属性(XData
或YData
)设置为一个比当前数组长或短的数组。s.XData = s.XData (1:50 0);
我们可以通过设计一个我们命名的图表来解决这些挑战和其他挑战ScatterFit
.
构造图表代码:函数还是类?
函数将代码封装为可重用的单元,允许您创建多个图表,而无需复制代码。
函数scatterfit(变长度输入宗量)%确保2或3个输入。narginchk(2、3)我们支持调用语法scatterfit(x, y)或% scatterfit(f, x, y),其中f是父图形。开关输入参数个数情况下2 f = gcf;x =变长度输入宗量{1};y =变长度输入宗量{2};否则% case 3 f = varargin{1};x =变长度输入宗量{2};y =变长度输入宗量{3};结束% switch / case创建图表轴和散点图。。ax =轴(“父”f);分散(ax, x, y, 6,“填充”)%计算并创建最适合的行。M = fitlm(x, y);(ax,“上”图(ax, x, m.)“线宽”2)持有(ax,“关闭”)结束% scatterfit函数
注意,此函数需要两个数据输入(x而且y).您可以指定图形父级f
(例如,一个数字)作为第一个输入参数。
scatterfit (x, y)
指定两个数据输入。scatterfit (f, x, y)
指定图形父级和数据。
在第一种情况下,函数显示autoparenting行为-也就是说,图表会自动创建一个数字。
使用函数创建图表有一些缺点:
- 在创建图表之后,不能修改数据。
- 要更改图表数据,您需要再次调用该函数来重新创建图表。
- 最终用户很难找到可配置的图表参数(例如,标签和装饰图形属性,如颜色、线条样式等)。
将图表作为类实现具有函数提供的代码封装和可重用性的所有好处,同时还允许您修改图表而不需要重新创建它。
选择图表超类:ChartContainer
或ComponentContainer
?
图表必须实现为处理
类,以便可以对其进行就地修改。为了与MATLAB图形对象的一致性,图表应该支持得到
/集
除了标准点表示法之外,属性的语法。这两个ChartContainer
而且ComponentContainer
是否处理类和提供支持得到
/集
语法,这意味着您可以从这些超类之一派生自定义图表。
classdefScatterFit < matlab.ui.componentcontainer.ComponentContainer
因此,对于任何属性,都自动支持表1所示的语法。
语法类型 | 访问 | 修改 |
---|---|---|
点符号 | x = SF.XData; |
科幻小说。XData = x; |
获取/设置 |
x = get(SF, "XData"); |
集(科幻“XData”x) |
根据图表的要求选择一个超类。如果图表不需要交互式的面向用户的控件,如按钮、下拉菜单和复选框,则从其中派生图表ChartContainer
;否则,使用ComponentContainer
.这是因为图表容器超类提供了一个平铺的布局作为顶级图形对象,该对象可以包含轴,但不包含用户控件。与组件容器关联的顶级图形对象是面板类对象,它支持轴和用户控件。
注意,框架超类自动管理图表生命周期,保证以下行为:
- 当删除图表图形时(例如,通过关闭主图表窗口),图表对象将被删除。
- 当删除图表对象时(例如,当它超出作用域或当它的句柄被删除时),图表图形将被删除。
框架超类支持对所有图表输入参数使用名称-值对。这意味着在创建图表时不需要指定输入参数,并且所有输入都是可选的。
写作的图表设置
而且更新
方法
现在我们需要实现两个特殊的方法,这两个方法都是框架超类所需要的:
设置
:创建图表时自动调用更新
:当用户修改某些图表属性时自动调用
这些方法必须在图表类中具有受保护的访问,因为它们在超类中具有此属性。
方法(访问=保护)函数设置(obj)结束%设置函数更新(obj)结束%更新结束% methods (Access = protected)
我们来看看设置
方法。这是类定义中的函数,我们在这里初始化图表。方法中复制代码是一个很好的开始scatterfit
函数设置
方法。然后我们进行以下修改以支持所需的图表行为:
父图形。与上面描述的方法不同
scatterfit
函数,如果没有父
如果指定了输入,则不会自动为图表创建一个输入。注意,此行为不同于方便函数,如情节
而且散射
,它们表现出自动育儿。在设置
方法后,我们创建主图形对象(如轴、面板或布局)并将其父属性分配给超类提供的顶级图形对象。的getLayout
的方法ChartContainer
超类返回顶层平铺布局的引用。为ComponentContainer
在图表中,我们可以简单地将图形父属性分配给对象本身。obj。一个xes = axes("Parent", obj.getLayout()); % ChartContainer obj.Axes = axes("Parent", obj); % ComponentContainer
如果
父
作为输入参数指定,那么它将由超类自动设置,以及在创建图表期间提供的任何其他名称-值对。超类将把用户指定的父类分配为顶级图形对象的父类。图表图形。我们创建并存储图表所需的任何图形对象。大多数图表都需要一个坐标轴对象和一些坐标轴内容,如行或补丁对象。在
ScatterFit
图表,我们需要一个散射
对象和一个行
对象。obj。ScatterSeries =散射(obj。轴,南南);obj。BestFitLine =线(obj。轴,南南);
注意,我们在初始化这些图形时将其数据属性设置为
南
.如果用户指定了XData
和/或YData
在构造时,我们推迟更新散点图和最佳拟合线到相应的位置集
方法(稍后讨论)。这种编码实践确保用户在指定名称-值对时引起的任何错误都将被捕获并单独处理。图形配置。我们通过设置任何必需的属性来配置图表图形。例如,我们可以创建诸如标签或标题之类的注释,设置坐标轴的特定视图,添加网格,或调整线条的颜色、样式或宽度。
在实际情况下,我们使用基本对象(表2)来创建图表图形,因为高级便利函数在调用时重置许多现有的轴属性。然而,这一原则也有例外:在内部ScatterFit
,我们使用非原语函数scatter来创建图形对象,因为它支持对单个标记大小和颜色的后续更改(而单个行对象则不支持)。
原始图形函数 | 高级图形函数 |
---|---|
行 |
情节 |
表面 |
冲浪 |
补丁 |
填满 |
稍后我们将返回到图表的更新方法。
封装海图数据和图形
在大多数图表中,底层图形包括至少一个轴对象及其内容(例如,线或面对象)或轴对等对象(例如,图例或颜色条)。图表还维护内部数据属性,以确保公共属性正确地呈现给最终用户。我们将底层图形和内部数据存储为私有图表属性。例如,ScatterFit
Chart维护以下私有属性。
属性(访问=私人)XData属性的内部存储。XData_ =双。空(0,1)YData属性的内部存储。YData_ =双。空(0,1)指定是否需要计算的逻辑标量。ComputationRequired = false ()结束%属性(访问=私有)
我们使用命名约定XData_
表示这是图表数据的私有、内部版本。对用户可见的相应公共数据属性将被命名XData
.
属性(访问=私有的,短暂的,不可复制的)%的图表坐标轴。轴(1)matlab.graphics.axis.Axes(x, y)数据的%分散序列。ScatterSeries matlab.graphics.chart.primitive.Scatter (1,1)最适合行的% Line对象。BestFitLine matlab.graphics.primitive.Line (1,1)结束%属性(访问=私有,瞬态,不可复制)
使用私人
内部图表数据和图形的属性主要有三个目的。
- 私有属性限制了底层图形的可见性,隐藏了实现细节,减少了图表API中的视觉混乱。
- 对低级图形的访问受到限制,从而减少了绕过API的机会。
- 可以很容易地同步图表数据(例如,我们需要
XData
而且YData
的属性ScatterFit
有关)。
对于内部图形属性,最好指定瞬态
而且NonCopyable
属性。这将确保图表对象在保存到mat文件或复制时行为正确。为了获得额外的健壮性,并在图表类中工作时对图形属性启用制表符补全,我们还实现了属性的验证.
提供可视化API
设计图表的一个主要原因是提供方便和直观的API。我们装备ScatterFit
具有易于识别的属性的图表,使用与现有图形对象属性一致的名称(图5)。
用户可以使用表1中的示例语法访问或修改这些属性。相关的图表图形根据属性修改动态更新。例如,更改线宽
属性更新线宽
最适合的直线。
我们使用依赖
属性。一个依赖
属性的值没有显式存储,而是从类中的其他属性派生而来。在图表中,依赖
属性依赖于私有属性,如低级图形或内部数据属性。
定义一个依赖
属性,我们首先在带有attribute的属性块中声明其名称依赖
.这表明属性的值依赖于类中的其他属性。
属性(依赖)% x数据图表。XData(:, 1) double {mustBeReal}%图表数据。YData(:, 1) double {mustBeReal}结束%属性(依赖)
我们还需要通过编写相应的get方法来指定属性如何依赖于其他类的属性。方法返回单个输出参数—即依赖
财产。在ScatterFit
图表,XData
属性(图表的公共接口的一部分)只是底层XData_
属性,该属性作为图表的私有属性存储在内部。
函数value = get.XData(obj) value = obj. xdata_;结束%。XData
每个数据属性还需要集
方法。这会将用户指定的值赋给正确的内部图表属性,并触发任何必要的图形更新。
为ScatterFit
图表中,我们支持对数据属性(XData
而且YData
).当用户设置(public)XData
对于图表,我们填充或截断相反的(私有)数据属性YData_
,取决于新的数据向量分别比现有数据长还是短。回想一下,这集
方法将在构造时调用,如果用户已指定XData
在创建图表时。
函数集。XData (obj,值)标记图表以便更新。。obj。ComputationRequired = true ();决定如何修改图表数据。。nX =元素个数(价值);纽约=元素个数(obj.YData_);如果nX <纽约如果新的x数据太短,那么截断图表y数据。obj。YData_ = obj.YData_ (1: nX);其他的否则,如果nX >= nY,则填充y数据。obj。YData_(end+1:nX, 1) = NaN;结束%如果设置内部x-data。obj。XData_ =价值;结束%设置。XData
注意这个图表更新
方法在用户设置公共属性时自动调用。为了避免不必要和耗时的计算,我们使用一个私有的内部逻辑属性ComputationRequired
要记录,在集
方法,是否需要完全更新。
更改时不需要新的计算的公共API属性不需要得到
或集
方法。方法的末尾刷新相应的内部对象更新
方法。通常,公共API属性包括图表的装饰和修饰方面,如颜色、线宽和样式,这些更新成本不高。
在ScatterFit
图表,更新
方法中设置新数据所需的代码散射
对象,重新计算最佳拟合行,并在对应的行
对象。
函数更新(obj)如果obj。ComputationRequired用新数据更新散点序列。。设置(obj。散射Series,“XData”obj。XData_,“YData”obj.YData_)%获取新的最佳拟合线。m = fitlm (obj。XData_,obj。YData_);更新最合适的线条图形。[~, posMin] = min(obj.XData_);[~, posMax] = max(obj.XData_);设置(obj。BestFitLine,“XData”obj。XData_([posMin, posMax]),“YData”m.Fitted ([posMin posMax]))把图表标记干净。obj。ComputationRequired = false ();结束%如果刷新图表的装饰属性。。设置(obj。散射Series,“CData”obj。CData,“SizeData”obj.SizeData)结束%更新
我们实现了集
方法YData
以同样的方式,切换角色X / YData
属性。
为了创建适合最终用户的丰富API,我们实现了一组广泛的公共属性。注意标准属性,如父
,位置
,单位
,可见
都继承自超类,不需要在图表中进行额外的实现。
添加图表标注方法
在API中,我们提供了熟悉且易于使用的方法来注释图表。这些注释的方法过载(与)对应的高级图形装饰功能。要使用这些方法,用户需要提供对图表的引用作为第一个输入参数,然后是装饰函数的输入。
包含(科幻小说,“x数据”,“字形大小”, 12)
如果装饰函数支持,还可以用输出调用注释方法,以返回对图形对象的引用,以便进一步定制。例如,包含
函数返回一个文本对象。
xl =包含(科幻小说,“x数据”);
为了支持名称-值对和输出参数,可以方便地使用单元格数组变长度输入宗量
而且varargout
.的语法变长度输入宗量{:}
生成以逗号分隔的输入参数列表。我们确定调用者使用的输出数量nargout
.为了处理数量可变的输出参数(对于这些方法,通常为0或1),我们使用该语法[varargout {1: nargout}]
调用装饰功能时。典型的注释方法具有以下结构:
函数Varargout = xlabel(obj, Varargout) [Varargout {1:nargout}] = xlabel(obj。轴,变长度输入宗量{:});结束%包含
在图表中包含交互式控件
除了图表的API之外,我们还可以包括为最终用户提供图表交互和修改选项的控件(图6)。
我们在图表中初始化这些控件设置
方法,使用用于应用程序构建的组件.每个控件都有一个回调函数,作为私有方法实现。这个方法有三个输入参数:
- 图表对象。
- 引用源对象(负责触发回调的对象)—在本例中,源对象是相应的用户控件。
- 事件数据。这是一个当用户与控件交互时由MATLAB自动传递给回调函数的对象。事件数据对象包含关于事件的附加信息。
例如,考虑控制最佳拟合行可见性的复选框的回调函数。此函数根据复选框的值切换底层行对象的可见性。
函数toggleLineVisibility (obj s ~)togglel必然性切换最适合线条的可见性。obj.BestFitLine.Visible = s.Value;结束% toggleLineVisibility
每个控件的值必须与相应的图表属性同步。为实现这一点,我们用依赖
属性,然后实现其得到
而且集
方法。注意,除了更新内部图形对象之外,集
方法也必须更新控件对象的值。
与最佳拟合行可见性对应的代码如下所示。为确保属性与复选框值之间的兼容性,我们将属性转换为matlab.lang.OnOffSwitchState
类型。此类型支持任何兼容的表示语法真正的
而且假
值,如1
而且0
,以及“上”
而且“关闭”
.
属性(依赖)%最佳拟合线的可见性。LineVisible matlab.lang.OnOffSwitchState (1,1)结束%属性(依赖)函数value = get. linevvisible (obj) value = obj. bestfitline . visible;结束%。行可见函数集。LineVisible (obj,值)更新属性。obj.BestFitLine.Visible =价值;%更新复选框。obj.BestFitLineCheckBox.Value =价值;结束%设置。行可见
集成图表与应用程序设计器
从MATLAB R2021a,图表开发使用ComponentContainer
超类可以与应用程序设计器集成使用App Designer,您可以通过创建元数据与最终用户共享图表。安装的图表将出现在用户的App Designer Component Library中,在那里它可以像任何其他组件一样在画布中交互使用。
总结
在本文中,我们描述了用于实现自定义图表的设计模式和最佳实践ScatterFit
图表作为例子。许多常见的可视化任务,特别是那些需要动态图形的任务,都可以使用适当的图表来执行。设计和创建图表需要预先的开发时间和精力,但是图表可以极大地简化许多可视化工作流程。
2021年出版的