| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- /**
- * 算法检测器 JSON(单机图 / 风场总图)→ 前端与报告共用的面板结构。
- * 单机:通用 data[],或多面板 panels[]。
- * 风场:chartType=series 曲线叠加,chartType=value 单值柱状对比。
- */
- export const PO_TO_TEMPLATE_KEY = {
- anomalyPowercurvePO: "wind_power_curve",
- anomalyScatterPO: "wind_power_scatter",
- anomalyStaticyawPO: "yaw_static",
- anomalyCabletwistPO: "yaw_twist",
- anomalyYawErrorPO: "yaw_error",
- anomalyYawCountPO: "yaw_count",
- anomalyPitchcoordPO: "pitch_coord",
- anomalyPitchregulationPO: "pitch_regulation",
- anomalyMinpitchPO: "pitch_min",
- anomalyPowerqualityPO: "ctrl_power_quality",
- anomalyOperationPO: "ctrl_op_state",
- anomalyDeloadPO: "ctrl_deload",
- anomalyCpPO: "aero_cp",
- anomalyTsrPO: "aero_tsr",
- anomalyCpTsrPO: "aero_tsr_wind",
- };
- export const FARM_VALUE_AXIS = {
- yaw_error: "静态偏航误差 (°)",
- yaw_count: "偏航次数",
- pitch_min: "最小桨距角漂移 (°)",
- };
- export const FARM_VALUE_TITLES = {
- yaw_error: "全场静态偏航误差",
- yaw_count: "全场偏航次数",
- pitch_min: "全场最小桨距角漂移",
- };
- export const FARM_SERIES_TITLES = {
- wind_power_curve: "全场功率曲线分析",
- wind_power_scatter: "全场风功率散点分析",
- yaw_static: "全场静态偏航异常分布",
- yaw_twist: "全场扭缆分析",
- pitch_regulation: "全场变桨一致性分析",
- pitch_coord: "全场变桨协调分析",
- ctrl_power_quality: "全场电能质量分析",
- ctrl_op_state: "全场运行状态分析",
- ctrl_deload: "全场降载判定分析",
- aero_cp: "全场Cp功率系数分析",
- aero_tsr: "全场TSR叶尖速比分析",
- aero_tsr_wind: "全场TSR-Cp联合分析",
- };
- export function resolveTemplateKey(options = {}, detectorKey = "") {
- if (options.templateKey) return options.templateKey;
- const key = options.detectorKey || options.poKey || detectorKey || "";
- return PO_TO_TEMPLATE_KEY[key] || options.detectorName || "";
- }
- export function isYawErrorDetector(templateKey) {
- return templateKey === "yaw_error";
- }
- export function isYawCountDetector(templateKey) {
- return templateKey === "yaw_count";
- }
- export function isPitchMinDetector(templateKey) {
- return templateKey === "pitch_min";
- }
- export function isFarmAggregateJson(plotJson) {
- return Boolean(
- plotJson &&
- (plotJson.chartType === "series" || plotJson.chartType === "value") &&
- Array.isArray(plotJson.turbines),
- );
- }
- export function isValueBarPanel(panel) {
- return panel?.chartKind === "valueBar";
- }
- export function isBubbleColorbar(row = {}, templateKey = "") {
- if (isPitchMinDetector(templateKey)) return true;
- return /点数/.test(String(row.colorbarTitle || row.colorbartitle || ""));
- }
- export function findPeakCoord(xArr = [], yArr = []) {
- const len = Math.min(xArr.length, yArr.length);
- if (!len) return null;
- let maxIdx = 0;
- let maxY = Number(yArr[0]);
- for (let i = 1; i < len; i += 1) {
- const y = Number(yArr[i]);
- if (Number.isFinite(y) && (!Number.isFinite(maxY) || y > maxY)) {
- maxY = y;
- maxIdx = i;
- }
- }
- if (!Number.isFinite(maxY)) return null;
- return { x: xArr[maxIdx], y: maxY, index: maxIdx };
- }
- export function formatPcaSubtitle(panel = {}) {
- const defs = Array.isArray(panel.componentDefinition)
- ? panel.componentDefinition
- : [];
- if (!defs.length) return "";
- return defs
- .map((item) => {
- const ratio =
- item?.explainedVarianceRatio != null
- ? ` ${(Number(item.explainedVarianceRatio) * 100).toFixed(1)}%`
- : "";
- const loads = (item?.loadings || [])
- .slice(0, 4)
- .map((load) => {
- const name = load.displayName || load.feature || "";
- const coef = Number(load.coefficient);
- return `${name}${Number.isFinite(coef) ? `:${coef.toFixed(2)}` : ""}`;
- })
- .filter(Boolean)
- .join(", ");
- return `${item.name || "PC"}${ratio}${loads ? ` (${loads})` : ""}`;
- })
- .join(" | ");
- }
- function copyPanelMeta(panel, plotJson = {}) {
- return {
- title: panel.panelTitle || panel.title || plotJson.title || "",
- xaxis: panel.xaxis || plotJson.xaxis || "",
- yaxis: panel.yaxis || plotJson.yaxis || "",
- xrange: panel.xrange || plotJson.xrange,
- yrange: panel.yrange || plotJson.yrange,
- data: panel.data || [],
- componentDefinition: panel.componentDefinition,
- rawMeans: panel.rawMeans,
- detectorName: plotJson.detector_name || plotJson.analysisType || plotJson.detector || "",
- };
- }
- export function unwrapFarmPlotJson(raw) {
- if (!raw || typeof raw !== "object") return null;
- if (raw.__imageUrl) return raw;
- if (isFarmAggregateJson(raw)) return raw;
- if (raw.data && isFarmAggregateJson(raw.data)) return raw.data;
- return raw;
- }
- export function extractDetectorPanels(plotJson, options = {}) {
- const json = unwrapFarmPlotJson(plotJson);
- if (!json) return [];
- if (Array.isArray(json.panels) && json.panels.length) {
- return json.panels.map((panel) => copyPanelMeta(panel, json));
- }
- if (isFarmAggregateJson(json)) {
- return farmAggregateToPanels(json, options);
- }
- return [copyPanelMeta(json, json)];
- }
- function formatTurbineLabel(name, nameMap = {}) {
- const raw = String(name || "");
- if (!raw) return "--";
- const mapped = nameMap[raw] || nameMap[String(raw)];
- const text = mapped ? String(mapped) : raw;
- if (/号$/.test(text)) return text;
- if (/^\d+$/.test(text)) return `${text}号`;
- return text;
- }
- function meanOf(values = []) {
- const nums = values.map(Number).filter((n) => Number.isFinite(n));
- if (!nums.length) return null;
- return nums.reduce((sum, n) => sum + n, 0) / nums.length;
- }
- function farmSeriesMode(detector, item = {}) {
- if (item.xType === "time") return "markers";
- if (detector === "wind_power_curve") return "lines";
- if (detector === "pitch_regulation") return "lines+markers";
- return "markers";
- }
- function farmSeriesKind(item = {}) {
- if (item.status) return String(item.status);
- if (item.anomaly) return "异常";
- return "正常";
- }
- function sliceByFlag(item, flags, keepAnomaly) {
- const xData = [];
- const yData = [];
- const timeData = [];
- const sourceTime = Array.isArray(item.timeData) ? item.timeData : [];
- (item.xData || []).forEach((x, idx) => {
- const flagged = Boolean(flags[idx]);
- if (flagged !== keepAnomaly) return;
- xData.push(x);
- yData.push((item.yData || [])[idx]);
- if (sourceTime[idx] != null) timeData.push(sourceTime[idx]);
- });
- return { xData, yData, timeData };
- }
- function buildFarmSeriesRows(item, detector, nameMap) {
- const turbineName = formatTurbineLabel(item.turbine, nameMap);
- const mode = farmSeriesMode(detector, item);
- const flags = Array.isArray(item.pointAnomaly) ? item.pointAnomaly : [];
- const canSplit =
- flags.length && flags.length === (item.xData || []).length;
- const rows = canSplit
- ? [
- { kind: "正常", sliced: sliceByFlag(item, flags, false) },
- { kind: "异常", sliced: sliceByFlag(item, flags, true) },
- ].filter((row) => row.sliced.xData.length)
- : [{ kind: farmSeriesKind(item), sliced: null }];
- return rows.map((row) => {
- const sliced = row.sliced;
- return {
- label: `${turbineName} ${row.kind}`,
- originalLabel: row.kind,
- mode,
- xData: sliced ? sliced.xData : item.xData || [],
- yData: sliced ? sliced.yData : item.yData || [],
- timeData: sliced
- ? sliced.timeData
- : item.xType === "time"
- ? item.xData || []
- : [],
- pointAnomaly: canSplit ? undefined : item.pointAnomaly,
- __farm: true,
- anomaly: Boolean(item.anomaly),
- extra: item,
- };
- });
- }
- export function farmAggregateToPanels(farmJson = {}, options = {}) {
- const detector = farmJson.detector || options.templateKey || "";
- const turbines = Array.isArray(farmJson.turbines) ? farmJson.turbines : [];
- const nameMap = options.nameMap || {};
- const title =
- options.title ||
- FARM_VALUE_TITLES[detector] ||
- FARM_SERIES_TITLES[detector] ||
- farmJson.farm ||
- "全场检测汇总";
- if (farmJson.chartType === "value") {
- const values = turbines.map((item) => Number(item.value));
- return [
- {
- chartKind: "valueBar",
- title,
- detectorName: detector,
- xaxis: "风机",
- yaxis: FARM_VALUE_AXIS[detector] || "检测值",
- categories: turbines.map((item) =>
- formatTurbineLabel(item.turbine, nameMap),
- ),
- values,
- anomalyFlags: turbines.map((item) => Boolean(item.anomaly)),
- extras: turbines,
- mean: meanOf(values),
- },
- ];
- }
- return [
- {
- title,
- xaxis: farmJson.xaxis || "",
- yaxis: farmJson.yaxis || "",
- xrange: farmJson.xrange,
- yrange: farmJson.yrange,
- detectorName: detector,
- data: turbines.flatMap((item, turbineIndex) =>
- buildFarmSeriesRows(item, detector, nameMap).map((row) => ({
- ...row,
- turbineIndex,
- engineName: formatTurbineLabel(item.turbine, nameMap),
- })),
- ),
- },
- ];
- }
- export function formatPointHover(point) {
- if (!point || typeof point !== "object") return "";
- const lines = [];
- if (point.dataTime) lines.push(`时间: ${point.dataTime}`);
- const appendMap = (obj, prefix) => {
- if (!obj || typeof obj !== "object") return;
- Object.keys(obj).forEach((key) => {
- const val = obj[key];
- if (val == null || val === "") return;
- lines.push(`${prefix}${key}: ${val}`);
- });
- };
- appendMap(point.raw, "");
- appendMap(point.deviation, "偏差 ");
- appendMap(point.mean, "均值 ");
- appendMap(point.meanDeviation, "均值偏差 ");
- return lines.join("<br/>");
- }
- export function formatFarmSeriesHover(extra = {}, detector = "") {
- if (!extra || typeof extra !== "object") return "";
- const lines = [];
- const push = (label, val, suffix = "") => {
- if (val == null || val === "") return;
- lines.push(`${label}: ${val}${suffix}`);
- };
- push("状态", extra.status);
- push("发电量比值", extra.ratio);
- push("异常点数", extra.anomalyPoints);
- push("降载占比", extra.deloadRatio);
- if (detector === "yaw_error") {
- push("误差", extra.value, "°");
- push("发电量损失", extra.powerloss, "%");
- push("覆盖率", extra.coverage);
- }
- if (detector === "yaw_count") {
- push("CW", extra.cw);
- push("CCW", extra.ccw);
- push("覆盖率", extra.coverage);
- }
- if (detector === "pitch_min") {
- push("P10", extra.p10, "°");
- push("基线", extra.baseline, "°");
- }
- return lines.join("<br/>");
- }
- export function formatFarmValueHover(extra = {}, detector = "") {
- if (!extra || typeof extra !== "object") return "";
- const lines = [];
- const push = (label, val, suffix = "") => {
- if (val == null || val === "") return;
- lines.push(`${label}: ${val}${suffix}`);
- };
- if (detector === "yaw_error") {
- push("误差", extra.value, "°");
- push("发电量损失", extra.powerloss, "%");
- push("覆盖率", extra.coverage != null ? Number(extra.coverage) : extra.coverage);
- } else if (detector === "yaw_count") {
- push("次数", extra.value);
- push("CW", extra.cw);
- push("CCW", extra.ccw);
- push("覆盖率", extra.coverage);
- } else if (detector === "pitch_min") {
- push("漂移", extra.value, "°");
- push("P10", extra.p10, "°");
- push("基线", extra.baseline, "°");
- } else {
- push("数值", extra.value);
- }
- return lines.join("<br/>");
- }
- export function scaleBubbleSize(value, min, max, { minSize = 8, maxSize = 28 } = {}) {
- const num = Number(value);
- if (!Number.isFinite(num)) return minSize;
- if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) return 14;
- const ratio = (num - min) / (max - min);
- return minSize + ratio * (maxSize - minSize);
- }
|