桑基图绘制的简易操作

本文介绍了桑基图的概念,它是一种用于展示流量分布的图表,常用于能源、材料成分等领域。并详细阐述了如何在Excel、MATLAB和Python中创建桑基图。在Excel中,通过E2D3加载项实现;在MATLAB中,提供了一段代码示例;在Python中,利用Sankey函数进行绘制,并给出了注意事项。
摘要由CSDN通过智能技术生成

1、什么是桑吉图?

桑基图 (Sankey Diagram),是一种特定类型的流图,用于描述一组值到另一组值的流向。上图为1869年,查尔斯米纳德(Charles Minard)绘制的1812年拿破仑征俄图(Map of Napolean's Russian Campaign of 1812),这是一个在地图上覆盖桑基图的流程图。1898年爱尔兰人Matthew Henry Phineas Riall Sankey 在土木工程师学会会报纪要的一篇关于蒸汽机能源效率的文章中首次推出了第一个能量流动图,此后便以其名字命名为 Sankey 图,中文音译为桑基图。

图中延伸的分支的宽度对应数据流量的大小。桑基图的特点如下:

  • 起始流量和结束流量相同,所有主支宽度的总和与所有分出去的分支宽度总和相等,保持能量的平衡;
  • 在内部,不同的线条代表了不同的流量分流情况,它的宽度成比例地显示此分支占有的流量;
  • 节点不同的宽度代表了特定状态下的流量大小。

桑基图通常应用于能源、材料成分、金融等数据的可视化分析。

英文名:Sankey Diagram

2、简要画法(这里汇总了三种最简单的桑吉图画法,分别使用excel、matlab、python)

2.1.EXCEL画桑基图

①调出开发工具文件→选项→自定义功能区→勾选【开发工具】→确定

②添加E2D3:加载项开发工具→加载项→应用商店→搜索框中输入【E2D3】→搜索→添加→继续

③生成图表点击左边的图标,可选择不同的图表类别

点击左上角的Recommend,把鼠标移至桑基图上方,然后点击弹出页面中的Visualize

自动生成桑基图及数据源

④套用图表虽然生成了我们想要的图表样式,但数据源不正确,此时可通过以下方式修改

删除数据直接选中多余数据,然后删除,此时图表会自动更新

直接替换数据源中的原始数据,图表也会自动引用更新后的数据

直接新增数据,图表自动更新(如果新增数据后,发现图表未更新,需要手动更改数据区域。先用鼠标选取数据区域,然后点击图表左上角的Reset data area,图表即可重新引用选中的数据区域)

 2.2.MATLAB画桑基图

代码如下

function sankeyHdl=sankey2(varargin)
if strcmp(get(varargin{1},'type'),'axes' )
    ax=varargin{1};
else
    ax=gca;
end
hold(ax,'on')
 
%若未设置,则图像的初始值==================================================
prop.Color=[0,0,0];
prop.FontSize=10;
prop.FontColor=[0,0,0];
prop.Xlim=[0,1];
prop.YLim=[0,1];
prop.PieceWidth=0.15;
prop.List=[];
prop.Margin=0.05;
prop.Sep=1/8;
prop.EdgeColor=[0 0 0];
 
%从可变长度变量中提取有用信息==============================================
for i=1:length(varargin)
    tempVar=varargin{i};
    if ischar(tempVar)&&length(tempVar)>1
        prop.(tempVar)=varargin{i+1};
    end
end
 
%流量矩阵构建==============================================================
nameList=unique([prop.List(:,1);prop.List(:,3)],'stable');
blockMat=zeros(length(nameList));
for i=1:size(prop.List,1)
    s=strcmp(nameList,prop.List(i,1));
    e=strcmp(nameList,prop.List(i,3));
    blockMat(s,e)=prop.List{i,2};
end
totalFlow=max([sum(blockMat,1);sum(blockMat,2)'],[],1);
 
 
%划分桑基图层次============================================================
List_L=prop.List(:,1);
List_R=prop.List(:,3);
prop.layer=[];layerRoot=[];n=1;
for i=length(List_R):-1:1
    if ~any(strcmp(List_L,List_R{i}))
        layerRoot=[layerRoot;find(strcmp(nameList,List_R{i}))];
    end
end
layerRoot=unique(layerRoot,'stable');
while ~isempty(List_L)
    layer_n=[];
    for i=length(List_L):-1:1
        if ~any(strcmp(List_R,List_L{i}))
            layer_n=[layer_n;find(strcmp(nameList,List_L{i}))];
            List_L(i)=[];
            List_R(i)=[];
        end
    end
    layer_n=unique(layer_n,'stable');
    prop.layer(length(layer_n),n)=0;
    prop.layer(1:length(layer_n),n)=layer_n;
    n=n+1;
end
prop.layer(length(layerRoot),n)=0;
prop.layer(1:length(layerRoot),n)=layerRoot;
prop.layerNum=size(prop.layer,2);
 
 
 
 
%绘制方块==================================================================
baseBlockX=[0,1,1,0];
baseBlockY=[0,0,1,1];
bnul=max(sum(prop.layer~=0,1));   %block number upper limit
baseLenY=(diff(prop.YLim)-2*prop.Margin)/(bnul+(bnul-1)*prop.Sep)*bnul;
baseLenX=(diff(prop.XLim)-2*prop.Margin)/(prop.layerNum-0.5);
colorIndex=1;
for i=1:prop.layerNum
    tempY=prop.Margin;
    elemSet=prop.layer(prop.layer(:,i)~=0,i);
    flowSet=totalFlow(elemSet);
    offSet=(diff(prop.YLim)-2*prop.Margin-baseLenY/length(elemSet)*((length(elemSet)+(length(elemSet)-1)*prop.Sep)))/2;
    for j=1:length(elemSet)
        tempLenY=baseLenY./sum(flowSet).*flowSet(j);
         
        sankeyHdl.block(prop.layer(j,i))=...
        fill(baseBlockX.*prop.PieceWidth+prop.Margin+(i-1)*baseLenX,...
            baseBlockY.*tempLenY+tempY+offSet,...
            prop.Color(colorIndex,:),'EdgeColor',prop.EdgeColor);
         
        tempY=tempY+tempLenY+baseLenY/length(elemSet)*prop.Sep;
        colorIndex=mod(colorIndex,size(prop.Color,1))+1;
    end
end
 
%绘制连接
layerList=prop.layer(:);
for i=1:length(nameList)
    for j=i:length(nameList)
        if blockMat(i,j)~=0
            Hdl_L=sankeyHdl.block(i);
            Hdl_R=sankeyHdl.block(j);
            list_L=find(blockMat(i,:)~=0);
            list_R=find(blockMat(:,j)~=0);
            [~,pl,~]=intersect(layerList,list_L(:));
            [~,pr,~]=intersect(layerList,list_R(:));
            list_L=layerList(sort(pl));
            list_R=layerList(sort(pr));
            flow_L=blockMat(i,list_L);
            flow_R=blockMat(list_R,j);
            XData_L=Hdl_L.XData;YData_L=Hdl_L.YData;
            XData_R=Hdl_R.XData;YData_R=Hdl_R.YData;
            xx=[XData_L(1:2);XData_R(1:2)]';
            k_L=find(list_L==j);
            k_R=find(list_R==i);
            yy=[YData_L(1:2)+(YData_L(3:4)-YData_L(1:2))./sum(flow_L).*sum(flow_L(1:k_L-1));
                YData_R(1:2)+(YData_R(3:4)-YData_R(1:2))./sum(flow_R).*sum(flow_R(1:k_R-1))]';
            xxq=XData_L(2):0.01:XData_R(1);
            yyq=interp1(xx,yy,xxq,'pchip');
            tempColor=Hdl_L.FaceColor;
            width=(YData_R(3)-YData_R(1))./sum(flow_R).*flow_R(k_R);
             sankeyHdl.connect(i,k_L)=...
            fill([xxq,xxq(end:-1:1)],[yyq,yyq(end:-1:1)+width],tempColor,'EdgeColor','none','FaceAlpha',0.3);
        end    
    end
end
 
%绘制文本
for i=1:prop.layerNum
    tempY=prop.Margin;
    elemSet=prop.layer(prop.layer(:,i)~=0,i);
    flowSet=totalFlow(elemSet);
    offSet=(diff(prop.YLim)-2*prop.Margin-baseLenY/length(elemSet)*((length(elemSet)+(length(elemSet)-1)*prop.Sep)))/2;
    for j=1:length(elemSet)
        tempLenY=baseLenY./sum(flowSet).*flowSet(j);
         
        sankeyHdl.txt(prop.layer(j,i))=...
        text(prop.PieceWidth+prop.Margin+(i-1)*baseLenX,tempLenY/2+tempY+offSet,[' ',nameList{elemSet(j)}],...
            'FontSize',prop.FontSize,'Color',prop.FontColor);
         
        tempY=tempY+tempLenY+baseLenY/length(elemSet)*prop.Sep;
    end
end
sankeyHdl.nameList=nameList';
end

3.3.Python

def get_tu(tablename,df):
    nodes1,links = get_data(df)
    sankey = (
        Sankey(init_opts=opts.InitOpts(width="2000px", height="800px"))
        .add(
            tablename,
            nodes1,
            links,
            pos_top="10%",
            node_width = 30,  #每个桑基图矩形的宽度
            node_gap= 12,  #桑基图中每一列任意两个矩形节点之间的间隔。
            is_draggable = True,
            layout_iterations = 5,

            # focus_node_adjacency=True,
            itemstyle_opts=opts.ItemStyleOpts(border_width=2, border_color="#aaa"),
            linestyle_opt=opts.LineStyleOpts(opacity=0.8, curve=0.5, color='source'),
            label_opts=opts.LabelOpts(position='right'),
        )
        .set_global_opts(title_opts=opts.TitleOpts(title="XXXX"))
    )
    return sankey

file ='XXX.xlsx'
df= pd.read_excel(file,sheet_name='XXXX') ##获得数据
sk1 = get_tu('XXX',df)  ##调用函数画图
sk1.render("XXX.html") #输出网址
print('XXX')

 需要注意的是:1、nodes中的"name"不要重复命名;2、links中source和target对应的值一定要在nodes的name对应的值中;3、links中source和target对应的值不能相同

  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一袭青衫为故人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值