refactor & feat & fix: 重写SVG生成器,优化前端线路绘制与修复样式
重写服务器端SVG生成逻辑,将原铁路线路图生成功能替换为站点间票价矩阵生成。 优化前端换乘路径绘制:新增线路偏移与弯道处理,避免多条线路路径重叠。 修复站点标记的外层圆圈填充颜色,使其正确使用对应线路的配色。
This commit is contained in:
+31
-5
@@ -63,10 +63,11 @@
|
||||
return out;
|
||||
};
|
||||
|
||||
const buildTransitPath = (points, radius = 12) => {
|
||||
const buildTransitPath = (points, radius = 12, options = {}) => {
|
||||
const src = Array.isArray(points) ? points.filter(Boolean) : [];
|
||||
if (src.length === 0) return '';
|
||||
if (src.length === 1) return `M ${src[0].x} ${src[0].y}`;
|
||||
const laneOffset = Number(options?.laneOffset) || 0;
|
||||
|
||||
const fmt = (n) => {
|
||||
const v = Number(n);
|
||||
@@ -86,6 +87,23 @@
|
||||
const code = String(p?.code || '').trim();
|
||||
return !!code && stationTransfer && stationTransfer.has(code);
|
||||
};
|
||||
const resolveLaneBendX = (from, to) => {
|
||||
if (!laneOffset) return null;
|
||||
const fromTransfer = isTransfer(from);
|
||||
const toTransfer = isTransfer(to);
|
||||
if (!fromTransfer && !toTransfer) return null;
|
||||
|
||||
const minX = Math.min(from.x, to.x);
|
||||
const maxX = Math.max(from.x, to.x);
|
||||
if ((maxX - minX) < 28) return null;
|
||||
|
||||
const anchorX = toTransfer && !fromTransfer
|
||||
? to.x
|
||||
: (fromTransfer && !toTransfer ? from.x : ((from.x + to.x) / 2));
|
||||
const candidate = Math.max(minX + 12, Math.min(maxX - 12, anchorX + laneOffset));
|
||||
if (Math.abs(candidate - from.x) < 4 || Math.abs(candidate - to.x) < 4) return null;
|
||||
return candidate;
|
||||
};
|
||||
const simplify = (arr) => {
|
||||
const out = [];
|
||||
for (const p of arr) push(out, p);
|
||||
@@ -121,7 +139,11 @@
|
||||
|
||||
const aT = isTransfer(prev);
|
||||
const bT = isTransfer(next);
|
||||
if (bT && !aT) {
|
||||
const bendX = resolveLaneBendX(prev, next);
|
||||
if (Number.isFinite(bendX)) {
|
||||
push(orth, { x: bendX, y: prev.y, code: prev?.code || next?.code });
|
||||
push(orth, { x: bendX, y: y2, code: next?.code || prev?.code });
|
||||
} else if (bT && !aT) {
|
||||
push(orth, { x: x2, y: prev.y, code: next?.code });
|
||||
} else if (aT && !bT) {
|
||||
push(orth, { x: prev.x, y: y2, code: prev?.code });
|
||||
@@ -366,6 +388,10 @@
|
||||
};
|
||||
|
||||
const labelPlacements = {};
|
||||
const getLaneOffset = (lineIdx) => {
|
||||
const center = (Math.max(totalLines, 1) - 1) / 2;
|
||||
return Math.max(-18, Math.min(18, Math.round((lineIdx - center) * 4)));
|
||||
};
|
||||
const placeLabel = (x, y, isStart, name, enName, color) => {
|
||||
const key = `${x},${y},${isStart ? 'left' : 'right'}`;
|
||||
const count = labelPlacements[key] || 0;
|
||||
@@ -411,7 +437,7 @@
|
||||
};
|
||||
});
|
||||
|
||||
const d = buildTransitPath(pts, 16);
|
||||
const d = buildTransitPath(pts, 16, { laneOffset: getLaneOffset(li.idx) });
|
||||
svgContent += `<path d="${d}" fill="none" stroke="${li.color}" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" />`;
|
||||
|
||||
// Add line name labels at ends
|
||||
@@ -446,8 +472,8 @@
|
||||
markerSvg += `<circle class="node-core" cx="${x}" cy="${y}" r="6.5" fill="#ffffff" stroke="#334155" stroke-width="2.25" />`;
|
||||
} else {
|
||||
const color = uniqueColors[0] || '#93a2b7';
|
||||
markerSvg += `<circle cx="${x}" cy="${y}" r="12.5" fill="#ffffff" stroke="${color}" stroke-width="5" />`;
|
||||
markerSvg += `<circle class="node-core" cx="${x}" cy="${y}" r="7" fill="#ffffff" stroke="#334155" stroke-width="2.25" />`;
|
||||
markerSvg += `<circle cx="${x}" cy="${y}" r="12.5" fill="${color}" stroke="${color}" stroke-width="4" />`;
|
||||
markerSvg += `<circle class="node-core" cx="${x}" cy="${y}" r="7" fill="#ffffff" stroke="${color}" stroke-width="2.25" />`;
|
||||
}
|
||||
|
||||
const preferTop = isTransfer || (i % 2 === 0 && y >= baseY + 20);
|
||||
|
||||
Reference in New Issue
Block a user