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>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
* 횡단은 종/횡 공통 Y스케일을 쓰지 않고 카드마다 자체 1:1 ppm으로 스케일한다.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CrossSection, SectionSample } from "./B06_Section_Api_Fetch";
|
||||
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import {
|
||||
appendCrossAreaBands,
|
||||
type AreaHighlightSetter,
|
||||
@@ -37,7 +37,6 @@ import type { RevetHighlightSetter, RevetKey } from "./B06_Section_UI_Cross_Culv
|
||||
import type { RevetMaterial } from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import {
|
||||
CROSS_HEIGHT,
|
||||
CROSS_PAD,
|
||||
CROSS_WIDTH,
|
||||
type DesignChangeHandler,
|
||||
@@ -48,6 +47,8 @@ import {
|
||||
svgText,
|
||||
validElevation,
|
||||
} from "./B06_Section_UI_Section_Common";
|
||||
import { crossPlotMetrics } from "./B06_Section_UI_Cross_View_Metrics";
|
||||
export { crossCardNaturalHeight } from "./B06_Section_UI_Cross_View_Metrics";
|
||||
|
||||
/**
|
||||
* 횡단 카드 요소. 선택 표시와 면적 강조를 **카드를 다시 만들지 않고** 갈아 끼우는 핸들을 단다
|
||||
@@ -57,96 +58,6 @@ export interface CrossCardElement extends HTMLElement {
|
||||
applySelection?: (selected: boolean, areaKey: CrossAreaKey | null) => void;
|
||||
}
|
||||
|
||||
// 절·성토 값 오버레이(그래프 상단 고정)가 계획고 선을 가리는 드문 경우를 대비해, Y 플롯
|
||||
// 최대값에 더하는 상단 여유(px). 오버레이·라벨은 손대지 않고 데이터를 그만큼 아래로 내린다.
|
||||
// 오버레이가 칩 한 줄에서 표(머리글 + 절토 + 성토 3행)로 바뀌어 그만큼 키웠다.
|
||||
const AREA_OVERLAY_HEADROOM_PX = 58;
|
||||
|
||||
interface CrossPlotMetrics {
|
||||
sourceSamples: SectionSample[];
|
||||
minOffset: number;
|
||||
maxOffset: number;
|
||||
elevationMid: number;
|
||||
displaySpan: number;
|
||||
displayMax: number;
|
||||
pixelsPerMeter: number;
|
||||
heightPx: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 횡단 카드의 X:Y 1:1 스케일 지표를 계산한다.
|
||||
* X pixels-per-meter는 반폭(offset 범위)이 카드 폭을 채우도록 고정하고, 같은 ppm을 Y에도 써서
|
||||
* 형상 왜곡을 없앤다(과장=1 → 정확히 1:1). 카드 높이는 데이터가 요구하는 자연 높이에 최소
|
||||
* `CROSS_HEIGHT`(250px) 바닥을 적용하며, `forcedHeightPx`가 오면 그 높이로 강제한다(같은 행 높이 통일).
|
||||
* 어느 경우든 displaySpan을 실제 plotHeight에 맞춰 되계산하므로 ppm(=1:1)은 그대로 보존되고,
|
||||
* 남는 세로 공간은 elevationMid를 중심으로 대칭 여백이 된다.
|
||||
*/
|
||||
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 hasDesign = designElevation !== undefined && Number.isFinite(designElevation);
|
||||
const elevations = valid.map((sample) => sample.elevation_m);
|
||||
if (hasDesign) elevations.push(designElevation as number);
|
||||
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 exaggeration = Math.max(verticalExaggeration, 0.1);
|
||||
const plotWidth = widthPx - CROSS_PAD.left - CROSS_PAD.right;
|
||||
const xSpan = Math.max(maxOffset - minOffset, 1e-6);
|
||||
const pixelsPerMeter = plotWidth / xSpan;
|
||||
const rawSpan = Math.max((rawMax - rawMin + padding * 2) * exaggeration, 1e-6);
|
||||
// 상단 오버레이 여유(headroom)를 자연 높이에 더해 카드가 그만큼 커지게 한다.
|
||||
const naturalHeight =
|
||||
rawSpan * pixelsPerMeter + CROSS_PAD.top + CROSS_PAD.bottom + AREA_OVERLAY_HEADROOM_PX;
|
||||
// 강제 높이가 있으면 그것을, 없으면 자연 높이에 250px 바닥 적용.
|
||||
const heightPx = forcedHeightPx ?? Math.max(naturalHeight, CROSS_HEIGHT);
|
||||
const plotHeight = Math.max(heightPx - CROSS_PAD.top - CROSS_PAD.bottom, 1e-6);
|
||||
// 실제 plotHeight에 맞춰 표시 표고폭을 되계산 → ppm(1:1) 보존, 여유분은 상하 대칭 여백.
|
||||
const displaySpan = plotHeight / pixelsPerMeter;
|
||||
// 늘린 세로 여유의 절반을 위쪽에 몰아 데이터·계획선을 오버레이 아래로 내린다(라벨 위치 불변).
|
||||
const headroomM = AREA_OVERLAY_HEADROOM_PX / pixelsPerMeter;
|
||||
const displayMax = elevationMid + displaySpan / 2 + headroomM / 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 카드 SVG에 확대·축소 + 드래그 팬 + 더블클릭 원복을 붙인다(E-5).
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user