mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-02-20 06:40:49 +08:00
- ChatReplayAnimation:时间戳移到顶栏,简化气泡尾巴;补齐 Game Boy/DOS/Win98 顶栏文案颜色与边框风格 - 热力图:新增 win98 配色分支,并为 heatmap/legend 单元加硬边框 - Overview/MessageChars 等可视化补充 Win98 主题样式(键盘/进度条/雷达图等)
80 lines
2.2 KiB
JavaScript
80 lines
2.2 KiB
JavaScript
// Utilities for Wrapped heatmap rendering.
|
|
|
|
export const clamp01 = (v) => {
|
|
const n = Number(v)
|
|
if (!Number.isFinite(n)) return 0
|
|
if (n < 0) return 0
|
|
if (n > 1) return 1
|
|
return n
|
|
}
|
|
|
|
export const maxInMatrix = (matrix) => {
|
|
if (!Array.isArray(matrix)) return 0
|
|
let m = 0
|
|
for (const row of matrix) {
|
|
if (!Array.isArray(row)) continue
|
|
for (const v of row) {
|
|
const n = Number(v)
|
|
if (Number.isFinite(n) && n > m) m = n
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
// Color inspired by WeChat green, with a slight "gold" shift on high intensity
|
|
// (EchoTrace-style accent) while keeping the overall WeChat vibe.
|
|
export const heatColor = (value, max) => {
|
|
const v = Number(value) || 0
|
|
const m = Number(max) || 0
|
|
if (!(v > 0) || !(m > 0)) return 'rgba(0,0,0,0.05)'
|
|
|
|
// Use sqrt scaling to make low values still visible.
|
|
const t = clamp01(Math.sqrt(v / m))
|
|
|
|
// Hue from green (~145) -> yellow-green (~95)
|
|
const hue = 145 - 50 * t
|
|
const sat = 70
|
|
const light = 92 - 42 * t
|
|
return `hsl(${hue.toFixed(1)} ${sat}% ${light.toFixed(1)}%)`
|
|
}
|
|
|
|
// Theme-aware heat color function
|
|
export const themedHeatColor = (value, max, theme) => {
|
|
const v = Number(value) || 0
|
|
const m = Number(max) || 0
|
|
const t = (v > 0 && m > 0) ? clamp01(Math.sqrt(v / m)) : 0
|
|
|
|
switch (theme) {
|
|
case 'gameboy': {
|
|
// Game Boy 4-color palette: #0f380f, #306230, #8bac0f, #9bbc0f
|
|
if (t === 0) return '#9bbc0f'
|
|
if (t < 0.33) return '#8bac0f'
|
|
if (t < 0.66) return '#306230'
|
|
return '#0f380f'
|
|
}
|
|
case 'dos': {
|
|
// DOS green phosphor: from dark to bright green
|
|
if (t === 0) return 'rgba(51, 255, 51, 0.1)'
|
|
const light = 20 + 60 * t
|
|
return `hsl(120 100% ${light.toFixed(1)}%)`
|
|
}
|
|
case 'win98': {
|
|
// Win98-ish "system colors": gray -> blue highlight
|
|
if (t === 0) return '#dfdfdf'
|
|
if (t < 0.33) return '#c0c0c0'
|
|
if (t < 0.66) return '#808080'
|
|
return '#000080'
|
|
}
|
|
default:
|
|
// Modern (off) - use original heatColor
|
|
return heatColor(value, max)
|
|
}
|
|
}
|
|
|
|
export const formatHourRange = (hour) => {
|
|
const h = Number(hour)
|
|
if (!Number.isFinite(h)) return ''
|
|
const hh = String(h).padStart(2, '0')
|
|
return `${hh}:00-${hh}:59`
|
|
}
|