ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MATLAB图例控制:从基础到进阶的实用技巧

2026/8/8 4:18:10 拓冰建站 浏览量
MATLAB图例控制:从基础到进阶的实用技巧

1. 从一次“图例灾难”说起:为什么你需要控制图例

那天下午,我正对着屏幕上一张即将交付给客户的仿真结果图发愁。这张图包含了四条曲线,分别代表四种不同的控制策略在系统响应中的表现。为了让图更清晰,我特意为每条曲线设置了不同的线型和颜色。然而,当我加上legend函数后,整个图例区域几乎占去了四分之一的画布空间,四条图例条目密密麻麻地挤在一起,不仅遮挡了关键的曲线交叉区域,也让整个图面显得臃肿不堪。更糟糕的是,其中有一条曲线是“基准策略”,在报告的正文中已经详细说明,在图例中重复出现显得多余且干扰重点。我需要的是一个干净、突出重点的图,而不是一个包含所有信息的“说明书”。那一刻,我意识到,在 MATLAB 中画图,学会“添加”图例只是第一步,而真正体现功力的,是知道如何“去掉”或“精简”图例。

这不仅仅是美观问题。在学术论文、工程报告、数据可视化演示中,图例的滥用会直接削弱核心信息的传递效率。读者(可能是你的导师、审稿人、客户或领导)的注意力是有限的,一个杂乱无章的图例会让他们花费额外精力去甄别,甚至误解数据的主次关系。因此,掌握精确控制图例显示内容的技巧,是每个经常使用 MATLAB 进行数据可视化的人必须跨过的一道坎。它意味着你从“能画出图”进阶到了“能画出专业、有效、有说服力的图”。

本文将彻底解决这个问题。我们将不局限于简单的legend off,而是深入探讨几种常见且实用的场景:如何从多条目图例中隐藏特定的曲线条目;如何创建不包含某些曲线的全新图例;以及如何对已有图例进行动态的、精细化的编辑。这些方法覆盖了从绘图时预先设计,到出图后临时调整的全流程,确保你在面对任何图例相关的需求时都能游刃有余。

2. 理解核心:图例与图形对象句柄的绑定关系

在动手操作之前,我们必须先理解 MATLAB 中图例工作的基本原理。很多人在试图修改图例时碰壁,根本原因在于对底层机制不清晰。

在 MATLAB 的图形系统中,绘制的每一条曲线、每一组散点、每一个条形图,都是一个独立的“图形对象”。当你调用plot,scatter,bar等函数时,这些函数不仅会在坐标轴上画出图形,还会返回一个或多个“句柄”。这个句柄就像是这个图形对象的身份证号或遥控器,通过它,你可以在之后任意时刻对这个对象的属性进行读取和修改,例如改变它的颜色、线宽、数据,或者——是否在图例中显示。

legend函数,本质上是一个“自动匹配”机制。当你简单地调用legend(‘Label1’, ‘Label2’, …)时,MATLAB 会按照当前坐标轴中图形对象的创建顺序(后创建的在上层),尝试将你提供的标签文本与这些图形对象一一绑定。这个绑定关系,就是图例控制的关键。

一个关键特性是:图例条目默认会为坐标轴中所有支持图例的图形对象创建条目(除非该对象在创建时被显式指定排除)。这就引出了我们的核心需求:我们可能在一个坐标轴上画了多条曲线,但只希望其中几条出现在图例中。

例如,你可能会先画一条辅助的参考线,再画三条主要的数据曲线。参考线不需要图例,但如果你直接调用legend(‘Data A’, ‘Data B’, ‘Data C’),MATLAB 会试图将这四个标签分配给四个图形对象,导致数量不匹配而报错,或者出现你不想要的参考线图例。

因此,所有“去掉某些图例”的高级技巧,都围绕着一个中心思想:告诉legend函数,哪些图形对象需要被纳入图例管理,并为其指定标签。下面我们就从最常见的场景开始。

3. 方法一:绘图时预先规划,仅将目标曲线传入 Legend

这是最直接、最符合直觉,也是代码最清晰的方法。其核心思路是:在调用legend函数时,不仅传入标签文本,还显式传入你需要显示图例的那些曲线对象的句柄。这样,legend就只会为这些指定的对象创建条目,其他对象则被完全忽略。

