feat(web): 新增后台线路拓扑图布局编辑器,允许手动拖拽调整所有站点坐标
This commit is contained in:
+32
-6
@@ -209,10 +209,28 @@
|
||||
}
|
||||
|
||||
stationXByCanonical = {};
|
||||
stationYByCanonical = {};
|
||||
const customXByCanonical = {};
|
||||
const customYByCanonical = {};
|
||||
|
||||
for (const s of stationsData) {
|
||||
const code = String(s.code || s.编号 || '').trim();
|
||||
if (!code) continue;
|
||||
const canonical = stationCanonicalByCode[code] || code;
|
||||
if (s.svg_x != null && s.svg_y != null) {
|
||||
customXByCanonical[canonical] = Number(s.svg_x);
|
||||
customYByCanonical[canonical] = Number(s.svg_y);
|
||||
}
|
||||
}
|
||||
|
||||
for (const canonical of Object.keys(occurrences)) {
|
||||
const arr = occurrences[canonical];
|
||||
const avg = arr.reduce((a,b)=>a+b,0) / Math.max(1, arr.length);
|
||||
stationXByCanonical[canonical] = startX + Math.round(avg);
|
||||
if (customXByCanonical[canonical] != null) {
|
||||
stationXByCanonical[canonical] = customXByCanonical[canonical];
|
||||
} else {
|
||||
const arr = occurrences[canonical];
|
||||
const avg = arr.reduce((a,b)=>a+b,0) / Math.max(1, arr.length);
|
||||
stationXByCanonical[canonical] = startX + Math.round(avg);
|
||||
}
|
||||
}
|
||||
|
||||
for (let pass = 0; pass < 3; pass++) {
|
||||
@@ -221,6 +239,10 @@
|
||||
for (const code of li.stops) {
|
||||
if (code === '-') continue;
|
||||
const canonical = stationCanonicalByCode[code] || code;
|
||||
if (customXByCanonical[canonical] != null) {
|
||||
prevX = customXByCanonical[canonical];
|
||||
continue;
|
||||
}
|
||||
const x = stationXByCanonical[canonical] ?? (prevX + minGapX);
|
||||
const nx = Math.max(x, prevX + minGapX);
|
||||
if (stationXByCanonical[canonical] == null || nx > stationXByCanonical[canonical]) {
|
||||
@@ -234,18 +256,21 @@
|
||||
// 3. Assign Grid Y to canonical stations (Average of their lines)
|
||||
const hubYByCanonical = {};
|
||||
for (const canonical of Object.keys(lineIdxsByCanonical)) {
|
||||
if (customYByCanonical[canonical] != null) {
|
||||
hubYByCanonical[canonical] = customYByCanonical[canonical];
|
||||
continue;
|
||||
}
|
||||
const idxs = Array.from(lineIdxsByCanonical[canonical] || []).sort((a, b) => a - b);
|
||||
if (!idxs.length) continue;
|
||||
const ys = idxs.map(idx => baseY + idx * lineGapY);
|
||||
hubYByCanonical[canonical] = ys.reduce((sum, y) => sum + y, 0) / ys.length;
|
||||
}
|
||||
|
||||
stationYByCanonical = {};
|
||||
for (const li of lineStops) {
|
||||
const yNode = baseY + li.idx * lineGapY;
|
||||
for (const c of li.stops) {
|
||||
const canonical = stationCanonicalByCode[c] || c;
|
||||
stationYByCanonical[c] = hubYByCanonical[canonical] ?? yNode;
|
||||
stationYByCanonical[c] = customYByCanonical[canonical] ?? hubYByCanonical[canonical] ?? yNode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,8 +421,9 @@
|
||||
const allCanonicals = Object.keys(stationXByCanonical);
|
||||
const minX = Math.min(...allCanonicals.map(c => stationXByCanonical[c] || 0));
|
||||
const maxX = Math.max(...allCanonicals.map(c => stationXByCanonical[c] || 0));
|
||||
const maxY = Math.max(...allCanonicals.map(c => stationYByCanonical[c] || 0));
|
||||
const width = Math.max(560, maxX + 200);
|
||||
const height = Math.max(260, baseY + currentLineIdx * lineGapY + 120);
|
||||
const height = Math.max(260, maxY + 120, baseY + lineStops.length * lineGapY + 120);
|
||||
mapContainer.innerHTML = `<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">${svgContent}</svg>`;
|
||||
|
||||
} catch(e) {
|
||||
|
||||
Reference in New Issue
Block a user