当前位置: 首页 > news >正文

MATLAB | 绘图复刻(二十一)| 扇形热图+小提琴图

前段时间在小红书刷到了一个很有特色的热力图,由大佬@滚筒洗衣机创作,感觉很有意思,尝试 MATLAB 复刻:

作者使用的是 python 代码,赶快去瞅瞅。

复刻效果

正文部分

0.数据准备

数据需要一个用来画热图的矩阵以及一个和矩阵相同列数的数组或者元胞数组。例如:

% 例子1 随便构造数据
% 随便构造的数据,可以换成自己的数据
clc; clear; rng(4)
Data = rand([7, 12]) + 1 + sin(linspace(0, 2*pi, 12) - pi/1.2) + (1:7).'./12;
Data = Data./max(max(Data));% 绘制小提琴图的数据,应为列数与 Data 相同的矩阵或元胞数组
VData = mean(Data, 1) + randn([50, size(Data, 2)]).*.6;

这组数据画出来大概长这样:

此外假如我有两行数据,一行是日期,一行是数值,我们也可以直接将这组数据进行处理,统计其均值来画热图,然后按照每个月的数据分类画小提琴图,例如我们让 chatGPT 生成一组数据:

% 对chatGPT:
% 使用matlab构造一组数据,为2018第一天到2024年最后一天的数据,要求具有季节性clc;clear
% 创建日期向量
t = datetime(2018,1,1):days(1):datetime(2024,12,31);
n = length(t);% 将日期转换为一年中的位置(1 到 365/366)
day_of_year = day(t, 'dayofyear');
year_length = year(t);  % 判断闰年时有用
is_leap = eomday(year(t),2) == 29;% 构造季节性数据:例如正弦函数,每年重复一次(周期365)
% 基本模式:sin(2*pi * day_of_year / 365)
% 添加噪声 + 趋势(可选)
seasonal = 10 * sin(2*pi * day_of_year ./ 365);   % 季节性(年周期)
noise = randn(1, n).*5;                           % 噪声
trend = 0.01 * (1:n);                             % 微小上升趋势% 最终数据
v = seasonal + noise + trend;% 可视化
plot(t, v)
xlabel('Date')
ylabel('Value')
title('Synthetic Seasonal Data (2018–2024)')Data.t = t;
Data.v = v;
save test.mat Data

这组数据大概长这样:

我们读取存储的数据并进行统计:

% 例子2 已有各年份每一天数据,对其进行统计
clc; clear
testData = load('test.mat');
t = testData.Data.t;
v = testData.Data.v;
y = 2024:-1:2018;
% 构造一个矩阵,第 i 行第 j 列是第 i 年第 j 个月的数值平均值
Data = zeros(length(y), 12);
for i = 1:length(y)for m = 1:12idx = (year(t) == y(i)) & (month(t) == m);Data(i, m) = mean(v(idx));end
end
% 构造一个元胞数组,第 i 个元胞是 i 月全部数值合集
VData{12} = [];
for m = 1:12idx = (month(t) == m);VData{m} = v(idx)';
end

这组数据画出图来形状如下:

当然还需要定义行标签和列标签:

