MATLAB科研绘图必备:Hatchfill2纹理填充解决灰度图区分难题
2026/9/3 23:57:37 网站建设 项目流程

简介:Hatchfill2是面向MATLAB用户的图形增强工具,解决标准fill函数纯色填充的局限,提供斜线、交叉线、斑点等复杂图案填充,适用于科研绘图、工程报告等场景,适合有一定MATLAB绘图基础、希望提升图表表现力的开发者。压缩包共4个文件,约60KB,包含两个m文件(核心填充函数与演示脚本)、一个mat数据文件(演示所需数据)及一个txt许可证文本,结构精简,便于快速上手。已有1443人学习下载。通过演示脚本可直观掌握不同图案、角度、透明度等参数的组合用法,理解填充算法实现细节;数据文件与注释完整的示例代码相结合,能帮助用户学会将自定义填充应用到实际图形中,使数据可视化更专业、更具层次感。许可证文件则明确了使用规范,方便后续合法修改与再分发。 做科研绘图的人应该都遇到过这种尴尬:论文要求用灰度或黑白图,可是画的时候为了区分数据组,习惯性地上了红、绿、蓝三套颜色。屏幕上一看,层次分明。等导成灰度图或者打印出来,几组颜色全部糊成一团,图例标得再清楚也救不回来。我的处理办法是直接用Hatchfill2,一个在MATLAB生态里专门给图形对象叠加填充纹理的小工具。它的核心能力是在patch、bar、fill、contour这类对象上,自动生成斜线、交叉线、点阵等图案,让图形在完全去色之后依然能通过纹理方向、密度和样式区分不同区域。本文就围绕这个工具,从原理到实操,把怎么用、踩过哪些坑、以及如何配合论文和汇报场景做图,完整讲一遍。

1. Hatchfill2解决的核心瓶颈

1.1 没有填充纹理时,图表区分度有多差

先还原一个最典型的崩溃现场。画三组柱状图,分别用三套颜色填充,RGB值大概是[0.8 0.2 0.2][0.2 0.8 0.2][0.2 0.2 0.8]。这三组颜色转到灰度后,亮度值会变得非常接近,某些情况下甚至只差十几个灰度级。人眼对灰度差异的敏感度本来就低于对色相和饱和度的敏感度,一旦图幅变小、打印分辨率下降,这种差异基本就消失了。期刊审稿人如果看到的是这种图,轻则要求返修,重则直接认为数据呈现不专业。

Hatchfill2的思路是用“纹理”而不是“灰度深浅”来编码类别信息。斜线方向、稀疏程度、单线还是交叉线,这些特征在黑白环境下依然能保留很强的辨识度。换句话说,它把原本只能靠颜色表达的分类维度,换成了一个对输出环境几乎不敏感的视觉通道。

1.2 为什么不用MATLAB自带的绘图功能

很多人会问:MATLAB不是后来有FacePattern吗?确实,R2021b开始,Bar对象的属性里加入了FacePattern相关能力,可以用线条填充柱体。但问题在于,这个能力目前覆盖的范围非常有限,只对bar这类对象友好,遇到patchfill、等高线组、任意闭合面片,它照样无能为力。而且在R2021a及更早版本里,这个功能根本不存在。与此同时,科研场景里大量使用自定义面片,比如用patch画区域标注、用fill画置信区间、用contourf画分布图,这些恰恰是最需要加纹理的对象。

另一种替代方案是自己生成二值图案矩阵,配合坐标映射叠加到图形上。这个方案不是不行,但要做的事情很多:解析对象边界、生成图案、处理缩放、保证旋转参数正确、还要避免覆盖坐标轴内容。一套代码写下来,画一张图的时间比画十张图还长。Hatchfill2把这些细节全部封装好了,用户只需要传入对象句柄和几个样式参数,它内部自动完成坐标计算、线段生成和层级管理,直接把结果画在图上。

2. 环境准备与安装验证

2.1 下载与路径配置

Hatchfill2发布在MATLAB Central的File Exchange上,搜索“hatchfill2”即可找到最新版本,Github上也有同步镜像。下载之后解压,里面通常包含核心函数文件、示例脚本和一段说明文档。我建议不要图省事把文件随手放到某个临时文件夹,而是固定在一个有意义的路径下,比如D:/MyMatlabToolbox/hatchfill2,方便后期维护和换机器同步。

