fix(B06): 계획선 플롯 이탈 clip 처리·전체 반영 시 개별 반폭 초기화

- 횡단 카드 설계선/면적 밴드/포장층/암 경계선을 플롯 영역 clipPath 그룹에
  그려 표시 반폭 밖으로 뚫고 나가지 않게 함
- [전체 측점 반영]이 개별 반폭(세션·저장 display_half_width_m)을 전역값으로
  초기화 — 저장 잔재로 0측점이 전역 반폭을 무시하던 문제 해결
- 오버레이 함수 시그니처 SVGSVGElement→SVGElement

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 20:01:36 +09:00
co-authored by Claude Fable 5
parent d93bd6e28d
commit 8b359cd49c
4 changed files with 33 additions and 8 deletions
@@ -156,7 +156,7 @@ function soilDepth(design: CrossDesign): number | null {
* 밴드는 평소 투명에 가깝게 두되 클릭 대상은 되도록 남겨 "면적을 눌러도 강조"가 성립한다.
*/
export function appendCrossAreaBands(
svg: SVGSVGElement,
svg: SVGElement,
design: CrossDesign,
groundSamples: SectionSample[],
x: (offset: number) => number,
@@ -437,7 +437,7 @@ export function buildDesignControls(
* 부분)만 점선으로 그려 뒤에 깔린 지표선이 비쳐 보이게 하고, 나머지는 실선으로 둔다.
*/
export function appendCrossDesignOverlay(
svg: SVGSVGElement,
svg: SVGElement,
design: CrossDesign,
x: (offset: number) => number,
toDisplayY: (elevation: number) => number,
@@ -516,7 +516,7 @@ export function appendCrossDesignOverlay(
* 무효 샘플 구간은 지반선과 동일하게 선을 끊어 그린다.
*/
export function appendRockBoundaryOverlay(
svg: SVGSVGElement,
svg: SVGElement,
groundSamples: Array<{ offset_m?: number; elevation_m?: number | null; valid: boolean }>,
offsetM: number,
x: (offset: number) => number,
@@ -544,7 +544,7 @@ export function appendRockBoundaryOverlay(
/** 포장 측점의 노면 포장층 박스를 겹쳐 그린다 (노면 양 끝점 기준, 두께만큼 하향). */
export function appendPavementOverlay(
svg: SVGSVGElement,
svg: SVGElement,
design: CrossDesign,
x: (offset: number) => number,
toDisplayY: (elevation: number) => number,
@@ -476,11 +476,26 @@ export function createCrossSectionCard(
if (section.design) {
const toDisplayY = (elevation: number): number =>
y(elevationMid + (elevation - elevationMid) * exaggeration);
// 표시 반폭으로 지면 샘플만 잘라도 설계선·면적 밴드·포장·암경계는 원래 좌표를 그대로
// 쓰므로 플롯 밖으로 뚫고 나간다(2026-08-06 사용자 지적). 플롯 영역 clipPath로 잘라
// 지정한 반폭 범위만 보이게 한다.
const clipId = `b06-cross-clip-${section.station_id}`;
const clip = svgElement("clipPath", { id: clipId });
clip.append(
svgElement("rect", {
x: CROSS_PAD.left,
y: CROSS_PAD.top,
width: Math.max(widthPx - CROSS_PAD.left - CROSS_PAD.right, 1),
height: Math.max(heightPx - CROSS_PAD.top - CROSS_PAD.bottom, 1),
}),
);
const overlayGroup = svgElement("g", { "clip-path": `url(#${clipId})` });
svg.append(clip, overlayGroup);
// 면적 밴드는 지면선 위·설계선 아래에 깔아 선이 밴드에 가리지 않게 한다.
// 선택 여부와 무관하게 항상 깐다 — 평소에는 투명이고, 선택이 바뀔 때 카드를 새로 만들지
// 않고 클래스만 켜면 되므로 줌·팬이 살아남는다.
setBandActive = appendCrossAreaBands(
svg,
overlayGroup,
section.design,
sourceSamples,
x,
@@ -488,12 +503,12 @@ export function createCrossSectionCard(
toggleArea,
);
// 포장층 → 설계선 → 암 경계선 순으로 겹쳐, 설계선이 포장 박스 위에 오게 한다.
appendPavementOverlay(svg, section.design, x, toDisplayY);
appendCrossDesignOverlay(svg, section.design, x, toDisplayY, sourceSamples);
appendPavementOverlay(overlayGroup, section.design, x, toDisplayY);
appendCrossDesignOverlay(overlayGroup, section.design, x, toDisplayY, sourceSamples);
// 암 경계선은 지면선(지반선) 복사 + 오프셋 — 계획선 기준이 아님에 유의.
if (rockBoundary && section.design.geometry_preset === "rock") {
appendRockBoundaryOverlay(
svg,
overlayGroup,
sourceSamples,
rockBoundary.offsetFor(section),
x,
@@ -282,6 +282,16 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise<void> {
}
hideLoadingOverlay();
}
// 전체 반영 = 개별 반폭도 전역값으로 초기화(2026-08-06). 저장된 개별값
// (design.display_half_width_m)이 남아 있으면 전역 반폭이 무시되므로, 세션에
// 전역값을 명시해 저장값보다 우선하게 한다(확정 시 저장값도 전역으로 덮인다).
stationWidths.clear();
if (requested !== undefined) {
for (const section of sectionDetail.cross_sections) {
stationWidths.set(widthKey(section.chainage_m), clampStationWidth(requested));
}
}
persistStationWidths();
persistDisplayHalfWidth();
renderSectionDetail();
updateActionState();