让我们通过一个对比示例来感受其威力。假设我们要绘制一条正弦曲线、一条余弦曲线和一条作为零基准的黑色虚线。

不推荐的原始做法(会导致多余图例):

x = linspace(0, 2*pi, 100); y1 = sin(x); y2 = cos(x); figure; hold on; plot(x, y1, ‘b-‘, ‘LineWidth’, 1.5); % 曲线1 plot(x, y2, ‘r--‘, ‘LineWidth’, 1.5); % 曲线2 % 画一条不需要图例的参考线 plot([x(1), x(end)], [0, 0], ‘k:‘, ‘LineWidth’, 0.5); % 如果直接这样写,图例会尝试为三条线都分配标签,要么报错,要么出现‘Zero Line’的图例 legend(‘Sine Wave’, ‘Cosine Wave’); % 这里只提供了两个标签,但图里有三个图形对象,行为可能不确定 hold off;

运行上述代码,MATLAB 可能会报错:“The number of legend entries must match the number of data objects which have the ‘DisplayName’ property set, or the number of data objects in the axes if the ‘DisplayName’ property is not set.” 或者,它可能用前两个标签匹配前两个图形对象,但你的参考线依然占据了一个图形对象位置,这并非我们的本意。

推荐的精确控制做法:

x = linspace(0, 2*pi, 100); y1 = sin(x); y2 = cos(x); figure; hold on; h1 = plot(x, y1, ‘b-‘, ‘LineWidth’, 1.5); % 获取正弦曲线的句柄,存入变量 h1 h2 = plot(x, y2, ‘r--‘, ‘LineWidth’, 1.5); % 获取余弦曲线的句柄,存入变量 h2 % 画参考线,我们不关心它的句柄(或者用一个不用于图例的变量存储) plot([x(1), x(end)], [0, 0], ‘k:‘, ‘LineWidth’, 0.5); % 关键步骤:将需要图例的曲线句柄和对应的标签,一起传给 legend legend([h1, h2], {‘Sine Wave’, ‘Cosine Wave’}); hold off;

在这段改进的代码中,plot函数返回的句柄被保存在变量h1h2中。legend([h1, h2], …)这个调用明确地指示 MATLAB:“请只为h1h2这两个图形对象创建图例,标签分别是 ‘Sine Wave’ 和 ‘Cosine Wave’。至于坐标轴上的其他对象(那条黑色虚线参考线),请完全无视它们。”

这种方法的好处非常明显:

  1. 意图清晰:代码明确表达了哪些曲线需要图例,哪些不需要,便于日后维护和他人阅读。
  2. 绝对可靠:不受坐标轴中其他图形对象(如之后添加的注释、补丁等)干扰,只要你不把这些对象的句柄传入legend,它们就不会出现在图例里。
  3. 顺序可控:图例条目的顺序完全由你传入的句柄数组顺序决定,而不是图形对象的创建顺序。你可以自由安排图例的排列。

注意:传入句柄时,确保句柄数组和标签单元格数组的长度严格一致,否则会报错。这是一种“白名单”思维,是你作为绘图者主动、精确地定义图例内容。

4. 方法二:事后编辑,从现有图例中“剔除”特定条目

很多时候,我们可能是在一个已有的脚本上工作,或者一开始没考虑周全,已经生成了一个包含所有曲线图例的图形。这时,我们并不想重新绘制图形,而是希望直接在现有的图例上“动手术”,删除其中一两个条目。MATLAB 提供了这种动态编辑的能力。

实现这一功能的关键在于理解:图例本身也是一个图形对象,它有一个名为String的属性(存储所有标签文本)和一个名为PlotChildren或与子对象相关的属性(关联着对应的图形对象句柄)。修改这些属性,就能改变图例的显示内容。

