这几天写了一个代码很长的聚类分析树状图绘图工具函数(上一期文章立的flag总算实现了),能够比较轻松的绘制以下图形:
工具基本已经成型了,未来有需求未来有空再加哈哈哈,要求MATLAB至少需要17b版本(理论上是17b,实际上可能需要更新的版本),先讲解基本使用方法,工具函数代码放在最后,建议去gitee仓库或者fileexchange下载,因为未来有更新的话比较方便修改。先讲讲基本用法:
STree 使用教程
工具函数名为 STree
,是在 dendrogram 函数的基础上写出来的,因此必须要先下载 Statistics and Machine Learning Toolbox 工具箱后才可以使用:
1-1 基本使用
% 随机生成数据 -- random data
% rng(10)
Data = rand(75,3);
% 分类数 -- clust number
N = 5;
fig1 = figure('Units', 'normalized', 'Position', [.05,.3,.7,.4], 'Color', 'w');
% 创建聚类树状图对象 -- create tree(dendrogram) object
Z = linkage(Data, 'average');
ST = STree(Z, 'MaxClust', N);
ST.draw()
set(gca,'XColor','none','YColor','none')
和 dendrogram 函数绘制的树状图长相差不多的树状图就绘制出来啦!当然这样看起来有些单调,大家可以设置以下属性来修饰:
- ClustGap – 分类间隙
- BranchColor – 不同类树枝添加颜色
- BranchHighlight – 树枝高亮
- ClassHighlight – 分类高亮
- ClassLabel – 分类文本信息
就简单的在ST.draw()
之前设置就好,比如大概这样:
% ... ... 一堆其他代码
% ... ... 一堆其他代码
% ... ... 一堆其他代码
ST.ClustGap = 'on';
ST.draw()
为了更直观,我们续写之前的代码新开一个窗口来绘图:
fig2 = figure('Units', 'normalized', 'Position', [.05,.3,.7,.4], 'Color', 'w');
ST.ax = gca; % 更换坐标区域 -- change the axes
ST.ClustGap = 'on'; % 每个类之间加入间隙 -- insert gap between each clust
ST.BranchColor = 'on'; % 为不同类树枝添加颜色 -- set the branch's color of each clust
ST.BranchHighlight = 'on'; % 增添树枝高亮 -- add highlight for each clust's branches
ST.ClassHighlight = 'on'; % 分类高亮 -- add class highlight
ST.ClassLabel = 'on'; % 分类文本信息 -- add class-label
ST.SampleName = compose('slan%d', 1:size(Data,1));
ST.ClassName = compose('Class-%c', 64 + (1:N));
% 调整字体 -- adjust font
ST.SampleFont = {'FontSize', 10, 'FontName', 'Times New Roman'};
ST.ClassFont = {'FontSize', 14, 'FontName', 'Times New Roman', 'FontWeight', 'bold'};
ST.draw()
set(gca,'XColor','none','YColor','none')
还是同一个m文件,再新开一个窗口展示一下如何换颜色:
% change color
fig3 = figure('Units', 'normalized', 'Position', [.05,.3,.7,.4], 'Color', 'w');
ST.ax = gca;% 更换坐标区域 -- change the axes
ST.CData = [0.3569 0.0784 0.0784
0.6784 0.4471 0.1725
0.1020 0.3882 0.5176
0.1725 0.4196 0.4392
0.2824 0.2275 0.2902];
ST.draw()
set(gca,'XColor','none','YColor','none')
1-2 树枝样式
我们可以通过设置Layout
属性设置树枝样式,比如:
% 随机生成数据 -- random data
% rng(10)
Data = rand(75,3);
% 分类数 -- clust number
N = 5;
fig1 = figure('Units', 'normalized', 'Position', [.05,.3,.7,.4], 'Color', 'w');
% 创建聚类树状图对象 -- create tree(dendrogram) object
Z = linkage(Data, 'average');
ST = STree(Z, 'MaxClust', N);
ST.Layout = 'bezier';
ST.draw()
set(gca,'XColor','none','YColor','none')
树枝就会变成用贝塞尔曲线插值的样式。写段代码展示一下全部可选择样式:
% STree_demo2
% + layout : 'rectangular'(default) / 'rounded' / 'slanted' / 'ellipse' / 'bezier'
% 随机生成数据 -- random data
% rng(10)
Data = rand(75,3);
% 分类数 -- clust number
N = 5;
fig = figure('Units', 'normalized', 'Position', [.05,.1,.7,.8], 'Color', 'w');
% 创建聚类树状图对象 -- create tree(dendrogram) object
Z = linkage(Data, 'average');
ST = STree(Z, 'MaxClust', N);
% 每个类之间加入间隙 -- insert gap between each clust
ST.ClustGap = 'on';
delete(gca);
% 所有类型树枝展示 -- all kinds of branch(layout)
layoutSet = {'rectangular', 'rounded', 'slanted', 'ellipse', 'bezier'};
for i = 1:length(layoutSet)
% 创建坐标区域并修改绘图坐标区域 -- create subplots and change the parent of the tree object
ax = axes('Parent', fig, 'Position', [1/40, (5-i)/5+1/20, 1-1/20, 1/5-1/15], 'XColor', 'none', 'YColor', 'none');
ST.ax = ax;
% 改变树枝类型 -- change the layout
ST.Layout = layoutSet{i};
% 修改绘图范围以便方便添加text信息
% -- change the X-limit and the Y-limit to add the following text
ST.XLim = [0,1];
ST.YLim = [0,1];
ST.draw();
text(0, 1, layoutSet{i}, 'FontName', 'Times New Roman',...
'FontSize', 16, 'FontWeight', 'bold','Color', [1,1,1].*.2);
end
1-3 树的朝向
树有四种朝向,通过设置Orientation
来完成:
再写段代码把四个朝向展示在一个图里:
% STree_demo3
% + Orientation : 'left' / 'right' / 'top' / 'bottom'
% 随机生成数据 -- random data
% rng(10)
Data = rand(75,3);
% 分类数 -- clust number
N = 5;
fig = figure('Units', 'normalized', 'Position', [.05,.1,.9,.8], 'Color', 'w');
% 创建聚类树状图对象 -- create tree(dendrogram) object
Z = linkage(Data, 'average');
ST = STree(Z, 'MaxClust', N);
% 每个类之间加入间隙 -- insert gap between each clust
ST.ClustGap = 'on';
delete(gca);
% 创建坐标区域并修改绘图坐标区域 -- create subplots and change the parent of the tree object
axR = axes('Parent', fig, 'Position', [1/20, 1/30, 1/4-1/20, 1-1/15], 'XColor', 'none', 'YColor', 'none');
ST.ax = axR;
% 改变树枝类型 -- change the layout
ST.Layout = 'bezier';
% 改变方向 -- change the orientation
ST.Orientation = 'right';
ST.draw()
%% =========================================================================
axL = axes('Parent', fig, 'Position', [3/4, 1/30, 1/4-1/20, 1-1/15], 'XColor', 'none', 'YColor', 'none');
ST.ax = axL;
ST.Orientation = 'left';
ST.draw()
axT = axes('Parent', fig, 'Position', [1/4, 1/10, 2/4, 1/2-1/10-1/40], 'XColor', 'none', 'YColor', 'none');
ST.ax = axT;
ST.Orientation = 'top';
ST.draw()
axB = axes('Parent', fig, 'Position', [1/4, 1/2+1/40, 2/4, 1/2-1/10-1/40], 'XColor', 'none', 'YColor', 'none');
ST.ax = axB;
ST.Orientation = 'bottom';
ST.draw()
1-4 树的大小范围和旋转
通过设置XLim
, YLim
, TLim
属性来完成,其中TLim
是旋转范围,如果两个值相等就是不形变的旋转:
% STree_demo4
% + position
% + rotation
% 随机生成数据 -- random data
% rng(10)
Data = rand(75,3);
% 分类数 -- clust number
N = 5;
% 创建聚类树状图对象 -- create tree(dendrogram) object
Z = linkage(Data, 'average');
ST = STree(Z, 'MaxClust', N);
ST.ClustGap = 'on'; % 每个类之间加入间隙 -- insert gap between each clust
ST.BranchColor = 'on'; % 为不同类树枝添加颜色 -- set the branch's color of each clust
ST.BranchHighlight = 'on'; % 增添树枝高亮 -- add highlight for each clust's branches
ST.Label = 'off'; % 关闭样本标签 -- close sample label
ST.XLim = [1,3]; % 改变X坐标范围 -- change X-limit
ST.YLim = [1,2]; % 改变Y坐标范围 -- change Y-limit
ST.TLim = [pi/6,pi/6]; % 围绕0点旋转pi/6(当TLim 两个数值相等时,围绕(0,0)点做不形变的旋转)
% Rotate pi/6 around point 0 (when TLim has two equal values, rotate around point (0,0) without deformation)
close all
fig1 = figure('Units', 'normalized', 'Position', [.05,.1,.5,.7], 'Color', 'w');
ST.ax = gca;
ST.Orientation = 'left';
ST.draw()
% exportgraphics(fig1, '.\gallery\demo4_position_rotation_left.png')
%%
fig2 = figure('Units', 'normalized', 'Position', [.05,.1,.5,.7], 'Color', 'w');
ST.ax = gca;
ST.Orientation = 'top';
ST.draw()
% exportgraphics(fig2, '.\gallery\demo4_position_rotation_top.png')
1-5 扇形的树
就是设置TLim
属性两个值不相等:展示一下四种朝向的树生成的[pi/6,pi/2]范围的扇形树状图:
% STree_demo5
% + Fan-shaped rotation
% 随机生成数据 -- random data
% rng(10)
Data = rand(75,3);
% 分类数 -- clust number
N = 5;
% 创建聚类树状图对象 -- create tree(dendrogram) object
Z = linkage(Data, 'average');
ST = STree(Z, 'MaxClust', N);
ST.ClustGap = 'on'; % 每个类之间加入间隙 -- insert gap between each clust
ST.BranchColor = 'on'; % 为不同类树枝添加颜色 -- set the branch's color of each clust
ST.BranchHighlight = 'on'; % 增添树枝高亮 -- add highlight for each clust's branches
ST.Label = 'off'; % 关闭样本标签 -- close sample label
ST.Layout = 'bezier'; % 改变树枝类型 -- change the layout
ST.XLim = [1,3]; % 改变X坐标范围 -- change X-limit
ST.TLim = [pi/6,pi/2]; % 绘制pi/6到pi/2范围的扇形树状图 -- Draw a dendrogram of the range from pi/6 to pi/2
close all
OrientationSet = {'left', 'right', 'top', 'bottom'};
for i = 1:4
tempFig = figure('Units', 'normalized', 'Position', [.05,.1,.5,.7], 'Color', 'w');
ST.ax = gca;
ST.Orientation = OrientationSet{i};
ST.draw()
set(gca,'XColor','none','YColor','none')
% exportgraphics(tempFig, ['.\gallery\demo5_Fan_shaped_rotation_',OrientationSet{i},'.png'])
end
1-6 圆面(left朝向)
展示一下X坐标是否从0开始,以及树枝类型不同导致的差别:
% STree_demo6
% + Larger angle range [1]
% + Different x coordinate ranges
% 随机生成数据 -- random data
% rng(10)
Data = rand(75,3);
% 分类数 -- clust number
N = 5;
% 创建聚类树状图对象 -- create tree(dendrogram) object
Z = linkage(Data, 'average');
ST = STree(Z, 'MaxClust', N);
% 改变方向 -- change the orientation
ST.Orientation = 'left';
ST.ClustGap = 'on'; % 每个类之间加入间隙 -- insert gap between each clust
ST.BranchColor = 'on'; % 为不同类树枝添加颜色 -- set the branch's color of each clust
ST.BranchHighlight = 'on'; % 增添树枝高亮 -- add highlight for each clust's branches
ST.Label = 'on'; % 关闭样本标签 -- close sample label
ST.Layout = 'bezier'; % 改变树枝类型 -- change the layout
ST.ClassHighlight = 'on'; % 分类高亮 -- add class highlight
ST.ClassLabel = 'on'; % 分类文本信息 -- add class-label
ST.TLim = [0,2*pi];
close all
layoutSet = {'rectangular', 'rounded', 'slanted', 'ellipse', 'bezier'};
% 调整各个元素半径 -- adjust the radius of each elementa
% 样本文本 类弧形内侧 类弧形外侧 类文本
% Sample text, inner side of class arc, outer side of class arc, class text
ST.RTick = [1+1/40, 1.22, 1.27, 1.35];
ST.XLim = [0,3];
for i = 1:length(layoutSet)
tempFig = figure('Units', 'normalized', 'Position', [.05,.1,.5,.7], 'Color', 'w');
ST.ax = gca;
ST.Layout = layoutSet{i};
ST.draw()
set(gca,'XColor','none','YColor','none')
% exportgraphics(tempFig, ['.\gallery\demo6_left_full_fan_XLim_0_3_',layoutSet{i},'.png'])
end
%% ========================================================================
% 调整各个元素半径 -- adjust the radius of each elementa
% 样本文本 类弧形内侧 类弧形外侧 类文本
% Sample text, inner side of class arc, outer side of class arc, class text
ST.RTick = [1+1/40, 1.26, 1.31, 1.37];
ST.XLim = [1,3];
for i = 1:length(layoutSet)
tempFig = figure('Units', 'normalized', 'Position', [.05,.1,.5,.7], 'Color', 'w');
ST.ax = gca;
ST.Layout = layoutSet{i};
ST.draw()
set(gca,'XColor','none','YColor','none')
% exportgraphics(tempFig, ['.\gallery\demo6_left_full_fan_XLim_1_3_',layoutSet{i},'.png'])
end
X 0-3范围
X 1-3范围
1-7 圆面(right朝向)
树枝类型不同导致的差别:
% STree_demo7
% + Larger angle range [2]
% 随机生成数据 -- random data
% rng(10)
Data = rand(75,3);
% 分类数 -- clust number
N = 5;
% 创建聚类树状图对象 -- create tree(dendrogram) object
Z = linkage(Data, 'average');
ST = STree(Z, 'MaxClust', N);
% 改变方向 -- change the orientation
ST.Orientation = 'right';
ST.ClustGap = 'on'; % 每个类之间加入间隙 -- insert gap between each clust
ST.BranchColor = 'on'; % 为不同类树枝添加颜色 -- set the branch's color of each clust
ST.BranchHighlight = 'on'; % 增添树枝高亮 -- add highlight for each clust's branches
ST.Label = 'on'; % 关闭样本标签 -- close sample label
ST.Layout = 'bezier'; % 改变树枝类型 -- change the layout
ST.ClassHighlight = 'on'; % 分类高亮 -- add class highlight
ST.ClassLabel = 'on'; % 分类文本信息 -- add class-label
ST.TLim = [0,2*pi];
close all
layoutSet = {'rectangular', 'rounded', 'slanted', 'ellipse', 'bezier'};
% 调整各个元素半径 -- adjust the radius of each elementa
% 样本文本 类弧形内侧 类弧形外侧 类文本
% Sample text, inner side of class arc, outer side of class arc, class text
ST.RTick = [1+1/40, 1.4, 1.5, 1.6];
ST.XLim = [2.5,4];
for i = 1:length(layoutSet)
tempFig = figure('Units', 'normalized', 'Position', [.05,.1,.5,.7], 'Color', 'w');
ST.ax = gca;
ST.Layout = layoutSet{i};
ST.draw()
set(gca,'XColor','none','YColor','none')
% exportgraphics(tempFig, ['.\gallery\demo7_right_full_fan_XLim_0_3_',layoutSet{i},'.png'])
end
SMatrix 使用教程
这个函数是我专门写出来用来配合STree
来添加热图用的,给了三个使用实例,基本上能用到的函数都提到了:
2-1 带聚类树状图的热图
% STree_SMatrix_demo1
% 随便捏造了点数据 -- made up some data casually
X1 = randn(20,20) + [(linspace(-1,2.5,20)').*ones(1,8),(linspace(.5,-.7,20)').*ones(1,5),(linspace(.9,-.2,20)').*ones(1,7)];
X2 = randn(20,25) + [(linspace(-1,2.5,20)').*ones(1,10),(linspace(.5,-.7,20)').*ones(1,8),(linspace(.9,-.2,20)').*ones(1,7)];
% 求相关系数矩阵 -- get the correlation matrix
Data = corr(X1,X2);
% rowName and colName
rowName = {'FREM2','ALDH9A1','RBL1','AP2A2','HNRNPK','ATP1A1','ARPC3','SMG5','RPS27A',...
'RAB8A','SPARC','DDX3X','EEF1D','EEF1B2','RPS11','RPL13','RPL34','GCN1','FGG','CCT3'};
colName = compose('slan%d', 1:25);
% 分类配色 -- Color schemes for each clust
CList = [0.1490 0.4039 0.4980
0.3882 0.3608 0.4471
0.5373 0.2157 0.3098
0.7686 0.4353 0.2431];
fig1 = figure('Units', 'normalized', 'Position', [.05,.1,.6,.8], 'Color', 'w');
% 创建聚类树状图对象 -- create tree(dendrogram) object
% 左侧聚类树状图 -- left Cluster Tree
Z1 = linkage(Data, 'average');
ST1 = STree(Z1, 'MaxClust', 3);
ST1.Orientation = 'left';
ST1.XLim = [-.25,-.05];
ST1.YLim = [0,1.2];
ST1.Label = 'off';
ST1.BranchColor = 'on';
ST1.BranchHighlight = 'on';
ST1.ClassHighlight = 'on';
ST1.RTick = [0,1,1.2,0];
ST1.CData = CList;
ST1.draw()
% 右侧聚类树状图 -- right Cluster Tree
Z2 = linkage(Data.', 'average');
ST2 = STree(Z2, 'MaxClust', 3);
ST2.Orientation = 'top';
ST2.XLim = [0,1];
ST2.YLim = [1.25,1.45];
ST2.Label = 'off';
ST2.BranchColor = 'on';
ST2.BranchHighlight = 'on';
ST2.ClassHighlight = 'on';
ST2.RTick = [0,1,1.2,0];
ST2.CData = CList;
ST2.draw()
% -------------------------------------------------------------------------
% 创建热图对象 -- create heatmap object
SM = SMatrix(Data);
% 添加分组信息 -- Add grouping information
SM.RowName = rowName;
SM.ColName = colName;
SM.RowOrder = ST1.order;
SM.RowClass = ST1.class;
SM.ColOrder = ST2.order;
SM.ColClass = ST2.class;
% 设置文本和字体 -- Set Text and Font
SM.LeftLabel = 'off';
SM.RightLabel = 'on';
SM.BottomLabelFont = {'FontSize', 12, 'FontName', 'Times New Roman'};
SM.RightLabelFont = {'FontSize', 12, 'FontName', 'Times New Roman'};
% 设置位置 -- set position
SM.XLim = [0,1];
SM.YLim = [0,1.2];
SM.draw()
% 修饰坐标区域 -- Decorate axes
set(gca, 'XColor', 'none', 'YColor', 'none',...
'DataAspectRatio', [1,1,1], 'XLim', [-.5,1.3]);
CB = colorbar;
CB.Position(4) = CB.Position(4).*0.75;
CB.Position(4) = CB.Position(4).*0.75;
% exportgraphics(fig1, '.\gallery\STree_SMatrix_demo1_1.png')
%% ========================================================================
fig2 = figure('Units', 'normalized', 'Position', [.05,.1,.6,.8], 'Color', 'w');
ST1.ax = gca;
ST2.ax = gca;
SM.ax = gca;
% SM.Colormap = slanCM(141, 64);
% 每个类之间加入间隙 -- insert gap between each clust
ST1.ClustGap = 'on';
ST2.ClustGap = 'on';
SM.ClustGap = 'on';
ST1.draw()
ST2.draw()
SM.draw()
% 修饰坐标区域 -- Decorate axes
set(gca, 'XColor', 'none', 'YColor', 'none',...
'DataAspectRatio', [1,1,1], 'XLim', [-.5,1.3]);
CB = colorbar;
CB.Position(4) = CB.Position(4).*0.75;
CB.Position(4) = CB.Position(4).*0.75;
% exportgraphics(fig2, '.\gallery\STree_SMatrix_demo1_2.png')
2-2 环形热图
其中提到的slanCM
可以去fileexchange官网下载,也可以去文末gitee仓库获取全部文件,该函数的介绍在这:
% STree_SMatrix_demo2
% 随便捏造了点数据 -- made up some data casually
rng(5)
X = randn(100,80) + [(linspace(-1,2.5,100)').*ones(1,15),(linspace(.5,-.7,100)').*ones(1,15),...
(linspace(.1,-.7,100)').*ones(1,15),(linspace(.9,-.2,100)').*ones(1,15),...
(linspace(-.1,.7,100)').*ones(1,10),(linspace(-.9,-.2,100)').*ones(1,10)];
Y = randn(100,8) + [(linspace(-1,2.5,100)').*ones(1,2),(linspace(.5,-.7,100)').*ones(1,3),(linspace(-1,-2.5,100)').*ones(1,3)];
% 求相关系数矩阵 -- get the correlation matrix
Data = corr(X,Y);
% rowName and colName
rowName = compose('slan%d', 1:80);
colName = compose('var%d', 1:8);
% 分类配色 -- Color schemes for each clust
CList = [0.1490 0.4039 0.4980
0.3882 0.3608 0.4471
0.5373 0.2157 0.3098
0.7686 0.4353 0.2431];
fig1 = figure('Units', 'normalized', 'Position', [.05,.1,.6,.8], 'Color', 'w');
% 创建聚类树状图对象 -- create tree(dendrogram) object
% 内侧聚类树状图 -- inner Cluster Tree
Z1 = linkage(Data, 'average');
ST1 = STree(Z1, 'MaxClust', 4);
ST1.Orientation = 'left';
ST1.XLim = [0,1];
ST1.TLim = [pi/2,2*pi+pi/4];
ST1.Label = 'off';
ST1.BranchColor = 'on';
ST1.CData = CList;
ST1.draw()
% 径向聚类树状图 -- radial Cluster Tree
Z2 = linkage(Data.', 'average');
ST2 = STree(Z2, 'MaxClust', 2);
ST2.Orientation = 'top';
ST2.XLim = [1,2];
ST2.TLim = [pi/4,pi/4];
ST2.YLim = [0,0.3];
ST2.Label = 'off';
ST2.BranchColor = 'on';
ST2.RTick = [0,1,1.2,0];
ST2.CData = CList;
ST2.draw()
% -------------------------------------------------------------------------
% 创建热图对象 -- create heatmap object
SM = SMatrix(Data);
% 添加分组信息 -- Add grouping information
SM.RowName = rowName;
SM.ColName = colName;
SM.RowOrder = ST1.order;
SM.RowClass = ST1.class;
SM.ColOrder = ST2.order;
SM.ColClass = ST2.class;
% 设置文本和字体 -- Set Text and Font
SM.LeftLabel = 'off';
SM.RightLabel = 'on';
SM.BottomLabelFont = {'FontSize', 12, 'FontName', 'Times New Roman'};
SM.RightLabelFont = {'FontSize', 12, 'FontName', 'Times New Roman'};
% 设置位置 -- set position
SM.XLim = [1,2];
SM.TLim = [pi/2,2*pi+pi/4];
SM.draw()
% 修饰坐标区域 -- Decorate axes
set(gca, 'XColor', 'none', 'YColor', 'none',...
'DataAspectRatio', [1,1,1], 'XLim', [-2.2,2.3]);
exportgraphics(fig1, '.\gallery\STree_SMatrix_demo2_1.png')
%% ========================================================================
fig2 = figure('Units', 'normalized', 'Position', [.05,.1,.6,.8], 'Color', 'w');
ST1.ax = gca;
ST2.ax = gca;
SM.ax = gca;
% SM.Colormap = slanCM(141, 64);
% 每个类之间加入间隙 -- insert gap between each clust
ST1.ClustGap = 'on';
ST2.ClustGap = 'on';
SM.ClustGap = 'on';
% see slanCMdisplay
% and Zhaoxu Liu / slandarer (2024). 200 colormap, MATLAB Central File Exchange.
% https://www.mathworks.com/matlabcentral/fileexchange/120088-200-colormap
SM.Colormap = slanCM(136, 64);
ST1.draw()
ST2.draw()
SM.draw()
% 修饰坐标区域 -- Decorate axes
set(gca, 'XColor', 'none', 'YColor', 'none',...
'DataAspectRatio', [1,1,1], 'XLim', [-2.2,2.3]);
exportgraphics(fig2, '.\gallery\STree_SMatrix_demo2_2.png')
2-3 倾斜45°角热图
STree
和SMatrix
构造的对象其中会有一些名字以Hdl结尾的属性,实际上都是图形对象,可以直接对线条粗细呀,文字颜色之类的一系列信息进行修改,可自行研究,未来有机会可能会集成进一个set方法中:
% STree_SMatrix_demo3
% 随机生成数据
X=randn(20,20)+[(linspace(-1,2.5,20)').*ones(1,8),(linspace(.5,-.7,20)').*ones(1,5),(linspace(.9,-.2,20)').*ones(1,7)];
Data=corr(X);
% 变量名列表
NameList=compose('Sl-%d',1:20);
% 分类配色 -- Color schemes for each clust
CList = [0.1490 0.4039 0.4980
0.3882 0.3608 0.4471
0.5373 0.2157 0.3098
0.7686 0.4353 0.2431];
fig1 = figure('Units', 'normalized', 'Position', [.05,.1,.6,.8], 'Color', 'w');
% 创建聚类树状图对象 -- create tree(dendrogram) object
% 左侧聚类树状图 -- left Cluster Tree
Z1 = linkage(Data, 'average');
ST1 = STree(Z1, 'MaxClust', 3);
ST1.Orientation = 'left';
ST1.XLim = [-.25,-.05];
ST1.YLim = [0,1];
ST1.TLim = [-pi/4,-pi/4];
ST1.Label = 'off';
ST1.BranchColor = 'on';
ST1.BranchHighlight = 'on';
ST1.ClassHighlight = 'on';
ST1.RTick = [0,1,1.2,0];
ST1.CData = CList;
ST1.draw()
% 创建热图对象 -- create heatmap object
SM = SMatrix(Data);
% 添加分组信息 -- Add grouping information
SM.ColName = NameList;
SM.RowOrder = ST1.order;
SM.RowClass = ST1.class;
SM.ColOrder = ST1.order;
SM.ColClass = ST1.class;
% 设置文本和字体 -- Set Text and Font
SM.LeftLabel = 'off';
SM.BottomLabel = 'off';
SM.TopLabel = 'on';
SM.TopLabelFont = {'FontSize', 12, 'FontName', 'Times New Roman', 'Rotation', 45};
% 设置位置 -- set position
SM.XLim = [0,1];
SM.YLim = [0,1];
SM.TLim = [-pi/4,-pi/4];
SM.draw()
% 修饰句柄 -- decorate handles
% 清除热图下半部分 -- Clear the bottom half of the heatmap
tData = triu(ones(size(Data)),1);
tInd = find(tData(:) == 1);
for i = 1:length(tInd)
set(SM.heatmapHdl{tInd(i)}, 'Visible', 'off')
end
% 修饰坐标区域 -- decorate axes
set(gca, 'XColor', 'none', 'YColor', 'none',...
'DataAspectRatio', [1,1,1],'XLim', [-.15,1.45]);
CB = colorbar();
CB.Location = 'southoutside';
exportgraphics(fig1, '.\gallery\STree_SMatrix_demo3_1.png')
%% ========================================================================
fig2 = figure('Units', 'normalized', 'Position', [.05,.1,.6,.8], 'Color', 'w');
ST1.ax = gca;
SM.ax = gca;
% SM.Colormap = slanCM(141, 64);
% 每个类之间加入间隙 -- insert gap between each clust
ST1.ClustGap = 'on';
SM.ClustGap = 'on';
% see slanCMdisplay
% and Zhaoxu Liu / slandarer (2024). 200 colormap, MATLAB Central File Exchange.
% https://www.mathworks.com/matlabcentral/fileexchange/120088-200-colormap
SM.Colormap = slanCM(141, 64);
ST1.draw()
SM.draw()
% 修饰句柄 -- decorate handles
% 清除热图下半部分 -- Clear the bottom half of the heatmap
tData = triu(ones(size(Data)),1);
tInd = find(tData(:) == 1);
for i = 1:length(tInd)
set(SM.heatmapHdl{tInd(i)}, 'Visible', 'off')
end
% 修饰坐标区域 -- decorate axes
set(gca, 'XColor', 'none', 'YColor', 'none',...
'DataAspectRatio', [1,1,1],'XLim', [-.15,1.45]);
CB = colorbar();
CB.Location = 'southoutside';
exportgraphics(fig2, '.\gallery\STree_SMatrix_demo3_2.png')
工具函数完整代码
代码都是几百行,编写不易,点个赞叭!!!
3-1 STree 完整代码
classdef STree < handle
% Copyright (c) 2024, Zhaoxu Liu / slandarer
% =========================================================================
% @author : slandarer
% 公众号 : slandarer随笔
% 知乎 : slandarer
% -------------------------------------------------------------------------
% Zhaoxu Liu / slandarer (2024). STree dendrogram
% (https://www.mathworks.com/matlabcentral/fileexchange/160048-stree-dendrogram),
% MATLAB Central File Exchange.
properties
ax, Z, T, cutoff, CData, Parent, class, lineClass, hClass, H
MaxClust = 3;
Layout = 'rectangular'; % 'rectangular'(default) / 'rounded' / 'slanted'
% 'ellipse' / 'bezier'
Orientation = 'top'; % 'top' -- Top to bottom
% 'bottom' -- Bottom to top
% 'left' -- Left to right
% 'right' -- Right to left
oriXLim, oriYLim
XLim, YLim, TLim = [0,0];
ClustGap = 'off';
BranchColor = 'off';
BranchHighlight = 'off';
Label = 'on' ;
LabelColor = 'off'; % uncompleted
ClassHighlight = 'off';
ClassLabel = 'off';
branchHdl , sampleLabelHdl, classLabelHdl
branchHLTHdl, sampleHLTHdl , classHLTHdl
SampleName , ClassName , WTick
% 样本文本 类弧形内侧 类弧形外侧 类文本
RTick = [1+1/40, 1.22, 1.27, 1.35];
SampleFont = {'FontSize', 10, 'FontName', 'Times New Roman'};
ClassFont = {'FontSize', 14, 'FontName', 'Times New Roman', 'FontWeight', 'bold'};
lineSet, order, oriXSet, oriYSet, oriWSet, oriHSet, newXSet, newYSet, newWSet, newHSet
branchHLTXSet, sampleHLTXSet, classHLTXSet
branchHLTYSet, sampleHLTYSet, classHLTYSet
branchHLTWSet, sampleHLTWSet, classHLTWSet
branchHLTHSet, sampleHLTHSet, classHLTHSet
arginList = {'Parent', 'Layout', 'CData', 'XLim', 'YLim', 'TLim',...
'SampleName', 'ClassName', 'Orientation', 'MaxClust', 'RTick',...
'SampleFont', 'ClassFont', 'ClustGap', 'BranchColor', 'BranchHighlight',...
'Label', 'LabelColor', 'ClassHighlight', 'ClassLabel'};
end
% 构造函数 =================================================================
methods
function obj = STree(varargin)
% 获取基本数据 -------------------------------------------------
if isa(varargin{1}, 'matlab.graphics.axis.Axes')
obj.ax = varargin{1}; varargin(1) = [];
else
end
obj.Z = varargin{1}; varargin(1) = [];
% 获取其他信息 -------------------------------------------------
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
% 基础配色 -----------------------------------------------------
if isempty(obj.CData)
colorList = [204 61 36
243 197 88
109 174 144
48 180 204
0 79 122]./255;
N = size(colorList, 1);
colorList = colorList(mod((1:obj.MaxClust)-1, N)+1,:);
colorList = colorList.*(.9.^(floor(((1:obj.MaxClust)-1)./N).'));
obj.CData = colorList;
end
% 基础命名 -----------------------------------------------------
if isempty(obj.SampleName)
obj.SampleName = compose('slan%d', 1:(size(obj.Z,1)+10));
obj.ClassName = compose('Class-%c', 64 + (1:obj.MaxClust));
end
end
% Copyright (c) 2024, Zhaoxu Liu / slandarer
% =========================================================================
% @author : slandarer
% 公众号 : slandarer随笔
% 知乎 : slandarer
% -------------------------------------------------------------------------
% Zhaoxu Liu / slandarer (2024). STree dendrogram
% (https://www.mathworks.com/matlabcentral/fileexchange/160048-stree-dendrogram),
% MATLAB Central File Exchange.
function draw(obj)
obj.ax.NextPlot = 'add';
% 各类数据准备 =================================================
% 数据处理、绘制树状图、提取图形、关闭图窗 ------------------------
tempFigure = figure();
N = obj.MaxClust;
obj.T = cluster(obj.Z, 'maxclust', N);
obj.cutoff = median([obj.Z(end-(N-1),3), obj.Z(end-(N-2),3)]);
[obj.lineSet, ~, obj.order] = dendrogram(obj.Z, 0, 'Orientation', obj.Orientation);
obj.oriXSet = reshape([obj.lineSet(:).XData], 4, []).';
obj.oriYSet = reshape([obj.lineSet(:).YData], 4, []).';
if strcmpi(obj.Orientation, 'top') || strcmpi(obj.Orientation, 'bottom')
obj.oriWSet = obj.oriXSet; obj.oriHSet = obj.oriYSet;
else
obj.oriWSet = obj.oriYSet; obj.oriHSet = obj.oriXSet;
end
obj.class = obj.T(obj.order);
close(tempFigure)
% 计算高亮高度 -------------------------------------------------
WSet = [obj.oriWSet(:,1:2); obj.oriWSet(:,3:4)];
HSet = [obj.oriHSet(:,1:2); obj.oriHSet(:,3:4)];
BSet = (HSet(:,1)-obj.cutoff).*(HSet(:,2)-obj.cutoff)<0;
obj.H = (HSet(BSet,1)+HSet(BSet,2))./2;
obj.hClass = obj.class(round(WSet(BSet,1)));
% 预生成树枝配色 -----------------------------------------------
obj.lineClass = all(obj.oriHSet < obj.cutoff,2).*obj.class(round((obj.oriWSet(:,2)+obj.oriWSet(:,3))./2));
% 生成间隙 -----------------------------------------------------
gap = find(diff(obj.class)~=0)+.5;
obj.WTick = 1:length(obj.class);
if strcmpi(obj.ClustGap, 'on')
for i = length(gap):-1:1
obj.oriWSet(obj.oriWSet>gap(i)) = obj.oriWSet(obj.oriWSet>gap(i))+1;
obj.WTick(obj.WTick>gap(i)) = obj.WTick(obj.WTick>gap(i))+1;
end
end
obj.newWSet = [];
obj.newHSet = [];
% 修改树枝形状 =================================================
switch obj.Layout
case 'rectangular'
for i = 1:size(obj.oriWSet,1)
obj.newWSet(i,:) = [linspace(obj.oriWSet(i,1), obj.oriWSet(i,2), 30),...
linspace(obj.oriWSet(i,2), obj.oriWSet(i,3), 30),...
linspace(obj.oriWSet(i,3), obj.oriWSet(i,4), 30)];
obj.newHSet(i,:) = [linspace(obj.oriHSet(i,1), obj.oriHSet(i,2), 30),...
linspace(obj.oriHSet(i,2), obj.oriHSet(i,3), 30),...
linspace(obj.oriHSet(i,3), obj.oriHSet(i,4), 30)];
end
case 'rounded'
tX = [-1.*ones(1,15),...
cos(linspace(pi,pi/2,20)).*.3-.7,...
linspace(-.7,.7,15),...
cos(linspace(pi/2,0,20)).*.3+.7,...
1.*ones(1,15)];
tY = [linspace(0,.7,15),...
sin(linspace(pi,pi/2,20)).*.3+.7,...
1.*ones(1,15),...
sin(linspace(pi/2,0,20)).*.3+.7,...
linspace(.7,0,15)];
obj.newWSet = [obj.oriWSet(:,1),...
tX.*(obj.oriWSet(:,4)-obj.oriWSet(:,1))./2 + (obj.oriWSet(:,4)+obj.oriWSet(:,1))./2,...
obj.oriWSet(:,4)];
obj.newHSet = [obj.oriHSet(:,1),...
tY.*(obj.oriHSet(:,2)-max(obj.oriHSet(:,[1,4]), [], 2)) + max(obj.oriHSet(:,[1,4]), [], 2),...
obj.oriHSet(:,4)];
case 'slanted'
for i = 1:size(obj.oriWSet,1)
tWId = obj.Z(:,1:2) == (i+length(obj.class)); tW1 = [];
if all(tWId(:,1) == 0) && all(tWId(:,2) == 0)
tW = mean(obj.oriWSet(i,2:3));
elseif all(tWId(:,1) == 0)
tW1 = obj.oriWSet(tWId(:,2),1);
tW2 = obj.oriWSet(tWId(:,2),4);
elseif all(tWId(:,2) == 0)
tW1 = obj.oriWSet(tWId(:,1),1);
tW2 = obj.oriWSet(tWId(:,1),4);
end
if ~isempty(tW1)
if abs(tW1 - mean(obj.oriWSet(i,2:3))) > abs(tW2 - mean(obj.oriWSet(i,2:3)))
tW = tW2;
else
tW = tW1;
end
end
obj.newWSet(i,:) = [linspace(obj.oriWSet(i,1), tW, 30),...
linspace(tW, obj.oriWSet(i,4), 30)];
obj.newHSet(i,:) = [linspace(obj.oriHSet(i,1), mean(obj.oriHSet(i,2:3)), 30),...
linspace(mean(obj.oriHSet(i,2:3)), obj.oriHSet(i,4), 30)];
end
case 'ellipse'
tT = linspace(pi,0,30);
t01 = linspace(0,1,25);
obj.newWSet = [obj.oriWSet(:,1).*ones(1,25),...
cos(tT).*(obj.oriWSet(:,4)-obj.oriWSet(:,1))./2 + (obj.oriWSet(:,4)+obj.oriWSet(:,1))./2,...
obj.oriWSet(:,4).*ones(1,25)];
obj.newHSet = [obj.oriHSet(:,1) + t01.*(max(obj.oriHSet(:,[1,4]), [], 2) - obj.oriHSet(:,1)),...
sin(tT).*(obj.oriHSet(:,2)-max(obj.oriHSet(:,[1,4]), [], 2)) + max(obj.oriHSet(:,[1,4]), [], 2),...
max(obj.oriHSet(:,[1,4]), [], 2) + t01.*(obj.oriHSet(:,4) - max(obj.oriHSet(:,[1,4]), [], 2))];
case 'bezier'
obj.newWSet = zeros(size(obj.oriWSet,1), 60);
obj.newHSet = zeros(size(obj.oriHSet,1), 60);
for i = 1:size(obj.oriHSet,1)
pntsL = [obj.oriWSet(i,[1,2]),...
mean(obj.oriWSet(i,[2,3]));
obj.oriHSet(i,[1,2]),...
obj.oriHSet(i,2)].';
pntsR = [mean(obj.oriWSet(i,[2,3])),...
obj.oriWSet(i,[3,4]);
obj.oriHSet(i,[3,3]),...
obj.oriHSet(i,4)].';
pntsL = bezierCurve(pntsL, 30);
pntsR = bezierCurve(pntsR, 30);
obj.newWSet(i,:) = [pntsL(:,1).', pntsR(:,1).'];
obj.newHSet(i,:) = [pntsL(:,2).', pntsR(:,2).'];
end
end
% 高亮区域计算 -------------------------------------------------
classNum = unique(obj.class, 'stable');
for i = 1:obj.MaxClust
tX = [obj.WTick(find(obj.class == classNum(i), 1, 'first')) - .5,...
obj.WTick(find(obj.class == classNum(i), 1, 'last')) + .5];
obj.branchHLTWSet(i,:) = [linspace(tX(1), tX(2), 50), tX(2).*ones(1,50),...
linspace(tX(2), tX(1), 50), tX(1).*ones(1,50)];
obj.branchHLTHSet(i,:) = [obj.H(classNum(i) == obj.hClass).*ones(1,50),...
linspace(obj.H(classNum(i) == obj.hClass), 0, 50),...
zeros(1,50),...
linspace(0, obj.H(classNum(i) == obj.hClass), 50)];
obj.classHLTWSet(i,:) = [linspace(tX(1), tX(2), 50), tX(2).*ones(1,50),...
linspace(tX(2), tX(1), 50), tX(1).*ones(1,50)];
maxH = max(max(obj.oriHSet));
minH = min(min(obj.oriHSet));
diffH = maxH - minH;
obj.classHLTHSet(i,:) = [-diffH.*(obj.RTick(2)-1).*ones(1,50),...
linspace(-diffH.*(obj.RTick(2)-1), -diffH.*(obj.RTick(3)-1), 50),...
-diffH.*(obj.RTick(3)-1).*ones(1,50),...
linspace(-diffH.*(obj.RTick(3)-1), -diffH.*(obj.RTick(2)-1), 50)];
end
% 数据转换 =====================================================
if strcmpi(obj.Orientation, 'left')
maxH = max(max(obj.newHSet));
obj.newHSet = maxH - obj.newHSet;
obj.branchHLTHSet = maxH - obj.branchHLTHSet;
obj.classHLTHSet = maxH - obj.classHLTHSet;
elseif strcmpi(obj.Orientation, 'bottom')
obj.newHSet = - obj.newHSet;
obj.branchHLTHSet = - obj.branchHLTHSet;
obj.classHLTHSet = - obj.classHLTHSet;
end
if strcmpi(obj.Orientation, 'top') || strcmpi(obj.Orientation, 'bottom')
obj.newXSet = obj.newWSet; obj.newYSet = obj.newHSet;
obj.branchHLTXSet = obj.branchHLTWSet; obj.branchHLTYSet = obj.branchHLTHSet;
obj.classHLTXSet = obj.classHLTWSet; obj.classHLTYSet = obj.classHLTHSet;
else
obj.newXSet = obj.newHSet; obj.newYSet = obj.newWSet;
obj.branchHLTXSet = obj.branchHLTHSet; obj.branchHLTYSet = obj.branchHLTWSet;
obj.classHLTXSet = obj.classHLTHSet; obj.classHLTYSet = obj.classHLTWSet;
end
% 原始X,Y范围获取 ----------------------------------------------
if strcmp(obj.ClustGap,'on')
gap = 1;
else
gap = .5;
end
if strcmpi(obj.Orientation, 'top') || strcmpi(obj.Orientation, 'bottom')
obj.oriXLim = [min(min(obj.newXSet)) - gap, max(max(obj.newXSet)) + gap];
obj.oriYLim = [min(min(obj.newYSet)), max(max(obj.newYSet))];
else
obj.oriXLim = [min(min(obj.newXSet)), max(max(obj.newXSet))];
obj.oriYLim = [min(min(obj.newYSet)) - gap, max(max(obj.newYSet)) + gap];
end
% X,Y范围调整 --------------------------------------------------
if ~isempty(obj.XLim)
obj.newXSet = (obj.newXSet - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
obj.branchHLTXSet = (obj.branchHLTXSet - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
obj.classHLTXSet = (obj.classHLTXSet - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
else
obj.XLim = obj.oriXLim;
end
if ~isempty(obj.YLim)
obj.newYSet = (obj.newYSet - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.YLim) + obj.YLim(1);
obj.branchHLTYSet = (obj.branchHLTYSet - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.YLim) + obj.YLim(1);
obj.classHLTYSet = (obj.classHLTYSet - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.YLim) + obj.YLim(1);
else
obj.YLim = obj.oriYLim;
end
% 旋转 --------------------------------------------------------
if abs(obj.TLim(1)-obj.TLim(2)) < eps
rotateMat = [cos(obj.TLim(1)), -sin(obj.TLim(1));
sin(obj.TLim(1)), cos(obj.TLim(1))];
% 旋转树枝
tNewXYSet = rotateMat*[obj.newXSet(:).'; obj.newYSet(:).'];
obj.newXSet = reshape(tNewXYSet(1,:), size(obj.newXSet,1), []);
obj.newYSet = reshape(tNewXYSet(2,:), size(obj.newYSet,1), []);
% 旋转树枝高亮
tBranchHLTXYSet = rotateMat*[obj.branchHLTXSet(:).'; obj.branchHLTYSet(:).'];
obj.branchHLTXSet = reshape(tBranchHLTXYSet(1,:), size(obj.branchHLTXSet,1), []);
obj.branchHLTYSet = reshape(tBranchHLTXYSet(2,:), size(obj.branchHLTYSet,1), []);
% 旋转类高亮
tClassHLTXYSet = rotateMat*[obj.classHLTXSet(:).'; obj.classHLTYSet(:).'];
obj.classHLTXSet = reshape(tClassHLTXYSet(1,:), size(obj.classHLTXSet,1), []);
obj.classHLTYSet = reshape(tClassHLTXYSet(2,:), size(obj.classHLTYSet,1), []);
else
% 旋转树枝
tNewTSet = (obj.newYSet - obj.YLim(1))./diff(obj.YLim).*diff(obj.TLim) + obj.TLim(1);
tNewRSet = obj.newXSet;
obj.newXSet = cos(tNewTSet).*tNewRSet;
obj.newYSet = sin(tNewTSet).*tNewRSet;
% 旋转树枝高亮
tBranchHLTTSet = (obj.branchHLTYSet - obj.YLim(1))./diff(obj.YLim).*diff(obj.TLim) + obj.TLim(1);
tBranchHLTRSet = obj.branchHLTXSet;
obj.branchHLTXSet = cos(tBranchHLTTSet).*tBranchHLTRSet;
obj.branchHLTYSet = sin(tBranchHLTTSet).*tBranchHLTRSet;
% 旋转类高亮
tClassHLTTSet = (obj.classHLTYSet - obj.YLim(1))./diff(obj.YLim).*diff(obj.TLim) + obj.TLim(1);
tClassHLTRSet = obj.classHLTXSet;
obj.classHLTXSet = cos(tClassHLTTSet).*tClassHLTRSet;
obj.classHLTYSet = sin(tClassHLTTSet).*tClassHLTRSet;
end
if obj.TLim(1) ~= 0 || obj.TLim(2) ~= 0
obj.ax.DataAspectRatio = [1,1,1];
end
% 图形重绘 =====================================================
% 绘制树枝 -----------------------------------------------------
colorList = [obj.CData];
if strcmpi(obj.BranchColor, 'off')
colorList = colorList.*0;
end
for i = 1:obj.MaxClust
obj.branchHdl{i} = plot(obj.ax, obj.newXSet(classNum(i) == obj.lineClass,:).',...
obj.newYSet(classNum(i) == obj.lineClass,:).', 'Color', colorList(i,:), 'LineWidth', .8);
end
obj.branchHdl{obj.MaxClust+1} = plot(obj.ax, obj.newXSet(0 == obj.lineClass,:).',...
obj.newYSet(0 == obj.lineClass,:).', 'Color', [0,0,0], 'LineWidth', .7);
% 绘制树枝高亮 -------------------------------------------------
for i = 1:obj.MaxClust
obj.branchHLTHdl{i}=fill(obj.ax, obj.branchHLTXSet(i,:), obj.branchHLTYSet(i,:), obj.CData(i,:), 'EdgeColor', 'none', 'FaceAlpha', .25);
end
if strcmpi(obj.BranchHighlight,'off')
for i = 1:obj.MaxClust
set(obj.branchHLTHdl{i},'Visible','off');
end
end
% 绘制类高亮 ---------------------------------------------------
for i = 1:obj.MaxClust
obj.classHLTHdl{i}=fill(obj.ax, obj.classHLTXSet(i,:), obj.classHLTYSet(i,:), obj.CData(i,:), 'EdgeColor', 'none', 'FaceAlpha', .9);
end
if strcmpi(obj.ClassHighlight,'off')
for i = 1:obj.MaxClust
set(obj.classHLTHdl{i},'Visible','off');
end
end
% 绘制样本标签 -------------------------------------------------
if abs(obj.TLim(1)-obj.TLim(2)) < eps
rotateMat = [cos(obj.TLim(1)), -sin(obj.TLim(1));
sin(obj.TLim(1)), cos(obj.TLim(1))];
switch obj.Orientation
case 'left'
tY = (obj.WTick - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.YLim) + obj.YLim(1);
tX = ones(size(tY)).*abs(diff(obj.XLim)).*(obj.RTick(1)-1) + obj.XLim(2);
tXY = rotateMat*[tX;tY];
tT = obj.TLim(1)/pi*180;
if mod(tT,360)>90 && mod(tT,360)<270
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-180, 'HorizontalAlignment', 'right', obj.SampleFont{:});
end
else
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT, obj.SampleFont{:});
end
end
for i = 1:obj.MaxClust
tY = (mean(obj.WTick(obj.class == classNum(i))) - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.YLim) + obj.YLim(1);
tX = abs(diff(obj.XLim)).*(obj.RTick(4)-1) + obj.XLim(2);
tXY = rotateMat*[tX;tY];
if mod(tT,360)>180 && mod(tT,360)<360
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT+180-90, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
else
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT-90, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
end
end
% -----------------------------------------------------
case 'right'
tY = (obj.WTick - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.YLim) + obj.YLim(1);
tX = - ones(size(tY)).*abs(diff(obj.XLim)).*(obj.RTick(1)-1) + obj.XLim(1);
tXY = rotateMat*[tX;tY];
tT = obj.TLim(1)/pi*180;
if mod(tT,360)>90 && mod(tT,360)<270
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-180, obj.SampleFont{:});
end
else
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT, 'HorizontalAlignment', 'right', obj.SampleFont{:});
end
end
for i = 1:obj.MaxClust
tY = (mean(obj.WTick(obj.class == classNum(i))) - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.YLim) + obj.YLim(1);
tX = - abs(diff(obj.XLim)).*(obj.RTick(4)-1) + obj.XLim(1);
tXY = rotateMat*[tX;tY];
if mod(tT,360)>180 && mod(tT,360)<360
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT+180-90, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
else
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT-90, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
end
end
% -----------------------------------------------------
case 'top'
tX = (obj.WTick - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
tY = - ones(size(tX)).*abs(diff(obj.YLim)).*(obj.RTick(1)-1) + obj.YLim(1);
tXY = rotateMat*[tX;tY];
tT = obj.TLim(1)/pi*180;
if mod(tT,360)>180 && mod(tT,360)<360
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-90-180, 'HorizontalAlignment', 'right',obj.SampleFont{:});
end
else
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-90, obj.SampleFont{:});
end
end
for i = 1:obj.MaxClust
tX = (mean(obj.WTick(obj.class == classNum(i))) - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
tY = - abs(diff(obj.YLim)).*(obj.RTick(4)-1) + obj.YLim(1);
tXY = rotateMat*[tX;tY];
if mod(tT,360)>90 && mod(tT,360)<270
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT+180, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
else
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
end
end
% -----------------------------------------------------
case 'bottom'
tX = (obj.WTick - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
tY = ones(size(tX)).*abs(diff(obj.YLim)).*(obj.RTick(1)-1) + obj.YLim(2);
tXY = rotateMat*[tX;tY];
tT = obj.TLim(2)/pi*180;
if mod(tT,360)>180 && mod(tT,360)<360
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-90-180, obj.SampleFont{:});
end
else
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-90, 'HorizontalAlignment', 'right', obj.SampleFont{:});
end
end
for i = 1:obj.MaxClust
tX = (mean(obj.WTick(obj.class == classNum(i))) - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
tY = abs(diff(obj.YLim)).*(obj.RTick(4)-1) + obj.YLim(2);
tXY = rotateMat*[tX;tY];
if mod(tT,360)>90 && mod(tT,360)<270
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT+180, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
else
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
end
end
end
% =============================================================
else
rotateMat1 = [cos(obj.TLim(1)), -sin(obj.TLim(1));
sin(obj.TLim(1)), cos(obj.TLim(1))];
rotateMat2 = [cos(obj.TLim(2)), -sin(obj.TLim(2));
sin(obj.TLim(2)), cos(obj.TLim(2))];
tT3 = obj.TLim(1) - diff(obj.TLim).*(obj.RTick(4)-1);
tT4 = obj.TLim(2) + diff(obj.TLim).*(obj.RTick(4)-1);
rotateMat3 = [cos(tT3), -sin(tT3);
sin(tT3), cos(tT3)];
rotateMat4 = [cos(tT4), -sin(tT4);
sin(tT4), cos(tT4)];
tT3 = tT3/pi*180;
tT4 = tT4/pi*180;
switch obj.Orientation
case 'left'
tT = (obj.WTick - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.TLim) + obj.TLim(1);
tT = tT./pi.*180;
tR = ones(size(tT)).*abs(diff(obj.XLim)).*(obj.RTick(1)-1) + obj.XLim(2);
for i = 1:length(tT)
if mod(tT(i),360)<90 || mod(tT(i),360)>270
obj.sampleLabelHdl{i} = text(obj.ax, tR(i).*cos(tT(i)*pi/180), tR(i).*sin(tT(i)*pi/180), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT(i), obj.SampleFont{:});
else
obj.sampleLabelHdl{i} = text(obj.ax, tR(i).*cos(tT(i)*pi/180), tR(i).*sin(tT(i)*pi/180), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT(i)+180, 'HorizontalAlignment', 'right', obj.SampleFont{:});
end
end
for i = 1:obj.MaxClust
tT = (mean(obj.WTick(obj.class == classNum(i))) - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.TLim) + obj.TLim(1);
tT = tT./pi.*180;
tR = abs(diff(obj.XLim)).*(obj.RTick(4)-1) + obj.XLim(2);
if mod(tT,360)<180
obj.classLabelHdl{i} = text(obj.ax, tR.*cos(tT*pi/180), tR.*sin(tT*pi/180), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT-90, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
else
obj.classLabelHdl{i} = text(obj.ax, tR.*cos(tT*pi/180), tR.*sin(tT*pi/180), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT+90, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
end
end
% -----------------------------------------------------
case 'right'
tT = (obj.WTick - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.TLim) + obj.TLim(1);
tT = tT./pi.*180;
tR = - ones(size(tT)).*abs(diff(obj.XLim)).*(obj.RTick(1)-1) + obj.XLim(1);
for i = 1:length(tT)
if mod(tT(i),360)<90 || mod(tT(i),360)>270
obj.sampleLabelHdl{i} = text(obj.ax, tR(i).*cos(tT(i)*pi/180), tR(i).*sin(tT(i)*pi/180), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT(i), 'HorizontalAlignment', 'right', obj.SampleFont{:});
else
obj.sampleLabelHdl{i} = text(obj.ax, tR(i).*cos(tT(i)*pi/180), tR(i).*sin(tT(i)*pi/180), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT(i)+180, obj.SampleFont{:});
end
end
for i = 1:obj.MaxClust
tT = (mean(obj.WTick(obj.class == classNum(i))) - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.TLim) + obj.TLim(1);
tT = tT./pi.*180;
tR = - abs(diff(obj.XLim)).*(obj.RTick(4)-1) + obj.XLim(1);
if mod(tT,360)<180
obj.classLabelHdl{i} = text(obj.ax, tR.*cos(tT*pi/180), tR.*sin(tT*pi/180), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT-90, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
else
obj.classLabelHdl{i} = text(obj.ax, tR.*cos(tT*pi/180), tR.*sin(tT*pi/180), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT+90, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
end
end
% -----------------------------------------------------
case 'top'
tX = (obj.WTick - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
tY = - ones(size(tX)).*abs(diff(obj.YLim)).*(obj.RTick(1)-1);
tXY = rotateMat1*[tX;tY];
tT = obj.TLim(1)/pi*180;
if mod(tT,360)>180 && mod(tT,360)<360
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-90-180, 'HorizontalAlignment', 'right',obj.SampleFont{:});
end
else
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-90, obj.SampleFont{:});
end
end
for i = 1:obj.MaxClust
tX = (mean(obj.WTick(obj.class == classNum(i))) - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
tY = 0;
tXY = rotateMat3*[tX;tY];
if mod(tT3,360)>90 && mod(tT3,360)<270
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT3+180, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
else
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT3, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
end
end
% -----------------------------------------------------
case 'bottom'
abs(diff(obj.YLim))
tX = (obj.WTick - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
tY = ones(size(tX)).*abs(diff(obj.YLim)).*(obj.RTick(1)-1);
tXY = rotateMat2*[tX;tY];
tT = obj.TLim(2)/pi*180;
if mod(tT,360)>180 && mod(tT,360)<360
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-90-180, obj.SampleFont{:});
end
else
for i = 1:length(tX)
obj.sampleLabelHdl{i} = text(obj.ax, tXY(1,i), tXY(2,i), obj.SampleName{obj.order(i)},...
'FontSize', 12, 'Rotation', tT-90, 'HorizontalAlignment', 'right', obj.SampleFont{:});
end
end
for i = 1:obj.MaxClust
tX = (mean(obj.WTick(obj.class == classNum(i))) - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
tY = 0;
tXY = rotateMat4*[tX;tY];
if mod(tT4,360)>90 && mod(tT4,360)<270
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT4+180, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
else
obj.classLabelHdl{i} = text(obj.ax, tXY(1), tXY(2), obj.ClassName{i},...
'FontSize', 12, 'Rotation', tT4, 'Color', colorList(i,:), 'HorizontalAlignment', 'center', obj.ClassFont{:});
end
end
end
end
if strcmpi(obj.ClassLabel, 'off')
for i = 1:obj.MaxClust
set(obj.classLabelHdl{i}, 'Visible', 'off');
end
end
if strcmpi(obj.Label, 'off')
for i = 1:length(obj.sampleLabelHdl)
set(obj.sampleLabelHdl{i}, 'Visible', 'off')
end
end
axis tight
% 部分功能函数 -------------------------------------------------
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
end
% Copyright (c) 2024, Zhaoxu Liu / slandarer
% =========================================================================
% @author : slandarer
% 公众号 : slandarer随笔
% 知乎 : slandarer
% -------------------------------------------------------------------------
% Zhaoxu Liu / slandarer (2024). STree dendrogram
% (https://www.mathworks.com/matlabcentral/fileexchange/160048-stree-dendrogram),
% MATLAB Central File Exchange.
end
3-2 SMatrix 完整代码
classdef SMatrix < handle
% Copyright (c) 2024, Zhaoxu Liu / slandarer
% =========================================================================
% @author : slandarer
% 公众号 : slandarer随笔
% 知乎 : slandarer
% -------------------------------------------------------------------------
% Zhaoxu Liu / slandarer (2024). STree dendrogram
% (https://www.mathworks.com/matlabcentral/fileexchange/160048-stree-dendrogram),
% MATLAB Central File Exchange.
properties
ax, H, XLim, YLim, TLim = [0,0]
oriXLim, oriYLim, XSet, YSet, Colormap, Parent, sInd
LeftLabelFont = {'FontSize', 10, 'FontName', 'Times New Roman'}
RightLabelFont = {'FontSize', 10, 'FontName', 'Times New Roman'}
TopLabelFont = {'FontSize', 10, 'FontName', 'Times New Roman'}
BottomLabelFont = {'FontSize', 10, 'FontName', 'Times New Roman'}
RowOrder, RowClass, RowName
ColOrder, ColClass, ColName
heatmapHdl
maxH, ClustGap = 'off';
XTick, YTick
TopLabel = 'off'
BottomLabel = 'on'
LeftLabel = 'on'
RightLabel = 'off'
topLabelHdl, bottomLabelHdl, leftLabelHdl, rightLabelHdl
dfColor1=[0.9686 0.9882 0.9412; 0.9454 0.9791 0.9199; 0.9221 0.9700 0.8987; 0.8988 0.9609 0.8774;
0.8759 0.9519 0.8560; 0.8557 0.9438 0.8338; 0.8354 0.9357 0.8115; 0.8152 0.9276 0.7892;
0.7909 0.9180 0.7685; 0.7545 0.9039 0.7523; 0.7180 0.8897 0.7361; 0.6816 0.8755 0.7199;
0.6417 0.8602 0.7155; 0.5962 0.8430 0.7307; 0.5507 0.8258 0.7459; 0.5051 0.8086 0.7610;
0.4596 0.7873 0.7762; 0.4140 0.7620 0.7914; 0.3685 0.7367 0.8066; 0.3230 0.7114 0.8218;
0.2837 0.6773 0.8142; 0.2483 0.6378 0.7929; 0.2129 0.5984 0.7717; 0.1775 0.5589 0.7504;
0.1421 0.5217 0.7314; 0.1066 0.4853 0.7132; 0.0712 0.4488 0.6950; 0.0358 0.4124 0.6768;
0.0314 0.3724 0.6364; 0.0314 0.3319 0.5929; 0.0314 0.2915 0.5494; 0.0314 0.2510 0.5059]
dfColor2=[0.6196 0.0039 0.2588; 0.6892 0.0811 0.2753; 0.7588 0.1583 0.2917; 0.8283 0.2354 0.3082;
0.8706 0.2966 0.2961; 0.9098 0.3561 0.2810; 0.9490 0.4156 0.2658; 0.9660 0.4932 0.2931;
0.9774 0.5755 0.3311; 0.9887 0.6577 0.3690; 0.9930 0.7266 0.4176; 0.9943 0.7899 0.4707;
0.9956 0.8531 0.5238; 0.9968 0.9020 0.5846; 0.9981 0.9412 0.6503; 0.9994 0.9804 0.7161;
0.9842 0.9937 0.7244; 0.9526 0.9810 0.6750; 0.9209 0.9684 0.6257; 0.8721 0.9486 0.6022;
0.7975 0.9183 0.6173; 0.7228 0.8879 0.6325; 0.6444 0.8564 0.6435; 0.5571 0.8223 0.6448;
0.4698 0.7881 0.6460; 0.3868 0.7461 0.6531; 0.3211 0.6727 0.6835; 0.2553 0.5994 0.7139;
0.2016 0.5261 0.7378; 0.2573 0.4540 0.7036; 0.3130 0.3819 0.6694; 0.3686 0.3098 0.6353]
arginList = {'Parent', 'Layout', 'Colormap', 'XLim', 'YLim', 'TLim',...
'RowName', 'ColName', 'Font', 'Parent',...
'RowOrder', 'RowClass', 'RowName', 'ColOrder', 'ColClass', 'ColName',...
'TopLabelFont' , 'BottomLabelFont', 'LeftLabelFont', 'RightLabelFont',...
'ClustGap'};
end
methods
function obj = SMatrix(varargin)
% 获取基本数据 -------------------------------------------------
if isa(varargin{1}, 'matlab.graphics.axis.Axes')
obj.ax = varargin{1}; varargin(1) = [];
else
end
obj.H = varargin{1}; varargin(1) = [];
% 获取其他信息 -------------------------------------------------
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.maxH=max(max(abs(obj.H)));
% 设置配色 ----------------------------------------------------
if isempty(obj.Colormap)
if any(any(obj.H<0))
obj.Colormap=obj.dfColor2;
% tX=linspace(0,1,size(obj.Colormap,1));
% tXi=linspace(0,1,256);
% tR=interp1(tX,obj.Colormap(:,1),tXi);
% tG=interp1(tX,obj.Colormap(:,2),tXi);
% tB=interp1(tX,obj.Colormap(:,3),tXi);
% obj.Colormap=[tR(:),tG(:),tB(:)];
else
obj.Colormap=obj.dfColor1(end:-1:1,:);
end
end
% 分类情况 -----------------------------------------------------
if isempty(obj.RowClass), obj.RowClass = ones(1, size(obj.H, 1)); end
if isempty(obj.ColClass), obj.ColClass = ones(1, size(obj.H, 2)); end
if isempty(obj.RowOrder), obj.RowOrder = 1:size(obj.H, 1); end
if isempty(obj.ColOrder), obj.ColOrder = 1:size(obj.H, 2); end
if isempty(obj.RowName), obj.RowName = compose('row%d', 1:size(obj.H, 1)); end
if isempty(obj.ColName), obj.ColName = compose('col%d', 1:size(obj.H, 2)); end
end
function draw(obj)
obj.ax.NextPlot = 'add';
% 配色设置 -----------------------------------------------------
colormap(obj.ax, obj.Colormap)
colorbar(obj.ax)
if any(any(obj.H < 0))
try caxis(obj.ax, obj.maxH.*[-1,1]), catch, end
try clim(obj.ax, obj.maxH.*[-1,1]), catch, end
else
try caxis(obj.ax, obj.maxH.*[0,1]), catch,end
try clim(obj.ax, obj.maxH.*[0,1]), catch,end
end
% 原始X,Y范围获取 ----------------------------------------------
gapRow = find(diff(obj.RowClass)~=0)+.5;
gapCol = find(diff(obj.ColClass)~=0)+.5;
obj.XSet = 1:size(obj.H, 2);
obj.YSet = 1:size(obj.H, 1);
if strcmpi(obj.ClustGap, 'on')
for i = length(gapRow):-1:1
obj.YSet(obj.YSet>gapRow(i)) = obj.YSet(obj.YSet>gapRow(i))+1;
end
for i = length(gapCol):-1:1
obj.XSet(obj.XSet>gapCol(i)) = obj.XSet(obj.XSet>gapCol(i))+1;
end
end
if abs(obj.TLim(1)-obj.TLim(2)) < eps
obj.XTick = [obj.XSet(1)-.75, obj.XSet, obj.XSet(end)+.75];
obj.YTick = [obj.YSet(1)-.75, obj.YSet, obj.YSet(end)+.75];
else
obj.XTick = [obj.XSet(1)-.75, obj.XSet, obj.XSet(end)+.75];
obj.YTick = [obj.YSet(1)-.5, obj.YSet, obj.YSet(end)+.5];
end
if strcmp(obj.ClustGap,'on')
gap = 1;
else
gap = .5;
end
obj.oriXLim = [1 - gap, max(max(obj.XSet)) + gap];
obj.oriYLim = [1 - gap, max(max(obj.YSet)) + gap];
if isempty(obj.XLim), obj.XLim = obj.oriXLim; end
if isempty(obj.YLim), obj.YLim = obj.oriYLim; end
% 坐标放缩 -----------------------------------------------------
obj.sInd = reshape(1: size(obj.H, 2)*size(obj.H, 1), size(obj.H));
baseX = [linspace(-1,1,30), ones(1,30), linspace(1,-1,30), -ones(1,30)].*.5;
baseY = [-ones(1,30), linspace(-1,1,30), ones(1,30), linspace(1,-1,30)].*.5;
[obj.XSet, obj.YSet] = meshgrid(obj.XSet, obj.YSet);
obj.XSet = obj.XSet(:) + baseX;
obj.YSet = obj.YSet(:) + baseY;
obj.XSet = (obj.XSet - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
obj.YSet = (obj.YSet - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.YLim) + obj.YLim(1);
obj.XTick = (obj.XTick - obj.oriXLim(1))./diff(obj.oriXLim).*diff(obj.XLim) + obj.XLim(1);
obj.YTick = (obj.YTick - obj.oriYLim(1))./diff(obj.oriYLim).*diff(obj.YLim) + obj.YLim(1);
% 坐标旋转 -----------------------------------------------------
[XTick_B, YTick_B] = rotate(obj.XTick(2:end-1), obj.YTick(1) + obj.XTick(2:end-1).*0, obj.YLim, obj.TLim);
[XTick_T, YTick_T] = rotate(obj.XTick(2:end-1), obj.YTick(end) + obj.XTick(2:end-1).*0, obj.YLim, obj.TLim);
[XTick_L, YTick_L] = rotate(obj.XTick(1) + obj.YTick(2:end-1).*0, obj.YTick(2:end-1), obj.YLim, obj.TLim);
[XTick_R, YTick_R] = rotate(obj.XTick(end) + obj.YTick(2:end-1).*0, obj.YTick(2:end-1), obj.YLim, obj.TLim);
[obj.XSet, obj.YSet] = rotate(obj.XSet, obj.YSet, obj.YLim, obj.TLim);
function [X2,Y2] = rotate(X1,Y1,YLim,TLim)
if abs(TLim(1)-TLim(2)) < eps
rotateMat = [cos(TLim(1)), -sin(TLim(1));
sin(TLim(1)), cos(TLim(1))];
tXYSet = rotateMat*[X1(:).'; Y1(:).'];
X2 = reshape(tXYSet(1,:), size(X1,1), []);
Y2 = reshape(tXYSet(2,:), size(Y1,1), []);
else
tTSet = (Y1 - YLim(1))./diff(YLim).*diff(TLim) + TLim(1);
tRSet = X1;
X2 = cos(tTSet).*tRSet;
Y2 = sin(tTSet).*tRSet;
end
end
if obj.TLim(1) ~= 0 || obj.TLim(2) ~= 0
obj.ax.DataAspectRatio = [1,1,1];
end
% 图形绘制 -----------------------------------------------------
HH = obj.H(obj.RowOrder, obj.ColOrder);
for i = 1:size(obj.XSet,1)
obj.heatmapHdl{i} = fill(obj.ax, obj.XSet(i,:), obj.YSet(i,:), HH(i == obj.sInd),'EdgeColor','w','LineWidth',.5);
end
if abs(obj.TLim(1)-obj.TLim(2)) < eps
tT = obj.TLim(1)/pi*180;
for i = 1:length(XTick_B)
if mod(tT,360)>45 && mod(tT,360)<225
obj.bottomLabelHdl{i}=text(obj.ax, XTick_B(i), YTick_B(i), [obj.ColName{obj.ColOrder(i)}], 'FontSize', 12,...
'Rotation', tT+45+180, 'HorizontalAlignment', 'left', obj.BottomLabelFont{:});
else
obj.bottomLabelHdl{i}=text(obj.ax, XTick_B(i), YTick_B(i), [obj.ColName{obj.ColOrder(i)}], 'FontSize', 12,...
'Rotation', 45+tT, 'HorizontalAlignment', 'right', obj.BottomLabelFont{:});
end
end
for i = 1:length(XTick_T)
if mod(tT,360)>45 && mod(tT,360)<225
obj.topLabelHdl{i}=text(obj.ax, XTick_T(i), YTick_T(i), [obj.ColName{obj.ColOrder(i)}], 'FontSize', 12,...
'Rotation', tT+45+180, 'HorizontalAlignment', 'right', obj.TopLabelFont{:});
else
obj.topLabelHdl{i}=text(obj.ax, XTick_T(i), YTick_T(i), [obj.ColName{obj.ColOrder(i)}], 'FontSize', 12,...
'Rotation', 45+tT, 'HorizontalAlignment', 'left', obj.TopLabelFont{:});
end
end
for i = 1:length(XTick_L)
if mod(tT,360)>90 && mod(tT,360)<270
obj.leftLabelHdl{i}=text(obj.ax, XTick_L(i), YTick_L(i), [obj.RowName{obj.RowOrder(i)}], 'FontSize', 12,...
'Rotation', tT+180, 'HorizontalAlignment', 'left', obj.LeftLabelFont{:});
else
obj.leftLabelHdl{i}=text(obj.ax, XTick_L(i), YTick_L(i), [obj.RowName{obj.RowOrder(i)}], 'FontSize', 12,...
'Rotation', tT, 'HorizontalAlignment', 'right', obj.LeftLabelFont{:});
end
end
for i = 1:length(XTick_R)
if mod(tT,360)>90 && mod(tT,360)<270
obj.rightLabelHdl{i}=text(obj.ax, XTick_R(i), YTick_R(i), [obj.RowName{obj.RowOrder(i)}], 'FontSize', 12,...
'Rotation', tT+180, 'HorizontalAlignment', 'right', obj.RightLabelFont{:});
else
obj.rightLabelHdl{i}=text(obj.ax, XTick_R(i), YTick_R(i), [obj.RowName{obj.RowOrder(i)}], 'FontSize', 12,...
'Rotation', tT, 'HorizontalAlignment', 'left', obj.RightLabelFont{:});
end
end
else
tT1 = obj.TLim(1)/pi*180;
tT2 = obj.TLim(2)/pi*180;
for i = 1:length(XTick_B)
if mod(tT1,360)>180
obj.bottomLabelHdl{i}=text(obj.ax, XTick_B(i), YTick_B(i), [' ',obj.ColName{obj.ColOrder(i)},' '], 'FontSize', 12,...
'Rotation', tT1+90, 'HorizontalAlignment', 'right', obj.BottomLabelFont{:});
else
obj.bottomLabelHdl{i}=text(obj.ax, XTick_B(i), YTick_B(i), [' ',obj.ColName{obj.ColOrder(i)},' '], 'FontSize', 12,...
'Rotation', tT1-90, 'HorizontalAlignment', 'left', obj.BottomLabelFont{:});
end
end
for i = 1:length(XTick_T)
if mod(tT2,360)>180
obj.topLabelHdl{i}=text(obj.ax, XTick_T(i), YTick_T(i), [' ',obj.ColName{obj.ColOrder(i)},' '], 'FontSize', 12,...
'Rotation', tT2+90, 'HorizontalAlignment', 'left', obj.TopLabelFont{:});
else
obj.topLabelHdl{i}=text(obj.ax, XTick_T(i), YTick_T(i), [' ',obj.ColName{obj.ColOrder(i)},' '], 'FontSize', 12,...
'Rotation', tT2-90, 'HorizontalAlignment', 'right', obj.TopLabelFont{:});
end
end
tT = (obj.YTick(2:end-1) - obj.YLim(1))./diff(obj.YLim).*diff(obj.TLim) + obj.TLim(1);
tT = tT./pi.*180;
RR = obj.XTick(end);
RL = obj.XTick(1);
for i = 1:length(tT)
if mod(tT(i),360)<90 || mod(tT(i),360)>270
obj.leftLabelHdl{i} = text(obj.ax, RL.*cos(tT(i)*pi/180), RL.*sin(tT(i)*pi/180), [obj.RowName{obj.RowOrder(i)}],...
'FontSize', 12, 'Rotation', tT(i), 'HorizontalAlignment', 'right', obj.LeftLabelFont{:});
else
obj.leftLabelHdl{i} = text(obj.ax, RL.*cos(tT(i)*pi/180), RL.*sin(tT(i)*pi/180), [obj.RowName{obj.RowOrder(i)}],...
'FontSize', 12, 'Rotation', tT(i)+180, obj.LeftLabelFont{:});
end
end
for i = 1:length(tT)
if mod(tT(i),360)<90 || mod(tT(i),360)>270
obj.rightLabelHdl{i} = text(obj.ax, RR.*cos(tT(i)*pi/180), RR.*sin(tT(i)*pi/180), [obj.RowName{obj.RowOrder(i)}],...
'FontSize', 12, 'Rotation', tT(i), obj.RightLabelFont{:});
else
obj.rightLabelHdl{i} = text(obj.ax, RR.*cos(tT(i)*pi/180), RR.*sin(tT(i)*pi/180), [obj.RowName{obj.RowOrder(i)}],...
'FontSize', 12, 'Rotation', tT(i)+180, 'HorizontalAlignment', 'right', obj.RightLabelFont{:});
end
end
end
if strcmpi(obj.TopLabel,'off'),for i = 1:length(obj.topLabelHdl), set(obj.topLabelHdl{i}, 'Visible', 'off'); end, end
if strcmpi(obj.BottomLabel,'off'),for i = 1:length(obj.bottomLabelHdl), set(obj.bottomLabelHdl{i}, 'Visible', 'off'); end, end
if strcmpi(obj.LeftLabel,'off'),for i = 1:length(obj.leftLabelHdl), set(obj.leftLabelHdl{i}, 'Visible', 'off'); end, end
if strcmpi(obj.RightLabel,'off'),for i = 1:length(obj.rightLabelHdl), set(obj.rightLabelHdl{i}, 'Visible', 'off'); end, end
end
end
% Copyright (c) 2024, Zhaoxu Liu / slandarer
% =========================================================================
% @author : slandarer
% 公众号 : slandarer随笔
% 知乎 : slandarer
% -------------------------------------------------------------------------
% Zhaoxu Liu / slandarer (2024). STree dendrogram
% (https://www.mathworks.com/matlabcentral/fileexchange/160048-stree-dendrogram),
% MATLAB Central File Exchange.
end
完
未经允许本代码请勿作商业用途,引用的话可以引用我file exchange上的链接,可使用如下格式:
Zhaoxu Liu / slandarer (2024). STree dendrogram (https://www.mathworks.com/matlabcentral/fileexchange/160048-stree-dendrogram), MATLAB Central File Exchange. Retrieved February 23, 2024.
若转载请保留以上file exchange链接及本文链接!!!!!
本文全部代码已同时上传gitee仓库,若懒得一一获取代码和工具,可以去以下gitee仓库获取全部代码:
- https://gitee.com/slandarer/matlab-stree-dendrogram