|
@@ -0,0 +1,436 @@
|
|
|
|
|
+import * as echarts from "echarts";
|
|
|
|
|
+
|
|
|
|
|
+function formatHeatPercent(value) {
|
|
|
|
|
+ const pct = Math.max(0, Math.min(100, Number(value) || 0));
|
|
|
|
|
+ return `${Math.round(pct)}%`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function matrixToHeat(matrix) {
|
|
|
|
|
+ const data = [];
|
|
|
|
|
+ matrix.forEach((row, y) => {
|
|
|
|
|
+ row.forEach((value, x) => {
|
|
|
|
|
+ data.push([x, y, Math.max(0, Math.min(100, Number(value) || 0))]);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ return data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function buildRadarOption({
|
|
|
|
|
+ indicators,
|
|
|
|
|
+ values,
|
|
|
|
|
+ seriesName,
|
|
|
|
|
+ color,
|
|
|
|
|
+}) {
|
|
|
|
|
+ const safeIndicators = (indicators || []).map((ind) => ({
|
|
|
|
|
+ ...ind,
|
|
|
|
|
+ max: Math.max(Number(ind.max) || 0, 1),
|
|
|
|
|
+ }));
|
|
|
|
|
+ const safeValues = safeIndicators.map(
|
|
|
|
|
+ (_, idx) => Number(values?.[idx]) || 0,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (!safeIndicators.length) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ backgroundColor: "transparent",
|
|
|
|
|
+ graphic: {
|
|
|
|
|
+ type: "text",
|
|
|
|
|
+ left: "center",
|
|
|
|
|
+ top: "middle",
|
|
|
|
|
+ style: {
|
|
|
|
|
+ text: "暂无数据",
|
|
|
|
|
+ fill: "#8db6d1",
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const areaGradient = new echarts.graphic.RadialGradient(0.5, 0.5, 0.85, [
|
|
|
|
|
+ { offset: 0, color: color.areaInner || color.area },
|
|
|
|
|
+ { offset: 0.65, color: color.areaMid || color.area },
|
|
|
|
|
+ { offset: 1, color: color.areaOuter || "rgba(0,0,0,0)" },
|
|
|
|
|
+ ]);
|
|
|
|
|
+ const radarIndicators = safeIndicators.map((ind, idx) => ({
|
|
|
|
|
+ ...ind,
|
|
|
|
|
+ name: `${ind.name}\n{count|${safeValues[idx]}台}`,
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ backgroundColor: "transparent",
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: "item",
|
|
|
|
|
+ backgroundColor: "rgba(16, 21, 39, 0.96)",
|
|
|
|
|
+ borderColor: color.line,
|
|
|
|
|
+ borderWidth: 1,
|
|
|
|
|
+ padding: [10, 14],
|
|
|
|
|
+ textStyle: { color: "#d8efff", fontSize: 11 },
|
|
|
|
|
+ formatter: () => {
|
|
|
|
|
+ const lines = safeIndicators
|
|
|
|
|
+ .map((ind, idx) => {
|
|
|
|
|
+ const label = ind.name.replace(/\n/g, "");
|
|
|
|
|
+ return `<span style="color:${color.line}">●</span> ${label}:<b>${safeValues[idx]}</b> 台`;
|
|
|
|
|
+ })
|
|
|
|
|
+ .join("<br/>");
|
|
|
|
|
+ return `<div style="font-weight:600;margin-bottom:8px;color:#d8efff">${seriesName}</div>${lines}`;
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ radar: {
|
|
|
|
|
+ indicator: radarIndicators,
|
|
|
|
|
+ shape: "polygon",
|
|
|
|
|
+ center: ["50%", "54%"],
|
|
|
|
|
+ radius: "48%",
|
|
|
|
|
+ startAngle: 90,
|
|
|
|
|
+ axisName: {
|
|
|
|
|
+ color: "#b7dff5",
|
|
|
|
|
+ fontSize: safeIndicators.length > 5 ? 7 : 8,
|
|
|
|
|
+ lineHeight: 12,
|
|
|
|
|
+ fontWeight: 500,
|
|
|
|
|
+ rich: {
|
|
|
|
|
+ count: {
|
|
|
|
|
+ color: color.line,
|
|
|
|
|
+ fontWeight: 700,
|
|
|
|
|
+ fontSize: safeIndicators.length > 5 ? 8 : 9,
|
|
|
|
|
+ lineHeight: 13,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ axisNameGap: 8,
|
|
|
|
|
+ splitNumber: 4,
|
|
|
|
|
+ splitLine: {
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ color: "rgba(101, 167, 201, 0.22)",
|
|
|
|
|
+ type: "dashed",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ splitArea: {
|
|
|
|
|
+ areaStyle: {
|
|
|
|
|
+ color: [
|
|
|
|
|
+ "rgba(16, 21, 39, 0.55)",
|
|
|
|
|
+ "rgba(20, 28, 52, 0.4)",
|
|
|
|
|
+ "rgba(22, 30, 56, 0.25)",
|
|
|
|
|
+ "rgba(28, 38, 68, 0.1)",
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ axisLine: {
|
|
|
|
|
+ lineStyle: { color: "rgba(101, 167, 201, 0.28)" },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "radar",
|
|
|
|
|
+ name: seriesName,
|
|
|
|
|
+ symbol: "circle",
|
|
|
|
|
+ symbolSize: 5,
|
|
|
|
|
+ data: [
|
|
|
|
|
+ {
|
|
|
|
|
+ value: safeValues,
|
|
|
|
|
+ name: seriesName,
|
|
|
|
|
+ areaStyle: { color: areaGradient, opacity: 0.92 },
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ color: color.line,
|
|
|
|
|
+ width: 2.5,
|
|
|
|
|
+ shadowColor: color.shadow || "transparent",
|
|
|
|
|
+ shadowBlur: 10,
|
|
|
|
|
+ },
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: color.line,
|
|
|
|
|
+ borderColor: "rgba(255,255,255,0.85)",
|
|
|
|
|
+ borderWidth: 1.5,
|
|
|
|
|
+ },
|
|
|
|
|
+ label: { show: false },
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ scale: true,
|
|
|
|
|
+ lineStyle: { width: 3 },
|
|
|
|
|
+ areaStyle: { opacity: 1 },
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ shadowBlur: 14,
|
|
|
|
|
+ shadowColor: color.shadow || color.line,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function buildHeatmapOption({ yLabels, xLabels, matrix, highColor }) {
|
|
|
|
|
+ const safeYLabels = yLabels || [];
|
|
|
|
|
+ const safeXLabels = xLabels || [];
|
|
|
|
|
+ const safeMatrix = matrix || [];
|
|
|
|
|
+
|
|
|
|
|
+ if (!safeYLabels.length || !safeXLabels.length) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ backgroundColor: "transparent",
|
|
|
|
|
+ graphic: {
|
|
|
|
|
+ type: "text",
|
|
|
|
|
+ left: "center",
|
|
|
|
|
+ top: "middle",
|
|
|
|
|
+ style: {
|
|
|
|
|
+ text: "暂无数据",
|
|
|
|
|
+ fill: "#8db6d1",
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const visibleCount = 5;
|
|
|
|
|
+ const heatXZoomEnd =
|
|
|
|
|
+ safeXLabels.length <= visibleCount
|
|
|
|
|
+ ? 100
|
|
|
|
|
+ : (visibleCount / safeXLabels.length) * 100;
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ backgroundColor: "transparent",
|
|
|
|
|
+ grid: { left: 98, right: 24, top: 32, bottom: 52 },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ position: "top",
|
|
|
|
|
+ backgroundColor: "rgba(16, 21, 39, 0.96)",
|
|
|
|
|
+ borderColor: "rgba(95, 189, 227, 0.35)",
|
|
|
|
|
+ textStyle: { color: "#d8efff", fontSize: 11 },
|
|
|
|
|
+ formatter: (params) =>
|
|
|
|
|
+ `${safeYLabels[params.value[1]]}<br/>${safeXLabels[params.value[0]]}:${formatHeatPercent(params.value[2])}`,
|
|
|
|
|
+ },
|
|
|
|
|
+ visualMap: {
|
|
|
|
|
+ min: 0,
|
|
|
|
|
+ max: 100,
|
|
|
|
|
+ calculable: false,
|
|
|
|
|
+ show: false,
|
|
|
|
|
+ inRange: { color: ["#101527", highColor] },
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: "category",
|
|
|
|
|
+ data: safeXLabels,
|
|
|
|
|
+ position: "top",
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ color: "#8fbfe0",
|
|
|
|
|
+ fontSize: 10,
|
|
|
|
|
+ margin: 8,
|
|
|
|
|
+ },
|
|
|
|
|
+ axisLine: { lineStyle: { color: "rgba(101,167,201,0.42)" } },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: "category",
|
|
|
|
|
+ data: safeYLabels,
|
|
|
|
|
+ inverse: true,
|
|
|
|
|
+ axisLabel: { color: "#8fbfe0", fontSize: 10 },
|
|
|
|
|
+ axisLine: { lineStyle: { color: "rgba(101,167,201,0.42)" } },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ },
|
|
|
|
|
+ dataZoom: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "inside",
|
|
|
|
|
+ xAxisIndex: 0,
|
|
|
|
|
+ start: 0,
|
|
|
|
|
+ end: heatXZoomEnd,
|
|
|
|
|
+ zoomOnMouseWheel: true,
|
|
|
|
|
+ moveOnMouseMove: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "inside",
|
|
|
|
|
+ yAxisIndex: 0,
|
|
|
|
|
+ start: 0,
|
|
|
|
|
+ end: 100,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "slider",
|
|
|
|
|
+ xAxisIndex: 0,
|
|
|
|
|
+ start: 0,
|
|
|
|
|
+ end: heatXZoomEnd,
|
|
|
|
|
+ left: "center",
|
|
|
|
|
+ width: "88%",
|
|
|
|
|
+ height: 14,
|
|
|
|
|
+ bottom: 10,
|
|
|
|
|
+ borderColor: "rgba(101,167,201,0.35)",
|
|
|
|
|
+ backgroundColor: "rgba(16, 21, 39, 0.85)",
|
|
|
|
|
+ fillerColor: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
+ { offset: 0, color: "rgba(50,115,138,0.75)" },
|
|
|
|
|
+ { offset: 1, color: "rgba(43,103,129,0.75)" },
|
|
|
|
|
+ ]),
|
|
|
|
|
+ moveHandleSize: 0,
|
|
|
|
|
+ showDetail: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "heatmap",
|
|
|
|
|
+ data: matrixToHeat(safeMatrix),
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ color: "#d8efff",
|
|
|
|
|
+ fontSize: 9,
|
|
|
|
|
+ formatter: (params) => formatHeatPercent(params.value[2]),
|
|
|
|
|
+ },
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ borderColor: "#0c1122",
|
|
|
|
|
+ borderWidth: 1,
|
|
|
|
|
+ borderRadius: 0,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function adjustColorForChart(color, amount) {
|
|
|
|
|
+ const hex = color.replace("#", "");
|
|
|
|
|
+ const r = Math.max(
|
|
|
|
|
+ 0,
|
|
|
|
|
+ Math.min(255, parseInt(hex.substr(0, 2), 16) + amount),
|
|
|
|
|
+ );
|
|
|
|
|
+ const g = Math.max(
|
|
|
|
|
+ 0,
|
|
|
|
|
+ Math.min(255, parseInt(hex.substr(2, 2), 16) + amount),
|
|
|
|
|
+ );
|
|
|
|
|
+ const b = Math.max(
|
|
|
|
|
+ 0,
|
|
|
|
|
+ Math.min(255, parseInt(hex.substr(4, 2), 16) + amount),
|
|
|
|
|
+ );
|
|
|
|
|
+ return `rgb(${r}, ${g}, ${b})`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function buildCarouselOption(module) {
|
|
|
|
|
+ if (!module?.turbines?.length) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ backgroundColor: "transparent",
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: module?.title || "",
|
|
|
|
|
+ left: "center",
|
|
|
|
|
+ top: 0,
|
|
|
|
|
+ textStyle: {
|
|
|
|
|
+ color: module?.color || "#5ecdee",
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontWeight: "bold",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ graphic: {
|
|
|
|
|
+ type: "text",
|
|
|
|
|
+ left: "center",
|
|
|
|
|
+ top: "middle",
|
|
|
|
|
+ style: {
|
|
|
|
|
+ text: "暂无数据",
|
|
|
|
|
+ fill: "#8db6d1",
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const detectorColors =
|
|
|
|
|
+ module.detectorColors ||
|
|
|
|
|
+ module.detectors.map(
|
|
|
|
|
+ (_, i) =>
|
|
|
|
|
+ ["#5ecdee", "#4bc1e6", "#f2c84e", "#7eb8d9", "#49A1BB"][i % 5],
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const series = module.detectors.map((detector, i) => {
|
|
|
|
|
+ const barColor = detectorColors[i];
|
|
|
|
|
+ return {
|
|
|
|
|
+ name: detector.name,
|
|
|
|
|
+ type: "bar",
|
|
|
|
|
+ data: module.turbines.map((turbine) => turbine.values[i] || 0),
|
|
|
|
|
+ barGap: "10%",
|
|
|
|
|
+ barMaxWidth: 28,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
+ { offset: 0, color: barColor },
|
|
|
|
|
+ { offset: 1, color: adjustColorForChart(barColor, -45) },
|
|
|
|
|
+ ]),
|
|
|
|
|
+ borderRadius: [3, 3, 0, 0],
|
|
|
|
|
+ },
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ position: "top",
|
|
|
|
|
+ color: "#b7dcf3",
|
|
|
|
|
+ fontSize: 10,
|
|
|
|
|
+ formatter: "{c}%",
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const turbineNames = module.turbines.map((item) => item.name);
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ backgroundColor: "transparent",
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: module.title,
|
|
|
|
|
+ left: "center",
|
|
|
|
|
+ top: 0,
|
|
|
|
|
+ textStyle: {
|
|
|
|
|
+ color: module.color,
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontWeight: "bold",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: "axis",
|
|
|
|
|
+ axisPointer: { type: "shadow" },
|
|
|
|
|
+ backgroundColor: "rgba(16, 21, 39, 0.96)",
|
|
|
|
|
+ borderColor: "rgba(95, 189, 227, 0.35)",
|
|
|
|
|
+ textStyle: { color: "#d8efff" },
|
|
|
|
|
+ formatter(params) {
|
|
|
|
|
+ let html = `<div style="font-weight:bold;margin-bottom:6px;color:${module.color}">${params[0].name}</div>`;
|
|
|
|
|
+ params.forEach((p) => {
|
|
|
|
|
+ html += `<div style="display:flex;align-items:center;gap:8px;margin:4px 0">
|
|
|
|
|
+ <span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:${p.color}"></span>
|
|
|
|
|
+ <span>${p.seriesName}: ${p.value}%</span>
|
|
|
|
|
+ </div>`;
|
|
|
|
|
+ });
|
|
|
|
|
+ return html;
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ data: module.detectors.map((item) => item.name),
|
|
|
|
|
+ bottom: 0,
|
|
|
|
|
+ textStyle: { color: "#8fbfe0", fontSize: 10 },
|
|
|
|
|
+ itemWidth: 12,
|
|
|
|
|
+ itemHeight: 12,
|
|
|
|
|
+ },
|
|
|
|
|
+ grid: { left: "6%", right: "4%", top: "18%", bottom: "28%" },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: "category",
|
|
|
|
|
+ data: turbineNames,
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ color: "#8fbfe0",
|
|
|
|
|
+ fontSize: 10,
|
|
|
|
|
+ interval: 0,
|
|
|
|
|
+ rotate: turbineNames.length > 8 ? 28 : 0,
|
|
|
|
|
+ },
|
|
|
|
|
+ axisLine: { lineStyle: { color: "rgba(101,167,201,0.45)" } },
|
|
|
|
|
+ },
|
|
|
|
|
+ dataZoom: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "inside",
|
|
|
|
|
+ xAxisIndex: 0,
|
|
|
|
|
+ start: 0,
|
|
|
|
|
+ end: turbineNames.length > 8 ? 65 : 100,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "slider",
|
|
|
|
|
+ xAxisIndex: 0,
|
|
|
|
|
+ height: 14,
|
|
|
|
|
+ bottom: 36,
|
|
|
|
|
+ borderColor: "rgba(101,167,201,0.35)",
|
|
|
|
|
+ backgroundColor: "rgba(16, 21, 39, 0.85)",
|
|
|
|
|
+ fillerColor: "rgba(50,115,138,0.55)",
|
|
|
|
|
+ showDetail: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: "value",
|
|
|
|
|
+ name: "异常百分比(%)",
|
|
|
|
|
+ nameTextStyle: { color: "#8fbfe0", fontSize: 11 },
|
|
|
|
|
+ min: 0,
|
|
|
|
|
+ max: 100,
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ color: "#8fbfe0",
|
|
|
|
|
+ fontSize: 11,
|
|
|
|
|
+ formatter: "{value}%",
|
|
|
|
|
+ },
|
|
|
|
|
+ splitLine: { lineStyle: { color: "rgba(84,145,180,0.22)" } },
|
|
|
|
|
+ },
|
|
|
|
|
+ series,
|
|
|
|
|
+ };
|
|
|
|
|
+}
|