feat(B06): 확대 상태 팬으로 캐시 보유 범위까지 열람
- 도형(지반선·면적 밴드·설계선·암 경계선)을 표시 반폭으로 자르지 않고 **캐시 보유 전 구간**으로 그린다. 표시 반폭 밖은 기존 clip이 가리므로 원배율 화면은 그대로다. - 팬 한계(clampPan)를 그려진 범위까지 확장 — 배율 1에서는 종전대로 이동 0으로 묶고, 확대 상태에서만 캐시 끝까지 가운데 버튼 드래그로 끌어다 볼 수 있다. - 축 눈금은 팬 상태를 역산해 다시 그리므로(_UI_Cross_Axes) X·Y 값이 따라 움직인다. 자체검증: node tmp/tests/test_cross_pan_bounds.mjs (배율 1 고정, 2배에서 좌우 한계가 캐시 끝과 일치, 범위 밖 이탈 없음, content 없을 때 옛 규칙 유지), test_cross_axes_zoom.mjs 재실행, tsc --noEmit 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,18 @@ export interface ZoomPanState {
|
||||
ty: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 그려 둔 도형이 실제로 차지하는 범위(원배율 SVG 좌표). 표시 반폭 밖에 있어 잘려 있는
|
||||
* **캐시 보유분**까지 포함한다 — 확대 상태의 팬은 이 범위까지 갈 수 있다
|
||||
* (2026-08-23 사용자 지시).
|
||||
*/
|
||||
export interface ContentBounds {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export function attachZoomPan(
|
||||
svg: SVGSVGElement,
|
||||
plotLayer: SVGGElement,
|
||||
@@ -47,6 +59,8 @@ export function attachZoomPan(
|
||||
initial?: ZoomPanState,
|
||||
/** 상태가 바뀔 때마다 부른다 — 카드 밖(뷰)이 담아 뒀다가 다음 렌더에 되돌린다. */
|
||||
onChange?: (state: ZoomPanState) => void,
|
||||
/** 도형이 실제로 그려진 범위(캐시 보유 폭). 확대 상태에서 팬 한계가 여기까지 늘어난다. */
|
||||
content?: ContentBounds,
|
||||
): ZoomPanHandle {
|
||||
// 플롯 영역(축 안쪽) — 확대·축소의 중심이자 이동 한계의 기준이다.
|
||||
const plot = {
|
||||
@@ -64,9 +78,32 @@ export function attachZoomPan(
|
||||
onChange?.({ scale, tx, ty });
|
||||
};
|
||||
// 확대한 도형이 플롯 영역을 항상 덮게 이동량을 가둔다 — 원배율에서는 이동량이 0으로 묶인다.
|
||||
// **확대 상태**에서는 한계가 그려진 도형 전체(표시 반폭 밖 캐시 보유분 포함)까지 늘어나,
|
||||
// 가운데 버튼 팬으로 잘려 있던 지반·설계선을 끌어다 볼 수 있다(2026-08-23 사용자 지시).
|
||||
const clampAxis = (
|
||||
value: number,
|
||||
start: number,
|
||||
size: number,
|
||||
from: number,
|
||||
to: number,
|
||||
): number =>
|
||||
Math.min(
|
||||
Math.max(value, Math.min(start + size - scale * to, start - scale * from)),
|
||||
Math.max(start + size - scale * to, start - scale * from),
|
||||
);
|
||||
const clampPan = (): void => {
|
||||
tx = Math.min(Math.max(tx, (plot.x + plot.width) * (1 - scale)), plot.x * (1 - scale));
|
||||
ty = Math.min(Math.max(ty, (plot.y + plot.height) * (1 - scale)), plot.y * (1 - scale));
|
||||
// 원배율이거나 도형 범위를 모르면 플롯 영역 자신이 한계다(기존 규칙).
|
||||
const bounds =
|
||||
content && scale > 1 + 1e-6
|
||||
? {
|
||||
x: Math.min(content.x, plot.x),
|
||||
y: Math.min(content.y, plot.y),
|
||||
right: Math.max(content.x + content.width, plot.x + plot.width),
|
||||
bottom: Math.max(content.y + content.height, plot.y + plot.height),
|
||||
}
|
||||
: { x: plot.x, y: plot.y, right: plot.x + plot.width, bottom: plot.y + plot.height };
|
||||
tx = clampAxis(tx, plot.x, plot.width, bounds.x, bounds.right);
|
||||
ty = clampAxis(ty, plot.y, plot.height, bounds.y, bounds.bottom);
|
||||
};
|
||||
|
||||
// 보이는 **플롯 영역의 중앙**을 붙잡고 확대·축소한다 — 버튼에는 마우스 위치가 없다.
|
||||
|
||||
Reference in New Issue
Block a user