fix(B06): 종단 Y축 눈금 정수화 + 최대·최소 표고 여유
- 0.25 비율 고정 5눈금(소수 표기) → 1·2·5×10^n 중 10칸에 가장 가까운 간격으로 자동 선택. 눈금값이 딱 떨어져 표고를 읽을 수 있다. - 라벨 겹침 방지로 눈금 간격 16px 하한(plotHeight/16 개수 상한). - 자동 스케일(B05)은 표고 범위에 위아래 10%씩 여유 — 최고·최저점이 축선에 붙지 않는다. 공통 Y스케일(B06 yScaleOptions)은 불변. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,30 @@ 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,
|
||||
@@ -202,7 +226,9 @@ export function createLongitudinalProfile(
|
||||
const plotHeight = heightPx - LONG_PAD.top - padBottom;
|
||||
const elevationSpan = yScaleOptions
|
||||
? plotHeight / yScaleOptions.pixelsPerMeter
|
||||
: Math.max(rawMax - rawMin, 1);
|
||||
: // 자동 스케일(B05)은 최고·최저 표고가 위아래 축선에 딱 붙지 않게 10%씩 여유를 둔다
|
||||
// (2026-08-23 사용자 지시). 공통 Y스케일(B06 yScaleOptions)은 그대로 둔다.
|
||||
Math.max(rawMax - rawMin, 1) * 1.2;
|
||||
const x = (chainage: number) =>
|
||||
LONG_PAD.left + originOffsetPx + (chainage / maxChainage) * plotWidth;
|
||||
const xInverse = inverseOf(x, maxChainage);
|
||||
@@ -210,12 +236,23 @@ export function createLongitudinalProfile(
|
||||
LONG_PAD.top + ((elevationMid + elevationSpan / 2 - elevation) / elevationSpan) * plotHeight;
|
||||
const stationInterval = configuredStationInterval ?? inferStationInterval(data.stations);
|
||||
|
||||
// Y축 눈금: 화면에 담긴 표고 범위를 10칸 안팎으로 나누되, 눈금값이 1·2·5 계열의
|
||||
// 딱 떨어지는 수(범위가 아주 좁을 때만 소수)로 오게 한다 — 0.25 비율 고정 눈금은
|
||||
// 표고가 소수로 나와 어느 지점인지 못 읽었다(2026-08-23 사용자 보고).
|
||||
const yAxisTicks: Array<{ y: number; label: string }> = [];
|
||||
for (const ratio of [0, 0.25, 0.5, 0.75, 1]) {
|
||||
const gridY = LONG_PAD.top + ratio * plotHeight;
|
||||
const displayed = elevationMid + elevationSpan / 2 - ratio * elevationSpan;
|
||||
const rawValue = elevationMid + (displayed - elevationMid) / exaggeration;
|
||||
yAxisTicks.push({ y: gridY, label: `${rawValue.toFixed(1)}m` });
|
||||
const rawSpan = elevationSpan / exaggeration;
|
||||
const rawTop = elevationMid + rawSpan / 2;
|
||||
// 라벨 글자(최대 13px, sticky 축)가 겹치지 않게 눈금 간격은 16px 이상 띄운다.
|
||||
const tickStep = niceTickStep(rawSpan, 10, Math.floor(plotHeight / 16));
|
||||
const tickDecimals = Math.max(0, Math.ceil(-Math.log10(tickStep) - 1e-9));
|
||||
for (
|
||||
let value = Math.ceil((elevationMid - rawSpan / 2) / tickStep) * tickStep;
|
||||
value <= rawTop + 1e-9;
|
||||
value += tickStep
|
||||
) {
|
||||
const gridY = LONG_PAD.top + ((rawTop - value) / rawSpan) * plotHeight;
|
||||
const label = `${value.toFixed(tickDecimals)}m`;
|
||||
yAxisTicks.push({ y: gridY, label });
|
||||
svg.append(
|
||||
svgElement("line", {
|
||||
x1: LONG_PAD.left,
|
||||
@@ -224,7 +261,7 @@ export function createLongitudinalProfile(
|
||||
y2: gridY,
|
||||
class: "b06-chart__grid",
|
||||
}),
|
||||
svgText(`${rawValue.toFixed(1)}m`, {
|
||||
svgText(label, {
|
||||
x: LONG_PAD.left - 9,
|
||||
y: gridY + 4,
|
||||
"text-anchor": "end",
|
||||
|
||||
Reference in New Issue
Block a user