MATLAB和弦图绘制实战:从关系矩阵到贝塞尔曲线 简介面向MATLAB用户与音乐理论初学者的和弦图绘制工具包基于MATLAB环境提供从数据整理到和弦图、双向和弦图biChordChart绘制的完整函数与脚本适合教学演示、音乐可视化分析及科研绘图场景。包内共141个文件其中46个.m脚本为绘图核心89个.png图片可用于效果预览与案例参考另含readme.txt说明、许可证文件及示例数据mat/xlsx整体大小约19.87MB结构清晰便于按需调用。已有56人学习下载。通过chordDemo系列示例与“弦图绘制挑战”使用者可快速掌握自定义配色、标签布局及交互式调整等操作同时理解音乐和弦结构在图形化表达中的映射关系为后续在音乐信息检索或数据可视化项目中复用提供可直接修改的工具基础。1. 为什么要和弦图关系矩阵可视化的最后一块拼图先说一个我自己的场景。前两年我在做通信网络的呼叫量分析手里有一张几十个节点之间的流量矩阵早期一直用热力图和桑基图凑合着看。热力图的问题是它能告诉你哪两个节点之间有流量但流量具体是怎么在整圈节点里流动的视觉上特别难追踪。桑基图又太偏流程方向用在双向关系上往往要拆成两张图反而更乱。后来第一次接触 chord chart和弦图把矩阵往里一丢所有关系清清楚楚绕成一圈我当时的感觉是这玩意儿就是给关系矩阵量身定做的。如果你在 MATLAB 里搜 chord chart大概率是在找两个东西一是 File Exchange 上现成的绘图工具二是怎么自己不依赖别人的包从底层画出来。本文把两条路都讲透。前半部分讲清楚和弦图的绘制原理和数据准备逻辑后半部分给到可以直接抄作业的完整代码、自定义配色和方向箭头的实现方式以及我在实际项目里踩过的一些坑。结果导向地说读完你可以做到三件事第一拿到任意一个方阵关系数据能快速判断适不适合用和弦图第二用现成工具在 10 行代码内画出第一版图第三遇到默认样式不满意的时候可以改成你想要的颜色、粗细、方向箭头还能导出高清图放进论文或者汇报材料里。2. 弦图生成的底层逻辑矩阵、圆弧与贝塞尔曲线2.1 数据从哪来一张对称或非对称的方阵和弦图的所有输入本质上都是一张 (n \times n) 的方阵行和列代表同一组对象。矩阵里的数值 (M(i,j)) 表示从对象 (i) 到对象 (j) 的连接强度或者关系流量。举个例子假如你在分析 GitHub 上几个仓库之间的互相引用关系那么行和列都是仓库名矩阵中第 3 行第 7 列的数就是仓库 3 指向仓库 7 的引用次数。关于对称和非对称这里要明确区分一下对称矩阵(M(i,j) M(j,i))常见于无向网络比如基因共表达、社交好友关系。画出来每条弦不带方向两端粗细一致。非对称矩阵(M(i,j)) 和 (M(j,i)) 不相等常见于资金流向、呼叫量、邮件往来这种有向关系。画出来通常要求每一端宽度不同甚至要加箭头来表示方向。我在最开始做图的时候吃过一个亏拿着一张非对称矩阵直接用了某个默认不支持方向的工具包结果图里的弦看起来全是双向等宽的完全看不出来谁是主动发起方。后来才明白画之前必须先确认矩阵是否对称再决定工具包。这个判断直接影响后面的图形解读务必放在第一步。2.2 圆弧分段把矩阵的行合计映射到一圈周长弦图的外部是一圈圆弧每一段弧对应一个节点。弧的长度通常正比于该节点的总连接强度也就是矩阵对应行的和或列的和取决于你想强调输出关系还是输入关系。具体计算很简单先把每个节点的总和算出来然后除以所有节点总和得到占比再乘以 (2\pi)就是这段弧的圆心角。[ \theta_i \frac{\sum_j M(i,j)}{\sum_i \sum_j M(i,j)} \times 2\pi ]这里有一个实践经验如果你的数据量级差异很大比如一个节点占 80% 的流量其他几个节点总共才 20%那么直接映射会得到一条几乎绕了 4/5 圈的弧视觉上非常不协调。更稳的做法是先对原始数据做开方或者对数缩放再计算占比这样小节点的弧长不会细成一条线。2.3 弦的绘制贝塞尔曲线是怎么绕过去的从圆弧上的一点连到另一点弦的轨迹不是直线而是贴着圆环内部弯曲的曲线。实际绘图中基本都用二次或三次贝塞尔曲线来实现这个弯曲效果。三次贝塞尔曲线的基本公式是[ B(t) (1-t)^3 P_0 3(1-t)^2 t P_1 3(1-t) t^2 P_2 t^3 P_3, \quad t \in [0,1] ]其中 (P_0) 是起点也就是弧段的一端(P_3) 是终点也就是另一个节点弧段的对应端而 (P_1)、(P_2) 是控制点。控制点放在哪里直接决定弦的弯曲程度和线条平滑度。最常见的做法是把两个控制点放在圆心附近、半径很小的位置或者放在垂直于弦切线方向上这样弦的进出方向能和圆弧自然衔接看起来像从弧上滑出来一样。早期测试时我试过最简单的直线连接结果是边缘处全是刺猬般的交叉射线乱得没法看。换成带控制点的贝塞尔曲线之后视觉上瞬间立起来了。所以如果你打算自己从底层写一个最小实现控制点的设置是整个代码里最需要花时间调的一处。2.4 一个 20 行的最小实现很多人一开始就去找 File Exchange 现成代码但我建议先自己写一个最小实现这样你能真真切切理解弦图的核心思路后面调包出了问题也不会一脸懵。下面这个示例代码不依赖任何第三方工具包纯 MATLAB 基础绘图函数就能跑function drawChordDemo() % 输入数据6x6 关系矩阵 M [0 3 1 0 2 0; 3 0 0 4 0 0; 1 0 0 2 1 3; 0 4 2 0 0 1; 2 0 1 0 0 2; 0 0 3 1 2 0]; n size(M, 1); rowSum sum(M, 2); totalSum sum(rowSum); % 每个节点的圆弧起始角度按累积和计算 thetaStart cumsum([0; rowSum(1:end-1)]) / totalSum * 2 * pi; thetaEnd cumsum(rowSum) / totalSum * 2 * pi; figure(Color, w, Position, [100 100 700 700]); ax axes(DataAspectRatio, [1 1 1]); hold(ax, on); axis(ax, off); % 画外围圆弧 colors lines(n); for i 1:n t linspace(thetaStart(i), thetaEnd(i), 50); plot(ax, cos(t), sin(t), LineWidth, 6, Color, colors(i, :)); end % 画弦贝塞尔曲线连接两个节点对应的边缘点 for i 1:n for j 1:n if M(i, j) 0 % 取起点和终点分别位于两段弧的中间位置 t0 (thetaStart(i) thetaEnd(i)) / 2; t1 (thetaStart(j) thetaEnd(j)) / 2; x0 cos(t0); y0 sin(t0); x1 cos(t1); y1 sin(t1); % 控制点取在圆心附近 ctrl [0, 0]; bezierPts bezierCurve([x0 y0], ctrl, ctrl, [x1 y1], 30); % 线宽正比于关系强度 lw max(0.5, M(i, j) / max(M(:)) * 4); plot(ax, bezierPts(:, 1), bezierPts(:, 2), ... Color, colors(i, :), LineWidth, lw); end end end end function pts bezierCurve(P0, P1, P2, P3, numPts) t linspace(0, 1, numPts); pts (1-t).^3 * P0 3*(1-t).^2.*t * P1 ... 3*(1-t).*t.^2 * P2 t.^3 * P3; end这段代码用到的最小 API 就是plot和linspace逻辑非常直白。它画出来的版本胜在让你看懂最核心的三件事角度计算、弧段绘制、贝塞尔曲线。有了这个底子下面再讲现成工具包的时候你对每一步在做什么就有感觉了。3. 基于 File Exchange 现成工具10 行代码出第一版图3.1 工具选型主流的两个包怎么选如果你不是想从零造轮子File Exchange 上早就有人写好了开箱即用的和弦图。常见的有两个工具包作者特点适合场景CircularGraphPaul Kassebaum代码简洁支持节点排序、可点击交互小规模、无方向或方向要求不高的图ChordChartKoert Kuipers / 其他维护者支持方向和箭头、支持自定义颜色映射、参数较多需要精细定制、需要展示方向的场景具体哪个更合适取决于你的矩阵是否对称。如果你的矩阵不对称且必须要表达方向我的建议直接选 ChordChart 这类支持箭头的版本如果只是无向关系图CircularGraph 用起来更省事。3.2 拿一个示例数据跑通流程以我在项目里实际用过的数据为例。假设你有 8 个区域之间的货物流量数据8x8 矩阵如下data [0 12 5 0 0 7 0 3; 11 0 0 6 0 0 4 0; 4 0 0 0 9 2 0 0; 0 8 0 0 0 0 5 1; 0 0 7 0 0 0 0 4; 6 0 3 0 2 0 8 0; 0 5 0 6 0 7 0 0; 2 0 1 0 5 0 0 0]; labels {A区,B区,C区,D区,E区,F区,G区,H区};把工具包文件添加到 MATLAB 路径后核心画图代码就是figure(Color, w, Position, [100 100 800 800]); c chordChart(data, labels, labels, colormap, parula(8)); c.draw();上述代码中chordChart构造器负责接收数据和标签c.draw()负责把所有几何对象画到当前 figure 上。第一版图出来之后先不要急着修改仔细对比一下弧长是否直观对应节点总流量弦的粗细是否明显不同标签是否重叠把这些问题记下来基本就是你接下来要定制的几个方向。3.3 为什么这类工具包普遍比手写快如果你对比 2.4 节的最小实现和上面的 5 行代码差距不仅是在代码量上更在于现成工具已经把如下几个细节处理得很完善节点间弦宽是否按源节点流量还是目标节点流量来决定默认逻辑是否合理。标签文字自动调整位置避免相互压盖。颜色映射接口统一可以直接对接 MATLAB 内置 colormap。对大量节点几十个情况下的贝塞尔曲线计算做了性能优化。所以我个人在实际项目中除非需求极其特殊否则不会从零实现整套绘图。正确的方式是先会写最小实现再依赖现成工具完成 90% 的基础工作剩下 10% 用自定义代码去改细节。4. 把默认图变成能放进汇报里的图配色、双向关系与标签排布4.1 配色映射的核心逻辑和弦图最容易被吐槽的就是配色花里胡哨。默认的 colormap 往往颜色太艳几十个节点全用高饱和色图看完眼睛都花了。我自己习惯的处理方法是节点少小于 10时直接用lines或parula节点多的时候用hsv但要降低饱和度或者用brewermap这类扩展配色方案。在 ChordChart 工具里指定颜色非常直接c chordChart(data, labels, labels, colormap, brewermap(8, Set2)); c.draw();这里brewermap不是 MATLAB 内置函数需要在 File Exchange 上下载扩展包。如果你不想多装一个依赖也能用内置的lines(8)或者parula(8)。还有一个实用细节如果你想突出某一个节点可以把其他节点的颜色统一调成灰色只让目标节点保留亮色。这样汇报时观众的目光会自动聚焦到你想讲的事情上。做法并不复杂构造 colormap 的时候手动替换对应行即可cmap repmat([0.7 0.7 0.7], 8, 1); % 默认灰色 cmap(3, :) [0.85 0.2 0.2]; % 第3个节点变红 c chordChart(data, labels, labels, colormap, cmap); c.draw();4.2 方向性标注箭头和不等宽弦如果你的矩阵不对称那么你实际上画的是有向图。默认的弦图两端宽度相等看不出来谁流向谁这时候需要手动标注方向。以我这边改过的一个实际例子来说思路是这样的在每条弦的终点端附近沿贝塞尔曲线的切线方向画一个小的三角形箭头。动手写扩展代码之前先检查你选用的工具是否已经内置箭头。CircleGraph 默认不支持箭头我在实际使用中后期加了箭头逻辑发现需要拿到工具内部贝塞尔曲线的控制点数据比较麻烦。所以我后来的做法是优先选支持Direction或Arrow属性的工具包或者干脆在画完弦之后收集弦端点坐标再叠加箭头。叠加箭头的大致代码如下% 获取曲线上最后几个点作为箭头方向参考 % pts 是某条贝塞尔曲线的坐标矩阵 endIdx size(pts, 1); delta pts(endIdx, :) - pts(endIdx - 4, :); arrowAngle atan2(delta(2), delta(1)); len 0.06; arrowW 0.03; % 在终点处画一个三角形 triX [pts(endIdx,1), ... pts(endIdx,1) - len*cos(arrowAngle - arrowW), ... pts(endIdx,1) - len*cos(arrowAngle arrowW)]; triY [pts(endIdx,2), ... pts(endIdx,2) - len*sin(arrowAngle - arrowW), ... pts(endIdx,2) - len*sin(arrowAngle arrowW)]; patch(triX, triY, r, EdgeColor, none);这段代码看起来很普通但实际踩坑点藏在endIdx - 4这个取差分点的距离上。如果取的太靠近终点方向角受数值噪声影响会抖动太远则箭头方向偏离实际曲线。我从经验上看取终点往回 3-6 个点最稳定具体可以根据贝塞尔曲线的采样密度微调。4.3 标签排布与弧段间隙标签重叠是新手最常见的抱怨。节点一多文字都挤成一团。解决办法分两类节点数少10 个以内直接用工具自带标签并将字体调小同时把图幅设置得大一些比如Position设为[100 100 900 900]。节点数多10-30 个建议关闭自动标签手动把文字放在弧外侧的固定半径处根据角度确定文字锚点。手动标签的参考实现for i 1:n midAngle (thetaStart(i) thetaEnd(i)) / 2; labelR 1.15; % 半径比弧段略大 xPos labelR * cos(midAngle); yPos labelR * sin(midAngle); if xPos 0 text(xPos, yPos, labels{i}, ... HorizontalAlignment, left, FontSize, 10); else text(xPos, yPos, labels{i}, ... HorizontalAlignment, right, FontSize, 10); end end这里有个细节根据 x 坐标正负决定左对齐还是右对齐看似简单却能很大程度上改善可读性。如果文字在左半边还默认左对齐文字会叠在图形中心方向非常丑。4.4 弧段间隙提升高级感的小技巧如果你看上过一些商业数据产品的弦图会发现节点的弧段之间通常有一小段空白而不是首尾相接。这种间隙感让整体图上了一个档次。实现也不复杂在计算每个节点起始角度时不要直接用累积和而是把每个节点的弧段两端各留出 1-3 度的空隙gapDeg 2 * pi / n * 0.03; % 按节点数缩放间隙 thetaStart(i) thetaStart(i) gapDeg / 2; thetaEnd(i) thetaEnd(i) - gapDeg / 2;不过要注意加了间隙之后贝塞尔曲线的起终点如果仍然取弧段中间位置视觉上会和弧段略微脱离。更好的做法是曲线端点仍然使用带间隙之前的角度位置这样弦能看起来“穿进”弧段内部。5. 实测避坑清单这些坑我基本全踩过5.1 矩阵不对称导致的宽度误解好多次画完图对方问我“这个弦从 A 到 B 这么粗B 到 A 回头却那么细是不是数据录入有问题”其实是因为矩阵不对称弦的两端宽度本就按各自方向上的强度定义。作图前一定要在示例图例中说明这一规则否则很容易被误读。另外如果你真的要表达总关系强度可以先用M M转成对称矩阵再作图把方向信息丢弃只保留总量。5.2 数据量级差异过大的处理我之前画过一张金融产品关联图其中头部产品的流量比其他产品高 100 倍画出来小产品的弦细成头发丝。后来我采用了一个折中方案先对矩阵做log10(M 1)缩放再画图。这样大流量的弦不会过分粗小流量的弦也能看清。但这种处理会改变弦宽的实际表示意义所以必须在注释里写清楚。5.3 导出图片模糊与字体丢失MATLAB 图形窗口直接截图通常画质不够。推荐用exportgraphics导出指定 300 分辨率exportgraphics(gcf, chord_diagram.png, Resolution, 300);如果要放入学术论文更建议导出矢量格式exportgraphics(gcf, chord_diagram.pdf, ContentType, vector);字体问题方面如果导出的 PDF 中文字显示为方框多半是字体缺失。解决方案是作图前统一设置字体比如set(gca, FontName, Helvetica)或者将文本对象的字体名指定为你系统里存在的字体。5.4 性能问题别拿几十上百节点的密集图硬跑和弦图最怕的是节点特别多且关系密集比如 50 个节点两两有连接那么弦的数量是 1225 条每条弦绘制为一个贝塞尔曲线对象渲染开销会明显变大。更麻烦的是这些弦互相交叉视觉上几乎不可读。我个人的处理建议是如果节点超过 30 个先做数据过滤只保留强度高于某阈值的连接把稀疏弱连接过滤掉。另一种方案是做社区归并把极小节点合并成一个大类再画图信息丢失可控但可读性大幅提升。5.5 交互式查看的扩展思路如果你想在汇报演示当中让人亲手点击某个弧段查看该节点所有关联和弦高亮可以考虑用 MATLAB App Designer 包一层把 ChordChart 画到UIAxes上再监听鼠标点击事件。这块实现起来需要额外注意坐标转换从屏幕坐标转换回数据坐标再计算点击角度从而判断命中了哪个节点。功能做完之后演示效果会非常加分这也是我认为和弦图在 MATLAB 里最被低估的扩展方向。6. 我实际工作流中的一个完整案例最后分享一个可以整体参考的流程。我之前处理区域货物流量数据时完成过一版从数据到成图的完整链路先用readmatrix把 Excel 表格读成矩阵核对行列标签和实际业务口径是否一致。这一步最容易出错因为表格里经常混着合计行、表头多行内容。用data data判断矩阵是否对称并打印出不对称的差异值大部分业务数据其实是不对称的所以这一步基本都会发现一些数据录入错误。按 2.2 节的缩放逻辑先绘制一版默认图观察哪几个节点占比过大哪几个节点几乎不可见。根据观察结果决定要不要对矩阵做 log 缩放并重新计算弧段占比。用第 4 节的配色方案替换默认颜色重点突出 2-3 个核心节点。手动添加方向箭头导出 300 dpi 的 PNG 放入汇报材料。整个流程跑下来大概只花了一个下午。第一次做的时候卡在“为什么弦的宽度不是按我想的那样”这个问题上很久后来才意识到根源在于我对矩阵是否对称的理解不够。希望你看完这篇能在我踩过的这些坑上直接跳过去。和弦图不是万能的它更适合中小规模、关系语义清晰的数据。一旦数据量和业务逻辑匹配它几乎是表达关系矩阵最有效的一类图。你在 MATLAB 里画图的时候如果还有别的定制需求比如调整弦的透明度、给某些连接单独加高亮也完全可以照着本文中的扩展思路把已有的贝塞尔曲线和颜色对象再做一层包装。本文还有配套的精品资源点击获取

