- 길: POST analyze2d(뒤에서 · 일꾼 프로세스 병렬 · 바뀐 측점만) · GET progress · result2d · section · corridor3d · PUT representative - 입력 = E01 복사본(측점 · 횡단 · DTM 넓힘) + E02 지반 조건 · 엔진 = sub4 analyze_station · 벽 = 다음 일감(532-4) - 화면: 측점 목록(조건 · 정렬 · N.G 만 · 대표 단면) · 횡단(지층 · 지하수선 · 임계면 · 후보 · 등안전율 지도 · 중심 격자 · 절편) · 결과 · 절편표 · 자유물체도 · 절편력 그래프 · 평면 띠 · 3D 보기 - 계산 = logic/E03_StabSection_Logic.ts · 시험 test_532_3_e03_section.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.4 KiB
TypeScript
90 lines
3.4 KiB
TypeScript
/* =============================================================================
|
|
* E03_StabSection_UI_Plan.ts
|
|
* E03 위 평면 위험 띠(PLAN 532-3) — 측점 중심을 이은 노선을 그 조건 판정 색으로(이웃 측점 사이
|
|
* 가운데서 끊음 · 북쪽 위 · 같은 축척) · 고른 측점 표시 · 누르면 가장 가까운 측점.
|
|
* 범례 = 판정마다 아이콘 + 글 + 측점 수. 계산은 logic `bandSegments` · `planFit`.
|
|
* ========================================================================== */
|
|
|
|
import { el } from "@ui/ui_template_elements";
|
|
import { t, type LocaleKey } from "@ui/ui_template_locale";
|
|
import {
|
|
bandSegments,
|
|
planFit,
|
|
verdictOf,
|
|
type Condition,
|
|
type Point,
|
|
type StationRow,
|
|
} from "./logic/E03_StabSection_Logic";
|
|
import { svgEl } from "./E03_StabSection_UI_Section";
|
|
|
|
const H = 96;
|
|
const PAD = 10;
|
|
|
|
export function buildPlan(
|
|
rows: StationRow[],
|
|
condition: Condition,
|
|
active: string | null,
|
|
onSelect: (key: string) => void,
|
|
): HTMLElement {
|
|
const box = el("div", { className: "e03-plan e03-card" });
|
|
const head = el("div", { className: "e03-card__head" });
|
|
head.append(el("h3", { className: "e03-card__title", text: t("E03_Plan") }));
|
|
const legend = el("div", { className: "e03-legend", attrs: { "data-role": "plan-legend" } });
|
|
const counts = { ok: 0, ng: 0, none: 0 };
|
|
rows.forEach((r) => (counts[verdictOf(r, condition)] += 1));
|
|
for (const v of ["ng", "ok", "none"] as const) {
|
|
if (!counts[v]) continue;
|
|
const item = el("span", { className: `e03-verdict e03-verdict--${v}` });
|
|
item.append(`${t(`E03_Verdict_${v}` as LocaleKey)} ${counts[v]}`);
|
|
legend.append(item);
|
|
}
|
|
head.append(legend);
|
|
box.append(head);
|
|
|
|
const host = el("div");
|
|
box.append(host);
|
|
const draw = (): void => {
|
|
const W = Math.max(host.clientWidth, 300);
|
|
const segs = bandSegments(rows, condition);
|
|
const all: Point[] = segs.flatMap((s) => s.points);
|
|
const svg = svgEl("svg", { viewBox: `0 0 ${W} ${H}`, "data-role": "plan" });
|
|
if (all.length) {
|
|
const fit = planFit(all, W, H, PAD);
|
|
const scr = (p: Point): Point => [fit.k * p[0] + fit.tx, -fit.k * p[1] + fit.ty];
|
|
for (const seg of segs) {
|
|
svg.append(
|
|
svgEl("polyline", {
|
|
points: seg.points.map((p) => scr(p).join(",")).join(" "),
|
|
class: `e03-plan__seg e03-plan__seg--${seg.verdict}`,
|
|
"data-verdict": seg.verdict,
|
|
}),
|
|
);
|
|
}
|
|
const centers = rows.filter((r) => r.center && r.center[0] !== null && r.center[1] !== null);
|
|
const picked = centers.find((r) => r.key === active);
|
|
if (picked) {
|
|
const [x, y] = scr([picked.center![0]!, picked.center![1]!]);
|
|
svg.append(
|
|
svgEl("circle", { cx: x, cy: y, r: 6, class: "e03-plan__pick", "data-key": picked.key }),
|
|
);
|
|
}
|
|
svg.addEventListener("click", (ev) => {
|
|
const box2 = svg.getBoundingClientRect();
|
|
const px = ((ev.clientX - box2.left) / box2.width) * W;
|
|
const py = ((ev.clientY - box2.top) / box2.height) * H;
|
|
let best: StationRow | null = null;
|
|
let dist = Infinity;
|
|
for (const r of centers) {
|
|
const [x, y] = scr([r.center![0]!, r.center![1]!]);
|
|
const d = Math.hypot(x - px, y - py);
|
|
if (d < dist) [best, dist] = [r, d];
|
|
}
|
|
if (best && dist < 30) onSelect(best.key);
|
|
});
|
|
}
|
|
host.replaceChildren(svg);
|
|
};
|
|
new ResizeObserver(draw).observe(host);
|
|
return box;
|
|
}
|