anomalyHeatmapColor.mjs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /**
  2. * 功能诊断热力图按指标含义配色,不用同一条色带。
  3. * 异常率、次数、角度:数值越大越需要关注。
  4. * 功率曲线符合度:数值越高越好,颜色按「偏离满分」加深。
  5. */
  6. const SCALES = {
  7. anomaly: { cap: 100 },
  8. conformity: { cap: 100, invert: true },
  9. degree: { cap: 15 },
  10. count: { cap: 40 },
  11. };
  12. /** 报告和浅色页面用浅底色带;科技蓝页面用深底色带,贴近大屏底色 */
  13. const PALETTES = {
  14. light: {
  15. anomaly: { low: "#dbeafe", high: "#1d4ed8" },
  16. conformity: { low: "#dcfce7", high: "#15803d" },
  17. degree: { low: "#ffedd5", high: "#c2410c" },
  18. count: { low: "#ede9fe", high: "#6d28d9" },
  19. },
  20. dark: {
  21. anomaly: { low: "#16324a", high: "#22d3ee" },
  22. conformity: { low: "#14382c", high: "#34d399" },
  23. degree: { low: "#3a2a16", high: "#f59e0b" },
  24. count: { low: "#2a2244", high: "#c4b5fd" },
  25. },
  26. };
  27. function familyOf(metric, tone = "light") {
  28. const scale = SCALES[metric] || SCALES.anomaly;
  29. const palette = PALETTES[tone] || PALETTES.light;
  30. const colors = palette[metric] || palette.anomaly;
  31. return { ...scale, ...colors };
  32. }
  33. function parseHex(hex) {
  34. const text = String(hex || "").replace("#", "");
  35. return [0, 2, 4].map((index) => parseInt(text.slice(index, index + 2), 16));
  36. }
  37. function mixHex(from, to, amount) {
  38. const start = parseHex(from);
  39. const end = parseHex(to);
  40. const t = Math.max(0, Math.min(1, amount));
  41. const mixed = start.map((channel, index) =>
  42. Math.round(channel + (end[index] - channel) * t),
  43. );
  44. return `#${mixed.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
  45. }
  46. function relativeLuminance(hex) {
  47. const [r, g, b] = parseHex(hex).map((channel) => {
  48. const value = channel / 255;
  49. return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
  50. });
  51. return 0.2126 * r + 0.7152 * g + 0.0722 * b;
  52. }
  53. /** 行标签或字段类型 → 配色家族 */
  54. export function resolveHeatmapMetric(meta = {}) {
  55. const label = String(meta.label || "");
  56. const mapKey = String(meta.mapKey || "");
  57. const kind = String(meta.valueKind || "");
  58. if (
  59. kind === "degree" ||
  60. /静态偏航误差|最小桨距角/.test(label) ||
  61. /yawerror|minpitch/i.test(mapKey)
  62. ) {
  63. return "degree";
  64. }
  65. if (
  66. kind === "count" ||
  67. /偏航次数/.test(label) ||
  68. /yawcount/i.test(mapKey)
  69. ) {
  70. return "count";
  71. }
  72. if (
  73. /风功率曲线情况|功率曲线符合|符合度/.test(label) ||
  74. /powercurve/i.test(mapKey)
  75. ) {
  76. return "conformity";
  77. }
  78. return "anomaly";
  79. }
  80. export function heatmapFamilyColors(metric, tone = "light") {
  81. const family = familyOf(metric, tone);
  82. return [family.low, family.high];
  83. }
  84. /** 映射到 0~1。符合度按偏离满分计算,其余按绝对值相对上限计算 */
  85. export function heatmapSeverity(value, metric) {
  86. const family = familyOf(metric);
  87. const num = Number(value);
  88. const magnitude = Number.isFinite(num) ? Math.abs(num) : 0;
  89. const capped = Math.min(magnitude, family.cap) / family.cap;
  90. return family.invert ? 1 - capped : capped;
  91. }
  92. export function heatmapLabelColor(value, metric, tone = "light") {
  93. const [low, high] = heatmapFamilyColors(metric, tone);
  94. const color = mixHex(low, high, heatmapSeverity(value, metric));
  95. const lightText = tone === "dark" ? "#d8efff" : "#ffffff";
  96. const darkText = tone === "dark" ? "#071018" : "#0f172a";
  97. return relativeLuminance(color) < (tone === "dark" ? 0.42 : 0.45)
  98. ? lightText
  99. : darkText;
  100. }
  101. /** 每个指标单独一条 visualMap,热力图才能按不同色带上色 */
  102. export function heatmapMetricVisualMap(metric, seriesIndex, tone = "light") {
  103. return {
  104. type: "continuous",
  105. show: false,
  106. min: 0,
  107. max: 1,
  108. dimension: 2,
  109. seriesIndex,
  110. inRange: { color: heatmapFamilyColors(metric, tone) },
  111. };
  112. }