|
|
@@ -1,43 +1,33 @@
|
|
|
// 相位方向箭头在"路口方块"内的几何布局
|
|
|
-// 共享于 SignalTimingChart (canvas 内绘图) 与 PhaseDiagram (CSS 布局)
|
|
|
//
|
|
|
-// 坐标系: 30 × 30 单位的路口方块, 四角 + padX/padY + baseW/baseH
|
|
|
-// pos: LT / RT / LB / RB -- 哪个角落锚定
|
|
|
-// padX, padY: 离该角落的偏移 (与 pos 共同决定 anchor 边)
|
|
|
-// baseW, baseH: 箭头本身占的尺寸
|
|
|
-
|
|
|
-export const POS_MAP = {
|
|
|
- // 1. 上方驶入 (北方向) -> 靠左上角 (LT)
|
|
|
- 'STRAIGHT_DOWN': { pos: 'LT', padX: 10, padY: 0, baseW: 7, baseH: 20.67 },
|
|
|
- 'TURN_DOWN_LEFT': { pos: 'LT', padX: 10, padY: 0, baseW: 13, baseH: 21.33 },
|
|
|
- 'TURN_DOWN_LEFT_UTURN': { pos: 'LT', padX: 10, padY: 0, baseW: 13, baseH: 22.67 },
|
|
|
- 'TURN_DOWN_RIGHT': { pos: 'LT', padX: 10, padY: 0, baseW: 13, baseH: 21.33 }, // 北右转(镜像)
|
|
|
-
|
|
|
- // 2. 下方驶入 (南方向) -> 靠右下角 (RB)
|
|
|
- 'STRAIGHT_UP': { pos: 'RB', padX: 10, padY: 0, baseW: 7, baseH: 20.67 },
|
|
|
- 'TURN_UP_LEFT': { pos: 'RB', padX: 10, padY: 0, baseW: 13, baseH: 21.33 },
|
|
|
- 'TURN_UP_LEFT_UTURN': { pos: 'RB', padX: 10, padY: 0, baseW: 13, baseH: 22.67 },
|
|
|
- 'TURN_UP_RIGHT': { pos: 'RB', padX: 10, padY: 0, baseW: 13, baseH: 21.33 }, // 南右转(镜像)
|
|
|
-
|
|
|
- // 3. 右侧驶入 (东方向) -> 靠右上角 (RT)
|
|
|
- 'STRAIGHT_LEFT': { pos: 'RT', padX: 0, padY: 10, baseW: 20.33, baseH: 6.33 },
|
|
|
- 'TURN_LEFT_DOWN': { pos: 'RT', padX: 0, padY: 10, baseW: 20.67, baseH: 12.33 },
|
|
|
- 'TURN_LEFT_DOWN_UTURN': { pos: 'RT', padX: 0, padY: 10, baseW: 22.67, baseH: 12.33 },
|
|
|
- 'TURN_LEFT_UP': { pos: 'RT', padX: 0, padY: 10, baseW: 20.67, baseH: 12.33 }, // 东右转(镜像)
|
|
|
-
|
|
|
- // 4. 左侧驶入 (西方向) -> 靠左下角 (LB)
|
|
|
- 'STRAIGHT_RIGHT': { pos: 'LB', padX: 0, padY: 10, baseW: 20.33, baseH: 6.33 },
|
|
|
- 'TURN_RIGHT_UP': { pos: 'LB', padX: 0, padY: 10, baseW: 20.67, baseH: 12.33 },
|
|
|
- 'TURN_RIGHT_UP_UTURN': { pos: 'LB', padX: 0, padY: 10, baseW: 22.67, baseH: 12.33 },
|
|
|
- 'TURN_RIGHT_DOWN': { pos: 'LB', padX: 0, padY: 10, baseW: 20.67, baseH: 12.33 }, // 西右转(镜像)
|
|
|
-};
|
|
|
+// 本文件聚焦于 layoutIconsAdaptive 算法本身。
|
|
|
+// 数据 / 规则 / 参数全部托管到 src/config/:
|
|
|
+// - POS_MAP → @/config/phaseIconTokens
|
|
|
+// - actionPriority → @/config/phaseIconActions
|
|
|
+// - CORNER_REFERENCE → @/config/phaseIconActions
|
|
|
+// - sortIconsByLane → @/config/phaseIconActions
|
|
|
+// - RIGHT_TURN_TOKENS → @/config/phaseIconActions
|
|
|
+// - PHASE_DIAGRAM_LAYOUT → @/config/phaseDiagramConfig
|
|
|
+//
|
|
|
+// 为向后兼容, 仍 re-export 常用 token 工具, 旧调用
|
|
|
+// (`import { POS_MAP } from '@/utils/phaseLayout'`) 仍然有效。
|
|
|
+
|
|
|
+import { POS_MAP } from '@/config/phaseIconTokens';
|
|
|
+import {
|
|
|
+ actionPriority,
|
|
|
+ sortIconsByLane,
|
|
|
+ CORNER_REFERENCE,
|
|
|
+} from '@/config/phaseIconActions';
|
|
|
+import { PHASE_DIAGRAM_LAYOUT } from '@/config/phaseDiagramConfig';
|
|
|
+
|
|
|
+// 向后兼容 re-export
|
|
|
+export { POS_MAP, actionPriority, sortIconsByLane };
|
|
|
|
|
|
// 把 token 对应的 POS_MAP 项换算成 CSS 绝对定位 style (百分比, 自适应任意尺寸的方框)
|
|
|
//
|
|
|
// 两个独立缩放参数:
|
|
|
// sizeScale - 作用于 baseW/baseH, 控制 "箭头本身大小" (1=原图, <1 变小, >1 变大)
|
|
|
// padScale - 作用于 padX/padY, 控制 "离角偏移" (1=原图, <1 更靠角=中央留白大, >1 更靠中心)
|
|
|
-// 拆成两个是因为常见需求是"大小别动, 多腾点中央空间"。
|
|
|
export function positionStyleOf(token, sizeScale = 1, padScale = 1) {
|
|
|
const m = POS_MAP[token];
|
|
|
if (!m) return {};
|
|
|
@@ -53,54 +43,22 @@ export function positionStyleOf(token, sizeScale = 1, padScale = 1) {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
-// ----------------------------------------------------------------------------
|
|
|
-// 自适应多 icon 布局:当同一 corner 有多个 token (NS 或 EW 同向多 icon) 时,
|
|
|
-// 沿对应方向均分 slot,每个 icon 按其原始 baseW/baseH 比例 fit 到 slot 内。
|
|
|
+// 自适应多 icon 布局: 固定 3 slot, 让 1 icon 与多 icon 中同位置 icon 完全对齐
|
|
|
//
|
|
|
-// 角落分组规则:
|
|
|
-// LT/RB → NS corner,icons 沿 X 轴水平排列(北/南方向用上下半区)
|
|
|
-// RT/LB → EW corner,icons 沿 Y 轴垂直排列(东/西方向用左右半区)
|
|
|
+// 算法步骤:
|
|
|
+// 1. icons 按 corner (LT/RT/LB/RB) 分组
|
|
|
+// 2. 每 corner 始终按 3 个 reference token (右转/直行/左转) 算 slot 位置
|
|
|
+// 3. 实际 icon 按 actionPriority (0/1/2/3) 落到对应 slot
|
|
|
//
|
|
|
-// 单 corner 单 icon 时退化为 positionStyleOf 行为(保持向后兼容)。
|
|
|
-//
|
|
|
-// @returns { [token: string]: style }
|
|
|
-// ----------------------------------------------------------------------------
|
|
|
-const HALF_BLOCK = 15; // 30×30 路口方块的半区大小(单位)
|
|
|
-const CENTER_RESERVED = 1; // 中央留给编号 (.phase-no) 的半边距(单位)→ 中央总宽 2 单位
|
|
|
-const EFFECTIVE_HALF = HALF_BLOCK - CENTER_RESERVED; // icon 主轴最大可占用(14 单位 = 46.7%)
|
|
|
-const ICON_GAP = 0.5; // 多 icon 之间的间距(单位)
|
|
|
-const ICON_MAIN_SIZE = 12; // 每个 icon 主轴方向的统一尺寸(单位)
|
|
|
- // 1 icon 时主轴 = 12×0.55 = 6.6 单位 (pct 22%)
|
|
|
- // 3 icon 时受 EFFECTIVE_HALF=14 限制 compress 到 pct 14.3%
|
|
|
-
|
|
|
-// 按真实车道布局的排列优先级:从外(路口边)到内(路口中心)依次为
|
|
|
-// 右转 → 直行 → 左转 → 左转掉头
|
|
|
-// corner 内多 icon 按此顺序沿主轴累加,与司机视角一致
|
|
|
-const RIGHT_TURN_TOKENS = new Set([
|
|
|
- 'TURN_DOWN_RIGHT', 'TURN_UP_RIGHT', 'TURN_LEFT_UP', 'TURN_RIGHT_DOWN'
|
|
|
-]);
|
|
|
-
|
|
|
-export function actionPriority(token) {
|
|
|
- if (RIGHT_TURN_TOKENS.has(token)) return 0; // 右转最外
|
|
|
- if (token.startsWith('STRAIGHT_')) return 1; // 直行居中
|
|
|
- if (token.endsWith('_UTURN')) return 3; // 左转掉头最内
|
|
|
- return 2; // 其它为左转
|
|
|
-}
|
|
|
-
|
|
|
-export function sortIconsByLane(tokens) {
|
|
|
- return [...tokens].sort((a, b) => actionPriority(a) - actionPriority(b));
|
|
|
-}
|
|
|
-
|
|
|
-// 每个 corner 的 3 个 reference token:[右转, 直行, 左转]
|
|
|
-// 用于计算"固定 3 slot"位置,让单 icon 与多 icon 中同位置 icon 完全对齐
|
|
|
-const CORNER_REFERENCE = {
|
|
|
- LT: ['TURN_DOWN_RIGHT', 'STRAIGHT_DOWN', 'TURN_DOWN_LEFT'],
|
|
|
- RB: ['TURN_UP_RIGHT', 'STRAIGHT_UP', 'TURN_UP_LEFT'],
|
|
|
- RT: ['TURN_LEFT_UP', 'STRAIGHT_LEFT', 'TURN_LEFT_DOWN'],
|
|
|
- LB: ['TURN_RIGHT_DOWN', 'STRAIGHT_RIGHT', 'TURN_RIGHT_UP'],
|
|
|
-};
|
|
|
-
|
|
|
+// 参数全部从 PHASE_DIAGRAM_LAYOUT 读取, 调整请改 src/config/phaseDiagramConfig.js
|
|
|
export function layoutIconsAdaptive(icons, sizeScale = 1, padScale = 1) {
|
|
|
+ const {
|
|
|
+ HALF_BLOCK, CENTER_RESERVED, ICON_MAIN_SIZE, ICON_GAP,
|
|
|
+ PAD_FACTOR, TURN_RATIO, ENABLE_STRAIGHT_SHRINK,
|
|
|
+ } = PHASE_DIAGRAM_LAYOUT;
|
|
|
+ const EFFECTIVE_HALF = HALF_BLOCK - CENTER_RESERVED;
|
|
|
+ const mainSize = ICON_MAIN_SIZE * sizeScale;
|
|
|
+
|
|
|
// 按 corner 分组
|
|
|
const byCorner = { LT: [], RT: [], LB: [], RB: [] };
|
|
|
icons.forEach(token => {
|
|
|
@@ -111,17 +69,12 @@ export function layoutIconsAdaptive(icons, sizeScale = 1, padScale = 1) {
|
|
|
const styles = {};
|
|
|
const pct = n => `${(n / 30) * 100}%`;
|
|
|
|
|
|
- // STRAIGHT 整体缩小 sf 让副轴与 TURN 对齐
|
|
|
- const TURN_RATIO = 1.64;
|
|
|
- const mainSize = ICON_MAIN_SIZE * sizeScale;
|
|
|
- const PAD_FACTOR = 0.5;
|
|
|
-
|
|
|
const computeSize = (token, isNS) => {
|
|
|
const m = POS_MAP[token];
|
|
|
if (!m) return { w: 0, h: 0 };
|
|
|
const isStraight = token.startsWith('STRAIGHT_');
|
|
|
const straightRatio = isNS ? (m.baseH / m.baseW) : (m.baseW / m.baseH);
|
|
|
- const sf = isStraight ? (TURN_RATIO / straightRatio) : 1;
|
|
|
+ const sf = (isStraight && ENABLE_STRAIGHT_SHRINK) ? (TURN_RATIO / straightRatio) : 1;
|
|
|
let w, h;
|
|
|
if (isNS) {
|
|
|
w = mainSize * sf;
|
|
|
@@ -141,7 +94,7 @@ export function layoutIconsAdaptive(icons, sizeScale = 1, padScale = 1) {
|
|
|
const sideY = corner === 'LT' || corner === 'RT' ? 'top' : 'bottom';
|
|
|
|
|
|
// 始终按 corner 的 3 个 reference token (右转/直行/左转) 计算 slot 位置
|
|
|
- // 这样 1 icon 与多 icon 中同位置 icon 完全对齐(大小 + 位置)
|
|
|
+ // 这样 1 icon 与多 icon 中同位置 icon 完全对齐 (大小 + 位置)
|
|
|
const refTokens = CORNER_REFERENCE[corner];
|
|
|
const refSizes = refTokens.map(t => computeSize(t, isNS));
|
|
|
|
|
|
@@ -151,7 +104,7 @@ export function layoutIconsAdaptive(icons, sizeScale = 1, padScale = 1) {
|
|
|
const totalLen = firstPad + sumMain + ICON_GAP * (refSizes.length - 1);
|
|
|
const compress = totalLen > EFFECTIVE_HALF ? EFFECTIVE_HALF / totalLen : 1;
|
|
|
|
|
|
- // 计算每个 slot 的位置(基于 reference)
|
|
|
+ // 计算每个 slot 的位置 (基于 reference)
|
|
|
let cumOffset = firstPad * compress;
|
|
|
const slotPositions = refSizes.map(size => {
|
|
|
const w = size.w * compress;
|
|
|
@@ -161,14 +114,14 @@ export function layoutIconsAdaptive(icons, sizeScale = 1, padScale = 1) {
|
|
|
return slot;
|
|
|
});
|
|
|
|
|
|
- // 渲染实际 token,按 actionPriority 找对应 slot
|
|
|
+ // 渲染实际 token, 按 actionPriority 找对应 slot
|
|
|
// priority 3 (左转掉头) 暂时归到 slot 2 (左转位置)
|
|
|
tokens.forEach(actualToken => {
|
|
|
const priority = actionPriority(actualToken);
|
|
|
const slotIdx = Math.min(priority, refSizes.length - 1);
|
|
|
const slotPos = slotPositions[slotIdx];
|
|
|
|
|
|
- // 实际 token 的尺寸(按其原 baseW/baseH 算)
|
|
|
+ // 实际 token 的尺寸 (按其原 baseW/baseH 算)
|
|
|
const actSize = computeSize(actualToken, isNS);
|
|
|
const w = actSize.w * compress;
|
|
|
const h = actSize.h * compress;
|