步骤详解:

  1. 获取图例句柄:首先,你需要拿到当前图例的“遥控器”。这通常在创建图例时保存其句柄,或者通过findobj函数查找。

    % 创建带有完整图例的图 x = linspace(0, 2*pi, 100); figure; plot(x, sin(x), ‘b-‘, ‘DisplayName’, ‘Sine’); hold on; plot(x, cos(x), ‘r--‘, ‘DisplayName’, ‘Cosine’); plot([x(1), x(end)], [0, 0], ‘k:‘, ‘DisplayName’, ‘Zero Line’); % 这次我们给它也加了DisplayName hold off; lgd = legend(‘show’); % ‘show’ 会使用各曲线‘DisplayName’属性。同时将图例句柄保存到变量lgd
  2. 确定要删除的条目索引:你需要知道你想删除的条目是第几个。图例的String属性是一个单元格数组,按顺序存储着所有标签。

    current_labels = lgd.String; % 返回类似 {‘Sine’, ‘Cosine’, ‘Zero Line’}

    假设我们想删除 ‘Zero Line’ 这个条目,它在数组中是第3个元素。

  3. 删除标签和对应的图形对象关联:仅仅删除String中的文本是不够的,还必须同步处理图例内部与图形对象的关联。更稳健的方法是,重新构建我们想要的标签和句柄列表。

    % 获取当前坐标轴中所有子对象(通常是各种图形对象) ax = gca; % 获取当前坐标轴句柄 children = ax.Children; % 获取坐标轴的所有子对象,顺序通常与创建顺序相反(后创建的在前) % 我们需要找到那些有‘DisplayName’且不是我们想删除的曲线 % 但更简单的方法是:直接指定我们要保留的条目 % 我们知道想保留 ‘Sine’ 和 ‘Cosine’ % 我们可以通过图例句柄的‘PlotChildren’属性来获取当前图例关联的对象句柄 % 注意:PlotChildren的顺序可能与String的顺序对应 if ~isempty(lgd.PlotChildren) % 假设 lgd.PlotChildren(3) 对应 ‘Zero Line’ % 我们可以创建一个新的句柄数组和标签数组,排除第三个 new_handles = lgd.PlotChildren; new_labels = lgd.String; index_to_remove = 3; % 要删除的条目索引 new_handles(index_to_remove) = []; new_labels(index_to_remove) = []; % 使用新的句柄和标签重新创建图例 delete(lgd); % 删除旧的图例 legend(new_handles, new_labels); end

    上面的方法略显繁琐,且依赖于PlotChildren属性的稳定性。一个更通用、更清晰的事后处理方法是:在绘图时保存所有需要参与图例的曲线句柄,即使一开始创建了完整图例,我们也可以利用这些句柄来重建

更优的事后处理实践:

x = linspace(0, 2*pi, 100); figure; hold on; h_sin = plot(x, sin(x), ‘b-‘, ‘DisplayName’, ‘Sine’); h_cos = plot(x, cos(x), ‘r--‘, ‘DisplayName’, ‘Cosine’); h_zero = plot([x(1), x(end)], [0, 0], ‘k:‘, ‘DisplayName’, ‘Zero Line’); % 保存句柄,但后续可能不用 hold off; % 1. 初始创建包含所有的图例 lgd = legend(‘show’); % 此时图例包含三项 % 2. 后来决定去掉 ‘Zero Line’ % 方法:直接使用我们保存的句柄,重新创建仅包含目标曲线的图例 delete(lgd); % 删除旧图例 legend([h_sin, h_cos], {‘Sine’, ‘Cosine’}); % 创建新图例

这种方法结合了方法一的精确性和事后处理的灵活性。它要求你在绘图时有意识地保存句柄,这是一个非常好的编程习惯,为后续任何图形修改提供了最大的便利。

5. 方法三:利用 DisplayName 属性与 legend(‘off’) 的局部控制

除了上述两种主要方法,还有一些技巧性的组合拳,可以应对更特殊的情况。

技巧1:选择性设置 DisplayNameDisplayName是图形对象的一个属性。当你调用legend(‘show’)时,MATLAB 会自动为所有设置了DisplayName属性的图形对象生成图例条目。利用这一点,你可以只给你想要的曲线设置这个属性。

figure; hold on; plot(x, sin(x), ‘b-‘, ‘DisplayName’, ‘Sine’); % 设置,会出现在图例 plot(x, cos(x), ‘r--‘, ‘DisplayName’, ‘Cosine’); % 设置,会出现在图例 plot([x(1), x(end)], [0, 0], ‘k:‘); % 不设置 DisplayName hold off; legend(‘show’); % 此时图例只显示 ‘Sine’ 和 ‘Cosine’

