地图样式新增自定义分类
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
Slider,
|
||||
Typography,
|
||||
Button,
|
||||
TextField,
|
||||
Box,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
@@ -46,6 +47,7 @@ interface StyleConfig {
|
||||
showLabels: boolean;
|
||||
opacity: number;
|
||||
adjustWidthByProperty: boolean; // 是否根据属性调整线条宽度
|
||||
customBreaks: number[]; // 自定义断点(用于 custom_breaks)
|
||||
}
|
||||
|
||||
// 图层样式状态接口
|
||||
@@ -143,6 +145,7 @@ const CLASSIFICATION_METHODS = [
|
||||
{ name: "优雅分段", value: "pretty_breaks" },
|
||||
// 浏览器中实现Jenks算法性能较差,暂时移除
|
||||
// { name: "自然间断", value: "jenks_optimized" },
|
||||
{ name: "自定义", value: "custom_breaks" },
|
||||
];
|
||||
|
||||
const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
@@ -193,6 +196,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
showLabels: false,
|
||||
opacity: 0.9,
|
||||
adjustWidthByProperty: true,
|
||||
customBreaks: [],
|
||||
});
|
||||
|
||||
// 颜色方案选择
|
||||
@@ -305,6 +309,32 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
const layerId = selectedRenderLayer.get("value");
|
||||
const property = styleConfig.property;
|
||||
if (layerId !== undefined && property !== undefined) {
|
||||
// 验证自定义断点设置
|
||||
if (styleConfig.classificationMethod === "custom_breaks") {
|
||||
const expected = styleConfig.segments + 1;
|
||||
const custom = styleConfig.customBreaks || [];
|
||||
if (
|
||||
custom.length !== expected ||
|
||||
custom.some((v) => v === undefined || v === null || isNaN(v))
|
||||
) {
|
||||
open?.({
|
||||
type: "error",
|
||||
message: `请设置 ${expected} 个有效的自定义阈值(数字)`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (custom.some((v) => v < 0)) {
|
||||
open?.({ type: "error", message: "自定义阈值必须大于等于 0" });
|
||||
return;
|
||||
}
|
||||
// 升序排序
|
||||
setStyleConfig((prev) => ({
|
||||
...prev,
|
||||
customBreaks: (prev.customBreaks || [])
|
||||
.slice(0, expected)
|
||||
.sort((a, b) => a - b),
|
||||
}));
|
||||
}
|
||||
// 更新文字标签设置
|
||||
if (layerId === "junctions") {
|
||||
if (setJunctionText && setShowJunctionText) {
|
||||
@@ -351,11 +381,30 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
|
||||
// 更新节点数据属性
|
||||
const segments = junctionStyleConfigState?.styleConfig.segments ?? 5;
|
||||
const breaks = calculateClassification(
|
||||
currentJunctionCalData.map((d) => d.value),
|
||||
segments,
|
||||
styleConfig.classificationMethod
|
||||
);
|
||||
let breaks: number[] = [];
|
||||
if (
|
||||
junctionStyleConfigState?.styleConfig.classificationMethod ===
|
||||
"custom_breaks"
|
||||
) {
|
||||
// 使用自定义断点(保证为 segments + 1 个断点,按升序)
|
||||
const desired = segments + 1;
|
||||
breaks = (
|
||||
junctionStyleConfigState?.styleConfig.customBreaks || []
|
||||
).slice(0, desired);
|
||||
breaks.sort((a, b) => a - b);
|
||||
// 过滤出 >= 0
|
||||
breaks = breaks.filter((v) => v >= 0);
|
||||
// 如果不足则补齐最后一个值
|
||||
while (breaks.length < desired)
|
||||
breaks.push(breaks[breaks.length - 1] ?? 0);
|
||||
} else {
|
||||
const calc = calculateClassification(
|
||||
currentJunctionCalData.map((d) => d.value),
|
||||
segments,
|
||||
styleConfig.classificationMethod
|
||||
);
|
||||
breaks = calc;
|
||||
}
|
||||
if (breaks.length === 0) {
|
||||
console.warn("计算的 breaks 为空,无法应用样式");
|
||||
return;
|
||||
@@ -373,11 +422,29 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
);
|
||||
// 更新管道数据属性
|
||||
const segments = pipeStyleConfigState?.styleConfig.segments ?? 5;
|
||||
const breaks = calculateClassification(
|
||||
currentPipeCalData.map((d) => d.value),
|
||||
segments,
|
||||
styleConfig.classificationMethod
|
||||
);
|
||||
let breaks: number[] = [];
|
||||
if (
|
||||
pipeStyleConfigState?.styleConfig.classificationMethod ===
|
||||
"custom_breaks"
|
||||
) {
|
||||
// 使用自定义断点(保证为 segments + 1 个断点,按升序)
|
||||
const desired = segments + 1;
|
||||
breaks = (pipeStyleConfigState?.styleConfig.customBreaks || []).slice(
|
||||
0,
|
||||
desired
|
||||
);
|
||||
breaks.sort((a, b) => a - b);
|
||||
breaks = breaks.filter((v) => v >= 0);
|
||||
while (breaks.length < desired)
|
||||
breaks.push(breaks[breaks.length - 1] ?? 0);
|
||||
} else {
|
||||
const calc = calculateClassification(
|
||||
currentPipeCalData.map((d) => d.value),
|
||||
segments,
|
||||
styleConfig.classificationMethod
|
||||
);
|
||||
breaks = calc;
|
||||
}
|
||||
if (pipeStyleConfigState) applyLayerStyle(pipeStyleConfigState, breaks);
|
||||
}
|
||||
};
|
||||
@@ -784,6 +851,45 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
}
|
||||
}, [styleConfig.colorType]);
|
||||
|
||||
// 初始化或调整自定义断点数组长度,默认使用 pretty_breaks 生成若存在数据
|
||||
useEffect(() => {
|
||||
if (styleConfig.classificationMethod !== "custom_breaks") return;
|
||||
|
||||
const numBreaks = styleConfig.segments + 1;
|
||||
setStyleConfig((prev) => {
|
||||
const prevBreaks = prev.customBreaks || [];
|
||||
if (prevBreaks.length === numBreaks) return prev;
|
||||
|
||||
const selectedLayerId = selectedRenderLayer?.get("value");
|
||||
let dataArr: number[] = [];
|
||||
if (selectedLayerId === "junctions" && currentJunctionCalData)
|
||||
dataArr = currentJunctionCalData.map((d) => d.value);
|
||||
else if (selectedLayerId === "pipes" && currentPipeCalData)
|
||||
dataArr = currentPipeCalData.map((d) => d.value);
|
||||
|
||||
let defaultBreaks: number[] = Array.from({ length: numBreaks }, () => 0);
|
||||
if (dataArr && dataArr.length > 0) {
|
||||
defaultBreaks = calculateClassification(
|
||||
dataArr,
|
||||
styleConfig.segments,
|
||||
"pretty_breaks"
|
||||
);
|
||||
defaultBreaks = defaultBreaks.slice(0, numBreaks);
|
||||
if (defaultBreaks.length < numBreaks)
|
||||
while (defaultBreaks.length < numBreaks)
|
||||
defaultBreaks.push(defaultBreaks[defaultBreaks.length - 1] ?? 0);
|
||||
}
|
||||
|
||||
return { ...prev, customBreaks: defaultBreaks };
|
||||
});
|
||||
}, [
|
||||
styleConfig.classificationMethod,
|
||||
styleConfig.segments,
|
||||
selectedRenderLayer,
|
||||
currentJunctionCalData,
|
||||
currentPipeCalData,
|
||||
]);
|
||||
|
||||
const getColorSetting = () => {
|
||||
if (styleConfig.colorType === "single") {
|
||||
return (
|
||||
@@ -1222,13 +1328,67 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
onChange={(_, value) =>
|
||||
setStyleConfig((prev) => ({ ...prev, segments: value as number }))
|
||||
}
|
||||
min={3}
|
||||
min={2}
|
||||
max={10}
|
||||
step={1}
|
||||
marks
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
{/* 自定义分类:手动填写区间分段(仅当选择自定义方式时显示) */}
|
||||
{styleConfig.classificationMethod === "custom_breaks" && (
|
||||
<Box className="mt-3 p-2 bg-gray-50 rounded">
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
手动设置区间阈值(按升序填写,最小值 >= 0)
|
||||
</Typography>
|
||||
<Box className="flex flex-col gap-2">
|
||||
{Array.from({ length: styleConfig.segments + 1 }).map(
|
||||
(_, idx) => (
|
||||
<TextField
|
||||
key={idx}
|
||||
label={`阈值 ${idx + 1}`}
|
||||
type="number"
|
||||
size="small"
|
||||
slotProps={{ input: { inputProps: { min: 0, step: 0.1 } } }}
|
||||
value={
|
||||
(styleConfig.customBreaks &&
|
||||
styleConfig.customBreaks[idx]) ??
|
||||
""
|
||||
}
|
||||
onChange={(e) => {
|
||||
const v = parseFloat(e.target.value);
|
||||
setStyleConfig((prev) => {
|
||||
const prevBreaks = prev.customBreaks
|
||||
? [...prev.customBreaks]
|
||||
: [];
|
||||
// 保证长度
|
||||
while (prevBreaks.length < styleConfig.segments + 1)
|
||||
prevBreaks.push(0);
|
||||
prevBreaks[idx] = isNaN(v) ? 0 : Math.max(0, v);
|
||||
return { ...prev, customBreaks: prevBreaks };
|
||||
});
|
||||
}}
|
||||
onBlur={() => {
|
||||
// on blur 保证升序
|
||||
setStyleConfig((prev) => {
|
||||
const prevBreaks = (prev.customBreaks || []).slice(
|
||||
0,
|
||||
styleConfig.segments + 1
|
||||
);
|
||||
prevBreaks.sort((a, b) => a - b);
|
||||
return { ...prev, customBreaks: prevBreaks };
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
注: 阈值数量由分类数量决定 (segments + 1)。例如 segments=5 将显示
|
||||
6 个阈值。
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{/* 颜色方案 */}
|
||||
<FormControl variant="standard" fullWidth margin="dense">
|
||||
<InputLabel>
|
||||
|
||||
@@ -37,6 +37,7 @@ interface StyleConfig {
|
||||
showLabels: boolean;
|
||||
opacity: number;
|
||||
adjustWidthByProperty: boolean;
|
||||
customBreaks: number[];
|
||||
}
|
||||
|
||||
interface LegendStyleConfig {
|
||||
@@ -98,6 +99,7 @@ const Toolbar: React.FC<ToolbarProps> = ({ hiddenButtons, queryType }) => {
|
||||
showLabels: false,
|
||||
opacity: 0.9,
|
||||
adjustWidthByProperty: true,
|
||||
customBreaks: [],
|
||||
},
|
||||
legendConfig: {
|
||||
layerId: "junctions",
|
||||
@@ -128,6 +130,7 @@ const Toolbar: React.FC<ToolbarProps> = ({ hiddenButtons, queryType }) => {
|
||||
showLabels: false,
|
||||
opacity: 0.9,
|
||||
adjustWidthByProperty: true,
|
||||
customBreaks: [],
|
||||
},
|
||||
legendConfig: {
|
||||
layerId: "pipes",
|
||||
|
||||
Reference in New Issue
Block a user