技术文章和通讯

用MATLAB面向对象编程创建专门的图表

作者:Ken Deeley和David Sampson, MathWorks


发展先进MATLAB®可视化通常涉及管理多个低级图形对象。对于包含动态更新的图形的应用程序尤其如此。这样的应用程序可能需要耗时的编程。

一个图表对象提供用于创建自定义可视化的高级应用程序编程接口(API)。图表不仅为最终用户提供了方便的可视化API;它还消除了用户实现低级图形编程的需要。

MATLAB包含一个面向对象的框架,用于通过以下容器超类开发自定义图表:

本文提供了使用该框架创建和实现自定义图表的逐步指南,包括设计模式和最佳实践。通过一个包含最佳拟合线的散点图来说明这些步骤。主题包括:

  • 编写标准图表模板
  • 写表的设置而且更新方法
  • 封装数据和图形
  • 为最终用户提供高级API
  • 包括交互控制

图表的例子

MATLAB中有几个图表,包括的热图图表,它可视化覆盖在彩色网格方格上的矩阵值,以及geobubble图表,它提供了在地图上绘制离散数据点的快速方法(图1)。

图1。热图和地理气泡图。

图1。的的热图而且geobubble图表。

此外,我们还创建了几个特定于应用程序的图表(图2)。您可以从以下网站下载这些图表以及本文中使用的MATLAB代码文件交换

图2。可在File Exchange上下载自定义图表。

图2。可在File Exchange上下载自定义图表。

创建2D散点图

假设我们想要创建一个包含最佳拟合对应线的2D散点图(图3)散射函数可视化离散(x, y)数据点和fitlm函数从统计和机器学习工具箱™计算最佳拟合线。

Rng ("default") x = randn(1,000, 1);Y = 2*x + 1 + randn(size(x));s = scatter(x, y, 6, "filled", "MarkerFaceAlpha", 0.5);M = fitlm(x, y);按住plot(x, m. fitting, "LineWidth", 2)
图3。最佳拟合线和底层分散数据。

图3。最佳拟合线和底层分散数据。

上面的代码对于静态可视化已经足够了。然而,如果应用程序要求数据是可动态修改的,那么我们会遇到几个挑战:

  • 如果我们替换XDataYData使用与当前数组长度相同的新数组XData,最佳拟合行没有动态更新(图4)。
    s.XData = s.XData + 4;
图4。在更改散点图的XData后,不更新最佳拟合线。

图4。更改后,不更新最合适行XData散点图。

  • 散射对象年代如果其数据属性(XDataYData)设置为一个比当前数组长或短的数组。
    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行为-也就是说,图表会自动创建一个数字。

使用函数创建图表有一些缺点:

  • 在创建图表之后,不能修改数据。
  • 要更改图表数据,您需要再次调用该函数来重新创建图表。
  • 最终用户很难找到可配置的图表参数(例如,标签和装饰图形属性,如颜色、线条样式等)。

将图表作为类实现具有函数提供的代码封装和可重用性的所有好处,同时还允许您修改图表而不需要重新创建它。

选择图表超类:ChartContainerComponentContainer?

图表必须实现为处理类,以便可以对其进行就地修改。为了与MATLAB图形对象的一致性,图表应该支持得到/除了标准点表示法之外,属性的语法。这两个ChartContainer而且ComponentContainer是否处理类和提供支持得到/语法,这意味着您可以从这些超类之一派生自定义图表。

classdefScatterFit < matlab.ui.componentcontainer.ComponentContainer

因此,对于任何属性,都自动支持表1所示的语法。

语法类型 访问 修改
点符号 x = SF.XData; 科幻小说。XData = x;
获取/设置 x = get(SF, "XData"); 集(科幻“XData”x)

表1。图表属性的访问和修改语法。

根据图表的要求选择一个超类。如果图表不需要交互式的面向用户的控件,如按钮、下拉菜单和复选框,则从其中派生图表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来创建图形对象,因为它支持对单个标记大小和颜色的后续更改(而单个行对象则不支持)。

原始图形函数 高级图形函数
情节
表面 冲浪
补丁 填满

表2。基本和高级图形函数的示例。

稍后我们将返回到图表的更新方法。

封装海图数据和图形

在大多数图表中,底层图形包括至少一个轴对象及其内容(例如,线或面对象)或轴对等对象(例如,图例或颜色条)。图表还维护内部数据属性,以确保公共属性正确地呈现给最终用户。我们将底层图形和内部数据存储为私有图表属性。例如,ScatterFitChart维护以下私有属性。

属性(访问=私人)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)。

图5。ScatterFit图表API。

图5。ScatterFit图表的API。

用户可以使用表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)。

图6。交互式图表控件的例子。

图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中,在那里它可以像任何其他组件一样在画布中交互使用。

图7。与应用程序设计器集成的自定义图表。

图7。与应用程序设计器集成的自定义图表。

总结

在本文中,我们描述了用于实现自定义图表的设计模式和最佳实践ScatterFit图表作为例子。许多常见的可视化任务,特别是那些需要动态图形的任务,都可以使用适当的图表来执行。设计和创建图表需要预先的开发时间和精力,但是图表可以极大地简化许多可视化工作流程。

2021年出版的

2022世界杯八强谁会赢?产品使用

了解更多

    查看相关功能的文章

    Baidu
    map