相关新闻

最新新闻

电赛G题备赛:从51到STM32的单片机稳定方案框架

电赛G题备赛:从51到STM32的单片机稳定方案框架

2026电赛G题目前谁也说不准具体会考什么,但备赛完全不需要等题目。很多队伍最大的问题是到了现场才把开发板和传感器拼起来,结果测试时间一到,连基础功能都跑不稳。G题的难点通常不是某一个电路原理,而是把单片机、传感器、执行器…

2026/9/1 8:26:40
游戏手机稳定性实测指南:帧率、温控与功耗的评判标准

游戏手机稳定性实测指南:帧率、温控与功耗的评判标准

同价位游戏机谁更稳?iQOO Neo10 和 Z10 Turbo Pro 是两千元档里经常被放在一起比较的两台直屏机型,都有高刷屏,都强调游戏调度,价格又落在同一区间。可“稳”这个字在不同人口中含义完全不同:有人说的稳是长时间不掉帧…

2026/9/1 8:26:40
Cesium自定义Shader实战:雷达扫描与飞线动画性能优化

Cesium自定义Shader实战:雷达扫描与飞线动画性能优化

之前在做 Cesium 项目时,被一个很实际的问题卡了很久:同样的雷达扫描、飞线效果,我拿CallbackProperty一帧一帧去改位置,结果浏览器 CPU 直接拉满,帧率掉到个位数。而别人做的特效,不仅顺滑,而且…

