使用 echart 绘制图表
This commit is contained in:
53
package-lock.json
generated
53
package-lock.json
generated
@@ -29,6 +29,8 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"deck.gl": "^9.1.14",
|
"deck.gl": "^9.1.14",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
|
"echarts-for-react": "^3.0.5",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"next": "^15.2.4",
|
"next": "^15.2.4",
|
||||||
"next-auth": "^4.24.5",
|
"next-auth": "^4.24.5",
|
||||||
@@ -10691,6 +10693,36 @@
|
|||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/echarts": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0",
|
||||||
|
"zrender": "6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/echarts-for-react": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-YpEI5Ty7O/2nvCfQ7ybNa+S90DwE8KYZWacGvJW4luUqywP7qStQ+pxDlYOmr4jGDu10mhEkiAuMKcUlT4W5vg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"size-sensor": "^1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"echarts": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0",
|
||||||
|
"react": "^15.0.0 || >=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/echarts/node_modules/tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -17374,6 +17406,12 @@
|
|||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/size-sensor": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/skin-tone": {
|
"node_modules/skin-tone": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
|
||||||
@@ -19071,6 +19109,21 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/zrender": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zrender/node_modules/tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/zstd-codec": {
|
"node_modules/zstd-codec": {
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/zstd-codec/-/zstd-codec-0.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/zstd-codec/-/zstd-codec-0.1.5.tgz",
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"deck.gl": "^9.1.14",
|
"deck.gl": "^9.1.14",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
|
"echarts-for-react": "^3.0.5",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"next": "^15.2.4",
|
"next": "^15.2.4",
|
||||||
"next-auth": "^4.24.5",
|
"next-auth": "^4.24.5",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
Drawer,
|
Drawer,
|
||||||
Slider,
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import {
|
import {
|
||||||
Refresh,
|
Refresh,
|
||||||
@@ -25,7 +24,8 @@ import {
|
|||||||
ChevronRight,
|
ChevronRight,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||||
import { LineChart } from "@mui/x-charts";
|
import ReactECharts from "echarts-for-react";
|
||||||
|
import * as echarts from "echarts";
|
||||||
import "dayjs/locale/zh-cn"; // 引入中文包
|
import "dayjs/locale/zh-cn"; // 引入中文包
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
@@ -347,10 +347,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
const [isCleaning, setIsCleaning] = useState<boolean>(false);
|
const [isCleaning, setIsCleaning] = useState<boolean>(false);
|
||||||
const [selectedSource, setSelectedSource] = useState<
|
const [selectedSource, setSelectedSource] = useState<
|
||||||
"raw" | "clean" | "sim" | "all"
|
"raw" | "clean" | "sim" | "all"
|
||||||
>("all");
|
>(() => (deviceIds.length === 1 ? "all" : "clean"));
|
||||||
|
|
||||||
// 滑块状态:用于图表缩放
|
|
||||||
const [zoomRange, setZoomRange] = useState<[number, number]>([0, 100]);
|
|
||||||
|
|
||||||
// 获取 SCADA 设备信息,生成 deviceLabels
|
// 获取 SCADA 设备信息,生成 deviceLabels
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -396,21 +393,6 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
[timeSeries, deviceIds, fractionDigits, showCleaning]
|
[timeSeries, deviceIds, fractionDigits, showCleaning]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 根据滑块范围过滤数据集
|
|
||||||
const filteredDataset = useMemo(() => {
|
|
||||||
if (dataset.length === 0) return dataset;
|
|
||||||
|
|
||||||
const startIndex = Math.floor((zoomRange[0] / 100) * dataset.length);
|
|
||||||
const endIndex = Math.ceil((zoomRange[1] / 100) * dataset.length);
|
|
||||||
|
|
||||||
return dataset.slice(startIndex, endIndex);
|
|
||||||
}, [dataset, zoomRange]);
|
|
||||||
|
|
||||||
// 重置滑块范围当数据变化时
|
|
||||||
useEffect(() => {
|
|
||||||
setZoomRange([0, 100]);
|
|
||||||
}, [timeSeries]);
|
|
||||||
|
|
||||||
const handleFetch = useCallback(
|
const handleFetch = useCallback(
|
||||||
async (reason: string) => {
|
async (reason: string) => {
|
||||||
if (!hasDevices) {
|
if (!hasDevices) {
|
||||||
@@ -530,10 +512,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
if (deviceIds.length > 1 && selectedSource === "all") {
|
if (deviceIds.length > 1 && selectedSource === "all") {
|
||||||
setSelectedSource("clean");
|
setSelectedSource("clean");
|
||||||
}
|
}
|
||||||
// else if (deviceIds.length === 1 && selectedSource !== "all") {
|
}, [deviceIds.length, selectedSource]);
|
||||||
// setSelectedSource("all");
|
|
||||||
// }
|
|
||||||
}, [deviceIds.length]);
|
|
||||||
|
|
||||||
const columns: GridColDef[] = useMemo(() => {
|
const columns: GridColDef[] = useMemo(() => {
|
||||||
const base: GridColDef[] = [
|
const base: GridColDef[] = [
|
||||||
@@ -680,17 +659,119 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
"#3f51b5", // 靛蓝色
|
"#3f51b5", // 靛蓝色
|
||||||
];
|
];
|
||||||
|
|
||||||
// 获取当前显示范围的时间边界
|
const xData = dataset.map((item) => item.label);
|
||||||
const getTimeRangeLabel = () => {
|
|
||||||
if (filteredDataset.length === 0) return "";
|
const getSeries = () => {
|
||||||
const firstTime = filteredDataset[0].time;
|
if (showCleaning) {
|
||||||
const lastTime = filteredDataset[filteredDataset.length - 1].time;
|
if (selectedSource === "all") {
|
||||||
if (firstTime instanceof Date && lastTime instanceof Date) {
|
return deviceIds.flatMap((id, index) => [
|
||||||
return `${dayjs(firstTime).format("MM-DD HH:mm")} ~ ${dayjs(
|
{
|
||||||
lastTime
|
name: `${deviceLabels?.[id] ?? id} (原始)`,
|
||||||
).format("MM-DD HH:mm")}`;
|
type: "line",
|
||||||
|
symbol: "none",
|
||||||
|
sampling: "lttb",
|
||||||
|
itemStyle: { color: colors[index % colors.length] },
|
||||||
|
data: dataset.map((item) => item[`${id}_raw`]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `${deviceLabels?.[id] ?? id} (清洗)`,
|
||||||
|
type: "line",
|
||||||
|
symbol: "none",
|
||||||
|
sampling: "lttb",
|
||||||
|
itemStyle: { color: colors[(index + 3) % colors.length] },
|
||||||
|
data: dataset.map((item) => item[`${id}_clean`]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `${deviceLabels?.[id] ?? id} (模拟)`,
|
||||||
|
type: "line",
|
||||||
|
symbol: "none",
|
||||||
|
sampling: "lttb",
|
||||||
|
itemStyle: { color: colors[(index + 6) % colors.length] },
|
||||||
|
data: dataset.map((item) => item[`${id}_sim`]),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return deviceIds.map((id, index) => ({
|
||||||
|
name: deviceLabels?.[id] ?? id,
|
||||||
|
type: "line",
|
||||||
|
symbol: "none",
|
||||||
|
sampling: "lttb",
|
||||||
|
itemStyle: { color: colors[index % colors.length] },
|
||||||
|
data: dataset.map((item) => item[`${id}_${selectedSource}`]),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return deviceIds.map((id, index) => ({
|
||||||
|
name: deviceLabels?.[id] ?? id,
|
||||||
|
type: "line",
|
||||||
|
symbol: "none",
|
||||||
|
sampling: "lttb",
|
||||||
|
itemStyle: { color: colors[index % colors.length] },
|
||||||
|
data: dataset.map((item) => item[id]),
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: colors[index % colors.length],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: "rgba(255, 255, 255, 0)",
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
return "";
|
};
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: "axis",
|
||||||
|
confine: true,
|
||||||
|
position: function (pt: any[]) {
|
||||||
|
return [pt[0], "10%"];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: "top",
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: "5%",
|
||||||
|
right: "5%",
|
||||||
|
bottom: "11%",
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
toolbox: {
|
||||||
|
feature: {
|
||||||
|
dataZoom: {
|
||||||
|
yAxisIndex: "none",
|
||||||
|
},
|
||||||
|
restore: {},
|
||||||
|
saveAsImage: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
boundaryGap: false,
|
||||||
|
data: xData,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value",
|
||||||
|
scale: true,
|
||||||
|
},
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: "inside",
|
||||||
|
start: 0,
|
||||||
|
end: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: 0,
|
||||||
|
end: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: getSeries(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -700,237 +781,15 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ flex: 1 }}>
|
<ReactECharts
|
||||||
<LineChart
|
option={option}
|
||||||
dataset={filteredDataset}
|
style={{ height: "100%", width: "100%" }}
|
||||||
height={480}
|
notMerge={true}
|
||||||
margin={{ left: 70, right: 40, top: 30, bottom: 90 }}
|
lazyUpdate={true}
|
||||||
xAxis={[
|
/>
|
||||||
{
|
|
||||||
dataKey: "time",
|
|
||||||
scaleType: "time",
|
|
||||||
valueFormatter: (value) =>
|
|
||||||
value instanceof Date
|
|
||||||
? dayjs(value).format("MM-DD HH:mm")
|
|
||||||
: String(value),
|
|
||||||
tickLabelStyle: {
|
|
||||||
angle: -45,
|
|
||||||
textAnchor: "end",
|
|
||||||
fontSize: 11,
|
|
||||||
fill: "#666",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
yAxis={[
|
|
||||||
{
|
|
||||||
label: "压力/流量值",
|
|
||||||
labelStyle: {
|
|
||||||
fontSize: 13,
|
|
||||||
fill: "#333",
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
|
||||||
tickLabelStyle: {
|
|
||||||
fontSize: 11,
|
|
||||||
fill: "#666",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
series={(() => {
|
|
||||||
if (showCleaning) {
|
|
||||||
if (selectedSource === "all") {
|
|
||||||
// 全部模式:显示所有设备的三种数据
|
|
||||||
return deviceIds.flatMap((id, index) => [
|
|
||||||
{
|
|
||||||
dataKey: `${id}_raw`,
|
|
||||||
label: `${deviceLabels?.[id] ?? id} (原始)`,
|
|
||||||
showMark: dataset.length < 50,
|
|
||||||
curve: "catmullRom",
|
|
||||||
color: colors[index % colors.length],
|
|
||||||
valueFormatter: (value: number | null) =>
|
|
||||||
value !== null ? value.toFixed(fractionDigits) : "--",
|
|
||||||
area: false,
|
|
||||||
stack: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataKey: `${id}_clean`,
|
|
||||||
label: `${deviceLabels?.[id] ?? id} (清洗)`,
|
|
||||||
showMark: dataset.length < 50,
|
|
||||||
curve: "catmullRom",
|
|
||||||
color: colors[(index + 3) % colors.length],
|
|
||||||
valueFormatter: (value: number | null) =>
|
|
||||||
value !== null ? value.toFixed(fractionDigits) : "--",
|
|
||||||
area: false,
|
|
||||||
stack: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataKey: `${id}_sim`,
|
|
||||||
label: `${deviceLabels?.[id] ?? id} (模拟)`,
|
|
||||||
showMark: dataset.length < 50,
|
|
||||||
curve: "catmullRom",
|
|
||||||
color: colors[(index + 6) % colors.length],
|
|
||||||
valueFormatter: (value: number | null) =>
|
|
||||||
value !== null ? value.toFixed(fractionDigits) : "--",
|
|
||||||
area: false,
|
|
||||||
stack: undefined,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
// 单一数据源模式:只显示选中的数据源
|
|
||||||
return deviceIds.map((id, index) => ({
|
|
||||||
dataKey: `${id}_${selectedSource}`,
|
|
||||||
label: deviceLabels?.[id] ?? id,
|
|
||||||
showMark: dataset.length < 50,
|
|
||||||
curve: "catmullRom",
|
|
||||||
color: colors[index % colors.length],
|
|
||||||
valueFormatter: (value: number | null) =>
|
|
||||||
value !== null ? value.toFixed(fractionDigits) : "--",
|
|
||||||
area: false,
|
|
||||||
stack: undefined,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return deviceIds.map((id, index) => ({
|
|
||||||
dataKey: id,
|
|
||||||
label: deviceLabels?.[id] ?? id,
|
|
||||||
showMark: dataset.length < 50,
|
|
||||||
curve: "catmullRom",
|
|
||||||
color: colors[index % colors.length],
|
|
||||||
valueFormatter: (value: number | null) =>
|
|
||||||
value !== null ? value.toFixed(fractionDigits) : "--",
|
|
||||||
area: false,
|
|
||||||
stack: undefined,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
grid={{ vertical: true, horizontal: true }}
|
|
||||||
sx={{
|
|
||||||
"& .MuiLineElement-root": {
|
|
||||||
strokeWidth: 2.5,
|
|
||||||
strokeLinecap: "round",
|
|
||||||
strokeLinejoin: "round",
|
|
||||||
},
|
|
||||||
"& .MuiMarkElement-root": {
|
|
||||||
scale: "0.8",
|
|
||||||
strokeWidth: 2,
|
|
||||||
},
|
|
||||||
"& .MuiChartsAxis-line": {
|
|
||||||
stroke: "#e0e0e0",
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
"& .MuiChartsAxis-tick": {
|
|
||||||
stroke: "#e0e0e0",
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
"& .MuiChartsGrid-line": {
|
|
||||||
stroke: "#d0d0d0",
|
|
||||||
strokeWidth: 0.8,
|
|
||||||
strokeDasharray: "4 4",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
slotProps={{
|
|
||||||
legend: {
|
|
||||||
direction: "row",
|
|
||||||
position: { horizontal: "middle", vertical: "bottom" },
|
|
||||||
padding: { bottom: 2, left: 0, right: 0 },
|
|
||||||
itemMarkWidth: 16,
|
|
||||||
itemMarkHeight: 3,
|
|
||||||
markGap: 8,
|
|
||||||
itemGap: 16,
|
|
||||||
labelStyle: {
|
|
||||||
fontSize: 12,
|
|
||||||
fill: "#333",
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
loadingOverlay: {
|
|
||||||
style: { backgroundColor: "rgba(255, 255, 255, 0.7)" },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
tooltip={{
|
|
||||||
trigger: "axis",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 时间范围滑块 */}
|
|
||||||
<Box sx={{ px: 3, pb: 2, pt: 1 }}>
|
|
||||||
<Stack direction="row" spacing={2} alignItems="center">
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ minWidth: 60, color: "text.secondary", fontSize: "0.8rem" }}
|
|
||||||
>
|
|
||||||
时间范围
|
|
||||||
</Typography>
|
|
||||||
<Slider
|
|
||||||
value={zoomRange}
|
|
||||||
onChange={(_, newValue) =>
|
|
||||||
setZoomRange(newValue as [number, number])
|
|
||||||
}
|
|
||||||
valueLabelDisplay="auto"
|
|
||||||
valueLabelFormat={(value) => {
|
|
||||||
const index = Math.floor((value / 100) * dataset.length);
|
|
||||||
if (dataset[index] && dataset[index].time instanceof Date) {
|
|
||||||
return dayjs(dataset[index].time).format("MM-DD HH:mm");
|
|
||||||
}
|
|
||||||
return `${value}%`;
|
|
||||||
}}
|
|
||||||
marks={[
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
label:
|
|
||||||
dataset.length > 0 && dataset[0].time instanceof Date
|
|
||||||
? dayjs(dataset[0].time).format("MM-DD HH:mm")
|
|
||||||
: "起始",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 100,
|
|
||||||
label:
|
|
||||||
dataset.length > 0 &&
|
|
||||||
dataset[dataset.length - 1].time instanceof Date
|
|
||||||
? dayjs(dataset[dataset.length - 1].time).format(
|
|
||||||
"MM-DD HH:mm"
|
|
||||||
)
|
|
||||||
: "结束",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
sx={{
|
|
||||||
flex: 1,
|
|
||||||
"& .MuiSlider-thumb": {
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
"& .MuiSlider-markLabel": {
|
|
||||||
fontSize: "0.7rem",
|
|
||||||
color: "text.secondary",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
onClick={() => setZoomRange([0, 100])}
|
|
||||||
sx={{ minWidth: 60, fontSize: "0.75rem" }}
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
{getTimeRangeLabel() && (
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
sx={{
|
|
||||||
color: "primary.main",
|
|
||||||
display: "block",
|
|
||||||
textAlign: "center",
|
|
||||||
mt: 0.5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
当前显示: {getTimeRangeLabel()} (共 {filteredDataset.length}{" "}
|
|
||||||
个数据点)
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1087,11 +946,18 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
label="开始时间"
|
label="开始时间"
|
||||||
value={from}
|
value={from}
|
||||||
onChange={(value) =>
|
onChange={(value) => {
|
||||||
value && dayjs.isDayjs(value) && setFrom(value)
|
if (value && dayjs.isDayjs(value) && value.isValid()) {
|
||||||
}
|
setFrom(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
onAccept={(value) => {
|
onAccept={(value) => {
|
||||||
if (value && dayjs.isDayjs(value) && hasDevices) {
|
if (
|
||||||
|
value &&
|
||||||
|
dayjs.isDayjs(value) &&
|
||||||
|
value.isValid() &&
|
||||||
|
hasDevices
|
||||||
|
) {
|
||||||
handleFetch("date-change");
|
handleFetch("date-change");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -1103,11 +969,18 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
label="结束时间"
|
label="结束时间"
|
||||||
value={to}
|
value={to}
|
||||||
onChange={(value) =>
|
onChange={(value) => {
|
||||||
value && dayjs.isDayjs(value) && setTo(value)
|
if (value && dayjs.isDayjs(value) && value.isValid()) {
|
||||||
}
|
setTo(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
onAccept={(value) => {
|
onAccept={(value) => {
|
||||||
if (value && dayjs.isDayjs(value) && hasDevices) {
|
if (
|
||||||
|
value &&
|
||||||
|
dayjs.isDayjs(value) &&
|
||||||
|
value.isValid() &&
|
||||||
|
hasDevices
|
||||||
|
) {
|
||||||
handleFetch("date-change");
|
handleFetch("date-change");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user