MATLAB | MATLAB桑基图绘制函数更新啦~

文摘   其他   2024-04-16 00:17   山东  
请尊重原创劳动成果
转载请注明本文链接
及文章作者:slandarer

MATLAB 桑基图绘制函数大更新啦,本次更新主要是根据fileexchange上大家提出和反馈的建议增添的新功能,我们首先看一下该工具的介绍,基本用法,然后再看看新功能叭:


工具介绍

SSankey 是本人编写的 MATLAB 桑基图绘制工具,可以实现高度自定义的桑基图,例如:

该工具可在以下fileexchange链接或者文末提供的gitee仓库下载:

  • https://www.mathworks.com/matlabcentral/fileexchange/128679-sankey-plot

基本用法

基本用法非常简单,例如:

links={'a1','A',1.2;'a2','A',1;'a1','B',.6;'a3','A',1'a3','C',0.5;
       'b1','B',.4'b2','B',1;'b3','B',1'c1','C',1;
       'c2','C',1;  'c3','C',1;'A','AA',2'A','BB',1.2;
       'B','BB',1.5'B','AA',1.5'C','BB',2.3'C','AA',1.2};

% 创建桑基图对象(Create a Sankey diagram object)
SK=SSankey(links(:,1),links(:,2),links(:,3));
% 开始绘图(Start drawing)
SK.draw()

更多用法请见以下文章:


更新内容

前两条更新意见是由Kamuran Turksoy提出的:The figures look great but its difficult to use the tool if you have multiple layers with many data points. You need to define the links one by one. It would be great if it accepted data in table/matrix format and created the links automatically.

1 邻接矩阵创建桑基图

支持使用邻接矩阵创建桑基图,不预先设定节点名称的话,节点将被命名为node-n: (注意,目前不支持两个节点互相有数据流动的情况,即流动必须是单向的)

adjMat=[0,0,0,1,2,1,0,0,0,0;
        0,0,0,1,2,3,0,0,0,0;
        0,0,0,2,0,1,0,0,0,0;
        0,0,0,0,0,0,1,4,0,0;
        0,0,0,0,0,0,2,1,0,0;
        0,0,0,0,0,0,0,3,0,0;
        0,0,0,0,0,0,0,0,1,5;
        0,0,0,0,0,0,0,0,2,3;
        0,0,0,0,0,0,0,0,0,0;
        0,0,0,0,0,0,0,0,0,0];

nodeList=compose('C%d',1:10);

% 创建桑基图对象(Create a Sankey diagram object)

SK=SSankey([],[],[],'NodeList',nodeList,'AdjMat',adjMat);
% method 1
% SK=SSankey([],[],[],'AdjMat',adjMat);
% method 2
% SK=SSankey([],[],[],'NodeList',nodeList,'AdjMat',adjMat)
% method 3
% SK=SSankey([],[],[]);
% SK.AdjMat=adjMat; 

% 开始绘图(Start drawing)
SK.draw()

2 自由调整节点层级

可以通过调整Layer属性来实现,Layer是一个数组,显示每一个节点属于哪一层,比如第i个数值是n就意味着第i个节点在n层(注意,暂不支持相互之间有流动的节点在同一层级,即层级内暂不支持数据流动)

adjMat=[0,0,0,1,2,1,0,0,0,0;
        0,0,0,1,2,3,0,0,0,0;
        0,0,0,2,0,1,0,0,0,0;
        0,0,0,0,0,0,1,4,0,0;
        0,0,0,0,0,0,2,1,0,0;
        0,0,0,0,0,0,0,3,0,0;
        0,0,0,0,0,0,0,0,1,5;
        0,0,0,0,0,0,0,0,2,3;
        0,0,0,0,0,0,0,0,0,0;
        0,0,0,0,0,0,0,0,0,0];
