- B05_Profile_UI_Profile_Zoom.ts 신규 — 배율 상태(가로 폭 배수·세로 배율·창 중심)와 버튼 7개(+ − ⇕+ ⇕− ▲ ▼ ⤢). 자리는 요약줄 오른쪽 끝(그래프 우측 상단), 설명은 툴팁, 클릭은 stopPropagation 으로 측점 선택에 번지지 않게 함 - X 줌은 폭 배수 — computeProfileLayout 에 zoomX 를 넘겨 측점 간격·폭맞춤 폭에 함께 곱함. 그래프·테이블·편집 버튼층·구조물 레인이 같은 매핑을 쓰므로 넷의 x 정렬이 유지됨 - Y 는 표시 표고창(제안 A) — createLongitudinalProfile 에 elevationOffsetRatio 인자 추가, 창 높이 = 전범위 ÷ 배율, ▲▼ 가 창 높이의 10%씩 중심 이동. 세로 스크롤 없음 - 지반선·계획선·절성토 음영을 플롯 사각형 clipPath 안으로 옮김 — 배율을 올려도 선이 축·측점 라벨 위로 흘러나오지 않음 (B06 은 배율 1·오프셋 0 이라 표시 불변) - 요약줄 렌더러에 trailing 자리 추가, .b05-profile__zoom* CSS 추가 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
94 lines
4.1 KiB
TypeScript
94 lines
4.1 KiB
TypeScript
/* =============================================================================
|
||
* 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 }) };
|
||
}
|