MATLAB | 秒杀Nature绘图 | 附带弦图绘制函数版本更新

文摘   2025-01-10 00:37   英国  
请尊重原创劳动成果
转载请注明本文链接
及文章作者:slandarer

Hey,最近刷到公众号CNSplot复刻Nature弦图,于是想拿自己开发的工具试一下,同时为工具查漏补缺,增填了一些新功能,本篇文章先讲解如何复刻,再讲解一些新属性该怎么使用。

本人工具复刻图:

与Nature原图对比:

本文绘图需要调用我自己写的弦图绘制工具函数,该函数可在fileexchange或者gitee仓库下载,里面有弦图函数基本使用例子:

gitee链接

  • 弦图+有向弦图 https://gitee.com/slandarer/matlab-chord-chart

fileexchange链接

  • chord chart 弦图 https://www.mathworks.com/matlabcentral/fileexchange/116550-chord-chart
  • Digraph chord chart 有向弦图https://www.mathworks.com/matlabcentral/fileexchange/121043-digraph-chord-chart

同时我发现,我在MATLAB官网fileexchange平台发布的文件2024年下载总量排到了官网前10,喜提小徽章:

  • https://www.mathworks.com/matlabcentral/profile/badges/157

Nature弦图复刻

数据准备及导入

Nature文章网站:

  • https://www.nature.com/articles/s41586-023-05769-3#MOESM9

下载数据:

导入数据并调整格式:

% Data source : 
% Lake, B.B., Menon, R., Winfree, S. et al. 
% An atlas of healthy and injured cell states and niches in the human kidney. 
% Nature 619, 585–594 (2023). https://doi.org/10.1038/s41586-023-05769-3
% https://www.nature.com/articles/s41586-023-05769-3
% 41586_2023_5769_MOESM9_ESM.xlsx | sheet : panel_d_chordplot
Data = readtable("41586_2023_5769_MOESM9_ESM.xlsx");
dataMat = rot90(Data.Variables, 2);
dataMat = triu(dataMat, 1);
nameList = fliplr(Data.Properties.VariableNames);
nameList = cellfun(@(s)strrep(s, '_''-'), nameList, 'UniformOutput',false);
CList = [203,122,165208,112,85214,95,1106,103,900,114,178121,171,123;
         240,228,65123,199,940,160,11746,164,16585,182,231159,171,110230,157,5]./255;

基础绘制

有下面三个新参数:

  • Rotation整个弦图旋转角度
  • OSqRatio弧形块外圈宽度,范围[0,1]
  • SSqRatio弧形块内圈宽度,范围[-0.5,1]
figure('Units','normalized''Position',[.02,.05,.6,.85])
BCC=biChordChart(dataMat, 'Label',nameList, 'Rotation',pi/1.62,...
    'CData',CList, 'SSqRatio'-30/100'OSqRatio'80/100'LRadius',1.21);
BCC=BCC.draw();

调整文字旋转角度

把所有标签旋转至水平:

% 旋转标签(Rotate labels)
textHdl = findobj(gca, 'Tag','BiChordLabel');
for i = 1:length(textHdl)
    set(textHdl(i), 'Rotation',0'FontSize',17)
    if textHdl(i).Position(1) < -.1
        set(textHdl(i), 'HorizontalAlignment','right')
    elseif textHdl(i).Position(1) > .1
        set(textHdl(i), 'HorizontalAlignment','left')
    end
end

修改弦透明度

% 调整弦透明度(Adjust the chords opacity)
for i = 1:size(dataMat, 1)
    for j = 1:size(dataMat, 2)
        BCC.setChordMN(i,j'FaceAlpha'.5)
    end
end

添加X轴标签

ax=gca;
ax.XLabel.String = {'Pairwise connections in neighbourhoods''(1,240,569 neighbourhoods)'};
ax.XLabel.Color = 'k';
ax.XLabel.FontSize = 15;

完整代码