% nodeList={'C1','C2',C3',...'C10'}
nodeList=compose('C%d',1:10);
layer=[1,1,2,4,4,3,6,6,7,7];

% 创建桑基图对象(Create a Sankey diagram object)
SK=SSankey([],[],[],'NodeList',nodeList,'AdjMat',adjMat,'Layer',layer);
% SK.Layer = layer;

% 开始绘图(Start drawing)
SK.draw()

SK.moveBlockY(3,-10)
SK.moveBlockY(6,-10)

第三条更新意见是由Paul Maximilian Röhrig提出的:Is it possible to move blocks in the X direction as well as the Y direction? Many thanks for your help.

3 移动节点x坐标

使用以下方式移动节点x坐标:

  • obj.moveBlockX(n,dx)
links={'a1','A',1.2;'a2','A',1;'a1','B',.6;'a3','A',1'a3','C',0.5;
       'b1','B',.4'b2','B',1;'b3','B',1'c1','C',1;
       'c2','C',1;  'c3','C',1;'A','AA',2'A','BB',1.2;
       'B','BB',1.5'B','AA',1.5'C','BB',2.3'C','AA',1.2};

% 创建桑基图对象(Create a Sankey diagram object)
SK=SSankey(links(:,1),links(:,2),links(:,3));

% 开始绘图(Start drawing)
SK.draw()

for i=1:9
    SK.moveBlockX(i,i/10)
end
for i=10:12
    SK.moveBlockX(i,(i-9)*3/10-.3)
end

新版本工具函数完整代码

classdef SSankey < handle
% Copyright (c) 2023, Zhaoxu Liu / slandarer
% =========================================================================
% @author : slandarer
% 公众号  : slandarer随笔
% 知乎    : slandarer
% =========================================================================
% # update 2.0.0(2024-02-04)
% see natureSankeyDemo1.m
%
% + 层向右对齐(Align layers to the right)
%   try : obj.LayerOrder='reverse';
%
% + 单独调整每层间隙大小(Adjust the Sep size of each layer separately)
%   try : obj.Sep=[.2,.06,.05,.07,.07,.08,.15];
% =========================================================================
% # update 3.0.0(2024-04-15)
% see sankeyDemo9.m sankeyDemo10.m sankeyDemo11.m

% + 通过邻接矩阵创建桑基图(Creating a Sankey diagram through adjacency matrix)
%   method 1 :
%     SK=SSankey([],[],[],'AdjMat',adjMat);
%   method 2 :
%     SK=SSankey([],[],[],'NodeList',nodeList,'AdjMat',adjMat)
%   method 3 :
%     SK=SSankey([],[],[]);
%     SK.AdjMat=adjMat;

%   try : 
%     adjMat=zeros(10,10);
%     layerNum=[3,3,2,2];
%     layerInd=cumsum([0,layerNum]);
%     for i=1:length(layerInd)-2
%         adjMat(layerInd(i)+1:layerInd(i+1),layerInd(i+1)+1:layerInd(i+2))=randi([1,6],[layerNum([i,i+1])]);
%     end
%     disp(adjMat)
%     SK=SSankey([],[],[],'NodeList',nodeList,'AdjMat',adjMat);
%     SK.draw()
%
% + 每层情况可被设置(Each layer state can be set)
%   try : obj.Layer = [1,1,1, 2,2,2, 3,3, 4,4,...];

% + 每个节点可在x方向上位移(Each node can be displaced in the x-direction)
%   try : obj.moveBlockX(n,dx)

    properties
        Source;Target;Value;
        SourceInd;TargetInd;
        Layer;LayerPos;LayerOrder='normal';
        AdjMat;BoolMat;
        RenderingMethod='interp'  % 'left'/'right'/'interp'/'map'/'simple'
        LabelLocation='left'      % 'left'/'right'/'top'/'center'/'bottom'
        Align='center'            % 'up'/'down'/'center'
        BlockScale=0.05;          %  BlockScale>0 ! !
        Sep=0.05;                 %  Sep>=0 ! !
        NodeList={};
        ColorList=[[65,140,240;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;
                   [127,91,93;187,128,110;197,173,143;59,71,111;104,95,126;76,103,86;112,112,124;
                    72,39,24;197,119,106;160,126,88;238,208,146]./255];
        BlockHdl;LinkHdl;LabelHdl;ax;Parent;
        BN;LN;VN;TotalLen;SepLen;
        arginList={'RenderingMethod','LabelLocation','BlockScale','Layer'...
                   'Sep','Align','ColorList','Parent','NodeList','AdjMat'}
    end
% 构造函数 =================================================================
    methods
        function obj=SSankey(varargin)
            % 获取基本数据 -------------------------------------------------
            if isa(varargin{1},'matlab.graphics.axis.Axes')
                obj.ax=varargin{1};varargin(1)=[];
            else  
            end
            obj.Source=varargin{1};
            obj.Target=varargin{2};
            obj.Value=varargin{3};
            varargin(1:3)=[];
            % 获取其他信息 -------------------------------------------------
            for i=1:2:(length(varargin)-1)
                tid=ismember(obj.arginList,varargin{i});
                if any(tid)
                obj.(obj.arginList{tid})=varargin{i+1};
                end
            end
            if isempty(obj.ax)&&(~isempty(obj.Parent)),obj.ax=obj.Parent;end
            if isempty(obj.ax),obj.ax=gca;end
            obj.ax.NextPlot='add';
            % 基本数据预处理 -----------------------------------------------
            if isempty(obj.NodeList)
                if isempty(obj.Source)
                    if ~isempty(obj.AdjMat)
                        obj.NodeList=compose('node%d',1:size(obj.AdjMat,1));
                    end
                else
                    obj.NodeList=[obj.Source;obj.Target];
                    obj.NodeList=unique(obj.NodeList,'stable');
                end
            end
            obj.BN=length(obj.NodeList);
            if length(obj.NodeList)>size(obj.ColorList,1)
                obj.ColorList=[obj.ColorList;rand(length(obj.NodeList),3).*.7];
            end
            % obj.VN=length(obj.Value);
            % 坐标区域基础设置 ---------------------------------------------
            obj.ax.YDir='reverse';
            obj.ax.XColor='none';
            obj.ax.YColor='none';
        end
% 绘图函数 =================================================================
        function draw(obj)      
            % 生成整体邻接矩阵 ---------------------------------------------
            if isempty(obj.AdjMat)
                obj.AdjMat=zeros(obj.BN,obj.BN);
                for i=1:length(obj.Source)
                    obj.SourceInd(i)=find(strcmp(obj.Source{i},obj.NodeList));
                    obj.TargetInd(i)=find(strcmp(obj.Target{i},obj.NodeList));
                    obj.AdjMat(obj.SourceInd(i),obj.TargetInd(i))=obj.Value{i};
                end
            end
            if isempty(obj.NodeList)
                obj.NodeList=compose('node%d',1:size(obj.AdjMat,1));
            end;obj.BN=length(obj.NodeList);help SSankey
            obj.BoolMat=abs(obj.AdjMat)>0;
            if any(any(obj.BoolMat+obj.BoolMat.'==2))
                warning('Currently, bidirectional flow sankey diagram plotting is not supported.')
            end
            obj.VN=sum(sum(obj.BoolMat));
            % 计算每个对象位于的层、每层方块长度、每个方块位置 ----------------
            if isempty(obj.Layer)
                if strcmp(obj.LayerOrder,'normal')
                    obj.Layer=zeros(obj.BN,1);
                    obj.Layer(sum(obj.BoolMat,1)==0)=1;
                    startMat=diag(obj.Layer);
                    for i=1:(obj.BN-1)
                        tLayer=(sum(startMat*obj.BoolMat^i,1)>0).*(i+1);
                        obj.Layer=max([obj.Layer,tLayer'],[],2);
                    end
                else
                    obj.Layer=zeros(obj.BN,1);
                    obj.Layer(sum(obj.BoolMat,2)==0)=-1;
                    startMat=diag(obj.Layer);
                    for i=1:(obj.BN-1)
                        tLayer=(sum(startMat*(obj.BoolMat.')^i,1)<0).*(-i-1);
                        obj.Layer=min([obj.Layer,tLayer'],[],2);
                    end
                    obj.Layer=obj.Layer-min(obj.Layer)+1;
                end
            end
            obj.Layer=obj.Layer(:);
            obj.LN=max(obj.Layer);
            obj.TotalLen=max([sum(obj.AdjMat,1).',sum(obj.AdjMat,2)],[],2);
            obj.SepLen=max(obj.TotalLen).*obj.Sep;
            obj.LayerPos=zeros(obj.BN,4);
            for i=1:obj.LN
                tBlockInd=find(obj.Layer==i);
                tBlockLen=[0;cumsum(obj.TotalLen(tBlockInd))];
                tY1=tBlockLen(1:end-1)+(0:length(tBlockInd)-1).'.*obj.SepLen(min(i,length(obj.Sep)));
                tY2=tBlockLen(2:end)+(0:length(tBlockInd)-1).'.*obj.SepLen(min(i,length(obj.Sep)));
                obj.LayerPos(tBlockInd,3)=tY1;
                obj.LayerPos(tBlockInd,4)=tY2;
                % for j=1:length(tY2)
                %     plot([i,i],[tY1(j),tY2(j)],'LineWidth',2)
                % end
            end
            obj.LayerPos(:,1)=obj.Layer;
            obj.LayerPos(:,2)=obj.Layer+obj.BlockScale;
            % 根据对齐方式调整Y坐标 -----------------------------------------
            tMinY=min(obj.LayerPos(:,3));
            tMaxY=max(obj.LayerPos(:,4));
            for i=1:obj.LN
                tBlockInd=find(obj.Layer==i);
                tBlockPos3=obj.LayerPos(tBlockInd,3);
                tBlockPos4=obj.LayerPos(tBlockInd,4);
                switch obj.Align
                    case 'up'
                    case 'down'
                        obj.LayerPos(tBlockInd,3)=obj.LayerPos(tBlockInd,3)+tMaxY-max(tBlockPos4);
                        obj.LayerPos(tBlockInd,4)=obj.LayerPos(tBlockInd,4)+tMaxY-max(tBlockPos4);
                    case 'center'
                        obj.LayerPos(tBlockInd,3)=obj.LayerPos(tBlockInd,3)+...
                            min(tBlockPos3)/2-max(tBlockPos4)/2+tMinY/2-tMaxY/2;
                        obj.LayerPos(tBlockInd,4)=obj.LayerPos(tBlockInd,4)+...
                            min(tBlockPos3)/2-max(tBlockPos4)/2+tMinY/2-tMaxY/2;
                end
            end
            % 绘制连接 -----------------------------------------------------
            if isempty(obj.SourceInd)
                [obj.SourceInd,obj.TargetInd]=find(obj.AdjMat~=0);
            end
            for i=1:obj.VN
                tSource=obj.SourceInd(i);
                tTarget=obj.TargetInd(i);
                tS1=sum(obj.AdjMat(tSource,1:(tTarget-1)))+obj.LayerPos(tSource,3);
                tS2=sum(obj.AdjMat(tSource,1:tTarget))+obj.LayerPos(tSource,3);
                tT1=sum(obj.AdjMat(1:(tSource-1),tTarget))+obj.LayerPos(tTarget,3);
                tT2=sum(obj.AdjMat(1:tSource,tTarget))+obj.LayerPos(tTarget,3);
                if isempty(tS1),tS1=0;end
                if isempty(tT1),tT1=0;end
                tX=[obj.LayerPos(tSource,1),obj.LayerPos(tSource,2),obj.LayerPos(tTarget,1),obj.LayerPos(tTarget,2)];
                if abs(tX(1)-tX(3))<eps
                    warning('Currently, flow between the same layer is not supported.')
                    break
                end
                qX=linspace(obj.LayerPos(tSource,1),obj.LayerPos(tTarget,2),200);qT=linspace(0,1,50);
                qY1=interp1(tX,[tS1,tS1,tT1,tT1],qX,'pchip');
                qY2=interp1(tX,[tS2,tS2,tT2,tT2],qX,'pchip');
                XX=repmat(qX,[50,1]);YY=qY1.*(qT'.*0+1)+(qY2-qY1).*(qT');
                MeshC=ones(50,200,3);
                switch obj.RenderingMethod
                    case 'left'
                        MeshC(:,:,1)=MeshC(:,:,1).*obj.ColorList(tSource,1);
                        MeshC(:,:,2)=MeshC(:,:,2).*obj.ColorList(tSource,2);
                        MeshC(:,:,3)=MeshC(:,:,3).*obj.ColorList(tSource,3);
                    case 'right'
                        MeshC(:,:,1)=MeshC(:,:,1).*obj.ColorList(tTarget,1);
                        MeshC(:,:,2)=MeshC(:,:,2).*obj.ColorList(tTarget,2);
                        MeshC(:,:,3)=MeshC(:,:,3).*obj.ColorList(tTarget,3);
                    case 'interp'
                        MeshC(:,:,1)=repmat(linspace(obj.ColorList(tSource,1),obj.ColorList(tTarget,1),200),[50,1]);
                        MeshC(:,:,2)=repmat(linspace(obj.ColorList(tSource,2),obj.ColorList(tTarget,2),200),[50,1]);
                        MeshC(:,:,3)=repmat(linspace(obj.ColorList(tSource,3),obj.ColorList(tTarget,3),200),[50,1]);
                    case 'map'
                        MeshC=MeshC(:,:,1).*obj.Value{i};
                    case 'simple'
                        MeshC(:,:,1)=MeshC(:,:,1).*.6;
                        MeshC(:,:,2)=MeshC(:,:,2).*.6;
                        MeshC(:,:,3)=MeshC(:,:,3).*.6;
                end
                obj.LinkHdl(i)=surf(obj.ax,XX,YY,XX.*0,'EdgeColor','none','FaceAlpha',.3,'CData',MeshC);
            end
            % 绘制方块 -----------------------------------------------------
            for i=1:obj.BN
                obj.BlockHdl(i)=fill(obj.ax,obj.LayerPos(i,[1,2,2,1]),...
                    obj.LayerPos(i,[3,3,4,4]),obj.ColorList(i,:),'EdgeColor','none');
            end
            % 绘制文本 -----------------------------------------------------
            for i=1:obj.BN
                switch obj.LabelLocation
                    case 'right'
                        obj.LabelHdl(i)=text(obj.ax,obj.LayerPos(i,2),mean(obj.LayerPos(i,[3,4])),...
                            [' ',obj.NodeList{i}],'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','left');
                    case 'left'
                        obj.LabelHdl(i)=text(obj.ax,obj.LayerPos(i,1),mean(obj.LayerPos(i,[3,4])),...
                            [obj.NodeList{i},' '],'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','right');
                    case 'top'
                        obj.LabelHdl(i)=text(obj.ax,mean(obj.LayerPos(i,[1,2])),obj.LayerPos(i,3),...
                            obj.NodeList{i},'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','center','VerticalAlignment','bottom');
                    case 'center'
                        obj.LabelHdl(i)=text(obj.ax,mean(obj.LayerPos(i,[1,2])),mean(obj.LayerPos(i,[3,4])),...
                            obj.NodeList{i},'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','center');
                    case 'bottom'
                        obj.LabelHdl(i)=text(obj.ax,mean(obj.LayerPos(i,[1,2])),obj.LayerPos(i,4),...
                            obj.NodeList{i},'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','center','VerticalAlignment','top');
                end
            end
            % -------------------------------------------------------------
            axis tight;
        end
% =========================================================================
        function setBlock(obj,n,varargin)
            set(obj.BlockHdl(n),varargin{:})
        end
        function setLink(obj,n,varargin)
            set(obj.LinkHdl(n),varargin{:})
        end
        function setLabel(obj,n,varargin)
            set(obj.LabelHdl(n),varargin{:})
        end
        function moveBlockX(obj,n,dx)
            obj.LayerPos(n,[1,2])=obj.LayerPos(n,[1,2])+dx;
            set(obj.BlockHdl(n),'XData',obj.LayerPos(n,[1,2,2,1]));
            switch obj.LabelLocation
                case 'right',set(obj.LabelHdl(n),'Position',[obj.LayerPos(n,2),mean(obj.LayerPos(n,[3,4]))]);
                case 'left',set(obj.LabelHdl(n),'Position',[obj.LayerPos(n,1),mean(obj.LayerPos(n,[3,4]))]);
                case 'top',set(obj.LabelHdl(n),'Position',[mean(obj.LayerPos(n,[1,2])),obj.LayerPos(n,3)]);
                case 'center',set(obj.LabelHdl(n),'Position',[mean(obj.LayerPos(n,[1,2])),mean(obj.LayerPos(n,[3,4]))]);
                case 'bottom',set(obj.LabelHdl(n),'Position',[mean(obj.LayerPos(n,[1,2])),obj.LayerPos(n,4)]);
            end
            for i=1:obj.VN
                tSource=obj.SourceInd(i);
                tTarget=obj.TargetInd(i);
                if tSource==n||tTarget==n
                    tS1=sum(obj.AdjMat(tSource,1:(tTarget-1)))+obj.LayerPos(tSource,3);
                    tS2=sum(obj.AdjMat(tSource,1:tTarget))+obj.LayerPos(tSource,3);
                    tT1=sum(obj.AdjMat(1:(tSource-1),tTarget))+obj.LayerPos(tTarget,3);
                    tT2=sum(obj.AdjMat(1:tSource,tTarget))+obj.LayerPos(tTarget,3);
                    if isempty(tS1),tS1=0;end
                    if isempty(tT1),tT1=0;end
                    tX=[obj.LayerPos(tSource,1),obj.LayerPos(tSource,2),obj.LayerPos(tTarget,1),obj.LayerPos(tTarget,2)];
                    qX=linspace(obj.LayerPos(tSource,1),obj.LayerPos(tTarget,2),200);qT=linspace(0,1,50);
                    qY1=interp1(tX,[tS1,tS1,tT1,tT1],qX,'pchip');
                    qY2=interp1(tX,[tS2,tS2,tT2,tT2],qX,'pchip');
                    YY=qY1.*(qT'.*0+1)+(qY2-qY1).*(qT');
                    set(obj.LinkHdl(i),'YData',YY,'XData',qX);
                end
            end
        end
        function moveBlockY(obj,n,dy)
            obj.LayerPos(n,[3,4])=obj.LayerPos(n,[3,4])-dy;
            set(obj.BlockHdl(n),'YData',obj.LayerPos(n,[3,3,4,4]));
            switch obj.LabelLocation
                case 'right',set(obj.LabelHdl(n),'Position',[obj.LayerPos(n,2),mean(obj.LayerPos(n,[3,4]))]);
                case 'left',set(obj.LabelHdl(n),'Position',[obj.LayerPos(n,1),mean(obj.LayerPos(n,[3,4]))]);
                case 'top',set(obj.LabelHdl(n),'Position',[mean(obj.LayerPos(n,[1,2])),obj.LayerPos(n,3)]);
                case 'center',set(obj.LabelHdl(n),'Position',[mean(obj.LayerPos(n,[1,2])),mean(obj.LayerPos(n,[3,4]))]);
                case 'bottom',set(obj.LabelHdl(n),'Position',[mean(obj.LayerPos(n,[1,2])),obj.LayerPos(n,4)]);
            end
            for i=1:obj.VN
                tSource=obj.SourceInd(i);
                tTarget=obj.TargetInd(i);
                if tSource==n||tTarget==n
                    tS1=sum(obj.AdjMat(tSource,1:(tTarget-1)))+obj.LayerPos(tSource,3);
                    tS2=sum(obj.AdjMat(tSource,1:tTarget))+obj.LayerPos(tSource,3);
                    tT1=sum(obj.AdjMat(1:(tSource-1),tTarget))+obj.LayerPos(tTarget,3);
                    tT2=sum(obj.AdjMat(1:tSource,tTarget))+obj.LayerPos(tTarget,3);
                    if isempty(tS1),tS1=0;end
                    if isempty(tT1),tT1=0;end
                    tX=[obj.LayerPos(tSource,1),obj.LayerPos(tSource,2),obj.LayerPos(tTarget,1),obj.LayerPos(tTarget,2)];
                    qX=linspace(obj.LayerPos(tSource,1),obj.LayerPos(tTarget,2),200);qT=linspace(0,1,50);
                    qY1=interp1(tX,[tS1,tS1,tT1,tT1],qX,'pchip');
                    qY2=interp1(tX,[tS2,tS2,tT2,tT2],qX,'pchip');
                    YY=qY1.*(qT'.*0+1)+(qY2-qY1).*(qT');
                    set(obj.LinkHdl(i),'YData',YY);
                end
            end
        end
    end
% Copyright (c) 2023, Zhaoxu Liu / slandarer
% =========================================================================
% @author : slandarer
% 公众号  : slandarer随笔
% 知乎    : slandarer
% -------------------------------------------------------------------------
end

以上已经是完整代码,未经允许本代码请勿作商业用途,引用的话可以引用我file exchange上的链接,可使用如下格式:

  • Zhaoxu Liu / slandarer (2023). sankey plot (https://www.mathworks.com/matlabcentral/fileexchange/128679-sankey-plot), MATLAB Central File Exchange. 检索来源 2023/4/28.

若转载请保留以上file exchange链接及本文链接!!!!!

该工具可通过上述fileexchange链接获取,或者通过以下gitee仓库下载:

  • https://gitee.com/slandarer/matlab-sankey-diagram


slandarer随笔
slandarer个人公众号,目前主要更新MATLAB相关内容。
 最新文章