perf(B05,B06): 유토곡선·B06 페이지도 세로 창을 실시간으로 따라오게
사용자 지적(2026-09-04) — 「유토곡선도 반영해야지. B06 페이지도 있잖아. 같은 로직일 텐데 왜 반영이 안 된 거야」. 그리는 쪽(종단 렌더러)은 공용이라 이미 바뀌어 있었고, **스크롤을 받아 창을 옮기는 쪽이 화면마다 따로**라 B05 만 연결돼 있던 것. - 세로 창 모듈을 `common_util_chart_ywindow.ts` 로 옮겨 B05·B06 이 함께 씀. 좌측 고정 축은 표식(`b05-profile__yaxis--ywindow`)이 붙은 것만 옮김 — B06 은 한 컨테이너에 종단·유토곡선 축이 함께 살아 남의 축까지 옮기면 안 됨. - 세로 과장(B06 조절값)을 각인·반영 — 예전에는 과장이 1일 때만 대상이었음. - 유토곡선은 변환이 아니라 **곡선만 다시 그림**(스크롤마다). 곡선 위 말풍선·EP 표·측점 점처럼 세로로 늘어나면 안 되는 것이 많아, 그것만 따로 옮기는 값이 다시 그리는 값보다 큼 (유토 SVG 234개 = 종단의 1/3). - B06 상단 패널을 통째로 다시 그리던 스크롤 처리를 걷어내고, 종단은 변환·유토곡선은 제자리 교체로 바꿈. 700줄 제한에 맞춰 종단 조립을 `_View_Chart.ts`(116줄)로, 유토곡선 조립기를 `_View_MassHaul.ts` 로 분리(727 → 718줄). 자체검증(공용 브라우저 임시 탭): ① B05 유토곡선 눈금이 스크롤을 따라 4.3㎥ → −1,348.8㎥ 로 이동(예전에는 안 따라옴) ② B06 종단 눈금 830/840/850m → 824/826/828m, 유토곡선 68.2㎥ → −3,375.1㎥ 로 동시 이동 ③ B06 최대 프레임 **35.9 → 23.5ms**, 33ms 초과 1 → **0건**(중앙값 16.7ms 유지) ④ B05 최대 프레임 27.4ms, 33ms 초과 0건 ⑤ 전체 시험 381 통과·17 건너뜀. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/* =============================================================================
|
||||
* B06_Section_UI_Section_View_Chart.ts
|
||||
* B06 상단 패널의 **종단면도 몫**만 떼어 낸 조립기 (2026-09-04 · 700줄 제한).
|
||||
*
|
||||
* 세로는 **보이는 구간에 자동으로 맞춘다**(2026-09-04 사용자 확정, B05 와 같은 규칙·같은
|
||||
* 함수). 가로 스크롤 위치와 컨테이너 폭이 곧 보이는 구간이다. 스크롤할 때 그래프를 다시
|
||||
* 만들지 않고 세로 창을 **변환으로** 옮기는 것도 B05 와 같다
|
||||
* (`common_util_chart_ywindow`).
|
||||
* ========================================================================== */
|
||||
|
||||
import type { SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import { createLongitudinalProfile } from "./B06_Section_UI_Longitudinal";
|
||||
import {
|
||||
LONG_PAD,
|
||||
longitudinalMaxChainage,
|
||||
windowElevationRange,
|
||||
} from "./B06_Section_UI_Section_Common";
|
||||
import { unwrapChart } from "./B06_Section_UI_Section_View_Panel";
|
||||
|
||||
export interface LongitudinalChartInput {
|
||||
detail: SectionDetailResponse;
|
||||
selectedStationId: string | null;
|
||||
/** 세로 과장(사용자 조절값). */
|
||||
exaggeration: number;
|
||||
selectStation: (stationId: string) => void;
|
||||
stationInterval: number;
|
||||
chartWidth: number;
|
||||
chartHeight: number;
|
||||
minWidth: number;
|
||||
/** 지금 보이는 구간을 정하는 값 — 가로 스크롤 위치와 컨테이너 안쪽 폭. */
|
||||
scrollLeft: number;
|
||||
viewportWidth: number;
|
||||
}
|
||||
|
||||
export interface LongitudinalChartResult {
|
||||
node: Element;
|
||||
axis: { padLeft: number; ticks: Array<{ y: number; label: string }> } | null;
|
||||
/** 화면 x(px) → 누가거리(m). 스크롤 갱신이 보이는 구간을 다시 잴 때 쓴다. */
|
||||
toChainage: (px: number) => number;
|
||||
maxChainageM: number;
|
||||
viewFromM: number;
|
||||
viewToM: number;
|
||||
}
|
||||
|
||||
/** 지금 보이는 구간(누가거리 m)을 잰다 — 그릴 때와 스크롤 갱신 때가 **같은 식**을 쓴다. */
|
||||
export function visibleChainageRange(
|
||||
toChainage: (px: number) => number,
|
||||
maxChainageM: number,
|
||||
scrollLeft: number,
|
||||
viewportWidth: number,
|
||||
): { fromM: number; toM: number } {
|
||||
return {
|
||||
fromM: Math.max(0, toChainage(scrollLeft)),
|
||||
toM: Math.min(maxChainageM, toChainage(scrollLeft + viewportWidth)),
|
||||
};
|
||||
}
|
||||
|
||||
/** 보이는 구간의 표고 범위 — 종단 세로 창의 기준. */
|
||||
export function visibleElevationRange(
|
||||
detail: SectionDetailResponse,
|
||||
fromM: number,
|
||||
toM: number,
|
||||
): { min: number; max: number } | null {
|
||||
return (
|
||||
windowElevationRange(
|
||||
[
|
||||
detail.longitudinal.samples,
|
||||
...(detail.longitudinal.design_profiles ?? []).map((profile) => profile.samples),
|
||||
],
|
||||
fromM,
|
||||
toM,
|
||||
) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
export function buildLongitudinalChart(input: LongitudinalChartInput): LongitudinalChartResult {
|
||||
const { detail } = input;
|
||||
const maxChainageM = longitudinalMaxChainage(detail.longitudinal);
|
||||
const plotWidth = Math.max(1, input.chartWidth - LONG_PAD.left - LONG_PAD.right);
|
||||
const toChainage = (px: number): number => ((px - LONG_PAD.left) / plotWidth) * maxChainageM;
|
||||
const { fromM, toM } = visibleChainageRange(
|
||||
toChainage,
|
||||
maxChainageM,
|
||||
input.scrollLeft,
|
||||
input.viewportWidth,
|
||||
);
|
||||
|
||||
// Y축 눈금을 렌더러에서 받아 가로 스크롤 고정 오버레이로 얹는다(2026-08-04 사용자
|
||||
// 지시 — 테이블 행 이름표처럼 스크롤해도 계속 보이게, B05와 같은 방식).
|
||||
let axis: { padLeft: number; ticks: Array<{ y: number; label: string }> } | null = null;
|
||||
const node = unwrapChart(
|
||||
createLongitudinalProfile(
|
||||
detail.longitudinal,
|
||||
input.selectedStationId,
|
||||
input.exaggeration,
|
||||
// 공통 Y 스케일(횡단 카드 몫)을 여기 넘기면 종단이 전 구간 축에 묶여 눌린다.
|
||||
undefined,
|
||||
input.selectStation,
|
||||
input.stationInterval,
|
||||
input.chartWidth,
|
||||
input.chartHeight,
|
||||
input.minWidth,
|
||||
detail.longitudinal.design_profiles ?? [],
|
||||
0,
|
||||
(next) => {
|
||||
axis = next;
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
visibleElevationRange(detail, fromM, toM) ?? undefined,
|
||||
),
|
||||
);
|
||||
return { node, axis, toChainage, maxChainageM, viewFromM: fromM, viewToM: toM };
|
||||
}
|
||||
Reference in New Issue
Block a user