这本质上和方法一(传入句柄)是等价的,但语法上更简洁一些。不过,它依赖于legend(‘show’)这个特定调用,并且如果坐标轴内有其他非数据对象也设置了DisplayName(比如某些注释文本),也可能被意外加入图例。

技巧2:临时关闭与恢复如果你只是想在一段时间内隐藏图例以便截图或观察,之后还需要恢复,可以使用legend(‘off’)legend(‘show’)

lgd = legend(…); % 创建图例 % … 进行一些操作或观察 … legend(‘off’); % 隐藏图例 % … 可能需要截图 … legend(lgd); % 或者 legend(‘show’) 来重新显示。注意,legend(‘off’)会删除图例对象,用保存的句柄lgd可以恢复,但用‘show’可能按当前状态重建。

需要注意的是,legend(‘off’)会直接删除图例对象。如果你没有保存其句柄,后续再调用legend(‘show’)会根据当前坐标轴状态重新生成,这可能不是你之前的样子。所以,如果打算恢复,最好先保存图例句柄lgd,然后使用set(lgd, ‘Visible’, ‘off’)来隐藏它,而不是删除它。

lgd = legend(…); set(lgd, ‘Visible’, ‘off’); % 隐藏图例,但对象还在 % … 其他操作 … set(lgd, ‘Visible’, ‘on’); % 重新显示

这是一种更安全的“隐藏”方式。

6. 实战进阶:在多子图与复杂图形中的图例管理

单一坐标轴内的图例控制是基础。在实际项目中,我们更常遇到的是包含多个子图(subplot)的图形,或者一个坐标轴内包含多种类型图形对象(如plotscatter混合)的复杂情况。这些场景下的图例管理,原则相通,但需要更多技巧。

场景一:多子图(subplot)中共享或独立图例

假设我们创建了一个 2x2 的子图阵列,每个子图都画了多条曲线。我们可能希望每个子图有自己的独立图例,或者只在最后一个子图放置一个涵盖所有公共曲线的图例。

  • 为每个子图创建独立图例(并可能删除某个子图的特定条目)

    figure; for i = 1:4 ax(i) = subplot(2,2,i); hold(ax(i), ‘on’); % 在每个子图绘制多条曲线,并保存句柄 h_line1(i) = plot(ax(i), …, ‘DisplayName’, ‘Model A’); h_line2(i) = plot(ax(i), …, ‘DisplayName’, ‘Model B’); if i ~= 3 % 假设我们不想在第三个子图显示 ‘Model B’ 的图例 % 方法一:创建图例时精确指定句柄和标签 legend(ax(i), [h_line1(i), h_line2(i)], {‘Model A’, ‘Model B’}); else % 第三个子图只显示 ‘Model A’ 的图例 legend(ax(i), h_line1(i), {‘Model A’}); end hold(ax(i), ‘off’); end

    关键在于针对每个坐标轴句柄(ax(i))单独操作其图例。你可以为每个子图自由决定图例的内容和样式。

  • 为整个图形窗口创建一个共享图例: 有时,所有子图的曲线含义相同,在角落放置一个共享图例更节省空间、更美观。这时,你需要在一个“虚拟”的、不显示数据的坐标轴上创建图例。

    figure; % 创建子图并绘图,保存所有需要图例的曲线句柄到一个数组 all_handles = []; all_labels = {}; for i = 1:4 ax(i) = subplot(2,2,i); h1 = plot(ax(i), …, ‘b-‘); all_handles = [all_handles, h1]; if i == 1 % 标签只需要添加一次 all_labels = [all_labels, {‘Data Series 1’}]; end % … 可能还有其他曲线 end % 在图形窗口的某个位置(如右下角)创建不可见坐标轴,用于放置图例 lgd_ax = axes(‘Position’, [0.78, 0.15, 0.15, 0.2], ‘Visible’, ‘off’); % 位置可调 % 在这个坐标轴上,用之前收集的句柄创建图例 legend(lgd_ax, all_handles, all_labels, ‘Location’, ‘northwest’); % 注意:需要确保 all_handles 中的句柄对应的图形对象是可见的,并且标签顺序正确。

    这种方法更高级,需要仔细调整图例坐标轴的位置 (Position),使其不与子图重叠。

