利用Matlab繪制好看的弦圖

弦圖在python中以及R中非常常見,但是MATLAB中卻始終沒有相關函數,file exchange中也沒有工作做的較為完備的弦圖繪制函數(不過現在有瞭,我已經往上面也傳瞭一份hiahiahia)

僅工具函數主體部分約300行,字符數約8000,能畫出與R語言同等質量的弦圖實屬不易,希望能有個`點贊“!!!

由於工具函數過長,將被放在最後展示,以下將先展示函數用法

封面圖

使用教程

1.數據格式

數據要求為全部數值大於等於0的數值矩陣,或者table數組,或者數值矩陣+行列名元胞數組,首先舉個數值矩陣的例子:

數值矩陣

dataMat=randi([0,5],[5,4]); 

% 繪圖
CC=chordChart(dataMat);
CC=CC.draw();

這樣由於沒對各個對象命名,因此會自動命名為RnCn

數值矩陣+行列名元胞數組

這是最推薦的一種格式:

dataMat=[2 0 1 2 5 1 2;
         3 5 1 4 2 0 1;
         4 0 5 5 2 4 3];
colName={'G1','G2','G3','G4','G5','G6','G7'};
rowName={'S1','S2','S3'};

CC=chordChart(dataMat,'rowName',rowName,'colName',colName);
CC=CC.draw();

rowName要和矩陣的行相同大小

colName要和矩陣的列相同大小

對於本列子來說第2行第3列數值是1,就說明有一份能量從S2流向G3,也就在這倆之間需要畫單位寬度的弦。

table 數組

需要使用如下格式的table數組:

當然,如果各個行沒有命名的話依舊會自動命名的。

2.修飾弦

弦的批量修飾

弦的批量修飾可以使用setChordProp函數,一切Patch對象所具有的屬性均可以被修飾,舉個例子(修飾一下弦的顏色,邊緣顏色,邊緣線形狀等):

CC.setChordProp('EdgeColor',[.3,.3,.3],'LineStyle','--',...
    'LineWidth',.1,'FaceColor',[.3,.3,.3])

弦的單獨修飾

弦的單獨修飾可以使用setChordMN函數,其中m,n值是和原始數值矩陣的行列完全對應的,舉個例子(把S2流向G4的弦顏色更改為紅色):

CC.setChordMN(2,4,'FaceColor',[1,0,0])

弦的顏色映射

使用setChordColorByMap函數進行顏色映射,

matlab 自帶的colormap均可用:

或者也可自行放入一個n×3大小的顏色列表,程序會自動對其進行插值:舉個例子:

CC.setChordColorByMap(copper(100))

3.圓弧狀方塊修飾

圓弧狀方塊批量修飾

使用

setSquareT_Prop

setSquareF_Prop

分別修飾上方方塊和下方方塊,一切Patch對象所具有的屬性均可以被修飾,舉個例子,上方方塊批量修飾(改為黑色):

CC.setSquareT_Prop('FaceColor',[0,0,0])

圓弧狀方塊單獨修飾

使用

setSquareT_N

setSquareF_N

分別修飾上方方塊和下方方塊,舉個例子,上方第二個方塊單獨修飾(改為紅色):

CC.setSquareT_N(2,'FaceColor',[.8,0,0])

4.字體調整

使用setFont函數對字體進行調整,所有text對象具有的屬性均可以修飾,舉個例子(更改文本的字號、字體和顏色):

CC.setFont('FontSize',25,'FontName','Cambria','Color',[0,0,.8])

5.顯示和隱藏刻度

用法:

CC.tickState('on')
% CC.tickState('off')

工具函數完整代碼

classdef chordChart
% @author : slandarer
% gzh  : slandarer隨筆

% 使用示例:
% =========================================================================
% dataMat=[2 0 1 2 5 1 2;
%          3 5 1 4 2 0 1;
%          4 0 5 5 2 4 3];
% colName={'G1','G2','G3','G4','G5','G6','G7'};
% rowName={'S1','S2','S3'};
% 
% CC=chordChart(dataMat,'rowName',rowName,'colName',colName);
% CC=CC.draw()

    properties
        ax
        arginList={'colName','rowName'}
        verMatlab   % MATLAB 版本: R2021a顯示為2021,R2021b顯示為2021.5
        chordTable  % table數組
        dataMat     % 數值矩陣
        colName={}; % 列名稱
        rowName={}; % 行名稱
        thetaSetF
        thetaSetT
        % -----------------------------------------------------------
        squareFHdl  % 繪制下方方塊的圖形對象矩陣
        squareTHdl  % 繪制下上方方塊的圖形對象矩陣
        nameFHdl    % 繪制下方文本的圖形對象矩陣
        nameTHdl    % 繪制上方文本的圖形對象矩陣
        chordMatHdl % 繪制弦的圖形對象矩陣
        thetaTickFHdl % 刻度句柄
        thetaTickTHdl % 刻度句柄
        RTickFHdl % 軸線句柄
        RTickTHdl % 軸線句柄
    end

    methods
        function obj=chordChart(varargin)
            if isa(varargin{1},'matlab.graphics.axis.Axes')
                obj.ax=varargin{1};varargin(1)=[];
            else
                obj.ax=gca;
            end
            
            % 獲取版本信息
            tver=version('-release');
            obj.verMatlab=str2double(tver(1:4))+(abs(tver(5))-abs('a'))/2;

            if obj.verMatlab<2017
                hold on
            else
                hold(obj.ax,'on')
            end

            
            obj.dataMat=varargin{1};varargin(1)=[];
            if isa(obj.dataMat,'table')
            obj.chordTable=obj.dataMat;
                if isempty(obj.chordTable.Properties.RowNames)
                    for i=1:size(obj.chordTable.Variables,1)
                        obj.rowName{i}=['R',num2str(i)];
                    end
                end
            else

            % 獲取其他數據
            for i=1:(length(varargin)-1)
                tid=ismember(obj.arginList,varargin{i});
                if any(tid)
                obj.(obj.arginList{tid})=varargin{i+1};
                end
            end
            tzerocell{1,size(obj.dataMat,2)}=zeros(size(obj.dataMat,1),1);
            for i=1:size(obj.dataMat,2)
                tzerocell{1,i}=zeros(size(obj.dataMat,1),1);
            end
            if isempty(obj.colName)
                for i=1:size(obj.dataMat,2)
                    obj.colName{i}=['C',num2str(i)];
                end
            end
            if isempty(obj.rowName)
                for i=1:size(obj.dataMat,1)
                    obj.rowName{i}=['R',num2str(i)];
                end
            end
            

            % 創建table數組
            obj.chordTable=table(tzerocell{:});
            obj.chordTable.Variables=obj.dataMat;
            obj.chordTable.Properties.VariableNames=obj.colName;
            obj.chordTable.Properties.RowNames=obj.rowName;

            help chordChart
            end
        end

        function obj=draw(obj)
            obj.ax.XLim=[-1.38,1.38];
            obj.ax.YLim=[-1.38,1.38];
            obj.ax.XTick=[];
            obj.ax.YTick=[];
            obj.ax.XColor='none';
            obj.ax.YColor='none';
            obj.ax.PlotBoxAspectRatio=[1,1,1];

            % 計算繪圖所用數值
            tDMat=obj.chordTable.Variables;
            tDFrom=obj.chordTable.Properties.RowNames;
            tDTo=obj.chordTable.Properties.VariableNames;

            tDMatUni=tDMat-min(min(tDMat));
            tDMatUni=tDMatUni./max(max(tDMatUni));

            sep1=1/20;
            sep2=1/40;

            ratioF=sum(tDMat,2)./sum(tDMat,[1,2]);
            ratioF=[0,ratioF'];
            ratioT=[0,sum(tDMat,1)./sum(tDMat,[1,2])];

            sepNumF=size(tDMat,1);
            sepNumT=size(tDMat,2);

            sepLen=pi*(1-2*sep1)*sep2;
            baseLenF=(pi*(1-sep2)-(sepNumF-1)*sepLen);
            baseLenT=(pi*(1-sep2)-(sepNumT-1)*sepLen);
            tColor=[61 96 137;76 103 86]./255;
            % 繪制下方方塊
            for i=1:sepNumF
                theta1=2*pi-pi*sep1/2-sum(ratioF(1:i))*baseLenF-(i-1)*sepLen;
                theta2=2*pi-pi*sep1/2-sum(ratioF(1:i+1))*baseLenF-(i-1)*sepLen;
                theta=linspace(theta1,theta2,100);
                X=cos(theta);Y=sin(theta);
                obj.squareFHdl(i)=fill([1.05.*X,1.15.*X(end:-1:1)],[1.05.*Y,1.15.*Y(end:-1:1)],...
                    tColor(1,:),'EdgeColor','none');
                theta3=(theta1+theta2)/2;
                obj.nameFHdl(i)=text(cos(theta3).*1.28,sin(theta3).*1.28,tDFrom{i},'FontSize',12,'FontName','Arial',...
                    'HorizontalAlignment','center','Rotation',-(1.5*pi-theta3)./pi.*180);
                obj.RTickFHdl(i)=plot(cos(theta).*1.17,sin(theta).*1.17,'Color',[0,0,0],'LineWidth',.8,'Visible','off');
            end
            % 繪制上方放塊
            for j=1:sepNumT
                theta1=pi-pi*sep1/2-sum(ratioT(1:j))*baseLenT-(j-1)*sepLen;
                theta2=pi-pi*sep1/2-sum(ratioT(1:j+1))*baseLenT-(j-1)*sepLen;
                theta=linspace(theta1,theta2,100);
                X=cos(theta);Y=sin(theta);
                obj.squareTHdl(j)=fill([1.05.*X,1.15.*X(end:-1:1)],[1.05.*Y,1.15.*Y(end:-1:1)],...
                    tColor(2,:),'EdgeColor','none');
                theta3=(theta1+theta2)/2;
                obj.nameTHdl(j)=text(cos(theta3).*1.28,sin(theta3).*1.28,tDTo{j},'FontSize',12,'FontName','Arial',...
                    'HorizontalAlignment','center','Rotation',-(.5*pi-theta3)./pi.*180);
                obj.RTickTHdl(j)=plot(cos(theta).*1.17,sin(theta).*1.17,'Color',[0,0,0],'LineWidth',.8,'Visible','off');
            end

            colorFunc=colorFuncFactory(flipud(summer(50)));
            % 繪制弦

            for i=1:sepNumF
                for j=sepNumT:-1:1
                    theta1=2*pi-pi*sep1/2-sum(ratioF(1:i))*baseLenF-(i-1)*sepLen;
                    theta2=2*pi-pi*sep1/2-sum(ratioF(1:i+1))*baseLenF-(i-1)*sepLen;
                    theta3=pi-pi*sep1/2-sum(ratioT(1:j))*baseLenT-(j-1)*sepLen;
                    theta4=pi-pi*sep1/2-sum(ratioT(1:j+1))*baseLenT-(j-1)*sepLen;

                    tRowV=tDMat(i,:);tRowV=[0,tRowV(end:-1:1)./sum(tRowV)];
                    tColV=tDMat(:,j)';tColV=[0,tColV./sum(tColV)];       

                    % 貝塞爾曲線斷點計算
                    theta5=(theta2-theta1).*sum(tRowV(1:(sepNumT+1-j)))+theta1;
                    theta6=(theta2-theta1).*sum(tRowV(1:(sepNumT+2-j)))+theta1;
                    theta7=(theta3-theta4).*sum(tColV(1:i))+theta4;
                    theta8=(theta3-theta4).*sum(tColV(1:i+1))+theta4;
                    tPnt1=[cos(theta5),sin(theta5)];
                    tPnt2=[cos(theta6),sin(theta6)];
                    tPnt3=[cos(theta7),sin(theta7)];
                    tPnt4=[cos(theta8),sin(theta8)];

                    if j==sepNumT,obj.thetaSetF(i,1)=theta5;end
                    obj.thetaSetF(i,j+1)=theta6;
                    if i==1,obj.thetaSetT(1,j)=theta7;end
                    obj.thetaSetT(i+1,j)=theta8;

                    % 計算曲線
                    tLine1=bezierCurve([tPnt1;0,0;tPnt3],200);
                    tLine2=bezierCurve([tPnt2;0,0;tPnt4],200);
                    tline3=[cos(linspace(theta6,theta5,100))',sin(linspace(theta6,theta5,100))'];
                    tline4=[cos(linspace(theta7,theta8,100))',sin(linspace(theta7,theta8,100))'];
                    obj.chordMatHdl(i,j)=fill([tLine1(:,1);tline4(:,1);tLine2(end:-1:1,1);tline3(:,1)],...
                         [tLine1(:,2);tline4(:,2);tLine2(end:-1:1,2);tline3(:,2)],...
                         colorFunc(tDMatUni(i,j)),'FaceAlpha',.3,'EdgeColor','none');
                    if tDMat(i,j)==0
                        set(obj.chordMatHdl(i,j),'Visible','off')
                    end     
                end

                % 繪制刻度線
                tX=[cos(obj.thetaSetF(i,:)).*1.17;cos(obj.thetaSetF(i,:)).*1.19;nan.*ones(1,sepNumT+1)];
                tY=[sin(obj.thetaSetF(i,:)).*1.17;sin(obj.thetaSetF(i,:)).*1.19;nan.*ones(1,sepNumT+1)];
                obj.thetaTickFHdl(i)=plot(tX(:),tY(:),'Color',[0,0,0],'LineWidth',.8,'Visible','off');
            end
            for j=1:sepNumT
                tX=[cos(obj.thetaSetT(:,j)').*1.17;cos(obj.thetaSetT(:,j)').*1.19;nan.*ones(1,sepNumF+1)];
                tY=[sin(obj.thetaSetT(:,j)').*1.17;sin(obj.thetaSetT(:,j)').*1.19;nan.*ones(1,sepNumF+1)];
                obj.thetaTickTHdl(j)=plot(tX(:),tY(:),'Color',[0,0,0],'LineWidth',.8,'Visible','off');
            end


            % 貝塞爾函數
            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

            % 漸變色句柄生成函數
            function colorFunc=colorFuncFactory(colorList)
                x=(0:size(colorList,1)-1)./(size(colorList,1)-1);
                y1=colorList(:,1);y2=colorList(:,2);y3=colorList(:,3);
                colorFunc=@(X)[interp1(x,y1,X,'linear')',interp1(x,y2,X,'linear')',interp1(x,y3,X,'linear')'];
            end
        end
        % =================================================================
        % 批量弦屬性設置
        function setChordProp(obj,varargin)
            tDMat=obj.chordTable.Variables;
            for i=1:size(tDMat,1)
                for j=1:size(tDMat,2)
                    set(obj.chordMatHdl(i,j),varargin{:});
                end
            end
        end
        % 單獨弦屬性設置
        function setChordMN(obj,m,n,varargin)
            set(obj.chordMatHdl(m,n),varargin{:});
        end
        % 根據colormap映射顏色
        function setChordColorByMap(obj,colorList)
            tDMat=obj.chordTable.Variables;
            tDMatUni=tDMat-min(min(tDMat));
            tDMatUni=tDMatUni./max(max(tDMatUni));

            colorFunc=colorFuncFactory(colorList);
            for i=1:size(tDMat,1)
                for j=1:size(tDMat,2)
                    set(obj.chordMatHdl(i,j),'FaceColor',colorFunc(tDMatUni(i,j)));
                end
            end
            % 漸變色句柄生成函數
            function colorFunc=colorFuncFactory(colorList)
                x=(0:size(colorList,1)-1)./(size(colorList,1)-1);
                y1=colorList(:,1);y2=colorList(:,2);y3=colorList(:,3);
                colorFunc=@(X)[interp1(x,y1,X,'linear')',interp1(x,y2,X,'linear')',interp1(x,y3,X,'linear')'];
            end
        end


        % -----------------------------------------------------------------
        % 批量上方方塊屬性設置
        function setSquareT_Prop(obj,varargin)
            tDMat=obj.chordTable.Variables;
            for j=1:size(tDMat,2)
                set(obj.squareTHdl(j),varargin{:});
            end
        end
        % 單獨上方方塊屬性設置
        function setSquareT_N(obj,n,varargin)
            set(obj.squareTHdl(n),varargin{:});
        end
        % 批量下方方塊屬性設置
        function setSquareF_Prop(obj,varargin)
            tDMat=obj.chordTable.Variables;
            for i=1:size(tDMat,1)
                set(obj.squareFHdl(i),varargin{:});
            end
        end
        % 單獨上方方塊屬性設置
        function setSquareF_N(obj,n,varargin)
            set(obj.squareFHdl(n),varargin{:});
        end


        % -----------------------------------------------------------------
        % 字體設置
        function setFont(obj,varargin)
            tDMat=obj.chordTable.Variables;
            for i=1:size(tDMat,1)
                set(obj.nameFHdl(i),varargin{:});
            end
            for j=1:size(tDMat,2)
                set(obj.nameTHdl(j),varargin{:});
            end 
        end


        % -----------------------------------------------------------------
        % 刻度開關
        function tickState(obj,state)
            tDMat=obj.chordTable.Variables;
            for i=1:size(tDMat,1)
                set(obj.thetaTickFHdl(i),'Visible',state);
                set(obj.RTickFHdl(i),'Visible',state);
            end
            for j=1:size(tDMat,2)
                set(obj.thetaTickTHdl(j),'Visible',state);
                set(obj.RTickTHdl(j),'Visible',state);
            end          
        end
    end
end

封面圖繪制代碼

封面一

% demo 1
% @author : slandarer
% gzh  : slandarer隨筆

dataMat=[2 0 1 2 5 1 2;
         3 5 1 4 2 0 1;
         4 0 5 5 2 4 3];
colName={'G1','G2','G3','G4','G5','G6','G7'};
rowName={'S1','S2','S3'};

CC=chordChart(dataMat,'rowName',rowName,'colName',colName);
CC=CC.draw();

CC.setFont('FontSize',17,'FontName','Cambria')
CC.tickState('on')

封面二

% demo 2
% @author : slandarer
% gzh : slandarer隨筆

dataMat=[2 0 1 2 5 1 2;
         3 5 1 4 2 0 1;
         4 0 5 5 2 4 3];
colName={'G1','G2','G3','G4','G5','G6','G7'};
rowName={'S1','S2','S3'};

CC=chordChart(dataMat,'rowName',rowName,'colName',colName);
CC=CC.draw();

% 弦屬性設置 ===============================================================
% CC.setChordProp('EdgeColor',[.3,.3,.3],'LineStyle','--',...
%     'LineWidth',.1,'FaceColor',[.3,.3,.3])

% CC.setChordMN(2,4,'FaceColor',[1,0,0])

CC.setChordColorByMap(copper(100))


% 方塊屬性設置 =============================================================
CC.setSquareT_Prop('FaceColor',[0,0,0])
CC.setSquareT_N(2,'FaceColor',[.8,0,0])
% CC.setSquareF_Prop('FaceColor',[0,0,0])
% CC.setSquareF_N(2,'FaceColor',[.8,0,0])

% 字體設置 =================================================================
CC.setFont('FontSize',17,'FontName','Cambria','Color',[0,0,.8])


% 刻度開關設置 =============================================================
CC.tickState('on')

以上就是利用Matlab繪制好看的弦圖的詳細內容,更多關於Matlab弦圖的資料請關註WalkonNet其它相關文章!

推薦閱讀: