转载请注明本文链接
及文章作者:slandarer
没错,该工具又双叒叕更新啦,本次更新是该工具一年内的第六次功能增添,目前是4.0.0版本!!
工具介绍
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()
上一版本更新后也支持使用邻接矩阵创建:
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];
% 创建桑基图对象(Create a Sankey diagram object)
SK=SSankey([],[],[],'AdjMat',adjMat);
% 开始绘图(Start drawing)
SK.draw()
更多使用教程请见以下文章:
更新内容
前两个更新建议来自于Joona
:How can I add the values of the streams to be visible in the graph?
1 为链接添加数值标签
通过设置ValueLabelLocation
属性添加标签,默认值为none
即无标签,下面举个设置为left
标签在链接左侧的例子:
% 含跨层级流动(Including cross level flow)
links={'a1','A',1.2;'a2','A',2;'a1','B',.6;'a3','D',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','D',1.5; 'C','BB',2.3; 'C','AA',1.2;
'D','AA',1.4; 'D','BB',1.1};
% 创建桑基图对象(Create a Sankey diagram object)
SK=SSankey(links(:,1),links(:,2),links(:,3));
% 修改节点排列次序(Modify node arrangement order)
SK.NodeList={'a3','a1','a2','b1','b2','b3','c1','c2','c3','D','A','B','C','AA','BB'};
SK.Sep=.1;
% 修改链接文本位置(Set link label location)
% 'none'(default)/'left'/'right'/'center'
SK.ValueLabelLocation='left';
% 开始绘图(Start drawing)
SK.draw()
% 修改节点Y轴位置变化(Modify the position change of node Y direction)
SK.moveBlockY(10,+6);
2 修改数值标签格式和字体
通过设置ValueLabelFormat
修改格式,通过setValueLabel
函数设置字体,例如:
% 含跨层级流动(Including cross level flow)
links={'a1','A',1.2;'a2','A',2;'a1','B',.6;'a3','D',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','D',1.5; 'C','BB',2.3; 'C','AA',1.2;
'D','AA',1.4; 'D','BB',1.1};
% 创建桑基图对象(Create a Sankey diagram object)
SK=SSankey(links(:,1),links(:,2),links(:,3));
% 修改节点排列次序(Modify node arrangement order)
SK.NodeList={'a3','a1','a2','b1','b2','b3','c1','c2','c3','D','A','B','C','AA','BB'};
SK.Sep=.1;
% 修改链接文本位置(Set link label location)
% 'none'(default)/'left'/'right'/'center'
SK.ValueLabelLocation='left';
% 修改链接文本格式(Set link label location)
SK.ValueLabelFormat=@(X) ['V:',num2str(X)];
% 开始绘图(Start drawing)
SK.draw()
% 修改节点Y轴位置变化(Modify the position change of node Y direction)
SK.moveBlockY(10,+6);
% 修改链接字体(Set link label font)
for i=1:19
SK.setValueLabel(i,'Color',[0,0,.8])
end
后两个更新意见来源于BENSON IGESA
:Supposing you have extra nodes to add that are linked how can they be added to the alrealdy defined links
3 新增节点
通过addNode(name,layer)
函数新增节点,该函数具有节点名称和节点位于第几层两个参数:
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);
% add node to sankey diagram
% try : obj.addNode(name,layer)
SK.addNode('Add1',3)
SK.addNode('Add2')
SK.addNode()
% 开始绘图(Start drawing)
SK.draw()
SK.addNode('Add3',5)
4 新增链接
通过addLink(source,target,value)
函数新增链接:
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);
SK.addNode('Add1',3)
SK.addNode('Add2')
SK.addNode('Add2',5)
% add link to sankey diagram
% try : obj.addLink(source,target,value)
SK.addLink(5,11,3)
% 开始绘图(Start drawing)
SK.draw()
SK.addLink(7,12,3)
SK.addLink(11,12,3)
SK.addLink(10,13,3)
SK.addLink(12,13,6)
完整代码
classdef SSankey < handle
% Copyright (c) 2023-2024, 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)
% =========================================================================
% # update 3.1.0(2024-05-15)
% see sankeyDemo12.m sankeyDemo13.m
% + 为链接添加显示数值的文本(Display value labels for each link)
% try : SK.ValueLabelLocation='left';
% =========================================================================
% # update 4.0.0(2024-05-17)
% see sankeyDemo14.m sankeyDemo15.m
% + 增添节点及链接(Add node and link)
% try : obj.addNode(name,layer)
% try : obj.addLink(source,target,value)
properties
Source;Target;Value;
SourceInd;TargetInd;
Layer;LayerPos;MovePos;LayerOrder='normal';
AdjMat;BoolMat;
RenderingMethod='interp' % 'left'/'right'/'interp'/'map'/'simple'
LabelLocation='left' % 'left'/'right'/'top'/'center'/'bottom'
ValueLabelLocation='none' % 'left'/'right'/'center'/'none'
ValueLabelFormat=@(X)num2str(X);
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;ValueLabelHdl;ax;Parent;
BN;LN;VN;TotalLen;SepLen;
arginList={'RenderingMethod','LabelLocation','ValueLabelLocation','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.MovePos=zeros(obj.BN,4);
% obj.VN=length(obj.Value);
% 坐标区域基础设置 ---------------------------------------------
obj.ax.YDir='reverse';
obj.ax.XColor='none';
obj.ax.YColor='none';
end
% 绘图函数 =================================================================
function draw(obj)
% 生成整体邻接矩阵 ---------------------------------------------
obj.getAdjMat()
% 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)
obj.getLayer()
end
obj.getLayerPos()
% 绘制连接 -----------------------------------------------------
for i=1:obj.VN
obj.drawLink(i)
end
% 绘制方块 -----------------------------------------------------
for i=1:obj.BN
drawNode(obj,i)
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 setValueLabel(obj,n,varargin)
set(obj.ValueLabelHdl(n),varargin{:})
end
% =========================================================================
function addLink(obj,S,T,V)
obj.getAdjMat()
if isempty(obj.BlockHdl)
obj.AdjMat(S,T)=obj.AdjMat(S,T)+abs(V);
else
if obj.AdjMat(S,T)==0
obj.AdjMat(S,T)=obj.AdjMat(S,T)+abs(V);
obj.getLayerPos()
[M,N]=find(obj.AdjMat~=0);
obj.drawLink(find(M==S&N==T))
else
obj.AdjMat(S,T)=obj.AdjMat(S,T)+abs(V);
obj.getLayerPos()
end
% disp(obj.AdjMat)
obj.refresh()
end
end
function addNode(obj,name,layer)
obj.getAdjMat()
obj.AdjMat(end+1,:)=0;obj.AdjMat(:,end+1)=0;
if nargin<2
obj.NodeList{end+1}=compose('node%d',size(obj.AdjMat,1));
else
obj.NodeList{end+1}=name;
end
obj.BN=length(obj.NodeList);
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)
obj.getLayer()
if nargin<3,obj.Layer(end)=max(obj.Layer);else,obj.Layer(end)=layer;end
else
if nargin<3,obj.Layer(end+1)=max(obj.Layer);else,obj.Layer(end+1)=layer;end
end
obj.ColorList(end+1,:)=rand(1,3).*.7;
obj.MovePos(end+1,:)=0;
% -------------------------------------------------------------
if isempty(obj.BlockHdl)
else
obj.getLayerPos()
obj.drawNode(length(obj.NodeList))
N=find(obj.Layer==obj.Layer(end));
for n=1:length(N)
obj.moveBlock(N(n))
end
end
end
% =========================================================================
function refresh(obj)
tLayerPos=obj.MovePos+obj.LayerPos;
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));
for n=1:obj.BN
set(obj.BlockHdl(n),'XData',tLayerPos(n,[1,2,2,1]));
set(obj.BlockHdl(n),'YData',tLayerPos(n,[3,3,4,4]));
switch obj.LabelLocation
case 'right',set(obj.LabelHdl(n),'Position',[tLayerPos(n,2),mean(tLayerPos(n,[3,4]))]);
case 'left',set(obj.LabelHdl(n),'Position',[tLayerPos(n,1),mean(tLayerPos(n,[3,4]))]);
case 'top',set(obj.LabelHdl(n),'Position',[mean(tLayerPos(n,[1,2])),tLayerPos(n,3)]);
case 'center',set(obj.LabelHdl(n),'Position',[mean(tLayerPos(n,[1,2])),mean(tLayerPos(n,[3,4]))]);
case 'bottom',set(obj.LabelHdl(n),'Position',[mean(tLayerPos(n,[1,2])),tLayerPos(n,4)]);
end
end
[obj.SourceInd,obj.TargetInd]=find(obj.AdjMat~=0);
for n=1:obj.VN
tSource=obj.SourceInd(n);
tTarget=obj.TargetInd(n);
tS1=sum(obj.AdjMat(tSource,1:(tTarget-1)))+tLayerPos(tSource,3);
tS2=sum(obj.AdjMat(tSource,1:tTarget))+tLayerPos(tSource,3);
tT1=sum(obj.AdjMat(1:(tSource-1),tTarget))+tLayerPos(tTarget,3);
tT2=sum(obj.AdjMat(1:tSource,tTarget))+tLayerPos(tTarget,3);
if isempty(tS1),tS1=0;end
if isempty(tT1),tT1=0;end
tX=[tLayerPos(tSource,1),tLayerPos(tSource,2),tLayerPos(tTarget,1),tLayerPos(tTarget,2)];
qX=linspace(tLayerPos(tSource,1),tLayerPos(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(n),'YData',YY,'XData',qX);
set(obj.ValueLabelHdl(n),'String',[' ',obj.ValueLabelFormat(obj.AdjMat(obj.SourceInd(n),obj.TargetInd(n)))])
switch obj.ValueLabelLocation
case 'left'
set(obj.ValueLabelHdl(n),'Position',[tLayerPos(tSource,2),tS1/2+tS2/2]);
case 'right'
set(obj.ValueLabelHdl(n),'Position',[tLayerPos(tTarget,1),tT1/2+tT2/2]);
case 'center'
set(obj.ValueLabelHdl(n),'Position',[tLayerPos(tSource,2)/2+tLayerPos(tTarget,1)/2,tS1/4+tS2/4+tT1/4+tT2/4]);
case 'none'
set(obj.ValueLabelHdl(n),'Position',[tLayerPos(tSource,2),tS1/2+tS2/2]);
end
end
end
function drawLink(obj,n)
% 绘制连接 -----------------------------------------------------
[obj.SourceInd,obj.TargetInd]=find(obj.AdjMat~=0);
tSource=obj.SourceInd(n);
tTarget=obj.TargetInd(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)];
if abs(tX(1)-tX(3))<eps
warning('Currently, flow between the same layer is not supported.')
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{n};
case 'simple'
MeshC(:,:,1)=MeshC(:,:,1).*.6;
MeshC(:,:,2)=MeshC(:,:,2).*.6;
MeshC(:,:,3)=MeshC(:,:,3).*.6;
end
tLinkHdl=surf(obj.ax,XX,YY,XX.*0,'EdgeColor','none','FaceAlpha',.3,'CData',MeshC);
obj.LinkHdl=[obj.LinkHdl(1:n-1),tLinkHdl,obj.LinkHdl(n:end)];
switch obj.ValueLabelLocation
case 'left'
tValueLabelHdl=text(obj.LayerPos(tSource,2),tS1/2+tS2/2,[' ',obj.ValueLabelFormat(obj.AdjMat(obj.SourceInd(n),obj.TargetInd(n)))],...
'FontSize',12,'FontName','Times New Roman','HorizontalAlignment','left');
case 'right'
tValueLabelHdl=text(obj.LayerPos(tTarget,1),tT1/2+tT2/2,[obj.ValueLabelFormat(obj.AdjMat(obj.SourceInd(n),obj.TargetInd(n))),' '],...
'FontSize',12,'FontName','Times New Roman','HorizontalAlignment','right');
case 'center'
tValueLabelHdl=text(obj.LayerPos(tSource,2)/2+obj.LayerPos(tTarget,1)/2,tS1/4+tS2/4+tT1/4+tT2/4,obj.ValueLabelFormat(obj.AdjMat(obj.SourceInd(n),obj.TargetInd(n))),...
'FontSize',12,'FontName','Times New Roman','HorizontalAlignment','center');
case 'none'
tValueLabelHdl=text(obj.LayerPos(tSource,2),tS1/2+tS2/2,[' ',obj.ValueLabelFormat(obj.AdjMat(obj.SourceInd(n),obj.TargetInd(n)))],...
'FontSize',12,'FontName','Times New Roman','HorizontalAlignment','left', 'Visible','off');
end
obj.ValueLabelHdl=[obj.ValueLabelHdl(1:n-1),tValueLabelHdl,obj.ValueLabelHdl(n:end)];
end
function drawNode(obj,n)
% 绘制方块 -----------------------------------------------------
obj.BlockHdl(n)=fill(obj.ax,obj.LayerPos(n,[1,2,2,1]),...
obj.LayerPos(n,[3,3,4,4]),obj.ColorList(n,:),'EdgeColor','none');
% 绘制文本 -----------------------------------------------------
switch obj.LabelLocation
case 'right'
obj.LabelHdl(n)=text(obj.ax,obj.LayerPos(n,2),mean(obj.LayerPos(n,[3,4])),...
[' ',obj.NodeList{n}],'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','left');
case 'left'
obj.LabelHdl(n)=text(obj.ax,obj.LayerPos(n,1),mean(obj.LayerPos(n,[3,4])),...
[obj.NodeList{n},' '],'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','right');
case 'top'
obj.LabelHdl(n)=text(obj.ax,mean(obj.LayerPos(n,[1,2])),obj.LayerPos(n,3),...
obj.NodeList{n},'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','center','VerticalAlignment','bottom');
case 'center'
obj.LabelHdl(n)=text(obj.ax,mean(obj.LayerPos(n,[1,2])),mean(obj.LayerPos(n,[3,4])),...
obj.NodeList{n},'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','center');
case 'bottom'
obj.LabelHdl(n)=text(obj.ax,mean(obj.LayerPos(n,[1,2])),obj.LayerPos(n,4),...
obj.NodeList{n},'FontSize',15,'FontName','Times New Roman','HorizontalAlignment','center','VerticalAlignment','top');
end
end
% =========================================================================
function getAdjMat(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
end
function getLayer(obj)
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
function getLayerPos(obj)
obj.Layer=obj.Layer(:);
obj.LN=max(obj.Layer);
obj.TotalLen=max([sum(obj.AdjMat,1).',sum(obj.AdjMat,2)],[],2);
obj.TotalLen(obj.TotalLen==0)=mean(obj.TotalLen)/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;
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
end
% =========================================================================
function moveBlock(obj,n)
tLayerPos=obj.MovePos+obj.LayerPos;
set(obj.BlockHdl(n),'XData',tLayerPos(n,[1,2,2,1]));
set(obj.BlockHdl(n),'YData',tLayerPos(n,[3,3,4,4]));
switch obj.LabelLocation
case 'right',set(obj.LabelHdl(n),'Position',[tLayerPos(n,2),mean(tLayerPos(n,[3,4]))]);
case 'left',set(obj.LabelHdl(n),'Position',[tLayerPos(n,1),mean(tLayerPos(n,[3,4]))]);
case 'top',set(obj.LabelHdl(n),'Position',[mean(tLayerPos(n,[1,2])),tLayerPos(n,3)]);
case 'center',set(obj.LabelHdl(n),'Position',[mean(tLayerPos(n,[1,2])),mean(tLayerPos(n,[3,4]))]);
case 'bottom',set(obj.LabelHdl(n),'Position',[mean(tLayerPos(n,[1,2])),tLayerPos(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)))+tLayerPos(tSource,3);
tS2=sum(obj.AdjMat(tSource,1:tTarget))+tLayerPos(tSource,3);
tT1=sum(obj.AdjMat(1:(tSource-1),tTarget))+tLayerPos(tTarget,3);
tT2=sum(obj.AdjMat(1:tSource,tTarget))+tLayerPos(tTarget,3);
if isempty(tS1),tS1=0;end
if isempty(tT1),tT1=0;end
tX=[tLayerPos(tSource,1),tLayerPos(tSource,2),tLayerPos(tTarget,1),tLayerPos(tTarget,2)];
qX=linspace(tLayerPos(tSource,1),tLayerPos(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);
switch obj.ValueLabelLocation
case 'left'
set(obj.ValueLabelHdl(i),'Position',[tLayerPos(tSource,2),tS1/2+tS2/2]);
case 'right'
set(obj.ValueLabelHdl(i),'Position',[tLayerPos(tTarget,1),tT1/2+tT2/2]);
case 'center'
set(obj.ValueLabelHdl(i),'Position',[tLayerPos(tSource,2)/2+tLayerPos(tTarget,1)/2,tS1/4+tS2/4+tT1/4+tT2/4]);
case 'none'
set(obj.ValueLabelHdl(i),'Position',[tLayerPos(tSource,2),tS1/2+tS2/2]);
end
end
end
end
function moveBlockX(obj,n,dx)
obj.MovePos(n,[1,2])=obj.MovePos(n,[1,2])+dx;
obj.moveBlock(n)
end
function moveBlockY(obj,n,dy)
obj.MovePos(n,[3,4])=obj.MovePos(n,[3,4])-dy;
obj.moveBlock(n)
end
end
% Copyright (c) 2023-2024, Zhaoxu Liu / slandarer
% =========================================================================
% @author : slandarer
% 公众号 : slandarer随笔
% 知乎 : slandarer
% -------------------------------------------------------------------------
end
完
以上已经是完整代码,未经允许本代码请勿作商业用途,引用的话可以引用我file exchange上的链接,可使用如下格式:
Zhaoxu Liu / slandarer (2024). sankey plot (https://www.mathworks.com/matlabcentral/fileexchange/128679-sankey-plot), MATLAB Central File Exchange. Retrieved May 17, 2024.
若转载请保留以上file exchange链接及本文链接!!!!!
该工具可通过上述fileexchange链接获取,或者通过以下gitee仓库下载:
https://gitee.com/slandarer/matlab-sankey-diagram