尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MATLAB pcolor绘制二维色块图实战指南:从绘图机制到colormap调色

MATLAB pcolor绘制二维色块图实战指南:从绘图机制到colormap调色 我还在念研究生那会儿第一次用MATLAB画二维场图就被pcolor摆了一道。当时手里的数据是某个物理量在平面上的分布网格也是规规矩矩的矩形网格我照着help文档写了几行出来的图一塌糊涂——颜色怪、坐标错、边缘还有莫名其妙的黑色缝隙。后来翻了无数帖子、试了各种参数组合才摸透这个函数的脾气。这篇东西不是把官方文档翻译一遍我想从实际使用的角度把pcolor函数拆开揉碎讲清楚它的绘图机制是怎么回事、和imagesc/contourf这些亲戚怎么选、坐标矩阵为什么比数据矩阵小一圈、colormap到底怎么调才不辣眼睛。无论你是刚碰MATLAB的新手还是被论文图表折磨的老手这篇应该都能给你一些能直接抄走的经验。1. pcolor函数到底是什么先搞清楚绘图机制再动手1.1 一张图看懂pcolor的工作原理pcolor这个名字是pseudo color的缩写叫“伪彩色图”或者“色块图”。它的本质是用颜色来表示一个二维矩阵中每个位置数值的大小把数据矩阵映射成一堆彩色的小格子。理解pcolor的关键在于它是用顶点坐标来定位网格的。假设你的数据矩阵C是m行n列那么pcolor绘制的时候需要X和Y来指定每个网格顶点的位置X和Y的尺寸是m行n列。绘制结果就是(m-1)行(n-1)列个色块因为最后一个行和最后一列顶点是用来闭合网格的。我第一次用的时候就是没搞明白这个“少一行一列”的关系总以为C是10×10的数据坐标给个1:10就好结果画出来的图位置总是偏离。实际上如果你用的是向量形式的坐标x 1:n和y 1:mpcolor内部会自动用meshgrid把它们扩展成和C一样大小的矩阵。但真正的色块区域是相邻顶点围出来的那一小块。从底层实现来看pcolor就是surf函数在二维平面上的投影或者说surf在view(2)视角下的效果。它在内部生成了很多小的patch对象每个patch填充一个颜色。这也是为什么当数据量很大的时候pcolor会明显变卡——本质上是在画几千上万个多边形。1.2 pcolor、imagesc、contourf三兄弟怎么选很多人画二维分布图的时候会在pcolor、imagesc、contourf这三个函数之间纠结。我个人的使用习惯可以给大家一个参考。函数坐标形式网格灵活性输出类型适用场景pcolorX、Y需与C尺寸匹配支持不规则网格矢量图形patch论文图、需叠加其他元素、网格不规则imagesc只用x、y指定四个边界仅规则矩形网格图像对象image快速查看、大矩阵、无需矢量导出contourf与pcolor类似支持不规则网格等值线填充面关注数值等值、需要等值线标注如果只是为了快速看一眼数据长什么样用imagesc就够了速度快、内存省。但如果这个图是要放进论文或者汇报材料里的我强烈建议用pcolor或者contourf因为它们是矢量格式放大不模糊而且可以精确控制坐标轴、叠加箭头、标注等。还有一个细节值得注意imagesc默认的坐标是像素索引画出来之后还需要手动调整坐标轴范围而pcolor直接用你提供的X、Y来绘制定位坐标天然就是实际物理坐标省去一步换算。1.3 为什么论文党偏爱pcolor就我接触到的场景而言数据可视化做二维场图的需求通常集中在几个方面温度场分布、有限元计算结果、流场速度分布、各种误差或残差的空间分布、遥感反演的地表参数。这类图的共同特点是有真实的物理坐标而且经常需要叠加等值线、标注点、矢量箭头。pcolor能成为这类图的主力除了矢量格式之外还有一个重要原因它的face颜色是严格对应网格单元数值的不会像插值类方法那样“抹平”数据中的特征。某些场景下我们需要如实展示数据的离散性比如有限元中每个单元的应力值这时候pcolor就是最合适的选择。另外pcolor配合shading interp可以实现平滑过渡效果视觉效果和contourf的填充图非常接近但颜色变化更细腻。想表现连续物理场时这是一个很大的加分项。2. 核心参数与坐标体系把pcolor的参数吃透2.1 坐标矩阵的构建规则与“少一圈”陷阱先来看pcolor最常见的调用方式pcolor(X, Y, C)X和Y是网格顶点的坐标C是网格单元上的数值。理解这句话最关键C的尺寸是(m-1)×(n-1)X和Y的尺寸是m×n。X和Y比C多一行一列因为顶点永远比单元多。但MATLAB官方其实允许X、Y、C三者尺寸一致。这种情况下C的最后一行和最后一列会被忽略。这就是为什么我用10×10的数据配1:10的坐标也能画出图来但实际只画了9×9个色块。当你发现图比预期小了一圈或者位置和坐标轴对不上时先检查一下是不是这里出了问题。实际使用中最常见的坐标构造方式是配合meshgridx linspace(-2, 2, 30); y linspace(-2, 2, 30); [X, Y] meshgrid(x, y); C X .* exp(-X.^2 - Y.^2); pcolor(X, Y, C);这样X和Y的尺寸是30×30C也是30×30pcolor会认为最后一行一列的数据无效绘制29×29个色块对应的顶点范围仍然是-2到2。这个用法是官方推荐的大家按这个写基本不会出错。2.2 shading、EdgeColor和FaceColor的搭配艺术pcolor画出来的图一开始往往很难看因为默认情况下每个色块之间都有黑色的网格线看久了眼睛疼。这时候就要用shading来调整。shading有三个选项faceted默认、flat、interp。faceted就是每个色块独立着色并且带边框。flat是每个色块独立着色但不带边框颜色值是均匀填充整个格子。interp则是根据四个顶点的值做双线性插值相邻色块之间颜色平滑过渡。我的经验是数据本身是离散单元值比如有限元单元应力用flat最合适能真实反映数据特征。数据是从连续物理场采样而来的比如温度场用interp能让图看起来更光滑、更接近真实物理过程。faceted这个默认选项除非网格很密否则基本可以抛弃。如果想要边框但不想用黑色可以用EdgeColor属性单独设置pcolor(X, Y, C); shading flat; set(findobj(gca, Type, patch), EdgeColor, [0.7 0.7 0.7]);这种做法在地形图或者网格剖分图上特别常见灰色细边框既保留了网格信息又不会喧宾夺主。还有一个不算冷门但很多人不知道的搭配pcolor可以配合FaceAlpha设置半透明。这在需要叠加两张图的时候非常实用。比如先画一张地形阴影图再叠加一层半透明的温度分布图两张图的信息能同时展示清楚。2.3 坐标方向与颜色范围控制clim的三两事pcolor画完之后图形的横纵比例和坐标方向经常需要调整。最常用的三个设置是axis equal、axis xy、clim。axis equal很直观它让横纵坐标的单位长度一致。对于物理场图来说如果横纵坐标都是空间位置加上axis equal能保证图形形状不变形这一点在展示圆形对称的物理场时尤为重要。axis ij的作用是将y轴方向翻转让坐标从顶部向下增加。MATLAB默认的图形坐标是y轴向上但很多时候我们的数据是按行从上到下排列的比如图像数据如果不翻转就会上下颠倒。遇到图和预期方向相反的情况先试试axis ij。clim是控制颜色映射范围的核心命令在新版MATLAB中它替代了旧版的caxis。clim([cmin cmax])将C数据中小于cmin的值全部映射为colormap的第一个颜色大于cmax的值映射为最后一个颜色两者之间的值线性映射。这个命令在对比多张图的时候极其重要。画三张不同时间点的分布图如果不统一clim每张图的颜色范围会各不相同肉眼根本看不出差异。统一clim之后再对比数据变化一目了然。3. 数据准备与网格插值pcolor图的底层功夫3.1 离散散点数据如何“变成”规则网格实际工程中的数据很少直接是整齐的网格数据更多情况是散点形式一组x坐标、一组y坐标、一组对应的数值z。比如气象站的观测数据、地形测量点、传感器采集的数据都是散点。pcolor没法直接处理散点得先插值成规则网格。MATLAB里最常用的散点插值工具是scatteredInterpolant用法非常简单% x, y, z 为原始散点数据都是列向量 F scatteredInterpolant(x, y, z, natural, linear); % 构造目标网格 gx linspace(min(x), max(x), 50); gy linspace(min(y), max(y), 50); [GX, GY] meshgrid(gx, gy); % 在网格上计算插值结果 CZ F(GX, GY); % 绘制 pcolor(GX, GY, CZ); shading interp;这里的插值方法我推荐先用natural自然邻域插值它在大多数情况下表现稳健不会产生太大的振荡。linear速度快但容易出现尖角nearest适合类别数据。有一个细节需要注意scatteredInterpolant要求输入的x、y、z都是列向量且长度一致。很多新手在这里栽跟头因为从Excel读入的数据可能是行向量转置一下就好。插值完成之后网格密度记得根据数据密度调整。网格太少会丢失细节网格太多则会在无数据区域产生明显的插值假象。我自己常用的经验是网格数在50×50到200×200之间具体看数据覆盖范围和精度需求。3.2 NaN与无数据区域的处理技巧数据处理中经常遇到某些网格点没有有效数值的情况这些点应该用NaN填充而不是0。pcolor对NaN有天然支持——NaN值的区域不会被填充颜色而是显示为空白。这里有个重要经验如果你用0填充无数据区域然后在颜色条上做了clim控制无数据区域可能会显示成最深的颜色会误导看图的人。用NaN填充无数据区域就是干净的白色一眼就能看出哪些地方没有数据。但要注意pcolor加上shading interp之后NaN区域边缘可能会有颜色泄漏现象也就是周围的颜色向无数据区域略微渗透。解决方法是把NaN区域边缘的几层网格也置为NaN或者改用shading flat渗透情况会好很多。还有一种情况数据中存在异常值比如几个点突然比其他点大两个数量级色块的数值范围会瞬间拉平真实分布完全看不见。处理思路是先对数据做log变换或者用百分位数截断% 用百分位数截断异常值 lo prctile(C(:), 1); hi prctile(C(:), 99); C(C lo) lo; C(C hi) hi;这种做法虽然损失了极端值的信息但能保住整体分布的可见性。如果想保留极端值信息也可以不截断数据而是通过设置clim来达到同样的可视化效果。3.3 数据行列方向与坐标轴方向的对应关系初学者经常被一个问题绕晕C矩阵的行列和图片上显示的横纵方向到底怎么对应结论是C的第1行默认显示在图形的底部y最小处第1列显示在图形的左侧x最小处。也就是说C的行方向对应y轴列方向对应x轴。如果你发现图是“转置”的说明数据在存储时行列方向和你的预期相反直接把矩阵转置一下就好。如果发现图“上下颠倒”先试axis ij再考虑是否需要翻转矩阵。这个坑在读取外部数据时特别容易踩。有人用python的numpy处理过数据再存成csvnumpy的行列顺序和MATLAB虽然一致但有些文本格式的文件在保存时是按行优先还是列优先很容易搞混。读入数据之后先画一张小图检查一下方向比起埋头分析半天发现方向是错的高效得多。4. 实操案例用pcolor绘制一张论文级二维场图4.1 完整可运行的绘制代码光讲理论不练手是耍流氓。我这里给出一段完整的代码生成一个有真实物理意义的二维场图平面上两个热源叠加形成的温度分布。这段代码覆盖了pcolor绘图的所有关键环节数据生成、网格插值、绘图参数、颜色条设置、高清导出。%% 生成模拟温度场数据 % 设定计算区域 x linspace(-3, 3, 100); y linspace(-3, 3, 100); [X, Y] meshgrid(x, y); % 两个不同位置、不同强度的热源叠加 T 30 * exp(-((X - 1).^2 Y.^2) / 0.5) ... 45 * exp(-((X 1.2).^2 (Y 0.8).^2) / 1.2) ... 20 * exp(-(X.^2 (Y - 1.5).^2) / 0.3); % 增加一点噪声模拟测量误差 T T 1.5 * randn(size(T)); %% 用pcolor绘制 figure(Color, w, Position, [100 100 560 480]); pcolor(X, Y, T); shading interp; axis equal; axis tight; clim([0 60]); % 设置坐标轴标签与字体 xlabel(X / m, FontSize, 12); ylabel(Y / m, FontSize, 12); % 添加颜色条并设置标签 cb colorbar; cb.Label.String Temperature / °C; cb.Label.FontSize 12; set(gca, FontSize, 11, TickDir, out, Box, on); colormap(gca, colormap_custom()); % 自定义colormap见下文5.2节 %% 导出高清图 exportgraphics(gcf, temperature_field.png, Resolution, 300);4.2 代码逐段讲解与效果验证上面的代码有几个值得细说的点。数据生成部分用三个高斯函数叠加形成温度场分布外加随机噪声模拟测量误差。这个合成数据的优势是兼顾了“连续变化”和“局部特征”既考验pcolor平滑过渡的能力也能检验是否有足够的分辨率呈现细节。shading interp配合高斯型分布是最佳组合。温度场本身是连续物理量插值着色之后颜色过渡自然不会出现阶梯状的色块边界。axis equal在这里很关键否则横纵坐标比例不一致热源的圆对称特征会被拉成椭圆。clim([0 60])是精心选的。合成的温度场理论范围大约在0到95之间但大部分区域温度在40以下只有热源中心超过60。如果让MATLAB自动选择范围高温区会占据颜色条的很大比例低温区域的颜色差异会被压缩得很难分辨。限幅到0到60之后颜色对比度明显提升热源结构看得更清楚。颜色条标签用cb.Label.String来设置这是新版本MATLAB的推荐做法。旧版的colorbar(ylabel, ...)在R2016a之后就不推荐了。导出用exportgraphics而不用saveas或print这是新版本的一个重大改进。exportgraphics会自动处理图形的尺寸和分辨率导出的PNG图片清晰度远高于saveas默认参数。如果是出版级别的需求一般设置300dpi就够需要更大的图可以改成600dpi。4.3 pcolor叠加等值线专业图表的进阶操作二维场图叠加等值线是气象、海洋、地质领域的常规操作。pcolor提供颜色填充的“面”信息contour线提供数值的“线”信息两者结合一张图就能同时看到量值和形态。实现方式非常简单hold on; [C, h] contour(X, Y, T, [15 20 25 30 35 40 45 50], k); clabel(C, h, FontSize, 9, LabelSpacing, 200); hold off;这里的关键是等值线数值要和你关心的物理量级匹配。选15到50的等差序列是因为温度场的等温线在这个范围有足够的分辨率。线的颜色用黑色k在彩色pcolor背景上最醒目。clabel函数用于标记等值线数值。如果不加这个函数等值线只是一堆曲线数值方向的信息还得靠猜。LabelSpacing参数控制相邻标记的距离单位是像素点数值越大标记越稀疏。200一般比较合适太密会糊成一团太疏又等于没标。叠加等值线有一个小坑如果等值线数值超出了clim范围contour依然会画出来但对应位置的颜色已经饱和视觉上可能会产生误导。解决方法是保持等值线数值范围不大于clim范围或者在绘图前检查数据的最大值和最小值。5. colormap调色秘籍让你的图从“能看”到“好看”5.1 内置colormap的选择与适用场景colormap对于数据可视化的影响怎么强调都不为过。同样的数据用不同的colormap呈现的信息重点可以天差地别。先说常用内置colormap的适用场景。colormap名称颜色风格适合场景注意事项parula蓝黄渐变通用默认连续场数据新版MATLAB默认赛博感较强turbo彩虹色数据范围大、需要分辨率感知均匀性较好但仍有彩虹色缺陷jet蓝绿黄红老式彩虹图学术圈争议大色带在黄色区域过窄容易造假象hot黑红黄白热辐射、红外图像适合强调高值区域cool青紫渐变负值到正值对称数据缺乏明显的中性参考点bone黑白蓝渐变医学图像、地形灰度适合黑白打印gray黑白线性灰度图、地形阴影最严谨信息保真度高我的建议是优先考虑parula、turbo这类感知均匀的colormap它们在不同数值区间的颜色亮度差异比较均衡不容易产生视觉错觉。jet那种彩虹色虽然好看但在黄色区域颜色跨度很小两个数值相差很多的值在视觉上可能看不出区别这是很大的信息呈现缺陷。学术出版界对人类感知均匀性的重视程度越来越高很多人评审时对jet有明确的负面态度。如果你的图是投期刊用的尽早抛弃jet改用parula、viridis这类现代colormap能避免不少麻烦。5.2 手把手教你自定义colormap的两种经典方法内置colormap难免有不合心意的时刻自定义colormap就是必备技能。理解了colormap的本质之后你会发现这东西非常简单。colormap的本质是一个N行3列的矩阵。N是颜色划分细度比如256代表256种颜色每一行是一个RGB三元组取值0到1顺序是从数据最小值对应的颜色排到最大值对应的颜色。第一种方法直接罗列锚点颜色然后用MATLAB自动插值生成渐变。function cmap colormap_custom() % 构建从白色到深蓝色的渐变 % 锚点颜色白 - 淡蓝 - 天蓝 - 深蓝 anchors [ 1.0 1.0 1.0; % 白色 0.8 0.9 1.0; % 淡蓝 0.3 0.6 1.0; % 天蓝 0.0 0.1 0.5; % 深蓝 ]; % 用线性插值扩展到256色 cmap interp1(linspace(0, 1, size(anchors, 1)), anchors, linspace(0, 1, 256)); end这段代码的核心是interp1对锚点颜色做线性插值生成一条平滑的颜色渐变路径。你可以随意增删锚点控制渐变经过的颜色。注意锚点之间的插值默认是RGB空间的线性插值颜色过渡直观、可控。第二种方法构造发散型配色diverging colormap常用于表示“负-零-正”类型的数据。function cmap diverging_blue_red() % 蓝色末端 blue [0.0 0.2 0.8]; % 中间白色 white [1.0 1.0 1.0]; % 红色末端 red [0.8 0.1 0.0]; cmap [interp1(1:2, [blue; white], linspace(1, 2, 128)); ... interp1(1:2, [white; red], linspace(1, 2, 128))]; end发散型配色的关键在中间色必须是低饱和度的颜色通常用白色或浅灰色。这样数值从负到正跨过零点时颜色有明确的“无信号”区。用于误差分布图或者异常检测图时这个特征很有用。5.3 让colormap贴合数据语义选colormap不能只看好看更要看数据的语义特征。我用过一段时间的教训是错误的配色会让人理解数据时产生偏差。温度数据通常用暖色表示高温、冷色表示低温这是人类的本能直觉。地形高程数据适合用绿色代表低海拔、棕色代表中等海拔、白色代表高海拔雪线这也是绝大多数人习惯接受的地图配色。降雨量数据用浅色到深蓝色代表降雨从少到多符合“水深”的隐喻。有一个很容易犯的错误类别数据用了连续渐变colormap。比如处理的变量是土壤类型类别1到5如果用了parula渐变相邻类型之间的颜色差异是渐变的人眼很难区分边界。这种情况下应该用离散颜色每个类别一个完全不同的颜色。实现方式是把colormap设置为5行每行一个颜色然后用categorical数据或者直接索引绘图。检验colormap是否“欺骗”了数据一个很简单的方法把图打印成黑白的看看。把colormap转成灰度后如果只出现很少几个灰度级别说明不同数值之间的颜色区别在黑白打印时会完全丢失。论文投稿时编辑经常要求提供灰度兼容的图表提前做这一步检查能省很多事。5.4 借鉴其他生态的配色资源MATLAB自带的colormap还是偏少而且有些配色确实不讨喜。我的常用操作是从Python的matplotlib库中把配色导入MATLAB。Python生态中有名的viridis、plasma、magma、cividis这几个colormap在数据可视化圈子里几乎是品质的代名词。MATLAB R2019b及以上版本其实自带parula而且parula的设计思路和viridis非常接近所以大多数人并不需要额外导入。但如果你就是偏爱viridis的色调或者需要和已有的Python绘图保持一致导入方式很简单从matplotlib的colormap中提取RGB值可以用Python把色表输出成文本import matplotlib.cm as cm import numpy as np viridis cm.get_cmap(viridis, 256) np.savetxt(viridis.rgb.txt, viridis.colors[:, :3], fmt%.6f)然后在MATLAB中读取这个rgb文件作为colormapcmap load(viridis.rgb.txt); colormap(gca, cmap);跨生态调配色就这么简单。用同样的思路也能导入GMT、R的RColorBrewer等配色方案只要拿到RGB三元组就行。6. 常见问题与排查技巧实录6.1 高频故障排查速查表把这几年的使用经验整理成一张速查表遇到问题直接对照查问题现象常见原因解决方案图形是纯黑色或纯白色C数据整体为NaN或clim范围设置不当导致所有数据超出范围检查数据是否为空重置clim为[min(C(:)) max(C(:))]图画出来了但只有一条线X、Y、C尺寸不匹配MATLAB只给出了部分顶点数据用size()检查三个变量的尺寸确保X、Y比C多一行一列或严格一致图上下颠倒数据行方向与坐标方向相反尝试axis ij或翻转C矩阵使用flipud图整体位置偏移顶点坐标与数据单元错位检查坐标是顶点坐标还是单元中心坐标若是中心坐标需换算到顶点色块边缘出现白线shading flat时patch之间的抗锯齿缝隙使用shading interp或将EdgeColor设为与图表底色一致导出图片模糊用了saveas或截图而非exportgraphics改用exportgraphics并设置300dpi以上分辨率大数据矩阵绘图卡顿pcolor创建大量patch对象内存和绘制开销大改用imagesc替代或对数据做降采样颜色条范围与图不对应多张图共用同一个colormap但未统一clim在每张图前显式调用clim([cmin cmax])6.2 pcolor大数据量卡顿的替代方案pcolor的底层机制决定了它在大数据量下会卡。一万乘一万的矩阵用pcolor画光是创建patch对象就能把内存撑爆更别提交互式缩放了。如果数据量超过500×500我的建议是权衡一下是否存在性能瓶颈。可以做降采样例如只取每隔一个的数据点但要注意降采样会损失峰值信息。更优雅的方案是改用imagesc配合插值显示或者使用surf(X, Y, C, EdgeColor, none)并调整视角到俯视。surf配合view(2)的方法在性能上和pcolor类似因为同样存在大量patch对象。真正适合大数据的还是imagescimagesc(x, y, C); axis xy; set(gca, YDir, normal); colormap(gca, parula); clim([cmin cmax]);imagesc的底层是图像对象GPU加速好、内存占用小一万乘一万的矩阵毫无压力。代价是它不是矢量图导出之后放大可能模糊但用exportgraphics设置高分辨率导出也能缓解这个问题。6.3 网格中心坐标与顶点坐标的“偏移”迷思最后分享一个我很长时间才想明白的细节。很多外部数据文件里存的是网格单元中心的坐标不是顶点坐标。直接把这些中心坐标作为pcolor的X、Y会导致整个色块偏移半个网格宽度的距离。在展示一些有明确边界的数据比如土地利用图、模型格点输出时这个偏移肉眼可见而且很难排查。解决方法是把中心坐标转换成顶点坐标。如果网格是均匀的转换方式很简单假设中心坐标向量为xc长度为n则顶点坐标xv的长度应该是n1每个顶点等于相邻两个中心的平均值首尾可以用外推补齐xv zeros(1, n1); xv(1) xc(1) - (xc(2) - xc(1)) / 2; xv(end) xc(end) (xc(end) - xc(end-1)) / 2; xv(2:end-1) (xc(1:end-1) xc(2:end)) / 2;这个操作在绘制定位精度要求高的图时非常关键。我自己在画气象格点数据时就遇到过这个问题当时数据的经纬度坐标是格点中心坐标直接用pcolor画出来等值线和地形边界对不上怎么查也查不出原因最后才发现是坐标本身偏了半格。其实pcolor用顺手之后你会发现它就是数据可视化工具箱里一个极其可靠的伙伴。它没有imagesc那么简单粗暴也不像contourf那样遮蔽了单元间的细节而是恰到好处地提供了颜色填充和坐标控制的平衡。在项目复盘时我常感慨这个函数值得花点时间认真掌握因为不管你是处理科学计算数据、工程仿真结果还是日常分析报表的矩阵数据它都能给你足够专业、足够清晰的视觉呈现。最后再说一个个人的小习惯我在保存所有pcolor绘图脚本时会强制规定三件事——显式设置clim、统一colormap、用exportgraphics导出。这三点看起来基础但能确保任何时候翻出旧脚本都能复现同样高质量的图。数据可视化从来都是细节决定成败的领域希望这篇经验总结能帮你少走一些弯路。
返回列表