博客更新热力图
这个功能最早是看到椒盐豆豉 如何给 Hugo 博客添加热力图 做的分享,不过当时还没下定心思来弄。后来陆续在蜗牛、空白大佬们的博客上看到,还是比较想加到自己博客上的,不过在这个月十几号的时候研究过一下,当时没太研究明白,放弃了。
但是今天在长毛象上看到空白大佬在 称赞蜗牛大佬的新热力图 ,所以跑过去看了一下,发现好像的确和第一版确实不一样,遂通过蜗牛大佬的 Github仓库源码 折腾了一下午,终于弄好了,分享一下折腾经过。
Cal-heatmap
大佬新的热力图是基于 Cal-heatmap
这个工具,这是一个专门制作热力图表的JS工具包,我们只需要通过简单的配置即可生成漂亮的可定制热点图。

基本初始化
首先是引入Cal-heatmap的js库和css库,当然你可以用使用CDN也可以直接下载源码到项目内。
另外大佬没有将这部分代码写在 head
里面,所以我也是照猫画虎直接在网页中需要使用的地方插入。
其中悬浮提示插件、日历插件可以根据需求引入。
其次添加一个 script
代码标签,之后的业务逻辑、初始化等代码都写在这个里面。
然后再添加一个 id
为 cal-heatmap
的 div
容器,用于存放生成的热力图。
<!-- 基础图表库 -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- cal-heatmap 核心库-->
<script src="https://unpkg.com/cal-heatmap/dist/cal-heatmap.min.js"></script>
<!-- 样式库 -->
<link rel="stylesheet" href="https://unpkg.com/cal-heatmap/dist/cal-heatmap.css">
<!-- 悬浮提示插件 -->
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/cal-heatmap/dist/plugins/Tooltip.min.js"></script>
<!-- 日历标签插件 -->
<script src="https://unpkg.com/cal-heatmap/dist/plugins/CalendarLabel.min.js"></script>
<script>
// 这里写逻辑代码
</script>
<!-- 容器代码 -->
<div id="cal-heatmap"></div>
数据初始化
热力图是一种将数据图表化的一种形式,所以我们也需要给它提供一定量的数据,而在博客中,我们一般都是用于展示每个月的文章更新频次。
Cal-heatmap支持多种形式的数据,如:json
、csv
、text
等,这边蜗牛大佬使用的是 json
格式,并直接将数据通过模板生成在了网页之上,而我使用的是一个用于数据索引的,包含全站文章信息的 json
文件。
<script type="text/javascript">
// 获取最近一年的文章数据
{{ $pages := where .Site.RegularPages "Date" ">" (now.AddDate -1 0 0) }}
{{ $pages := $pages.Reverse }}
var data = {
"pages": [
{{ range $index, $element := $pages }}
{
"title": "{{ .Title }}",
"date": "{{ .Date.Format "2006-01-02" }}",
"year": "{{ .Date.Format "2006" }}",
"month": "{{ .Date.Format "01" }}",
"day": "{{ .Date.Format "02" }}",
"word_count": "{{ .WordCount }}"
}{{ if ne (add $index 1) (len $pages) }},{{ end }}
{{ end }}
]
};
// console.log(data)
</script>
蜗牛大佬hugo的方式
// <script> 标签内
var data2 = { pages: [] };
// 请求json文件
fetch("/assets/search-data.json")
.then((respone) => respone.json())
.then((posts) => {
// 重新将数据组装成功想要的格式
posts.forEach(function (item) {
var date = item.date.substring(0, 10);
var year = new Date(item.date).getFullYear();
var month = new Date(item.date).getMonth() + 1;
var day = new Date(item.date).getDate();
item.year = year;
item.date = date;
item.month = month;
item.day = day;
item.word_count;
data2.pages.push(item)
});
});
// ...其他逻辑代码
我的方式
其实,这里你可以用你能做到的任何形式将数据定义、引用到页面中。
当然,蜗牛大佬的方式应该效率更好一些,我之后可能也会使用这种方法。
不过无论你使用那种形式,都推荐组成以下形式的 json
数据,其中 word_count
用于在最后的热力图上展现不同的颜色时作为判断依据使用。
{
pages: [
0: {
date: '2024-04-01',
year: '2024',
month: '04',
day: '01',
word_count: 1000 //文章字数
}
]
}
推荐的数据格式
Cal-heatmap绘制
为了便于理解,我大概说一下Cal-heatmap的组成形式。
(我下午弄的时候想了好久🤣)
- 黄色框框为主数据,既下方配置中的
domain
,一般是以年或月为单位 - 红色框框内的小格子为单项数据,既下方配置中的
subDomain
,一般是以天、小时、星期为单位 - 下方截图就是由一个年为单位的domain和不同天的subDomain组合而成。

