MATLAB | 如何使用MATLAB绘制弧形图/弧长链接图(arc diagram)

嘿大家好久不见,今天来画一个弧长链接图,大概长这样:

感觉有点像弦图把原本环绕的节点有序的放在了一条直线上:

能够明显的看出弧长链接图更加重视了节点之间的顺序,常见的用法比如:

  • 使用节点表示一辆列车各个站点,两个站点之间的连线宽度代表有多少人前一个站点上车,后一个站点下车。
  • 人物关系网络,将人物按照首次出场顺序排列。
  • 将基因按照其在染色体上的物理位置排列成一条线。如果两个基因在功能上相关或同时被表达(共表达),就用一条弧线将它们连接起来。

代码获取

工具函数完整代码见文末或者gitee仓库:

示例

基本使用

假设我们有一个上三角矩阵,第i行第j列的数值代表由节点i流向节点j的流量,主对角线的数值可以都设置为1,或者用来存储其他可视化信息,用来控制节点大小:

matlab 复制代码
Data = [25,  0, 11, 22, 24, 25,  8,  0;
         0, 14,  0,  0,  0,  0,  4,  0;
         0,  0, 11,  0,  0,  0,  0,  0;
         0,  0,  0, 22,  0, 20, 17,  0;
         0,  0,  0,  0, 24,  3,  6,  3;
         0,  0,  0,  0,  0, 25,  7,  0;
         0,  0,  0,  0,  0,  0, 17,  0;
         0,  0,  0,  0,  0,  0,  0, 11];

% 创建弧长链接图对象(Create a arc chart object)
ARC = arcChart(Data);

% 开始绘图(Start drawing)
ARC.draw();

% 显示并修改标签字体(Show labels and set the font)
ARC.setLabel('FontSize', 17, 'FontName', 'Times New Roman')

% 显示图例(Display legend)
% ARC.nodeLegend()
% ARC.edgeLegend()
%              Position
ARC.nodeLegend([-.5, 0], 'FontSize', 15, 'FontName', 'Times New Roman')
ARC.edgeLegend([-.5, 2.5], 'FontSize', 15, 'FontName', 'Times New Roman')

节点大小与线条粗细

节点大小和线条粗细都是一个含两个元素的数组,如果两个元素设置的一样的话,所有节点都一样大,所有弦图都一样粗细:

matlab 复制代码
Data = [1, 1, 6, 0;
        0, 1, 0, 1;
        0, 0, 5, 2;
        0, 0, 0, 1];

ARC = arcChart(Data);
ARC.NodeName = {'北京','上海','广州','深圳'};     % 设置节点名称(Set node names)
ARC.NodeSizeLim = [.07, .07];                    % 设置节点大小范围(Set size limit of node)
ARC.EdgeWidthLim = [.1, .1];                     % 设置链接宽度范围(Set width limit of edge)

ARC.draw();
ARC.setLabel()

顺带一提:字体可以单独修改,例如:

matlab 复制代码
ARC.setLabelN(2, 'Rotation', 0, 'Color','r', 'HorizontalAlignment','center')

链接的渲染

设置RenderingMethod属性即可,可以选择的方法有:

'simple', 'left', 'right', 'interp', 'map', 'bicolor', 'source', 'target'

matlab 复制代码
rng(1)
Data = randi([1, 25], [8, 8]);
Data(rand([8, 8]) < .65) = 0;
Data = Data + eye(8);

% Color list
CList = [ 75,146,241; 252,180, 65; 224, 64, 10;   5,100,146; 191,191,191;
          26, 59,105; 255,227,130;  18,156,221; 202,107, 75;   0, 92,219;
         243,210,136;  80, 99,129; 241,185,168; 224,131, 10; 120,147,190]./255;

ARC = arcChart(Data);
% 设置节点和链接颜色,若未设置链接颜色则默认与节点颜色相同
% Set node color and edge color, the default edge color is as same as node color
ARC.NodeColor = CList;
ARC.EdgeColor = CList;
ARC.NodeSizeLim = [.2, .2];
ARC.EdgeWidthLim = [.02, .2];

% 设置链接样式(Set the rendering method for edges)
% 'simple', 'left', 'right', 'interp', 'map', 'bicolor', 'source', 'target'
method = 'target';
ARC.RenderingMethod = method;
ARC.draw();

% colorbar
text(4.5, 4, method, 'FontSize',27, 'FontWeight','bold', 'HorizontalAlignment','center')

