From 94266757cdf57badd2d22e7abb2210f17e39f66f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 6 Sep 2026 17:07:01 +0900 Subject: [PATCH] =?UTF-8?q?feat(B05):=20=ED=86=A0=EB=9F=89=EB=B6=84?= =?UTF-8?q?=EB=B0=B0=20=EC=8B=9C=20=EC=9C=A0=ED=86=A0=EA=B3=A1=EC=84=A0=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EB=A7=9E=EC=B6=A4=20+=20=EC=83=81?= =?UTF-8?q?=EB=8B=A8=20=EC=97=AC=EB=B0=B1=C2=B7=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=20=EB=86=92=EC=9D=B4=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 토량분배를 켜면 유토곡선이 종단과의 X 맞춤·세로 창을 버리고 스크롤러 안쪽 폭·전 구간 Y 로 그려 곡선 전체가 한 화면에 들어옴(평형선·운반 블록을 노선 전체로 읽는 그림이라). 끄면 예전대로 종단과 맞물린 가로 스크롤로 복귀. - 요약 막대가 상단줄로 올라간 뒤 남던 상단 여백 축소 — 범례 top 34px → 4px, 차트 padTop 40 → 30(범례 높이 24 + 여유). - 종단 테이블 오버레이 높이 상한 신설 — 6행 × 행 30px + 24px(=204px). 12행에서 6행으로 줄인 뒤 같은 높이를 나눠 가지며 행이 부풀던 것을 막음. 레이아웃 제어(리사이저·패널 비례 연동)는 그대로 두고 상한만 걸음. Co-Authored-By: Claude Opus 5 (1M context) --- .../B05_Profile_UI_Profile_MassHaul.ts | 21 ++++++++++++++----- B05_Profile/B05_Profile_UI_Profile_Render.ts | 6 +++--- .../B05_Profile_UI_Profile_TableOverlay.ts | 20 +++++++++++++----- B05_Profile/B05_Profile_UI_Style_MassHaul.css | 8 +++---- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts b/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts index a3eec8c1..8eba82c8 100644 --- a/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts +++ b/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts @@ -422,17 +422,26 @@ export function createRouteMassHaulPanel( (overlay.clientHeight || OVERLAY_DEFAULT_HEIGHT) - bar.offsetHeight - 12; const chartHeight = Math.max(scrollHeight, MASS_HAUL_MIN_HEIGHT); + // 토량 분배를 켜면 **곡선 전체가 한 화면에** 들어와야 한다(2026-09-06 사용자 지시). + // 평형선·운반 블록은 노선 전체를 놓고 읽는 그림이라, 종단 그래프와의 X 맞춤(가로 + // 스크롤)과 보이는 구간만 보는 Y 창을 둘 다 버리고 스크롤러 안쪽 폭·전 구간 Y로 그린다. + const fitAll = haulPlan !== null; + const widthPx = fitAll ? scroll.clientWidth || params.widthPx : params.widthPx; + const chartAxis = fitAll + ? { ...params.axis, viewRange: undefined, window: undefined } + : { ...params.axis, window: windowState }; + let massAxis: { padLeft: number; ticks: Array<{ y: number; label: string }> } | null = null; const chart = createMassHaulChart( series, visible, params.stationSource, - { ...params.axis, window: windowState }, + chartAxis, params.selectedStationId, params.stationInterval, - params.widthPx, + widthPx, chartHeight, - params.widthPx, + widthPx, params.onSelectStation, haulPlan, (axis) => { @@ -442,14 +451,16 @@ export function createRouteMassHaulPanel( // 종단 캔버스와 같은 폭의 래퍼 — sticky Y축 앵커의 기준(position: relative)이 된다. const wrap = document.createElement("div"); wrap.className = "b05-profile__chart b05-profile__masshaul-chart"; - wrap.style.width = `${params.widthPx}px`; + wrap.style.width = `${widthPx}px`; wrap.style.height = `${chartHeight}px`; wrap.append(chart); // 앵커는 첫 자식 — SVG 뒤면 흐름 위치가 밀려 스크롤 시 축이 안 보인다(B06과 같은 규칙). if (massAxis) wrap.prepend(buildStickyYAxis(massAxis, chartHeight)); scroll.replaceChildren(wrap); // 종단 스크롤러가 이미 옆으로 가 있으면 새로 그린 곡선도 같은 자리에서 시작한다. - if (syncedMain) scroll.scrollLeft = syncedMain.scrollLeft; + // 전체 맞춤일 때는 스크롤할 것이 없으므로 왼쪽 끝에 둔다. + if (fitAll) scroll.scrollLeft = 0; + else if (syncedMain) scroll.scrollLeft = syncedMain.scrollLeft; legendLayer.append( createMassHaulLegend(series, visible, toggleSeries, () => { diff --git a/B05_Profile/B05_Profile_UI_Profile_Render.ts b/B05_Profile/B05_Profile_UI_Profile_Render.ts index 8b01e971..062d250e 100644 --- a/B05_Profile/B05_Profile_UI_Profile_Render.ts +++ b/B05_Profile/B05_Profile_UI_Profile_Render.ts @@ -432,9 +432,9 @@ export function renderProfile(ctx: ProfileRenderContext): void { padRight: LONG_PAD.right + originOffset, // 축 선·눈금은 위 종단 그래프의 축과 같은 자리에 — 축이 두 개로 보이지 않게. axisX: LONG_PAD.left, - // 범례·기준 버튼 오버레이(top 34px)가 곡선 위에 떠서 그만큼 상단 여유를 준다 - // (2026-08-05 사용자 보고: 버튼과 커브 겹침). - padTop: 40, + // 범례·기준 버튼 오버레이(top 4px)가 곡선 위에 떠서 그 높이만큼만 여유를 준다 + // (2026-08-05: 버튼과 커브 겹침 / 2026-09-06: 요약 막대가 빠져 상단이 너무 비었음). + padTop: 30, // 유토곡선 Y 도 종단과 같은 창을 본다 — 전 구간 최대 토량으로 고정하면 확대해도 // 곡선이 납작하게 눌린다(2026-09-04 사용자 지시). viewRange: { fromM: viewFromM, toM: viewToM }, diff --git a/B05_Profile/B05_Profile_UI_Profile_TableOverlay.ts b/B05_Profile/B05_Profile_UI_Profile_TableOverlay.ts index eb0be9fc..ea11fbce 100644 --- a/B05_Profile/B05_Profile_UI_Profile_TableOverlay.ts +++ b/B05_Profile/B05_Profile_UI_Profile_TableOverlay.ts @@ -13,6 +13,7 @@ import { readStateRaw, stateKey, writeStateRaw } from "../A00_Common/b_page_state"; import { createWorkflowPanelHandle } from "@ui/ui_template_overlay"; import { createPanelResizer } from "@ui/ui_template_resizer"; +import { TABLE_ROW_COUNT } from "./B05_Profile_UI_Profile_Layout"; import { attachWheelHorizontalScroll } from "./B05_Profile_UI_Profile_Wheel"; /* 펼침·높이는 화면 취향(①) — 키는 등록표(`b_page_state`)가 만든다. */ @@ -21,8 +22,16 @@ const OVERLAY_MIN_HEIGHT = 140; /** 테이블 오버레이 최소 높이 — 메인 패널 하한 계산(Profile_Panel)이 함께 쓴다. */ export const TABLE_OVERLAY_MIN_HEIGHT = OVERLAY_MIN_HEIGHT; const OVERLAY_MAX_RATIO = 0.75; -/** CSS 기본 높이 — `var(--b05-table-height, 300px)`(_Style_MassHaul.css)와 같아야 한다. */ -const OVERLAY_DEFAULT_HEIGHT = 300; +/** 행 하나의 높이 상한(px). 행이 이보다 커지면 글자만 커지고 읽을 내용은 그대로다 — + * 12행에서 6행으로 줄인 뒤 같은 높이를 나눠 가지며 행이 두 배로 부푼 것을 막는다 + * (2026-09-06 사용자 지시: 「최대값 제한, 레이아웃 제어는 현재 유지」). */ +const MAX_ROW_HEIGHT = 30; +/** 오버레이 높이 상한 = 6행 × 행 상한 + 가로 스크롤바·경계 몫. */ +const OVERLAY_MAX_HEIGHT = TABLE_ROW_COUNT * MAX_ROW_HEIGHT + 24; +/** CSS 기본 높이 — `var(--b05-table-height, 204px)`(_Style_MassHaul.css)와 같아야 한다. */ +const OVERLAY_DEFAULT_HEIGHT = OVERLAY_MAX_HEIGHT; +/** 어느 경로로 들어온 높이든 상한을 넘지 않게 한다(리사이저·패널 비례 연동 공용). */ +const clampHeight = (px: number): number => Math.min(px, OVERLAY_MAX_HEIGHT); /** 리사이저가 높이를 담는 CSS 변수. */ const HEIGHT_VAR = "--b05-table-height"; @@ -71,7 +80,8 @@ export function createProfileTableOverlay(onChanged: () => void): RouteProfileTa cssVar: HEIGHT_VAR, direction: -1, min: OVERLAY_MIN_HEIGHT, - max: () => (overlay.parentElement?.clientHeight ?? window.innerHeight) * OVERLAY_MAX_RATIO, + max: () => + clampHeight((overlay.parentElement?.clientHeight ?? window.innerHeight) * OVERLAY_MAX_RATIO), storageKey: HEIGHT_KEY, onResize: () => { syncHandlePosition(); @@ -132,10 +142,10 @@ export function createProfileTableOverlay(onChanged: () => void): RouteProfileTa isOpen: () => open, desiredHeight: () => { const raw = parseFloat(overlay.style.getPropertyValue(HEIGHT_VAR)); - return Number.isFinite(raw) && raw > 0 ? raw : OVERLAY_DEFAULT_HEIGHT; + return clampHeight(Number.isFinite(raw) && raw > 0 ? raw : OVERLAY_DEFAULT_HEIGHT); }, commitHeight: (px) => { - const next = Math.round(px); + const next = Math.round(clampHeight(px)); overlay.style.setProperty(HEIGHT_VAR, `${next}px`); writeStateRaw("table-height", String(next)); syncHandlePosition(); diff --git a/B05_Profile/B05_Profile_UI_Style_MassHaul.css b/B05_Profile/B05_Profile_UI_Style_MassHaul.css index 5d9234cf..02b120e3 100644 --- a/B05_Profile/B05_Profile_UI_Style_MassHaul.css +++ b/B05_Profile/B05_Profile_UI_Style_MassHaul.css @@ -101,7 +101,7 @@ 공용 범례(.b06-masshaul__legend)가 이미 absolute + right 배치라 top만 이 래퍼가 정한다. */ .b05-profile__masshaul-legend { position: absolute; - top: 34px; + top: 4px; right: 0; left: 0; z-index: 3; @@ -120,10 +120,10 @@ /* 재계산 대기 표시 — 곡선을 지우지 않고(2026-09-03 깜빡임 제거) 곡선 영역 우측 상단에 **떠 있는** 알림으로 둔다. 요약 막대에 붙이면 막대가 줄바꿈되며 곡선을 밀어냈다 - (2026-09-04 사용자 보고). 범례(top: 34px) 아래 줄에 앉혀 서로 가리지 않는다. */ + (2026-09-04 사용자 보고). 범례(top: 4px) 아래 줄에 앉혀 서로 가리지 않는다. */ .b05-profile__masshaul-pending { position: absolute; - top: 62px; + top: 32px; right: var(--spacing-8); z-index: 4; padding: 2px var(--spacing-8); @@ -173,7 +173,7 @@ z-index: 5; display: flex; flex-direction: column; - height: var(--b05-table-height, 300px); + height: var(--b05-table-height, 204px); border-top: 1px solid var(--color-border); background: var(--color-surface-raised); box-shadow: 0 -4px 12px rgb(0 0 0 / 18%);