From 24843c71435ccbebd5134c91429aeb3299e4644f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 2 Aug 2026 14:36:35 +0900 Subject: [PATCH] =?UTF-8?q?fix(B06):=200=EA=B0=92=20=EC=B9=B8=EB=8F=84=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EA=B0=80=EB=8A=A5,=20=EC=84=B1=ED=86=A0?= =?UTF-8?q?=20=EA=B2=BD=EA=B3=84=EB=8A=94=20=EC=B2=AB=20=EA=B5=90=EC=B0=A8?= =?UTF-8?q?=EA=B9=8C=EC=A7=80,=20=EC=A0=91=EA=B8=B0=20=EC=86=90=EC=9E=A1?= =?UTF-8?q?=EC=9D=B4=20=EC=B9=98=EC=88=98=20=EA=B5=90=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 면적표의 모든 칸을 버튼으로 만든다. 예전에는 그 측점의 암종과 맞지 않는 칸(값 0)을 막아 칸마다 눌리는 자리가 달라졌다. 강조 키를 cut_rock 하나에서 cut_rr/cut_br 둘로 갈라, 표는 늘 따로 서되 그림의 암반 밴드는 암종이 맞는 쪽을 눌렀을 때만 켜진다. - 성토 밴드는 중심선에서 바깥으로 나가다 지면과 처음 만나는 곳까지만 유효하다. 그 너머에서 지면이 다시 설계선 아래로 내려가 생기던 조각을 버린다(좌·우 각각 안쪽 조각만 유지). - 접기 손잡이 치수를 좌측 사이드 손잡이(24×64, 오른쪽 모서리 둥금)의 시계방향 90° 회전값인 64×24 · 아래쪽 모서리 둥금으로 교정한다. 리사이저 위치와 패널 여유 높이도 함께 맞췄다. Co-Authored-By: Claude Opus 5 (1M context) --- .../B06_wf3_ProfileCross_UI_Cross_Areas.ts | 84 +++++++++++++++---- .../B06_wf3_ProfileCross_UI_Section_View.ts | 2 +- .../B06_wf3_ProfileCross_UI_Style_Cross.css | 10 ++- 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Cross_Areas.ts b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Cross_Areas.ts index 602aea9c..8b630558 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Cross_Areas.ts +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Cross_Areas.ts @@ -18,8 +18,15 @@ import type { CrossDesign, SectionSample } from "./B06_wf3_ProfileCross_Api_Fetch"; import { L, svgElement } from "./B06_wf3_ProfileCross_UI_Section_Common"; -/** 강조 대상. `cut_total`은 토사·암반 두 밴드를 함께 켠다. */ -export type CrossAreaKey = "cut_soil" | "cut_rock" | "cut_total" | "fill"; +/** + * 강조 대상 = 면적표의 칸. `cut_total`은 토사·암반 두 밴드를 함께 켠다. + * `cut_rr`/`cut_br`은 표에서는 늘 따로 서지만 그림에서는 암반 밴드 **하나**를 가리키므로, + * 그 측점의 암종과 맞는 쪽을 눌렀을 때만 밴드가 켜진다(값이 0인 쪽은 눌려도 그림 변화 없음). + */ +export type CrossAreaKey = "cut_soil" | "cut_rr" | "cut_br" | "cut_total" | "fill"; + +/** 그림에 실제로 깔리는 밴드. 표의 칸(`CrossAreaKey`)보다 적다. */ +type BandKey = "cut_soil" | "cut_rock" | "fill"; export type AreaHighlightSetter = (key: CrossAreaKey | null) => void; @@ -43,6 +50,37 @@ function interpolator(points: Array<{ offset: number; value: number }>) { }; } +/** 밴드 조각 하나. `from`/`to`는 그 조각이 덮는 편거리(offset) 범위다. */ +interface BandPiece { + points: string; + from: number; + to: number; +} + +/** + * 성토 밴드는 **중심선에서 바깥으로 나가다 지면과 처음 만나는 곳까지**만 유효하다. + * 그 너머에서 지면이 다시 설계선 아래로 내려가 생기는 조각은 성토부가 아니므로 버린다 + * (2026-08-02 사용자 지시). 좌·우 각각 중심선에 가장 가까운 조각만 남긴다. + */ +function keepInnermost(pieces: BandPiece[]): BandPiece[] { + let left: BandPiece | null = null; + let right: BandPiece | null = null; + const kept: BandPiece[] = []; + for (const piece of pieces) { + if (piece.from <= 0 && piece.to >= 0) { + // 중심선을 물고 있는 조각은 좌우 어느 쪽으로도 첫 조각이라 그대로 둔다. + kept.push(piece); + continue; + } + if (piece.to < 0) { + if (!left || piece.to > left.to) left = piece; + } else if (!right || piece.from < right.from) right = piece; + } + if (left) kept.push(left); + if (right) kept.push(right); + return kept; +} + /** * `top > bottom`인 구간만 잘라 폴리곤 점 문자열을 만든다. * 두께가 0이 되는 지점을 끼워 넣어 밴드 끝이 지면선·설계선 교점에서 정확히 닫히게 한다. @@ -53,16 +91,21 @@ function bandPolygons( bottom: number[], px: (offset: number) => number, py: (elevation: number) => number, -): string[] { - const polygons: string[] = []; +): BandPiece[] { + const polygons: BandPiece[] = []; let upper: string[] = []; let lower: string[] = []; + let from = 0; + let to = 0; const flush = (): void => { - if (upper.length >= 2) polygons.push([...upper, ...lower.reverse()].join(" ")); + if (upper.length >= 2) + polygons.push({ points: [...upper, ...lower.reverse()].join(" "), from, to }); upper = []; lower = []; }; const add = (offset: number, high: number, low: number): void => { + if (!upper.length) from = offset; + to = offset; upper.push(`${px(offset)},${py(high)}`); lower.push(`${px(offset)},${py(low)}`); }; @@ -142,7 +185,7 @@ export function appendCrossAreaBands( const depth = soilDepth(design); const rockLevels = depth === null ? null : groundLevels.map((level) => level - depth); - const bands: Array<{ key: CrossAreaKey; top: number[]; bottom: number[] }> = [ + const bands: Array<{ key: BandKey; top: number[]; bottom: number[] }> = [ { key: "fill", top: designLevels, bottom: groundLevels }, ]; if (rockLevels) { @@ -156,16 +199,19 @@ export function appendCrossAreaBands( bands.push({ key: "cut_soil", top: groundLevels, bottom: designLevels }); } - const groups = new Map(); + // 표에서 암반 칸을 눌렀을 때 이 측점의 암종과 맞는 쪽만 밴드를 켠다. + const rockCell: CrossAreaKey = design.cut_rock_kind === "blasting_rock" ? "cut_br" : "cut_rr"; + const groups = new Map(); for (const band of bands) { - const polygons = bandPolygons(xs, band.top, band.bottom, x, toDisplayY); - if (!polygons.length) continue; + let pieces = bandPolygons(xs, band.top, band.bottom, x, toDisplayY); + if (band.key === "fill") pieces = keepInnermost(pieces); + if (!pieces.length) continue; const group = svgElement("g", { class: `b06-chart__area b06-chart__area--${band.key}` }); - for (const points of polygons) group.append(svgElement("polygon", { points })); + for (const piece of pieces) group.append(svgElement("polygon", { points: piece.points })); group.addEventListener("click", (event) => { // 카드까지 올라가면 측점 선택이 다시 걸려 카드가 통째로 다시 그려진다 — 방금 켠 강조가 사라진다. event.stopPropagation(); - onSelect(band.key); + onSelect(band.key === "cut_rock" ? rockCell : band.key); }); groups.set(band.key, group); svg.append(group); @@ -174,8 +220,11 @@ export function appendCrossAreaBands( return (key) => { for (const [bandKey, group] of groups) { const active = - key === bandKey || - (key === "cut_total" && (bandKey === "cut_soil" || bandKey === "cut_rock")); + key === "cut_total" + ? bandKey === "cut_soil" || bandKey === "cut_rock" + : bandKey === "cut_rock" + ? key === rockCell + : key === bandKey; group.classList.toggle("is-active", active); } }; @@ -282,18 +331,17 @@ export function buildAreaReadout( const cells = new Map(); const cut = cutBreakdown(design); - const rockKind = - design.cut_rock_kind ?? (design.ground_type === "soil" ? null : design.ground_type); const tbody = document.createElement("tbody"); const rows: Array<{ label: string; variant: string; cells: AreaCell[] }> = [ { label: L("B06_Design_Cut_Area"), variant: "cut_total", + // 값이 0이어도 누를 수 있다(2026-08-02 사용자 지시) — 그림에 없는 면적이면 강조만 켜지고 + // 밴드는 그대로다. 칸마다 눌리는 자리가 달라지면 어느 칸이 눌리는지 매번 확인해야 한다. cells: [ { key: "cut_soil", value: cut.ea, variant: "cut_soil" }, - // 암반 밴드는 하나뿐이다 — 그 측점의 암종에 해당하는 칸만 강조 버튼이 된다. - { key: rockKind === "ripping_rock" ? "cut_rock" : null, value: cut.rr, variant: "cut_rr" }, - { key: rockKind === "blasting_rock" ? "cut_rock" : null, value: cut.br, variant: "cut_br" }, + { key: "cut_rr", value: cut.rr, variant: "cut_rr" }, + { key: "cut_br", value: cut.br, variant: "cut_br" }, { key: "cut_total", value: cut.total, variant: "cut_total" }, ], }, diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts index 11fa359d..a6d169fe 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts @@ -65,7 +65,7 @@ export type { CrossDesignChange, DesignChangeHandler, RockBoundaryControl }; * 그래프 몫이 된다. (`.b06-section__panel-body`가 `overflow: hidden`이라 모자라면 아래가 * 잘린다. 범례는 그래프 위에 겹치는 오버레이라 흐름 높이를 먹지 않는다.) */ -const PANEL_CHROME_PX = 116; +const PANEL_CHROME_PX = 126; /** 손대지 않았을 때의 패널 높이 — 이 값이 축소 비례의 기준(H₀)이 된다. */ const BASE_PANEL_HEIGHT = LONG_HEIGHT + MASS_HAUL_HEIGHT + PANEL_CHROME_PX; const MIN_LONG_HEIGHT = 110; diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_Cross.css b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_Cross.css index 3a12d9c5..7654b50c 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_Cross.css +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_Cross.css @@ -60,8 +60,8 @@ 잘려 나가 잡히지 않는다 — 안쪽에 붙여 전체가 클릭 영역으로 남게 한다. */ .b06-section__panel > .ui-resizer--vertical { top: auto; - /* 하단 가운데 접기 손잡이(14px) 위에 얹는다 — 겹치면 어느 쪽이 잡히는지 헷갈린다. */ - bottom: 14px; + /* 하단 가운데 접기 손잡이(24px) 위에 얹는다 — 겹치면 어느 쪽이 잡히는지 헷갈린다. */ + bottom: var(--spacing-24); height: 8px; } @@ -107,10 +107,12 @@ /* `ui-collapsible__title`이 flex + space-between을 걸어 두므로 아이콘을 가운데로 되돌린다. */ justify-content: center; align-self: center; + /* 좌측 사이드 손잡이(24×64, 오른쪽 모서리 둥금)를 시계방향 90° 돌린 값 — + 가로 64 × 세로 24, 아래쪽 모서리가 둥글다. */ width: var(--spacing-64); - height: 14px; + height: var(--spacing-24); margin-top: auto; - border-radius: var(--radius-buttons) var(--radius-buttons) 0 0; + border-radius: 0 0 var(--radius-buttons) var(--radius-buttons); } .b06-section__panel-toggle::after {