然后在MATLAB里执行:

addpath(genpath('D:/MyMatlabToolbox/hatchfill2')); savepath;

为什么要用genpath而不是addpath?因为有些版本的工具会在子目录里存放示例数据或者辅助函数,只加顶层目录会导致部分功能找不到依赖文件。装完之后验证一下:

which hatchfill2

如果返回了完整路径,说明安装成功。如果返回“not found”,优先检查路径字符串有没有写错,以及解压目录下是不是确实存在hatchfill2.m

2.2 不同MATLAB版本的兼容情况

从R2014a到R2023a,我在多个大版本上都跑过这个工具,绝大多数场景下表现稳定。需要注意的一个分水岭是R2014b,旧版本用的HG1图形系统和新的HG2图形系统对句柄的处理方式不同,Hatchfill2在HG2下的适配明显更顺滑。如果还在使用R2014a之前的版本,建议先升级,否则可能出现句柄类型解析异常。

另一个容易踩坑的地方出现在App Designer里。UIAxes和传统Figure的坐标区底层实现不完全一致,直接在UIAxes上调用Hatchfill2,有时候会提示句柄类型不支持或者图形没有反应。我的做法是,需要加纹理的图形永远用传统figure绘制,最后通过exportgraphicscopygraphics导出成图片,再嵌入到App里展示,这样既保证了功能的兼容性,也避免了界面刷新带来的额外卡顿。

3. 核心功能实操

3.1 调用格式、风格与关键参数

Hatchfill2的基本调用方式不长,几行就能看懂:

hatchfill2(H) hatchfill2(H, 'single', 45) hatchfill2(H, 'cross', 30, 5)

H是你要处理的图形对象句柄,可以是patchfill对象、bar序列或者contour组。第二个参数是填充风格,常见的有single(单向斜线)、cross(交叉线)、speckle(颗粒点)、fill(实色填充)和none(不加纹理)。后面的数字参数分别是角度和线密度。

角度参数单位是度,表示线条与水平方向的夹角,工程图里常用的范围是0到90度。线密度则控制纹理的稀疏程度,数值越小线条越密。常用的风格和适用场景可以参考下面这个表:

样式名视觉效果典型适用场景
single单方向平行斜线两组数据区分,最常用
cross两组斜线交叉成网格三组以上数据,或需要强标记区域
speckle随机颗粒点面积较大的区域,适合低密度填充
fill近似纯色填充需要替代色块颜色时使用
none不做处理批量循环中临时占位

3.2 控制颜色、线型和边界

实际使用中,纹理线条的颜色和线型是高度可定制的。默认情况下,Hatchfill2会根据对象的FaceColor自动推导出线条颜色,但这个推导结果不一定符合预期,尤其是当FaceColorflat或者使用了透明度时,线条颜色可能和原图边界颜色不一致。为了稳定输出,我一般会在调用时显式指定颜色:

hatchfill2(H, 'single', 45, 5, 'Color', [0 0 0]);

如果想把默认实线改成虚线,可以继续追加参数:

hatchfill2(H, 'single', 45, 5, 'Color', [0 0 0], 'LineStyle', '--');

需要注意的是,函数解析参数时,位置参数和Name-Value参数是按顺序处理的。如果你只想修改颜色而不想重新设置角度和密度,依然要把角度、密度这两个位置参数占位补上,否则解析会错位。不同版本对参数名的支持略有差异,拿到工具后可以先看一眼help hatchfill2,确认当前版本支持哪些属性再动手。

3.3 多组数据的纹理分配

同一个坐标系下面同时绘制多组数据时,需要给每一组分配不同的纹理,核心思路是分别拿到每个图形对象的句柄,再循环调用Hatchfill2。以三组柱状图为例:

figure; hBar = bar(1:5, rand(5,3), 'FaceColor', 'flat'); hPatch = findobj(hBar, 'Type', 'patch'); angles = [30 60 90]; for i = 1:3 hatchfill2(hPatch(i), 'single', angles(i), 5, 'Color', 'k'); end