开始正式初始化热力图。
这部分代码比较散乱,相关说明我直接写在代码片断的备注中。
document.addEventListener("DOMContentLoaded", function () {
// 创建新的Calheatmap实例
const cal = new CalHeatmap();
// 获取今天的日期
const today = new Date();
// 将之前定义的数据放在blogInfo中
let blogInfo = data;
// 该时间用于告诉Cal-heatmap从那一天开始展示数据
let startDate = new Date(today.setMonth(today.getMonth() - 1));
//定义一个点击时间,我这里是点击热力图上的方块,自动跳转到文章归档页面对应的时间位置
cal.on('click', (event, timestamp, value) => {
// 将时间戳转换成标准时间,并截去前面的 `2024-04` 部分。
var date = new Date(timestamp).toISOString().substring(0,7);
// 跳转页面,并定位到id为2024-04的部分。
window.location.href = '/archives#' + date;
});
// 绘制热力图
cal.paint({
// 设置热力图主题,light | dark 可选
theme: 'dark',
// 展示几个月的数据
range: 2,
// 热力图上的方块颜色部分
scale: {
color: {
type: 'threshold',
// 下面的range和domain定义了两个有四个值4的数组,domain为需要判断的数值内容(这里判断的是word_count),如果在100到1000则使用'rgba(77, 208, 90,0.1)'颜色,其他类推。
range: ['rgba(77, 208, 90,0.1)', 'rgba(77, 208, 90,0.3)', 'rgba(77, 208, 90,0.6)', 'rgba(77, 208, 90,1)'],
domain: [100, 1000, 2000, 4000],
},
},
// 设置主数据的格式
domain: {
//按月形式展示,可选的还有year等
type: 'month',
// 设置月和月之间的间隙,类似css中的gap属性
gutter: 5,
// 标签的位置
position: 'right',
// 我这里不想让他显示标签所以设置的null(但是不起作用,最后我通过css隐藏的)
text: null
},
// 设置子数据
subDomain: {
// subDomain的数据形式,可以设置week、hour等,根据颗粒度来。
// ghDay为该月的每一天的第一周和周末,按列展示,具有比较规整的排列
type: 'ghDay',
// 小格子的圆角度
radius: 5,
// 小格子的大小
width: 18,
height: 18,
// 小格子的间隙,类似css中的gap
gutter: 5,
},
// 设置展示时间
date: {
// 从合适开始展示数据,我们之前定义的startDate为2个月
start: startDate,
// 开始时间设置为周一
locale: { weekStart: 1 },
},
// 设置数据部分
data: {
//数据设置为blogInfo.pages,x轴为date,y轴为我们的wordcount,并用groupY进汇总分组(把每天的写的字数加在一起)
source: blogInfo.pages, x: "date", y: item => {
return parseInt(item.word_count);
}, groupY: 'sum'
},
},
// 以下部分为插件定义
[[
// 悬浮提示插件
Tooltip,
{
// 定一个函数,悬浮时把日期传入,通过日期查询当天有几天文章,和总字数多少
text: function (date, value, dayjsDate) {
const currentPost = blogInfo.pages.filter(page => page.date === dayjsDate.format('YYYY-MM-DD'));
const postCount = currentPost.length;
const postText = postCount === 0 ? '' : (postCount === 1 ? '共 1 篇' : `共 ${postCount} 篇`);
const wordText = value ? ' 字 ' : '';
const valueText = value ? value : '';
return (
postText +
' ' +
valueText +
wordText +
'<span class="block">' +
dayjsDate.format('YYYY-MM-DD') +
'</span>'
);
},
},
],
[
// 日历标签插件,用以自定义右侧的周几标签
CalendarLabel,
{
// 显示在右侧
position: 'right',
// 中周一开始显示一、二、三等
text: () => ["一", "", "三", "", "五", "", "日"],
// 居中显示
textAlign:'middle',
// 圆角弧度
radius: 5
},
],
]);
});
<script>// 业务逻辑 </script> 中的代码
定义样式
同时,你还可以使用css定义一些热力图中的样式。
.ch-plugin-calendar-label{
position: absolute;
top: 20px;
left: 30px;
gutter: 5px;
}
/** 周几初标签部分样式 **/
.ch-plugin-calendar-label-text{
fill: var(--gray-500) !important;
font-size: 12px !important;
transform: translate(6px, 0px);
}
/** 隐藏月份信息 **/
.ch-domain-text{
display: none;
}
/* 修改小方块的背景填充颜色 */
[data-theme=dark] .ch-subdomain-bg {
fill: var(--gray-200);
}
结束
God Job!恭喜你,至此你应该已经成功完成了热力图的绘制!
本文大多都是个人基于Cal-heatmap官方文档+ChatGTP理解,如有错漏请指正。
加入评论