2026/9/1 8:26:40
【单片机毕业设计】基于 STM32 或 51 单片机的 WiFi 多路温度数据传输与报警系统设计 基于 STM32 或 51 单片机的 LED 状态指示多路温度监测装置设计(022805)

【单片机毕业设计】基于 STM32 或 51 单片机的 WiFi 多路温度数据传输与报警系统设计 基于 STM32 或 51 单片机的 LED 状态指示多路温度监测装置设计(022805)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/9/1 8:26:40
ASP.NET Core 从入门到精通:系统学习路线与实战项目指南

ASP.NET Core 从入门到精通:系统学习路线与实战项目指南

如果你是一名 .NET 开发者,或者正打算进入这个领域,那么“ASP.NET Core”这个词对你来说一定不陌生。但你可能正面临一个更实际的问题: 面对海量的视频教程、博客文章和官方文档,如何才能真正高效地掌握 ASP.NET Core&#xff0c…

2026/9/1 8:26:40
从H桥到FOC:电机驱动与控制全链路工程实践指南

从H桥到FOC:电机驱动与控制全链路工程实践指南

在机器人、自动化、无人机和智能硬件领域,电机驱动与控制是连接数字指令与物理动作的核心桥梁。无论是让机械臂精准抓取,还是让无人机稳定悬停,其背后都离不开对电机转矩、转速和位置的精确控制。然而,从原理图上的一个H桥电路&am…

2026/9/1 8:21:40