• MATLAB | 全网唯一,使用MATLAB绘制精致的环形树状图


    绘制效果

    请叫我平平无奇的绘图天才,这期一次性带来俩工具函数,分别是两层和三层的环形树状图,可以用来展示数据分类和层级:

    理顺层级关系属实理得头秃,代码编写不易点个叭!!

    由于工具函数过长,将被放在最后展示,以下将先展示函数用法。


    双层环形树状图

    基本使用

    处理的数据对象是两列的元胞数组,数组内容物为字符串,像下面这样:

    treeList={'竞赛','数学竞赛';'竞赛','英语竞赛';'竞赛','美赛';'竞赛','数模国赛';'竞赛','网络挑战赛';'竞赛','英语翻译';
        '编程','python';'编程','MATLAB';'编程','C#';'编程','C++';'编程','C';'编程','java';'编程','js';'编程','R';'编程','html';
        '编程','php';'编程','rust';'编程','julia';'编程','perl';'编程','carbon';'编程','lisp';
        '技能','office';'技能','LaTeX';'技能','PS';'技能','PR';'技能','excel';'技能','视频剪辑';'技能','bat';
        '基础知识','代数集合';'基础知识','傅里叶';'基础知识','抽代';'基础知识','数分';'基础知识','高代';'基础知识','解几';
        '基础知识','实变';'基础知识','复变';'基础知识','运筹学';'基础知识','泛函';'基础知识','张量分解';'基础知识','概率论';
        '开发工具','eclipse';'开发工具','git';'开发工具','svn';'开发工具','gitee';'开发工具','jupyter';'开发工具','macos';
        '开发工具','postman';'开发工具','vscode';'开发工具','mdnice';'开发工具','pycharm';'开发工具','vim';'开发工具','svn';
        '爱好','国画';'爱好','乒乓球';'爱好','折纸';'爱好','雕刻';'爱好','篮球';'爱好','足球'
        '爱好','唱';'爱好','跳';'爱好','rap';'爱好','背带裤制作';'爱好','理发'};
    
    CT=circleTree2(treeList);
    CT=CT.draw();
    

    好,画完结束了,当然线更多会比较好看,这里自己构造一些ABCD类再画个图:

    treeList={[],[]};
    k=1;
    classNameSet={'A','B','C','D','E','F'};
    classNumSet=[20,20,20,25,30,35];
    for i=1:6
        for j=1:classNumSet(i)
            treeList{k,1}=['CLASS ',classNameSet{i}];
            treeList{k,2}=[classNameSet{i},num2str(j)];
            k=k+1;
        end
    end
    
    CT=circleTree2(treeList);
    CT=CT.draw();
    

    修改配色

    使用setColorN设置各个类的颜色,比如将第2个类设为红色:

    CT.setColorN(2,[.8,0,0]);
    

    循环全部设置为灰色:

    for i=1:6
        CT.setColorN(i,[.8,.8,.8]);
    end
    

    修改字体

    使用setLable1setLable1修改两层文本字体,例如:

    CT.setLable1('FontSize',16,'Color',[0,0,.8]);
    CT.setLable2('FontSize',13,'Color',[0,.6,.8]);
    


    三层环形树状图

    基本使用

    与上面双层环形树状图绘制方式一模一样,三层的不太好构造,这里直接从excel读取数据绘制,数据为往年诺贝尔奖获得者及其奖项、国籍

    读取数据并绘制三层环形树状图:

    treeList=readcell('data.xlsx');
    treeList=treeList(:,1:3);
    
    CT=circleTree3(treeList);
    CT=CT.draw();
    

    其他属性设置方式与之前的双层绘制函数完全相同,由于怕文字互相遮挡,故只展示占比大于5%的类,想要隐藏或显示全部类,可以设置文本的Visible属性为on/off

    CT.setLable2('Visible','on');
    

    虽然有点乱,但是可以缩放查看细节:


    完整代码

    大量代码,前方高能!!

    双层

    classdef circleTree2
    % @author : slandarer
    % gzh  : slandarer随笔 
    
        properties
            ax,treeList
            ClassLine,ChildLine
            ClassScatter,MeanScatter
            LabelHdl0,LabelHdl1
        end
    
        methods
            function obj=circleTree2(treeList)
                obj.ax=gca;hold on;
                obj.treeList=treeList;         
            end
    
            function obj=draw(obj)
                % 坐标区域属性设置 ==============================================
                obj.ax=gca;hold on
                obj.ax.XLim=[-1,1];
                obj.ax.YLim=[-1,1];
                obj.ax.XTick=[];
                obj.ax.YTick=[];
                obj.ax.XColor='none';
                obj.ax.YColor='none';
                obj.ax.PlotBoxAspectRatio=[1,1,1];
    
                fig=obj.ax.Parent;
                if max(fig.Position(3:4))<600
                    fig.Position(3:4)=1.8.*fig.Position(3:4);
                    fig.Position(1:2)=fig.Position(1:2)./3;
                end
                % ax.LooseInset=[0,0,0,0];
                % 理顺层级 =====================================================
                classNameList=unique(obj.treeList(:,1));
                childrenList{length(classNameList)}=[];
                classSize(length(classNameList))=0;
                for i=1:length(classNameList)
                    childrenList{i}=find(strcmp(classNameList{i},obj.treeList(:,1)));
                    classSize(i)=length(childrenList{i});
                end
                % 开始绘图 =====================================================
                sepTheta=2/30/length(classSize);
                cumTheta=[0,28/30*cumsum(classSize)./sum(classSize)];
                colorList=[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;
                colorList=[colorList;rand(length(classSize),3)];
                colorList=colorList(4:end,:);
                for i=1:length(classSize)
                    thetaList=linspace(sepTheta*i+cumTheta(i),sepTheta*i+cumTheta(i+1),classSize(i)).*2.*pi;
                    bX1=[];bY1=[];
                    for j=1:classSize(i)
                        oX1=[cos(thetaList(j)),cos(thetaList(j)).*0.9,cos(mean(thetaList)).*0.7,cos(mean(thetaList)).*0.45];
                        oY1=[sin(thetaList(j)),sin(thetaList(j)).*0.9,sin(mean(thetaList)).*0.7,sin(mean(thetaList)).*0.45];
                        bXY1=bezierCurve([oX1',oY1'],200);
                        bX1=[bX1;bXY1(:,1);nan];
                        bY1=[bY1;bXY1(:,2);nan];
                        nameList1=obj.treeList(:,2);
                        nameList1=nameList1(childrenList{i});
                        rotation=thetaList(j)/pi*180;
                        if rotation>90&&rotation<270
                            rotation=rotation+180;
                            obj.LabelHdl1(i,j)=text(cos(thetaList(j)).*1.03,sin(thetaList(j)).*1.03,nameList1{j},'Rotation',rotation,'HorizontalAlignment','right');
                        else
                            obj.LabelHdl1(i,j)=text(cos(thetaList(j)).*1.03,sin(thetaList(j)).*1.03,nameList1{j},'Rotation',rotation);
                        end
                        
                    end
                    meanTheta=mean(thetaList);
                    obj.ChildLine(i)=plot(bX1,bY1,'Color',[colorList(i,:),.4],'LineWidth',1.5);
                    oX0=[cos(meanTheta).*0.46,cos(meanTheta).*0.44,0.05,0];
                    oY0=[sin(meanTheta).*0.46,sin(meanTheta).*0.44,-0.15,0];
                    bXY0=bezierCurve([oX0',oY0'],200);
                    obj.ClassLine(i)=plot(bXY0(:,1),bXY0(:,2),'Color',[colorList(i,:),.8],'LineWidth',1.5);
                    obj.ClassScatter(i)=scatter(cos(meanTheta).*0.45,sin(meanTheta).*0.45,'filled',...
                        'CData',colorList(i,:),'MarkerEdgeColor',[.4,.4,.4],'SizeData',60);
                    rotation=meanTheta/pi*180;
                    if rotation>90&&rotation<270
                        rotation=rotation+180;
                        obj.LabelHdl0(i)=text(cos(meanTheta).*0.48,sin(meanTheta).*0.48,classNameList{i},...
                            'Rotation',rotation,'HorizontalAlignment','right','FontSize',12,'VerticalAlignment','cap');
                    else
                        obj.LabelHdl0(i)=text(cos(meanTheta).*0.48,sin(meanTheta).*0.48,classNameList{i},...
                            'Rotation',rotation,'FontSize',12,'VerticalAlignment','cap');
                    end
                end
                obj.MeanScatter=scatter(0,0,'filled','CData',mean(colorList(1:length(classSize),:)),'MarkerEdgeColor',[.4,.4,.4],'SizeData',60);
                disp(char([64 97 117 116 104 111 114 32 58 32 115 108 97 110 100 97 114 101 114]))
    
                % 贝塞尔函数 ===================================================
                function pnts=bezierCurve(pnts,N)
                    t=linspace(0,1,N);
                    p=size(pnts,1)-1;
                    coe1=factorial(p)./factorial(0:p)./factorial(p:-1:0);
                    coe2=((t).^((0:p)')).*((1-t).^((p:-1:0)'));
                    pnts=(pnts'*(coe1'.*coe2))';
                end
            end
            % 修改颜色函数 
            function obj=setColorN(obj,n,color)
                set(obj.ClassLine(n),'Color',[color,.8]);
                set(obj.ChildLine(n),'Color',[color,.4]);
                set(obj.ClassScatter(n),'MarkerFaceColor',color);
                set(obj.ClassScatter(n),'CData',color);
    
                tColorList=zeros(length(obj.ClassScatter),3);
                for i=1:length(obj.ClassScatter)
                    tColorList(i,:)=get(obj.ClassScatter(i),'CData');
                end
                set(obj.MeanScatter,'MarkerFaceColor',mean(tColorList));
                set(obj.MeanScatter,'CData',mean(tColorList));
            end
            % 修改第一层文字属性
            function obj=setLable1(obj,varargin)
                for i=1:length(obj.LabelHdl0)
                    set(obj.LabelHdl0(i),varargin{:});
                end
            end
    
            % 修改第二层文字属性
            function obj=setLable2(obj,varargin)
                for i=1:size(obj.LabelHdl1,1)
                    for j=1:size(obj.LabelHdl1,2)
                        if obj.LabelHdl1(i,j)~=0
                        set(obj.LabelHdl1(i,j),varargin{:});
                        end
                    end
                end
            end
        end
    % Zhaoxu Liu (2022). Circular dendrogram 环形树状图 
    % (https://www.mathworks.com/matlabcentral/fileexchange/118325-circular-dendrogram), 
    % MATLAB Central File Exchange. 检索来源 2022/9/29.
    end
    

    三层

    classdef circleTree3
    % @author : slandarer
    % gzh  : slandarer随笔 
    
        properties
            ax,treeList
            ClassLine,ChildLine1,ChildLine2
            MeanScatter,ClassScatter1,ClassScatter2
            LabelHdl0,LabelHdl1,LabelHdl2
        end
    
        methods
            function obj = circleTree3(treeList)
                obj.ax=gca;hold on;
                obj.treeList=treeList;   
            end
    
            function obj=draw(obj)
                % 坐标区域属性设置 ==============================================
                obj.ax=gca;hold on
                obj.ax.XLim=[-1,1];
                obj.ax.YLim=[-1,1];
                obj.ax.XTick=[];
                obj.ax.YTick=[];
                obj.ax.XColor='none';
                obj.ax.YColor='none';
                obj.ax.PlotBoxAspectRatio=[1,1,1];
    
                fig=obj.ax.Parent;
                if max(fig.Position(3:4))<600
                    fig.Position(3:4)=1.8.*fig.Position(3:4);
                    fig.Position(1:2)=fig.Position(1:2)./3;
                end
                % ax.LooseInset=[0,0,0,0];
                % 理顺层级 =====================================================
                classNameList=unique(obj.treeList(:,1));
                
                childNameList1{length(classNameList)}={[]};
                childrenList2{length(classNameList)}={[]};
                for i=1:length(classNameList)
                    tList=obj.treeList(:,2);
                    tList=tList(strcmp(classNameList{i},obj.treeList(:,1)));
                    childNameList1{i}=unique(tList);
                end
    
                for i=1:length(classNameList)
                    for j=1:length(childNameList1{i})
                        tchildNameList=childNameList1{i};
                        tbool1=strcmp(classNameList{i},obj.treeList(:,1));
                        tbool2=strcmp(tchildNameList{j},obj.treeList(:,2));
                        childrenList2{i,j}=find(tbool1&tbool2);
                        classSize(i,j)=length(childrenList2{i,j});
                    end
                end
                cumSize=classSize';
                colSize=cumSize(cumSize~=0)';
                cumSize=cumsum(colSize);
    
                % 开始绘图 =====================================================
                sepTheta=2/30/length(cumSize);
                cumTheta=[0,28/30.*cumSize./cumSize(end)];
                colorList=[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;
                colorList=[colorList;rand(length(classSize),3)];
                colorList=colorList(4:end,:);
                n=1;
                for i=1:length(classNameList)
                    meanThetaList=[];
                    for j=1:length(childNameList1{i})
                        thetaList=linspace(sepTheta*n+cumTheta(n),sepTheta*n+cumTheta(n+1),classSize(i,j)).*2.*pi;
                        bX2=[];bY2=[];
                        for k=1:classSize(i,j)
                            oX2=[cos(thetaList(k)),cos(thetaList(k)).*0.95,cos(mean(thetaList)).*0.9,cos(mean(thetaList)).*0.7];
                            oY2=[sin(thetaList(k)),sin(thetaList(k)).*0.95,sin(mean(thetaList)).*0.9,sin(mean(thetaList)).*0.7];
                            bXY2=bezierCurve([oX2',oY2'],200);
                            bX2=[bX2;bXY2(:,1);nan];
                            bY2=[bY2;bXY2(:,2);nan]; 
                        end
                        obj.ChildLine2(i,j)=plot(bX2,bY2,'Color',[colorList(i,:),.4],'LineWidth',1.1);
                        meanThetaList=[meanThetaList,mean(thetaList)];
                        
    
                        tchildNameList=childNameList1{i};
                        rotation=mean(thetaList)/pi*180;
                        if rotation>90&&rotation<270
                            rotation=rotation+180;
                            obj.LabelHdl1(n)=text(cos(mean(thetaList)).*0.7,sin(mean(thetaList)).*0.7,tchildNameList{j},...
                                'Rotation',rotation,'HorizontalAlignment','right','FontSize',11,'VerticalAlignment','bottom');
                        else
                            obj.LabelHdl1(n)=text(cos(mean(thetaList)).*0.7,sin(mean(thetaList)).*0.7,tchildNameList{j},...
                                'Rotation',rotation,'FontSize',11,'VerticalAlignment','cap');
                        end
                        if classSize(i,j)<sum(classSize(i,:))/20
                            set(obj.LabelHdl1(n),'Visible','off');
                        end
    
                        n=n+1;
                    end
                    obj.ClassScatter2(i)=scatter(cos(meanThetaList).*0.7,sin(meanThetaList).*0.7,'filled',...
                        'CData',colorList(i,:),'MarkerEdgeColor',[.4,.4,.4],'SizeData',20);
                    bX1=[];bY1=[];
                    for m=1:length(meanThetaList)
                        oX1=[cos(meanThetaList(m)).*0.7,cos(meanThetaList(m)).*0.65,cos(mean(meanThetaList)).*0.5,cos(mean(meanThetaList)).*0.35];
                        oY1=[sin(meanThetaList(m)).*0.7,sin(meanThetaList(m)).*0.65,sin(mean(meanThetaList)).*0.5,sin(mean(meanThetaList)).*0.35];
                        bXY1=bezierCurve([oX1',oY1'],200);
                        bX1=[bX1;bXY1(:,1);nan];
                        bY1=[bY1;bXY1(:,2);nan];
                    end
                    obj.ChildLine1(i)=plot(bX1,bY1,'Color',[colorList(i,:),.5],'LineWidth',1.2);
                    meanTheta=mean(meanThetaList);
                    oX0=[cos(meanTheta).*0.35,cos(meanTheta).*0.32,0.05,0];
                    oY0=[sin(meanTheta).*0.35,sin(meanTheta).*0.32,-0.15,0];
                    bXY0=bezierCurve([oX0',oY0'],200);
                    obj.ClassLine(i)=plot(bXY0(:,1),bXY0(:,2),'Color',[colorList(i,:),.7],'LineWidth',1.4);
                    obj.ClassScatter1(i)=scatter(cos(meanTheta).*0.35,sin(meanTheta).*0.35,'filled',...
                        'CData',colorList(i,:),'MarkerEdgeColor',[.4,.4,.4],'SizeData',40);
                    rotation=meanTheta/pi*180;
                    if rotation>90&&rotation<270
                        rotation=rotation+180;
                        obj.LabelHdl0(i)=text(cos(meanTheta).*0.35,sin(meanTheta).*0.35,classNameList{i},...
                            'Rotation',rotation,'HorizontalAlignment','right','FontSize',12,'VerticalAlignment','bottom');
                    else
                        obj.LabelHdl0(i)=text(cos(meanTheta).*0.35,sin(meanTheta).*0.35,classNameList{i},...
                            'Rotation',rotation,'FontSize',12,'VerticalAlignment','cap');
                    end
                end
                obj.MeanScatter=scatter(0,0,'filled','CData',mean(colorList(1:length(classNameList),:)),'MarkerEdgeColor',[.4,.4,.4],'SizeData',60);
                disp(char([64 97 117 116 104 111 114 32 58 32 115 108 97 110 100 97 114 101 114]))
                % 贝塞尔函数 ===================================================
                function pnts=bezierCurve(pnts,N)
                    t=linspace(0,1,N);
                    p=size(pnts,1)-1;
                    coe1=factorial(p)./factorial(0:p)./factorial(p:-1:0);
                    coe2=((t).^((0:p)')).*((1-t).^((p:-1:0)'));
                    pnts=(pnts'*(coe1'.*coe2))';
                end
            end
    
            % 修改颜色函数 
            function obj=setColorN(obj,n,color)
                set(obj.ClassLine(n),'Color',[color,.8]);
                set(obj.ChildLine1(n),'Color',[color,.4]);
                for i=1:length(obj.ChildLine2(n,:))
                    if obj.ChildLine2(n,i)~=0
                        set(obj.ChildLine2(n,i),'Color',[color,.4]);
                    end
                end
                set(obj.ClassScatter1(n),'MarkerFaceColor',color);
                set(obj.ClassScatter2(n),'CData',color);
    
                tColorList=zeros(length(obj.ClassScatter1),3);
                for i=1:length(obj.ClassScatter1)
                    tColorList(i,:)=get(obj.ClassScatter1(i),'CData');
                end
                set(obj.MeanScatter,'MarkerFaceColor',mean(tColorList));
                set(obj.MeanScatter,'CData',mean(tColorList));
            end
            % 修改第一层文字属性
            function obj=setLable1(obj,varargin)
                for i=1:length(obj.LabelHdl0)
                    set(obj.LabelHdl0(i),varargin{:});
                end
            end
    
            % 修改第二层文字属性
            function obj=setLable2(obj,varargin)
                for i=1:size(obj.LabelHdl1,1)
                    for j=1:size(obj.LabelHdl1,2)
                        if obj.LabelHdl1(i,j)~=0
                        set(obj.LabelHdl1(i,j),varargin{:});
                        end
                    end
                end
            end
        end
    % Zhaoxu Liu (2022). Circular dendrogram 环形树状图 
    % (https://www.mathworks.com/matlabcentral/fileexchange/118325-circular-dendrogram), 
    % MATLAB Central File Exchange. 检索来源 2022/9/29.
    end
    

    MATLAB树状图绘制能画成这样属实不易,如果有用请留个叭~

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

    Zhaoxu Liu (2022). Circular dendrogram 环形树状图 (https://www.mathworks.com/matlabcentral/fileexchange/118325-circular-dendrogram), MATLAB Central File Exchange. 检索来源 2022/9/29.

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

    全部m文件及数据获取:

    链接:https://pan.baidu.com/s/12iTl6P82PsMUhdZc4tEQJg?pwd=slan
    提取码:slan

  • 相关阅读:
    华为机试真题 C++ 实现【数组连续和】
    Ubuntu配置静态IP
    第十六章 反射与注解
    ElasticSearch的简单了解和使用
    ctf.show刷题记录_web(1-10)
    黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
    需求管理手册-对需求描述的要求(8)
    leetcode二叉树相关模板
    2023山东建筑大学考研介绍
    MATLAB continue语句
  • 原文地址:https://blog.csdn.net/slandarer/article/details/127109881