/** * 异常检测图表视觉语义(前端全场图 + 报告共用) * * 原则: * - 颜色优先表达诊断结论,不用彩虹色表达大量风机编号 * - 异常使用红色和更高层级,散点统一圆形,避免只依赖颜色 * - 连续变量使用感知均匀、色盲友好的 Viridis 色带 */ export const DIAGNOSTIC_COLORS = { normal: "#2E90FA", anomaly: "#F04438", warning: "#F79009", reference: "#98A2B3", contract: "#E11D48", /** 全场风功率总图合同曲线:与异常红、正常蓝分开 */ farmContract: "#7C3AED", mean: "#7F56D9", /** 单机降载点:深琥珀,浅底上要和正常蓝点分开 */ deload: "#A16207", /** 单机风功率散点:停机 */ shutdown: "#111827", blade1: "#2E90FA", blade2: "#12B76A", blade3: "#7F56D9", }; export const DIAGNOSTIC_CATEGORICAL_COLORS = [ "#2E90FA", "#12B76A", "#F79009", "#7F56D9", "#06AED4", "#EE46BC", "#6172F3", "#47CD89", "#FDB022", "#84ADFF", ]; /** 全场总图:正常风机冷色,异常风机红橙暖色;同组内用不同色相区分机组 */ export const FARM_NORMAL_COOL_COLORS = Object.freeze([ "#3B82F6", "#06B6D4", "#0EA5E9", "#2563EB", "#14B8A6", "#6366F1", "#38BDF8", "#0D9488", ]); export const FARM_ANOMALY_WARM_COLORS = Object.freeze([ "#F04438", "#F97316", "#EF4444", "#FB7185", "#EA580C", "#E11D48", "#F59E0B", "#DC2626", ]); export const DIAGNOSTIC_CONTINUOUS_COLORS = [ "#440154", "#3B528B", "#21918C", "#5EC962", "#FDE725", ]; /** 报告点数色带:白 → 浅蓝 → 深蓝(浅底报告) */ export const DIAGNOSTIC_COUNT_COLORS = [ "#FFFFFF", "#D6E8F8", "#7EB6E0", "#2E75B6", "#0B3A6E", ]; /** * 点数色带:低点数深、高点数亮。页面使用。 */ export const PAGE_DIAGNOSTIC_COUNT_COLORS = [ "#1a2740", "#234b73", "#2f74a8", "#3d9fd4", "#6ec8f0", ]; /** * 单机风功率散点「停机」点色。页面科技蓝深底需浅色才可见, * 浅色主题沿用 DIAGNOSTIC_COLORS.shutdown;报告始终用 shutdown 深色。 */ export const PAGE_SHUTDOWN_COLORS = Object.freeze({ dark: "#CBD5E1", light: DIAGNOSTIC_COLORS.shutdown, }); /** 报告最小桨距角色带停靠:0% / 10% / 20% / 30% / 100% */ export const PITCH_MIN_REPORT_COLOR_STOPS = [ [0, "#e6f4f5"], [0.1, "#74ABF7"], [0.2, "#3E7AF6"], [0.3, "#0000E4"], [1, "#020085"], ]; function hexToRgb(hex) { const text = String(hex || "").replace("#", ""); return [ parseInt(text.slice(0, 2), 16), parseInt(text.slice(2, 4), 16), parseInt(text.slice(4, 6), 16), ]; } function rgbToHex(rgb) { return `#${rgb .map((value) => Math.max(0, Math.min(255, Math.round(value))) .toString(16) .padStart(2, "0"), ) .join("")}`; } /** 按停靠位置铺成等距色阶,供 ECharts 连续色带按比例取色 */ export function expandColorStops(stops, steps = 101) { const sorted = [...stops].sort((a, b) => a[0] - b[0]); const colors = []; const last = Math.max(steps - 1, 1); for (let index = 0; index < steps; index += 1) { const t = index / last; let upper = sorted.findIndex((stop) => stop[0] >= t); if (upper < 0) upper = sorted.length - 1; const lower = Math.max(0, upper - (sorted[upper][0] === t ? 0 : 1)); const [startT, startColor] = sorted[lower]; const [endT, endColor] = sorted[upper]; if (startT === endT) { colors.push(endColor); continue; } const ratio = (t - startT) / (endT - startT); const startRgb = hexToRgb(startColor); const endRgb = hexToRgb(endColor); colors.push( rgbToHex( startRgb.map( (value, channel) => value + (endRgb[channel] - value) * ratio, ), ), ); } return colors; } export const PITCH_MIN_REPORT_COUNT_COLORS = expandColorStops( PITCH_MIN_REPORT_COLOR_STOPS, ); export const DIAGNOSTIC_FARM_STYLES = Object.freeze({ normal: Object.freeze({ pointOpacity: 0.5, lineWidth: 1, lineOpacity: 0.5, z: 1, }), anomaly: Object.freeze({ pointOpacity: 1, lineWidth: 2.4, lineOpacity: 1, z: 20, }), contract: Object.freeze({ pointOpacity: 1, lineWidth: 3.8, lineOpacity: 1, z: 40, }), category: Object.freeze({ pointOpacity: 0.88, lineWidth: 1.8, lineOpacity: 1, z: 2, }), }); /** 全场图:正常点统一色、异常点统一红,图例按异常机组优先 */ export const FARM_STATUS_LEGEND_DETECTORS = new Set([ "wind_power_curve", "wind_power_scatter", "yaw_static", "yaw_twist", "pitch_coord", "pitch_regulation", "ctrl_power_quality", "ctrl_op_state", "ctrl_deload", "aero_cp", "aero_tsr", "aero_tsr_wind", ]); /** 按散点/点状态上色(正常点蓝、异常点红;以 pointAnomaly 或系列名拆分为准) */ const FARM_POINT_STATUS_COLOR_DETECTORS = new Set([ "wind_power_curve", "wind_power_scatter", "yaw_static", "yaw_twist", "pitch_coord", "aero_cp", "aero_tsr", "aero_tsr_wind", ]); export function usesFarmPointStatusColor(templateKey = "") { return FARM_POINT_STATUS_COLOR_DETECTORS.has(templateKey); } export function usesFarmTurbineStatusColor(templateKey = "") { return ( FARM_STATUS_LEGEND_DETECTORS.has(templateKey) && !FARM_POINT_STATUS_COLOR_DETECTORS.has(templateKey) ); } /** * 静态偏航误差绝对值分档(度): * [0,3]、 (3,5]、 >5 */ export const YAW_ERROR_ABS_RANGES = Object.freeze([ Object.freeze({ key: "low", label: "[0,3]", color: DIAGNOSTIC_COLORS.blade2, }), Object.freeze({ key: "mid", label: "(3,5]", color: DIAGNOSTIC_COLORS.warning, }), Object.freeze({ key: "high", label: ">5", color: DIAGNOSTIC_COLORS.anomaly, }), ]); export function classifyYawErrorAbsRange(value) { const abs = Math.abs(Number(value)); if (!Number.isFinite(abs)) return null; if (abs <= 3) return YAW_ERROR_ABS_RANGES[0]; if (abs <= 5) return YAW_ERROR_ABS_RANGES[1]; return YAW_ERROR_ABS_RANGES[2]; } export function yawErrorAbsRangeColor(value) { return classifyYawErrorAbsRange(value)?.color || DIAGNOSTIC_COLORS.normal; } export function countYawErrorAbsRanges(values = []) { const counts = YAW_ERROR_ABS_RANGES.map(() => 0); values.forEach((value) => { const range = classifyYawErrorAbsRange(value); if (!range) return; const idx = YAW_ERROR_ABS_RANGES.findIndex( (item) => item.key === range.key, ); if (idx >= 0) counts[idx] += 1; }); return counts; } export const FARM_STATUS_NORMAL_LEGEND = "正常机组"; /** 全场单值柱图:正常统一色、异常红,图例优先列出异常机组 */ export function buildFarmStatusBarSeries({ categories = [], values = [], extras = [], anomalyFlags = [], } = {}) { const flags = categories.map((_, idx) => Boolean(anomalyFlags[idx])); const abnormalNames = categories.filter((_, idx) => flags[idx]); const hasNormal = flags.some((flag) => !flag); const makeItem = (idx, color) => ({ name: categories[idx], value: values[idx], extra: extras[idx], itemStyle: { color }, }); const series = []; if (hasNormal) { series.push({ type: "bar", name: FARM_STATUS_NORMAL_LEGEND, color: DIAGNOSTIC_COLORS.normal, itemStyle: { color: DIAGNOSTIC_COLORS.normal }, data: categories.map((_, idx) => flags[idx] ? null : makeItem(idx, DIAGNOSTIC_COLORS.normal), ), barMaxWidth: 28, barGap: "-100%", }); } abnormalNames.forEach((name) => { const hit = categories.indexOf(name); series.push({ type: "bar", name, color: DIAGNOSTIC_COLORS.anomaly, itemStyle: { color: DIAGNOSTIC_COLORS.anomaly }, data: categories.map((_, idx) => idx === hit ? makeItem(idx, DIAGNOSTIC_COLORS.anomaly) : null, ), barMaxWidth: 28, barGap: "-100%", }); }); return { legendNames: [ ...(hasNormal ? [FARM_STATUS_NORMAL_LEGEND] : []), ...abnormalNames, ], series, }; } export function usesFarmStatusLegend(templateKey = "") { return FARM_STATUS_LEGEND_DETECTORS.has(templateKey); } function seriesText(rowOrLabel) { if (rowOrLabel && typeof rowOrLabel === "object") { return `${rowOrLabel.originalLabel || ""} ${rowOrLabel.label || ""}`; } return String(rowOrLabel || ""); } /** 散点图内应叠加为折线的参考/功率曲线(非运行状态散点) */ export function isOverlayCurveSeries(rowOrLabel) { if (rowOrLabel?.__shared) return true; const text = seriesText(rowOrLabel); if (/参考功率|理论功率|合同功率|参考曲线/.test(text)) return true; if (/近90日.*功率曲线|近90日功率曲线/.test(text)) return true; return false; } /** 全场总图跨机组去重保留一条的参考曲线 */ export function isSharedOverlayCurveSeries(rowOrLabel) { const text = seriesText(rowOrLabel); if (/参考功率|理论功率|合同功率|参考曲线/.test(text)) return true; if (/近90日.*功率曲线|近90日功率曲线/.test(text)) return true; return false; } export function sharedOverlayCurveLabel(label) { const text = String(label || ""); if (/近90日/.test(text)) return "近90日功率曲线"; if (/合同功率/.test(text)) return "合同功率曲线"; if (/参考功率|理论功率|参考曲线/.test(text)) return "参考功率曲线"; return text; } export function isContractPowerCurve(rowOrLabel) { return /合同功率/.test(seriesText(rowOrLabel)); } /** 单机图:合同功率曲线在散点/曲线下方,异常点压在正常点上面 */ export const SINGLE_TURBINE_CONTRACT_Z = 1; export const SINGLE_TURBINE_SERIES_Z = 2; export const SINGLE_TURBINE_ANOMALY_Z = 8; export function diagnosticSingleSeriesZ(rowOrLabel) { const role = classifyDiagnosticSeries(rowOrLabel); if (role === "contract") return SINGLE_TURBINE_CONTRACT_Z; if (role === "anomaly" || role === "deload" || role === "shutdown") { return SINGLE_TURBINE_ANOMALY_Z; } return SINGLE_TURBINE_SERIES_Z; } function readSeriesCoord(point) { if (Array.isArray(point)) return point; if (Array.isArray(point?.value)) return point.value; return null; } /** 单机图里异常系列的坐标范围,用来把被 yrange 裁掉的异常点拉回坐标轴 */ export function anomalySeriesAxisExtent(series = []) { let xMin = Infinity; let xMax = -Infinity; let yMin = Infinity; let yMax = -Infinity; let found = false; (series || []).forEach((item) => { if (classifyDiagnosticSeries(item?.name) !== "anomaly") return; (item.data || []).forEach((point) => { const coord = readSeriesCoord(point); if (!coord) return; const x = Number(coord[0]); const y = Number(coord[1]); if (Number.isFinite(x)) { if (x < xMin) xMin = x; if (x > xMax) xMax = x; found = true; } if (Number.isFinite(y)) { if (y < yMin) yMin = y; if (y > yMax) yMax = y; found = true; } }); }); if (!found) return null; return { xMin: Number.isFinite(xMin) ? xMin : null, xMax: Number.isFinite(xMax) ? xMax : null, yMin: Number.isFinite(yMin) ? yMin : null, yMax: Number.isFinite(yMax) ? yMax : null, }; } /** 已锁定的轴范围若把异常点裁在外面,就扩到能包住这些点 */ export function expandAxisToExtent(axis, dataMin, dataMax) { if (!axis || dataMin == null || dataMax == null) return axis; const min = Number(axis.min); const max = Number(axis.max); if (Number.isFinite(min) && dataMin < min) axis.min = dataMin; if (Number.isFinite(max) && dataMax > max) axis.max = dataMax; return axis; } /** 变桨-转速-功率协调:横轴固定从该值起展示 */ export const PITCH_COORD_X_MIN = 1000; /** 变桨协调仍读取 yrange;横轴最小值固定,不跟 xrange 起点走 */ export function usesDataAxisRange(templateKey) { return templateKey === "pitch_coord"; } /** * 变桨协调横轴:最小固定为 1000。xrange 上限大于 1000 时沿用,否则最大值自适应。 * @returns {boolean} 横轴最小值已锁定 */ export function applyPitchCoordXAxis(axis, xrange) { if (!axis) return false; axis.min = PITCH_COORD_X_MIN; const max = Array.isArray(xrange) ? Number(xrange[1]) : NaN; if (Number.isFinite(max) && max > PITCH_COORD_X_MIN) { axis.max = max; axis.scale = false; } else { axis.max = undefined; axis.scale = true; } return true; } /** * 有 xrange / yrange 就设为坐标轴 min/max。 * @returns {boolean} 是否已锁定 */ export function lockAxisToRange(axis, range, convert) { if (!axis || !Array.isArray(range) || range.length < 2) return false; if ( range[0] == null || range[1] == null || range[0] === "" || range[1] === "" ) { return false; } const min = typeof convert === "function" ? convert(range[0]) : range[0]; const max = typeof convert === "function" ? convert(range[1]) : range[1]; if (min == null || max == null || min === "" || max === "") return false; if (typeof min === "number" && !Number.isFinite(min)) return false; if (typeof max === "number" && !Number.isFinite(max)) return false; axis.min = min; axis.max = max; axis.scale = false; return true; } export function classifyDiagnosticSeries(rowOrLabel) { const text = seriesText(rowOrLabel); if (/合同功率/.test(text)) return "contract"; if (/桨叶\s*1/.test(text)) return "blade1"; if (/桨叶\s*2/.test(text)) return "blade2"; if (/桨叶\s*3/.test(text)) return "blade3"; // 单机风功率散点运行状态(须先于「正常/异常」通用匹配) if (/停机/.test(text)) return "shutdown"; if (/降载/.test(text) && !/累计降载|降载率|降载占比|降载判定/.test(text)) { return "deload"; } if (/异常/.test(text) && !/正常/.test(text)) return "anomaly"; if (/上限|下限|阈值|警戒|频繁/.test(text)) return "warning"; if (/均值|平均|趋势/.test(text)) return "mean"; if (isOverlayCurveSeries(rowOrLabel)) return "reference"; if (/参考|理论|合同|基准|标准/.test(text) || rowOrLabel?.__shared) { return "reference"; } if (/正常/.test(text)) return "normal"; return "category"; } /** * 系列颜色。options.pageTheme 时「停机」改用页面可见色(深色主题浅灰), * 报告与单机浅色主题不传该选项,保持 shutdown 深色不变。 */ export function diagnosticSeriesColor(rowOrLabel, index = 0, options = {}) { const role = classifyDiagnosticSeries(rowOrLabel); if (options.pageTheme && role === "shutdown") { return options.darkTheme ? PAGE_SHUTDOWN_COLORS.dark : PAGE_SHUTDOWN_COLORS.light; } if (DIAGNOSTIC_COLORS[role]) return DIAGNOSTIC_COLORS[role]; const safeIndex = Math.abs(Number(index) || 0); return DIAGNOSTIC_CATEGORICAL_COLORS[ safeIndex % DIAGNOSTIC_CATEGORICAL_COLORS.length ]; } function paletteIndexOf(rowOrLabel, index = 0) { const turbineIndex = Number(rowOrLabel?.turbineIndex); if (Number.isFinite(turbineIndex)) return Math.abs(turbineIndex); return Math.abs(Number(index) || 0); } function turbineAnomalyFlag(rowOrLabel) { if (!rowOrLabel || typeof rowOrLabel !== "object") return null; // pointAnomaly 拆分后的行级 anomaly 优先于整机 extra.anomaly,避免正常点被整机标红 if (typeof rowOrLabel.anomaly === "boolean") return rowOrLabel.anomaly; if (typeof rowOrLabel.extra?.anomaly === "boolean") return rowOrLabel.extra.anomaly; return null; } /** * 全场总图是否按「异常风机」上色。 */ export function isFarmAnomalyTurbine(rowOrLabel, templateKey = "") { const flagged = turbineAnomalyFlag(rowOrLabel); if (typeof flagged === "boolean") return flagged; return classifyDiagnosticSeries(rowOrLabel) === "anomaly"; } export function farmStatusPaletteColor( rowOrLabel, index = 0, templateKey = "", ) { const palette = isFarmAnomalyTurbine(rowOrLabel, templateKey) ? FARM_ANOMALY_WARM_COLORS : FARM_NORMAL_COOL_COLORS; return palette[paletteIndexOf(rowOrLabel, index) % palette.length]; } /** * 全场图颜色语义: * - 风功率曲线/散点、偏航分析 / 扭缆 / 变桨协调、气动 Cp/TSR:正常点蓝、异常点红(pointAnomaly); * - 变桨一致性、电能质量、运行状态、降载:异常机组整机红,正常机组统一蓝(无 pointAnomaly 时);有 pointAnomaly 时仍按点拆分后的行级 anomaly; * - 参考 / 阈值 / 均值保持语义色。 * - 单机「降载黄 / 停机黑」仅用于单机风功率散点;全场总图一律红/蓝。 */ export function diagnosticFarmSeriesColor( rowOrLabel, index = 0, templateKey = "", ) { const role = classifyDiagnosticSeries(rowOrLabel); if ( role === "contract" || role === "reference" || role === "warning" || role === "mean" ) { return diagnosticSeriesColor(rowOrLabel, index); } if (templateKey === "wind_power_curve") { return isFarmAnomalyTurbine(rowOrLabel, templateKey) ? DIAGNOSTIC_COLORS.anomaly : DIAGNOSTIC_COLORS.normal; } if (usesFarmPointStatusColor(templateKey)) { return role === "anomaly" || role === "deload" || role === "shutdown" ? DIAGNOSTIC_COLORS.anomaly : DIAGNOSTIC_COLORS.normal; } if (usesFarmTurbineStatusColor(templateKey)) { // 报告拼接单机图时可能残留 label=降载/停机,按异常红而非单机黄/黑 if (role === "deload" || role === "shutdown") { return DIAGNOSTIC_COLORS.anomaly; } return isFarmAnomalyTurbine(rowOrLabel, templateKey) ? DIAGNOSTIC_COLORS.anomaly : DIAGNOSTIC_COLORS.normal; } if (role === "deload" || role === "shutdown") { return diagnosticSeriesColor(rowOrLabel, index); } return farmStatusPaletteColor(rowOrLabel, index, templateKey); } export function farmTurbineLegendName(rowOrLabel) { const obj = rowOrLabel && typeof rowOrLabel === "object" ? rowOrLabel : null; let text = String(obj?.engineName || obj?.turbine || "").trim(); if (!text) { text = String(obj?.label || rowOrLabel || "") .replace(/\s*(正常数据|异常点|正常|异常|降载|停机)$/u, "") .trim(); } if (!text) return ""; if (/^\d+$/.test(text)) return `${text}号`; return text; } export function farmStatusSeriesName( row, legendMode = "normal", templateKey = "", ) { const turbine = farmTurbineLegendName(row); if (!turbine) return row?.label || ""; if (legendMode !== "anomaly") return turbine; const role = classifyDiagnosticSeries(row); const matchLegend = usesFarmTurbineStatusColor(templateKey) ? isFarmAnomalyTurbine(row, templateKey) || role === "deload" || role === "shutdown" : role === "anomaly" || role === "deload" || role === "shutdown"; return matchLegend ? turbine : `${turbine} 正常`; } export function pickFarmStatusLegend(rows = [], templateKey = "") { const map = new Map(); let hasNormal = false; (rows || []).forEach((row) => { if (!row || row.__shared) return; const role = classifyDiagnosticSeries(row); if ( role === "contract" || role === "reference" || role === "warning" || role === "mean" ) { return; } const name = farmTurbineLegendName(row); if (!name) return; const anomalous = usesFarmTurbineStatusColor(templateKey) ? isFarmAnomalyTurbine(row, templateKey) || role === "deload" || role === "shutdown" : role === "anomaly" || role === "deload" || role === "shutdown" || isFarmAnomalyTurbine(row, templateKey); if (usesFarmPointStatusColor(templateKey)) { if (role === "normal") hasNormal = true; } else if (!anomalous) { hasNormal = true; } map.set(name, Boolean(map.get(name)) || anomalous); }); const entries = [...map.entries()]; const abnormal = entries.filter(([, flag]) => flag).map(([name]) => name); if (abnormal.length) { return { mode: "anomaly", names: hasNormal ? [FARM_STATUS_NORMAL_LEGEND, ...abnormal] : abnormal, }; } return { mode: "normal", names: entries.map(([name]) => name) }; } export function buildFarmNormalLegendSeries() { return { name: FARM_STATUS_NORMAL_LEGEND, type: "scatter", data: [], symbol: "circle", color: DIAGNOSTIC_COLORS.normal, itemStyle: { color: DIAGNOSTIC_COLORS.normal }, tooltip: { show: false }, silent: true, z: 0, }; } export function ensureFarmNormalLegendSeries(series = [], legendPick) { if (!legendPick?.names?.includes(FARM_STATUS_NORMAL_LEGEND)) return series; if (series.some((item) => item.name === FARM_STATUS_NORMAL_LEGEND)) { return series; } return [buildFarmNormalLegendSeries(), ...series]; } /** 全场散点统一点径:正常点与异常点一样大,只靠颜色和绘制顺序区分 */ export const FARM_SCATTER_SYMBOL_SIZE = 4; export function diagnosticFarmSymbolSize( _rowOrLabel, _pointCount = 0, _templateKey = "", ) { return FARM_SCATTER_SYMBOL_SIZE; } export function diagnosticFarmStyle(rowOrLabel, templateKey = "") { const role = classifyDiagnosticSeries(rowOrLabel); if (role === "contract") { return DIAGNOSTIC_FARM_STYLES.contract; } if (role === "reference" || role === "warning" || role === "mean") { return Object.freeze({ ...(DIAGNOSTIC_FARM_STYLES[role] || DIAGNOSTIC_FARM_STYLES.category), z: 30, }); } const base = DIAGNOSTIC_FARM_STYLES[role] || DIAGNOSTIC_FARM_STYLES.category; const pointAnomalyRole = role === "anomaly" || role === "deload" || role === "shutdown"; const farmAnomaly = isFarmAnomalyTurbine(rowOrLabel, templateKey) || role === "deload" || role === "shutdown"; if (pointAnomalyRole) { const anomaly = DIAGNOSTIC_FARM_STYLES.anomaly; return { ...base, z: anomaly.z, lineWidth: Math.max(base.lineWidth, anomaly.lineWidth), lineOpacity: anomaly.lineOpacity, pointOpacity: 1, }; } if (usesFarmTurbineStatusColor(templateKey) && farmAnomaly) { const anomaly = DIAGNOSTIC_FARM_STYLES.anomaly; return { ...base, z: anomaly.z, lineWidth: Math.max(base.lineWidth, anomaly.lineWidth), lineOpacity: anomaly.lineOpacity, pointOpacity: anomaly.pointOpacity, }; } return base; } /** 全场总图绘制顺序:异常点压在正常点上面,仍与坐标轴同一层,避免独立 zlevel 丢点 */ export function farmSeriesPaintOrder(rowOrLabel, templateKey = "") { const style = diagnosticFarmStyle(rowOrLabel, templateKey); const role = classifyDiagnosticSeries(rowOrLabel); const lifted = role === "anomaly" || role === "deload" || role === "shutdown" || (usesFarmTurbineStatusColor(templateKey) && isFarmAnomalyTurbine(rowOrLabel, templateKey) && role !== "normal" && role !== "contract" && role !== "reference" && role !== "warning" && role !== "mean"); return { z: lifted ? Math.max(Number(style.z) || 0, 20) : Number(style.z) || 1, z2: lifted ? 30 : 0, zlevel: 0, }; } export function diagnosticBladeStyle(rowOrLabel) { const role = classifyDiagnosticSeries(rowOrLabel); if (role === "blade1") { return { symbol: "rect", lineType: "dashed", showSymbol: true }; } if (role === "blade2") { return { symbol: "circle", lineType: "solid", showSymbol: true }; } if (role === "blade3") { return { symbol: "triangle", lineType: "dotted", showSymbol: true }; } return null; } export function diagnosticSeriesSymbol(rowOrLabel, fallback = "circle") { const role = classifyDiagnosticSeries(rowOrLabel); if (role === "blade1") return "rect"; if (role === "blade2") return "circle"; if (role === "blade3") return "triangle"; return fallback || "circle"; } export function diagnosticLineType(rowOrLabel) { const role = classifyDiagnosticSeries(rowOrLabel); if (role === "contract") return "solid"; if (role === "warning" || role === "reference" || role === "mean") { return "dashed"; } if (role === "blade1") return "dashed"; if (role === "blade3") return "dotted"; return "solid"; }