博客更新热力图

博客更新热力图

April 27, 2024
分享 , 编码 ,

这个功能最早是看到椒盐豆豉 如何给 Hugo 博客添加热力图 做的分享,不过当时还没下定心思来弄。后来陆续在蜗牛、空白大佬们的博客上看到,还是比较想加到自己博客上的,不过在这个月十几号的时候研究过一下,当时没太研究明白,放弃了。

但是今天在长毛象上看到空白大佬在 称赞蜗牛大佬的新热力图 ,所以跑过去看了一下,发现好像的确和第一版确实不一样,遂通过蜗牛大佬的 Github 仓库源码 折腾了一下午,终于弄好了,分享一下折腾经过。

Cal-heatmap

大佬新的热力图是基于 Cal-heatmap 这个工具,这是一个专门制作热力图表的 JS 工具包,我们只需要通过简单的配置即可生成漂亮的可定制热点图。

Cal-Heatmap | Cal-Heatmap
Cal-Heatmap is a javascript charting library to create a time-series calendar heatmap

基本初始化

首先是引入 Cal-heatmap 的 js 库和 css 库,当然你可以用使用 CDN 也可以直接下载源码到项目内。

另外大佬没有将这部分代码写在 head 里面,所以我也是照猫画虎直接在网页中需要使用的地方插入。

其中悬浮提示插件、日历插件可以根据需求引入。

其次添加一个 script 代码标签,之后的业务逻辑、初始化等代码都写在这个里面。

然后再添加一个 idcal-heatmapdiv 容器,用于存放生成的热力图。

<!-- 基础图表库 -->
<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 支持多种形式的数据,如:jsoncsvtext 等,这边蜗牛大佬使用的是 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 理解,如有错漏请指正。

加入评论