feat(b06): [상세] 모달 첫 보기 = 중심점이 칸 가운데 · 그림 전체가 들어가게 축마다 큰 쪽 반폭 · [⤢] · 더블클릭 원복 같은 보기 (295-2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
This commit is contained in:
2026-10-12 22:01:18 +09:00
co-authored by Claude Opus 5.5
parent 1ca7e61b08
commit 4eccca40d0
6 changed files with 75 additions and 8 deletions
+13 -1
View File
@@ -37,6 +37,7 @@ import {
type ZoomPanState,
} from "../A00_Common/b_svg_zoom_pan";
import { buildZoomControls } from "./B06_Section_UI_Cross_View_Zoom";
import { detailHomeView, layerPoints } from "./B06_Section_UI_Cross_Detail_Home";
import { attachDetailTable, designForTable, groupSpan } from "./B06_Section_UI_Cross_Detail_Table";
import { createCrossAxes, IDENTITY_VIEW, type CrossAxesOptions } from "./B06_Section_UI_Cross_Axes";
import {
@@ -194,7 +195,7 @@ export function buildQuantityTable(sheet: CrossQuantitySheet, title: string): HT
/** 도형 층에만 공용 줌 · 팬(179-3 · 축 · 눈금 · 축선 · clip 제자리) · 휠 = 커서 고정 줌 ·
* 바뀔 때마다 축을 보이는 범위로 다시 그림 + 십자 크기 되돌림(카드 `_Cross_View_Frame` 꼴) ·
* 처음 · 원복 = `homeScale`(266-1 · 가로 = 카드 반폭 · 줄이면 칸 맞춤 전체). */
* 처음 · 원복 = 중심점이 칸 가운데 · 그림 전체(가로 = 카드 반폭 266-1 · 세로 = 그 안 그림)(295-2). */
export function attachDetailZoom(
clone: SVGSVGElement,
axes: CrossAxesOptions | null,
@@ -220,6 +221,16 @@ export function attachDetailZoom(
);
};
follow(initial ?? IDENTITY_VIEW);
const plot = {
x: CROSS_PAD.left,
y: CROSS_PAD.top,
width: width - CROSS_PAD.left - CROSS_PAD.right,
height: height - CROSS_PAD.top - CROSS_PAD.bottom,
};
const home =
markerLine && Number.isFinite(cx) && Number.isFinite(cy)
? detailHomeView(layerPoints(layer), [cx, cy], plot, homeScale, 16)
: undefined;
return attachSvgZoomPan({
svg: clone,
layer,
@@ -232,6 +243,7 @@ export function attachDetailZoom(
wheelZoom: true,
initial,
homeScale,
home,
onChange: (state) => {
follow(state);
onChange(state);
@@ -0,0 +1,49 @@
/* =============================================================================
* B06_Section_UI_Cross_Detail_Home.ts
* [상세] 모달 첫 보기(295-2) — 중심점(중심 십자)이 칸 가운데 · 그림 전체가 들어가게
* 축마다 중심에서 큰 쪽 반폭으로. [⤢] · 더블클릭 원복도 같은 보기. 카드 보기는 그대로.
*
* 가로 반폭 = 카드 반폭(266-1 `homeScale` 이 되좁히던 폭)의 큰 쪽 · 세로 반폭 = 그 가로 안에
* 그려진 꼭짓점이 중심에서 위 · 아래로 먼 쪽.
* ========================================================================== */
import type { ZoomPanState } from "../A00_Common/b_svg_zoom_pan";
type Pt = [number, number];
interface Rect {
x: number;
y: number;
width: number;
height: number;
}
/** 첫 보기 배율 · 이동 — 중심 `center` 를 `plot` 가운데에. `homeScale` = 1배 대비 카드 반폭 배율. */
export function detailHomeView(
points: Pt[],
center: Pt,
plot: Rect,
homeScale: number,
maxScale: number,
): ZoomPanState {
const [cx, cy] = center;
const halfX = Math.max(cx - plot.x, plot.x + plot.width - cx) / Math.max(homeScale, 1e-6);
const ys = points.filter(([x]) => Math.abs(x - cx) <= halfX).map(([, y]) => y);
const halfY = Math.max(1, ...ys.map((y) => Math.abs(y - cy)));
const scale = Math.min(plot.width / 2 / halfX, plot.height / 2 / halfY, maxScale);
return {
scale,
tx: plot.x + plot.width / 2 - scale * cx,
ty: plot.y + plot.height / 2 - scale * cy,
};
}
/** 그림 층의 꺾은선 · 다각형 꼭짓점(그림 px). */
export function layerPoints(layer: Element): Pt[] {
return [...layer.querySelectorAll("polyline, polygon")].flatMap((el) =>
(el.getAttribute("points") ?? "")
.trim()
.split(/\s+/)
.map((pair) => pair.split(",").map(Number))
.filter((p): p is Pt => p.length === 2 && p.every(Number.isFinite)),
);
}