feat: 实现自己的热力图组件,并换用

This commit is contained in:
Ted 2026-06-30 20:12:58 +08:00
parent e689976543
commit e9f8b027fa
7 changed files with 537 additions and 544 deletions

539
package-lock.json generated
View file

@ -9,7 +9,6 @@
"version": "1.4.4",
"license": "GPL-3.0-only",
"dependencies": {
"cal-heatmap": "^4.2.4",
"react": "^19.1.0",
"react-calendar-heatmap": "^1.10.0",
"react-dom": "^19.1.0",
@ -1747,20 +1746,6 @@
"node": ">= 8"
}
},
"node_modules/@observablehq/plot": {
"version": "0.6.17",
"resolved": "https://registry.npmmirror.com/@observablehq/plot/-/plot-0.6.17.tgz",
"integrity": "sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==",
"license": "ISC",
"dependencies": {
"d3": "^7.9.0",
"interval-tree-1d": "^1.0.0",
"isoformat": "^0.2.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -1785,16 +1770,6 @@
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.34.35",
"resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.34.35.tgz",
@ -2721,12 +2696,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/binary-search-bounds": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz",
"integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -2827,23 +2796,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cal-heatmap": {
"version": "4.2.4",
"resolved": "https://registry.npmmirror.com/cal-heatmap/-/cal-heatmap-4.2.4.tgz",
"integrity": "sha512-TTNoQTRxHXrttOEbkraKv9vy2VpfQIwVLQJTlAfcBusQK9qrBL/UBO+WloAxv2yrR+P8URA2cuXEdc5iztER9g==",
"license": "MIT",
"dependencies": {
"@observablehq/plot": "^0.6.0",
"@popperjs/core": "^2.11.6",
"d3-color": "^3.1.0",
"d3-fetch": "^3.0.1",
"d3-selection": "^3.0.0",
"d3-transition": "^3.0.1",
"dayjs": "^1.11.7",
"eventemitter3": "^5.0.0",
"lodash-es": "^4.17.21"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
@ -3034,15 +2986,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
@ -3079,413 +3022,6 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/d3": {
"version": "7.9.0",
"resolved": "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz",
"integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
"license": "ISC",
"dependencies": {
"d3-array": "3",
"d3-axis": "3",
"d3-brush": "3",
"d3-chord": "3",
"d3-color": "3",
"d3-contour": "4",
"d3-delaunay": "6",
"d3-dispatch": "3",
"d3-drag": "3",
"d3-dsv": "3",
"d3-ease": "3",
"d3-fetch": "3",
"d3-force": "3",
"d3-format": "3",
"d3-geo": "3",
"d3-hierarchy": "3",
"d3-interpolate": "3",
"d3-path": "3",
"d3-polygon": "3",
"d3-quadtree": "3",
"d3-random": "3",
"d3-scale": "4",
"d3-scale-chromatic": "3",
"d3-selection": "3",
"d3-shape": "3",
"d3-time": "3",
"d3-time-format": "4",
"d3-timer": "3",
"d3-transition": "3",
"d3-zoom": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-axis": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz",
"integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-brush": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz",
"integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "3",
"d3-transition": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-chord": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz",
"integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
"license": "ISC",
"dependencies": {
"d3-path": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-contour": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz",
"integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
"license": "ISC",
"dependencies": {
"d3-array": "^3.2.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-delaunay": {
"version": "6.0.4",
"resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
"integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
"license": "ISC",
"dependencies": {
"delaunator": "5"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dsv": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz",
"integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
"license": "ISC",
"dependencies": {
"commander": "7",
"iconv-lite": "0.6",
"rw": "1"
},
"bin": {
"csv2json": "bin/dsv2json.js",
"csv2tsv": "bin/dsv2dsv.js",
"dsv2dsv": "bin/dsv2dsv.js",
"dsv2json": "bin/dsv2json.js",
"json2csv": "bin/json2dsv.js",
"json2dsv": "bin/json2dsv.js",
"json2tsv": "bin/json2dsv.js",
"tsv2csv": "bin/dsv2dsv.js",
"tsv2json": "bin/dsv2json.js"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-fetch": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz",
"integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
"license": "ISC",
"dependencies": {
"d3-dsv": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-force": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz",
"integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-quadtree": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-geo": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz",
"integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2.5.0 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-hierarchy": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
"integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-polygon": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz",
"integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-quadtree": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
"integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-random": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz",
"integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale-chromatic": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
"integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
"d3-interpolate": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"license": "ISC",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-transition": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
"d3-dispatch": "1 - 3",
"d3-ease": "1 - 3",
"d3-interpolate": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"d3-selection": "2 - 3"
}
},
"node_modules/d3-zoom": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "2 - 3",
"d3-transition": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz",
@ -3537,15 +3073,6 @@
"node": ">=0.10.0"
}
},
"node_modules/delaunator": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz",
"integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
"license": "ISC",
"dependencies": {
"robust-predicates": "^3.0.2"
}
},
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/detect-newline/-/detect-newline-3.1.0.tgz",
@ -3950,12 +3477,6 @@
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz",
@ -4420,18 +3941,6 @@
"node": ">=10.17.0"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
@ -4509,24 +4018,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/interval-tree-1d": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz",
"integrity": "sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==",
"license": "MIT",
"dependencies": {
"binary-search-bounds": "^2.0.0"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz",
@ -4618,12 +4109,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/isoformat": {
"version": "0.2.1",
"resolved": "https://registry.npmmirror.com/isoformat/-/isoformat-0.2.1.tgz",
"integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==",
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
@ -5460,12 +4945,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -6267,12 +5746,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/robust-predicates": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz",
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
"license": "Unlicense"
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
@ -6297,18 +5770,6 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rw": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz",
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
"license": "BSD-3-Clause"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.26.0.tgz",

View file

@ -30,11 +30,10 @@
"typescript": "4.7.4"
},
"dependencies": {
"cal-heatmap": "^4.2.4",
"react": "^19.1.0",
"react-calendar-heatmap": "^1.10.0",
"react-dom": "^19.1.0",
"react-tooltip": "^5.28.1",
"zustand": "^5.0.4"
}
}
}

View file

@ -12,7 +12,7 @@
- 筛选某一天时,筛选条件显示当天日期,而不是日期范围。
- 筛选结果中的标签,现在可以点击。
- 更改筛选方案现在会把条件同步到搜索框,现在可以基于当前筛选方案进行个性搜索。
- 更换热力图组件,现在可以查看所有日期的情况。
### 1.4.4

View file

@ -1,4 +1,4 @@
import { Heatmap } from 'src/pages/sidebar/heatmap/Heatmap';
import { CustomHeatmap } from 'src/pages/sidebar/heatmap/CustomHeatmap';
import { FilterSchemesInfo } from './filterScheme/FilterSchemesInfo';
import { ViewSchemesInfo } from './viewScheme/ViewSchemesInfo';
import { RandomReviewInfo } from './randomReview/RandomReviewInfo';
@ -22,7 +22,7 @@ export const SidebarContent = () => {
return (
<div className="sidebar-content">
<StatisticsInfo />
<Heatmap onCickDate={handleClickDate} />
<CustomHeatmap onCickDate={handleClickDate} />
<div className="sidebar-margin-top" />
<SidebarButton leftIconName="settings"
label={i18n.t('setting_panel')}

View file

@ -0,0 +1,316 @@
import moment from 'moment';
import { useMemo, useRef, useEffect, useState } from 'react';
import { Tooltip } from 'react-tooltip';
import { SortType } from 'src/models/Enum';
import { FileInfo } from 'src/models/FileInfo';
import { useCombineStore } from 'src/store';
import { i18n } from 'src/utils/i18n';
export type HeatmapData = {
date: string,
count: number,
}
export const CustomHeatmap = ({
onCickDate,
cellSize = 14,
gapSize = 6,
fontSize = 14,
}: {
onCickDate: (date: string) => void,
cellSize?: number,
gapSize?: number,
fontSize?: number,
}) => {
const sortType = useCombineStore((state) => state.appData.sortType);
const allFiles = useCombineStore((state) => state.allFiles);
const firstUseDateStr = useCombineStore((state) => state.appData.firstUseDate);
const values = useMemo(() => getHeatmapValues(allFiles, sortType), [allFiles, sortType]);
const valueMap = useMemo(() => {
const map = new Map<string, number>();
values.forEach(v => {
map.set(v.date, v.count);
});
return map;
}, [values]);
const today = useMemo(() => {
const d = new Date();
d.setHours(0, 0, 0, 0);
return d;
}, []);
const earliestDate = useMemo(() => {
// Default to 1 year ago (52 weeks ago)
let d = new Date(today);
d.setFullYear(today.getFullYear() - 1);
// Find the earliest file date in values
if (values && values.length > 0) {
values.forEach(v => {
const dateVal = new Date(v.date);
if (!isNaN(dateVal.getTime()) && dateVal < d) {
d = dateVal;
}
});
}
// Check first use date
if (firstUseDateStr) {
const fud = new Date(firstUseDateStr);
if (!isNaN(fud.getTime()) && fud < d) {
d = fud;
}
}
return d;
}, [values, firstUseDateStr, today]);
// Align to Sunday of start week
const startOfWeek = useMemo(() => {
const d = new Date(earliestDate);
const day = d.getDay();
d.setDate(d.getDate() - day);
d.setHours(0, 0, 0, 0);
return d;
}, [earliestDate]);
// Align to Saturday of end week
const endOfWeek = useMemo(() => {
const d = new Date(today);
const day = d.getDay();
d.setDate(d.getDate() + (6 - day));
d.setHours(23, 59, 59, 999);
return d;
}, [today]);
// Generate weeks array
const weeks = useMemo(() => {
const result: {
date: Date;
dateStr: string;
count: number;
level: number;
isToday: boolean;
isFuture: boolean;
tooltipText: string;
}[][] = [];
const currentDay = new Date(startOfWeek);
let currentWeek: typeof result[0] = [];
const getDayLevel = (count: number) => {
if (count === 0) return 0;
const numPerLevel = 4;
const numOflevels = 3;
return Math.min(numOflevels, Math.ceil(count / numPerLevel));
};
const todayStr = moment(today).format('YYYY-MM-DD');
while (currentDay <= endOfWeek) {
const dateStr = moment(currentDay).format('YYYY-MM-DD');
const count = valueMap.get(dateStr) || 0;
const level = getDayLevel(count);
const isToday = dateStr === todayStr;
const isFuture = currentDay > today;
const activityText = (sortType === 'created' || sortType === 'earliestCreated')
? i18n.t('notes_created_at')
: i18n.t('notes_modified_at');
const tooltipText = count > 0
? `${count} ${activityText} ${dateStr}`
: `${i18n.t('empty_note_result')} ${dateStr}`;
currentWeek.push({
date: new Date(currentDay),
dateStr,
count,
level: isFuture ? 0 : level,
isToday,
isFuture,
tooltipText,
});
if (currentWeek.length === 7) {
result.push(currentWeek);
currentWeek = [];
}
currentDay.setDate(currentDay.getDate() + 1);
}
return result;
}, [startOfWeek, endOfWeek, valueMap, today, sortType]);
// Precalculate which week columns should render a month label to avoid collisions
const labelIndices = useMemo(() => {
const indices = new Set<number>();
let lastLabelIndex = -10;
for (let i = 0; i < weeks.length; i++) {
const week = weeks[i];
const prevWeek = weeks[i - 1];
const isNewMonth = i === 0 || week[0].date.getMonth() !== prevWeek[0].date.getMonth();
if (isNewMonth) {
// If it's the very first column, check if the next month is too close (within 3 columns)
if (i === 0) {
let nextMonthIndex = -1;
for (let j = 1; j < weeks.length; j++) {
if (weeks[j][0].date.getMonth() !== week[0].date.getMonth()) {
nextMonthIndex = j;
break;
}
}
// Skip the very first month label if it's too short, prioritizing the next month's clean render
if (nextMonthIndex !== -1 && nextMonthIndex < 3) {
continue;
}
}
// Spacing check: Only render month label if it's at least 3 columns away from the last rendered label
if (i - lastLabelIndex >= 3) {
indices.add(i);
lastLabelIndex = i;
}
}
}
return indices;
}, [weeks]);
// Find the first index that displays a month label
const firstLabelIndex = useMemo(() => {
let min = Infinity;
labelIndices.forEach(idx => {
if (idx < min) min = idx;
});
return min === Infinity ? -1 : min;
}, [labelIndices]);
// Format the month label: January displays ONLY the year, first label displays Year + Month
const getMonthLabelText = (date: Date, isFirst: boolean) => {
const monthIndex = date.getMonth();
const monthName = i18n.t(`month${monthIndex + 1}` as any);
const year = date.getFullYear();
const isJanuary = monthIndex === 0;
if (isJanuary) {
return `${year}`;
}
if (isFirst) {
return `${monthName}`;
}
return monthName;
};
// Scroll control ref
const containerRef = useRef<HTMLDivElement>(null);
const hasScrolledRef = useRef(false);
// Initial scroll to today (far right)
useEffect(() => {
if (containerRef.current && !hasScrolledRef.current && weeks.length > 0) {
containerRef.current.scrollLeft = containerRef.current.scrollWidth;
hasScrolledRef.current = true;
}
}, [weeks.length]);
// Mouse drag-to-scroll panning state
const [isDragging, setIsDragging] = useState(false);
const [startX, setStartX] = useState(0);
const [scrollLeftState, setScrollLeftState] = useState(0);
const handleMouseDown = (e: React.MouseEvent) => {
if (!containerRef.current) return;
setIsDragging(true);
setStartX(e.pageX - containerRef.current.offsetLeft);
setScrollLeftState(containerRef.current.scrollLeft);
};
const handleMouseMove = (e: React.MouseEvent) => {
if (!isDragging || !containerRef.current) return;
e.preventDefault();
const x = e.pageX - containerRef.current.offsetLeft;
// Sensitivity factor: 1.5
const walk = (x - startX) * 1.5;
containerRef.current.scrollLeft = scrollLeftState - walk;
};
const handleMouseUpOrLeave = () => {
setIsDragging(false);
};
return (
<div className="custom-heatmap-container">
{/* Scrollable Grid */}
<div
className={`custom-heatmap-scroll-container ${isDragging ? 'is-dragging' : ''}`}
ref={containerRef}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUpOrLeave}
onMouseLeave={handleMouseUpOrLeave}
>
<div className="custom-heatmap-content-wrapper">
{/* Month Labels Row */}
<div className="custom-heatmap-months-row" style={{ gap: `${gapSize}px` }}>
{weeks.map((week, index) => {
const showLabel = labelIndices.has(index);
const isFirst = index === firstLabelIndex;
return (
<div key={index} className="custom-heatmap-month-label-col" style={{ width: `${cellSize}px` }}>
{showLabel && (
<span className="custom-heatmap-month-label" style={{ fontSize: `${fontSize}px` }}>
{getMonthLabelText(week[0].date, isFirst)}
</span>
)}
</div>
);
})}
</div>
{/* Day Grid Row */}
<div className="custom-heatmap-grid-row" style={{ gap: `${gapSize}px` }}>
{weeks.map((week, index) => (
<div key={index} className="custom-heatmap-week-column" style={{ gap: `${gapSize}px` }}>
{week.map((day) => (
<div
key={day.dateStr}
className={`custom-heatmap-day-cell color-scale-${day.level} ${day.isToday ? 'is-today' : ''} ${day.isFuture ? 'is-future' : ''}`}
style={{ width: `${cellSize}px`, height: `${cellSize}px` }}
data-tooltip-id="custom-heatmap-tooltip"
data-tooltip-content={day.tooltipText}
onClick={() => !day.isFuture && onCickDate(day.dateStr)}
/>
))}
</div>
))}
</div>
</div>
</div>
<Tooltip id="custom-heatmap-tooltip" className="custom-heatmap-tooltip" />
</div>
);
};
export const getHeatmapValues = (fileInfos: FileInfo[], sortType: SortType) => {
const valueMap = fileInfos
.map(f => f.file.stat)
.map(stat => {
const timestamp = (sortType === 'created' || sortType === 'earliestCreated') ? stat.ctime : stat.mtime;
return moment(timestamp).format('YYYY-MM-DD');
})
.reduce<Map<string, number>>(
(pre, cur) => pre.set(cur, pre.has(cur) ? pre.get(cur)! + 1 : 1),
new Map<string, number>());
return Array
.from(valueMap.entries())
.map(([key, value]) => {
return { date: key, count: value };
});
};

View file

@ -0,0 +1,216 @@
/* Container for the whole Heatmap widget */
.custom-heatmap-container {
display: flex;
flex-direction: column;
width: 100%;
}
/* Header style containing title and controls */
.custom-heatmap-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
padding: 0 4px;
}
.custom-heatmap-title {
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.custom-heatmap-controls {
display: flex;
align-items: center;
gap: 4px;
}
/* Small elegant navigation buttons */
.custom-heatmap-control-btn {
background: var(--background-secondary);
border: 1px solid var(--border-color, var(--background-modifier-border));
color: var(--text-normal);
border-radius: 4px;
padding: 2px 6px;
font-size: 10px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.15s ease, border-color 0.15s ease;
height: 18px;
}
.custom-heatmap-control-btn:hover {
background: var(--background-modifier-hover);
border-color: var(--text-muted);
}
.custom-heatmap-control-btn svg {
stroke: var(--text-muted);
}
.custom-heatmap-control-btn:hover svg {
stroke: var(--text-normal);
}
/* Scrollable area */
.custom-heatmap-scroll-container {
overflow-x: auto;
width: 100%;
cursor: grab;
user-select: none;
-webkit-user-select: none;
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb-bg, var(--background-modifier-border)) transparent;
padding-bottom: 4px;
}
.custom-heatmap-scroll-container.is-dragging {
cursor: grabbing;
}
/* We hide standard scrollbars on Webkit browsers for a cleaner look */
.custom-heatmap-scroll-container::-webkit-scrollbar {
height: 4px;
}
.custom-heatmap-scroll-container::-webkit-scrollbar-track {
background: transparent;
}
.custom-heatmap-scroll-container::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-thumb-bg, var(--background-modifier-border));
border-radius: 4px;
}
/* Wrapper to hold row sizes together */
.custom-heatmap-content-wrapper {
display: inline-flex;
flex-direction: column;
padding: 4px;
min-width: max-content;
}
/* Month labels row layout */
.custom-heatmap-months-row {
display: flex;
height: 14px;
margin-bottom: 4px;
}
.custom-heatmap-month-label-col {
position: relative;
}
.custom-heatmap-month-label {
position: absolute;
left: 0;
top: 0;
color: var(--text-muted);
white-space: nowrap;
line-height: 1;
}
/* Days grid layout */
.custom-heatmap-grid-row {
display: flex;
}
.custom-heatmap-week-column {
display: flex;
flex-direction: column;
}
/* Day cell styling */
.custom-heatmap-day-cell {
border-radius: 2px;
transition: transform 0.1s ease, box-shadow 0.15s ease;
cursor: pointer;
position: relative;
}
/* Prevent clicking future days */
.custom-heatmap-day-cell.is-future {
cursor: default;
opacity: 0.3;
}
/* Highlight today with a subtle border/outline */
.custom-heatmap-day-cell.is-today {
outline: 1.5px solid var(--color-accent);
outline-offset: 1px;
}
/* Hover effects for interactive cells */
.custom-heatmap-day-cell:not(.is-future):hover {
transform: scale(1.3);
z-index: 10;
box-shadow: 0 0 6px var(--color-accent);
}
/* Color scales mapped exactly as requested */
.custom-heatmap-day-cell.color-scale-0 {
background-color: var(--background-primary-alt);
}
.custom-heatmap-day-cell.color-scale-1 {
background-color: color-mix(in srgb, var(--color-accent) 33%, var(--background-primary-alt) 66%);
}
.custom-heatmap-day-cell.color-scale-2 {
background-color: color-mix(in srgb, var(--color-accent) 66%, var(--background-primary-alt) 33%);
}
.custom-heatmap-day-cell.color-scale-3 {
background-color: var(--color-accent);
}
/* Footer / Legend */
.custom-heatmap-footer {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 6px;
padding: 0 4px;
}
.custom-heatmap-legend {
display: flex;
align-items: center;
gap: 3px;
}
.custom-heatmap-legend-text {
color: var(--text-muted);
}
.custom-heatmap-legend-cell {
border-radius: 2px;
}
.custom-heatmap-legend-cell.color-scale-0 {
background-color: var(--background-primary-alt);
}
.custom-heatmap-legend-cell.color-scale-1 {
background-color: color-mix(in srgb, var(--color-accent) 33%, var(--background-primary-alt) 66%);
}
.custom-heatmap-legend-cell.color-scale-2 {
background-color: color-mix(in srgb, var(--color-accent) 66%, var(--background-primary-alt) 33%);
}
.custom-heatmap-legend-cell.color-scale-3 {
background-color: var(--color-accent);
}
/* Tooltip layering */
.custom-heatmap-tooltip {
z-index: 2001;
font-size: 10px !important;
padding: 4px 8px !important;
border-radius: 4px !important;
}

View file

@ -4,6 +4,7 @@
@import 'src/pages/sidebar/sidebar.css';
@import 'src/pages/sidebar/heatmap/heatmap.css';
@import 'src/pages/sidebar/heatmap/custom-heatmap.css';
@import 'src/pages/sidebar/filterScheme/filterScheme.css';
@import 'src/pages/sidebar/viewScheme/viewScheme.css';
@import 'src/pages/sidebar/randomReview/randomReview.css';