% 矩阵每行名称
% rowName = {'2024','2023','2022','2021',2020','2019','2018'};
rowName = compose('%d',2024:-1:2018);% 矩阵每列名称
colName = {'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', ...'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'};

1.基础样式

基本不太用动,数据范围及刻度位置不设置的话会自动计算,那个被注释掉的配色就是我尝试仿照原作者弄的配色:

% 标签格式
fontProp = {'FontSize',16, 'FontName','Times New Roman'};% 配色
CMap = 'summer';
% CMap = interp1([0,.5,1], [214,115,144;255,238,234;107,152,191]./255, 0:.01:1);% 小提琴图宽度
width = 0.9;% 数据范围,以及刻度位置
% VLim = [0, 1];
% VTick = 0:.2:1;
VLim = [];
VTick = [];


完整代码

因为绘图部分比较长,我这里直接放一下带着前面数据定义的完整代码得了:

% ----------------------------------------------------------------------
% 例子1 随便构造数据
% 随便构造的数据,可以换成自己的数据
clc; clear; rng(4)
Data = rand([7, 12]) + 1 + sin(linspace(0, 2*pi, 12) - pi/1.2) + (1:7).'./12;
Data = Data./max(max(Data));% 绘制小提琴图的数据,应为列数与 Data 相同的矩阵或元胞数组
VData = mean(Data, 1) + randn([50, size(Data, 2)]).*.6;% ----------------------------------------------------------------------
% % 例子2 已有各年份每一天数据,对其进行统计
% clc; clear
% testData = load('test.mat');
% t = testData.Data.t;
% v = testData.Data.v;
% y = 2024:-1:2018;
% % 构造一个矩阵,第 i 行第 j 列是第 i 年第 j 个月的数值平均值
% Data = zeros(length(y), 12);
% for i = 1:length(y)
%     for m = 1:12
%         idx = (year(t) == y(i)) & (month(t) == m);
%         Data(i, m) = mean(v(idx));
%     end
% end
% % 构造一个元胞数组,第 i 个元胞是 i 月全部数值合集
% VData{12} = [];
% for m = 1:12
%     idx = (month(t) == m);
%     VData{m} = v(idx)';
% end% 矩阵每行名称
% rowName = {'2024','2023','2022','2021',2020','2019','2018'};
rowName = compose('%d',2024:-1:2018);% 矩阵每列名称
colName = {'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', ...'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'};%% 格式设置 ===============================================================
% 标签格式
fontProp = {'FontSize',16, 'FontName','Times New Roman'};% 配色
CMap = 'summer';
% CMap = interp1([0,.5,1], [214,115,144;255,238,234;107,152,191]./255, 0:.01:1);% 小提琴图宽度
width = 0.9;% 数据范围,以及刻度位置
% VLim = [0, 1];
% VTick = 0:.2:1;
VLim = [];
VTick = [];%% 数据预处理 ============================================================
% 如果不设置 VLim, VTick
% 则自动计算一个比较合理的刻度位置
linearTickCompactDegree = 5;
if isempty(VLim)if iscell(VData)VLim = [min(min(min(Data)), min(cellfun(@min, VData)))...max(max(max(Data)), max(cellfun(@max, VData)))];elseVLim = [min(min(min(Data)), min(min(VData)))...max(max(max(Data)), max(max(VData)))];end
end
if isempty(VTick)tXS = diff(VLim) / linearTickCompactDegree;tXN = ceil(log(tXS) / log(10));tXS = round(round(tXS / 10^(tXN-2)) / 5) * 5 * 10^(tXN-2);tVTick1 = 0:-tXS:VLim(1);tVTick2 = 0:tXS:VLim(2);VTick = unique([tVTick1, tVTick2]);VTick(VTick < VLim(1)) = [];VTick(VTick > VLim(2)) = [];
end%% 绘图部分代码 ============================================================
% 构造图窗及坐标区域
fig = figure('Units','normalized', 'Position',[.1,.1,.6,.8], 'Color','w');
ax = axes('Parent',fig, 'XLim',[-sqrt(3), sqrt(3)], 'YLim',[0, 2], ...'DataAspectRatio',[1,1,1], 'Position',[.05,.2,.9,.8], ...'NextPlot','add', 'XColor','none', 'YColor','none');
colormap(CMap)% 绘制射线
w = 1*pi/3/size(Data, 2);
tt = linspace(5*pi/6 - w, pi/6 + w, size(Data, 2));
xx = cos(tt).*2; xx = [xx; xx.*0; xx.*nan(1)];
yy = sin(tt).*2; yy = [yy; yy.*0; yy.*nan(1)];
plot(ax, xx(:), yy(:), 'LineWidth',1, 'Color',[1,1,1].*.8, 'LineStyle','--')% 绘制列标签
for i = 1:length(colName)text(ax, cos(tt(i)).*2.01, sin(tt(i)).*2.01, colName(i), ...'HorizontalAlignment','center', 'VerticalAlignment', 'bottom', ...'Rotation', tt(i)/pi*180 - 90, fontProp{:})
end% 绘制行名称标签
for i = 1:length(rowName)r = 2/5 + (size(Data, 1) - i + .5)*4/size(Data, 1)/5;if mod(i, 2) == 1text(ax, cos(5*pi/6).*r - 1/100, sin(5*pi/6).*r - sqrt(3)/100, rowName{i}, ...'HorizontalAlignment','right', 'Rotation',60, fontProp{:})elsetext(ax, cos(pi/6).*r + 1/100, sin(pi/6).*r - sqrt(3)/100, rowName{i}, ...'HorizontalAlignment','left', 'Rotation',-60, fontProp{:})end
end% 绘制刻度轴线
plot(ax, cos(5*pi/6).*[6/5 + 1/10, 10/5 - 1/10], ...sin(5*pi/6).*[6/5 + 1/10, 10/5 - 1/10], 'LineWidth',1, 'Color','k')
plot(ax, cos(pi/6).*[6/5 + 1/10, 10/5 - 1/10], ...sin(pi/6).*[6/5 + 1/10, 10/5 - 1/10], 'LineWidth',1, 'Color','k')% 绘制刻度和刻度标签
for i = 1:length(VTick)r = (VTick(i) - VLim(1))./diff(VLim).*(3/5) + (6/5 + 1/10);x1 = [cos(5*pi/6).*r, cos(5*pi/6).*r + 1/100];y1 = [sin(5*pi/6).*r, sin(5*pi/6).*r + sqrt(3)/100];plot(ax, x1, y1, 'LineWidth',1, 'Color','k')x2 = [cos(pi/6).*r, cos(pi/6).*r - 1/100];y2 = [sin(pi/6).*r, sin(pi/6).*r + sqrt(3)/100];plot(ax, x2, y2, 'LineWidth',1, 'Color','k')if mod(length(VTick) - i, 2) == 0text(ax, cos(5*pi/6).*r - 1/100, sin(5*pi/6).*r - sqrt(3)/100, num2str(VTick(i)), ...'HorizontalAlignment','right', 'Rotation',60, fontProp{:})elsetext(ax, cos(pi/6).*r + 1/100, sin(pi/6).*r - sqrt(3)/100, num2str(VTick(i)), ...'HorizontalAlignment','left', 'Rotation',-60, fontProp{:})end
end% 绘制小提琴图
maxf = 0;
for i = 1:size(Data, 2)if iscell(VData)tY = VData{i};elsetY = VData(:,i);endtY(isnan(tY)) = [];[f, yi] = ksdensity(tY);maxf = max(maxf, max(f));
end
for i = 1:size(Data, 2)if iscell(VData)tY = VData{i};elsetY = VData(:,i);endtY(isnan(tY)) = [];[f, yi] = ksdensity(tY);yyi = [min(tY), yi(yi<max(tY) & yi>min(tY)), max(tY)];ind1 = find(yi<max(tY) & yi>min(tY), 1, 'first');ind2 = find(yi<max(tY) & yi>min(tY), 1, 'last');f1 = interp1(yi((ind1 - 1):ind1), f((ind1 - 1):ind1), min(tY));f2 = interp1(yi(ind2:(ind2 + 1)), f(ind2:(ind2 + 1)), max(tY));ff = [f1, f(yi<max(tY) & yi>min(tY)), f2];xx = [ff, -ff(end:-1:1)]./maxf.*(4*pi/5/size(Data, 2)).*width./2;yy = ([yyi, yyi(end:-1:1)] - VLim(1))./diff(VLim).*3./5 + 6/5 + 1/10;xy = [cos(tt(i) - pi/2), - sin(tt(i) - pi/2);sin(tt(i) - pi/2), cos(tt(i) - pi/2)]*[xx; yy];% 绘制小提琴fill(ax, xy(1,:), xy(2,:), mean(tY), 'EdgeColor',[0,0,0], 'LineWidth',1)qt25 = quantile(tY, 0.25);qt75 = quantile(tY, 0.75);med = median(tY);ind3 = find(yi < qt25, 1, 'last');ind4 = find(yi < qt75, 1, 'last');ind5 = find(yi < med, 1, 'last');f3 = interp1(yi(ind3:(ind3 + 1)), f(ind3:(ind3 + 1)), qt25);f4 = interp1(yi(ind4:(ind4 + 1)), f(ind4:(ind4 + 1)), qt75);f5 = interp1(yi(ind5:(ind5 + 1)), f(ind5:(ind5 + 1)), med);xx = [f3, -f3, f4, -f4, f5, -f5]./maxf.*(4*pi/5/size(Data, 2)).*width./2;yy = ([qt25, qt25, qt75, qt75, med, med] - VLim(1))./diff(VLim).*3./5 + 6/5 + 1/10;xy = [cos(tt(i) - pi/2), - sin(tt(i) - pi/2);sin(tt(i) - pi/2), cos(tt(i) - pi/2)]*[xx; yy];% 绘制四分位线和中位线plot(ax, xy(1,1:2), xy(2,1:2), 'LineWidth',1, 'Color','k')plot(ax, xy(1,3:4), xy(2,3:4), 'LineWidth',1, 'Color','k')plot(ax, xy(1,5:6), xy(2,5:6), 'LineWidth',2, 'Color','k')
end% 绘制热图
TT = linspace(5*pi/6, pi/6, size(Data, 2) + 1);
for i = 1:size(Data, 1)for j = 1:size(Data, 2)tt = linspace(TT(j), TT(j + 1), 30);r1 = 2/5 + (i - 1)*4/size(Data, 1)/5;r2 = 2/5 + i*4/size(Data, 1)/5;xx = [cos(tt).*r1, cos(tt(end:-1:1)).*r2];yy = [sin(tt).*r1, sin(tt(end:-1:1)).*r2];fill(ax, xx, yy, Data(i,j), 'EdgeColor','w', 'LineWidth',1)end
end% 绘制最上方弧线
tt = linspace(5*pi/6, pi/6, 80);
xx = cos(tt).*2;
yy = sin(tt).*2;
plot(ax, xx(:), yy(:), 'LineWidth',1, 'Color','k')colorbar(ax, 'Position',[.5-.01,.1,.02,.2], fontProp{:});

以上即为完整代码,数据和完整代码已经放在以下gitee仓库:

  • https://gitee.com/slandarer/PLTreprint
http://www.lryc.cn/news/582256.html

相关文章:

  • Spring AOP 设计解密:代理对象生成、拦截器链调度与注解适配全流程源码解析
  • 网络安全之重放攻击:原理、危害与防御之道
  • 指尖上的魔法:优雅高效的Linux命令手册
  • Spring Boot 操作 Redis 时 KeySerializer 和 HashKeySerializer 有什么区别?
  • 自动驾驶基本结构与组成
  • 【MyBatis】XML实现,配置方法和增、删、改、查
  • 第二届云计算与大数据国际学术会议(ICCBD 2025)
  • 物联网技术的关键技术与区块链发展趋势的深度融合分析
  • React Native 基础组件详解<一>
  • VSCODE创建JS项目
  • 常见问题与最佳实践——AI教你学Docker
  • 【力扣(LeetCode)】数据挖掘面试题0002:当面对实时数据流时您如何设计和实现机器学习模型?
  • EPLAN 电气制图:项目的创建(多功能天车系统案例)
  • 摄影后期:使用Photoshop进行暗角控制
  • 分布式生成 ID 策略的演进和最佳实践,含springBoot 实现(Java版本)
  • 【R语言】Can‘t subset elements that don‘t exist.
  • LastActivityView -查看电脑上的所有操作记录
  • 初识Neo4j之入门介绍(一)
  • 【Linux系统】Linux权限 | Shell命令以及运行原理
  • Python爬虫图片验证码和滑块验证码识别总结
  • Taro+Vue3实现微信小程序富文本编辑器组件开发指南
  • OpenCV人脸分析------绘制面部关键点函数drawFacemarks()
  • 虚幻引擎UE5 GAS开发RPG游戏-02 设置英雄角色-18 改成网络多人游戏
  • turborepo 如何解决git管理包过大的问题
  • 5、Receiving Messages:Message Listener Containers
  • Python实现文件夹中文件名与Excel中存在的文件名进行对比,并进行删除操作
  • 【无标题】三维拓扑量子色动力学模型:理论重构与实验验证
  • day16——Java集合进阶(Collection、List、Set)
  • windows安装python环境以及对应编辑器的详细流程
  • 从依赖地狱到依赖天堂PNPM