Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1
This commit is contained in:
@@ -19,6 +19,7 @@ import type {
|
||||
ProfileAlignment,
|
||||
} from "./logic/B05_Profile_UI_Profile_Alignment";
|
||||
import { stationLabel } from "../B06_Section/B06_Section_UI_Section_Common";
|
||||
import { crowdedThickness } from "../B06_Section/logic/B06_Section_Label_Tiers";
|
||||
import {
|
||||
irregularStationId,
|
||||
isPipeStation,
|
||||
@@ -74,7 +75,7 @@ const CHAR_WIDTH_EM = 0.6;
|
||||
const CELL_PADDING_PX = 2;
|
||||
/** 가로 여유가 있을 때 목표로 삼는 글자 크기. 캔버스 최소 폭 산정의 기준이 된다. */
|
||||
export const TABLE_TARGET_FONT_PX = 12;
|
||||
/** 곡선 셀이 이웃과 겹칠 때 세로(회전) 표기로 줄이는 셀 폭(px). */
|
||||
/** 셀이 이웃과 겹칠 때 세로(회전) 표기로 줄이는 셀 폭 위 끝(px · 이웃 사이 틈이 더 좁으면 그 틈). */
|
||||
const CURVE_ROTATED_WIDTH_PX = 16;
|
||||
/**
|
||||
* 같은 측점으로 볼 누가거리 차이(m).
|
||||
@@ -339,17 +340,17 @@ function buildCurveRows(
|
||||
: curveByChainage.get(key);
|
||||
// 직선화된 자리는 반경이 없다 — 빈 칸으로 둔다.
|
||||
const curve = found && !isStraightCurve(found) ? found : undefined;
|
||||
return { center: centers[index], curve, rotated: false };
|
||||
return { center: centers[index], curve };
|
||||
});
|
||||
const withCurve = curveEntries.filter((entry) => entry.curve);
|
||||
for (let pair = 1; pair < withCurve.length; pair += 1) {
|
||||
if (withCurve[pair].center - withCurve[pair - 1].center < options.cellWidth) {
|
||||
withCurve[pair - 1].rotated = true;
|
||||
withCurve[pair].rotated = true;
|
||||
}
|
||||
}
|
||||
// 세운 칸 두께 = 이웃 사이 틈에 맞춤(407-5 — 1.6 m 이웃도 겹치지 않게 · 측점 값 행과 같은 규칙)
|
||||
const thickness = crowdedThickness(
|
||||
curveEntries.map((entry) => (entry.curve ? entry.center : null)),
|
||||
options.cellWidth,
|
||||
CURVE_ROTATED_WIDTH_PX,
|
||||
);
|
||||
|
||||
curveEntries.forEach(({ center, curve, rotated }) => {
|
||||
curveEntries.forEach(({ center, curve }, entryIndex) => {
|
||||
const rotated = thickness[entryIndex];
|
||||
const lengthCell = element("span", "b05-profile-table__curve");
|
||||
const radiusCell = element("span", "b05-profile-table__curve");
|
||||
if (curve) {
|
||||
@@ -370,16 +371,16 @@ function buildCurveRows(
|
||||
);
|
||||
radiusCell.append(radiusInput);
|
||||
lengthCell.append(lengthInput);
|
||||
if (rotated) {
|
||||
if (rotated !== null) {
|
||||
[lengthCell, radiusCell].forEach((cell) => {
|
||||
cell.classList.add("is-rotated");
|
||||
cell.style.width = `${CURVE_ROTATED_WIDTH_PX}px`;
|
||||
cell.style.width = `${rotated}px`;
|
||||
});
|
||||
[lengthInput, radiusInput].forEach((input) => {
|
||||
const byLength = input.value.length
|
||||
? (rowHeightPx - 6) / (input.value.length * CHAR_WIDTH_EM)
|
||||
: fontPx;
|
||||
input.style.fontSize = `${Math.max(7, Math.min(fontPx, CURVE_ROTATED_WIDTH_PX - 4, byLength))}px`;
|
||||
input.style.fontSize = `${Math.max(6, Math.min(fontPx, rotated - 2, byLength))}px`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -585,21 +586,51 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement {
|
||||
table.append(
|
||||
...buildSegmentRows(alignment, x, fontSize, rowHeight, structureChainages, selectedChainageM),
|
||||
);
|
||||
buildStationRows(alignment, stationInterval, display).forEach((spec, index) => {
|
||||
// 이웃 칸과 글이 겹치는 값 칸(1.6 m 이웃 등)만 그 자리에서 세움 — 판정 = 세 행 가운데 가장 긴 글 폭 ·
|
||||
// 두께 = 이웃 사이 틈(407-5 · 곡선 행과 같은 함수)
|
||||
const stationSpecs = buildStationRows(alignment, stationInterval, display);
|
||||
const isStructural = alignment.stations.map((station) =>
|
||||
isNearChainage(station.chainage_m, structureChainageList),
|
||||
);
|
||||
const longest = Math.max(
|
||||
0,
|
||||
...stationSpecs.flatMap((spec) =>
|
||||
alignment.stations.map((_, stationIndex) =>
|
||||
isStructural[stationIndex] ? 0 : spec.cell(stationIndex).length,
|
||||
),
|
||||
),
|
||||
);
|
||||
const stationThickness = crowdedThickness(
|
||||
alignment.stations.map((_, stationIndex) =>
|
||||
isStructural[stationIndex] ? null : centers[stationIndex],
|
||||
),
|
||||
longest * fontSize * CHAR_WIDTH_EM + CELL_PADDING_PX * 2,
|
||||
CURVE_ROTATED_WIDTH_PX,
|
||||
);
|
||||
stationSpecs.forEach((spec, index) => {
|
||||
const row = createRow(
|
||||
`${spec.modifier ? `is-${spec.modifier}` : ""}${index === 0 ? " is-group-start" : ""}`,
|
||||
spec.label,
|
||||
spec.unit,
|
||||
);
|
||||
// 값이 없는 측점(절토고/성토고 중 한쪽)도 빈 칸을 만들어야 세로 구분선이 끊기지 않는다.
|
||||
alignment.stations.forEach((station, stationIndex) => {
|
||||
alignment.stations.forEach((_, stationIndex) => {
|
||||
// 구조물 측점 열은 기본값을 비운다 — 격자(빈 셀)만 남기고 값은 선택 오버레이가 맡는다.
|
||||
const structural = isNearChainage(station.chainage_m, structureChainageList);
|
||||
const structural = isStructural[stationIndex];
|
||||
const cell = element(
|
||||
"span",
|
||||
`b05-profile-table__cell${structural ? " is-structure" : ""}`,
|
||||
structural ? "" : spec.cell(stationIndex),
|
||||
);
|
||||
const rotated = stationThickness[stationIndex];
|
||||
if (rotated !== null) {
|
||||
const text = cell.textContent ?? "";
|
||||
const byLength = text.length ? (rowHeight - 2) / (text.length * CHAR_WIDTH_EM) : fontSize;
|
||||
cell.classList.add("is-rotated");
|
||||
cell.style.width = `${rotated}px`;
|
||||
cell.style.fontSize = `${Math.max(6, Math.min(fontSize, rotated - 2, byLength))}px`;
|
||||
cell.title = text;
|
||||
}
|
||||
placeCell(row, centers[stationIndex], cell);
|
||||
});
|
||||
table.append(row);
|
||||
|
||||
@@ -84,6 +84,12 @@
|
||||
|
||||
/* 이웃 곡선 셀과 겹치는 좁은 자리(배관 측점 등) — 셀을 얇게 줄이고 입력을 세로쓰기로
|
||||
세운다. 자리는 측점 수직선 그대로다(옆으로 비키면 다른 열 값으로 오해, 2026-08-04). */
|
||||
/* 측점 값 칸도 같은 규칙(407-5) — 이웃과 겹치면 칸을 얇게 · 글을 세움 · 지우지 않음. */
|
||||
.b05-profile-table__cell.is-rotated {
|
||||
writing-mode: vertical-rl;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.b05-profile-table__curve.is-rotated input {
|
||||
writing-mode: vertical-rl;
|
||||
width: 100%;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* B06_Section/logic/B06_Section_Label_Tiers.ts
|
||||
* 종단 측점 · 구조물 이름표 비켜 그리기(PLAN 407-4) — 가까운 측점(1.6 m 등)끼리 이름이 겹치면 위 단으로
|
||||
* 올림 · 이름은 지우지 않음. 그리는 곳 = `B06_Section_UI_Longitudinal`(B05 종단도 같은 함수).
|
||||
* B05 종단 아래 표 겹친 칸 세우기(407-5 · `crowdedThickness` · `B05_Profile_UI_Profile_Table`).
|
||||
* 시험 `resources/tester/test_407_4_label_tiers.py`. 화면 · 세션 없음(순수).
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -29,3 +30,26 @@ export function labelTiers(
|
||||
}
|
||||
return tiers;
|
||||
}
|
||||
|
||||
/** 겹친 칸 세우기(407-5 · B05 종단 아래 표) — 같은 폭(`width`) 칸이 이웃과 가로로 겹치면 세운 칸 두께
|
||||
* (px · 가까운 이웃까지 거리 − 1 · `minPx` ~ `maxPx`) · 안 겹치면 null(그대로) · x 가 null = 빈 칸(판정에서 뺌). */
|
||||
export function crowdedThickness(
|
||||
xs: ReadonlyArray<number | null>,
|
||||
width: number,
|
||||
maxPx: number,
|
||||
minPx = 6,
|
||||
): Array<number | null> {
|
||||
const order = xs
|
||||
.map((x, index) => ({ x, index }))
|
||||
.filter((item): item is { x: number; index: number } => item.x !== null)
|
||||
.sort((a, b) => a.x - b.x);
|
||||
const out: Array<number | null> = xs.map(() => null);
|
||||
order.forEach(({ x, index }, k) => {
|
||||
const nearest = Math.min(
|
||||
k > 0 ? x - order[k - 1].x : Infinity,
|
||||
k < order.length - 1 ? order[k + 1].x - x : Infinity,
|
||||
);
|
||||
if (nearest < width) out[index] = Math.max(minPx, Math.min(maxPx, Math.floor(nearest - 1)));
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""PLAN 407-4 — 종단 측점 · 구조물 이름표 비켜 그리기(`logic/B06_Section_Label_Tiers.ts`).
|
||||
"""PLAN 407-4 — 종단 측점 · 구조물 이름표 비켜 그리기(`logic/B06_Section_Label_Tiers.ts`) ·
|
||||
407-5 — B05 종단 아래 표 겹친 칸 세우기(`crowdedThickness` · 두께 = 이웃 사이 틈 − 1).
|
||||
|
||||
가까운 측점(1.6 m 등)끼리 이름이 겹치면 위 단 · 안 겹치면 제자리(0) · 이름은 지우지 않음 ·
|
||||
단 수 위 끝(`MAX_LABEL_TIER`) 넘으면 마지막 단 · 글 폭 짐작(한글 = 글자 크기).
|
||||
@@ -30,6 +31,13 @@ process.stdout.write(JSON.stringify({
|
||||
max: m.MAX_LABEL_TIER,
|
||||
tiers: cases.map((items) => m.labelTiers(items)),
|
||||
widths: ["19+18 (MC)", "20+0", "배수관"].map((t) => m.labelWidthPx(t, 10)),
|
||||
thick: [
|
||||
m.crowdedThickness([0, 50, 100], 40, 16),
|
||||
m.crowdedThickness([100, 108, 200], 40, 16),
|
||||
m.crowdedThickness([100, 130, 200], 40, 16),
|
||||
m.crowdedThickness([100, 104, null, 106], 40, 16),
|
||||
m.crowdedThickness([100, null, 103], 40, 16),
|
||||
],
|
||||
}));
|
||||
"""
|
||||
|
||||
@@ -77,3 +85,12 @@ def test_글_폭(r) -> None:
|
||||
mc, plain, korean = r["widths"]
|
||||
assert mc == pytest.approx(60.0) and plain == pytest.approx(24.0)
|
||||
assert korean == pytest.approx(30.0)
|
||||
|
||||
|
||||
def test_표_칸_세우기(r) -> None:
|
||||
no, near, wide, empty, floor = r["thick"]
|
||||
assert no == [None, None, None] # 안 겹침 = 그대로
|
||||
assert near == [7, 7, None] # 1.6 m 이웃(8 px) = 둘 다 세움 · 두께 7
|
||||
assert wide == [16, 16, None] # 틈 넓으면 위 끝 16
|
||||
assert empty == [6, 6, None, 6] # 빈 칸(null) 건너 이웃 · 아래 끝 6
|
||||
assert floor == [6, None, 6]
|
||||
|
||||
Reference in New Issue
Block a user