fix(B05·B06): 유토곡선 토량 분배 때 종단 · 표 스크롤 유지 · 횡단 보기 = 보기 반폭 그대로 · 십자선 크기 고정 · 보기 반폭 기본 10 m(79-1~4)

- 토량 분배(전체 맞춤) 중에는 유토곡선 ↔ 종단 스크롤 동기화를 끊음 — 0 으로 되돌던 것 없앰
- 횡단 카드는 사면 교차점 · 배수관 세트 폭으로 줌아웃하지 않음 · 밖은 가운데 버튼 팬 · ⤢ 는 보기값으로
- 줌인해도 중심 십자선 길이 그대로(도형 레이어 배율을 십자선 중심에서 되돌림)
- 횡단 보기 반폭 기본 12 → 10 m(계산 반폭과 따로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EMfGERKUg5nMMNQsyVZ2j
This commit is contained in:
2026-10-07 23:50:13 +09:00
co-authored by Claude Opus 5.5
parent 11148c60fb
commit fc1068a9cd
6 changed files with 33 additions and 39 deletions
+3 -3
View File
@@ -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 사용자 보고의 원인). 사용자가 실제로
* 고른 표시 폭만 되살린다: ① 측점 개별 표시 반폭 저장값의 대표값 ② 세션의 마지막 조절값. */
@@ -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
? {
+6 -10
View File
@@ -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);
+12 -3
View File
@@ -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<Sample | null>((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})`,
);
}
/**
@@ -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;
}
@@ -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");