Files
Aislo/B06_Section/B06_Section_UI_Cross_View_Metrics.ts
T
eomsangdonandClaude Opus 5 e54da341ea refactor(B06): 700줄 초과 파일 분리 — Router·Cross_View·Section_View·Style
- B06_Section_Router.py(824) → Router_Design.py(139) 분리
- Cross_View → Cross_View_Metrics.ts(75) 분리
- Cross_Design → Cross_Design_Surface.ts(54) 분리
- Section_View → Section_View_Panel.ts(53) 분리
- Page → Page_Common.ts(15) 분리
- Style_Cross.css → Style_Cross_Controls.css(174) 분리
- B06_Section/ 전체 700줄 초과 0건

검증: tsc --noEmit 통과 · ruff check 통과(import 정렬·포맷 적용) ·
prettier 적용 · pytest tmp/tests/ 148 passed, 7 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 17:04:08 +09:00

76 lines
2.5 KiB
TypeScript

import type { CrossSection, SectionSample } from "./B06_Section_Api_Fetch";
import { CROSS_HEIGHT, CROSS_PAD, validElevation } from "./B06_Section_UI_Section_Common";
const AREA_OVERLAY_HEADROOM_PX = 58;
interface CrossPlotMetrics {
sourceSamples: SectionSample[];
minOffset: number;
maxOffset: number;
elevationMid: number;
displaySpan: number;
displayMax: number;
pixelsPerMeter: number;
heightPx: number;
}
export function crossPlotMetrics(
section: CrossSection,
verticalExaggeration: number,
widthPx: number,
crossHalfWidth?: number,
designElevation?: number,
forcedHeightPx?: number,
): CrossPlotMetrics | null {
const sourceSamples = section.samples.filter(
(sample) =>
crossHalfWidth === undefined || Math.abs(sample.offset_m ?? 0) <= crossHalfWidth + 1e-6,
);
const valid = sourceSamples.filter(validElevation);
if (!valid.length) return null;
const offsets = sourceSamples.map((sample) => sample.offset_m ?? 0);
const minOffset = Math.min(...offsets, -1);
const maxOffset = Math.max(...offsets, 1);
const elevations = valid.map((sample) => sample.elevation_m);
if (designElevation !== undefined && Number.isFinite(designElevation))
elevations.push(designElevation);
const rawMin = Math.min(...elevations);
const rawMax = Math.max(...elevations);
const elevationMid = (rawMin + rawMax) / 2;
const padding = rawMax > rawMin ? (rawMax - rawMin) * 0.08 : 0.5;
const pixelsPerMeter =
(widthPx - CROSS_PAD.left - CROSS_PAD.right) / Math.max(maxOffset - minOffset, 1e-6);
const rawSpan = Math.max(
(rawMax - rawMin + padding * 2) * Math.max(verticalExaggeration, 0.1),
1e-6,
);
const naturalHeight =
rawSpan * pixelsPerMeter + CROSS_PAD.top + CROSS_PAD.bottom + AREA_OVERLAY_HEADROOM_PX;
const heightPx = forcedHeightPx ?? Math.max(naturalHeight, CROSS_HEIGHT);
const displaySpan = Math.max(heightPx - CROSS_PAD.top - CROSS_PAD.bottom, 1e-6) / pixelsPerMeter;
const displayMax = elevationMid + displaySpan / 2 + AREA_OVERLAY_HEADROOM_PX / pixelsPerMeter / 2;
return {
sourceSamples,
minOffset,
maxOffset,
elevationMid,
displaySpan,
displayMax,
pixelsPerMeter,
heightPx,
};
}
export function crossCardNaturalHeight(
section: CrossSection,
verticalExaggeration: number,
widthPx: number,
crossHalfWidth?: number,
designElevation?: number,
): number {
return (
crossPlotMetrics(section, verticalExaggeration, widthPx, crossHalfWidth, designElevation)
?.heightPx ?? CROSS_HEIGHT
);
}