更改 SCADA 设备列表条目关键词;样式设置中新增彩虹色带;强制计算后清除计算范围内的缓存;
This commit is contained in:
@@ -104,6 +104,40 @@ const GRADIENT_PALETTES = [
|
||||
end: "rgba(148, 103, 189, 1)",
|
||||
},
|
||||
];
|
||||
// 离散彩虹色系 - 提供高区分度的颜色
|
||||
const RAINBOW_PALETTES = [
|
||||
{
|
||||
name: "正向彩虹",
|
||||
colors: [
|
||||
"rgba(255, 0, 0, 1)", // 红 #FF0000
|
||||
"rgba(255, 127, 0, 1)", // 橙 #FF7F00
|
||||
"rgba(255, 215, 0, 1)", // 金黄 #FFD700
|
||||
"rgba(199, 224, 0, 1)", // 黄绿 #C7E000
|
||||
"rgba(76, 175, 80, 1)", // 中绿 #4CAF50
|
||||
"rgba(0, 158, 115, 1)", // 青绿/翡翠 #009E73
|
||||
"rgba(0, 188, 212, 1)", // 青/青色 #00BCD4
|
||||
"rgba(33, 150, 243, 1)", // 天蓝 #2196F3
|
||||
"rgba(63, 81, 181, 1)", // 靛青 #3F51B5
|
||||
"rgba(142, 68, 173, 1)", // 紫 #8E44AD
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "反向彩虹",
|
||||
colors: [
|
||||
"rgba(142, 68, 173, 1)", // 紫 #8E44AD
|
||||
"rgba(63, 81, 181, 1)", // 靛青 #3F51B5
|
||||
"rgba(33, 150, 243, 1)", // 天蓝 #2196F3
|
||||
"rgba(0, 188, 212, 1)", // 青/青色 #00BCD4
|
||||
"rgba(0, 158, 115, 1)", // 青绿/翡翠 #009E73
|
||||
"rgba(76, 175, 80, 1)", // 中绿 #4CAF50
|
||||
"rgba(199, 224, 0, 1)", // 黄绿 #C7E000
|
||||
"rgba(255, 215, 0, 1)", // 金黄 #FFD700
|
||||
"rgba(255, 127, 0, 1)", // 橙 #FF7F00
|
||||
"rgba(255, 0, 0, 1)", // 红 #FF0000
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// 预设分类方法
|
||||
const CLASSIFICATION_METHODS = [
|
||||
{ name: "优雅分段", value: "pretty_breaks" },
|
||||
@@ -164,6 +198,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
// 颜色方案选择
|
||||
const [singlePaletteIndex, setSinglePaletteIndex] = useState(0);
|
||||
const [gradientPaletteIndex, setGradientPaletteIndex] = useState(0);
|
||||
const [rainbowPaletteIndex, setRainbowPaletteIndex] = useState(0);
|
||||
// 根据分段数生成相应数量的渐进颜色
|
||||
const generateGradientColors = useCallback(
|
||||
(segments: number): string[] => {
|
||||
@@ -189,6 +224,29 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
},
|
||||
[gradientPaletteIndex, parseColor]
|
||||
);
|
||||
|
||||
// 根据分段数生成彩虹色
|
||||
const generateRainbowColors = useCallback(
|
||||
(segments: number): string[] => {
|
||||
const baseColors = RAINBOW_PALETTES[rainbowPaletteIndex].colors;
|
||||
|
||||
if (segments <= baseColors.length) {
|
||||
// 如果分段数小于等于基础颜色数,均匀选取
|
||||
const step = baseColors.length / segments;
|
||||
return Array.from(
|
||||
{ length: segments },
|
||||
(_, i) => baseColors[Math.floor(i * step)]
|
||||
);
|
||||
} else {
|
||||
// 如果分段数大于基础颜色数,重复使用
|
||||
return Array.from(
|
||||
{ length: segments },
|
||||
(_, i) => baseColors[i % baseColors.length]
|
||||
);
|
||||
}
|
||||
},
|
||||
[rainbowPaletteIndex]
|
||||
);
|
||||
// 保存当前图层的样式状态
|
||||
const saveLayerStyle = useCallback(
|
||||
(layerId?: string, newLegendConfig?: LegendStyleConfig) => {
|
||||
@@ -350,7 +408,9 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
Array.from({ length: breaksLength }, () => {
|
||||
return SINGLE_COLOR_PALETTES[singlePaletteIndex].color;
|
||||
})
|
||||
: generateGradientColors(breaksLength);
|
||||
: styleConfig.colorType === "gradient"
|
||||
? generateGradientColors(breaksLength)
|
||||
: generateRainbowColors(breaksLength);
|
||||
// 计算每个分段的线条粗细和点大小
|
||||
const dimensions: number[] =
|
||||
layerType === "linestring"
|
||||
@@ -803,6 +863,73 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
if (styleConfig.colorType === "rainbow") {
|
||||
return (
|
||||
<FormControl
|
||||
variant="standard"
|
||||
fullWidth
|
||||
margin="dense"
|
||||
className="mt-3"
|
||||
>
|
||||
<InputLabel>离散彩虹方案</InputLabel>
|
||||
<Select
|
||||
value={rainbowPaletteIndex}
|
||||
onChange={(e) => setRainbowPaletteIndex(Number(e.target.value))}
|
||||
>
|
||||
{RAINBOW_PALETTES.map((p, idx) => {
|
||||
// 根据当前分段数生成该方案的预览颜色
|
||||
const baseColors = p.colors;
|
||||
const segments = styleConfig.segments;
|
||||
let previewColors: string[];
|
||||
|
||||
if (segments <= baseColors.length) {
|
||||
const step = baseColors.length / segments;
|
||||
previewColors = Array.from(
|
||||
{ length: segments },
|
||||
(_, i) => baseColors[Math.floor(i * step)]
|
||||
);
|
||||
} else {
|
||||
previewColors = Array.from(
|
||||
{ length: segments },
|
||||
(_, i) => baseColors[i % baseColors.length]
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem key={idx} value={idx}>
|
||||
<Box
|
||||
width="100%"
|
||||
sx={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
<Typography sx={{ marginRight: 1 }}>{p.name}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: "60%",
|
||||
height: 16,
|
||||
borderRadius: 2,
|
||||
display: "flex",
|
||||
border: "1px solid #ccc",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{previewColors.map((color, colorIdx) => (
|
||||
<Box
|
||||
key={colorIdx}
|
||||
sx={{
|
||||
flex: 1,
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
};
|
||||
// 根据不同图层的类型和颜色分类方案显示不同的大小设置
|
||||
const getSizeSetting = () => {
|
||||
@@ -813,7 +940,9 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
} else if (styleConfig.colorType === "gradient") {
|
||||
const { start, end } = GRADIENT_PALETTES[gradientPaletteIndex];
|
||||
colors = [start, end];
|
||||
} else if (styleConfig.colorType === "categorical") {
|
||||
} else if (styleConfig.colorType === "rainbow") {
|
||||
const rainbowColors = RAINBOW_PALETTES[rainbowPaletteIndex].colors;
|
||||
colors = [rainbowColors[0], rainbowColors[rainbowColors.length - 1]];
|
||||
}
|
||||
|
||||
if (selectedRenderLayer?.get("type") === "point") {
|
||||
@@ -1024,10 +1153,20 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
}
|
||||
onChange={(e) => {
|
||||
const index = e.target.value as number;
|
||||
setSelectedRenderLayer(
|
||||
index >= 0 ? renderLayers[index] : undefined
|
||||
);
|
||||
setStyleConfig((prev) => ({ ...prev, property: "" }));
|
||||
const newLayer = index >= 0 ? renderLayers[index] : undefined;
|
||||
setSelectedRenderLayer(newLayer);
|
||||
|
||||
// 检查新图层是否有缓存的样式,没有才清空
|
||||
if (newLayer) {
|
||||
const layerId = newLayer.get("value");
|
||||
const cachedStyleState = layerStyleStates.find(
|
||||
(state) => state.layerId === layerId
|
||||
);
|
||||
// 只有在没有缓存时才清空属性
|
||||
if (!cachedStyleState) {
|
||||
setStyleConfig((prev) => ({ ...prev, property: "" }));
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{renderLayers.map((layer, index) => {
|
||||
@@ -1102,13 +1241,13 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
onChange={(e) =>
|
||||
setStyleConfig((prev) => ({
|
||||
...prev,
|
||||
colorType: e.target.value as "gradient" | "categorical",
|
||||
colorType: e.target.value as "single" | "gradient" | "rainbow",
|
||||
}))
|
||||
}
|
||||
>
|
||||
<MenuItem value="single">单一色</MenuItem>
|
||||
<MenuItem value="gradient">渐进色</MenuItem>
|
||||
{/* <MenuItem value="categorical">分类色</MenuItem> */}
|
||||
<MenuItem value="rainbow">离散彩虹</MenuItem>
|
||||
</Select>
|
||||
{getColorSetting()}
|
||||
</FormControl>
|
||||
|
||||
@@ -27,7 +27,6 @@ import { FiSkipBack, FiSkipForward } from "react-icons/fi";
|
||||
import { useData } from "../MapComponent";
|
||||
import { config, NETWORK_NAME } from "@/config/config";
|
||||
import { useMap } from "../MapComponent";
|
||||
import { Network } from "inspector/promises";
|
||||
const backendUrl = config.backendUrl;
|
||||
|
||||
interface TimelineProps {
|
||||
@@ -139,10 +138,6 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// "Query Time:",
|
||||
// queryTime.toLocaleDateString() + " " + queryTime.toLocaleTimeString()
|
||||
// );
|
||||
// 等待所有有效请求
|
||||
const responses = await Promise.all(requests);
|
||||
|
||||
@@ -211,7 +206,6 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
|
||||
// 播放时间间隔选项
|
||||
const intervalOptions = [
|
||||
// { value: 1000, label: "1秒" },
|
||||
{ value: 2000, label: "2秒" },
|
||||
{ value: 5000, label: "5秒" },
|
||||
{ value: 10000, label: "10秒" },
|
||||
@@ -239,7 +233,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
}
|
||||
debounceRef.current = setTimeout(() => {
|
||||
setCurrentTime(value);
|
||||
}, 300); // 300ms 防抖延迟
|
||||
}, 500); // 500ms 防抖延迟
|
||||
},
|
||||
[timeRange, minTime, maxTime]
|
||||
);
|
||||
@@ -441,6 +435,11 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// 提前提取日期和时间值,避免异步操作期间被时间轴拖动改变
|
||||
const calculationDate = selectedDate;
|
||||
const calculationTime = currentTime;
|
||||
const calculationDateStr = calculationDate.toISOString().split("T")[0];
|
||||
|
||||
setIsCalculating(true);
|
||||
// 显示处理中的通知
|
||||
open?.({
|
||||
@@ -451,8 +450,8 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
try {
|
||||
const body = {
|
||||
name: NETWORK_NAME,
|
||||
simulation_date: selectedDate.toISOString().split("T")[0], // YYYY-MM-DD
|
||||
start_time: `${formatTime(currentTime)}:00`, // HH:MM:00
|
||||
simulation_date: calculationDateStr, // YYYY-MM-DD
|
||||
start_time: `${formatTime(calculationTime)}:00`, // HH:MM:00
|
||||
duration: calculatedInterval,
|
||||
};
|
||||
|
||||
@@ -468,6 +467,44 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
// 清空当天当前时刻及之后的缓存
|
||||
const currentDateStr = calculationDateStr;
|
||||
const currentTimeInMinutes = calculationTime;
|
||||
|
||||
// 清空node缓存
|
||||
const nodeCacheKeys = Array.from(nodeCacheRef.current.keys());
|
||||
nodeCacheKeys.forEach((key) => {
|
||||
const keyParts = key.split("_");
|
||||
const cacheDate = keyParts[0].split("T")[0];
|
||||
const cacheTimeStr = keyParts[0].split("T")[1];
|
||||
|
||||
if (cacheDate === currentDateStr && cacheTimeStr) {
|
||||
const [hours, minutes] = cacheTimeStr.split(":");
|
||||
const cacheTimeInMinutes = parseInt(hours) * 60 + parseInt(minutes);
|
||||
|
||||
if (cacheTimeInMinutes >= currentTimeInMinutes) {
|
||||
nodeCacheRef.current.delete(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 清空link缓存
|
||||
const linkCacheKeys = Array.from(linkCacheRef.current.keys());
|
||||
linkCacheKeys.forEach((key) => {
|
||||
const keyParts = key.split("_");
|
||||
const cacheDate = keyParts[0].split("T")[0];
|
||||
const cacheTimeStr = keyParts[0].split("T")[1];
|
||||
|
||||
if (cacheDate === currentDateStr && cacheTimeStr) {
|
||||
const [hours, minutes] = cacheTimeStr.split(":");
|
||||
const cacheTimeInMinutes = parseInt(hours) * 60 + parseInt(minutes);
|
||||
|
||||
if (cacheTimeInMinutes >= currentTimeInMinutes) {
|
||||
linkCacheRef.current.delete(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
open?.({
|
||||
type: "success",
|
||||
message: "重新计算成功",
|
||||
|
||||
Reference in New Issue
Block a user