
简介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、135HatchDensity纹理密度50~100 之间HatchColor纹理颜色k、[0.3 0.3 0.3]HatchLineWidth线条粗细0.5~2HatchStyle填充样式single、double、cross、speckleHatchOffset纹理偏移0~10HatchVisible是否显示纹理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)很关键。如果先画曲线再画 patchpatch 会盖住曲线虽然 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:145 度线条会被拉成别的角度看起来就不再是标准的 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 页面下方也附带了很多示例空闲时翻翻能发现不少隐藏用法。本文还有配套的精品资源点击获取