
上周帮同学改论文配图看到他把五种滤波器的幅频响应曲线画在同一个坐标系里图例写的是 H1、H2、H3、H4、H5。我问他 H3 具体是哪种滤波器他说得翻一下代码才知道。图是自包含的可审稿人不可能为了弄清 H3 去翻你的程序。我告诉他给 legend 加一个标题就行。他当场愣住MATLAB 的图例还能加标题能而且做法非常简单。这篇文章就把 MATLAB 设置 legend 加标题这件事完整拆开核心写法、样式控制、老版本兼容、批量出图的工程化处理以及我平时遇到的各种坑。1. 图例标题一个被绝大多数MATLAB教程忽略的细节1.1 哪些场景强烈需要图例标题很多人觉得图例就是把每条线叫什么标出来不需要额外标题。这个理解在小图里没问题但只要曲线一多或者同一个图上出现多种含义的曲线图例就远远不够了。我总结过几个最常需要图例标题的场景都是实际项目里反复遇到的。参数扫描结果对比同一张图里画学习率 0.1、0.01、0.001 的 Loss 曲线图例只写 0.1、0.01、0.001 的话读者根本不知道这一列数字代表什么。如果图例标题写着“学习率”信息就完整了。多传感器或通道数据频响曲线 H1 到 H5用“滤波器型号”当标题传感器位置、通道编号同理。曲线一多没有分组标题很难快速定位。训练、验证、测试曲线图例里写 train、val、test加上“数据集”或“阶段”当标题语义会更清楚。尤其当你有不止一个数据集时光靠 train、val、test 根本区分不了。论文配图很多期刊要求图尽量自包含意思是读者不读正文也能看懂图。图例标题是成本最低的自包含手段之一一行字就能让图的独立表达能力上一个台阶。我自己的一个典型例子训练一个模型要看它在三个公开数据集上的表现差异。图片里画了三条 Loss 曲线图例分别是 datasetA、datasetB、datasetC。如果只写缩写别人可能还要去正文里找数据集全称。加上图例标题“评估数据集”之后整张图单独拿出去也能讲清楚。这个需求在实验类工作里太常见了。1.2 legend对象的结构标题藏在Text对象里为什么这个功能显得这么冷门我觉得很大程度是姿势问题。很多人在 MATLAB 里画图写完了 plot 和 legend 就不再管了legend 返回的是什么都没注意过。实际上legend是一个函数它返回一个Legend对象。这个对象下面挂着两类子对象一类是若干Text条目对象就是每个图例项另一类是Title对象本质也是一个Text对象相当于一组专门用来显示标题的文本。默认情况下这个 Title 是空的所以界面上看不到。用文本结构来看就是这样lgd (Legend 对象) ├── Text(1)条目1 ├── Text(2)条目2 └── Title (Text 对象) ├── String空字符串默认 └── FontSize / FontWeight / Color / Interpreter ...当你给lgd.Title.String赋值时MATLAB 就在图例框顶部画出一行标题。这个标题和图例项共用同一个容器缩放、位置调整时不会脱离图例区域。这就是用对象的思路去理解和修改图形元素比用鼠标点选更可控也比记一堆 get/set 更直观。2. 三种给legend加标题的写法2.1 推荐写法lgd.Title.String先看最标准、我平时最常用的写法。核心就两步创建 legend 时保存句柄然后给Title.String赋值。x 0:0.01:2*pi; y1 sin(x); y2 cos(x); figure(Color, w); plot(x, y1, b-, LineWidth, 1.5); hold on; plot(x, y2, r--, LineWidth, 1.5); grid on; lgd legend(sin(x), cos(x), Location, northeast); lgd.Title.String 三角函数;运行后图例框上方会出现一行文字“三角函数”。这里的lgd是 Legend 对象的句柄lgd.Title返回 Title 对象再用.String给它赋值。这是 MATLAB 新式面向对象的属性访问方式R2018b 以后体验最好更早版本如果支持直接点语法也能用不支持就退回 get/set。有几个细节值得注意。第一创建 legend 时一定要把返回值存下来写成lgd legend(...)而不是光写legend(sin(x),cos(x))否则后续没有入口设置任何属性。第二lgd.Title.String赋值后可以继续链式设置其他属性不需要重画图例MATLAB 会自动重绘。第三如果图例原本的位置在边界加了标题之后可能显得拥挤可以顺手改成Location,northeastoutside这个后面踩坑部分会细讲。2.2 title(lgd, ...) 老式习惯如果你在维护一些老脚本可能会看到这种写法直接对图例调用 title 函数。lgd legend(sin(x), cos(x)); title(lgd, 三角函数);它和lgd.Title.String 三角函数本质是同一件事title 函数找到 lgd 的 Title 属性把 String 写进去。这种写法在早期 MATLAB 版本里很常见后来官方把属性访问方式做得更统一了大家才更多改为直接点语法。顺带提一句title(lgd, ...)也有返回值返回的是 Title 对象本身。所以也可以这样连续设置t title(lgd, 三角函数); t.FontSize 12; t.FontWeight bold;不过我个人的习惯还是用lgd.Title.String因为代码的可读性更强一看就知道改的是图例标题而不是坐标轴标题。2.3 get/set 句柄式写法老代码里还有一种更古早的姿势用 get 和 set 去拿属性。比如lgd legend(sin(x), cos(x)); t get(lgd, Title); set(t, String, 三角函数, FontSize, 12);这种写法在 R2014b 图形系统更新之前几乎是标准做法。现在新写的代码不需要这么做但看懂它很有必要。因为很多网上流传的旧代码、论坛回答、甚至你自己三年前的脚本用的都是这套语法。看到get(lgd, Title)时你要知道它拿到的就是一个 Text 对象和在 2.1 里操作的对象完全一样。如果你正在用比较老的 MATLAB 版本比如 R2014a 之前点语法在某些场景下可能不稳定用 get/set 反而更可靠。判断标准很简单先在命令行跑一下lgd.Title能正常显示就是支持直接访问报错就退回 get/set。3. 标题样式调优让图例标题达到论文级排版3.1 字体、字号、颜色与粗体图例标题写出来只是第一步论文配图里字号、字体、颜色这些细活才是真正花时间的。首先看最基本的样式属性。lgd.Title.String 不同学习率; lgd.Title.FontSize 11; lgd.Title.FontWeight bold; lgd.Title.Color [0.15 0.15 0.15];这里FontSize的单位是磅point一般论文图里 9 到 12 磅比较合适。FontWeight可选normal或bold要不要加粗取决于整体风格。我个人建议标题别加粗用统一字号和深灰色就够了加粗有时候会显得抢眼反而不协调。Color是 RGB 三元素向量范围从 0 到 1比如[0 0 0]是纯黑[0.15 0.15 0.15]是深灰。如果你的图中图例条目文字已经调过字体比如用lgd.FontSize 10设置过条目字号注意 Title 的字号是独立记录的不会跟着条目字号变化。所以每次设置完条目样式要单独给 Title 设置一遍否则它会沿用默认值看起来跟图例条目风格不搭。这个小细节特别容易忽略。3.2 用TeX/LaTeX解释器写出数学符号科研图表里经常要在标题里写数学符号比如 α、β、μ。MATLAB 默认的文本解释器是 TeX直接用\alpha、\beta这类转义序列就能渲染。lgd.Title.String 学习率 \alpha 对比; lgd.Title.Interpreter tex; % 默认就是 tex可省略如果想要更复杂的公式布局比如真正的分数、求和符号、矩阵形式可以把 Interpreter 切成latexlgd.Title.Interpreter latex; lgd.Title.String $\alpha 0.1$ vs $\alpha 0.01$;这里有一个重要提醒改成 latex 之后标题里的中文字符大概率会渲染异常因为 MATLAB 自带的 LaTeX 环境对 CJK 字符支持不是很好。所以我的习惯是标题以中文为主时用默认 tex 解释器标题里要写复杂公式时标题本身写成英文或纯公式避免中英混合。另外TeX 解释器下_表示下标、^表示上标。如果标题里要显示一个正常的下划线比如文件名my_file必须写my\_file否则下划线后面内容会变成下标。这个问题在批量生成文件名相关图表时几乎必踩。3.3 多行标题与自动换行问题图例标题不会根据图例框宽度自动换行。默认情况下你给多长的String它就在一行里显示多长就算超出图例框边界也不会自动断行。所以需要多行标题时手动用 cell 数组指定每一行的内容lgd.Title.String {不同学习率下, 损失收敛曲线};这样设置之后图例上方会显示两行文本第一行是“不同学习率下”第二行是“损失收敛曲线”行间距由 MATLAB 根据字号自动处理。通常“主标题加副标题”的组合足够应付大多数场景比如第一行写分类名第二行写更具体的说明。我在实际工作中经常遇到某行做得特别长结果把图例框撑得很大把坐标系遮挡了一大片。解决办法有两个一是缩短文字把非必要信息移到图标题里二是用多行 cell 数组把长句子拆开。相比之下后者更好用因为它不牺牲信息量而且拆行之后图例框宽度会回落。4. 老版本MATLAB的兼容方案4.1 各版本下的支持情况MATLAB 版本段建议写法实测说明R2018b 及以后lgd.Title.String ...直接点语法手感最顺推荐R2014b 到 R2018aget(lgd,Title)set部分点语法可用建议先测试lgd.TitleR2014a 及更早text/annotation手动模拟直接上旁路方案这个表不是官方文档是我在几种常见版本上的使用手感总结。判断你当前版本支不支持最直接的方法是跑一句a lgd.Title不报错就说明可用。如果你的项目还钉在比较老的版本上别着急下面两种旁路方案也能做到类似效果。4.2 用text对象在legend上方模拟标题思路很简单图例框有一个Position属性表示它在 axes 坐标系中的归一化位置。拿到这个位置之后在 legend 框上方放一个 text 对象内容就是标题。figure(Color,w); plot(1:10, rand(1,10)); hold on; plot(1:10, rand(1,10)); lgd legend(A, B, Location, northeast); p lgd.Position; p(2) p(2) 0.04; % 把图例框稍微上移给标题留位置 lgd.Position p; text(p(1) p(3)/2, p(2) p(4) 0.01, ... 数据集对比, ... Units, normalized, ... HorizontalAlignment, center, ... FontSize, 10, ... FontWeight, bold);这里有几个操作要解释一下。lgd.Position返回[left bottom width height]四个值单位是相对于 axes 的归一化坐标。图例框上移一点是为了不让标题和线条重叠。text的Units也设置成normalized这样坐标原点一致计算才不出错。p(1)p(3)/2是图例框水平中心p(2)p(4)0.01是图例框正上方稍高的位置。这种做法的缺点是需要手动调几个偏移量一旦 Figure 大小调整标题和图例的相对位置可能要和Position一起再调一次。适合临时救急不适合放进需要反复改图的流水线。4.3 用annotation文本框模拟不想和 axes 坐标纠缠的话可以直接用annotation在 Figure 层面画一个文本框。它的坐标是相对整个 Figure 窗口的归一化范围 0 到 1调整窗口大小时相对位置更稳定。annotation(textbox, [0.70, 0.86, 0.15, 0.04], ... String, 标题, ... LineStyle, none, ... % 不显示线框 HorizontalAlignment, center, ... FontSize, 10, ... FontWeight, bold);方括号里的四个数字分别是[左下角x, 左下角y, 宽度, 高度]单位是 Figure 归一化坐标。难点在于这些数字要靠经验估计不同尺寸的图需要微调。所以在老版本上我通常先用 4.2 的 text 方案因为它至少以 legend 的 Position 为基准比纯靠肉眼估坐标靠谱得多。5. 批量出图时如何优雅地管理图例标题5.1 循环里动态设置标题写论文的人最常遇到的一个情况一组实验要出几十张图每张图的图例标题都不一样。这时候千万不要一张图一张图手工调直接写循环。alphas [0.1, 0.01, 0.001]; for k 1:numel(alphas) fig figure(Color, w); % 模拟两组实验数据 plot(1:100, cumsum(randn(1, 100))); hold on; plot(1:100, cumsum(randn(1, 100) * 0.5)); lg legend(实验组, 对照组, Location, best); lg.Title.String sprintf(学习率 \\alpha %.3f, alphas(k)); exportgraphics(fig, sprintf(result_%d.png, k), Resolution, 150); close(fig); end这段代码里有几个容易被坑的点。第一sprintf(学习率 \\alpha %.3f, alphas(k))中的\\alpha在 sprintf 里转义之后会变成\alpha再被 legend 的 TeX 解释器渲染成 α。如果你只在 sprintf 里写\alphaMATLAB 会提示非法转义字符或者输出一个奇怪的字符。第二每一轮循环都新建 figure出图后立刻close否则几百张图堆在内存里后面会卡到想砸电脑。第三exportgraphics在 R2020a 之后可用老版本可以用print(fig, ...)替代。循环出图的另一个优势是风格统一一旦你发现某个字号不合适只需要改一行循环里的代码全部图片都会更新。这也是我推荐所有绘图配置都走代码的原因。5.2 封装统一风格的设置函数当项目里有多个人、多段脚本都在出图时最好把图例标题样式的公共逻辑抽成一个函数。下面这个函数是我常用的模板function styleLgdTitle(lgd, txt) % 统一设置图例标题样式 if ~isprop(lgd, Title) title(lgd, txt); end lgd.Title.String txt; lgd.Title.FontSize 10; lgd.Title.FontWeight bold; lgd.Title.Color [0.2 0.2 0.2]; lgd.Title.Interpreter latex; endisprop(lgd, Title)这行是用来做兼容性判断的如果当前版本直接支持 Title 属性就走点语法不支持就先title(lgd, txt)兜底。这样同一个函数在老版本和新版本上都能跑。脚本里每次建完图例调用一句styleLgdTitle(lgd, 实验组对比)就行。使用这个函数最大的收益是改一处、改所有。比如论文倒排期前突然要统一把标题从 10 磅改成 9 磅你不需要去几百行脚本里搜索 FontSize改这一个文件就够了。这个习惯我从做实验研究开始一直用到现在非常值。5.3 多子图图例的布局协同如果是 subplot 或者 tiledlayout 多子图每个子图都有自己的 legend标题各自独立设置。具体做法和单图一样只是要注意图例框别把其他子图的内容盖住。tiledlayout(1, 2); ax1 nexttile; plot(ax1, 1:10, rand(1, 10)); hold on; plot(ax1, 1:10, rand(1, 10) * 2); lgd1 legend(ax1, A, B); lgd1.Title.String 子图1; ax2 nexttile; plot(ax2, 1:10, rand(1, 10)); hold on; plot(ax2, 1:10, rand(1, 10) * 2); lgd2 legend(ax2, A, B); lgd2.Title.String 子图2;当某个子图的图例条目特别多、或标题特别长时图例可能会把该子图的曲线数据盖住一大片。这时候可以试试把图例的Location设为eastoutside它会自动把图例放到子图右侧区域不进坐标系内部。如果图例条目多到一列放不下可以设置NumColumns让条目排列成多列比如lgd1.NumColumns 2这样图例整体高度下降标题和条目不会挤在一起。这个属性不是所有版本都有使用前可以用isprop(lgd1, NumColumns)判断一下。6. 实际踩过的坑与解决方案6.1 标题设置了却不显示先讲最诡异的一个。你在代码里写了lgd.Title.String xxx运行之后屏幕上也确实有了标题但等你想编辑图例的时候标题又消失了。这种问题九成九出在你后来又把 legend 重新创建了一次。举个例子lgd legend(曲线A, 曲线B); lgd.Title.String 对比结果; % 中间隔了很多行代码 legend(曲线A, 曲线B, 曲线C, Location, best);第二次调用legend(...)会重建一个图例对象之前那个带着标题的lgd已经不在图上挂载了新图例对象自然没有标题。解决方法是不要重复调用legend(...)统一用同一个句柄更新。上面的场景可以改成lgd legend(曲线A, 曲线B); lgd.Title.String 对比结果; % 需要加曲线C时 legend(lgd, 曲线A, 曲线B, 曲线C, Location, best); lgd.Title.String 对比结果; % 重建后重新设置第二个可能原因Interpreter设置成了latex但字符串内容里有无法解析的语法MATLAB 会渲染失败标题就表现为空白。可以先临时把 Interpreter 改成 tex 或者把字符串换成纯文本逐个排除。第三个原因是lgd.Title.Visible被设成了off这个属性在交互编辑时有可能被误改代码里检查一下即可。6.2 标题与图例条目重叠如果你用的是Location自动布局加了标题后偶尔会和图例条目重叠。原因是图例框高度没有自动把标题算进去或者FontSize太大把空间挤没了。优先做两件事把标题字号调小一点或者把图例搬到不会被裁剪的位置。更可控的方法是手动调整图例框的位置。下面这段代码把图例整体向上平移强行给标题腾出空间lgd legend(sin(x), cos(x), Location, northeast); lgd.Title.String 三角函数; lgd.Title.FontSize 12; p lgd.Position; p(2) p(2) 0.02; % 上移 0.02 lgd.Position p;这里的 0.02 是归一化单位具体值要看你图上内容。跑一次看不到效果就逐步加大。另外Location为north或northeast这种贴边位置时图例框稍微上移一点就可能超出 axes 边界标题会被裁剪。建议改成northeastoutside把图例放到坐标区外侧标题和条目都能完整显示。6.3 导出图片时标题被截断图例标题在屏幕上看着好好的用exportgraphics或print导出 PNG、PDF 后突然顶部被切掉一块这个问题在宽高比较小的图上尤其明显。最直接的原因是 Figure 的画布空间不够。图例在图内时标题和条目顶部紧贴 axes 上边界导出器按 axes 边缘裁剪时就把标题切了。解决方法按优先级排列把Location改成northeastoutside让图例移到 axes 外面标题不会被 axes 裁剪。手动调大 Figure 的宽度fig.Position(3) fig.Position(3) * 1.2。在exportgraphics之前设置fig.PaperPositionMode auto这个对部分 print 场景有帮助。最后再检查lgd.Position确保left width 1如果超过 1 说明图例已经超出 Figure 画布。我见过不少人的做法是靠鼠标拖大图例框导出时一调整分辨率又出问题。最靠谱的还是代码里把 Figure 尺寸固定下来统一走脚本导出。6.4 特殊字符与中文乱码标题里写中文很常见但导出的 PDF 或 EPS 里中文变成方块字这个坑几乎人人会遇到一次。原因一般是 MATLAB 的中文字体映射问题解决思路是显式指定一个系统里存在的中文字体。lgd.Title.FontName Microsoft YaHei; % Windows % 或 lgd.Title.FontName SimHei;Linux 下可以用WenQuanYi Micro HeimacOS 下用PingFang SC。具体哪些字体名字可用可以在 MATLAB 里执行listfonts查看。另一个问题是 TeX 解释器的转义。默认情况下标题里如果出现下划线_会被当成下标处理。比如你写my_model显示出来是mymodel且model变成下标。解决方法是在下划线前加一个反斜杠my\_model。如果你用 sprintf 拼字符串还要注意%是 sprintf 的格式符想显示一个百分比符号必须写%%。7. 一点补充从MATLAB到其他绘图工具的思路迁移7.1 Python matplotlib 的便捷写法经常在 MATLAB 和 Python 之间切换的人可能已经发现Python matplotlib 里给图例加标题是一件非常顺手的事import matplotlib.pyplot as plt ax.plot(x, y1, labelsin(x)) ax.plot(x, y2, labelcos(x)) ax.legend(title三角函数)一行写完。相比之下 MATLAB 要lgd.Title.String ...两步。但底层思想是一样的图例是一个对象标题是它下面的一个文本子对象。只是 matplotlib 把这种关系封装进了legend的参数里。所以我通常用一个记忆锚点来切换语言MATLAB 里的lgd.Title.String相当于 Python 里legend(title...)的 title 参数。如果你在写一个同时输出多语言风格图的工具可以在 MATLAB 里封装一个类似的小函数把styleLgdTitle(lgd, txt)当成legend(title...)用。这种命名习惯能让跨语言协作的队友更快看懂。7.2 用代码思维替代鼠标点选最后补一句有点理念的话。图例标题在界面里也能加选中图例打开属性检查器找到 Title.String 填进去。但你一旦面对几十张图或者要频繁调整样式鼠标操作就不行了。我强烈建议把所有跟图有关的美化配置都写进脚本里即使它只是简单的一行lgd.Title.String。这样做的好处有三个可复现换一台电脑重跑同样的脚本出来的图一模一样可批量循环里改一个变量就能出一整套风格统一的图可审查改过什么一清二楚不会出现“我当时在界面上点了一下”这种死无对证的改动。尤其是投稿前最后一刻要统一字号、统一颜色的时候有脚本是真的能救命。我自己现在已经养成一个习惯每次建完 legend不管图多简单都会顺手写上lgd.Title.String。很多时候这行字用户未必会注意但一旦有人问“这条线是啥”“这组实验什么条件”它就是最快的那份说明。再加上把这行设置塞进公共出图函数后面调整起来几乎不费时间。图例标题不是什么高深功能但正是这些不起眼的小细节决定了别人看你的图是轻松还是费劲。