260723_8
This commit is contained in:
@@ -75,7 +75,7 @@ export function createIrregularStationsSection(
|
||||
root.className = "b05-route__panel-section ui-collapsible";
|
||||
const heading = document.createElement("h3");
|
||||
heading.className = "ui-collapsible__title";
|
||||
heading.textContent = "비정규 측점 (구조물)";
|
||||
heading.textContent = "구조물 배치";
|
||||
const body = document.createElement("div");
|
||||
body.className = "b05-route__panel-body";
|
||||
root.append(heading, body);
|
||||
|
||||
@@ -223,6 +223,7 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
onRadiusChange: (radius) => viewer.markers.updateSelected({ radius_m: radius }),
|
||||
onInputChange: markStale,
|
||||
onIrregularChange: (stations) => applyIrregularStations(stations),
|
||||
onStationDisplayChange: (offset) => profilePanel.setStationDisplay(offset),
|
||||
onIrregularSelect: (station) => {
|
||||
if (selectionSyncing) return;
|
||||
selectionSyncing = true;
|
||||
@@ -309,6 +310,7 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
function renderSections(detail: SectionDetailResponse, routeId?: number): void {
|
||||
currentSectionDetail = detail;
|
||||
profilePanel.render(detail, panel.values().stationInterval ?? undefined, routeId);
|
||||
profilePanel.setStationDisplay(panel.stationDisplayOffset());
|
||||
profilePanel.setIrregularStations(irregularStations);
|
||||
renderStationLines(detail);
|
||||
}
|
||||
@@ -512,7 +514,7 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
mainContent.className = "b05-route__main";
|
||||
mainContent.append(viewer.root, profilePanel.root);
|
||||
const layout = createWorkflowLayout({
|
||||
title: "노선 설계",
|
||||
title: "종단 설계",
|
||||
steps: workflowSteps(),
|
||||
activeStep: 2,
|
||||
leftPanel: panel.root,
|
||||
|
||||
@@ -62,6 +62,8 @@ interface PanelCallbacks {
|
||||
onIrregularChange: (stations: IrregularStation[]) => void;
|
||||
/** 비정규 측점을 목록에서 선택/해제할 때 해당 측점(또는 null). */
|
||||
onIrregularSelect: (station: IrregularStation | null) => void;
|
||||
/** 이어 공사 시작 기준(시작 측점·누가거리 시작)이 바뀔 때. */
|
||||
onStationDisplayChange: (offset: { station: number; cumulative: number }) => void;
|
||||
}
|
||||
|
||||
type WrappedInput = HTMLInputElement & { wrapper: HTMLLabelElement };
|
||||
@@ -255,7 +257,7 @@ export function createRoutePanel(callbacks: PanelCallbacks) {
|
||||
longSampleInterval.wrapper,
|
||||
);
|
||||
|
||||
const gradeLine = section("계획선(시공계획고) 설계");
|
||||
const gradeLine = section("종단 설계 기준");
|
||||
const terrainType = document.createElement("select");
|
||||
terrainType.innerHTML =
|
||||
'<option value="normal">일반지형</option><option value="special">특수지형</option>';
|
||||
@@ -284,6 +286,28 @@ export function createRoutePanel(callbacks: PanelCallbacks) {
|
||||
);
|
||||
gradeLine.body.append(terrainLabel, criteriaNote, gradeAdvanced);
|
||||
|
||||
// 공사 시작점 — 이전 공사에 이어 시공할 때 0측점을 임의 측점/누가거리로 시작 표기한다.
|
||||
// (내부 chainage는 0기준 유지, 측점 라벨·누가거리 "표시"만 이 값만큼 이동.) 기본값 0/0.
|
||||
const startBasis = section("공사 시작점");
|
||||
const startStation = numberField("시작 측점", "0");
|
||||
startStation.step = "1";
|
||||
startStation.min = "0";
|
||||
const startCumulative = numberField("시작 누가거리 (m)", "0");
|
||||
// 2열×2행: 1행 항목명 · 2행 값 입력 / 1열 시작 측점 · 2열 시작 누가거리.
|
||||
const startRow = document.createElement("div");
|
||||
startRow.className = "b05-route__field-row";
|
||||
startRow.append(startStation.wrapper, startCumulative.wrapper);
|
||||
startBasis.body.append(startRow);
|
||||
const stationDisplayOffset = (): { station: number; cumulative: number } => ({
|
||||
station: Math.max(0, Math.round(Number(startStation.value) || 0)),
|
||||
cumulative: Number(startCumulative.value) || 0,
|
||||
});
|
||||
[startStation, startCumulative].forEach((input) =>
|
||||
input.addEventListener("change", () =>
|
||||
callbacks.onStationDisplayChange(stationDisplayOffset()),
|
||||
),
|
||||
);
|
||||
|
||||
// 비정규 측점(구조물 측점) — 측점번호+잔여거리로 추가/수정/삭제. 목록 변경은 Page로 올려
|
||||
// 그래프·테이블·3D에 반영한다. chainage 환산 기준인 측점간격은 실시간 조회한다.
|
||||
const irregular = createIrregularStationsSection({
|
||||
@@ -352,6 +376,7 @@ export function createRoutePanel(callbacks: PanelCallbacks) {
|
||||
conditions.root,
|
||||
sectionOptions.root,
|
||||
gradeLine.root,
|
||||
startBasis.root,
|
||||
irregular.root,
|
||||
result.root,
|
||||
actionRow,
|
||||
@@ -366,6 +391,8 @@ export function createRoutePanel(callbacks: PanelCallbacks) {
|
||||
viewControls,
|
||||
/** 비정규 측점 섹션 API(목록 조회·선택·초기화). 아직 백엔드로 보내지 않는다(프론트 프리뷰). */
|
||||
irregularStations: irregular as IrregularStationsSection,
|
||||
/** 이어 공사 시작 기준(측점번호·누가거리 오프셋) 현재값. */
|
||||
stationDisplayOffset,
|
||||
values(): RoutePanelValues {
|
||||
return {
|
||||
contourInterval: Number(contourInterval.value) || 1,
|
||||
|
||||
@@ -273,6 +273,8 @@ export function createRouteProfilePanel(
|
||||
let stationInterval: number | undefined;
|
||||
let routeId: number | null = null;
|
||||
let irregularStations: IrregularStation[] = [];
|
||||
// 이어 공사 시작 기준 — 측점번호·누가거리 표시 오프셋(내부 chainage는 0기준 유지).
|
||||
let stationDisplay = { station: 0, cumulative: 0 };
|
||||
let base: AlignmentBase | null = null;
|
||||
let alignment: ProfileAlignment | null = null;
|
||||
let store = createProfileEditStore(null, emptyEdits(), () => rebuild());
|
||||
@@ -432,6 +434,7 @@ export function createRouteProfilePanel(
|
||||
entry.chainage_m >= 0 && entry.chainage_m <= maxChainageOf(longitudinal) + 1e-6,
|
||||
),
|
||||
selectedStationId,
|
||||
stationDisplay,
|
||||
onCurveRadiusChange: (curve, radius) =>
|
||||
applyEdits(setCurveRadius(store.edits(), curve, radius ?? 0)),
|
||||
// 값 열 계획고 직접 입력 → 규칙 측점과 동일한 station_offset 파이프라인.
|
||||
@@ -474,6 +477,7 @@ export function createRouteProfilePanel(
|
||||
(axis) => {
|
||||
yAxis = axis;
|
||||
},
|
||||
stationDisplay.station,
|
||||
),
|
||||
);
|
||||
// 가로 스크롤에도 고정되는 sticky Y축 오버레이(SVG와 같은 눈금·불투명 배경으로 값 누출 차단).
|
||||
@@ -576,6 +580,11 @@ export function createRouteProfilePanel(
|
||||
irregularStations = stations;
|
||||
draw();
|
||||
},
|
||||
/** 이어 공사 시작 기준(측점번호·누가거리 오프셋)을 반영해 측점 라벨·누가거리 표시를 옮긴다. */
|
||||
setStationDisplay(next: { station: number; cumulative: number }) {
|
||||
stationDisplay = next;
|
||||
draw();
|
||||
},
|
||||
/**
|
||||
* 특정 chainage의 계획고 편집(station_offset·curve_radii)을 지운다.
|
||||
* 비정규 측점을 옮기거나 지울 때 옛 위치에 남는 편집(유령 변화점)을 청소하는 데 쓴다.
|
||||
|
||||
@@ -18,11 +18,7 @@ import type {
|
||||
ProfileAlignment,
|
||||
} from "./B05_wf2_Route_UI_Profile_Alignment";
|
||||
import { stationLabel } from "../B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_Common";
|
||||
import {
|
||||
irregularLabel,
|
||||
irregularStationId,
|
||||
type IrregularStation,
|
||||
} from "./B05_wf2_Route_UI_IrregularStations";
|
||||
import { irregularStationId, type IrregularStation } from "./B05_wf2_Route_UI_IrregularStations";
|
||||
|
||||
export interface ProfileTableOptions {
|
||||
alignment: ProfileAlignment;
|
||||
@@ -39,8 +35,10 @@ export interface ProfileTableOptions {
|
||||
x: (chainageM: number) => number;
|
||||
/** 규칙 격자 밖 비정규 측점(구조물). 선택된 측점만 값 열로 오버레이한다. */
|
||||
irregularStations?: IrregularStation[];
|
||||
/** 현재 선택된 측점 id. 비정규 측점이면 그 측점의 값 열을 테이블에 겹쳐 보여준다. */
|
||||
/** 현재 선택된 측점 id. 규칙/비정규 측점 모두 그 측점의 값 열을 테이블에 겹쳐 보여준다. */
|
||||
selectedStationId?: string | null;
|
||||
/** 이어 공사 시작 기준 — 측점번호·누가거리 표시를 이만큼 더한다(내부 chainage는 0기준 유지). */
|
||||
stationDisplay?: { station: number; cumulative: number };
|
||||
onCurveRadiusChange: (curve: AlignmentCurve, radiusM: number | null) => void;
|
||||
/** 임의 chainage의 계획고를 delta만큼 조정(비정규 측점 값 열 직접 입력용). */
|
||||
onAdjustStation?: (chainageM: number, deltaM: number) => void;
|
||||
@@ -149,7 +147,19 @@ const SEGMENT_ROWS: SegmentRowSpec[] = [
|
||||
{ label: "구배 S", unit: "% 구간 기울기", cell: (segment) => segment.grade_percent.toFixed(2) },
|
||||
];
|
||||
|
||||
function buildStationRows(alignment: ProfileAlignment, interval: number): StationRowSpec[] {
|
||||
/** 이어 공사 시작 기준을 반영한 측점 표기 — 측점번호에 시작 측점을 더한다(잔여거리는 그대로). */
|
||||
function displayStationLabel(chainageM: number, interval: number, stationOffset: number): string {
|
||||
const base = stationLabel(chainageM, interval); // "X+YY.Y"
|
||||
if (!stationOffset) return base;
|
||||
const [number, remainder] = base.split("+");
|
||||
return `${Number(number) + stationOffset}+${remainder}`;
|
||||
}
|
||||
|
||||
function buildStationRows(
|
||||
alignment: ProfileAlignment,
|
||||
interval: number,
|
||||
display: { station: number; cumulative: number },
|
||||
): StationRowSpec[] {
|
||||
const stations = alignment.stations;
|
||||
return [
|
||||
{
|
||||
@@ -171,7 +181,11 @@ function buildStationRows(alignment: ProfileAlignment, interval: number): Statio
|
||||
cell: (index) => stations[index].plan_elevation_m.toFixed(2),
|
||||
},
|
||||
{ label: "지반고", unit: "m", cell: (index) => stations[index].ground_elevation_m.toFixed(2) },
|
||||
{ label: "누가거리", unit: "m", cell: (index) => stations[index].chainage_m.toFixed(2) },
|
||||
{
|
||||
label: "누가거리",
|
||||
unit: "m",
|
||||
cell: (index) => (stations[index].chainage_m + display.cumulative).toFixed(2),
|
||||
},
|
||||
{
|
||||
label: "거리",
|
||||
unit: "m 전 측점 대비",
|
||||
@@ -180,7 +194,7 @@ function buildStationRows(alignment: ProfileAlignment, interval: number): Statio
|
||||
{
|
||||
label: "측점",
|
||||
unit: "측점번호+잔여거리",
|
||||
cell: (index) => stationLabel(stations[index].chainage_m, interval),
|
||||
cell: (index) => displayStationLabel(stations[index].chainage_m, interval, display.station),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -292,11 +306,6 @@ function buildCurveRows(options: ProfileTableOptions, centers: number[]): HTMLEl
|
||||
if (curve.omitted) cell.classList.add("is-omitted");
|
||||
});
|
||||
}
|
||||
// 선택된 규칙 측점 열의 곡선 셀도 함께 하이라이트한다.
|
||||
if (station.station_id && station.station_id === options.selectedStationId) {
|
||||
lengthCell.classList.add("is-selected");
|
||||
radiusCell.classList.add("is-selected");
|
||||
}
|
||||
placeCell(lengthRow, centers[index], lengthCell);
|
||||
placeCell(radiusRow, centers[index], radiusCell);
|
||||
});
|
||||
@@ -352,26 +361,27 @@ function interpolateSample(
|
||||
type CellValue = { text: string } | { left: string; right: string };
|
||||
|
||||
/**
|
||||
* 선택된 비정규 측점의 **값 열 오버레이**. 규칙 측점 열과 같은 12행 순서로 값(계획고·지반고·
|
||||
* 누가거리·측점 등, 계획선 샘플 보간)을 쌓아 테이블 위에 겹친다. 구조물 이름은 사이드바 입력에
|
||||
* 있으므로 여기엔 넣지 않고, 세로 점선도 두지 않는다.
|
||||
* 선택된 측점(규칙·비정규 공용)의 **값 열 오버레이**. 12행 순서 그대로 값(계획고·지반고·누가거리·
|
||||
* 측점·구배·곡선 등)을 쌓아 테이블 위에 겹친다. 규칙 측점을 눌러도 비정규와 **같은 오버레이 방식**으로
|
||||
* 눈에 잘 들어오게 한다. `centerX`는 호출부가 정한다 — 종점처럼 셀이 우측 이동된 규칙 측점은 그 이동된
|
||||
* 중심을, 비정규 측점은 `x(chainage)`를 넘긴다.
|
||||
*/
|
||||
function buildIrregularColumn(
|
||||
station: IrregularStation,
|
||||
function buildSelectedColumn(
|
||||
chainage: number,
|
||||
alignment: ProfileAlignment,
|
||||
centerX: number,
|
||||
cellWidth: number,
|
||||
interval: number,
|
||||
display: { station: number; cumulative: number },
|
||||
onAdjustStation?: (chainageM: number, deltaM: number) => void,
|
||||
): HTMLElement {
|
||||
const plan = interpolateSample(alignment.samples, station.chainage_m, "elevation_m");
|
||||
const ground = interpolateSample(alignment.samples, station.chainage_m, "ground_elevation_m");
|
||||
const plan = interpolateSample(alignment.samples, chainage, "elevation_m");
|
||||
const ground = interpolateSample(alignment.samples, chainage, "ground_elevation_m");
|
||||
const cut = plan !== null && ground !== null && ground - plan > 0.005 ? ground - plan : null;
|
||||
const fill = plan !== null && ground !== null && plan - ground > 0.005 ? plan - ground : null;
|
||||
|
||||
// 구배(L/H/S): 이 측점이 구간 안에 있으면 그 구간 값을 그대로(중복이어도) 표기하고,
|
||||
// 변화점이라 좌우 구간이 갈리면 세로 구분선으로 셀을 반 나눠 좌/우 값을 각각 넣는다.
|
||||
const chainage = station.chainage_m;
|
||||
const inside = alignment.segments.find(
|
||||
(segment) => segment.from_m + 1e-6 < chainage && chainage < segment.to_m - 1e-6,
|
||||
);
|
||||
@@ -384,6 +394,14 @@ function buildIrregularColumn(
|
||||
return left && right ? { left, right } : { text: left || right || "" };
|
||||
};
|
||||
const curve = alignment.curves.find((entry) => Math.abs(entry.chainage_m - chainage) < 0.01);
|
||||
// 거리: 바로 앞 측점(계획선 측점)까지의 간격.
|
||||
const previous = alignment.stations
|
||||
.filter((row) => row.chainage_m < chainage - 1e-6)
|
||||
.reduce<number | null>(
|
||||
(acc, row) => (acc === null ? row.chainage_m : Math.max(acc, row.chainage_m)),
|
||||
null,
|
||||
);
|
||||
const distance = previous !== null ? chainage - previous : null;
|
||||
|
||||
// 테이블 행 순서(구배 3 · 측점값 7 · 곡선 2)와 정확히 같게 채운다. 없는 값은 공백.
|
||||
// `plan: true`인 계획고 행만 직접 입력 셀로 만든다(값 열은 오버레이라 실제 12행 격자는 불변).
|
||||
@@ -395,9 +413,9 @@ function buildIrregularColumn(
|
||||
{ value: { text: fill !== null ? fill.toFixed(2) : "" }, modifier: "fill" }, // 성토고
|
||||
{ value: { text: plan !== null ? plan.toFixed(2) : "" }, modifier: "plan", plan: true }, // 계획고
|
||||
{ value: { text: ground !== null ? ground.toFixed(2) : "" }, modifier: "" }, // 지반고
|
||||
{ value: { text: station.chainage_m.toFixed(2) }, modifier: "" }, // 누가거리
|
||||
{ value: { text: "" }, modifier: "" }, // 거리
|
||||
{ value: { text: stationLabel(station.chainage_m, interval) }, modifier: "" }, // 측점
|
||||
{ value: { text: (chainage + display.cumulative).toFixed(2) }, modifier: "" }, // 누가거리
|
||||
{ value: { text: distance !== null ? distance.toFixed(2) : "" }, modifier: "" }, // 거리
|
||||
{ value: { text: displayStationLabel(chainage, interval, display.station) }, modifier: "" }, // 측점
|
||||
{ value: { text: curve ? curve.l_m.toFixed(2) : "" }, modifier: "" }, // 곡선 L
|
||||
{ value: { text: curve ? curve.r_m.toFixed(1) : "" }, modifier: "" }, // 곡선 R
|
||||
];
|
||||
@@ -410,7 +428,7 @@ function buildIrregularColumn(
|
||||
`b05-profile-table__irregular-col-cell${row.modifier ? ` is-${row.modifier}` : ""}`,
|
||||
);
|
||||
if (row.plan && onAdjustStation && plan !== null) {
|
||||
// 계획고 직접 입력 → (입력값 − 현재 계획고) delta로 규칙 측점과 동일한 station_offset 반영.
|
||||
// 계획고 직접 입력 → (입력값 − 현재 계획고) delta로 station_offset 반영(규칙·비정규 공용).
|
||||
const input = document.createElement("input");
|
||||
input.type = "number";
|
||||
input.step = "any";
|
||||
@@ -419,7 +437,7 @@ function buildIrregularColumn(
|
||||
input.title = `계획고 직접 입력 (현재 ${plan.toFixed(2)}m)`;
|
||||
input.addEventListener("change", () => {
|
||||
const target = Number.parseFloat(input.value);
|
||||
if (Number.isFinite(target)) onAdjustStation(station.chainage_m, target - plan);
|
||||
if (Number.isFinite(target)) onAdjustStation(chainage, target - plan);
|
||||
});
|
||||
cell.append(input);
|
||||
} else if ("left" in row.value) {
|
||||
@@ -434,12 +452,15 @@ function buildIrregularColumn(
|
||||
}
|
||||
column.append(cell);
|
||||
});
|
||||
column.title = `비정규 측점 ${irregularLabel(station)} · ${station.chainage_m.toFixed(2)}m`;
|
||||
column.title = `${displayStationLabel(chainage, interval, display.station)} · ${(
|
||||
chainage + display.cumulative
|
||||
).toFixed(2)}m`;
|
||||
return column;
|
||||
}
|
||||
|
||||
export function createProfileTable(options: ProfileTableOptions): HTMLElement {
|
||||
const { alignment, stationInterval, width, height, cellWidth, labelWidth, rowCount, x } = options;
|
||||
const display = options.stationDisplay ?? { station: 0, cumulative: 0 };
|
||||
const table = element("div", "b05-profile-table");
|
||||
const rowHeight = height / Math.max(rowCount, 1);
|
||||
const fontSize = fitFontSize(rowHeight, cellWidth);
|
||||
@@ -456,36 +477,50 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement {
|
||||
table.style.setProperty("--b05-table-label-font", `${Math.max(8, labelFontSize)}px`);
|
||||
|
||||
table.append(...buildSegmentRows(alignment, x, fontSize, rowHeight));
|
||||
buildStationRows(alignment, stationInterval).forEach((spec, index) => {
|
||||
buildStationRows(alignment, stationInterval, display).forEach((spec, index) => {
|
||||
const row = createRow(
|
||||
`${spec.modifier ? `is-${spec.modifier}` : ""}${index === 0 ? " is-group-start" : ""}`,
|
||||
spec.label,
|
||||
spec.unit,
|
||||
);
|
||||
// 값이 없는 측점(절토고/성토고 중 한쪽)도 빈 칸을 만들어야 세로 구분선이 끊기지 않는다.
|
||||
alignment.stations.forEach((station, stationIndex) => {
|
||||
alignment.stations.forEach((_station, stationIndex) => {
|
||||
const cell = element("span", "b05-profile-table__cell", spec.cell(stationIndex));
|
||||
// 선택된 규칙 측점은 그 열의 값 셀을 하이라이트한다(비정규 측점의 값 열 강조와 통일).
|
||||
if (station.station_id && station.station_id === options.selectedStationId) {
|
||||
cell.classList.add("is-selected");
|
||||
}
|
||||
placeCell(row, centers[stationIndex], cell);
|
||||
});
|
||||
table.append(row);
|
||||
});
|
||||
table.append(...buildCurveRows(options, centers));
|
||||
// 선택된 비정규 측점만 값 열로 오버레이한다(세로 점선·구조물 태그 없음).
|
||||
|
||||
// 선택된 측점(규칙·비정규 공용)을 **값 열 오버레이**로 강조한다.
|
||||
// 규칙 측점은 종점 이동을 반영한 `centers[index]`, 비정규 측점은 `x(chainage)`를 중심으로 쓴다.
|
||||
const selectedIrregular = options.irregularStations?.find(
|
||||
(entry) => irregularStationId(entry.id) === options.selectedStationId,
|
||||
);
|
||||
const selectedRegularIndex = options.selectedStationId
|
||||
? alignment.stations.findIndex((row) => row.station_id === options.selectedStationId)
|
||||
: -1;
|
||||
if (selectedIrregular) {
|
||||
table.append(
|
||||
buildIrregularColumn(
|
||||
selectedIrregular,
|
||||
buildSelectedColumn(
|
||||
selectedIrregular.chainage_m,
|
||||
alignment,
|
||||
x(selectedIrregular.chainage_m),
|
||||
cellWidth,
|
||||
stationInterval,
|
||||
display,
|
||||
options.onAdjustStation,
|
||||
),
|
||||
);
|
||||
} else if (selectedRegularIndex >= 0) {
|
||||
table.append(
|
||||
buildSelectedColumn(
|
||||
alignment.stations[selectedRegularIndex].chainage_m,
|
||||
alignment,
|
||||
centers[selectedRegularIndex],
|
||||
cellWidth,
|
||||
stationInterval,
|
||||
display,
|
||||
options.onAdjustStation,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -233,6 +233,13 @@
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
/* 2열 그리드 — 각 열은 항목명(위)+값 입력(아래). '공사 시작점' 등 나란한 입력에 쓴다. */
|
||||
.b05-route__field-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.b05-route__contour-row .b05-route__field {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -555,16 +562,6 @@
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* 선택된 규칙 측점 열의 값·곡선 셀 하이라이트(비정규 측점 값 열 강조와 같은 색). */
|
||||
.b05-profile-table__cell.is-selected,
|
||||
.b05-profile-table__curve.is-selected {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--color-royal-amethyst, rgb(109 40 217)) 14%,
|
||||
var(--color-surface)
|
||||
);
|
||||
}
|
||||
|
||||
.b05-profile-table__row.is-cut .b05-profile-table__cell {
|
||||
color: rgb(220 38 38);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,14 @@ import {
|
||||
type YScaleOptions,
|
||||
} from "./B06_wf3_ProfileCross_UI_Section_Common";
|
||||
|
||||
/** 측점 라벨의 측점번호에 시작 측점 오프셋을 더한다(잔여거리는 그대로). */
|
||||
function offsetStationLabel(chainageM: number, interval: number, stationOffset: number): string {
|
||||
const base = stationLabel(chainageM, interval);
|
||||
if (!stationOffset) return base;
|
||||
const [number, remainder] = base.split("+");
|
||||
return `${Number(number) + stationOffset}+${remainder}`;
|
||||
}
|
||||
|
||||
export function longitudinalMinimumWidth(
|
||||
data: LongitudinalSection,
|
||||
configuredStationInterval?: number,
|
||||
@@ -96,6 +104,8 @@ export function createLongitudinalProfile(
|
||||
* 오버레이를 그릴 때 SVG와 **같은 Y-스케일**을 공유하려고 쓴다. B06은 넘기지 않는다.
|
||||
*/
|
||||
onYAxis?: (axis: { padLeft: number; ticks: Array<{ y: number; label: string }> }) => void,
|
||||
/** 이어 공사 시작 측점 오프셋. 측점 라벨의 측점번호에 이만큼 더한다(B05 표시용, 기본 0). */
|
||||
stationNumberOffset = 0,
|
||||
): HTMLElement {
|
||||
const samples = data.samples.filter(validElevation);
|
||||
if (samples.length < 2) return emptyView(L("B06_Profile_View_NoLongitudinal"));
|
||||
@@ -215,7 +225,7 @@ export function createLongitudinalProfile(
|
||||
y2: heightPx - LONG_PAD.bottom + 8,
|
||||
class: `b06-chart__station-line b06-chart__station-line--${selected ? "selected" : station.kind}`,
|
||||
}),
|
||||
svgText(stationLabel(station.chainage_m, stationInterval), {
|
||||
svgText(offsetStationLabel(station.chainage_m, stationInterval, stationNumberOffset), {
|
||||
x: stationX,
|
||||
y: heightPx - 23,
|
||||
"text-anchor": "middle",
|
||||
|
||||
@@ -725,7 +725,7 @@ export const ui_locales = {
|
||||
B05_Route_Solve_Failed: ["경로 탐색에 실패했습니다.", "Route solve failed."],
|
||||
B05_Route_Confirm_Success: ["경로를 확정했습니다.", "Route confirmed."],
|
||||
B05_Route_Confirm_Failed: ["경로 확정에 실패했습니다.", "Route confirm failed."],
|
||||
B05_Route_Group_SectionOptions: ["측점·횡단 옵션", "Station & Cross Options"],
|
||||
B05_Route_Group_SectionOptions: ["측점 및 샘플링 설정", "Station & Sampling Settings"],
|
||||
B05_Route_Field_StationInterval: ["측점 간격(m)", "Station interval (m)"],
|
||||
B05_Route_Field_CrossHalfWidth: ["횡단 반폭(m)", "Cross half-width (m)"],
|
||||
B05_Route_Field_CrossSample: ["횡단 샘플 간격(m)", "Cross sample interval (m)"],
|
||||
|
||||
@@ -346,8 +346,11 @@
|
||||
.ui-collapsible__title::after {
|
||||
content: "▾";
|
||||
flex: 0 0 auto;
|
||||
margin-block: -0.4em;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.8em;
|
||||
/* 레이아웃 허용 범위 내에서 최대한 크게(제목 글자의 약 1.7배). 음수 margin으로 행 높이는 유지. */
|
||||
font-size: 1.7em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.ui-collapsible.is-collapsed > .ui-collapsible__title::after {
|
||||
|
||||
Reference in New Issue
Block a user