% Data source : 
% Lake, B.B., Menon, R., Winfree, S. et al. 
% An atlas of healthy and injured cell states and niches in the human kidney. 
% Nature 619, 585–594 (2023). https://doi.org/10.1038/s41586-023-05769-3
% https://www.nature.com/articles/s41586-023-05769-3
% 41586_2023_5769_MOESM9_ESM.xlsx | sheet : panel_d_chordplot
Data = readtable("41586_2023_5769_MOESM9_ESM.xlsx");
dataMat = rot90(Data.Variables, 2);
dataMat = triu(dataMat, 1);
nameList = fliplr(Data.Properties.VariableNames);
nameList = cellfun(@(s)strrep(s, '_''-'), nameList, 'UniformOutput',false);
CList = [203,122,165208,112,85214,95,1106,103,900,114,178121,171,123;
         240,228,65123,199,940,160,11746,164,16585,182,231159,171,110230,157,5]./255;

figure('Units','normalized''Position',[.02,.05,.6,.85])
BCC=biChordChart(dataMat, 'Label',nameList, 'Rotation',pi/1.62,...
    'CData',CList, 'SSqRatio'-30/100'OSqRatio'80/100'LRadius',1.21);
BCC=BCC.draw();


% 旋转标签(Rotate labels)
textHdl = findobj(gca, 'Tag','BiChordLabel');
for i = 1:length(textHdl)
    set(textHdl(i), 'Rotation',0'FontSize',17)
    if textHdl(i).Position(1) < -.1
        set(textHdl(i), 'HorizontalAlignment','right')
    elseif textHdl(i).Position(1) > .1
        set(textHdl(i), 'HorizontalAlignment','left')
    end
end

% 调整弦透明度(Adjust the chords opacity)
for i = 1:size(dataMat, 1)
    for j = 1:size(dataMat, 2)
        BCC.setChordMN(i,j'FaceAlpha'.5)
    end
end

ax=gca;
ax.XLabel.String = {'Pairwise connections in neighbourhoods''(1,240,569 neighbourhoods)'};
ax.XLabel.Color = 'k';
ax.XLabel.FontSize = 15;

内外圈宽度

无向弦图

这个在无向弦图工具更新的比较早,但是因为颜色区分度一般没那么大,所以颜色需要手动设置,可通过

  • OSqRatio设置弧形块外圈宽度,范围[0,1]
  • SSqRatio设置弧形块内圈宽度,范围[-0.5,1]
  • obj.setEachSquareF_Prop(i,j,...)设置从节点i流向节点j的弦对应的下方内圈弧形块颜色等属性
  • obj.setEachSquareT_Prop(i,j,...)设置从节点i流向节点j的弦对应的上方内圈弧形块颜色等属性