这里通过findobj找到柱状图内部的所有patch对象,再按顺序给每个patch分配不同的角度。30、60、90度三种斜线组合在一起,区分度非常高。如果想要更强的标记效果,可以在循环里混合使用不同样式,比如第一组用single、第二组用cross、第三组用speckle,这种组合在复杂图形中比单纯换角度更容易辨认。

4. 实测案例与代码示例

4.1 柱状图加单斜线纹理

用一组模拟数据完整跑一遍。三列数据放在同一个bar图上,然后给前两组加不同角度的斜线,第三组不加,用来模拟“对照组”:

data = [2.5 3.8 4.2; 3.1 2.9 4.6; 2.8 3.5 4.0]; figure; hBar = bar(data); hPatch = findobj(hBar, 'Type', 'patch'); for i = 1:3 ang = 30 + (i-1) * 30; hatchfill2(hPatch(i), 'single', ang, 4, 'Color', 'k'); end

运行下来,三个组的柱体分别出现了30度、60度、90度的黑色斜线,纹理密度足够均匀,没有出现断裂或偏移。这里线间距取了4,是对默认尺寸figure比较合适的一个密度值。如果图像幅面特别大,比如为了论文做了全页宽图,建议把密度调到6到8,避免线条密到变成一团黑。反过来,如果是小图,密度可以降到2到3,确保纹理肉眼可见。

4.2 等高线区域加交叉填充

处理contourf绘制的等高线图时,直接对整个Contour对象调用Hatchfill2并不总是理想,因为等高线对象内部包含大量线段和边界信息,函数解析产生的线条数量会急剧增加,图形容易变得杂乱,运行速度也会明显下降。更稳妥的做法是只对你要强调的特定区域手动构建patch,再对这个patch加纹理。

我常用的一种做法是先用contour获取目标高度的轮廓坐标,再把这些坐标组成的闭合区域填充成patch,然后调用交叉纹理。比如要标注等高线中的零值区:

[x, y] = meshgrid(-3:0.1:3); z = peaks(x, y); figure; contourf(x, y, z, 20); hold on; [c, h] = contour(x, y, z, [0 0]);

这里h是等高线对象,但零值线通常不是单一闭合区域,直接取坐标、填充成patch需要额外处理连通性。我的经验是,先用contourc获取坐标数据,再用patch手动勾画感兴趣的区域,拿到patch句柄后再加纹理:

C = contourc(x(1,:), y(:,1), z, [0 0]); % 手动处理C矩阵中的线段分类,选出目标边界XY坐标 patch(X, Y, 'w', 'EdgeColor', 'none'); hp = findobj(gca, 'Type', 'patch', 'FaceColor', 'w'); hatchfill2(hp, 'cross', 45, 3, 'Color', 'k');

这样做的好处是,contourf原有的色彩层次完全保留,显著性区域用交叉斜线单独标出来,灰度输出时一眼就能辨认。整个过程需要对轮廓坐标做一点手工过滤,代码量不多,但效果非常稳。

4.3 图例中正确显示纹理

加了Hatchfill2之后,一个高频问题是图例不显示纹理。默认情况下legend展示的是面片对象的原始色块,并不会因为你手动在patch上加了斜线就自动更新。解决这个问题通常需要“造假”一个样本面片,专门放进图例里:

hFakePatch = fill(NaN, NaN, 'w'); hatchfill2(hFakePatch, 'single', 45, 4, 'Color', 'k'); legend([hPatch(1), hPatch(2), hFakePatch], {'实验组', '对照组', '基准区域'});

这里用fill(NaN, NaN, 'w')创建一个看不见的透明面片,它只服务于图例,不会在坐标区显示任何内容。给这个样本面片加同样的纹理后,再把它追加到legend的句柄列表里,图例中就会出现和图中完全匹配的纹理样式。期刊投稿时,图例纹理和图中纹理对得上,这一条基本就是规范图的隐形门槛。

5. 常见问题与排查技巧实录

5.1 加载纹理后没有反应

如果调用Hatchfill2后图形毫无变化,优先排查两类原因。第一类是句柄类型不对,传入的是axes或者figure,这类对象不是面片,函数没有处理逻辑;第二类是当前对象所在坐标系处于“图片锁定”状态,比如在subplot循环里没有及时hold on,导致新增的Line对象被自动清除。还有一种容易被忽略的情况是对象面积太小,生成的线条数量过少,肉眼看不出纹理。处理办法是放大图形局部确认一下,如果确实没有线条,就调整线密度参数到更小的值试试。

