From c10fb3400e7ef8d2303a18a49df028cfdbad45e9 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 23 Aug 2026 17:51:38 +0900 Subject: [PATCH] =?UTF-8?q?fix(B06):=20=ED=9A=A1=EB=8B=A8=EB=A9=B4?= =?UTF-8?q?=EB=8F=84=20Y=EC=B6=95=20=EB=88=88=EA=B8=88=EB=8F=84=20?= =?UTF-8?q?=EC=A0=95=EC=88=98=C2=B71=C2=B72=C2=B75=20=EA=B3=84=EC=97=B4?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 5등분 고정(소수 표기) → 종단면도와 같은 niceTickStep 규칙 적용. - niceTickStep을 _UI_Section_Common으로 옮겨 종·횡단이 공유(정의 한 곳). - 횡단은 카드 높이가 낮아 라벨 간격 하한 14px(종단 16px)로 개수 상한 산정. - 횡단 표고 여유는 기존 crossPlotMetrics의 8% 패딩 그대로. Co-Authored-By: Claude Opus 5 (1M context) --- B06_Section/B06_Section_UI_Cross_View.ts | 18 +++++++++++--- B06_Section/B06_Section_UI_Longitudinal.ts | 25 +------------------- B06_Section/B06_Section_UI_Section_Common.ts | 24 +++++++++++++++++++ 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/B06_Section/B06_Section_UI_Cross_View.ts b/B06_Section/B06_Section_UI_Cross_View.ts index edb31e9b..a3a3ec08 100644 --- a/B06_Section/B06_Section_UI_Cross_View.ts +++ b/B06_Section/B06_Section_UI_Cross_View.ts @@ -43,6 +43,7 @@ import { type DesignChangeHandler, emptyView, L, + niceTickStep, stationLabel, svgElement, svgText, @@ -317,9 +318,20 @@ export function createCrossSectionCard( }), ); } + // Y축 눈금: 표고 범위를 10칸 안팎으로 나누되 눈금값이 1·2·5 계열로 딱 떨어지게 한다 + // (종단면도와 같은 규칙). 5등분 고정은 눈금값이 소수로 흘러 표고를 못 읽었다 + // (2026-08-23 사용자 지시). 라벨 겹침 방지로 눈금 간격은 14px 이상 띄운다. const rawDisplaySpan = displaySpan / exaggeration; - for (let index = 0; index < 5; index += 1) { - const tick = elevationMid - rawDisplaySpan / 2 + (rawDisplaySpan * index) / 4; + const rawBottom = elevationMid - rawDisplaySpan / 2; + const rawTop = elevationMid + rawDisplaySpan / 2; + const plotHeightPx = heightPx - CROSS_PAD.top - CROSS_PAD.bottom; + const tickStep = niceTickStep(rawDisplaySpan, 10, Math.floor(plotHeightPx / 14)); + const tickDecimals = Math.max(0, Math.ceil(-Math.log10(tickStep) - 1e-9)); + for ( + let tick = Math.ceil(rawBottom / tickStep) * tickStep; + tick <= rawTop + 1e-9; + tick += tickStep + ) { const displayTick = elevationMid + (tick - elevationMid) * exaggeration; svg.append( svgElement("line", { @@ -329,7 +341,7 @@ export function createCrossSectionCard( y2: y(displayTick), class: "b06-chart__grid", }), - svgText(tick.toFixed(1), { + svgText(tick.toFixed(tickDecimals), { x: CROSS_PAD.left - 7, y: y(displayTick) + 3, "text-anchor": "end", diff --git a/B06_Section/B06_Section_UI_Longitudinal.ts b/B06_Section/B06_Section_UI_Longitudinal.ts index 33932b90..dd8208c1 100644 --- a/B06_Section/B06_Section_UI_Longitudinal.ts +++ b/B06_Section/B06_Section_UI_Longitudinal.ts @@ -15,6 +15,7 @@ import { LONG_PAD, LONG_WIDTH, longitudinalMaxChainage, + niceTickStep, stationLabel, svgElement, svgText, @@ -30,30 +31,6 @@ function offsetStationLabel(chainageM: number, interval: number, stationOffset: return `${Number(number) + stationOffset}+${remainder}`; } -/** - * 눈금 간격을 1·2·5 ×10^n 중에서 고른다 — 눈금값이 딱 떨어지면서 눈금 개수가 - * 목표(10칸)에 가장 가까운 것. 다만 라벨이 겹치지 않게 최대 개수(maxCount)를 넘지 않는다. - */ -function niceTickStep(span: number, targetCount: number, maxCount: number): number { - if (!(span > 0)) return 1; - const exponent = Math.floor(Math.log10(span / targetCount)); - let best = Math.pow(10, exponent + 2); - let bestError = Infinity; - for (const power of [exponent - 1, exponent, exponent + 1, exponent + 2]) { - for (const mantissa of [1, 2, 5]) { - const step = mantissa * Math.pow(10, power); - const count = Math.floor(span / step) + 1; - if (count > Math.max(2, maxCount)) continue; - const error = Math.abs(count - targetCount); - if (error < bestError) { - bestError = error; - best = step; - } - } - } - return best; -} - export function longitudinalMinimumWidth( data: LongitudinalSection, configuredStationInterval?: number, diff --git a/B06_Section/B06_Section_UI_Section_Common.ts b/B06_Section/B06_Section_UI_Section_Common.ts index 9cdca760..90697d4c 100644 --- a/B06_Section/B06_Section_UI_Section_Common.ts +++ b/B06_Section/B06_Section_UI_Section_Common.ts @@ -165,3 +165,27 @@ export function emptyView(message: string): HTMLElement { empty.textContent = message; return empty; } + +/** + * 눈금 간격을 1·2·5 ×10^n 중에서 고른다 — 눈금값이 딱 떨어지면서 눈금 개수가 + * 목표(10칸)에 가장 가까운 것. 다만 라벨이 겹치지 않게 최대 개수(maxCount)를 넘지 않는다. + */ +export function niceTickStep(span: number, targetCount: number, maxCount: number): number { + if (!(span > 0)) return 1; + const exponent = Math.floor(Math.log10(span / targetCount)); + let best = Math.pow(10, exponent + 2); + let bestError = Infinity; + for (const power of [exponent - 1, exponent, exponent + 1, exponent + 2]) { + for (const mantissa of [1, 2, 5]) { + const step = mantissa * Math.pow(10, power); + const count = Math.floor(span / step) + 1; + if (count > Math.max(2, maxCount)) continue; + const error = Math.abs(count - targetCount); + if (error < bestError) { + bestError = error; + best = step; + } + } + } + return best; +}