|
1 |
| -<!-- credit for https://blog.douchi.space/hugo-blog-heatmap/ --> |
2 |
| -<div |
3 |
| - id="heatmap" |
| 1 | +<div> |
| 2 | + <div style="display: flex; justify-content: center; align-items: center"> |
| 3 | + <p style="margin-right: 15px; margin-bottom: 0; color: #333; padding: 8px"> |
| 4 | + {{ .Get "title_text" }} |
| 5 | + </p> |
| 6 | + <select |
| 7 | + id="yearSelector" |
| 8 | + style=" |
| 9 | + padding: 8px 12px; |
| 10 | + border: 1px solid #ccc; |
| 11 | + border-radius: 5px; |
| 12 | + background-color: #fff; |
| 13 | + cursor: pointer; |
| 14 | + transition: border-color 0.3s ease, box-shadow 0.3s ease; |
| 15 | + " |
| 16 | + > |
| 17 | + <!-- Years will be dynamically added here --> |
| 18 | + </select> |
| 19 | + </div> |
| 20 | + <div |
| 21 | + id="heatmap" |
4 | 22 | style="
|
5 |
| - display: block; |
6 |
| - height: 150px; |
7 |
| - width: 75%; |
8 |
| - padding: 2px; |
9 |
| - text-align: center;" |
10 |
| -></div> |
| 23 | + display: block; |
| 24 | + height: 150px; |
| 25 | + width: 75%; |
| 26 | + padding: 2px; |
| 27 | + margin: 0 auto; |
| 28 | + text-align: center; |
| 29 | + " |
| 30 | + ></div> |
| 31 | +</div> |
11 | 32 | <script src=" https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js" ></script>
|
12 | 33 | <script type="text/javascript">
|
13 |
| - var chartDom = document.getElementById('heatmap'); |
14 |
| - var myChart = echarts.init(chartDom); |
15 |
| - window.onresize = function() { |
16 |
| - myChart.resize(); |
17 |
| - }; |
18 |
| - var option; |
| 34 | + var chartDom = document.getElementById('heatmap'); |
| 35 | + var myChart = echarts.init(chartDom); |
| 36 | + var yearSelector = document.getElementById('yearSelector'); |
| 37 | + window.onresize = function() { |
| 38 | + myChart.resize(); |
| 39 | + }; |
| 40 | + var option; |
| 41 | + var postsByDate = new Map(); |
| 42 | + var years = new Set(); |
19 | 43 |
|
20 |
| - // get date range by number of months |
21 |
| - function getDateRange(months){ |
22 |
| - var startDate = new Date(); |
23 |
| - var mill = startDate.setMonth((startDate.getMonth() - months)); |
24 |
| - var endDate = +new Date(); |
25 |
| - startDate = +new Date(mill); |
| 44 | + {{ range ((where .Site.RegularPages "Type" "post")) }} |
| 45 | + var date = {{ .Date.Format "2006-01-02" }}; |
| 46 | + var year = {{ .Date.Format "2006" }}; |
| 47 | + years.add(year); |
| 48 | + var postObj = new Map(); |
| 49 | + postObj.set("title", {{ .Title }}); |
| 50 | + postObj.set("link", {{ .RelPermalink }}); |
| 51 | + var wordCount = {{ .WordCount }}; |
| 52 | + var data = postsByDate.get(date); |
| 53 | + if (data === undefined) { |
| 54 | + data = new Map(); |
| 55 | + data.set("posts", []); |
| 56 | + data.set("totalWordCount", 0); |
| 57 | + } |
| 58 | + var posts = data.get("posts"); |
| 59 | + posts.push(postObj); |
| 60 | + var totalWordCount = data.get("totalWordCount"); |
| 61 | + totalWordCount += wordCount; |
| 62 | + data.set("totalWordCount", totalWordCount); |
| 63 | + postsByDate.set(date, data); |
| 64 | + {{- end -}} |
26 | 65 |
|
27 |
| - endDate = echarts.format.formatTime('yyyy-MM-dd', endDate); |
28 |
| - startDate = echarts.format.formatTime('yyyy-MM-dd', startDate); |
| 66 | + // Populate year selector |
| 67 | + years = Array.from(years).sort().reverse(); |
| 68 | + years.forEach(year => { |
| 69 | + var option = document.createElement('option'); |
| 70 | + option.value = year; |
| 71 | + option.text = year; |
| 72 | + yearSelector.appendChild(option); |
| 73 | + }); |
29 | 74 |
|
30 |
| - var dateRange = []; |
31 |
| - dateRange.push([ |
32 |
| - startDate, |
33 |
| - endDate |
34 |
| - ]); |
35 |
| - return dateRange |
36 |
| - }; |
| 75 | + function getHeatmapData(year) { |
| 76 | + var heatmapData = []; |
| 77 | + for (const [date, data] of postsByDate.entries()) { |
| 78 | + if (date.startsWith(year)) { |
| 79 | + heatmapData.push([date, data.get("totalWordCount")]); |
| 80 | + } |
| 81 | + } |
| 82 | + return heatmapData; |
| 83 | + } |
37 | 84 |
|
38 |
| - // get number of months by window size |
39 |
| - function getMonthCount(){ |
40 |
| - const windowWidth = window.innerWidth; |
41 |
| - if (windowWidth >= 600) { |
42 |
| - return 12; |
43 |
| - } |
44 |
| - if (windowWidth >= 400) { |
45 |
| - return 9; |
46 |
| - } |
47 |
| - return 6; |
48 |
| - } |
| 85 | + function updateHeatmap(year) { |
| 86 | + option.calendar.range = year; |
| 87 | + option.series.data = getHeatmapData(year); |
| 88 | + myChart.setOption(option); |
| 89 | + } |
49 | 90 |
|
50 |
| - var postsByDate = new Map(); |
51 |
| - {{ range ((where .Site.RegularPages "Type" "post")) }} |
| 91 | + yearSelector.addEventListener('change', function() { |
| 92 | + updateHeatmap(this.value); |
| 93 | + }); |
52 | 94 |
|
53 |
| - var date = {{ .Date.Format "2006-01-02" }}; |
54 |
| - var postObj = new Map(); |
55 |
| - postObj.set("title", {{ .Title }}); |
56 |
| - postObj.set("link", {{ .RelPermalink }}); |
57 |
| - var wordCount = {{ .WordCount }}; |
| 95 | + option = { |
| 96 | + title: { |
| 97 | + show: false, |
| 98 | + }, |
| 99 | + legend: { |
| 100 | + show: false, |
| 101 | + }, |
| 102 | + visualMap: { |
| 103 | + show: false, |
| 104 | + min: 0, |
| 105 | + max: 10000, |
| 106 | + type: 'piecewise', |
| 107 | + showLable: false, |
| 108 | + orient: 'horizontal', |
| 109 | + left: 'center', |
| 110 | + top: 0, |
| 111 | + itemGap: 10, |
| 112 | + inRange: { |
| 113 | + color: ['#c6e48b', '#7bc96f', '#239a3b', '#196127'], |
| 114 | + }, |
| 115 | + }, |
| 116 | + calendar: { |
| 117 | + top: 30, |
| 118 | + left: 30, |
| 119 | + right: 30, |
| 120 | + cellSize: ['auto', 'auto'], |
| 121 | + range: years[0], |
| 122 | + itemStyle: { |
| 123 | + color: '#fff', |
| 124 | + borderWidth: 0.5, |
| 125 | + borderColor: '#eee', |
| 126 | + }, |
| 127 | + yearLabel: { show: false }, |
| 128 | + dayLabel: { |
| 129 | + firstDay: 1, |
| 130 | + nameMap: {{ .Get "language" }} |
| 131 | + }, |
| 132 | + monthLabel: { |
| 133 | + nameMap: {{ .Get "language" }} |
| 134 | + }, |
| 135 | + splitLine: { |
| 136 | + show: false, |
| 137 | + }, |
| 138 | + }, |
| 139 | + tooltip: { |
| 140 | + hideDelay: 1000, |
| 141 | + enterable: true, |
| 142 | + formatter: function (params) { |
| 143 | + const date = params.data[0]; |
| 144 | + const posts = postsByDate.get(date).get("posts"); |
| 145 | + var content = `${date}`; |
| 146 | + for (const [i, post] of posts.entries()) { |
| 147 | + content += "<br>"; |
| 148 | + var link = post.get("link"); |
| 149 | + var title = post.get("title"); |
| 150 | + content += `<a href="${link}" target="_blank">${title}</a>` |
| 151 | + } |
| 152 | + return content; |
| 153 | + } |
| 154 | + }, |
| 155 | + series: { |
| 156 | + type: 'heatmap', |
| 157 | + coordinateSystem: 'calendar', |
| 158 | + calendarIndex: 0, |
| 159 | + data: getHeatmapData(years[0]) |
| 160 | + } |
| 161 | + }; |
58 | 162 |
|
59 |
| - var data = postsByDate.get(date); |
60 |
| - if (data === undefined) { |
61 |
| - data = new Map(); |
62 |
| - data.set("posts", []); |
63 |
| - data.set("totalWordCount", 0); |
64 |
| - } |
65 |
| - var posts = data.get("posts"); |
66 |
| - posts.push(postObj); |
67 |
| - var totalWordCount = data.get("totalWordCount"); |
68 |
| - totalWordCount += wordCount; |
69 |
| - data.set("totalWordCount", totalWordCount); |
70 |
| - postsByDate.set(date, data); |
71 |
| - {{- end -}} |
72 |
| - |
73 |
| - var heatmapData = []; |
74 |
| - for (const [date, data] of postsByDate.entries()) { |
75 |
| - heatmapData.push([date, data.get("totalWordCount")]); |
76 |
| - } |
77 |
| - |
78 |
| - option = { |
79 |
| - title: { |
80 |
| - show: true, |
81 |
| - top: 0, |
82 |
| - left: 'center', |
83 |
| - text: '{{ .Get "title_text" }}' |
84 |
| - }, |
85 |
| - legend: { |
86 |
| - show: false, |
87 |
| - }, |
88 |
| - visualMap: { |
89 |
| - show: false, |
90 |
| - min: 0, |
91 |
| - max: 10000, |
92 |
| - type: 'piecewise', |
93 |
| - showLable: false, |
94 |
| - orient: 'horizontal', |
95 |
| - left: 'center', |
96 |
| - top: 0, |
97 |
| - itemGap: 10, |
98 |
| - inRange: { |
99 |
| - color: ['#c6e48b', '#7bc96f', '#239a3b', '#196127'], |
100 |
| - }, |
101 |
| - }, |
102 |
| - calendar: { |
103 |
| - top: 50, |
104 |
| - left: 30, |
105 |
| - right: 30, |
106 |
| - cellSize: ['auto', 'auto'], |
107 |
| - range: getDateRange(getMonthCount()), |
108 |
| - itemStyle: { |
109 |
| - color: '#fff', |
110 |
| - borderWidth: 0.5, |
111 |
| - borderColor: '#eee', |
112 |
| - }, |
113 |
| - yearLabel: { |
114 |
| - show: false, |
115 |
| - }, |
116 |
| - dayLabel: { |
117 |
| - align: 'center', |
118 |
| - nameMap: 'ZH', |
119 |
| - }, |
120 |
| - monthLabel: { |
121 |
| - nameMap: 'EN', |
122 |
| - }, |
123 |
| - splitLine: { |
124 |
| - show: false, |
125 |
| - }, |
126 |
| - }, |
127 |
| - tooltip: { |
128 |
| - hideDelay: 1000, |
129 |
| - enterable: true, |
130 |
| - formatter: function(params) { |
131 |
| - const date = params.data[0]; |
132 |
| - const posts = postsByDate.get(date).get("posts"); |
133 |
| - var content = `${date}`; |
134 |
| - for (const [i, post] of posts.entries()) { |
135 |
| - content += "<br>"; |
136 |
| - var link = post.get("link"); |
137 |
| - var title = post.get("title"); |
138 |
| - content += `<a href="${link}" target="_blank">${title}</a>` |
139 |
| - } |
140 |
| - return content; |
141 |
| - } |
142 |
| - |
143 |
| - }, |
144 |
| - series: { |
145 |
| - type: 'heatmap', |
146 |
| - coordinateSystem: 'calendar', |
147 |
| - data: heatmapData |
148 |
| - } |
149 |
| -}; |
150 |
| -option && myChart.setOption(option); |
| 163 | + option && myChart.setOption(option); |
151 | 164 | </script>
|
0 commit comments