Files
TJWaterFrontend_Refine/src/components/olmap/HealthRiskAnalysis/PredictDataPanel.tsx

238 lines
6.1 KiB
TypeScript

"use client";
import React, { useMemo, useRef } from "react";
import Draggable from "react-draggable";
import { Box, Chip, Stack, Typography } from "@mui/material";
import { ShowChart } from "@mui/icons-material";
import ReactECharts from "echarts-for-react";
import "dayjs/locale/zh-cn";
import { useHealthRisk } from "./HealthRiskContext";
export interface PredictDataPanelProps {
/** 选中的要素信息列表,格式为 [[id, type], [id, type]] */
featureInfos: [string, string][];
/** Y 轴数值的小数位数 */
fractionDigits?: number;
}
const PredictDataPanel: React.FC<PredictDataPanelProps> = ({
featureInfos,
fractionDigits = 4,
}) => {
const { predictionResults } = useHealthRisk();
const draggableRef = useRef<HTMLDivElement>(null);
// 提取选中的设备 ID
const selectedIds = useMemo(
() => featureInfos.map(([id]) => id),
[featureInfos]
);
// 过滤出选中管道的预测结果
const filteredResults = useMemo(() => {
return predictionResults.filter((res) => selectedIds.includes(res.link_id));
}, [predictionResults, selectedIds]);
const hasData = filteredResults.length > 0;
// 构建图表所需的数据集
const dataset = useMemo(() => {
if (filteredResults.length === 0) return [];
// 获取所有唯一的时间点并排序
const allX = Array.from(
new Set(filteredResults.flatMap((res) => res.survival_function.x))
).sort((a, b) => a - b);
return allX.map((x) => {
const row: any = { x, label: `${x}` };
filteredResults.forEach((res) => {
const index = res.survival_function.x.indexOf(x);
if (index !== -1) {
row[res.link_id] = res.survival_function.y[index];
}
});
return row;
});
}, [filteredResults]);
const renderEmpty = () => (
<Box
sx={{
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
py: 8,
color: "text.secondary",
height: "100%",
}}
>
<ShowChart sx={{ fontSize: 64, mb: 2, opacity: 0.3 }} />
<Typography variant="h6" gutterBottom sx={{ fontWeight: 500 }}>
</Typography>
<Typography variant="body2" color="text.secondary">
</Typography>
</Box>
);
const renderChart = () => {
if (!hasData) return renderEmpty();
const colors = [
"#1976d2",
"#dc004e",
"#ff9800",
"#4caf50",
"#9c27b0",
"#00bcd4",
"#f44336",
"#8bc34a",
"#ff5722",
"#3f51b5",
];
const xData = dataset.map((item) => item.x);
const series = filteredResults.map((res, index) => ({
name: `管道 ${res.link_id}`,
type: "line",
smooth: true,
symbol: "circle",
symbolSize: 6,
itemStyle: {
color: colors[index % colors.length],
},
data: res.survival_function.y,
}));
const option = {
tooltip: {
trigger: "axis",
formatter: (params: any) => {
let res = `${params[0].name}年<br/>`;
params.forEach((item: any) => {
res += `${item.marker} ${item.seriesName}: ${item.value.toFixed(
fractionDigits
)}<br/>`;
});
return res;
},
},
legend: {
top: "top",
type: "scroll",
},
grid: {
left: "5%",
right: "5%",
bottom: "10%",
containLabel: true,
},
xAxis: {
type: "category",
name: "年",
boundaryGap: false,
data: xData,
},
yAxis: {
type: "value",
name: "生存概率",
min: 0,
max: 1,
},
series,
};
return (
<Box sx={{ width: "100%", height: "100%" }}>
<ReactECharts
option={option}
style={{ height: "100%", width: "100%" }}
notMerge={true}
/>
</Box>
);
};
return (
<Draggable nodeRef={draggableRef}>
<Box
ref={draggableRef}
sx={{
position: "absolute",
right: "2rem",
top: "2rem",
width: "min(920px, calc(100vw - 2rem))",
maxWidth: "100vw",
height: "40vh",
maxHeight: "calc(100vh - 2rem)",
boxSizing: "border-box",
borderRadius: "12px",
boxShadow:
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
backdropFilter: "blur(8px)",
opacity: 0.95,
transition: "opacity 0.3s ease-in-out",
border: "none",
display: "flex",
flexDirection: "column",
zIndex: 1300,
backgroundColor: "white",
overflow: "hidden",
"&:hover": {
opacity: 1,
},
}}
>
<Box
className="flex flex-col h-full rounded-xl"
sx={{ height: "100%", width: "100%" }}
>
{/* Header */}
<Box
sx={{
p: 2,
borderBottom: 1,
borderColor: "divider",
backgroundColor: "primary.main",
color: "primary.contrastText",
}}
>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
>
<Stack direction="row" spacing={1} alignItems="center">
<ShowChart fontSize="small" />
<Typography variant="h6" sx={{ fontWeight: "bold" }}>
线
</Typography>
<Chip
size="small"
label={`${filteredResults.length}`}
sx={{
backgroundColor: "rgba(255,255,255,0.2)",
color: "primary.contrastText",
fontWeight: "bold",
}}
/>
</Stack>
</Stack>
</Box>
{/* Content */}
<Box sx={{ flex: 1, p: 2, overflow: "hidden" }}>{renderChart()}</Box>
</Box>
</Box>
</Draggable>
);
};
export default PredictDataPanel;