/**
* 风功率曲线 / 风功率散点 — 全场 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
? `
${WIND_POWER_3D_AXIS.x}: ${turbineText}`
: "";
return `${params.marker || ""}${params.seriesName || ""}
${WIND_POWER_3D_AXIS.y}: ${windSpeed}${turbineLine}
${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),
});
}