5.2 纹理被遮挡或顺序不对

Hatchfill2生成的线条对象会默认放在当前坐标系的顶层,但如果你后续又调用了hold on并画了新的数据,新对象可能覆盖掉纹理。这种事情在画完数据后又追加标注线、误差棒时尤为常见。解决方法是把生成的Line对象手动提升到最上层:

hLines = hatchfill2(hPatch, 'single', 45, 4, 'Color', 'k'); uistack(hLines, 'top');

如果线条数量太多导致图面卡顿,可以把密度调低,或者只在最终成图阶段加纹理,避免在调试过程中反复渲染。

5.3 导出图片时纹理丢失或偏移

导出为PDF、EPS这类矢量格式时,Hatchfill2生成的线段是普通Line对象,理论上不会丢,但渲染效果受输出尺寸影响很大。如果你发现导出的图片里纹理位置偏移,大概率是屏幕显示和打印尺寸的缩放比例不一致导致的。建议固定figure的PaperPositionMode并在导出时指定分辨率:

set(gcf, 'PaperPositionMode', 'manual'); exportgraphics(gcf, 'output.png', 'Resolution', 600);

导出PNG时如果出现锯齿,600dpi基本能解决;导出PDF时如果线条偏移,先检查坐标区和figure的尺寸比例是否锁定。

5.4 循环绘图时纹理串号

在批量处理多个子图时,如果你在循环里重复使用同一个句柄变量,或者上一次循环的图形对象没有清空,纹理就会串到下一张图上。我的处理方式是维护独立的line句柄列表,在每轮循环开始时先删除上一轮的线条:

for k = 1:n cla(ax(k)); % 绘制新数据 hPatch = findobj(ax(k), 'Type', 'patch'); if ~isempty(hPatch) hatchfill2(hPatch, 'single', 45, 4, 'Color', 'k'); end end

这样虽然粗暴,但能保证每个子图之间的纹理完全独立,不会因为旧的Line对象残留而产生干扰。

6. 扩展思路与替代方案

6.1 在地图与区域图上叠加纹理

除了数据图,Hatchfill2在地理区域图的类别标注上也很有用。用patch绘制行政区域或样方边界时,可以通过不同纹理区分土地利用类型、实验分区等信息。配合坐标转换,把经纬度多边形坐标转换为平面坐标后直接填充patch,再调用Hatchfill2的cross模式,就能在不改变底色透明度的情况下增加一层纹理编码。我个人的习惯是把纹理线条颜色设为黑色,并且透明度调低,这样既不会盖住底图要素,又能清晰区分区域类型。

6.2 与R2021b版FacePattern功能如何取舍

新版MATLAB的Bar对象确实支持FacePattern,使用起来更加“官方”,不需要额外下载工具包,代码上也更简洁。但如果你的图形既包含柱状图又包含自定义patch区域,或者需要对contourf结果做局部标注,FacePattern的覆盖范围就不够了。Hatchfill2最大的优点是通用、稳定、不挑图形对象,一套代码用到所有场景,不用关心版本差异。我自己在实际项目中,通常用FacePattern处理常规柱状图,遇到复杂面片、区域图和多对象混合图时,还是切回Hatchfill2。两者并不冲突,按需选用就好。

从刚开始觉得“加个纹理还要调参好麻烦”,到现在只要涉及黑白输出都会默认把它加进绘图脚本,Hatchfill2基本成了我的标配工具。回头再看,它帮助最大的地方还不是省了那几行代码,而是让我在做图时就强制考虑了灰度场景下的可读性,避免了交付前才发现区分度不足的尴尬。如果你也在为期刊投稿的灰度图发愁,不妨先下一份试试,从最简单的单组柱状图开始,跑通一遍之后再根据自己的数据结构做调整。最后提醒一句:无论最终效果看起来多满意,导出前务必在屏幕上先生成一版灰度预览,确认每种纹理在去色后依然能明确区分,这个小习惯能帮你省掉很多返工时间。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询