feat(B05): 유토곡선 총괄값을 종단 상단줄로 — 곡선을 접어도 남게
2026-09-06 사용자 지시. 요약 막대가 유토곡선 오버레이 안에 있어 패널을 접으면 같이 사라졌는데, 계획선을 만지며 봐야 하는 값이라 상단줄로 올림. - 상단줄 「최대 기울기」 오른쪽에 「누가토량」 추가. 값은 유토곡선 계산 결과의 마지막 누계이며 토량분배 켜짐과 무관하다. 음수(부족)면 경고색. - 세부(절토 자연·다짐, 성토, 잉여/부족)는 그 항목 툴팁으로 내림 — 예전 요약 막대가 세부를 툴팁에 두던 방식 그대로. - 유토곡선 오버레이의 요약 막대 행 삭제. 비면 접히게 해 곡선이 그만큼 넓게 씀. - 총괄값은 패널이 접혀 있어도 계산해 넘김(draw 앞단에서 산출). - 상단줄 높이 24 → 28px. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,12 @@
|
||||
|
||||
import type { ProfileAlignment } from "./B05_Profile_UI_Profile_Alignment";
|
||||
import type { MinCoverViolation } from "./B05_Profile_UI_Profile_MinCover";
|
||||
import type { MassHaulSummaryValues } from "./B05_Profile_UI_Profile_MassHaul";
|
||||
|
||||
/** 누가토량 표기 — 천 단위 구분 + 소수점 1자리(유토곡선 요약과 같은 규칙). */
|
||||
function volume(value: number): string {
|
||||
return `${value.toLocaleString("ko-KR", { minimumFractionDigits: 1, maximumFractionDigits: 1 })}㎥`;
|
||||
}
|
||||
|
||||
export interface BalanceBarParams {
|
||||
/** 표시줄 컨테이너. 그릴 때마다 통째로 갈아 끼운다. */
|
||||
@@ -36,6 +42,9 @@ export interface BalanceBarParams {
|
||||
minCoverViolations?: MinCoverViolation[];
|
||||
/** 요약줄 **오른쪽 끝**에 붙이는 묶음(줌·Y레인지 조작구) — 2026-09-04. */
|
||||
trailing?: HTMLElement;
|
||||
/** 유토곡선 총괄값 — 「최대 기울기」 오른쪽에 적는다(2026-09-06 사용자 지시).
|
||||
* 유토곡선 패널을 접어도 이 값은 남아야 계획선을 만지며 볼 수 있다. */
|
||||
massHaul?: MassHaulSummaryValues | null;
|
||||
/** [초기선 복원] — 편집·비정규 측점을 모두 지운다. */
|
||||
onResetAll: () => void;
|
||||
}
|
||||
@@ -77,6 +86,13 @@ export function renderBalanceBar(params: BalanceBarParams): void {
|
||||
// 절·성토 불균형은 2026-09-03 사용자 지시로 뺐다. 이 값은 **종단 기준**이었다 —
|
||||
// 계획선과 지반선 사이 세로 면적(㎡)의 절·성토 비이지 실제 토량이 아니다. 의미 있는
|
||||
// 균형은 횡단 단면적을 쌓아 부피로 내는 하단 유토곡선 요약(㎥)이고, 그쪽이 이미 있다.
|
||||
// 유토곡선 총괄값 — 「최대 기울기」 **바로 오른쪽**(2026-09-06 사용자 지시). 곡선을 접어도
|
||||
// 남는다. 세부 내역(절토·성토·잉여/부족)은 이 항목의 툴팁으로 내렸다 — 예전 요약 막대가
|
||||
// 세부를 툴팁에 두던 방식 그대로다.
|
||||
const mass = params.massHaul;
|
||||
if (mass) {
|
||||
entries.push(["누가토량", volume(mass.finalM3), mass.finalM3 < 0 ? "over" : undefined]);
|
||||
}
|
||||
// 필요한 곳이 없으면 적지 않는다 — "0곳"은 화면 폭만 먹는다.
|
||||
if (curvesNeeded) entries.push(["종단곡선 필요", `${curvesNeeded} 곳`, "over"]);
|
||||
// 횡단배수 최소고 표시는 2026-09-02 사용자 지시로 삭제했다. 편집 차단(강제)은
|
||||
@@ -91,6 +107,13 @@ export function renderBalanceBar(params: BalanceBarParams): void {
|
||||
item.append(caption, document.createTextNode(value));
|
||||
// 상한 초과 구간의 내역은 별도 경고 칩 대신 이 항목의 툴팁으로 붙인다 —
|
||||
// 같은 사실을 두 번 적지 않는다(2026-08-19 재편).
|
||||
if (label === "누가토량" && mass) {
|
||||
item.title = [
|
||||
`절토(자연) ${volume(mass.cutNaturalM3)} · 절토(다짐) ${volume(mass.cutCompactedM3)}`,
|
||||
`성토(다짐) ${volume(mass.fillCompactedM3)}`,
|
||||
mass.shortageM3 > 0 ? `부족 ${volume(mass.shortageM3)}` : `잉여 ${volume(mass.surplusM3)}`,
|
||||
].join("\n");
|
||||
}
|
||||
if (label === "최대 기울기" && violations.length) {
|
||||
item.title = violations
|
||||
.map(
|
||||
|
||||
@@ -44,7 +44,6 @@ import { resetBalloonOffsets } from "@util/common_util_mass_haul_balance_view";
|
||||
import {
|
||||
createMassHaulChart,
|
||||
createMassHaulLegend,
|
||||
createMassHaulSummary,
|
||||
createMassHaulWindowState,
|
||||
MASS_HAUL_MIN_HEIGHT,
|
||||
scheduleMassHaulSettle,
|
||||
@@ -180,7 +179,28 @@ function readVisible(): Set<string> {
|
||||
/**
|
||||
* @param onChanged 펼침·범례 토글·높이 조절로 다시 그려야 할 때 호출된다(패널 전체 redraw).
|
||||
*/
|
||||
export function createRouteMassHaulPanel(onChanged: () => void): RouteMassHaulPanel {
|
||||
/**
|
||||
* 종단 상단줄이 **접힌 상태에서도** 보여 주는 유토곡선 총괄값(2026-09-06 사용자 지시).
|
||||
* 곡선 자체는 접으면 사라져도 이 값은 계획선을 만지며 계속 봐야 한다.
|
||||
*/
|
||||
export interface MassHaulSummaryValues {
|
||||
/** 노선 끝 누가토량(㎥) — 양수 잉여, 음수 부족. */
|
||||
finalM3: number;
|
||||
/** 절토(자연상태) 합계(㎥). */
|
||||
cutNaturalM3: number;
|
||||
/** 절토를 다짐으로 환산한 값(㎥). */
|
||||
cutCompactedM3: number;
|
||||
/** 성토(다짐) 합계(㎥). */
|
||||
fillCompactedM3: number;
|
||||
surplusM3: number;
|
||||
shortageM3: number;
|
||||
}
|
||||
|
||||
export function createRouteMassHaulPanel(
|
||||
onChanged: () => void,
|
||||
/** 총괄값이 새로 나올 때마다 부른다 — 종단 상단줄이 받아 적는다. */
|
||||
onSummary?: (summary: MassHaulSummaryValues | null) => void,
|
||||
): RouteMassHaulPanel {
|
||||
// 손잡이는 다른 패널과 **같은 양식**의 표준 삼각형 손잡이 하나만 쓴다(2026-08-04 사용자
|
||||
// 지시 — 예전 풀폭 바 + "유토곡선" 캡션은 다른 패널들과 모양이 달랐다). 무엇의 손잡이인지는
|
||||
// 툴팁으로 밝힌다.
|
||||
@@ -190,7 +210,8 @@ export function createRouteMassHaulPanel(onChanged: () => void): RouteMassHaulPa
|
||||
handle.append(handleControl.root);
|
||||
handleControl.root.setAttribute("aria-label", "유토곡선 패널");
|
||||
|
||||
// 오버레이 뼈대 — 위 경계 리사이저 + 요약 막대 + 가로 스크롤러 + 범례.
|
||||
// 오버레이 뼈대 — 위 경계 리사이저 + 안내 문구 자리 + 가로 스크롤러 + 범례.
|
||||
// (요약 막대는 2026-09-06 종단 상단줄로 올라갔다.)
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "b05-profile__masshaul-overlay";
|
||||
const bar = document.createElement("div");
|
||||
@@ -316,11 +337,39 @@ export function createRouteMassHaulPanel(onChanged: () => void): RouteMassHaulPa
|
||||
/** 마지막으로 그린 입력 — 이동이 아직 안 끝났으면 다음 프레임에 이걸로 한 번 더 그린다. */
|
||||
let lastParams: RouteMassHaulDrawParams | null = null;
|
||||
|
||||
/** 총괄값만 따로 낸다 — 패널을 접어도 상단줄이 값을 잃지 않게(2026-09-06). */
|
||||
function reportSummary(params: RouteMassHaulDrawParams): void {
|
||||
if (!onSummary) return;
|
||||
if (!context || params.pendingRecalc) return;
|
||||
const series = computeMassHaulSeries(
|
||||
params.longitudinal,
|
||||
params.crossSections,
|
||||
context.conversion,
|
||||
context.naturalSpoilMinSlope,
|
||||
);
|
||||
if (!series.length) return onSummary(null);
|
||||
const visible = readVisible();
|
||||
const picked = series.find((entry) => visible.has(entry.key)) ?? series[0];
|
||||
const result = picked.result;
|
||||
const cut = result.cut_natural_m3;
|
||||
const points = result.points;
|
||||
onSummary({
|
||||
finalM3: points.length ? points[points.length - 1].cumulative_volume_m3 : 0,
|
||||
cutNaturalM3: cut.soil + cut.ripping_rock + cut.blasting_rock,
|
||||
cutCompactedM3: result.cut_compacted_m3,
|
||||
fillCompactedM3: result.fill_compacted_m3,
|
||||
surplusM3: result.surplus_m3,
|
||||
shortageM3: result.shortage_m3,
|
||||
});
|
||||
}
|
||||
|
||||
function draw(params: RouteMassHaulDrawParams): void {
|
||||
lastParams = params;
|
||||
clearSelection = params.onClearSelection;
|
||||
overlay.hidden = !open;
|
||||
syncHandlePosition();
|
||||
// 곡선을 그리기 전에 총괄값부터 낸다 — 접혀 있어도 상단줄에는 값이 서야 한다.
|
||||
reportSummary(params);
|
||||
if (!open) return;
|
||||
if (!context) {
|
||||
note("토량환산계수를 불러오지 못해 유토곡선을 계산할 수 없습니다.");
|
||||
@@ -361,8 +410,8 @@ export function createRouteMassHaulPanel(onChanged: () => void): RouteMassHaulPa
|
||||
banded && visible.has(MASS_HAUL_BALANCE_KEY)
|
||||
? computeHaulPlan(banded.result, context.haulLimits)
|
||||
: null;
|
||||
const summarySeries = banded ?? series[0];
|
||||
bar.append(createMassHaulSummary(summarySeries, haulPlan));
|
||||
// 요약 막대는 2026-09-06 사용자 지시로 뺐다 — 총괄값은 종단 상단줄이 늘 보여 주고,
|
||||
// 이 자리는 곡선이 넓게 쓴다. `bar` 는 안내 문구 자리로만 남는다.
|
||||
|
||||
// 곡선 몫 = 스크롤러의 **안쪽 높이**(clientHeight = 가로 스크롤바 제외). 오버레이 전체
|
||||
// 높이로 잡으면 스크롤바가 곡선 바닥(Y축 -200 라벨)을 덮는다(2026-08-04 사용자 보고).
|
||||
|
||||
@@ -46,6 +46,7 @@ import { structureGroupMenuItems } from "./B05_Profile_UI_Profile_Structures";
|
||||
import { configureBalloonOffsets } from "@util/common_util_mass_haul_balance_view";
|
||||
import {
|
||||
createRouteMassHaulPanel,
|
||||
type MassHaulSummaryValues,
|
||||
MASSHAUL_MIN_HEIGHT,
|
||||
type RouteMassHaulContext,
|
||||
} from "./B05_Profile_UI_Profile_MassHaul";
|
||||
@@ -144,7 +145,14 @@ export function createRouteProfilePanel(
|
||||
if (subPanelDragging) scheduleLightSync();
|
||||
else draw();
|
||||
};
|
||||
const massHaul = createRouteMassHaulPanel(subPanelChanged);
|
||||
/** 유토곡선 총괄값 — 상단줄이 쓴다. 패널을 접어도 값이 남게 여기에 들고 있는다. */
|
||||
let massHaulSummary: MassHaulSummaryValues | null = null;
|
||||
const massHaul = createRouteMassHaulPanel(subPanelChanged, (summary) => {
|
||||
const before = massHaulSummary;
|
||||
massHaulSummary = summary;
|
||||
// 값이 그대로면 상단줄을 다시 그리지 않는다 — 계획고를 연속으로 누를 때 헛일이 된다.
|
||||
if (before?.finalM3 !== summary?.finalM3) renderBalance();
|
||||
});
|
||||
bodyWrap.append(massHaul.overlay, massHaul.handle);
|
||||
// 오버레이의 가로 스크롤을 종단 스크롤러와 양방향 동기화 — 측점 세로선 정렬 유지.
|
||||
massHaul.attachScrollSync(body);
|
||||
@@ -329,6 +337,7 @@ export function createRouteProfilePanel(
|
||||
tools: tools.render(),
|
||||
trailing: profileZoom.bar,
|
||||
alignment,
|
||||
massHaul: massHaulSummary,
|
||||
legacyAlignment: !!detail && hasLegacyAlignment(detail.longitudinal),
|
||||
edited: store.edited(),
|
||||
hasIrregularStations: irregularStations.length > 0,
|
||||
|
||||
@@ -509,7 +509,9 @@
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--spacing-16);
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
/* 유토곡선 총괄값이 이 줄로 올라와(2026-09-06) 항목이 늘었다 — 두 픽셀 높여 글자가
|
||||
위아래로 눌리지 않게 한다. */
|
||||
height: 28px;
|
||||
padding: 0 var(--spacing-8);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
@@ -70,6 +70,12 @@
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
/* 요약 막대는 2026-09-06 종단 상단줄로 올라갔다 — 이 자리는 안내 문구가 있을 때만 쓴다.
|
||||
비어 있으면 테두리 한 줄도 남기지 않고 접어 곡선이 그만큼 넓게 쓴다. */
|
||||
.b05-profile__masshaul-bar:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.b05-profile__masshaul-bar {
|
||||
display: flex;
|
||||
flex: none;
|
||||
|
||||
Reference in New Issue
Block a user