dataMat = rand([11,4]);
dataMat = round(10.*dataMat.*((11:-1:1).'+1))./10;

colName = {'A','B','C','D'};
rowName = {'Acidobacteriota''Actinobacteriota''Proteobacteria', ...
           'Chloroflexi''Bacteroidota''Firmicutes''Gemmatimonadota', ...
           'Verrucomicrobiota''Patescibacteria''Planctomyetota''Others'};

figure('Units','normalized''Position',[.02,.05,.8,.85])
% CC = chordChart(dataMat, 'colName',colName, 'Sep',1/80, 'SSqRatio',30/100);

% SSqRatio Range : [-50/100, 100/100]; OSqRatio Range : [0, 100/100]
CC = chordChart(dataMat, 'colName',colName, 'Sep',1/80'SSqRatio',-30/100'OSqRatio',80/100);
CC = CC.draw();

% 修改上方方块颜色(Modify the color of the blocks above)
CListT = [0.93,0.60,0.620.55,0.80,0.990.95,0.82,0.181.00,0.81,0.91];
for i = 1:size(dataMat, 2)
    CC.setSquareT_N(i'FaceColor',CListT(i,:))
end

% 修改下方方块颜色(Modify the color of the blocks below)
CListF = [0.75,0.73,0.860.56,0.83,0.780.00,0.60,0.201.00,0.49,0.02
    0.78,0.77,0.950.59,0.24,0.360.98,0.51,0.450.96,0.55,0.75
    0.47,0.71,0.840.65,0.35,0.160.40,0.00,0.64];
for i = 1:size(dataMat, 1)
    CC.setSquareF_N(i'FaceColor',CListF(i,:))
end

% 修改弦颜色(Modify chord color)
for i = 1:size(dataMat, 1)
    for j = 1:size(dataMat, 2)
        % CC.setChordMN(i,j, 'FaceColor',CListF(i,:), 'FaceAlpha',.4)
        CC.setChordMN(i,j'FaceColor',CListT(j,:), 'FaceAlpha',.4)
    end
end

% 单独设置每一个弦末端方块(Set individual end blocks for each chord)
% Use obj.setEachSquareF_Prop 
% or  obj.setEachSquareT_Prop
% F means from (blocks below)
% T means to   (blocks above)
for i = 1:size(dataMat, 1)
    for j = 1:size(dataMat, 2)
        CC.setEachSquareT_Prop(i,j'FaceColor', CListF(i,:))
        CC.setEachSquareF_Prop(i,j'FaceColor', CListT(j,:))
    end
end

% 添加刻度
CC.tickState('on')
% 修改字体,字号及颜色
CC.setFont('FontName','Cambria''FontSize',17)

有向弦图

这个会自动设置颜色,会方便一些:

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

CList=[75,146,241;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;

figure('Units','normalized','Position',[.02,.05,.6,.85])
BCC=biChordChart(dataMat,'Arrow','on','CData',CList,...
    'TickMode','auto','SSqRatio',-30/100'OSqRatio',80/100);

BCC=BCC.draw();

% 添加刻度
BCC.tickState('on')
BCC.tickLabelState('on')

整体旋转

无向弦图

一个Rotation属性的基础使用例子:

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,'Rotation',pi/3);
CC=CC.draw();

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

一个更复杂的例子:

clc;clear
rng(2)
dataMat = rand([14,5]) > .3;

colName = {'phosphorylation''vasculature development''blood vessel development', ...
           'cell adhesion''plasma membrane'};
rowName = {'THY1''FGF2''MAP2K1''CDH2''HBEGF''CXCR4''ECSCR',...
           'ACVRL1''RECK''PNPLA6''CDH5''AMOT''EFNB2''CAV1'};
figure('Units','normalized''Position',[.02,.05,.9,.85])
CC = chordChart(dataMat, 'colName',colName, 'rowName',rowName, 'Sep',1/80'LRadius',1.2'Rotation',3*pi/2);
CC = CC.draw();

% 修改上方方块颜色(Modify the color of the blocks above)
CListT = [0.47 0.58 0.750.48 0.54 0.580.65 0.72 0.650.94 0.92 0.900.98 0.76 0.68];
for i = 1:size(dataMat, 2)
    CC.setSquareT_N(i'FaceColor',CListT(i,:), 'EdgeColor',[0,0,0])
end
% 修改弦颜色(Modify chord color)
for i = 1:size(dataMat, 1)
    for j = 1:size(dataMat, 2)
        CC.setChordMN(i,j'FaceColor',CListT(j,:), 'FaceAlpha',.9'EdgeColor',[0,0,0])
    end
end
% 修改下方方块颜色(Modify the color of the blocks below)
logFC = sort(rand(1,14))*6 - 3;
for i = 1:size(dataMat, 1)
    CC.setSquareF_N(i'CData',logFC(i), 'FaceColor','flat''EdgeColor',[0,0,0])
end
% 一个红白蓝配色 colorbar
CMap = interp1([0,.5,1].', [0,0,1;1,1,1;1,0,0], linspace(0,1,50).');
colormap(CMap);
try clim([-3,3]),catch,end
try caxis([-3,3]),catch,end
CBHdl = colorbar();
CBHdl.Position = [0.74,0.25,0.02,0.2];


% Draw legend
text(1.25,-.15'LogFC''FontSize',16)
text(1.25,1'Terms''FontSize',16)

patchHdl = [];
for i = 1:size(dataMat, 2)
    patchHdl(i) = fill([10,11,12],[10,13,13], CListT(i,:), 'EdgeColor',[0,0,0]);
end
lgdHdl = legend(patchHdl, colName, 'Location','best''FontSize',14'Box','off');
lgdHdl.Position = [.735,.53,.167,.27];
lgdHdl.ItemTokenSize = [18,8];

有向弦图

也是一样的用法:

dataMat=randi([0,8],[6,6]);

BCC=biChordChart(dataMat,'Arrow','on''Rotation',pi/3);
BCC=BCC.draw();

% 添加刻度
BCC.tickState('on')

% 修改字体,字号及颜色
BCC.setFont('FontName','Cambria','FontSize',17)

有区别的是,对于有向弦图,我写了个在测试的功能,就是能够设置每个弧形块的位置,这个功能要求Rotation是每个弧形块的角度构成的向量而不是角度数值:

这是应对弧形块有东南西北等实际方位信息,不过感觉用到的机会比较少,此外为了让弧形块不重叠,要把Sep属性设置的大一些:

links = {'N','NE',252.4'NW','N',44.7'NW','W',11.4'NW','SW',7.9
'W','NW',43.1'SW','NW',35'SW','W',37.4'SW','S',10.6
'S','N',20.3'S','SW',88.5'SE','SW',89.7'SE','S',40.3
'E','SE',69.6'NE','N',27.4'NE','NW',6.9'NE','SE',14.1'NE','E',93.4};

% Change links into adjacency matrix and name list
[adjMat, nodeList]=links2AdjMat(links);

Theta=linspace(pi/2,2*pi+pi/2,9);

figure('Units','normalized','Position',[.02,.05,.6,.85])
BCC=biChordChart(adjMat,'Label',nodeList,'Arrow','on','TickMode','auto','Sep',1/2.2,'Rotation',Theta);
BCC=BCC.draw();

% 调节标签半径
% Adjustable Label radius
BCC.setLabelRadius(1.4);

% 显示刻度和数值
% Displays scales and numeric values
BCC.tickState('on')
BCC.tickLabelState('on')

BCC.labelRotate('on')
txtHdl=findobj(gca,'Tag','BiChordLabel');
for i=1:length(txtHdl)
    if abs(txtHdl(i).Position(2))>1
        set(txtHdl(i),'Rotation',0,'HorizontalAlignment','center');
    end
end

% 修改字体,字号及颜色
BCC.setFont('FontName','Cambria','FontSize',25,'Color',[0,0,.8])

% An assistant function to change links into adjacency matrix and name list
function [adjMat, nodeList] = links2AdjMat(links)
nodeList=unique([links(:,1), links(:,2)], 'stable');
BN = length(nodeList);
adjMat=zeros(BN, BN);
for i=1:size(links,1)
    sourceInd=strcmp(links{i,1},nodeList);
    targetInd=strcmp(links{i,2},nodeList);
    adjMat(sourceInd,targetInd)=links{i,3};
end
end

结语

gitee链接

  • 弦图+有向弦图 https://gitee.com/slandarer/matlab-chord-chart

fileexchange链接

  • Zhaoxu Liu / slandarer (2025). chord chart 弦图 (https://www.mathworks.com/matlabcentral/fileexchange/116550-chord-chart), MATLAB Central File Exchange. Retrieved January 9, 2025.
  • Zhaoxu Liu / slandarer (2025). Digraph chord chart 有向弦图 (https://www.mathworks.com/matlabcentral/fileexchange/121043-digraph-chord-chart), MATLAB Central File Exchange. Retrieved January 9, 2025.

本文代码免费分享,禁止用做任何盈利用途,若在论文中使用本代码,可引用上述fileexchange链接,代码编写不易,留个点赞或者再看叭~~~~



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