场景二:混合图形类型(如 plot 与 scatter)

当坐标轴内同时有plot线对象和scatter散点对象时,legend默认会为所有对象创建条目。如果你只想为其中一种类型创建图例,方法依然是传入特定对象的句柄

x = 1:10; y1 = rand(1,10); y2 = rand(1,10) + 2; figure; hold on; h_plot = plot(x, y1, ‘b-o’); % 带圆标记的线,返回一个句柄 h_scatter = scatter(x, y2, 100, ‘r’, ‘filled’); % 散点图,返回一个句柄 % 假设我们只想为散点图添加图例 legend(h_scatter, ‘Scattered Data’); hold off;

即使h_plot这条线也在图上,但由于我们没有将其句柄传入legend,它就不会出现在图例中。这再次证明了句柄控制法的普适性。

7. 避坑指南与最佳实践心得

在长期与 MATLAB 图例“斗争”的过程中,我积累了一些容易踩坑的点和最佳实践,希望能帮你节省时间。

坑1:图例顺序混乱问题:图例条目的顺序不是你想要的,或者添加/删除条目后顺序错乱。 根因:图例默认顺序与图形对象创建顺序(或坐标轴Children顺序)有关,这个顺序可能和你的绘图顺序不一致,尤其是使用了hold on之后。 解决方案:

  • 首选方法:使用legend([handle1, handle2, …], {‘label1’, ‘label2’, …})语法,通过句柄数组明确指定顺序。这是最可靠的方式。
  • 备用方法:在绘图后,获取坐标轴的子对象ax.Children,并理解其顺序(通常是后创建的在前)。然后根据这个顺序来调整传入legend的句柄数组或DisplayName的设置顺序。

坑2:删除图例条目后,对应曲线无法通过图例交互隐藏问题:在 MATLAB 图形窗口中,点击图例上的条目可以隐藏/显示对应的曲线。如果你用“删除String属性”的方法粗暴地移除图例条目,这个交互关联可能不会正确更新,导致行为异常。 解决方案:尽量避免直接修改lgd.Stringlgd.PlotChildren来删除条目。推荐使用“重建法”:即delete(lgd)删除旧图例,然后用筛选后的句柄和标签数组legend(new_handles, new_labels)创建新图例。这样能保证交互功能的完整性。

坑3:图例位置遮挡数据问题:自动放置的图例 (‘Location’, ‘best’) 有时会挡住关键数据点。 解决方案:养成手动指定图例位置的习惯。legend(…, ‘Location’, ‘northoutside’)可以将图例放在坐标轴上方外部,‘southeast’放在右下角内部等。使用‘Position’属性可以更精确地控制坐标点位置。在最终出图前,花几秒钟调整图例位置是值得的。

最佳实践总结:

  1. 句柄是王道:只要绘图,就习惯性地保存图形对象的句柄,例如h1 = plot(…)。这是后续进行任何定制化操作(修改样式、控制图例)的基础。
  2. 创建即精确:在调用legend时,尽可能使用传入图形对象句柄的语法。这从一开始就明确了图例的涵盖范围,代码意图清晰,避免了后续的混乱。
  3. 一事一图例:如果一个坐标轴内的图形对象过于复杂,考虑是否可以用多个子图来分解,或者将辅助线、背景着色等不需要图例的元素与主要数据曲线分开管理。
  4. 保持简洁:图例不是越全越好。只标注那些真正需要区分、且在正文中未充分说明的数据系列。辅助线、参考线、标准线等,通常可以通过线型、颜色和正文描述来识别,无需占用图例空间。
  5. 测试交互:如果你的图形需要交互展示,在完成图例设置后,点击图例条目测试一下隐藏/显示功能是否正常工作,确保关联正确。

控制图例,本质上是在控制信息的呈现优先级。一个精炼、准确的图例,能让你的图形从“正确”走向“优雅”,从“展示数据”升维到“传达洞察”。希望这些方法能成为你 MATLAB 可视化工具箱中的得力武器。