From fc1068a9cd274beab7fe3c40b215d674978fc8e2 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 7 Oct 2026 23:50:13 +0900 Subject: [PATCH] =?UTF-8?q?fix(B05=C2=B7B06):=20=EC=9C=A0=ED=86=A0?= =?UTF-8?q?=EA=B3=A1=EC=84=A0=20=ED=86=A0=EB=9F=89=20=EB=B6=84=EB=B0=B0=20?= =?UTF-8?q?=EB=95=8C=20=EC=A2=85=EB=8B=A8=20=C2=B7=20=ED=91=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EC=9C=A0=EC=A7=80=20=C2=B7=20=ED=9A=A1?= =?UTF-8?q?=EB=8B=A8=20=EB=B3=B4=EA=B8=B0=20=3D=20=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=B0=98=ED=8F=AD=20=EA=B7=B8=EB=8C=80=EB=A1=9C=20=C2=B7=20?= =?UTF-8?q?=EC=8B=AD=EC=9E=90=EC=84=A0=20=ED=81=AC=EA=B8=B0=20=EA=B3=A0?= =?UTF-8?q?=EC=A0=95=20=C2=B7=20=EB=B3=B4=EA=B8=B0=20=EB=B0=98=ED=8F=AD=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=2010=20m(79-1~4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 토량 분배(전체 맞춤) 중에는 유토곡선 ↔ 종단 스크롤 동기화를 끊음 — 0 으로 되돌던 것 없앰 - 횡단 카드는 사면 교차점 · 배수관 세트 폭으로 줌아웃하지 않음 · 밖은 가운데 버튼 팬 · ⤢ 는 보기값으로 - 줌인해도 중심 십자선 길이 그대로(도형 레이어 배율을 십자선 중심에서 되돌림) - 횡단 보기 반폭 기본 12 → 10 m(계산 반폭과 따로) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019EMfGERKUg5nMMNQsyVZ2j --- A00_Common/design/design_view_cross.ts | 6 ++--- .../B05_Profile_UI_Profile_MassHaul.ts | 6 ++++- B06_Section/B06_Section_UI_Cross_View.ts | 16 +++++--------- .../B06_Section_UI_Cross_View_Frame.ts | 15 ++++++++++--- .../B06_Section_UI_Cross_View_Metrics.ts | 22 ++++--------------- B06_Section/B06_Section_UI_Cross_View_Zoom.ts | 7 +++--- 6 files changed, 33 insertions(+), 39 deletions(-) diff --git a/A00_Common/design/design_view_cross.ts b/A00_Common/design/design_view_cross.ts index 753f19d24..da41064f2 100644 --- a/A00_Common/design/design_view_cross.ts +++ b/A00_Common/design/design_view_cross.ts @@ -93,7 +93,7 @@ import "@util/common_util_mass_haul.css"; /** 횡단면도 **표시** 반폭 기본값(m) — 2026-08-19 사용자 지시로 20 → 12. 계산·보유 샘플 폭은 * 백엔드 기본(SECTION_CROSS_HALF_WIDTH_M = 20m) 그대로라 12는 표시 범위만 좁힌다. */ -const DISPLAY_HALF_WIDTH_DEFAULT_M = 12; +const DISPLAY_HALF_WIDTH_DEFAULT_M = 10; export async function mountCrossView( center: HTMLElement, @@ -591,7 +591,7 @@ export async function mountCrossView( return; } - // 표시 기본 12m — 계산 기본(context.defaults.cross_half_width_m = 20m)과 다르다 + // 표시 기본 10m(PLAN 79-4) — 계산 기본(context.defaults.cross_half_width_m = 20m)과 다르다 // (2026-08-19 사용자 지시). 사용자 확정 이력·세션값이 있으면 아래에서 덮는다. crossHalfWidthField.input.value = String(DISPLAY_HALF_WIDTH_DEFAULT_M); stationInterval = context.defaults.station_interval_m; @@ -645,7 +645,7 @@ export async function mountCrossView( // 확정 이력의 표준 횡단면 설정값 복원(진행 중 세션 편집값이 있으면 패널이 무시). if (storedOptions?.standard_cross_section) standardPanel?.applyStored(storedOptions.standard_cross_section); - /* 입력칸의 기준값은 **표시 기본 12m**. ⚠ `data.options.cross_half_width_m`은 종단·횡단을 + /* 입력칸의 기준값은 **표시 기본 10m**. ⚠ `data.options.cross_half_width_m`은 종단·횡단을 * 만들 때 쓴 **샘플링(계산) 반폭**이지 사용자가 고른 표시 폭이 아니다 — 그 값으로 덮으면 * 표시 기본값이 언제나 20m로 되돌아온다(2026-08-19 사용자 보고의 원인). 사용자가 실제로 * 고른 표시 폭만 되살린다: ① 측점 개별 표시 반폭 저장값의 대표값 ② 세션의 마지막 조절값. */ diff --git a/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts b/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts index 935241155..a85a79bf7 100644 --- a/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts +++ b/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts @@ -294,10 +294,13 @@ export function createRouteMassHaulPanel( } // 종단 스크롤러와의 양방향 동기화 — 재진입 루프는 플래그로 끊는다. + // 토량 분배(전체 맞춤) 중에는 끊는다 — 유토곡선이 0 으로 붙으며 낸 scroll 이 종단 그래프 · + // 표까지 0 으로 되돌렸다(PLAN 79-1). 유토곡선만 스크롤이 없는 상태. let syncing = false; + let fitted = false; function mirror(from: HTMLElement, to: HTMLElement): void { from.addEventListener("scroll", () => { - if (syncing) return; + if (syncing || fitted) return; syncing = true; to.scrollLeft = from.scrollLeft; syncing = false; @@ -433,6 +436,7 @@ export function createRouteMassHaulPanel( // 종단과 맞물릴 필요가 없으므로 반 칸 들여쓰기(originOffset)도 쓰지 않는다 — 그만큼 // 좌우가 비어 곡선이 가운데로 쪼그라들었다(2026-09-06 사용자 보고: 「우측 여유 과다」). const fitAll = haulPlan !== null; + fitted = fitAll; const widthPx = fitAll ? scroll.clientWidth || params.widthPx : params.widthPx; const chartAxis = fitAll ? { diff --git a/B06_Section/B06_Section_UI_Cross_View.ts b/B06_Section/B06_Section_UI_Cross_View.ts index e16177754..58f64ced8 100644 --- a/B06_Section/B06_Section_UI_Cross_View.ts +++ b/B06_Section/B06_Section_UI_Cross_View.ts @@ -54,7 +54,6 @@ import { culvertCardState, structurePanelDeps } from "./B06_Section_UI_Cross_Vie import { attachZoomPan, buildZoomControls, cardZoomStates } from "./B06_Section_UI_Cross_View_Zoom"; import type { CrossWidthActions } from "./B06_Section_UI_Cross_View_Zoom"; import type { CutSlopeControl } from "./B06_Section_UI_Cross_CutSlope"; -import { toeFitHalfWidth } from "./B06_Section_UI_Cross_Fit"; import { createCrossAxes, IDENTITY_VIEW } from "./B06_Section_UI_Cross_Axes"; import type { RevetHighlightSetter, RevetKey } from "./B06_Section_UI_Cross_Culvert"; import { REVET_FORM_DEFAULT } from "./B06_Section_UI_Cross_Culvert_Const"; @@ -552,7 +551,7 @@ export function createCrossSectionCard( } // 십자선(계획 노선 자리) · 축선 · 높이 축 이름 — `_Cross_View_Frame`(700줄 제한). - appendCardFrame(svg, plotLayer, { + const scaleCenter = appendCardFrame(svg, plotLayer, { valid, centerX: x(0), designElevation: hasDesign ? (designElevation as number) : null, @@ -587,9 +586,11 @@ export function createCrossSectionCard( (state) => { cardZoomStates.set(section.station_id, state); renderAxes(state); + scaleCenter(state.scale); }, contentBounds, ); + scaleCenter(cardZoomStates.get(section.station_id)?.scale ?? 1); // 절·성토 면적값은 그래프 중상단 오버레이로 표시(E-4). 값 칸은 항상 강조 토글이다. const readout = buildAreaReadout(section.design, toggleArea); setChipActive = readout.setActive; @@ -675,14 +676,9 @@ export function createCrossSectionCard( section.chainage_m, Math.round((effectiveHalfWidth ?? maxOffset) + deltaM), ), - // ⤢ = 초기화. 기본은 **전역 횡단 반폭 보기값**으로 되돌리고, 그 폭 안에서는 - // 절·성토선과 원지반의 교차점이 안 보이는 측점만 교차점이 보이는 폭으로 되돌린다 - // (2026-08-23 사용자 지시 — 그 측점의 기본값이 그 폭이라는 뜻). - fit: () => { - const base = crossHalfWidth ?? maxOffset; - const needed = toeFitHalfWidth(section); - stationWidth.request(section.chainage_m, needed !== null && needed > base ? needed : base); - }, + // ⤢ = 초기화 — **전역 횡단 반폭 보기값**으로 되돌린다. 사면 교차점이 그 밖이면 + // 팬으로 끌어다 본다(PLAN 79-2 · 교차점 폭으로 넓히던 것 폐지). + fit: () => stationWidth.request(section.chainage_m, crossHalfWidth ?? maxOffset), }; chartWrap.append(svg, readout.root, buildZoomControls(zoomPan, widthActions), panel.root); for (const panel of bodies.panels) chartWrap.append(panel.root); diff --git a/B06_Section/B06_Section_UI_Cross_View_Frame.ts b/B06_Section/B06_Section_UI_Cross_View_Frame.ts index 28c385e2d..66ef2bf9c 100644 --- a/B06_Section/B06_Section_UI_Cross_View_Frame.ts +++ b/B06_Section/B06_Section_UI_Cross_View_Frame.ts @@ -22,12 +22,13 @@ export interface CardFrameInput { heightPx: number; } -/** 십자선은 도형 레이어(줌·팬 따라감), 축선 · 축 이름은 틀 레이어(제자리)에 붙인다. */ +/** 십자선은 도형 레이어(줌·팬 따라감), 축선 · 축 이름은 틀 레이어(제자리)에 붙인다. + * 돌려준 함수 = 배율이 바뀔 때 부름 — 십자선은 자리만 따라가고 크기는 그대로(PLAN 79-3). */ export function appendCardFrame( svg: SVGElement, plotLayer: SVGElement, input: CardFrameInput, -): void { +): (scale: number) => void { const { valid, centerX, designElevation, toY, widthPx, heightPx } = input; const hasDesign = designElevation !== null; const centerSample = valid.reduce((nearest, sample) => { @@ -38,7 +39,8 @@ export function appendCardFrame( const centerElevation = hasDesign ? designElevation : (centerSample?.elevation_m ?? null); const centerY = centerElevation !== null ? toY(centerElevation) : heightPx / 2; const centerMarkerClass = `b06-chart__center-marker${hasDesign ? " b06-chart__center-marker--design" : ""}`; - plotLayer.append( + const marker = svgElement("g", { class: "b06-chart__center" }); + marker.append( svgElement("line", { x1: centerX, y1: centerY - 18, @@ -54,6 +56,7 @@ export function appendCardFrame( class: centerMarkerClass, }), ); + plotLayer.append(marker); svg.append( svgElement("line", { x1: CROSS_PAD.left, @@ -77,6 +80,12 @@ export function appendCardFrame( class: "b06-chart__axis-label", }), ); + // 도형 레이어 배율을 십자선 중심에서 되돌림 — 자리는 따라가고 길이 18px 은 그대로. + return (scale) => + marker.setAttribute( + "transform", + `translate(${centerX} ${centerY}) scale(${1 / Math.max(scale, 1e-6)}) translate(${-centerX} ${-centerY})`, + ); } /** diff --git a/B06_Section/B06_Section_UI_Cross_View_Metrics.ts b/B06_Section/B06_Section_UI_Cross_View_Metrics.ts index 03c8a8a58..43d315ecf 100644 --- a/B06_Section/B06_Section_UI_Cross_View_Metrics.ts +++ b/B06_Section/B06_Section_UI_Cross_View_Metrics.ts @@ -1,7 +1,5 @@ import type { CrossSection, SectionSample } from "./B06_Section_Api_Fetch"; import { CROSS_HEIGHT, CROSS_PAD, validElevation } from "./B06_Section_UI_Section_Common"; -import { culvertRequiredHalfWidth } from "./B06_Section_UI_Cross_Culvert_Wire"; -import { toeFitHalfWidth } from "./B06_Section_UI_Cross_Fit"; const AREA_OVERLAY_HEADROOM_PX = 58; @@ -105,24 +103,12 @@ export function crossPlotMetrics( /** * 카드가 실제로 그릴 표시 반폭 — 개별값 > 전역값(2026-08-06). * - * 절·성토선이 원지반과 만나는 지점(교차점)이 반폭 밖이면 **이 카드만** 자동 줌아웃한다 - * (2026-08-22, 판정 기준 개편 2026-08-23: 배수관용 5m 사면 규칙 대신 실제 교차점). - * 보유 샘플 밖까지는 넓히지 않는다 — 지반이 없어 빈 화면이 될 뿐이고, 그 경우 - * [보기 반폭 적용]이 필요한 폭으로 백엔드 재생성을 건다. + * 횡단 보기 설정 값 그대로다 — 사면 교차점 · 배수관 세트가 그 밖이어도 줌아웃하지 않는다 + * (PLAN 79-2 사용자: 너무 작아짐). 계산은 캐시 전 구간으로 해 두었으니 팬으로 끌어다 본다. */ export function effectiveCardHalfWidth( - section: CrossSection, + _section: CrossSection, baseHalfWidth: number | undefined, ): number | undefined { - const sampledExtent = Math.max( - 0, - ...section.samples.map((sample) => Math.abs(sample.offset_m ?? 0)), - ); - const requiredHalfWidth = Math.min( - Math.max(culvertRequiredHalfWidth(section) ?? 0, toeFitHalfWidth(section) ?? 0), - sampledExtent, - ); - return baseHalfWidth !== undefined && requiredHalfWidth > 0 - ? Math.max(baseHalfWidth, requiredHalfWidth) - : baseHalfWidth; + return baseHalfWidth; } diff --git a/B06_Section/B06_Section_UI_Cross_View_Zoom.ts b/B06_Section/B06_Section_UI_Cross_View_Zoom.ts index 928d20714..be95c5b3c 100644 --- a/B06_Section/B06_Section_UI_Cross_View_Zoom.ts +++ b/B06_Section/B06_Section_UI_Cross_View_Zoom.ts @@ -21,14 +21,13 @@ export type { ContentBounds, ZoomPanHandle, ZoomPanState }; /** * 카드 표시 반폭 조작(2026-08-23 사용자 지시). 카드 하단 ◀/▶/↺을 없애고 우측 상단 - * 줌 버튼에 합쳤다 — 원배율에서 −는 폭 넓히기, +는 폭 좁히기, ⤢는 교차점 맞춤이다. + * 줌 버튼에 합쳤다 — 원배율에서 −는 폭 넓히기, ⤢는 보기값 되돌림이다. */ export interface CrossWidthActions { /** 표시 반폭을 deltaM(m)만큼 조절한다. 계산 반폭을 넘으면 페이지가 재생성까지 처리. */ step: (deltaM: number) => void; /** - * 초기화 — 표시 반폭을 전역 보기값으로 되돌린다. 단 그 폭에서 절·성토선과 원지반의 - * 교차점이 안 보이는 측점은 교차점이 보이는 폭이 그 측점의 기본값이다. + * 초기화 — 표시 반폭을 전역 보기값으로 되돌린다(PLAN 79-2 · 사면 교차점은 팬으로 봄). */ fit: () => void; } @@ -69,7 +68,7 @@ export function attachZoomPan( * `width`를 넘기면 **원배율에서만** 버튼이 표시 반폭을 조절한다(2026-08-23 사용자 지시): * + : 배율 확대(항상) * − : 배율>1이면 축소, 원배율이면 표시 반폭 +1m(계산 반폭 20m를 넘으면 백엔드 재계산) - * ⤢ : 배율 원복 + 표시 반폭 초기화(전역 보기값, 교차점이 안 보이면 보이는 폭까지) + * ⤢ : 배율 원복 + 표시 반폭 초기화(전역 보기값) */ export function buildZoomControls(handle: ZoomPanHandle, width?: CrossWidthActions): HTMLElement { const bar = document.createElement("div");