Hugo:添加热力图

Posted by Nefelibata on Sat 2024-03-30 | | about 2 mins
Last Modified on Thu 2024-05-23

1 显示效果

鼠标未移动到上面的时候,根据发表文章的数量显示热力图:

image-20240330014458265

如果鼠标移动到上面,会显示当天发表的文章详情,并且可以点击进行跳转:

recording

2 解决方案

本文解决方案参考 HUGO 折腾随记之热力图 / 段落导航如何给 Hugo 博客添加热力图 两位大佬的文章,并进行了改造。

改造点主要包括:

  1. 适应性改为侧边栏热力图
  2. 只显示一个月的文章发表记录,即一个月的热力图

添加文件: themes\hugo-theme-cleanwhite\layouts\partials\heatmap.html ,然后在 themes\hugo-theme-cleanwhite\layouts\partials\sidebar.html 中添加:

1<!-- heatmap -->
2<section>
3    <hr>
4    <h5>HEATMAP | {{ .Date.Format "January" }}</h5>
5	{{ partial "heatmap" . }}
6</section>

heatmap.html 的完整代码贴在下面:

  1<div id="heatmap" style="max-width: 290px;height: 200px;margin-bottom: 6px;"></div>
  2<!-- <script src="https://img.koobai.com/echarts.min.js"></script> -->
  3<script src="/js/echarts.min.js"></script>
  4<script type="text/javascript">
  5  var chartDom = document.getElementById('heatmap');
  6  var myChart = echarts.init(chartDom);
  7  window.onresize = function() {
  8      myChart.resize();
  9  };
 10  var option;
 11  var dataMap = new Map();
 12  {{ range ((where .Site.RegularPages "Type" "post")) }}
 13    var key = {{ .Date.Format "2006-01-02" }};
 14    var value = dataMap.get(key);
 15    var wordCount = {{ div .WordCount 1 }};
 16    var link = {{ .RelPermalink}};
 17    var title = {{ .Title }};
 18    
 19    // multiple posts in same day
 20    if (value == null) {
 21      dataMap.set(key, [{wordCount,link, title}]);
 22    } else {
 23      value.push({wordCount,link, title});
 24    }
 25  {{- end -}}
 26
 27  var data = [];
 28  for (const [key, value] of dataMap.entries()) {
 29    data.push([key, value.length]);
 30  }
 31
 32  var currentDate = new Date();
 33  var currentYear = currentDate.getFullYear();
 34  var currentMonth = currentDate.getMonth();
 35  var firstDayCurrentMonth = new Date(currentYear, currentMonth, 1);
 36  var lastDayCurrentMonth = new Date(currentYear, currentMonth + 1, 0);
 37
 38  var startDate = echarts.format.formatTime('yyyy-MM-dd', firstDayCurrentMonth);
 39  var endDate = echarts.format.formatTime('yyyy-MM-dd', lastDayCurrentMonth);
 40  
 41  // 检测浏览器主题模式并选择颜色方案
 42  var prefersDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
 43
 44  // 定义明亮模式下的颜色方案
 45  var lightTheme = {
 46      backgroundColor: '#FFFFFF',
 47      fangkuaicolor:'#F1F1F1',
 48      gaoliangcolor: ['#0085A121','#0085A1B8'],
 49      // gaoliangcolor: '#0085A1B8',
 50      riqiColor: '#999',
 51      textbrcolor: '#FFF',
 52      xiankuangcolor:'rgba(0, 0, 0, 0.0)',
 53  };
 54
 55  // 定义暗黑模式下的颜色方案
 56  var darkTheme = {
 57      backgroundColor: '#1A1718',
 58      fangkuaicolor:'#282325',
 59      gaoliangcolor: ['#b25f2f'],
 60      riqiColor: '#666',
 61      textbrcolor: '#332D2F',
 62      xiankuangcolor:'rgba(0, 0, 0, 0.0)',
 63  };
 64
 65  // 根据浏览器主题模式选择当前主题
 66  var currentTheme = prefersDarkMode ? darkTheme : lightTheme;
 67
 68  option = {
 69    tooltip: {
 70    hideDelay: 1000,
 71    enterable: true,
 72    backgroundColor: currentTheme.textbrcolor,
 73    borderWidth: 0, // 边框宽度为0
 74    formatter: function (p) {
 75        const date = p.data[0];
 76        const posts = dataMap.get(date);
 77        var content = `<span style="font-size: 1.5rem;font-family: LXGW WenKai Screen;color:#ABABAB">${date}</span>`;
 78        for (const [i, post] of posts.entries()) {
 79            content += "<br>";          
 80            var link = post.link;
 81            var title = post.title;
 82            var wordCount = post.wordCount;
 83            content += `<a href="${link}" target="_blank" style="font-size: 1.6rem;font-family: LXGW WenKai Screen;">${title} | ${wordCount}字`;
 84        }
 85        return content;
 86        }
 87    },
 88    visualMap: {
 89        min: 0,
 90        max: 10,
 91        type: 'piecewise',
 92        show: false,
 93        hoverLink:true,
 94        inRange: {   
 95          color: currentTheme.gaoliangcolor
 96          // color: ['#7aa8744c' ] 
 97        },
 98        // itemGap: 20,
 99        showLabel: false,
100        splitNumber: 4,
101
102    },
103    calendar: {
104        left: 45,
105        top:0,
106        bottom:0,
107        right: 0,
108        cellSize: ['auto', 13],
109        range: [startDate, endDate],
110        itemStyle: {
111            color: currentTheme.fangkuaicolor,
112            borderWidth: 5.5,
113            borderColor: currentTheme.backgroundColor, 
114        },
115        yearLabel: { show: false },
116        monthLabel: { 
117          show: false
118        // nameMap: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
119        // textStyle: {
120        //     color: currentTheme.riqiColor,
121        // }
122    },
123        dayLabel: {
124          
125            firstDay: 1,
126            nameMap: ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'],
127            textStyle: {
128                color: currentTheme.riqiColor,
129                fontFamily:'LXGW WenKai Screen',
130                fontSize:14 
131            }
132        },
133        splitLine: {
134            lineStyle: {
135                color: currentTheme.xiankuangcolor,
136            }
137        }
138    },
139    series: {
140        type: 'heatmap',
141        coordinateSystem: 'calendar',
142        data: data,
143    }
144  };
145  
146  myChart.setOption(option);
147  myChart.on('click', function(params) {
148    if (params.componentType === 'series') {
149      // open the first post on the day
150      const post = dataMap.get(params.data[0])[0];
151      const link = window.location.origin + post.link;
152      window.open(link, '_blank').focus();
153    }
154});
155</script>