开发高级MATLAB可视化效果通常需要管理多个低层的图形对象,包含动态更新图形的应用程序更是如此。这种应用程序可能需要非常耗时的编程。Chart 对象可提供高级应用程序编程接口(API),实现可视化的自定义创建。图表可为最终用户提供方便的可视化 API,无需用户执行低层图形编程。
本文以包含最佳拟合线的散点图为主要示例,通过分步指导展示如何使用 MATLAB 面向对象的编程创建和实现自定义图表,包括:
编写标准图表模板
编写构造方法
使用 private 属性封装图表数据和图形
使用 Dependent 属性创建高级可视化 API
管理图表生命周期
使用继承简化附加图表的开发
图表示例
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 最佳拟合线和底层的分散数据
以上代码可满足静态可视化的需求。但是,如果应用程序要求对数据进行动态修改,我们会遇到几个难题:
如果使用长度与当前 XData 相同的新数组替换 XData 或 Ydata,最佳拟合线不会动态更新(图 4)。
图4 最佳拟合线在更改散点图的 XData 后未更新
如果 Scatter 对象 s 的任一数据属性(XData 或 YData)设置为长度比当前数组长或短的数组,该对象会发出警告且不会执行图形更新。
我们可以通过设计一个图表 ScatterFit 来解决这些难题。
构建图表代码:函数或类?
函数将代码封装为可重用单元,用户无需重复代码即可创建多个图表。
请注意,此函数需要输入两个数据(x 和 y)。您可以指定图形父项 f(例如,图形)作为第一个输入参数。
scatterfit(x,y) 指定输入的两个数据
scatterfit(f,x,y) 指定图形父项和数据
在第一种情况下,该函数展示自动生成的行为,即将自动创建图表的图形。
使用函数创建图表具有某些缺点:
图表创建后数据无法修改
要更改图表数据,指定不同的数据输入后,需要再次调用函数重新创建图表
最终用户很难查找可配置的图表参数(如注释和分散/线属性)
用类来实现图表具有代码封装及方法可重用性等好处,同时支持对图表进行修改。
定义图表类
为了与 MATLAB 图形对象保持一致,我们将图表实现为 handle 类,以便在适当位置修改图表。我们支持在图表属性中使用点记法和 get/set 语法。要实现这一目标,我们从预定义 matlab.mixin.SetGet 类(本身是 handle 类)中派生出 ScatterFit 图表。
因此,任何属性都将自动支持表 1 中显示的语法。
表1图表属性的访问和修改语法
编写图表类的构造方法
构造方法是类定义中的一个函数,用于构造图表对象。首先,将代码从 scatterfit 函数复制到我们的图表构造方法之中。随后,进行以下修改支持所需图表行为:
输入参数。我们使用 varargin 支持在所有图表输入参数中使用名称值对。这意味着不需要指定输入参数,所有输入值均可选。
父图形。不同于函数,如果已指定图形构造中没有输入 Parent,则无法为图表自动创建该输入。我们使用空 Parent 创建 axes 对象。
请注意,此行为与 plot 和 scatter 等便捷函数的行为不同,后两者可以自动生成输入。如果用户将 Parent 指定为输入参数,则随后将在构造方法中对其进行设置。
图表图形。我们可创建和存储图形所需的任何图形对象。大多数图表需要 axes 对象以及线或贴片对象等 axes 内容。在 ScatterFit 图表中,我们需要 Scatter 对象和 Line 对象。
图形配置。我们可通过设置任何所需属性配置图表图形。例如,我创建标签或标题等注释、设置 axes 的特定视图、添加网格,或者调整颜色、样式或线宽。
用户指定的输入。我们可设置最终用户提供的任何名称值对参数。由于图表派生自 matlab.mixin.SetGet,实现这一点非常容易:
如果用户已作为名称值对输入参数提供数据属性,在此处设置这些数据(XData 和 YData)。我们还注意到,此编码方法可确保用户在指定名称值对时出现的任何错误都将被图表的属性 set 方法发现和解决(稍后讨论)。
如可行,我们将使用基元对象创建图表图形,因为高级便捷函数在调用时将重置多个现有 axes 属性(图2)。但是,这条原则存在例外情况:在 ScatterFit 内部,我们将使用 scatter 函数创建 Scatter 图形对象,因为它支持对个别标记的尺寸和颜色进行后续更改。
表2 基元和高级图形函数的示例
封装图表数据和图形
在大多数图表中,底层图形包含至少一个 axes 对象及其内容(例如线或面对象)或多个对等的 axes 对象(例如,图例或彩条)。这些图表还包含内部数据属性,以确保公共属性之间保持一致。我们存储底层图形和内部数据为专有图表属性。例如,ScatterFit 图表会维护以下专有属性:
我们使用命名约定 XData_ 指示该版本是图表数据的专有内部版本。用户可见的对应公共数据属性将命名为 XData。
使用 private 属性主要有三个目的:
限制底层图形的可见性,隐藏实现细节,减少 API 中的视觉混乱
限制对底层图形的访问,减少绕过 API 的几率
轻松同步图表数据(例如,我们需要对 ScatterFit 的 XData 和 YData 属性进行关联)
提供可视化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 属性之一存储。要正确管理图表的生命周期,我们需要确保两种行为:
删除 axes(例如,通过关闭主图形窗口)即删除图表。如果不能保证这一点,一旦修改图表的数据属性,将导致 MATLAB 尝试更新删除的图形对象,从而引发错误
删除图表(例如,在其超出范围时或当其句柄显式删除时)即删除 axes。如果做不到这一点,则在删除图表后,仍会残留其静态图形
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。此方法进而将删除图表对象。
-
数据
+关注
关注
8文章
6884浏览量
88813 -
函数
+关注
关注
3文章
4304浏览量
62421 -
图表
+关注
关注
0文章
25浏览量
8850
发布评论请先 登录
相关推荐
评论