简介:Hatchfill2是面向MATLAB用户的开源绘图增强工具,其核心用途是在二维图形区域加入交叉线、斜线和圆点等阴影纹理,弥补标准填充函数仅支持纯色填充的不足,适合科研论文、实验报告以及工程图表等需要精细化表达的数据可视化场景。资源包整体约60KB,共4个文件,其中两个脚本文件分别承载核心算法与演示程序,一个数据文件存放演示所需数据,一个文本文件为许可说明,整体结构紧凑,可快速加载使用。演示脚本配有详细注释,并结合示例数据展示了线条角度、透明度、填充样式等参数的不同组合效果,读者通过修改变量即可直观掌握自定义阴影的进阶技巧,也能将同类思路迁移到其他绘图任务中。该资源已有1443人学习,适合希望在图表中突出区域特征、提升视觉层次感的中高级MATLAB使用者参考实践。 每次做图表,只要涉及黑白打印或者期刊投稿的灰度图,我都会第一时间想到 Hatchfill2。这个 MATLAB 绘图辅助工具解决的问题非常具体:MATLAB 自带的 patch、area、fill 只支持纯色或透明度填充,不支持斜线、网格、点状这类纹理填充;而论文里区分试验组与对照组、标注显著区间、给柱状图加底纹,靠纯灰度经常糊成一片。Hatchfill2 就是专门给这些 patch 对象加上填充纹理的小函数,在 MathWorks File Exchange 上非常受欢迎,适合经常画科研图、实验报告图、以及任何需要把区域背景做出层次感的 MATLAB 用户。
我最早接触这个工具是在改一篇论文配图的时候,当时需要把图中两个时间区间标成不同的底纹,试用之后发现它比想象中稳,于是把常用的调用方式、参数细节和踩过的坑整理了出来,这篇就当一份可直接参考的实操记录。
1. 为什么需要 Hatchfill2:从“靠颜色区分”到“靠纹理区分”
1.1 哪些场景容易踩到填充的坑
先用一个最典型的场景来说明。假设你要画两组实验数据的柱状图,一组是对照组,一组是加药组,常规做法是把两组柱子分别填成灰色和白色,然后用图例说明。问题在于,审稿人如果打印出来,灰色和白色在灰度下区别不大;如果期刊要求“黑白印刷”,编辑甚至会直接退回让你改成可区分的形式。这时候如果给其中一组柱子加上斜线纹理,问题就迎刃而解了。
类似的场景还有:
- 在时间序列图上用色块标出异常区间、干预区间,色块与曲线叠加后容易遮挡信息,纹理底纹更轻量。
- 在箱线图或小提琴图旁边叠加分组背景区域,用浅色填充区分组别。
- 在等高线图或地形图上填充不同海拔区间,用不同方向和密度的斜线代表不同等级。
- 在示意电路图、流程图里,给某个模块加斜线表示“禁用”或“高阻状态”。
这些场景的共同点是:图形已经有很多颜色元素,再用纯色填充会显得乱,或者打印后无法区分。以前我的笨办法是手动画 line 对象,一条一条算斜率,费时费力且不美观。Hatchfill2 的作用就是把这件事自动化,并且生成的图案是真正的 MATLAB 图形对象,可以随图片一起导出为矢量格式。
1.2 Hatchfill2 相对老版本 Hatchfill 的改动
这里稍微说下背景。File Exchange 上最早有一个经典的 Hatchfill 函数,很多老教程都在用,但它长期没有大的更新,对新的图形对象类型支持有限。Hatchfill2 是在这个基础上重构的版本,主要改进有三点:
第一,它支持更多的 patch 输入方式,包括 fill、area、patch 创建的各类对象,甚至能输入多个句柄一起处理,不用自己写循环。
第二,填充图案的样式更丰富,除了单斜线,还支持交叉线、双向斜线、点状填充等,这在区分多组数据时特别有用。
第三,它内部用 hggroup 把填充线组织起来,返回一个句柄,方便后续统一修改透明度、颜色,或者删除填充。
所以在今天的 MATLAB 版本里,我推荐直接找 Hatchfill2,原来的老版本不用再考虑了。安装方式也很简单,把函数文件下载后放到当前文件夹或添加到路径即可,它不依赖任何工具箱,纯 MATLAB 基础绘图函数实现。
2. 核心参数与调用语法:一次搞懂所有设置项
2.1 基本调用方式和返回对象
Hatchfill2 的调用非常直接。先画一个 patch 对象,然后把 patch 句柄传给函数,同时用参数名指定填充模式。基本语法如下:
p = patch([0 1 1 0], [0 0 1 1], [0.8 0.8 0.8], 'EdgeColor', 'none'); h = hatchfill2(p, 'HatchAngle', 45, 'HatchColor', 'k', 'HatchDensity', 80);如果是在已经存在的坐标区上操作,默认会使用gca当前坐标区,不需要特殊指定。返回的h是一个 hggroup 对象,里面包含组成纹理的多条 line。
这里需要强调一个使用习惯:填充图案是叠加在 patch 上面的独立图形对象,不是 patch 自身的一个属性。所以在保存图片之前,你可以单独对h进行操作,比如h.Visible = 'off'可以临时隐藏纹理而不影响 patch 本体。这个特性在对比有无纹理效果时很实用。
2.2 关键参数细节
下面说几个我最常用的参数,以及它们在实际使用中的表现。
HatchAngle控制斜线角度,单位是度,默认是 45 度。注意这里的角度是相对于水平方向顺时针计算的,所以 45 表示从左上到右下的斜线,135 表示反向斜线。交叉填充可以先把两个 patch 的填充角度设成 45 和 135,也可以用内置的 cross 样式。
HatchDensity控制纹理密度,数值越大,线条越密,默认值是 50。但这里有个容易踩的坑:这个值不是绝对的线条间距,而是受坐标范围影响的比例值。同样设置HatchDensity为 80,在 x 轴范围 0~10 的坐标区上和在 x 轴范围 0~1000 的坐标区上,实际线条密集程度会明显不同。所以多组子图放在一起时,尽量保证坐标范围一致,或者手动微调密度值,否则纹理密度看起来会不一样。
HatchColor设置线条颜色,支持 MATLAB 的颜色简写('k'、'r'、'b')和 RGB 三元组,比如[0.3 0.3 0.3]。HatchLineWidth设置线条粗细,如果最终要导出 PDF 再放大查看,建议设置成 0.5 以上,太细的线在打印或缩放时会显得发虚。
HatchStyle是填充样式,可选值包括'single'(单斜线)、'double'(双向斜线,呈网格状)、'cross'(交叉线)、'speckle'(随机点状)、'fill'(完全填充)。当我需要同时区分四组数据时,会组合使用这些样式。
还有一个参数HatchOffset用来控制纹理的偏移量,默认 0。较少用到,但在做相邻区域的纹理拼接时,微调偏移可以让边界看起来更自然,避免接缝处出现明显的线头重叠。
2.3 参数速查表
| 参数名 | 作用 | 常用值示例 |
|---|---|---|
| HatchAngle | 纹理角度 | 45、135 |
| HatchDensity | 纹理密度 | 50~100 之间 |
| HatchColor | 纹理颜色 | 'k'、[0.3 0.3 0.3] |
| HatchLineWidth | 线条粗细 | 0.5~2 |
| HatchStyle | 填充样式 | single、double、cross、speckle |
| HatchOffset | 纹理偏移 | 0~10 |
| HatchVisible | 是否显示纹理 | 'on' / 'off' |
不要试图把所有参数一次性全部设置到完美,我通常先画一个初步效果,再迭代调密度和角度,因为纹理效果在屏幕上和打印后的感觉差很多。
3. 实操示例:论文图表中可以直接抄的代码
3.1 柱状图上叠加填充纹理
这是一个非常常见的场景,也是最容易上手的一个示例。用bar命令画两组柱子,然后取返回的 patch 句柄做纹理填充。
data = [3.2 4.1; 2.8 3.5; 4.0 4.8]; b = bar(data); b(1).FaceColor = [0.7 0.7 0.7]; b(2).FaceColor = [0.95 0.95 0.95]; p1 = findobj(b(1), 'Type', 'patch'); p2 = findobj(b(2), 'Type', 'patch'); hatchfill2(p1, 'HatchAngle', 45, 'HatchColor', 'k', 'HatchDensity', 60); hatchfill2(p2, 'HatchAngle', 135, 'HatchColor', 'k', 'HatchDensity', 60); legend([b(1) b(2)], {'对照组', '实验组'}, 'Location', 'northwest');这里有一个关键技术点:bar返回的 Bar 对象在很多新版本里不是直接等于 patch,要先通过findobj找到内部的 patch 子对象,然后再传给 Hatchfill2。如果不加这一句,直接传b(1)进去,有时候会报错或者没反应,这也是新手最容易卡住的地方。
图例方面,填充纹理不会自动合并到图例里,当有纹理时图例默认显示的仍然是灰色色块,除非额外处理。这个问题后面会单独说。
3.2 标出显著区间
在时间序列图里标出某个“显著干预区间”是另一个高频需求。比如画一条股价曲线,想要把某段时间用底纹标出来,同时不影响上方曲线的可读性。
t = 0:0.1:20; y = sin(t) + 0.2 * t; plot(t, y, 'k-', 'LineWidth', 1.5); hold on; x = [5 12 12 5]; y_range = [min(y) max(y) max(y) min(y)]; p = patch(x, y_range, [0.9 0.9 0.9], 'EdgeColor', 'none'); hatchfill2(p, 'HatchAngle', 45, 'HatchDensity', 40, 'HatchColor', [0.4 0.4 0.4]); uistack(p, 'bottom');注意最后一行uistack(p, 'bottom')很关键。如果先画曲线再画 patch,patch 会盖住曲线;虽然 patch 本身是浅灰色,但纹理线很可能会压在曲线上,干扰阅读。把填充对象放到最底层之后,曲线始终在最上层,视觉效果干净很多。
我实际测试过,hatchfill2生成的纹理线会跟随 patch 所在层,只在 patch 里出现。所以不必担心纹理线乱穿到底层其他图形上。
3.3 多组条形图黑白打印方案
最考验纹理功底的是三组以上的条形对比。三组数据如果只用灰阶,打印出来肯定分不清。组合使用不同角度和样式就能解决。
data = [2.5 3.1 2.8; 4.1 3.8 4.5; 3.0 3.5 4.2]; b = bar(data); colors = [0.3 0.3 0.3; 0.6 0.6 0.6; 0.9 0.9 0.9]; for i = 1:3 b(i).FaceColor = colors(i, :); p = findobj(b(i), 'Type', 'patch'); switch i case 1 hatchfill2(p, 'HatchStyle', 'single', 'HatchAngle', 45, 'HatchDensity', 50); case 2 hatchfill2(p, 'HatchStyle', 'double', 'HatchAngle', 45, 'HatchDensity', 30); case 3 hatchfill2(p, 'HatchStyle', 'speckle', 'HatchDensity', 200); end end三组分别用单斜线、双斜线网格、点状底纹,黑白打印时也能一眼区分。这里要注意,speckle样式的HatchDensity值要调得比斜线高很多,因为它是在 patch 内随机撒点,密度低了会显得稀疏,不美观。我试下来密度 200 左右比较合适。
点状纹理有一个微不足道但让人恼火的细节:每次运行代码,随机点的位置都会变化,导致每次图看起来不完全一样。如果要求可复现,建议在 patch 填充前固定随机数种子:
rng(42);3.4 用 specke 填充实现点状底纹
虽然上面已经用到了 speckle,但值得单独拿出来讲,因为它和其他纹理的底层逻辑不太一样。斜线纹理是折线组成的,放大后线条很清晰;speckle 则是在 patch 区域内随机布置小圆点,打印时像报纸上的半色调,灰度层次感更好。
theta = linspace(0, 2*pi, 100); x_circle = cos(theta); y_circle = sin(theta); p = patch(x_circle, y_circle, [1 1 1], 'EdgeColor', 'k'); hatchfill2(p, 'HatchStyle', 'speckle', 'HatchDensity', 300, 'HatchColor', 'k');做示意图时,给圆形区域加一圈点状纹理,既表示“注意区域”,又不像纯色块那样喧宾夺主。这个方法我常用来标记地图上的某个区域。
3.5 批量处理多个 patch
实际做图时,往往不会只填充一个 patch。比如画多个小区块,每个区块要标不同纹理。Hatchfill2 支持传入 patch 句柄数组,一次调用即可完成批量填充。
x = [0 1 1 0; 1.2 2.2 2.2 1.2; 2.4 3.4 3.4 2.4]; y = [0 0 1 1; 0 0 1 1; 0 0 1 1]; for i = 1:3 p(i) = patch(x(i, :), y(i, :), [0.85 0.85 0.85], 'EdgeColor', 'k'); end hatchfill2(p, 'HatchAngle', 45, 'HatchDensity', 60, 'HatchColor', 'k');这样三个区块会得到完全一致的纹理,适合做规律排列的示意图。如果希望每个区块纹理角度不同,可以单独循环处理,灵活度更高。需要提醒的是,批量传句柄时,如果传入的句柄中包含不同类型的图形对象,可能导致意外结果,所以尽量只传同类的 patch。
4. 常见问题与排查技巧
4.1 填充后 patch 不见了
这个现象通常发生在 patch 对象的 FaceColor 设置成'none'时。Hatchfill2 生成的纹理本质上是独立的 line,但它的父对象是坐标区,如果 patch 本身没有颜色,纹理线条看起来会像贴在坐标区背景上,视觉上可能被误认为 patch “消失”了。
解决办法有两种:一是给 patch 设置一个极浅的 FaceColor,比如[0.98 0.98 0.98],几乎透明但能撑住形状;二是确认 patch 的 EdgeColor 是否可见,如果 EdgeColor 也是 none,那确实会只剩下纹理线,看起来像没有边框,但不是 bug。
另一个常见原因是最开始提到的:传进去的对象不是 patch。fill、patch、area生成的通常没问题,但部分新版本bar、fill3返回的不是单纯 patch,需要findobj后再传。
4.2 填充线方向和预期不一致
有时明明设置了HatchAngle为 45,画出来却是朝另一个方向偏,或者子图之间方向看着不统一。这是因为 Hatchfill2 的纹理角度是相对于数据坐标计算的,如果坐标区的纵横比不是 1:1,45 度线条会被拉成别的角度,看起来就不再是标准的 45 度。
解决办法是在绘图后调用axis equal或者在 Hatchfill2 之前设定坐标范围。但要注意,如果在填充之后再去修改坐标范围或axis equal,已经生成的纹理不会自动更新,需要重新调用一次填充。实际做图时,我会先把坐标范围、大小都调整好,最后再做纹理填充,避免反复更改坐标导致纹理错位。
4.3 导出 PDF 后线条发虚或丢失
这应该是被问得最多的问题。屏幕上看好好的,导出 PDF 或 EPS 后纹理线要么变淡、要么断断续续。原因通常是HatchLineWidth太细,默认值在很多版本里渲染得非常轻。我习惯在最终导出前统一设置:
set(findall(gcf, 'Type', 'line'), 'LineWidth', 0.8);但注意不能粗暴地全局设置,因为图中可能有其他需要保持细线的元素。更稳妥的做法是只修改 hggroup 内的线:
h = hatchfill2(p, 'HatchColor', 'k'); set(h, 'LineWidth', 1);另外,导出 PDF 时,要确认print命令里没有使用低分辨率位图选项。用print(gcf, '-dpdf', 'output.pdf')这类矢量导出,纹理线才能保持清晰。
4.4 图例、透明度与图层顺序
纹理不显示在图例中,这是 Hatchfill2 比较明显的短板。网上有一些 Hack 方式,比如手动画一个带纹理的小方块放在图例位置,但都比较繁琐。我的做法是:先正常用颜色生成图例,然后用 Illustrator 等工具在最终图中替换图例符号;如果必须全用 MATLAB 完成,可以用annotation手动画一个小的 patch 加纹理,再配text标签,勉强能做到接近自动图例的效果。
关于透明度,Hatchfill2 生成的纹理线本身不支持透明度设置,但填充区域的颜色可以通过FaceAlpha设置,透明度和纹理可以共存,不需要纠结。
图层顺序方面,上面示例里用uistack(p, 'bottom')解决问题,更通用的原则是:先画背景填充,再画主要数据线,这样代码顺序本身就代表了视觉顺序,不需要额外调整。如果已经画完了才发现顺序不对,再用uistack调整。
4.5 兼容性:老版本 MATLAB 和脚本化发布
老版本 MATLAB(比如 R2014a 之前)对 hggroup 的支持方式有些差异,但从我身边的反馈来看,Hatchfill2 在 R2016a 以上的各种版本都能正常运行。如果在老版本上报错,优先检查函数的路径是否配置正确,再检查传入的句柄是否有效。
如果要把代码交给别人运行,最好在脚本开头用assert(isfile('hatchfill2.m'))或者exist('hatchfill2', 'file')做一次检查,避免对方缺文件时直接报红色错误,排查半天才发现是函数没下载。
写在最后的实践建议
我用的流程是先搭好图形主体,确认坐标轴范围不再变化,然后一次性填充纹理并微调密度、角度,最后再导出。这个顺序能减少至少一半的无用功。初次使用时不要贪多,先从单斜线加浅色 patch 开始,掌握手感后再尝试 double、speckle 组合方案。
另外建议把常用组合封装成自己的函数,比如myFilledBar、myShadedRegion,传数据、位置、纹理方向,内部统一调用 Hatchfill2,之后画图就是一行调用的事。这样既保证学术图风格统一,又避免每次重复调参数。
如果你在论文或报告里使用它做图,记得在方法部分或者致谢里给原作者一个 credit,这是对开源社区的最好支持。Hatchfill2 的 File Exchange 页面下方也附带了很多示例,空闲时翻翻,能发现不少隐藏用法。
本文还有配套的精品资源,点击获取