单色

双色

与左节点颜色相同

与右节点颜色相同

渐变

与数值大小相关

与起始节点颜色相同

与结束节点颜色相同


一个例子

matlab 复制代码
rng(2)
% 随机生成数据(Randomly generated data)
Data = zeros(40, 40);
Data(1:15, 1:15)   = triu(rand(15));
Data(16:20, 16:20) = triu(rand(5));
Data(21:30, 21:30) = triu(rand(10));
Data(31:40, 31:40) = triu(rand(10));
Data(rand([40, 40]) < .65) = 0;
Data = Data + eye(40);

% Colors and name list for nodes
CList = [ 75,146,241; 252,180, 65; 224, 64, 10;   5,100,146]./255;
Class = [ones(1,15), 2.*ones(1,5), 3.*ones(1,10), 4.*ones(1,10)];
NList = [compose('nodeS-%d',1:15), compose('nodeL-%d',1:5), ...
         compose('nodeA-%d',1:10), compose('nodeN-%d',1:10)];

ARC = arcChart(Data);
ARC.NodeName = NList;
ARC.NodeColor = CList(Class.',:);
ARC.EdgeColor = [0,0,0];
ARC.NodeSizeLim = [.15,.15];
ARC.EdgeWidthLim = [.01,.06];
ARC.draw();

ARC.setLabel('FontSize',14, 'FontName','Times New Roman')
axis tight

工具函数完整代码:

matlab 复制代码
classdef arcChart < handle

    properties
        ax
        arginList = {'RenderingMethod', 'NodeSizeLim', 'EdgeWidthLim', ...
                     'NodeColor', 'EdgeColor'}      
        dataMat                      % 数值矩阵 
        NodeName = {}
        NodeColor = [0, 0, 0]        % 节点配色
        EdgeColor = 'flat'           % 链接配色
        NodeAlpha = 1                % 节点透明度
        EdgeAlpha = .3               % 连接透明度 
        RenderingMethod = 'simple'   % 'left'/'right'/'interp'/'map'
                                     %  'simple'/'bicolor'/'source'/'target'
        NodeSizeLim  = [.1, .2 ]     % 节点大小范围
        EdgeWidthLim = [.02, .1]     % 链接宽度范围
        
        edgeMatHdl
        nodeHdl
        labelHdl
    end

    methods
        function obj = arcChart(varargin)
            if isa(varargin{1},'matlab.graphics.axis.Axes')
                obj.ax = varargin{1};
                varargin(1) = [];
            else
                obj.ax = gca;
            end
           
            obj.dataMat = abs(varargin{1});
            varargin(1) = [];
            for i = 1:2:(length(varargin) - 1)
                tid = ismember(lower(obj.arginList), lower(varargin{i}));
                if any(tid)
                    obj.(obj.arginList{tid}) = varargin{i + 1};
                end
            end
            if isempty(obj.NodeName)
                obj.NodeName = compose('Node-%d', 1:size(obj.dataMat, 1));
            end
        end
        function obj = draw(obj)
            if isstring(obj.EdgeColor) || ischar(obj.EdgeColor)
                tmpColor = obj.NodeColor;
            else
                tmpColor = obj.EdgeColor;
            end
            % --------------------------------- 
            obj.ax.NextPlot = 'add';            
            obj.ax.DataAspectRatio = [1, 1, 1]; 
            obj.ax.XColor = 'none';
            obj.ax.YColor = 'none';
            % ---------------------------------
            t1 = linspace(0, 2*pi, 200);
            t2 = linspace(pi, 0, 200);
            x1 = cos(t1); y1 = sin(t1);
            x2 = cos(t2); y2 = sin(t2);
            minN = min(obj.dataMat(eye(size(obj.dataMat)) == 1 & obj.dataMat ~= 0));
            maxN = max(obj.dataMat(eye(size(obj.dataMat)) == 1 & obj.dataMat ~= 0));
            minE = min(obj.dataMat(eye(size(obj.dataMat)) == 0 & obj.dataMat ~= 0));
            maxE = max(obj.dataMat(eye(size(obj.dataMat)) == 0 & obj.dataMat ~= 0));


            % 绘制连接
            for i = 1:size(obj.dataMat, 1)
                for j = 1:size(obj.dataMat, 1)
                    if i ~= j && obj.dataMat(i, j) ~= 0
                        if maxE == minE
                            tR2 = 0*(obj.dataMat(i, j) == 0) + max(abs(obj.EdgeWidthLim))*(obj.dataMat(i, j) > 0);
                        else
                            tR2 = 0*(obj.dataMat(i, j) == 0) + ...
                                ((obj.dataMat(i, j) - minE)./(maxE - minE).*abs(diff(obj.EdgeWidthLim)) + min(abs(obj.EdgeWidthLim)))*(obj.dataMat(i, j) > 0);
                        end
                        if j > i
                            tX1 = (i + j)/2 + ((j - i)/2 + tR2)*(x2);
                            tX2 = (i + j)/2 + ((j - i)/2 - tR2)*x2(end:-1:1);
                            tY1 = ((j - i)/2 + tR2)*y2;
                            tY2 = ((j - i)/2 - tR2)*y2(end:-1:1);
                        else
                            tX1 = (i + j)/2 + ((i - j)/2 + tR2)*(x2);
                            tX2 = (i + j)/2 + ((i - j)/2 - tR2)*x2(end:-1:1);
                            tY1 = - ((i - j)/2 + tR2)*y2;
                            tY2 = - ((i - j)/2 - tR2)*y2(end:-1:1);
                        end
                        TT = repmat(linspace(0,1,10).', [1, 200]);
                        XX = repmat(tX2(end:-1:1) - tX1, [10, 1]).*TT + repmat(tX1, [10,1]);
                        YY = repmat(tY2(end:-1:1) - tY1, [10, 1]).*TT + repmat(tY1, [10,1]);
                        MC = ones(10, 200, 3);

                        switch obj.RenderingMethod
                            case 'simple'
                                obj.edgeMatHdl(i, j) = fill(obj.ax, [tX1, tX2], [tY1, tY2], tmpColor(1, :), 'EdgeColor','none', 'FaceAlpha',obj.EdgeAlpha);
                            case 'source'
                                obj.edgeMatHdl(i, j) = fill(obj.ax, [tX1, tX2], [tY1, tY2], tmpColor(mod(i - 1, size(tmpColor, 1)) + 1, :), 'EdgeColor','none', 'FaceAlpha',obj.EdgeAlpha);
                            case 'target'
                                obj.edgeMatHdl(i, j) = fill(obj.ax, [tX1, tX2], [tY1, tY2], tmpColor(mod(j - 1, size(tmpColor, 1)) + 1, :), 'EdgeColor','none', 'FaceAlpha',obj.EdgeAlpha);
                            case 'left'
                                obj.edgeMatHdl(i, j) = fill(obj.ax, [tX1, tX2], [tY1, tY2], tmpColor(mod(i*(j > i) + j*(j < i) - 1, size(tmpColor, 1)) + 1, :), 'EdgeColor','none', 'FaceAlpha',obj.EdgeAlpha);
                            case 'right'
                                obj.edgeMatHdl(i, j) = fill(obj.ax, [tX1, tX2], [tY1, tY2], tmpColor(mod(j*(j > i) + i*(j < i) - 1, size(tmpColor, 1)) + 1, :), 'EdgeColor','none', 'FaceAlpha',obj.EdgeAlpha);
                            case 'interp'
                                if j > i
                                    tCi = tmpColor(mod(i - 1, size(tmpColor, 1)) + 1, :);
                                    tCj = tmpColor(mod(j - 1, size(tmpColor, 1)) + 1, :);
                                else
                                    tCi = tmpColor(mod(j - 1, size(tmpColor, 1)) + 1, :);
                                    tCj = tmpColor(mod(i - 1, size(tmpColor, 1)) + 1, :);
                                end
                                MC(:, :, 1) = repmat(linspace(tCi(1), tCj(1), 200), [10, 1]);
                                MC(:, :, 2) = repmat(linspace(tCi(2), tCj(2), 200), [10, 1]);
                                MC(:, :, 3) = repmat(linspace(tCi(3), tCj(3), 200), [10, 1]);
                                obj.edgeMatHdl(i, j) = surf(obj.ax, XX, YY, XX.*0, 'CData',MC, 'EdgeColor','none', 'FaceAlpha',obj.EdgeAlpha);
                            case 'bicolor'
                                if j > i
                                    tCi = tmpColor(mod(1 - 1, size(tmpColor, 1)) + 1, :);
                                    tCj = tmpColor(mod(2 - 1, size(tmpColor, 1)) + 1, :);
                                else
                                    tCi = tmpColor(mod(2 - 1, size(tmpColor, 1)) + 1, :);
                                    tCj = tmpColor(mod(1 - 1, size(tmpColor, 1)) + 1, :);
                                end
                                MC(:, :, 1) = repmat(linspace(tCi(1), tCj(1), 200), [10, 1]);
                                MC(:, :, 2) = repmat(linspace(tCi(2), tCj(2), 200), [10, 1]);
                                MC(:, :, 3) = repmat(linspace(tCi(3), tCj(3), 200), [10, 1]);
                                obj.edgeMatHdl(i, j) = surf(obj.ax, XX, YY, XX.*0, 'CData',MC, 'EdgeColor','none', 'FaceAlpha',obj.EdgeAlpha);
                            case 'map'
                                obj.edgeMatHdl(i, j) = fill(obj.ax, [tX1, tX2], [tY1, tY2], [0,0,0], 'EdgeColor','none', 'FaceAlpha',obj.EdgeAlpha, 'FaceColor','flat', 'CData',obj.dataMat(i,j));
                        end
                        % if obj.dataMat(i, j) == 0
                        %     set(obj.edgeMatHdl(i, j), 'Visible', 'off')
                        % end
                    end
                end
            end


            % 绘制节点和标签
            for i = 1:size(obj.dataMat, 1)
                if maxN == minN
                    tR1 = 0*(obj.dataMat(i, i) == 0) + max(abs(obj.NodeSizeLim))*(obj.dataMat(i, i) > 0);
                else
                    tR1 = 0*(obj.dataMat(i, i) == 0) + ...
                    ((obj.dataMat(i, i) - minN)./(maxN - minN).*abs(diff(obj.NodeSizeLim)) + min(abs(obj.NodeSizeLim)))*(obj.dataMat(i, i) > 0);
                end
                obj.nodeHdl(i) = fill(obj.ax, i + x1.*tR1, y1.*tR1, obj.NodeColor(mod(i - 1, size(obj.NodeColor, 1)) + 1, :), ...
                    'EdgeColor','none', 'FaceAlpha',obj.NodeAlpha);
                obj.labelHdl(i) = text(obj.ax, i, -.3, obj.NodeName{i}, 'FontSize',17, 'HorizontalAlignment','right', 'Rotation',45, 'Visible','off');
            end
        end

        function setLabelN(obj, n, varargin)
            set(obj.labelHdl(n), 'Visible','on', varargin{:})
        end
        function setLabel(obj, varargin)
            for i = 1:size(obj.dataMat, 1)
                set(obj.labelHdl(i), 'Visible','on', varargin{:})
            end
        end

        function nodeLegend(obj, varargin)
            if nargin < 2
                pos = [-.5, 0];
            elseif isnumeric(varargin{1})
                pos = varargin{1};
                varargin(1) = [];
            end

            t1 = linspace(0, 2*pi, 200);
            x1 = cos(t1); y1 = sin(t1);
            minN = min(obj.dataMat(eye(size(obj.dataMat)) == 1 & obj.dataMat ~= 0));
            maxN = max(obj.dataMat(eye(size(obj.dataMat)) == 1 & obj.dataMat ~= 0));
            fill(obj.ax, x1.*min(abs(obj.NodeSizeLim)) + pos(1), y1.*min(abs(obj.NodeSizeLim)) + pos(2), [1,1,1],'EdgeColor','k','LineWidth',1)
            fill(obj.ax, x1.*mean(abs(obj.NodeSizeLim)) + pos(1), y1.*mean(abs(obj.NodeSizeLim)) + pos(2) + 2*sum(abs(obj.NodeSizeLim)), [1,1,1],'EdgeColor','k','LineWidth',1)
            fill(obj.ax, x1.*max(abs(obj.NodeSizeLim)) + pos(1), y1.*max(abs(obj.NodeSizeLim)) + pos(2) + 4*sum(abs(obj.NodeSizeLim)), [1,1,1],'EdgeColor','k','LineWidth',1)
            plot(obj.ax, pos(1) + max(abs(obj.NodeSizeLim)) + [.1,.1], pos(2) + [0, 4*sum(abs(obj.NodeSizeLim))], 'LineWidth',1, 'Color','k')
            plot(obj.ax, pos(1) + max(abs(obj.NodeSizeLim)) + [.1,.2], pos(2) + [0, 0], 'LineWidth',1, 'Color','k')
            plot(obj.ax, pos(1) + max(abs(obj.NodeSizeLim)) + [.1,.2], pos(2) + [0, 0] + 4*sum(abs(obj.NodeSizeLim)), 'LineWidth',1, 'Color','k')
            text(obj.ax, pos(1) + max(abs(obj.NodeSizeLim)) + .22, pos(2) + 0, num2str(minN), 'FontSize',14,varargin{:})
            text(obj.ax, pos(1) + max(abs(obj.NodeSizeLim)) + .22, pos(2) + 4*sum(abs(obj.NodeSizeLim)), num2str(maxN), 'FontSize',14,varargin{:})
        end
        function edgeLegend(obj, varargin)
            if nargin < 2
                pos = [-.5, size(obj.dataMat, 1)/2 *.5];
            elseif isnumeric(varargin{1})
                pos = varargin{1};
                varargin(1) = [];
            end
            x2 = [-1, -1, 1, 1];
            y2 = [-1, 1, 1, -1];
            minE = min(obj.dataMat(eye(size(obj.dataMat)) == 0 & obj.dataMat ~= 0));
            maxE = max(obj.dataMat(eye(size(obj.dataMat)) == 0 & obj.dataMat ~= 0));
            fill(obj.ax, x2.*min(abs(obj.EdgeWidthLim)) + pos(1), y2.*min(abs(obj.EdgeWidthLim)) + pos(2), [1,1,1],'EdgeColor','k','LineWidth',1)
            fill(obj.ax, x2.*mean(abs(obj.EdgeWidthLim)) + pos(1), y2.*mean(abs(obj.EdgeWidthLim)) + pos(2) + 2*sum(abs(obj.EdgeWidthLim)), [1,1,1],'EdgeColor','k','LineWidth',1)
            fill(obj.ax, x2.*max(abs(obj.EdgeWidthLim)) + pos(1), y2.*max(abs(obj.EdgeWidthLim)) + pos(2) + 4*sum(abs(obj.EdgeWidthLim)), [1,1,1],'EdgeColor','k','LineWidth',1)
            plot(obj.ax, pos(1) + max(abs(obj.EdgeWidthLim)) + [.1,.1], pos(2) + [0, 4*sum(abs(obj.EdgeWidthLim))], 'LineWidth',1, 'Color','k')
            plot(obj.ax, pos(1) + max(abs(obj.EdgeWidthLim)) + [.1,.2], pos(2) + [0, 0], 'LineWidth',1, 'Color','k')
            plot(obj.ax, pos(1) + max(abs(obj.EdgeWidthLim)) + [.1,.2], pos(2) + [0, 0] + 4*sum(abs(obj.EdgeWidthLim)), 'LineWidth',1, 'Color','k')
            text(obj.ax, pos(1) + max(abs(obj.EdgeWidthLim)) + .22, pos(2) + 0, num2str(minE), 'FontSize',14,varargin{:})
            text(obj.ax, pos(1) + max(abs(obj.EdgeWidthLim)) + .22, pos(2) + 4*sum(abs(obj.EdgeWidthLim)), num2str(maxE), 'FontSize',14,varargin{:})
        end
    end
end
相关推荐
ghie90904 小时前
基于学习的模型预测控制(LBMPC)MATLAB实现指南
开发语言·学习·matlab
cici158746 小时前
非线性模型预测控制(NMPC)基于CasADi的MATLAB实现
开发语言·matlab
t198751286 小时前
MATLAB模糊数学模型(Fuzzy Mathematical Model)实现指南
开发语言·matlab
沅_Yuan6 小时前
基于贝叶斯优化的稀疏高斯过程回归(BO-SGPR)多输入单输出回归模型【MATLAB】
神经网络·机器学习·matlab·回归·回归预测·贝叶斯优化
Evand J6 小时前
MATLAB批量保存现有绘图窗口的方法,简易方法,直接保存到当前目录,不手动设置
开发语言·matlab·教程
feifeigo1237 小时前
航天器交会的分布式模型预测控制(DMPC)MATLAB实现
开发语言·分布式·matlab
沅_Yuan7 小时前
基于不确定性量化的CNN-LSTM-Attention多输入单输出回归模型【MATLAB】
神经网络·matlab·回归·cnn·lstm·回归预测
551只玄猫8 小时前
【数学建模 matlab 实验报告9】数据的统计分析与描述
数学建模·matlab·数据分析·课程设计·实验报告
机器学习之心9 小时前
Matlab基于RF随机森林的锂电池剩余寿命预测
随机森林·matlab·锂电池剩余寿命预测·rf随机森林