| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856 |
- /**
- * 风功率曲线 / 风功率散点 — 全场 3D 图表(前端 + 报告 Node 共用)
- * 展示坐标:X = 风机名称,Y = 风速,Z = 功率(竖直)
- * 原始 panel 数据仍为 xData=风速、yData=功率
- */
- import {
- classifyDiagnosticSeries,
- diagnosticSeriesColor,
- isSharedOverlayCurveSeries,
- isContractPowerCurve,
- DIAGNOSTIC_COLORS,
- DIAGNOSTIC_FARM_STYLES,
- } from "./anomalyDiagnosticVisual.mjs";
- import {
- formatReportValueAxisLabel,
- isPointAnomalyFlag,
- readPointAnomalyFlags,
- resolvePanelSubtitle,
- } from "./anomalyDetectorPlotAdapter.mjs";
- export const WIND_POWER_3D_TEMPLATE_KEYS = new Set([
- "wind_power_curve",
- "wind_power_scatter",
- ]);
- export const WIND_POWER_3D_AXIS = {
- x: "风机名称",
- y: "风速 (m/s)",
- z: "有功功率 (kW)",
- };
- /** 参考图视角:功率轴竖直,风机与风速轴分别向左、右前方展开 */
- const REFERENCE_VIEW_CONTROL = {
- alpha: 25,
- beta: 45,
- center: [0, -4, 0],
- };
- /** Y 轴(机组)最多展示的名称数(折线/散点数据仍全部绘制) */
- export const FARM_WIND_POWER_3D_TURBINE_LABEL_COUNT = 8;
- /** 报告导出时更少标签,避免 3D 轴文字挤成一团 */
- export const FARM_WIND_POWER_3D_REPORT_LABEL_COUNT = 6;
- /** 机组在 Y 轴上的间距(数值越大,各风机切片越分开) */
- const TURBINE_Y_GAP = 10;
- /** 全场 3D 渐变色:提高饱和度与明度,避免深色底上看不清 */
- function farmTurbineGradientColor(turbineIndex, maxIndex, isAnomaly = false) {
- const t =
- maxIndex <= 1 ? 0 : Math.max(0, Math.min(1, (turbineIndex - 1) / (maxIndex - 1)));
- const hue = 88 + t * 172;
- if (isAnomaly) return `hsla(${hue}, 92%, 64%, 1)`;
- return `hsla(${hue}, 88%, 66%, 1)`;
- }
- function isPowerCurveAnomalyTurbine(row) {
- if (row?.anomaly === true || row?.extra?.anomaly === true) return true;
- if (row?.anomaly === false || row?.extra?.anomaly === false) return false;
- return isAnomalyRow(row);
- }
- /** 图例放在三维图右侧,避免压住底部机组轴和曲线 */
- function buildSideLegend(names, { legendColor, top }) {
- if (!names.length) return null;
- return {
- type: names.length > 16 ? "scroll" : "plain",
- orient: "vertical",
- right: 6,
- top,
- bottom: 16,
- width: 118,
- itemWidth: 10,
- itemHeight: 10,
- icon: "circle",
- itemGap: 8,
- textStyle: { color: legendColor, fontSize: 11 },
- formatter: (name) => String(name || "").replace(/\s+异常$/, ""),
- data: names,
- };
- }
- function isAnomalyRow(row) {
- const text = `${row?.originalLabel || ""} ${row?.label || ""}`;
- return /异常/.test(text) && !/正常/.test(text);
- }
- /** 行上仍带 pointAnomaly 时按同下标拆成正常/异常,与报告总图一致 */
- function splitScatterRowByPointAnomaly(row) {
- const xData = Array.isArray(row?.xData) ? row.xData : [];
- const yData = Array.isArray(row?.yData) ? row.yData : [];
- const len = Math.min(xData.length, yData.length);
- const alreadySplit =
- /正常|异常/.test(String(row?.originalLabel || "")) &&
- !Array.isArray(row?.pointAnomaly);
- const flags = alreadySplit ? null : readPointAnomalyFlags(row);
- if (!len || !flags || !flags.length) return [row];
- const normal = { x: [], y: [] };
- const anomaly = { x: [], y: [] };
- for (let i = 0; i < len; i += 1) {
- const bucket = isPointAnomalyFlag(flags[i]) ? anomaly : normal;
- bucket.x.push(xData[i]);
- bucket.y.push(yData[i]);
- }
- const baseLabel = String(row?.engineName || row?.label || "")
- .replace(/\s*(正常数据|异常点|正常|异常)$/u, "")
- .trim();
- const make = (bucket, kind) => {
- if (!bucket.x.length) return null;
- return {
- ...row,
- label: `${baseLabel} ${kind}`.trim(),
- originalLabel: kind,
- xData: bucket.x,
- yData: bucket.y,
- pointAnomaly: undefined,
- anomaly: kind === "异常",
- extra: { ...(row.extra || {}), anomaly: kind === "异常" },
- };
- };
- return [make(normal, "正常"), make(anomaly, "异常")].filter(Boolean);
- }
- function resolveFarmSeriesColor(
- row,
- index,
- turbineMeta,
- pickColor,
- farmMode,
- templateKey = "",
- ) {
- if (!farmMode) return pickColor(row.label, index, row, farmMode);
- const role = classifyDiagnosticSeries(row);
- if (
- role === "contract" &&
- (templateKey === "wind_power_curve" || templateKey === "wind_power_scatter")
- ) {
- return DIAGNOSTIC_COLORS.farmContract;
- }
- if (
- role === "contract" ||
- role === "reference" ||
- role === "warning" ||
- role === "mean"
- ) {
- return diagnosticSeriesColor(row, index);
- }
- if (typeof pickColor === "function") {
- return pickColor(row.label, index, row, farmMode);
- }
- // 无外部 pickColor 时:风功率等状态色检测器走正常蓝/异常红,其余保留渐变
- // 全场不用单机降载黄/停机黑
- if (
- templateKey === "wind_power_curve" ||
- templateKey === "wind_power_scatter"
- ) {
- return role === "anomaly" || role === "deload" || role === "shutdown"
- ? DIAGNOSTIC_COLORS.anomaly
- : DIAGNOSTIC_COLORS.normal;
- }
- if (role === "deload" || role === "shutdown") {
- return DIAGNOSTIC_COLORS.anomaly;
- }
- const ti = resolveTurbineIndex(row, turbineMeta);
- return farmTurbineGradientColor(ti, turbineMeta.maxIndex, role === "anomaly");
- }
- function resolveFarmSeriesVisual(row, templateKey, turbineMax, reportMode) {
- const isRef = isReferenceLineRow(row);
- const isScatter = !isRef && isMarkerRow(row, templateKey);
- const isAnomaly = isAnomalyRow(row);
- const dense = turbineMax > 10;
- if (isRef) {
- const contract = isContractPowerCurve(row);
- return {
- lineWidth: contract
- ? DIAGNOSTIC_FARM_STYLES.contract.lineWidth
- : 2.2,
- lineOpacity: 1,
- lineType: contract ? "solid" : "dashed",
- itemOpacity: 1,
- symbolSize: 3,
- };
- }
- if (isScatter) {
- // 报告白底用小点、低透明度;页面深色底必须不透明,否则正常蓝点会消失
- let symbolSize;
- let itemOpacity;
- if (reportMode && templateKey === "wind_power_scatter") {
- // 正常点略小、略淡;异常点只大一圈,纯红不描边
- const normalSize = dense ? 4 : 5;
- symbolSize = isAnomaly ? normalSize + 2 : normalSize;
- itemOpacity = isAnomaly ? 1 : dense ? 0.45 : 0.62;
- } else if (reportMode) {
- symbolSize = dense ? 2 : 3;
- itemOpacity = dense
- ? isAnomaly
- ? 0.68
- : 0.4
- : isAnomaly
- ? 0.85
- : 0.55;
- } else {
- symbolSize = dense ? 3 : 4;
- itemOpacity = 1;
- }
- return {
- lineWidth: 1,
- lineOpacity: 1,
- lineType: "solid",
- itemOpacity,
- symbolSize,
- };
- }
- return {
- lineWidth: dense ? (reportMode ? 2 : 1.9) : 2.4,
- lineOpacity: isAnomaly ? 1 : dense ? 0.92 : 0.98,
- lineType: "solid",
- itemOpacity: isAnomaly ? 1 : 0.92,
- symbolSize: dense ? (reportMode ? 0 : 1.5) : 2,
- };
- }
- function buildAxisSplitStyle(splitColor, show = true) {
- return {
- show,
- lineStyle: { color: splitColor, type: "solid", opacity: show ? 0.35 : 0 },
- };
- }
- export function isWindPower3DTemplate(templateKey) {
- return WIND_POWER_3D_TEMPLATE_KEYS.has(String(templateKey || ""));
- }
- /** 仅全场(farmMode)风功率曲线/散点使用 3D,单机保持 2D */
- export function shouldUseWindPower3D(templateKey, farmMode = false) {
- return Boolean(farmMode) && isWindPower3DTemplate(templateKey);
- }
- function getRowArrays(row) {
- const xData = Array.isArray(row?.xData) ? row.xData : [];
- const yData = Array.isArray(row?.yData) ? row.yData : [];
- const zData = Array.isArray(row?.zData) ? row.zData : [];
- return { xData, yData, zData };
- }
- function formatEngineLabel(name) {
- if (name == null || name === "") return "";
- const text = String(name);
- if (/号$/.test(text)) return text;
- if (/^\d+$/.test(text)) return `${text}号`;
- return text;
- }
- function resolveTurbineName(row) {
- if (row?.engineName) return formatEngineLabel(row.engineName);
- const label = String(row?.label || "");
- const head = label.split(/\s+/)[0] || "";
- if (/号/.test(head) || /^\d+$/.test(head)) return formatEngineLabel(head);
- return head;
- }
- function buildTurbineAxisMeta(rows = []) {
- const nameToIndex = new Map();
- let maxIndex = 0;
- rows.forEach((row) => {
- if (isReferenceLineRow(row)) return;
- const name = resolveTurbineName(row);
- let turbineIndex =
- row?.turbineIndex != null && Number.isFinite(Number(row.turbineIndex))
- ? Number(row.turbineIndex) + 1
- : null;
- if (turbineIndex == null && name) {
- if (!nameToIndex.has(name)) {
- maxIndex += 1;
- nameToIndex.set(name, maxIndex);
- }
- turbineIndex = nameToIndex.get(name);
- }
- if (turbineIndex != null) {
- maxIndex = Math.max(maxIndex, turbineIndex);
- if (name) nameToIndex.set(name, turbineIndex);
- }
- });
- const indexToName = new Map();
- nameToIndex.forEach((index, name) => indexToName.set(index, name));
- return { maxIndex, indexToName, nameToIndex };
- }
- function resolveTurbineIndex(row, meta) {
- if (row?.turbineIndex != null && Number.isFinite(Number(row.turbineIndex))) {
- return Number(row.turbineIndex) + 1;
- }
- const name = resolveTurbineName(row);
- if (name && meta.nameToIndex.has(name)) return meta.nameToIndex.get(name);
- return 1;
- }
- /** ECharts 3D 坐标:[机组位置, 风速, 功率] */
- function toChartPoint(windSpeed, power, turbineIndex) {
- return [turbineIndex * TURBINE_Y_GAP, windSpeed, power];
- }
- function resolveTurbineIndexFromPosition(position) {
- return Math.round(Number(position) / TURBINE_Y_GAP);
- }
- function resolveFarmTriplets(row, meta) {
- const { xData, yData, zData } = getRowArrays(row);
- const powerArr = zData.length ? zData : yData;
- const turbineIndex = resolveTurbineIndex(row, meta);
- const len = Math.min(xData.length, powerArr.length);
- const triplets = [];
- for (let i = 0; i < len; i += 1) {
- const windSpeed = Number(xData[i]);
- const power = Number(powerArr[i]);
- if (!Number.isFinite(windSpeed) || !Number.isFinite(power)) continue;
- triplets.push(toChartPoint(windSpeed, power, turbineIndex));
- }
- return triplets;
- }
- function isReferenceLineRow(row) {
- if (row?.__shared) return true;
- return isSharedOverlayCurveSeries(row);
- }
- function isMarkerRow(row, templateKey) {
- if (isReferenceLineRow(row)) return false;
- if (templateKey === "wind_power_curve") return false;
- if (templateKey === "wind_power_scatter") return true;
- const mode = String(row?.mode || "lines");
- return mode.includes("markers") && !mode.includes("lines");
- }
- function buildReferenceLineTriplets(row) {
- const { xData, yData, zData } = getRowArrays(row);
- const powerArr = zData.length ? zData : yData;
- const len = Math.min(xData.length, powerArr.length);
- const triplets = [];
- for (let i = 0; i < len; i += 1) {
- const windSpeed = Number(xData[i]);
- const power = Number(powerArr[i]);
- if (!Number.isFinite(windSpeed) || !Number.isFinite(power)) continue;
- triplets.push(toChartPoint(windSpeed, power, 0));
- }
- return triplets;
- }
- function formatTurbineAxisLabel(index, indexToName) {
- if (index === 0) return "";
- return indexToName.get(index) || `${index}号`;
- }
- /** 机组较多时风机名称轴仅抽样展示,避免标签重叠 */
- function buildTurbineAxisLabelConfig(
- maxIndex,
- indexToName,
- reportMode = false,
- ) {
- const maxLabels = reportMode
- ? FARM_WIND_POWER_3D_REPORT_LABEL_COUNT
- : FARM_WIND_POWER_3D_TURBINE_LABEL_COUNT;
- const labeledIndex = new Set();
- if (maxIndex <= maxLabels) {
- for (let index = 1; index <= maxIndex; index += 1) labeledIndex.add(index);
- } else {
- labeledIndex.add(1);
- labeledIndex.add(maxIndex);
- const reserved = labeledIndex.size;
- const slots = Math.max(0, maxLabels - reserved);
- if (slots > 0 && maxIndex > 2) {
- const step = Math.max(1, Math.round((maxIndex - 1) / (slots + 1)));
- for (let index = 1 + step; index < maxIndex; index += step) {
- labeledIndex.add(index);
- if (labeledIndex.size >= maxLabels) break;
- }
- }
- }
- const labelMap = {};
- labeledIndex.forEach((index) => {
- labelMap[String(index * TURBINE_Y_GAP)] = formatTurbineAxisLabel(
- index,
- indexToName,
- );
- });
- return {
- interval: TURBINE_Y_GAP,
- labeledIndex,
- labelMap,
- gap: TURBINE_Y_GAP,
- formatter(v) {
- const position = Number(v);
- if (!Number.isFinite(position)) return "";
- const index = resolveTurbineIndexFromPosition(position);
- if (index < 0 || index > maxIndex) return "";
- if (Math.abs(position - index * TURBINE_Y_GAP) > 0.01) return "";
- if (!labeledIndex.has(index)) return "";
- return formatTurbineAxisLabel(index, indexToName);
- },
- };
- }
- function computePlotExtents(series = []) {
- let windMin = Infinity;
- let windMax = -Infinity;
- let powerMin = Infinity;
- let powerMax = -Infinity;
- series.forEach((item) => {
- (item.data || []).forEach((point) => {
- if (!Array.isArray(point) || point.length < 3) return;
- const [, windSpeed, power] = point;
- if (Number.isFinite(windSpeed)) {
- windMin = Math.min(windMin, windSpeed);
- windMax = Math.max(windMax, windSpeed);
- }
- if (Number.isFinite(power)) {
- powerMin = Math.min(powerMin, power);
- powerMax = Math.max(powerMax, power);
- }
- });
- });
- if (!Number.isFinite(windMin) || !Number.isFinite(windMax)) {
- windMin = 0;
- windMax = 25;
- }
- if (!Number.isFinite(powerMin) || !Number.isFinite(powerMax)) {
- powerMin = 0;
- powerMax = 2000;
- }
- const windPad = Math.max(0.5, (windMax - windMin) * 0.04);
- const powerPad = Math.max(50, Math.abs(powerMax - powerMin) * 0.06);
- return {
- windMin: windMin - windPad,
- windMax: windMax + windPad,
- powerMin: powerMin - powerPad,
- powerMax: powerMax + powerPad,
- };
- }
- function resolveWindSpeedAxisName(panel) {
- const name = String(panel?.xaxis || "").trim();
- if (/风速/.test(name)) return name;
- return WIND_POWER_3D_AXIS.y;
- }
- function resolvePowerAxisName(panel) {
- const name = String(panel?.yaxis || "").trim();
- if (/功率/.test(name)) return name;
- return WIND_POWER_3D_AXIS.z;
- }
- function buildGrid3DLayout({
- hasLegend,
- legendRows = 0,
- hasSubtitle,
- reportMode,
- templateKey = "",
- legendSide = false,
- }) {
- const titleBlock = hasSubtitle ? 50 : 40;
- const reportLook = reportMode || templateKey === "wind_power_scatter";
- const bottom = legendSide
- ? 52
- : legendRows > 1
- ? 108
- : legendRows === 1
- ? 86
- : 72;
- return {
- boxWidth: reportLook ? 96 : 108,
- boxHeight: reportLook ? 88 : 96,
- boxDepth: reportLook ? 158 : 176,
- top: titleBlock,
- left: legendSide ? 4 : hasLegend ? 18 : "5%",
- right: legendSide ? 156 : hasLegend ? 112 : "5%",
- bottom,
- viewControl: {
- projection: "orthographic",
- autoRotate: false,
- distance: reportLook ? 225 : 218,
- orthographicSize: reportLook ? 215 : 150,
- alpha: REFERENCE_VIEW_CONTROL.alpha,
- beta: REFERENCE_VIEW_CONTROL.beta,
- minAlpha: 8,
- maxAlpha: 62,
- rotateSensitivity: 1,
- zoomSensitivity: 1,
- panSensitivity: 0.8,
- center: [...REFERENCE_VIEW_CONTROL.center],
- },
- };
- }
- function defaultPickColor(label, index) {
- const palette = ["#1E7CF8", "#7C5CFF", "#16A34A", "#F59E0B", "#EF4444"];
- const text = String(label || "");
- if (/合同功率/.test(text)) return DIAGNOSTIC_COLORS.contract;
- if (/参考|理论/.test(text)) return "#64748B";
- if (/异常/.test(text) && !/正常/.test(text)) return "#EF4444";
- if (/正常/.test(text)) return "#1E7CF8";
- return palette[index % palette.length];
- }
- /**
- * @param {object} params
- * @param {object} params.panel - { title, xaxis, yaxis, data }
- * @param {string} params.templateKey
- * @param {string} [params.title]
- * @param {boolean} [params.farmMode]
- * @param {function} [params.pickColor]
- * @param {object} [params.theme]
- * @param {boolean} [params.reportMode] - 报告白底样式
- */
- export function buildWindPower3DChartOption({
- panel,
- templateKey,
- title = "",
- farmMode = false,
- pickColor = defaultPickColor,
- theme = {},
- reportMode = false,
- }) {
- const sourceRows = panel?.data || [];
- const isFarmPowerCurve =
- farmMode && templateKey === "wind_power_curve";
- // JSON 里的机组曲线全部绘制。报告图例不列出正常机组。
- const rows = sourceRows;
- const turbineMeta = buildTurbineAxisMeta(rows);
- const turbineMax = Math.max(turbineMeta.maxIndex, 1);
- const turbineSeries = [];
- const referenceSeries = [];
- const legendNames = [];
- rows.forEach((sourceRow, index) => {
- const expanded =
- templateKey === "wind_power_scatter"
- ? splitScatterRowByPointAnomaly(sourceRow)
- : [sourceRow];
- expanded.forEach((row) => {
- let color = resolveFarmSeriesColor(
- row,
- index,
- turbineMeta,
- pickColor,
- farmMode,
- templateKey,
- );
- if (
- reportMode &&
- templateKey === "wind_power_scatter" &&
- isAnomalyRow(row) &&
- !isReferenceLineRow(row)
- ) {
- color = "#FF1E1E";
- }
- const visual = resolveFarmSeriesVisual(row, templateKey, turbineMax, reportMode);
- if (isReferenceLineRow(row)) {
- const data = buildReferenceLineTriplets(row);
- if (!data.length) return;
- referenceSeries.push({
- name: row.label,
- type: "line3D",
- data,
- lineStyle: {
- width: visual.lineWidth,
- color,
- type: visual.lineType,
- opacity: visual.lineOpacity,
- },
- itemStyle: { color, opacity: visual.itemOpacity },
- });
- legendNames.push(row.label);
- return;
- }
- const triplets = resolveFarmTriplets(row, turbineMeta);
- if (!triplets.length) return;
- const seriesName =
- isFarmPowerCurve && isPowerCurveAnomalyTurbine(row)
- ? String(row.engineName || resolveTurbineName(row) || row.label || "")
- : row.label;
- const useScatter = isMarkerRow(row, templateKey);
- const plotData = useScatter
- ? triplets
- : [...triplets].sort((a, b) => a[1] - b[1]);
- const item = {
- name: seriesName,
- type: useScatter ? "scatter3D" : "line3D",
- data: plotData,
- shading: "color",
- symbol: "circle",
- symbolSize: visual.symbolSize,
- lineStyle: useScatter
- ? undefined
- : {
- width: visual.lineWidth,
- color,
- type: visual.lineType,
- opacity: visual.lineOpacity,
- },
- itemStyle: {
- color,
- opacity: visual.itemOpacity,
- ...(visual.borderWidth
- ? {
- borderWidth: visual.borderWidth,
- borderColor: visual.borderColor || "#ffffff",
- }
- : {}),
- },
- };
- turbineSeries.push(item);
- const hideNormalLegend =
- reportMode && isFarmPowerCurve && !isPowerCurveAnomalyTurbine(row);
- if (!hideNormalLegend) legendNames.push(seriesName);
- });
- });
- turbineSeries.sort((a, b) => {
- const rank = (name) =>
- /异常/.test(String(name || "")) && !/正常/.test(String(name || ""))
- ? 1
- : 0;
- return rank(a.name) - rank(b.name);
- });
- const series = [...turbineSeries, ...referenceSeries];
- const plotExtents = computePlotExtents(series);
- const axisColor = theme.axis || (reportMode ? "#64748b" : "#8da3c7");
- const legendColor = theme.legend || (reportMode ? "#334155" : "#cde6ff");
- const titleColor = theme.title || (reportMode ? "#0f172a" : "#e3f2ff");
- const splitColor = theme.split || (reportMode ? "#e2e8f0" : "rgba(98, 121, 186, 0.16)");
- const bg = reportMode ? "#eef4fb" : "transparent";
- const legendData = legendNames;
- const chartTitle = title || panel?.title || "";
- const sideLegend = isFarmPowerCurve
- ? buildSideLegend(legendData, {
- legendColor,
- top: chartTitle ? (reportMode ? 36 : 40) : 12,
- })
- : null;
- const hasLegend = sideLegend ? legendData.length > 0 : legendData.length > 1;
- const chartSubtitle = resolvePanelSubtitle(panel);
- const hasReference = rows.some(isReferenceLineRow);
- const turbineAxisMax = (hasReference ? turbineMax + 0.5 : turbineMax) * TURBINE_Y_GAP;
- const turbineAxisLabel = buildTurbineAxisLabelConfig(
- turbineMax,
- turbineMeta.indexToName,
- reportMode,
- );
- const grid3DLayout = buildGrid3DLayout({
- hasLegend,
- legendSide: Boolean(sideLegend),
- hasSubtitle: Boolean(chartSubtitle),
- reportMode,
- templateKey,
- });
- return {
- backgroundColor: bg,
- animation: reportMode ? false : true,
- animationDuration: reportMode ? 0 : undefined,
- title: chartTitle
- ? {
- text: chartTitle,
- subtext: chartSubtitle,
- left: "center",
- top: reportMode ? 8 : 4,
- textStyle: {
- fontSize: reportMode ? 13 : 12,
- color: titleColor,
- fontWeight: reportMode ? 600 : 500,
- },
- subtextStyle: {
- fontSize: 10,
- color: axisColor,
- opacity: 0.82,
- },
- itemGap: chartSubtitle ? 4 : 8,
- }
- : undefined,
- toolbox: reportMode
- ? undefined
- : {
- show: true,
- top: 4,
- right: sideLegend ? 168 : hasLegend ? 112 : 12,
- itemSize: 14,
- itemGap: 10,
- iconStyle: {
- borderColor: axisColor,
- },
- emphasis: {
- iconStyle: {
- borderColor: titleColor,
- },
- },
- feature: {
- restore: {
- show: true,
- title: "还原视角",
- },
- saveAsImage: {
- show: true,
- title: "保存图片",
- name: chartTitle || "全场风功率图",
- backgroundColor: "#0f172a",
- pixelRatio: 2,
- },
- },
- },
- tooltip: {
- show: true,
- formatter(params) {
- if (!params) return "";
- const val = params.value || params.data;
- if (!Array.isArray(val)) return "";
- const [turbinePosition, windSpeed, power] = val;
- const turbineText = formatTurbineAxisLabel(
- resolveTurbineIndexFromPosition(turbinePosition),
- turbineMeta.indexToName,
- );
- const turbineLine = turbineText
- ? `<br/>${WIND_POWER_3D_AXIS.x}: ${turbineText}`
- : "";
- return `${params.marker || ""}${params.seriesName || ""}<br/>${WIND_POWER_3D_AXIS.y}: ${windSpeed}${turbineLine}<br/>${WIND_POWER_3D_AXIS.z}: ${power}`;
- },
- },
- legend: sideLegend
- ? sideLegend
- : hasLegend
- ? {
- type: "scroll",
- orient: "vertical",
- right: reportMode ? 4 : 0,
- top: chartTitle ? (reportMode ? 40 : 36) : 12,
- bottom: 8,
- width: 96,
- itemWidth: 12,
- itemHeight: 12,
- icon: "circle",
- itemGap: 5,
- pageButtonItemGap: 6,
- pageIconSize: 10,
- textStyle: { color: legendColor, fontSize: 10 },
- data: legendData,
- }
- : undefined,
- grid3D: {
- ...grid3DLayout,
- light: {
- main: {
- intensity: 1.35,
- shadow: reportMode && templateKey !== "wind_power_scatter",
- alpha: 35,
- beta: 120,
- },
- ambient: {
- intensity:
- reportMode && templateKey === "wind_power_scatter" ? 1.05 : 0.92,
- },
- },
- },
- xAxis3D: {
- type: "value",
- name: WIND_POWER_3D_AXIS.x,
- min: 0,
- max: turbineAxisMax,
- interval: turbineAxisLabel.interval,
- nameGap: 28,
- nameTextStyle: { color: axisColor, fontSize: 10 },
- // Puppeteer 无法序列化 formatter,报告侧靠此字段重建抽样标签
- _turbineLabelMeta: {
- gap: turbineAxisLabel.gap,
- labels: turbineAxisLabel.labelMap,
- },
- axisLabel: {
- color: axisColor,
- fontSize: reportMode ? 8 : 9,
- hideOverlap: true,
- showMinLabel: true,
- showMaxLabel: true,
- margin: 4,
- formatter: turbineAxisLabel.formatter,
- },
- axisLine: { lineStyle: { color: splitColor, opacity: 0.65 } },
- splitLine: buildAxisSplitStyle(splitColor, true),
- },
- yAxis3D: {
- type: "value",
- name: resolveWindSpeedAxisName(panel),
- min: plotExtents.windMin,
- max: plotExtents.windMax,
- splitNumber: 5,
- nameGap: 22,
- nameTextStyle: { color: axisColor, fontSize: 10, fontWeight: 500 },
- axisLabel: {
- color: axisColor,
- fontSize: 9,
- margin: 4,
- formatter: reportMode
- ? (value) => formatReportValueAxisLabel(value)
- : undefined,
- },
- axisLine: { lineStyle: { color: splitColor, opacity: 0.8 } },
- splitLine: buildAxisSplitStyle(splitColor, true),
- },
- zAxis3D: {
- type: "value",
- name: resolvePowerAxisName(panel),
- min: plotExtents.powerMin,
- max: plotExtents.powerMax,
- splitNumber: 5,
- nameGap: 24,
- nameTextStyle: { color: axisColor, fontSize: 10, fontWeight: 500 },
- axisLabel: {
- color: axisColor,
- fontSize: 9,
- margin: 4,
- formatter: reportMode
- ? (value) => formatReportValueAxisLabel(value)
- : undefined,
- },
- axisLine: { lineStyle: { color: splitColor, opacity: 0.8 } },
- splitLine: buildAxisSplitStyle(splitColor, true),
- },
- series,
- };
- }
- export function buildWindPower3DPanelOption(panel, options = {}) {
- return buildWindPower3DChartOption({
- panel,
- templateKey: options.templateKey || "",
- title: options.title || panel?.title || "",
- farmMode: Boolean(options.farmMode),
- pickColor: options.pickColor,
- theme: options.theme || {},
- reportMode: Boolean(options.reportMode),
- });
- }
|