From c7f3ff4e5a88398047729b9119e86fc860e58bd2 Mon Sep 17 00:00:00 2001 From: JIANG Date: Mon, 22 Dec 2025 10:36:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=9F=B1=E7=8A=B6=E5=9B=BE?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E6=95=B0=E6=8D=AE=E5=88=86=E5=B8=83=EF=BC=9B?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E6=95=B0=E6=8D=AE=E4=B8=AD=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=9C=86=E7=82=B9=E6=A0=87=E8=AF=86=EF=BC=8C=E6=8F=90=E9=AB=98?= =?UTF-8?q?=E6=95=A3=E7=82=B9=E6=95=B0=E6=8D=AE=E7=9A=84=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E6=80=A7=EF=BC=9B=E4=BF=AE=E6=94=B9=E5=B1=9E=E6=80=A7=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=8D=95=E4=BD=8D=E6=98=BE=E7=A4=BA=EF=BC=9B=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=B8=80=E4=BA=9B=E6=A0=B7=E5=BC=8F=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/health-risk-analysis/page.tsx | 4 +- src/app/OlMap/Controls/Toolbar.tsx | 7 +- src/app/OlMap/MapComponent.tsx | 3 +- ...kPieChart.tsx => HealthRiskStatistics.tsx} | 193 ++++++++++++------ src/components/olmap/SCADADataPanel.tsx | 18 +- 5 files changed, 147 insertions(+), 78 deletions(-) rename src/components/olmap/HealthRiskAnalysis/{HealthRiskPieChart.tsx => HealthRiskStatistics.tsx} (61%) diff --git a/src/app/(main)/health-risk-analysis/page.tsx b/src/app/(main)/health-risk-analysis/page.tsx index ef168be..67881f7 100644 --- a/src/app/(main)/health-risk-analysis/page.tsx +++ b/src/app/(main)/health-risk-analysis/page.tsx @@ -4,7 +4,7 @@ import MapComponent from "@app/OlMap/MapComponent"; import Timeline from "@components/olmap/HealthRiskAnalysis/Timeline"; import MapToolbar from "@app/OlMap/Controls/Toolbar"; import { HealthRiskProvider } from "@components/olmap/HealthRiskAnalysis/HealthRiskContext"; -import HealthRiskPieChart from "@components/olmap/HealthRiskAnalysis/HealthRiskPieChart"; +import HealthRiskStatistics from "@components/olmap/HealthRiskAnalysis/HealthRiskStatistics"; import PredictDataPanel from "@components/olmap/HealthRiskAnalysis/PredictDataPanel"; import StyleLegend from "@app/OlMap/Controls/StyleLegend"; import { @@ -24,7 +24,7 @@ export default function Home() { HistoryPanel={PredictDataPanel} /> - + = ({ const properties = highlightFeature.getProperties(); // 计算属性字段,增加 key 字段 const pipeComputedFields = [ - { key: "flow", label: "流量", unit: "m³/s" }, + { key: "flow", label: "流量", unit: "m³/h" }, { key: "friction", label: "摩阻", unit: "" }, { key: "headloss", label: "水头损失", unit: "m" }, + { key: "headlossPerKM", label: "单位水头损失", unit: "m/km" }, { key: "quality", label: "水质", unit: "mg/L" }, - { key: "reaction", label: "反应", unit: "1/s" }, + { key: "reaction", label: "反应", unit: "1/d" }, { key: "setting", label: "设置", unit: "" }, { key: "status", label: "状态", unit: "" }, { key: "velocity", label: "流速", unit: "m/s" }, ]; const nodeComputedFields = [ - { key: "actualdemand", label: "实际需水量", unit: "m³/s" }, + { key: "actualdemand", label: "实际需水量", unit: "m³/h" }, { key: "head", label: "水头", unit: "m" }, { key: "pressure", label: "压力", unit: "m" }, { key: "quality", label: "水质", unit: "mg/L" }, diff --git a/src/app/OlMap/MapComponent.tsx b/src/app/OlMap/MapComponent.tsx index b79dff5..1720ddb 100644 --- a/src/app/OlMap/MapComponent.tsx +++ b/src/app/OlMap/MapComponent.tsx @@ -293,7 +293,7 @@ const MapComponent: React.FC = ({ children }) => { type: "point", properties: [ // { name: "需求量", value: "demand" }, - // { name: "海拔高度", value: "elevation" }, + { name: "高程", value: "elevation" }, { name: "实际需求量", value: "actualdemand" }, { name: "水头", value: "head" }, { name: "压力", value: "pressure" }, @@ -318,6 +318,7 @@ const MapComponent: React.FC = ({ children }) => { { name: "流量", value: "flow" }, { name: "摩阻系数", value: "friction" }, { name: "水头损失", value: "headloss" }, + { name: "单位水头损失", value: "headlossPerKM" }, { name: "水质", value: "quality" }, { name: "反应速率", value: "reaction" }, { name: "设置值", value: "setting" }, diff --git a/src/components/olmap/HealthRiskAnalysis/HealthRiskPieChart.tsx b/src/components/olmap/HealthRiskAnalysis/HealthRiskStatistics.tsx similarity index 61% rename from src/components/olmap/HealthRiskAnalysis/HealthRiskPieChart.tsx rename to src/components/olmap/HealthRiskAnalysis/HealthRiskStatistics.tsx index 5806473..e02fa57 100644 --- a/src/components/olmap/HealthRiskAnalysis/HealthRiskPieChart.tsx +++ b/src/components/olmap/HealthRiskAnalysis/HealthRiskStatistics.tsx @@ -3,7 +3,6 @@ import React, { useMemo } from "react"; import ReactECharts from "echarts-for-react"; import { - Paper, Typography, Box, Chip, @@ -13,11 +12,24 @@ import { Slide, Fade, } from "@mui/material"; -import { ChevronLeft, ChevronRight, PieChart } from "@mui/icons-material"; +import { ChevronLeft, ChevronRight, BarChart } from "@mui/icons-material"; import { RAINBOW_COLORS, RISK_BREAKS, RISK_LABELS } from "./types"; import { useHealthRisk } from "./HealthRiskContext"; -const HealthRiskPieChart: React.FC = () => { +const SIMPLE_LABELS = [ + "0.0 - 0.1", + "0.1 - 0.2", + "0.2 - 0.3", + "0.3 - 0.4", + "0.4 - 0.5", + "0.5 - 0.6", + "0.6 - 0.7", + "0.7 - 0.8", + "0.8 - 0.9", + "0.9 - 1.0", +]; + +const HealthRiskStatistics: React.FC = () => { const { predictionResults, currentYear } = useHealthRisk(); const [isExpanded, setIsExpanded] = React.useState(true); const [hoveredYearIndex, setHoveredYearIndex] = React.useState( @@ -27,22 +39,31 @@ const HealthRiskPieChart: React.FC = () => { const datasetSource = useMemo(() => { if (!predictionResults || predictionResults.length === 0) return []; - const years = Array.from({ length: 70 }, (_, i) => 4 + i); // 4 to 73 + // 收集所有唯一的年份 + const allYears = new Set(); + predictionResults.forEach((result) => { + if (result.survival_function.x) { + result.survival_function.x.forEach((year) => allYears.add(year)); + } + }); + const years = Array.from(allYears).sort((a, b) => a - b); const header = ["Risk Level", ...years.map(String)]; - const rows = RISK_LABELS.map((label, riskIdx) => { + const rows = SIMPLE_LABELS.map((label, riskIdx) => { const row: (string | number)[] = [label]; years.forEach((year) => { let count = 0; predictionResults.forEach((result) => { - const { y } = result.survival_function; - const index = year - 4; - if (index >= 0 && index < y.length) { - const probability = y[index]; - const lowerBound = riskIdx === 0 ? -1 : RISK_BREAKS[riskIdx - 1]; - const upperBound = RISK_BREAKS[riskIdx]; - if (probability > lowerBound && probability <= upperBound) { - count++; + const { x, y } = result.survival_function; + if (x && x.includes(year)) { + const yearIndex = x.indexOf(year); + if (yearIndex >= 0 && yearIndex < y.length) { + const probability = y[yearIndex]; + const lowerBound = riskIdx === 0 ? -1 : RISK_BREAKS[riskIdx - 1]; + const upperBound = RISK_BREAKS[riskIdx]; + if (probability > lowerBound && probability <= upperBound) { + count++; + } } } }); @@ -58,13 +79,19 @@ const HealthRiskPieChart: React.FC = () => { if (hoveredYearIndex !== null) { return hoveredYearIndex + 1; } - // 默认显示当前时间轴年份 - return Math.max(1, Math.min(70, currentYear - 3)); - }, [hoveredYearIndex, currentYear]); + // 查找 currentYear 在 datasetSource header 中的索引 + if (datasetSource.length > 0) { + const header = datasetSource[0] as string[]; + const yearStr = String(currentYear); + const index = header.indexOf(yearStr); + if (index !== -1) return index; + } + return 1; + }, [hoveredYearIndex, currentYear, datasetSource]); const option = { legend: { - top: "48%", + top: "middle", left: "center", itemWidth: 10, itemHeight: 10, @@ -76,53 +103,105 @@ const HealthRiskPieChart: React.FC = () => { trigger: "axis", showContent: true, confine: true, + // formatter: function(params: any[]) { + // const year = params[0].axisValue; + // let content = `预测年份:${year}
`; + // params.forEach((p: any) => { + // content += `${p.seriesName}: ${p.value}
`; + // }); + // return content; + // }, }, dataset: { source: datasetSource, }, - xAxis: { - type: "category", - axisLabel: { - fontSize: 10, + grid: [ + { + // 折线图网格 + top: "62%", + bottom: "8%", + left: "12%", + right: "5%", }, - }, - yAxis: { - gridIndex: 0, - axisLabel: { - fontSize: 10, + { + // 柱状图网格 + top: "5%", + bottom: "60%", + left: "10%", + right: "10%", }, - }, - grid: { - top: "62%", - bottom: "8%", - left: "12%", - right: "5%", - }, + ], + xAxis: [ + { + type: "category", + gridIndex: 0, + data: + datasetSource.length > 0 + ? (datasetSource[0] as string[]).slice(1) + : [], + axisLabel: { + fontSize: 10, + }, + }, + { + type: "value", + gridIndex: 1, + name: "数量", + axisLabel: { + fontSize: 10, + }, + nameTextStyle: { + fontSize: 10, + }, + }, + ], + yAxis: [ + { + gridIndex: 0, + axisLabel: { + fontSize: 10, + }, + }, + { + type: "category", + gridIndex: 1, + data: SIMPLE_LABELS, + inverse: true, // 让极高风险在上方 + axisLabel: { + fontSize: 10, + }, + }, + ], series: [ ...RISK_LABELS.map((_, i) => ({ + name: RISK_LABELS[i], type: "line", smooth: true, seriesLayoutBy: "row", + xAxisIndex: 0, + yAxisIndex: 0, emphasis: { focus: "series" }, itemStyle: { color: RAINBOW_COLORS[i] }, symbol: "none", })), { - type: "pie", - id: "pie", - radius: "35%", - center: ["50%", "24%"], - emphasis: { - focus: "self", + type: "bar", + id: "bar", + xAxisIndex: 1, + yAxisIndex: 1, + encode: { + x: displayDimension, + y: "Risk Level", }, label: { - formatter: "{b}: {@[" + displayDimension + "]} ({d}%)", + show: true, + position: "right", fontSize: 10, }, - encode: { - itemName: "Risk Level", - value: displayDimension, - tooltip: displayDimension, + itemStyle: { + color: (params: any) => { + return RAINBOW_COLORS[params.dataIndex]; + }, }, }, ], @@ -158,7 +237,7 @@ const HealthRiskPieChart: React.FC = () => { color: "text.secondary", }} > - + 暂无预测数据 @@ -178,7 +257,7 @@ const HealthRiskPieChart: React.FC = () => { sx={{ zIndex: 1300 }} > - + { {/* 头部 */}
- - - - +

管道健康风险统计

{ ); }; -export default HealthRiskPieChart; +export default HealthRiskStatistics; diff --git a/src/components/olmap/SCADADataPanel.tsx b/src/components/olmap/SCADADataPanel.tsx index 1b5a0f3..b785233 100644 --- a/src/components/olmap/SCADADataPanel.tsx +++ b/src/components/olmap/SCADADataPanel.tsx @@ -718,7 +718,8 @@ const SCADADataPanel: React.FC = ({ { name: `${id} (原始)`, type: "line", - symbol: "none", + showSymbol: true, + symbolSize: 4, sampling: "lttb", itemStyle: { color: colors[index % colors.length] }, data: dataset.map((item) => item[`${id}_raw`]), @@ -726,7 +727,8 @@ const SCADADataPanel: React.FC = ({ { name: `${id} (清洗)`, type: "line", - symbol: "none", + showSymbol: true, + symbolSize: 4, sampling: "lttb", itemStyle: { color: colors[(index + 3) % colors.length] }, data: dataset.map((item) => item[`${id}_clean`]), @@ -734,7 +736,8 @@ const SCADADataPanel: React.FC = ({ { name: `${id} (模拟)`, type: "line", - symbol: "none", + showSymbol: true, + symbolSize: 4, sampling: "lttb", itemStyle: { color: colors[(index + 6) % colors.length] }, data: dataset.map((item) => item[`${id}_sim`]), @@ -744,7 +747,8 @@ const SCADADataPanel: React.FC = ({ return deviceIds.map((id, index) => ({ name: id, type: "line", - symbol: "none", + showSymbol: true, + symbolSize: 4, sampling: "lttb", itemStyle: { color: colors[index % colors.length] }, data: dataset.map((item) => item[`${id}_${selectedSource}`]), @@ -768,7 +772,8 @@ const SCADADataPanel: React.FC = ({ : "模拟" })`, type: "line", - symbol: "none", + showSymbol: true, + symbolSize: 4, sampling: "lttb", itemStyle: { color: colors[(index * 3 + sIndex) % colors.length], @@ -795,7 +800,8 @@ const SCADADataPanel: React.FC = ({ series.push({ name: id, type: "line", - symbol: "none", + showSymbol: true, + symbolSize: 4, sampling: "lttb", itemStyle: { color: colors[index % colors.length] }, data: dataset.map((item) => item[id]),