// 줌·팬 시 축 눈금이 도형과 맞는지 — Cross_Axes의 역산·배치 로직 복제 검증. // 실행: node tmp/tests/test_cross_axes_zoom.mjs import assert from "node:assert/strict"; const CROSS_PAD = { left: 58, right: 20, top: 10, bottom: 52 }; function niceTickStep(span, targetCount, maxCount) { 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; } const widthPx = 630; const heightPx = 250; const ppm = (widthPx - CROSS_PAD.left - CROSS_PAD.right) / 24; // 반폭 12m const maxOffset = 12; const plotLeft = CROSS_PAD.left; const plotRight = widthPx - CROSS_PAD.right; const x = (offset) => plotLeft + (maxOffset - offset) * ppm; function xTicks({ scale, tx }) { const offsetAt = (px) => maxOffset - ((px - tx) / scale - plotLeft) / ppm; const high = offsetAt(plotLeft); const low = offsetAt(plotRight); const step = niceTickStep(high - low, 7, Math.floor((plotRight - plotLeft) / 48)); const out = []; for (let o = Math.ceil(low / step) * step; o <= high + 1e-9; o += step) { out.push({ offset: o, screenX: tx + scale * x(o) }); } return { out, step, low, high }; } // 원배율: 눈금이 플롯 안, 라벨 자리 = 도형 자리. const base = xTicks({ scale: 1, tx: 0 }); assert.ok(base.out.length >= 4, `원배율 눈금 부족 ${base.out.length}`); for (const t of base.out) { assert.ok(t.screenX >= plotLeft - 1e-6 && t.screenX <= plotRight + 1e-6, "원배율 눈금 이탈"); assert.ok(Math.abs(t.screenX - x(t.offset)) < 1e-9, "원배율 자리 어긋남"); } // 4배 확대(중앙 고정): 보이는 범위가 1/4로 좁아지고, 눈금 자리가 변환된 도형 자리와 같아야 한다. const scale = 4; const cx = plotLeft + (plotRight - plotLeft) / 2; const tx = cx - cx * scale; const zoomed = xTicks({ scale, tx }); assert.ok( Math.abs(zoomed.high - zoomed.low - 24 / scale) < 1e-6, `확대 범위 오류 ${zoomed.high - zoomed.low}`, ); assert.ok(zoomed.step < base.step, `확대인데 눈금 간격이 안 촘촘해짐 ${zoomed.step}`); assert.ok(zoomed.out.length >= 4, `확대 눈금 부족 ${zoomed.out.length}`); for (const t of zoomed.out) { assert.ok(t.screenX >= plotLeft - 1e-6 && t.screenX <= plotRight + 1e-6, "확대 눈금 이탈"); // 도형은 transform으로 옮겨 그려진다 — 같은 offset의 도형 자리와 눈금 자리가 일치해야 한다. assert.ok(Math.abs(t.screenX - (tx + scale * x(t.offset))) < 1e-9, "확대 자리 어긋남"); } console.log("ok");