| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472 |
- import * as echarts from "echarts";
- import "echarts-gl";
- import { readCssVar, isTechBlueTheme } from "@/utils/chartTheme";
- import {
- shouldUseWindPower3D,
- buildWindPower3DPanelOption,
- } from "../../../../shared/anomalyWindPower3D.mjs";
- import {
- DIAGNOSTIC_COLORS,
- DIAGNOSTIC_CONTINUOUS_COLORS,
- PAGE_DIAGNOSTIC_COUNT_COLORS,
- classifyDiagnosticSeries,
- isOverlayCurveSeries,
- diagnosticBladeStyle,
- diagnosticFarmSeriesColor,
- diagnosticFarmStyle,
- diagnosticFarmSymbolSize,
- farmSeriesPaintOrder,
- diagnosticLineType,
- distinctLineXCount,
- isDeloadRateGuideLabel,
- isDeloadThresholdLabel,
- spanThresholdLinePairs,
- diagnosticSeriesColor,
- diagnosticSeriesSymbol,
- diagnosticSingleSeriesZ,
- expandAxisToExtent,
- lockAxisToRange,
- applyPitchCoordXAxis,
- farmStatusPaletteColor,
- farmTurbineLegendName,
- farmStatusSeriesName,
- pickFarmStatusLegend,
- ensureFarmNormalLegendSeries,
- usesFarmStatusLegend,
- YAW_ERROR_ABS_RANGES,
- FARM_STATUS_NORMAL_LEGEND,
- classifyYawErrorAbsRange,
- buildFarmStatusBarSeries,
- } from "../../../../shared/anomalyDiagnosticVisual.mjs";
- import {
- extractDetectorPanels,
- findPeakCoord,
- formatFarmSeriesHover,
- formatFarmValueHover,
- formatPointHover,
- resolvePanelSubtitle,
- isBubbleColorbar,
- usesBubbleSymbolSize,
- resolveRowColorMetric,
- sortScatterPairsByColorMetricAsc,
- isFarmPanelsJson,
- isValueBarPanel,
- isYawCountDetector,
- isYawErrorDetector,
- resolveTemplateKey,
- scaleBubbleSize,
- unwrapFarmPlotJson,
- isPointAnomalyFlag,
- readPointAnomalyFlags,
- splitConnectedLineByPointAnomaly,
- } from "../../../../shared/anomalyDetectorPlotAdapter.mjs";
- import {
- applyFarmPanelTitles,
- fillFarmPanelsToRule,
- getFarmPanelRule,
- selectFarmPanels,
- shouldExcludeFarmLabel,
- shouldKeepFullFarmLegend,
- applyZeroStartAxis,
- seriesAxisDataMin,
- } from "../../../../shared/anomalyFarmChartRules.mjs";
- const FORCE_SCATTER_TEMPLATE_KEYS = new Set([
- "wind_power_scatter",
- "pitch_coord",
- "ctrl_op_state",
- "ctrl_power_quality",
- "ctrl_deload",
- "yaw_static",
- "yaw_twist",
- "aero_tsr",
- "aero_cp",
- "aero_tsr_wind",
- ]);
- /** 以下检测器图表统一按散点图展示(含多 panel 轮播) */
- const SCATTER_ONLY_DETECTOR_KEYS = new Set(["anomalyPowerqualityPO"]);
- /** 变桨红蓝分段:蓝线共用「正常」,红线按异常机组列入图例 */
- function pitchSegmentSeriesName(row, anomaly) {
- if (!anomaly) return "正常";
- const name = String(farmTurbineLegendName(row) || "")
- .replace(/\s*桨叶\s*\d+\s*$/u, "")
- .trim();
- return name || "异常";
- }
- function pitchSegmentLegendData(series = []) {
- const anomalyNames = [];
- const seen = new Set();
- series.forEach((item) => {
- if (!item.__bladeSegment || item.name === "正常" || seen.has(item.name)) {
- return;
- }
- seen.add(item.name);
- anomalyNames.push(item.name);
- });
- anomalyNames.sort((a, b) => a.localeCompare(b, "zh-CN", { numeric: true }));
- const hasNormal = series.some(
- (item) => item.__bladeSegment && item.name === "正常",
- );
- return [...(hasNormal ? ["正常"] : []), ...anomalyNames];
- }
- function chartTheme() {
- return {
- axis: readCssVar("--anomaly-chart-axis-label", "#8da3c7"),
- split: readCssVar("--anomaly-chart-split", "rgba(98, 121, 186, 0.2)"),
- tooltipBg: readCssVar(
- "--anomaly-chart-tooltip-bg",
- "rgba(16, 30, 66, 0.95)",
- ),
- tooltipBorder: readCssVar(
- "--anomaly-chart-tooltip-border",
- "rgba(120, 156, 255, 0.4)",
- ),
- tooltipText: readCssVar("--anomaly-chart-tooltip-text", "#dbeafe"),
- legend: readCssVar("--anomaly-chart-legend", "#cde6ff"),
- title: readCssVar("--anomaly-title", "#e3f2ff"),
- dataZoomBg: readCssVar(
- "--anomaly-chart-datazoom-bg",
- "rgba(16, 21, 39, 0.85)",
- ),
- dataZoomBorder: readCssVar(
- "--anomaly-chart-datazoom-border",
- "rgba(101, 167, 201, 0.35)",
- ),
- dataZoomFiller: readCssVar(
- "--anomaly-chart-datazoom-filler",
- "rgba(50, 115, 138, 0.55)",
- ),
- };
- }
- function getTooltipBase() {
- return {
- appendToBody: true,
- confine: false,
- extraCssText: "z-index: 999999 !important; pointer-events: none;",
- };
- }
- export function extractPanels(plotJson, options = {}) {
- return extractDetectorPanels(plotJson, options);
- }
- function filterFarmPanelRows(panel, templateKey) {
- if (!panel || panel.chartKind === "valueBar") return panel;
- const data = (panel.data || []).filter(
- (row) =>
- !shouldExcludeFarmLabel(row.label || row.originalLabel, templateKey),
- );
- return { ...panel, data };
- }
- function emptyFarmPanelSlots(templateKey) {
- return (getFarmPanelRule(templateKey).titles || []).map((title) => ({
- title,
- xaxis: "",
- yaxis: "",
- data: [],
- }));
- }
- /** 页面总图按规则保留全部分面 Tab;JSON 缺哪面就留空,不再补模拟数据 */
- export function expandFarmPagePanels(plotJson, options = {}) {
- const json = unwrapFarmPlotJson(plotJson) || plotJson;
- const templateKey =
- options.templateKey || json?.detector || json?.__templateKey || "";
- const rule = getFarmPanelRule(templateKey);
- let panels = extractDetectorPanels(plotJson, options).map((panel) =>
- filterFarmPanelRows(panel, templateKey),
- );
- if (
- panels.length &&
- panels.every((panel) => panel?.chartKind === "valueBar")
- ) {
- return panels;
- }
- // chartType=panels:算法已按分面给全,按原顺序与原标题展示
- if (isFarmPanelsJson(json)) {
- panels =
- panels.length >= rule.count
- ? panels
- : fillFarmPanelsToRule(
- panels,
- emptyFarmPanelSlots(templateKey),
- templateKey,
- );
- } else {
- panels = selectFarmPanels(panels, templateKey);
- panels =
- rule.count <= 1
- ? applyFarmPanelTitles(panels, templateKey)
- : fillFarmPanelsToRule(
- panels,
- emptyFarmPanelSlots(templateKey),
- templateKey,
- );
- }
- return panels;
- }
- function isTimeAxis(xaxis, sampleX) {
- if (xaxis && String(xaxis).includes("时间")) return true;
- if (typeof sampleX === "string" && /\d{4}-\d{2}-\d{2}/.test(sampleX)) {
- return true;
- }
- const n = Number(sampleX);
- return Number.isFinite(n) && n > 1e12;
- }
- function toMs(v) {
- const n = Number(v);
- if (!Number.isFinite(n)) return v;
- return n > 1e15 ? Math.floor(n / 1e6) : n;
- }
- function formatTimeLabel(ms) {
- const d = new Date(ms);
- const pad = (n) => String(n).padStart(2, "0");
- return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(
- d.getHours(),
- )}:${pad(d.getMinutes())}`;
- }
- function formatTimeAxisLabel(ms) {
- const d = new Date(ms);
- const pad = (n) => String(n).padStart(2, "0");
- return `${pad(d.getMonth() + 1)}-${pad(d.getDate())}\n${pad(
- d.getHours(),
- )}:${pad(d.getMinutes())}`;
- }
- function pickSeriesColor(label, index) {
- // 页面「停机」点用可见色:科技蓝深底走浅灰,浅色主题仍用 shutdown 深色
- return diagnosticSeriesColor(label, index, {
- pageTheme: true,
- darkTheme: isTechBlueTheme(),
- });
- }
- function isDashedLine(label) {
- return diagnosticLineType(label) !== "solid";
- }
- function buildLineStyle(color, label) {
- return {
- width: 2,
- color,
- type: isDashedLine(label) ? "dashed" : "solid",
- };
- }
- function isCompositionPanel(panel) {
- if (panel.xaxis || panel.yaxis) return false;
- const rows = panel.data || [];
- if (!rows.length) return false;
- return rows.every(
- (row) =>
- Array.isArray(row.xData) &&
- row.xData.length === 1 &&
- Array.isArray(row.yData) &&
- row.yData.length === 1 &&
- Number(row.xData[0]) === Number(row.yData[0]),
- );
- }
- function buildCompositionOption(panel, theme) {
- const pieData = (panel.data || []).map((row, idx) => ({
- name: row.label,
- value: Number(row.xData?.[0]) || Number(row.yData?.[0]) || 0,
- itemStyle: { color: pickSeriesColor(row.label, idx) },
- }));
- return {
- backgroundColor: "transparent",
- tooltip: {
- ...getTooltipBase(),
- trigger: "item",
- backgroundColor: theme.tooltipBg,
- borderColor: theme.tooltipBorder,
- textStyle: { color: theme.tooltipText },
- },
- legend: {
- bottom: 6,
- left: "center",
- textStyle: { color: theme.legend, fontSize: 11 },
- },
- series: [
- {
- type: "pie",
- radius: ["38%", "62%"],
- center: ["50%", "52%"],
- data: pieData,
- label: {
- color: theme.legend,
- fontSize: 11,
- formatter: "{b}\n{d}%",
- },
- },
- ],
- };
- }
- function toAxisValue(v, useTime) {
- if (!useTime) return v;
- const n = Number(v);
- if (Number.isFinite(n)) return toMs(n);
- if (typeof v === "string" && v) {
- const parsed = Date.parse(v.replace(" ", "T"));
- if (Number.isFinite(parsed)) return parsed;
- }
- return v;
- }
- function looksLikeTimeArray(arr) {
- if (!Array.isArray(arr) || !arr.length) return false;
- return isTimeAxis(
- "",
- arr.find((item) => item != null && item !== ""),
- );
- }
- function sampleRowX(row) {
- const pools = [row?.timeData, row?.xData, row?.x];
- for (let i = 0; i < pools.length; i += 1) {
- const arr = pools[i];
- if (!Array.isArray(arr)) continue;
- const hit = arr.find((item) => item != null && item !== "");
- if (hit != null) return hit;
- }
- return undefined;
- }
- /** 与报告一致:时序优先 timeData/xData/x 中真正的时间列,否则读 xData、x */
- function getRowXData(row, useTime = false) {
- if (useTime) {
- if (looksLikeTimeArray(row?.timeData)) return row.timeData;
- if (looksLikeTimeArray(row?.xData)) return row.xData;
- if (looksLikeTimeArray(row?.x)) return row.x;
- }
- if (Array.isArray(row?.xData) && row.xData.length) return row.xData;
- if (Array.isArray(row?.x) && row.x.length) return row.x;
- if (Array.isArray(row?.timeData) && row.timeData.length) return row.timeData;
- return [];
- }
- function getRowYData(row) {
- if (Array.isArray(row?.yData) && row.yData.length) return row.yData;
- if (Array.isArray(row?.y) && row.y.length) return row.y;
- return [];
- }
- function toYNumber(v) {
- if (v == null || v === "") return null;
- const n = Number(v);
- return Number.isFinite(n) ? n : null;
- }
- function hoverExtra(row) {
- const extra = row?.extra;
- const model = row?.model;
- const extraHasModel =
- extra &&
- typeof extra === "object" &&
- extra.model != null &&
- extra.model !== "";
- if (extraHasModel || model == null || model === "") return extra || null;
- if (extra && typeof extra === "object") return { ...extra, model };
- return { model };
- }
- function buildScatterPairs(row, useTime) {
- const xArr = getRowXData(row, useTime);
- const yArr = getRowYData(row);
- const colorMetric = resolveRowColorMetric(
- row,
- Math.min(xArr.length, yArr.length),
- );
- const points = Array.isArray(row.pointData) ? row.pointData : [];
- const len = Math.min(xArr.length, yArr.length);
- const extra = hoverExtra(row);
- const pairs = [];
- for (let i = 0; i < len; i += 1) {
- const y = toYNumber(yArr[i]);
- if (y == null) continue;
- const value = [toAxisValue(xArr[i], useTime), y];
- if (colorMetric.length === len) value.push(colorMetric[i]);
- pairs.push({
- value,
- point: points[i] || null,
- extra,
- });
- }
- return pairs;
- }
- function finiteExtent(values = []) {
- let min = Infinity;
- let max = -Infinity;
- for (let i = 0; i < values.length; i += 1) {
- const value = Number(values[i]);
- if (!Number.isFinite(value)) continue;
- if (value < min) min = value;
- if (value > max) max = value;
- }
- return { min, max };
- }
- function collectFlaggedAnomalyPairs(row, useTime) {
- const flags = readPointAnomalyFlags(row);
- if (!Array.isArray(flags) || !flags.length) return [];
- if (classifyDiagnosticSeries(row?.label) === "anomaly") return [];
- const xArr = getRowXData(row, useTime);
- const yArr = getRowYData(row);
- const points = Array.isArray(row.pointData) ? row.pointData : [];
- const len = Math.min(xArr.length, yArr.length);
- const pairs = [];
- for (let i = 0; i < len; i += 1) {
- if (!isPointAnomalyFlag(flags[i])) continue;
- const y = toYNumber(yArr[i]);
- if (y == null) continue;
- pairs.push({
- value: [toAxisValue(xArr[i], useTime), y],
- point: points[i] || null,
- extra: row.extra || null,
- });
- }
- return pairs;
- }
- /** 图例已有「异常点」系列但坐标没读到时,用同图 pointAnomaly 把红点补上 */
- function fillEmptyAnomalySeries(series, rows, useTime) {
- const empty = (series || []).filter(
- (item) =>
- classifyDiagnosticSeries(item.name) === "anomaly" &&
- !(item.data || []).length,
- );
- if (!empty.length) return;
- const pairs = [];
- (rows || []).forEach((row) => {
- pairs.push(...collectFlaggedAnomalyPairs(row, useTime));
- });
- if (!pairs.length) return;
- empty.forEach((item) => {
- item.data = pairs;
- item.type = "scatter";
- item.symbol = "circle";
- item.symbolSize = item.symbolSize || 6;
- item.showSymbol = true;
- item.z = diagnosticSingleSeriesZ({ label: item.name });
- item.z2 = 2;
- item.zlevel = 0;
- item.itemStyle = {
- ...(item.itemStyle || {}),
- color: DIAGNOSTIC_COLORS.anomaly,
- opacity: 1,
- };
- });
- }
- function buildSeries(panel, theme, options = {}) {
- const farmMode = Boolean(options.farmMode);
- const templateKey =
- resolveTemplateKey(options) || resolveWindPowerTemplateKey(options);
- const forceScatter =
- SCATTER_ONLY_DETECTOR_KEYS.has(options.detectorKey) ||
- options.detectorKey === "anomalyScatterPO" ||
- (farmMode && FORCE_SCATTER_TEMPLATE_KEYS.has(templateKey));
- const firstRow = panel.data?.[0];
- const sampleX = sampleRowX(firstRow);
- const hasTimeData = (panel.data || []).some(
- (row) => Array.isArray(row.timeData) && row.timeData.length,
- );
- const axisSaysTime = String(panel.xaxis || "").includes("时间");
- const useTime =
- hasTimeData || axisSaysTime || isTimeAxis(panel.xaxis, sampleX);
- const series = [];
- const visualMaps = [];
- const densePitchCoord = farmMode && templateKey === "pitch_coord";
- const statusLegendPick =
- farmMode && usesFarmStatusLegend(templateKey)
- ? pickFarmStatusLegend(panel.data, templateKey)
- : null;
- (panel.data || []).forEach((row, idx) => {
- const rawMode = row.mode || "lines";
- const overlayCurve = isOverlayCurveSeries(row);
- let mode =
- forceScatter && !row.__shared && !overlayCurve ? "markers" : rawMode;
- if (
- farmMode &&
- templateKey === "pitch_regulation" &&
- !row.__shared &&
- !overlayCurve
- ) {
- mode = "lines+markers";
- }
- if (row.__shared || overlayCurve) mode = "lines";
- if (
- /累计降载|频繁阈值|正常上限/.test(
- String(row.originalLabel || row.label || ""),
- )
- ) {
- mode = "lines";
- }
- if (
- !farmMode &&
- !row.__shared &&
- !overlayCurve &&
- classifyDiagnosticSeries(row) === "anomaly"
- ) {
- mode = "markers";
- }
- const color = farmMode
- ? diagnosticFarmSeriesColor(row, idx, templateKey)
- : pickSeriesColor(row.label, idx);
- const seriesName = statusLegendPick
- ? farmStatusSeriesName(row, statusLegendPick.mode, templateKey)
- : row.label;
- const diagnosticRole = classifyDiagnosticSeries(row);
- const farmStyle = diagnosticFarmStyle(row, templateKey);
- const farmAnomaly = farmMode && diagnosticRole === "anomaly";
- const isMarker = mode.includes("markers");
- const isLine = mode.includes("lines");
- const pairs = buildScatterPairs(row, useTime);
- const pointCount = pairs.length;
- const sourcePointCount = Math.min(
- getRowXData(row, useTime).length,
- getRowYData(row).length,
- );
- const colorMetric = resolveRowColorMetric(row, sourcePointCount);
- const colorExtent = finiteExtent(colorMetric);
- const ignoreColorbar = Boolean(statusLegendPick);
- // 正常系列 x/y 都为空时,色值个数和点数都是 0,不能当成色带。
- // 否则 visualMap 的 min/max 会变成 Infinity / -Infinity,画布渐变报 addColorStop。
- const hasColorbar =
- !ignoreColorbar &&
- sourcePointCount > 0 &&
- Array.isArray(colorMetric) &&
- colorMetric.length === sourcePointCount &&
- Number.isFinite(colorExtent.min) &&
- Number.isFinite(colorExtent.max) &&
- colorExtent.max > colorExtent.min;
- const useBubble = hasColorbar && isBubbleColorbar(row, templateKey);
- const useBubbleSize = hasColorbar && usesBubbleSymbolSize(row, templateKey);
- const scatterPairs = useBubble
- ? sortScatterPairsByColorMetricAsc(pairs)
- : pairs;
- const bladeStyle = farmMode
- ? diagnosticBladeStyle(row.originalLabel)
- : null;
- const symbol = "circle";
- const peak = isYawErrorDetector(templateKey)
- ? findPeakCoord(getRowXData(row, useTime), getRowYData(row))
- : null;
- const markPoint =
- peak && !farmMode
- ? {
- symbol: "diamond",
- symbolSize: 10,
- itemStyle: { color: "#ff9f43" },
- label: {
- formatter: `${peak.x}°`,
- color: theme.legend,
- fontSize: 10,
- },
- data: [
- {
- name: "最大功率",
- coord: [toAxisValue(peak.x, useTime), peak.y],
- },
- ],
- }
- : undefined;
- if (hasColorbar) {
- const { min: cbMin, max: cbMax } = colorExtent;
- const seriesIndex = series.length;
- const useColorbar = !farmAnomaly && diagnosticRole !== "anomaly";
- series.push({
- name: seriesName,
- type: "scatter",
- data: scatterPairs,
- symbol: "circle",
- symbolSize: useBubbleSize
- ? (item) =>
- scaleBubbleSize(
- Array.isArray(item.value) ? item.value[2] : item[2],
- cbMin,
- cbMax,
- )
- : farmMode
- ? diagnosticFarmSymbolSize(row, pointCount, templateKey)
- : templateKey === "pitch_min"
- ? 8
- : pointCount > 5000
- ? 3
- : 6,
- large: false,
- progressive: 0,
- animation: false,
- hoverAnimation: false,
- emphasis: { scale: false },
- clip: true,
- itemStyle: useColorbar
- ? {
- opacity:
- templateKey === "pitch_min" ? 0.92 : densePitchCoord ? 0.72 : 1,
- }
- : {
- color: DIAGNOSTIC_COLORS.anomaly,
- opacity: farmMode ? farmStyle.pointOpacity : 1,
- borderColor: "#ffffff",
- borderWidth: 1,
- },
- __farmExtra: row.extra || null,
- ...(farmMode
- ? farmSeriesPaintOrder(row, templateKey)
- : { z: diagnosticSingleSeriesZ(row), z2: 0, zlevel: 0 }),
- });
- if (useColorbar) {
- const sharedPitchColorbar = farmMode && templateKey === "pitch_coord";
- const existingVisualMap = sharedPitchColorbar ? visualMaps[0] : null;
- if (existingVisualMap) {
- existingVisualMap.min = Math.min(existingVisualMap.min, cbMin);
- existingVisualMap.max = Math.max(existingVisualMap.max, cbMax);
- existingVisualMap.seriesIndex.push(seriesIndex);
- } else {
- visualMaps.push({
- show: true,
- dimension: 2,
- min: cbMin,
- max: cbMax,
- calculable: false,
- orient: "vertical",
- right: 6,
- top: "middle",
- text: [
- row.colorbarTitle ||
- row.colorbartitle ||
- (useBubble ? "点数" : "桨距角 (°)"),
- ],
- textStyle: { color: theme.legend, fontSize: 10 },
- inRange: {
- // 页面点数:暗→亮蓝(点数越高越醒目);报告色带另用 DIAGNOSTIC_COUNT_COLORS
- color: useBubble
- ? PAGE_DIAGNOSTIC_COUNT_COLORS
- : DIAGNOSTIC_CONTINUOUS_COLORS,
- },
- seriesIndex: sharedPitchColorbar ? [seriesIndex] : seriesIndex,
- });
- }
- }
- return;
- }
- if (isMarker && !isLine) {
- series.push({
- name: seriesName,
- type: "scatter",
- data: pairs,
- symbol,
- symbolSize: farmMode
- ? diagnosticFarmSymbolSize(row, pointCount, templateKey)
- : pointCount > 5000
- ? 3
- : 6,
- large: false,
- progressive: 0,
- animation: false,
- hoverAnimation: false,
- emphasis: { scale: false },
- clip: true,
- itemStyle: {
- color,
- opacity: farmMode
- ? farmStyle.pointOpacity
- : diagnosticRole === "anomaly" || diagnosticRole === "deload"
- ? 1
- : 0.82,
- },
- __farmExtra: row.extra || null,
- ...(farmMode
- ? farmSeriesPaintOrder(row, templateKey)
- : { z: diagnosticSingleSeriesZ(row), z2: 0, zlevel: 0 }),
- });
- return;
- }
- const farmPitchBlade = farmMode && templateKey === "pitch_regulation";
- const lineType = bladeStyle ? bladeStyle.lineType : diagnosticLineType(row);
- if (isLine && !isMarker) {
- const pitchRangeSinglePoint =
- templateKey === "pitch_regulation" &&
- /极差/.test(`${panel.title || ""} ${panel.yaxis || ""}`) &&
- pairs.length === 1;
- const deloadGuide = isDeloadRateGuideLabel(row);
- let lineData = pairs;
- if (deloadGuide && isDeloadThresholdLabel(row)) {
- const axisX = [];
- (panel.data || []).forEach((item) => {
- getRowXData(item, useTime).forEach((x) => {
- axisX.push(toAxisValue(x, useTime));
- });
- });
- const spanned = spanThresholdLinePairs(pairs, axisX);
- if (spanned) lineData = spanned;
- }
- // 只有一个时刻时折线没有线段。累计降载率和阈值都改成画点。
- const showLonePoint =
- (pitchRangeSinglePoint || deloadGuide) &&
- distinctLineXCount(lineData) <= 1;
- if (showLonePoint && lineData.length > 1) {
- lineData = [lineData[0]];
- }
- series.push({
- name: seriesName,
- type: "line",
- data: lineData,
- showSymbol: showLonePoint,
- symbol: showLonePoint ? "circle" : "none",
- symbolSize: showLonePoint ? 10 : 0,
- smooth: false,
- clip: showLonePoint ? false : true,
- lineStyle: {
- ...buildLineStyle(color, row.label),
- type: lineType,
- width: farmMode
- ? farmStyle.lineWidth
- : diagnosticRole === "contract"
- ? 3.8
- : row.__shared
- ? 2.5
- : 2,
- opacity: farmMode ? farmStyle.lineOpacity : 1,
- },
- itemStyle: { color },
- ...(farmMode
- ? farmSeriesPaintOrder(row, templateKey)
- : { z: diagnosticSingleSeriesZ(row), z2: 0, zlevel: 0 }),
- });
- return;
- }
- if (farmPitchBlade) {
- const flags = readPointAnomalyFlags(row);
- const segments = splitConnectedLineByPointAnomaly(
- getRowXData(row, useTime),
- getRowYData(row),
- flags,
- );
- if (Array.isArray(flags) && flags.length && segments.length) {
- const symbol = diagnosticSeriesSymbol(
- row,
- bladeStyle ? bladeStyle.symbol : "circle",
- );
- segments.forEach((segment) => {
- const data = [];
- segment.xData.forEach((xValue, index) => {
- const y = toYNumber(segment.yData[index]);
- if (y == null) return;
- data.push({
- value: [toAxisValue(xValue, useTime), y],
- extra: row.extra || null,
- });
- });
- if (!data.length) return;
- const segmentColor = segment.anomaly
- ? DIAGNOSTIC_COLORS.anomaly
- : DIAGNOSTIC_COLORS.normal;
- const singlePoint = data.length === 1;
- series.push({
- name: pitchSegmentSeriesName(row, segment.anomaly),
- type: "line",
- data,
- clip: true,
- showSymbol: true,
- symbol,
- symbolSize: singlePoint ? 10 : 8,
- smooth: false,
- lineStyle: {
- color: segmentColor,
- type: lineType,
- width: farmStyle.lineWidth,
- opacity: 1,
- },
- itemStyle: { color: segmentColor, opacity: 1 },
- __farmExtra: row.extra || null,
- __bladeSegment: true,
- z: segment.anomaly ? 20 : 2,
- z2: segment.anomaly ? 20 : 0,
- zlevel: 0,
- });
- });
- return;
- }
- }
- series.push({
- name: seriesName,
- type: "line",
- data: pairs,
- clip: true,
- showSymbol:
- (templateKey === "pitch_regulation" &&
- /极差/.test(`${panel.title || ""} ${panel.yaxis || ""}`) &&
- pairs.length === 1) ||
- Boolean(bladeStyle?.showSymbol) ||
- isMarker,
- symbol: diagnosticSeriesSymbol(
- row,
- bladeStyle ? bladeStyle.symbol : "circle",
- ),
- symbolSize:
- templateKey === "pitch_regulation" &&
- /极差/.test(`${panel.title || ""} ${panel.yaxis || ""}`) &&
- pairs.length === 1
- ? 10
- : bladeStyle
- ? 8
- : farmMode
- ? diagnosticFarmSymbolSize(row, pointCount, templateKey)
- : 4,
- smooth: false,
- markPoint,
- lineStyle: {
- ...buildLineStyle(color, row.label),
- type: lineType,
- width: farmMode ? farmStyle.lineWidth : 2,
- opacity: farmMode ? farmStyle.lineOpacity : 1,
- },
- itemStyle: {
- color,
- opacity: farmMode ? farmStyle.pointOpacity : 0.88,
- },
- ...(farmMode
- ? farmSeriesPaintOrder(row, templateKey)
- : { z: diagnosticSingleSeriesZ(row), z2: 0, zlevel: 0 }),
- });
- });
- if (!farmMode) {
- fillEmptyAnomalySeries(series, panel.data, useTime);
- }
- if (!visualMaps.length) {
- series.sort(
- (a, b) =>
- (Number(a.z) || 0) * 100 +
- (Number(a.z2) || 0) -
- ((Number(b.z) || 0) * 100 + (Number(b.z2) || 0)),
- );
- }
- return {
- series: ensureFarmNormalLegendSeries(series, statusLegendPick),
- useTime,
- visualMaps,
- optimizedDenseScatter: false,
- };
- }
- /** 检测器折线/散点图:区域圈选、滚轮缩放、拖拽平移、还原 */
- function buildPlotInteractionOptions(
- theme,
- { hasVisualMap, useTime, hasLegend } = {},
- ) {
- const sliderBottom = hasLegend ? 32 : 8;
- return {
- toolbox: {
- right: hasVisualMap ? 76 : 8,
- top: 4,
- itemSize: 14,
- itemGap: 8,
- showTitle: true,
- iconStyle: { borderColor: theme.legend },
- emphasis: { iconStyle: { borderColor: theme.title } },
- tooltip: {
- show: true,
- backgroundColor: theme.tooltipBg,
- borderColor: theme.tooltipBorder,
- textStyle: { color: theme.tooltipText, fontSize: 11 },
- },
- feature: {
- dataZoom: {
- xAxisIndex: 0,
- yAxisIndex: 0,
- title: { zoom: "区域缩放", back: "缩放还原" },
- },
- restore: { title: "还原" },
- saveAsImage: {
- title: "保存图片",
- pixelRatio: 2,
- backgroundColor: "transparent",
- },
- },
- },
- dataZoom: [
- {
- type: "inside",
- xAxisIndex: 0,
- filterMode: "none",
- zoomOnMouseWheel: true,
- moveOnMouseMove: true,
- preventDefaultMouseMove: true,
- },
- {
- type: "inside",
- yAxisIndex: 0,
- filterMode: "none",
- zoomOnMouseWheel: "shift",
- moveOnMouseMove: true,
- preventDefaultMouseMove: true,
- },
- ...(useTime
- ? [
- {
- type: "slider",
- xAxisIndex: 0,
- filterMode: "none",
- height: 16,
- bottom: sliderBottom,
- borderColor: theme.dataZoomBorder,
- backgroundColor: theme.dataZoomBg,
- fillerColor: theme.dataZoomFiller,
- handleSize: "80%",
- showDetail: false,
- brushSelect: false,
- },
- ]
- : []),
- ],
- };
- }
- const PO_TO_WIND_POWER_3D_TEMPLATE = {
- anomalyPowercurvePO: "wind_power_curve",
- anomalyScatterPO: "wind_power_scatter",
- };
- function resolveWindPowerTemplateKey(options = {}) {
- if (options.templateKey) return options.templateKey;
- const key = options.detectorKey || options.poKey || "";
- return PO_TO_WIND_POWER_3D_TEMPLATE[key] || "";
- }
- function buildWindPower3DOption(panel, options = {}) {
- const theme = chartTheme();
- const farmMode = Boolean(options.farmMode);
- const templateKey = resolveWindPowerTemplateKey(options);
- return buildWindPower3DPanelOption(panel, {
- ...options,
- templateKey,
- farmMode,
- theme,
- reportMode: false,
- pickColor: (label, index, row) =>
- farmMode
- ? diagnosticFarmSeriesColor(row, index, templateKey)
- : pickSeriesColor(label, index),
- });
- }
- function seriesAxisExtent(series = []) {
- let xMin = Infinity;
- let xMax = -Infinity;
- let yMin = Infinity;
- let yMax = -Infinity;
- let found = false;
- (series || []).forEach((item) => {
- (item.data || []).forEach((point) => {
- const coord = Array.isArray(point)
- ? point
- : Array.isArray(point?.value)
- ? point.value
- : null;
- 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,
- };
- }
- function pickValueCoord(item) {
- if (Array.isArray(item?.value)) return item.value;
- if (Array.isArray(item?.data?.value)) return item.data.value;
- if (Array.isArray(item?.data)) return item.data;
- return [item?.value];
- }
- function buildValueBarOption(panel, theme) {
- const categories = panel.categories || [];
- const detector = panel.detectorName || "";
- const colorMode = panel.colorMode || "";
- const showMean =
- panel.showMean !== false && Number.isFinite(Number(panel.mean));
- const isCountChart = colorMode === "yawErrorAbsCount";
- const isAbsRangeChart = colorMode === "yawErrorAbs";
- const isFarmStatusChart = colorMode === "farmStatus";
- const compactAxis = categories.length <= 6;
- const farmStatusBars = isFarmStatusChart
- ? buildFarmStatusBarSeries({
- categories,
- values: panel.values || [],
- extras: panel.extras || [],
- anomalyFlags: panel.anomalyFlags || [],
- })
- : null;
- const legendNames = isAbsRangeChart
- ? YAW_ERROR_ABS_RANGES.map((item) => item.label)
- : farmStatusBars
- ? farmStatusBars.legendNames
- : showMean
- ? ["风场均值"]
- : [];
- const data =
- isAbsRangeChart || isFarmStatusChart
- ? null
- : categories.map((name, idx) => {
- const anomaly = Boolean(panel.anomalyFlags?.[idx]);
- const value = panel.values?.[idx];
- return {
- name,
- value,
- extra: panel.extras?.[idx],
- itemStyle: {
- color: isCountChart
- ? panel.colors?.[idx] || yawErrorAbsRangeColorByIndex(idx)
- : farmStatusPaletteColor(
- { extra: { anomaly }, turbineIndex: idx },
- idx,
- ),
- },
- };
- });
- const absRangeSeries = isAbsRangeChart
- ? YAW_ERROR_ABS_RANGES.map((range) => ({
- type: "bar",
- name: range.label,
- color: range.color,
- itemStyle: { color: range.color },
- data: categories.map((name, idx) => {
- const value = panel.values?.[idx];
- const hit = classifyYawErrorAbsRange(value);
- if (!hit || hit.key !== range.key) return null;
- return {
- name,
- value,
- extra: panel.extras?.[idx],
- itemStyle: { color: range.color },
- };
- }),
- barMaxWidth: 28,
- barGap: "-100%",
- }))
- : [];
- const barSeries = isAbsRangeChart
- ? absRangeSeries
- : isFarmStatusChart
- ? farmStatusBars.series
- : [
- {
- type: "bar",
- name: panel.yaxis || "检测值",
- data,
- barMaxWidth: 28,
- label: isCountChart
- ? {
- show: true,
- position: "top",
- color: theme.axis,
- fontSize: 11,
- }
- : undefined,
- },
- ];
- const legendData = legendNames.map((name) => {
- const range = isAbsRangeChart
- ? YAW_ERROR_ABS_RANGES.find((item) => item.label === name)
- : null;
- if (range) {
- return { name, itemStyle: { color: range.color } };
- }
- if (isFarmStatusChart && name === FARM_STATUS_NORMAL_LEGEND) {
- return { name, itemStyle: { color: DIAGNOSTIC_COLORS.normal } };
- }
- if (isFarmStatusChart) {
- return { name, itemStyle: { color: DIAGNOSTIC_COLORS.anomaly } };
- }
- return name;
- });
- return {
- backgroundColor: "transparent",
- color: isAbsRangeChart
- ? YAW_ERROR_ABS_RANGES.map((range) => range.color)
- : undefined,
- ...buildPlotInteractionOptions(theme, {
- hasVisualMap: false,
- useTime: false,
- hasLegend: legendNames.length > 0,
- }),
- tooltip: {
- ...getTooltipBase(),
- trigger: isAbsRangeChart || isFarmStatusChart ? "axis" : "item",
- backgroundColor: theme.tooltipBg,
- borderColor: theme.tooltipBorder,
- textStyle: { color: theme.tooltipText },
- formatter(params) {
- const list = Array.isArray(params) ? params : [params];
- const hit = list.find((item) => item && item.value != null) || list[0];
- if (!hit) return "";
- const extra = hit?.data?.extra || {};
- const hover = formatFarmValueHover(extra, detector);
- const unit = isYawCountDetector(detector) ? "次" : "";
- return `${hit.marker || ""}${hit.name}<br/>${panel.yaxis || "数值"}: ${
- hit.value
- }${unit}${hover ? `<br/>${hover}` : ""}`;
- },
- },
- legend: legendNames.length
- ? {
- type: legendNames.length > 10 ? "scroll" : "plain",
- bottom: 4,
- left: "center",
- textStyle: { color: theme.legend, fontSize: 10 },
- data: legendData,
- }
- : undefined,
- grid: {
- top: 36,
- left: 52,
- right: 24,
- bottom: legendNames.length ? 56 : 40,
- containLabel: true,
- },
- xAxis: {
- type: "category",
- data: categories,
- name: panel.xaxis || "风机",
- nameLocation: "middle",
- nameGap: compactAxis ? 24 : 28,
- nameTextStyle: { color: theme.axis, fontSize: 10 },
- axisLabel: {
- color: theme.axis,
- fontSize: 10,
- hideOverlap: true,
- rotate: compactAxis ? 0 : 30,
- },
- axisLine: { lineStyle: { color: theme.split } },
- },
- yAxis: {
- type: "value",
- name: panel.yaxis || "",
- nameLocation: "middle",
- nameGap: 42,
- minInterval: isCountChart ? 1 : undefined,
- nameTextStyle: { color: theme.axis, fontSize: 10 },
- axisLabel: {
- color: theme.axis,
- fontSize: 10,
- formatter: isYawCountDetector(detector)
- ? (value) => `${value}次`
- : undefined,
- },
- splitLine: { lineStyle: { color: theme.split, type: "dashed" } },
- },
- series: [
- ...barSeries,
- ...(showMean
- ? [
- {
- type: "line",
- name: "风场均值",
- data: categories.map(() => panel.mean),
- symbol: "none",
- lineStyle: { type: "dashed", width: 2, color: "#f2c84e" },
- },
- ]
- : []),
- ],
- };
- }
- function yawErrorAbsRangeColorByIndex(idx) {
- return YAW_ERROR_ABS_RANGES[idx]?.color || YAW_ERROR_ABS_RANGES[0].color;
- }
- export function buildPanelChartOption(panel, options = {}) {
- const farmMode = Boolean(options.farmMode);
- const templateKey =
- resolveTemplateKey(options, panel?.detectorName) ||
- resolveWindPowerTemplateKey(options);
- if (shouldUseWindPower3D(templateKey, farmMode)) {
- return buildWindPower3DOption(panel, { ...options, templateKey, farmMode });
- }
- const theme = chartTheme();
- if (isValueBarPanel(panel)) {
- return buildValueBarOption(panel, theme);
- }
- if (isCompositionPanel(panel)) {
- return buildCompositionOption(panel, theme);
- }
- const { series, useTime, visualMaps, optimizedDenseScatter } = buildSeries(
- panel,
- theme,
- {
- ...options,
- templateKey,
- },
- );
- const anomalyLegendData = series
- .filter((item) => classifyDiagnosticSeries(item.name) === "anomaly")
- .map((item) => item.name);
- const compactFarmDiagnostic =
- farmMode &&
- series.length > 12 &&
- anomalyLegendData.length > 0 &&
- !shouldKeepFullFarmLegend(templateKey);
- const denseFarmChart =
- Boolean(optimizedDenseScatter) || (farmMode && series.length > 12);
- const statusLegendPick =
- farmMode && usesFarmStatusLegend(templateKey)
- ? pickFarmStatusLegend(panel.data, templateKey)
- : null;
- const bladeSegmentLegend = series.some((item) => item.__bladeSegment);
- const legendData = bladeSegmentLegend
- ? pitchSegmentLegendData(series)
- : statusLegendPick
- ? statusLegendPick.names
- : compactFarmDiagnostic
- ? anomalyLegendData
- : series.map((item) => item.name);
- const hasLegend = statusLegendPick
- ? legendData.length > 0
- : legendData.length > 0 && series.length > 1;
- const xName = panel.xaxis || "";
- const yName = panel.yaxis || "";
- const hasVisualMap = visualMaps.length > 0;
- const timeAxisBottom = hasLegend ? 82 : 66;
- const sliderExtraBottom = useTime ? (hasLegend ? 28 : 22) : 0;
- const panelSubtitle = resolvePanelSubtitle(panel);
- const xAxis = {
- type: useTime ? "time" : "value",
- name: xName,
- nameLocation: "middle",
- nameGap: useTime ? (hasLegend ? 48 : 40) : hasLegend ? 36 : 28,
- nameTextStyle: { color: theme.axis, fontSize: 10 },
- ...(useTime ? {} : { scale: true }),
- axisLabel: {
- color: theme.axis,
- fontSize: 10,
- hideOverlap: true,
- margin: useTime ? 10 : 8,
- formatter: useTime ? (v) => formatTimeAxisLabel(v) : undefined,
- },
- axisLine: { lineStyle: { color: theme.split } },
- splitLine: { lineStyle: { color: theme.split, type: "dashed" } },
- };
- const yAxis = {
- type: "value",
- name: yName,
- nameLocation: "middle",
- nameGap: 42,
- nameTextStyle: { color: theme.axis, fontSize: 10 },
- scale: true,
- axisLabel: {
- color: theme.axis,
- fontSize: 10,
- formatter: isYawCountDetector(templateKey)
- ? (value) => `${value}次`
- : undefined,
- },
- splitLine: { lineStyle: { color: theme.split, type: "dashed" } },
- axisLine: { lineStyle: { color: theme.split } },
- ...(isYawCountDetector(templateKey) ? { minInterval: 1 } : {}),
- };
- const pitchCoord = (templateKey || panel.detectorName) === "pitch_coord";
- const xRangeLocked = pitchCoord
- ? applyPitchCoordXAxis(xAxis, panel.xrange)
- : false;
- const yRangeLocked = pitchCoord
- ? lockAxisToRange(yAxis, panel.yrange)
- : false;
- if (farmMode && templateKey === "aero_cp" && !useTime) {
- const extent = seriesAxisDataMin(series);
- applyZeroStartAxis(xAxis, extent.xMin);
- applyZeroStartAxis(yAxis, extent.yMin);
- }
- const plottedExtent = seriesAxisExtent(series);
- if (plottedExtent && !pitchCoord) {
- const xFitsAxis = !useTime || Number(plottedExtent.xMin) > 1e11;
- if (xFitsAxis) {
- expandAxisToExtent(xAxis, plottedExtent.xMin, plottedExtent.xMax);
- }
- expandAxisToExtent(yAxis, plottedExtent.yMin, plottedExtent.yMax);
- } else if (plottedExtent && pitchCoord) {
- const xFitsAxis = !useTime || Number(plottedExtent.xMin) > 1e11;
- if (xFitsAxis && !xRangeLocked) {
- expandAxisToExtent(xAxis, plottedExtent.xMin, plottedExtent.xMax);
- }
- if (!yRangeLocked) {
- expandAxisToExtent(yAxis, plottedExtent.yMin, plottedExtent.yMax);
- }
- }
- const hasScatterLegend = series.some(
- (item) =>
- item.type === "scatter" &&
- (item.data || []).length > 0 &&
- !item.__bladePointOverlay,
- );
- return {
- backgroundColor: "transparent",
- ...buildPlotInteractionOptions(theme, { hasVisualMap, useTime, hasLegend }),
- title: panelSubtitle
- ? {
- show: true,
- text: panelSubtitle,
- left: 8,
- top: 6,
- textStyle: {
- color: theme.axis,
- fontSize: 10,
- fontWeight: 400,
- width: 520,
- },
- }
- : undefined,
- grid: {
- top: panelSubtitle ? 52 : hasLegend ? 44 : 36,
- left: yName ? 58 : 52,
- right: hasVisualMap ? 72 : 24,
- bottom:
- (useTime
- ? timeAxisBottom
- : xName
- ? hasLegend
- ? 68
- : 52
- : hasLegend
- ? 56
- : 40) + sliderExtraBottom,
- containLabel: true,
- },
- tooltip: {
- ...getTooltipBase(),
- trigger: denseFarmChart ? "item" : "axis",
- axisPointer: {
- type: denseFarmChart ? "none" : useTime ? "line" : "cross",
- },
- backgroundColor: theme.tooltipBg,
- borderColor: theme.tooltipBorder,
- textStyle: { color: theme.tooltipText },
- formatter(params) {
- const items = Array.isArray(params) ? params : params ? [params] : [];
- if (!items.length) return "";
- const p = items[0];
- const coord = pickValueCoord(p);
- const xVal = coord[0];
- const yVal = coord[1];
- const xText = useTime ? formatTimeLabel(xVal) : xVal;
- const pointHover = formatPointHover(p.data?.point);
- const isPcaProjection =
- /PCA|降维投影/.test(panel.title || "") ||
- /^PC\d/i.test(String(panel.xaxis || "").trim());
- const hideSeriesValue =
- isPcaProjection ||
- (/转速/.test(panel.title || "") && /功率/.test(panel.title || ""));
- if (isPcaProjection) {
- const lines = [];
- items.forEach((item) => {
- if (item.seriesName) {
- lines.push(`${item.marker || ""}${item.seriesName}`);
- }
- const extra = formatPointHover(item.data?.point);
- if (extra) lines.push(extra);
- });
- return lines.join("<br/>");
- }
- if (denseFarmChart) {
- const extraHover = formatFarmSeriesHover(
- p.data?.extra || series[p.seriesIndex]?.__farmExtra,
- templateKey || panel.detectorName,
- );
- const unit = isYawCountDetector(templateKey) ? "次" : "";
- return `${p.marker || ""}${p.seriesName}<br/>${
- panel.xaxis || "X"
- }: ${xText}<br/>${panel.yaxis || "Y"}: ${yVal}${unit}${
- pointHover ? `<br/>${pointHover}` : ""
- }${extraHover ? `<br/>${extraHover}` : ""}`;
- }
- const unit = isYawCountDetector(templateKey) ? "次" : "";
- let html = `${panel.xaxis || "X"}: ${xText}<br/>`;
- items.forEach((item) => {
- const itemCoord = pickValueCoord(item);
- html += hideSeriesValue
- ? `${item.marker || ""}${item.seriesName}<br/>`
- : `${item.marker}${item.seriesName}: ${itemCoord[1]}${unit}<br/>`;
- const extra = formatPointHover(item.data?.point);
- if (extra) html += `${extra}<br/>`;
- const model = item.data?.extra?.model;
- if (!farmMode && model != null && model !== "") {
- html += `机型: ${model}<br/>`;
- }
- const extraHover = farmMode
- ? formatFarmSeriesHover(
- item.data?.extra,
- templateKey || panel.detectorName,
- )
- : "";
- if (extraHover) html += `${extraHover}<br/>`;
- });
- return html;
- },
- },
- legend: hasLegend
- ? {
- type: "scroll",
- bottom: 4,
- left: "center",
- icon: hasScatterLegend ? "circle" : undefined,
- itemWidth: hasScatterLegend ? 10 : 14,
- itemHeight: hasScatterLegend ? 10 : 8,
- textStyle: { color: theme.legend, fontSize: 10 },
- data: legendData,
- }
- : undefined,
- visualMap: visualMaps.length ? visualMaps : undefined,
- xAxis,
- yAxis,
- series,
- };
- }
- export function buildFarmPanelChartOption(panel, options = {}) {
- return buildPanelChartOption(panel, {
- ...options,
- farmMode: true,
- });
- }
|