From 8b9c751d8545edf3d9c040a7eaf639556a1bf41f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 2 Aug 2026 14:23:09 +0900 Subject: [PATCH] =?UTF-8?q?fix(B06):=20=EA=B7=B8=EB=9E=98=ED=94=84=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=ED=91=9C=20=EC=98=A4=EB=B2=84=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=ED=99=94,=20=ED=9A=A1=EB=8B=A8=20=EC=A0=9C=EB=AA=A9?= =?UTF-8?q?=ED=96=89=20=EC=A0=9C=EA=B1=B0,=20=ED=91=9C=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=C2=B7=EC=A0=95=EB=A0=AC=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 종단면도 이름표를 플롯 안쪽 상단으로 겹치고 LONG_PAD.top 30 → 12로 줄여 위 여백을 그래프 몫으로 돌린다. 구조물 라벨은 그만큼 내려 겹치지 않게 했다. - 유토곡선에 같은 양식의 이름표를 하단에 넣는다. 곡선이 지나가는 자리라 불투명 배경판을 깔고 아래 여백을 16 → 8로 줄였다. - 상단 패널과 횡단 카드 사이의 횡단면도 제목·개수 행을 없앤다. 카드마다 측점이 적혀 있어 무엇을 보는지 이미 분명하고, 공간만 먹었다. 관련 CSS와 로케일 키도 정리. - 종단 기준 곡선을 기본 비활성화한다(계획고 기반 개략값이라 참고용). 세션 저장 키를 v3로 올려 옛 값이 이 곡선을 되켜지 못하게 했다. - 면적표 단위를 값 칸에서 좌상단 교차 셀로 옮기고, 열 제목을 가운데 맞춤으로 바꿨다. Co-Authored-By: Claude Opus 5 (1M context) --- .../B06_wf3_ProfileCross_UI_Cross_Areas.ts | 3 ++- .../B06_wf3_ProfileCross_UI_Longitudinal.ts | 9 ++++--- .../B06_wf3_ProfileCross_UI_MassHaul.ts | 7 +++-- .../B06_wf3_ProfileCross_UI_MassHaul_View.ts | 27 +++++++++++++++++-- .../B06_wf3_ProfileCross_UI_Section_Common.ts | 7 ++++- .../B06_wf3_ProfileCross_UI_Section_View.ts | 17 +++++------- .../B06_wf3_ProfileCross_UI_Style_Cross.css | 15 ++--------- ...B06_wf3_ProfileCross_UI_Style_MassHaul.css | 6 +++++ ui_template/ui_template_locale_b2.ts | 5 ++-- 9 files changed, 60 insertions(+), 36 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 ff2c6426..602aea9c 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Cross_Areas.ts +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Cross_Areas.ts @@ -264,8 +264,9 @@ export function buildAreaReadout( const table = document.createElement("table"); table.className = "b06-cross-card__areas"; const head = document.createElement("tr"); + // 좌상단(행제목 × 열제목 교차) 칸은 단위 자리다 — 값 칸마다 단위를 붙이지 않는다. for (const [text, tip] of [ - ["", ""], + [L("B06_Design_Area_Unit"), ""], [L("B06_Design_Area_EA"), L("B06_Design_Ground_Soil")], [L("B06_Design_Area_RR"), L("B06_Design_Ground_Ripping")], [L("B06_Design_Area_BR"), L("B06_Design_Ground_Blasting")], diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Longitudinal.ts b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Longitudinal.ts index c4981ee9..53d57907 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Longitudinal.ts +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Longitudinal.ts @@ -294,11 +294,12 @@ export function createLongitudinalProfile( }), ); // 구조물(비정규) 측점: 구조물 이름을 측점선 상단에 표기해 위치를 식별할 수 있게 한다. + // 그래프 이름표가 플롯 안 상단으로 들어왔으므로 그 아래로 내려 겹치지 않게 한다. if (station.structure) { marker.append( svgText(station.structure, { x: stationX, - y: LONG_PAD.top + 12, + y: LONG_PAD.top + 26, "text-anchor": "middle", class: "b06-chart__structure-label", }), @@ -340,11 +341,11 @@ export function createLongitudinalProfile( y2: heightPx - LONG_PAD.bottom, class: "b06-chart__axis", }), - // X축 제목은 그래프 **상단**에 둔다(2026-08-02 사용자 지시). 아래쪽 여백은 측점 라벨 몫이며, - // B06에서는 종단면도 바로 밑에 유토곡선이 붙어 두 그래프가 X축을 공유하는 것처럼 보여야 한다. + // 그래프 이름표는 플롯 **안쪽 상단에 겹쳐** 놓는다(2026-08-02 사용자 지시) — 위쪽 여백을 + // 이름표 전용으로 비워 두지 않고 그래프 몫으로 돌리기 위해서다. svgText(L("B06_Profile_View_LongitudinalXAxis"), { x: widthPx / 2, - y: 14, + y: LONG_PAD.top + 12, "text-anchor": "middle", class: "b06-chart__axis-label", }), diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_MassHaul.ts b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_MassHaul.ts index 368dec77..e776945c 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_MassHaul.ts +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_MassHaul.ts @@ -100,8 +100,11 @@ const GROUND_TYPES: GroundType[] = ["soil", "ripping_rock", "blasting_rock"]; /** 곡선을 그릴 산출 기준. 배열 순서가 곧 범례 순서다. */ const SERIES_BASES: MassHaulBasis[] = ["cross", "longitudinal"]; -/** 기본으로 켜 두는 곡선. */ -export const MASS_HAUL_DEFAULT_VISIBLE: MassHaulBasis[] = ["cross", "longitudinal"]; +/** + * 기본으로 켜 두는 곡선 — 횡단 기준만. 종단 기준은 계획고 기반 개략값이라 참고용이므로 + * 사용자가 필요할 때 범례에서 켠다(2026-08-02 사용자 지시). + */ +export const MASS_HAUL_DEFAULT_VISIBLE: MassHaulBasis[] = ["cross"]; /** 환산계수가 비어 있거나 값이 이상하면 1.0으로 떨어뜨려 계산이 멈추지 않게 한다. */ function factorFor(conversion: EarthworkConversion, ground: GroundType): number { diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_MassHaul_View.ts b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_MassHaul_View.ts index 7e3835cb..8743b836 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_MassHaul_View.ts +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_MassHaul_View.ts @@ -43,7 +43,7 @@ export const MASS_HAUL_HEIGHT = 190; * 아래쪽은 X축 제목을 종단면도 상단으로 합치면서 비었다. */ const MASS_PAD_TOP = 10; -const MASS_PAD_BOTTOM = 16; +const MASS_PAD_BOTTOM = 8; const BASIS_LABEL: Record = { cross: "B06_MassHaul_Basis_Cross", @@ -406,7 +406,7 @@ export function createMassHaulChart( y2: heightPx - MASS_PAD_BOTTOM, class: "b06-chart__axis", }), - // X축 제목은 종단면도 상단 하나로 합쳤다 — 여기서 또 적으면 두 그래프가 별개 축으로 읽힌다. + // X축 제목은 종단면도 이름표 하나로 합쳤다 — 여기서 또 적으면 두 그래프가 별개 축으로 읽힌다. svgText(L("B06_MassHaul_YAxis"), { x: 15, y: heightPx / 2, @@ -415,6 +415,29 @@ export function createMassHaulChart( class: "b06-chart__axis-label", }), ); + + // 그래프 이름표 — 종단면도와 같은 양식이되 **하단**에 놓고, 곡선이 지나가는 자리라 + // 불투명 배경을 깐다(2026-08-02 사용자 지시). + const nameText = L("B06_MassHaul_Title"); + const nameWidth = textWidth(nameText) + 14; + const nameY = heightPx - MASS_PAD_BOTTOM - 6; + svg.append( + svgElement("rect", { + x: widthPx / 2 - nameWidth / 2, + y: nameY - 12, + width: nameWidth, + height: 17, + rx: 4, + ry: 4, + class: "b06-masshaul__name-plate", + }), + svgText(nameText, { + x: widthPx / 2, + y: nameY, + "text-anchor": "middle", + class: "b06-chart__axis-label", + }), + ); return svg; } diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_Common.ts b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_Common.ts index 80a97d5d..32c6fe1a 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_Common.ts +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_Common.ts @@ -30,7 +30,12 @@ export const CROSS_GRID_GAP = 16; // bottom은 측점 라벨 한 줄 몫이다. X축 제목을 그래프 상단으로 올린 뒤 남던 여백을 걷어내, // 종단면도 아래 라벨이 그 밑 유토곡선과의 틈 **가운데**에 오도록 좁혔다(2026-08-02 사용자 지시). // left/right는 유토곡선과 X축을 맞추는 값이라 함부로 바꾸면 두 그래프 측점선이 어긋난다. -export const LONG_PAD = { left: 62, right: 24, top: 30, bottom: 26 }; +// top은 그래프 이름표가 겹쳐 놓이는 자리다. 이름표를 플롯 안 오버레이로 돌린 뒤 남던 위쪽 +// 여백을 걷어 그래프 몫으로 돌렸다(2026-08-02 사용자 지시). +// bottom은 측점 라벨 한 줄 몫이다. X축 제목을 이름표로 합치며 남던 여백을 걷어내, +// 종단면도 아래 라벨이 그 밑 유토곡선과의 틈 **가운데**에 오도록 좁혔다. +// left/right는 유토곡선과 X축을 맞추는 값이라 함부로 바꾸면 두 그래프 측점선이 어긋난다. +export const LONG_PAD = { left: 62, right: 24, top: 12, bottom: 26 }; export const CROSS_PAD = { left: 58, right: 20, top: 10, bottom: 52 }; export interface YScaleOptions { 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 a46e9c87..bc2b2318 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts @@ -71,9 +71,9 @@ const MIN_PANEL_HEIGHT = MIN_LONG_HEIGHT + MASS_HAUL_MIN_HEIGHT + PANEL_CHROME_P const MAX_PANEL_HEIGHT_RATIO = 0.8; const PANEL_HEIGHT_KEY = "b06:profile-panel-height"; const PANEL_COLLAPSED_KEY = "b06:profile-panel-collapsed"; -// 곡선 키가 `${기준}_${환산}`에서 기준 하나로 바뀌었다 — 옛 세션값을 읽으면 전부 꺼진 것처럼 -// 보이므로 저장 키의 판을 올려 갈아탄다. -const MASS_HAUL_VISIBLE_KEY = "b06:masshaul-visible-v2"; +// 곡선 키·기본값이 바뀔 때마다 저장 키의 판을 올려 갈아탄다(옛 세션값을 읽으면 의도와 다른 +// 곡선이 켜진다). v2: 키가 `${기준}_${환산}` → `${기준}`. v3: 기본 표시가 횡단 기준 하나로. +const MASS_HAUL_VISIBLE_KEY = "b06:masshaul-visible-v3"; /** 켜 둔 곡선 목록은 세션에만 남긴다(패널 높이·접힘과 같은 규칙). */ function readVisibleSeries(): Set { @@ -377,13 +377,8 @@ export function createSectionView( currentStationInterval ?? inferStationInterval(detail.longitudinal.stations); drawPanel(); - const crossHeading = document.createElement("div"); - crossHeading.className = "b06-section__heading"; - const crossTitle = document.createElement("h3"); - crossTitle.textContent = L("B06_Profile_View_Cross"); - const crossCount = document.createElement("span"); - crossCount.textContent = `${detail.cross_sections.length}${L("B06_Profile_View_CrossCountSuffix")}`; - crossHeading.append(crossTitle, crossCount); + // 횡단면도 제목·개수 표기는 두지 않는다(2026-08-02 사용자 지시) — 상단 패널과 카드 사이 + // 공간만 먹고, 카드마다 측점이 적혀 있어 무엇을 보는지는 이미 분명하다. const grid = document.createElement("div"); grid.className = "b06-section__grid"; const columnCount = Math.max( @@ -424,7 +419,7 @@ export function createSectionView( } // panel은 이미 root의 자식이라 replaceChildren이 떼었다 붙이면서 스크롤을 잃는다. const keepScrollLeft = chartWrap.scrollLeft; - root.replaceChildren(panel, crossHeading, grid); + root.replaceChildren(panel, grid); chartWrap.scrollLeft = keepScrollLeft; }; 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 d14707f2..64acfe8b 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_Cross.css +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_Cross.css @@ -100,10 +100,6 @@ content: "▾"; } -.b06-section__heading h3 { - font-size: var(--text-body); -} - /* 종단면도와 유토곡선이 이 컨테이너 하나를 공유한다 — 가로 스크롤을 함께 써야 같은 측점이 두 그래프에서 같은 자리에 선다. wrap을 나누면 정렬이 깨진다. */ .b06-section__chart-wrap { @@ -154,15 +150,6 @@ width: 100%; } -.b06-section__heading { - display: flex; - align-items: end; - justify-content: space-between; - gap: var(--spacing-16); - color: var(--color-text-secondary); - font-size: var(--text-caption); -} - .b06-section__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(480px, 100%), 1fr)); @@ -547,7 +534,9 @@ font-weight: var(--font-weight-regular); } +/* 열 제목은 가운데 맞춤 — 값은 우측 정렬이라 자릿수가 흔들려도 머리글은 열 중앙에 선다. */ .b06-cross-card__areas thead th { + text-align: center; color: var(--color-text-muted); } diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_MassHaul.css b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_MassHaul.css index 02aaa059..901648e8 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_MassHaul.css +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_MassHaul.css @@ -99,6 +99,12 @@ font-weight: var(--font-weight-medium); } +/* 그래프 이름표 배경 — 이름이 곡선 위에 놓이므로 불투명하게 깐다. */ +.b06-masshaul__name-plate { + fill: var(--color-surface); + stroke: none; +} + .b06-masshaul__summary { display: flex; flex-wrap: wrap; diff --git a/ui_template/ui_template_locale_b2.ts b/ui_template/ui_template_locale_b2.ts index 191eba00..7b8c81ef 100644 --- a/ui_template/ui_template_locale_b2.ts +++ b/ui_template/ui_template_locale_b2.ts @@ -186,7 +186,6 @@ export const ui_locales_b2 = { B06_Profile_View_Longitudinal: ["종단면도", "Longitudinal profile"], B06_Profile_View_Cross: ["횡단면도", "Cross sections"], B06_Profile_View_StationCount: ["횡단 측점", "Cross stations"], - B06_Profile_View_CrossCountSuffix: ["개 도면", " drawings"], B06_Profile_View_LongitudinalXAxis: [ "BP 기준 누적거리 (횡단 측점)", "Chainage from BP (cross stations)", @@ -259,7 +258,9 @@ export const ui_locales_b2 = { B06_Design_Area_EA: ["EA", "EA"], B06_Design_Area_RR: ["RR", "RR"], B06_Design_Area_BR: ["BR", "BR"], - B06_Design_Area_Total: ["계 (㎡)", "Total (㎡)"], + B06_Design_Area_Total: ["계", "Total"], + /* 단위는 값 칸마다 붙이지 않고 표 좌상단(행제목 × 열제목 교차) 칸에 한 번만 적는다. */ + B06_Design_Area_Unit: ["㎡", "㎡"], B06_Design_Area_Highlight: ["누르면 해당 면적을 강조합니다", "Click to highlight this area"], B06_Design_Fill_Area: ["성토", "Fill"], B06_Design_Unset: ["미지정", "Not set"],