/* ============================================================================= * B05_Profile_UI_Profile_Zoom.ts * 종단면도 줌·Y레인지 조작구 (2026-09-04 사용자 지시). * * 공사 범위가 넓고 고저차가 크면 종단 그래프가 눌려 읽히지 않는다. 조작은 두 갈래다. * * X (가로) — **폭 배수**다. SVG transform 으로 늘리면 그래프만 커지고 측점 테이블· * 계획고 편집 버튼층·구조물 알약 레인이 어긋난다(넷이 같은 `chainageMapper` * 를 쓴다). 캔버스 폭 자체를 키우고 가로 스크롤로 훑는다. * Y (세로) — **표시 표고창**이다(제안 A, 2026-09-04 사용자 확정). 창 높이 = 전범위 ÷ 배율, * 창 중심은 창 높이 대비 비율로 위·아래로 옮긴다. 세로 스크롤이 생기지 않아 * X축·측점 라벨·편집 버튼이 항상 바닥에 남는다. * * 배율은 페이지가 들고 있다 — 편집·재계산으로 다시 그려도 유지된다(B06 `cardZoomStates` 규칙). * 버튼 양식은 횡단도 줌 버튼세트(`b06-cross-card__zoom-btn`)와 같고, 설명은 툴팁이다. * ========================================================================== */ /** 한 번 누를 때 배율 배수. 횡단도 줌(1/0.85)보다 성글게 — 폭 배수라 한 칸이 크게 느껴진다. */ const ZOOM_STEP = 1.25; /** 가로 폭 배수 상한 — 이 이상은 캔버스가 수만 px이 되어 브라우저가 버겁다. */ const MAX_X = 8; /** 세로 배율 상한. 전범위의 1/20 까지 좁혀 본다. */ const MAX_Y = 20; /** 창 중심 이동 한 번의 몫 — 창 높이의 10%. */ const OFFSET_STEP = 0.1; /** 창 중심 이동 한계 — 전범위 밖으로 완전히 벗어나지 않게 창 높이의 ±2배까지. */ const MAX_OFFSET = 2; export interface ProfileZoomState { /** 가로 폭 배수(1 = 현행 폭맞춤). */ x: number; /** 세로 배율(1 = 표고 전범위). */ y: number; /** 창 중심 이동 — 창 높이 대비 비율(+ 가 위쪽). */ offsetRatio: number; } export interface ProfileZoom { /** 절·성토 요약줄 오른쪽 끝에 붙는 버튼 묶음(한 번 만들어 계속 쓴다). */ bar: HTMLElement; state: () => ProfileZoomState; } const clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value)); export function createProfileZoom(onChange: () => void): ProfileZoom { const state: ProfileZoomState = { x: 1, y: 1, offsetRatio: 0 }; const bar = document.createElement("div"); bar.className = "b05-profile__zoom"; function add(label: string, title: string, action: () => void): void { const button = document.createElement("button"); button.type = "button"; button.className = "b05-profile__zoom-btn"; button.textContent = label; button.title = title; button.addEventListener("click", (event) => { // 카드·측점 선택으로 번지면 그래프를 다시 그리며 방금 맞춘 배율이 날아간다. event.stopPropagation(); action(); onChange(); }); bar.append(button); } add("+", "가로 확대 — 측점 간격을 넓혀 폅니다 (가로 스크롤로 훑음)", () => { state.x = clamp(state.x * ZOOM_STEP, 1, MAX_X); }); add("−", "가로 축소", () => { state.x = clamp(state.x / ZOOM_STEP, 1, MAX_X); }); add("⇕+", "세로 확대 — 표시 표고 폭을 좁혀 고저차를 크게 봅니다", () => { state.y = clamp(state.y * ZOOM_STEP, 1, MAX_Y); }); add("⇕−", "세로 축소", () => { state.y = clamp(state.y / ZOOM_STEP, 1, MAX_Y); }); add("▲", "표시 표고창을 위로 (창 높이의 10%)", () => { state.offsetRatio = clamp(state.offsetRatio + OFFSET_STEP, -MAX_OFFSET, MAX_OFFSET); }); add("▼", "표시 표고창을 아래로 (창 높이의 10%)", () => { state.offsetRatio = clamp(state.offsetRatio - OFFSET_STEP, -MAX_OFFSET, MAX_OFFSET); }); add("⤢", "가로·세로 배율과 표시 표고창을 처음 상태로", () => { state.x = 1; state.y = 1; state.offsetRatio = 0; }); return { bar, state: () => ({ ...state }) }; }