/** * 功能诊断热力图按指标含义配色,不用同一条色带。 * 异常率、次数、角度:数值越大越需要关注。 * 功率曲线符合度:数值越高越好,颜色按「偏离满分」加深。 */ const SCALES = { anomaly: { cap: 100 }, conformity: { cap: 100, invert: true }, degree: { cap: 15 }, count: { cap: 40 }, }; /** 报告和浅色页面用浅底色带;科技蓝页面用深底色带,贴近大屏底色 */ const PALETTES = { light: { anomaly: { low: "#dbeafe", high: "#1d4ed8" }, conformity: { low: "#dcfce7", high: "#15803d" }, degree: { low: "#ffedd5", high: "#c2410c" }, count: { low: "#ede9fe", high: "#6d28d9" }, }, dark: { anomaly: { low: "#16324a", high: "#22d3ee" }, conformity: { low: "#14382c", high: "#34d399" }, degree: { low: "#3a2a16", high: "#f59e0b" }, count: { low: "#2a2244", high: "#c4b5fd" }, }, }; function familyOf(metric, tone = "light") { const scale = SCALES[metric] || SCALES.anomaly; const palette = PALETTES[tone] || PALETTES.light; const colors = palette[metric] || palette.anomaly; return { ...scale, ...colors }; } function parseHex(hex) { const text = String(hex || "").replace("#", ""); return [0, 2, 4].map((index) => parseInt(text.slice(index, index + 2), 16)); } function mixHex(from, to, amount) { const start = parseHex(from); const end = parseHex(to); const t = Math.max(0, Math.min(1, amount)); const mixed = start.map((channel, index) => Math.round(channel + (end[index] - channel) * t), ); return `#${mixed.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`; } function relativeLuminance(hex) { const [r, g, b] = parseHex(hex).map((channel) => { const value = channel / 255; return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4; }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; } /** 行标签或字段类型 → 配色家族 */ export function resolveHeatmapMetric(meta = {}) { const label = String(meta.label || ""); const mapKey = String(meta.mapKey || ""); const kind = String(meta.valueKind || ""); if ( kind === "degree" || /静态偏航误差|最小桨距角/.test(label) || /yawerror|minpitch/i.test(mapKey) ) { return "degree"; } if ( kind === "count" || /偏航次数/.test(label) || /yawcount/i.test(mapKey) ) { return "count"; } if ( /风功率曲线情况|功率曲线符合|符合度/.test(label) || /powercurve/i.test(mapKey) ) { return "conformity"; } return "anomaly"; } export function heatmapFamilyColors(metric, tone = "light") { const family = familyOf(metric, tone); return [family.low, family.high]; } /** 映射到 0~1。符合度按偏离满分计算,其余按绝对值相对上限计算 */ export function heatmapSeverity(value, metric) { const family = familyOf(metric); const num = Number(value); const magnitude = Number.isFinite(num) ? Math.abs(num) : 0; const capped = Math.min(magnitude, family.cap) / family.cap; return family.invert ? 1 - capped : capped; } export function heatmapLabelColor(value, metric, tone = "light") { const [low, high] = heatmapFamilyColors(metric, tone); const color = mixHex(low, high, heatmapSeverity(value, metric)); const lightText = tone === "dark" ? "#d8efff" : "#ffffff"; const darkText = tone === "dark" ? "#071018" : "#0f172a"; return relativeLuminance(color) < (tone === "dark" ? 0.42 : 0.45) ? lightText : darkText; } /** 每个指标单独一条 visualMap,热力图才能按不同色带上色 */ export function heatmapMetricVisualMap(metric, seriesIndex, tone = "light") { return { type: "continuous", show: false, min: 0, max: 1, dimension: 2, seriesIndex, inRange: { color: heatmapFamilyColors(metric, tone) }, }; }