feat(b06): 횡단 카드 — 단추 줄 빈자리 누름 = 카드 고름 · 중심고 = Y축 라벨 둥근 상자(눈금과 같은 글자 · 소수 첫째 자리 · 축선 앞에서 멈춤 · 카드 · 상세 모달) · 아래 띠 중심고 글 뺌 · 암 경계 왼쪽 · 카드 = 내용 높이(아래 여유 없앰 · 빈 띠 높이 그대로) (318-1~3 · 요구 215 · 216)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6BNBFXFndbqYxu6sZuknK
This commit is contained in:
@@ -26,6 +26,8 @@ export interface CrossAxesOptions {
|
||||
/** 표고 과장 기준점과 배율 — 화면 표고 → 실제 표고 환산에 쓴다. */
|
||||
elevationMid: number;
|
||||
exaggeration: number;
|
||||
/** 중심고(실제 표고) — Y축 라벨 자리에 둥근 상자로(318-2 · 소수 첫째 자리). 없으면 안 그림. */
|
||||
centerZ?: number | null;
|
||||
}
|
||||
|
||||
/** X 눈금 라벨("-12" 등)이 겹치지 않는 최소 간격(px). */
|
||||
@@ -36,6 +38,11 @@ const X_TARGET_TICKS = 7;
|
||||
/** 표고(Y) 눈금 최소 간격(m) — 1m 아래 단위는 표기하지 않는다. */
|
||||
const MIN_ELEVATION_STEP_M = 1;
|
||||
const Y_TARGET_TICKS = 10;
|
||||
/** Y 눈금 글 오른끝 ↔ Y 축선(px). */
|
||||
const Y_LABEL_GAP_PX = 7;
|
||||
/** 중심고 상자 — 글자 좌우 여백 · 높이(px). 글자 크기는 눈금과 같음(10px). */
|
||||
const CENTER_PAD_X = 3;
|
||||
const CENTER_BOX_H = 14;
|
||||
|
||||
/**
|
||||
* 축 레이어를 그리는 함수를 만든다. 줌·팬이 바뀔 때마다 그 상태로 다시 부르면 된다.
|
||||
@@ -108,9 +115,16 @@ export function createCrossAxes(
|
||||
MIN_ELEVATION_STEP_M,
|
||||
);
|
||||
const yDecimals = Math.max(0, Math.ceil(-Math.log10(yStep) - 1e-9));
|
||||
const screenYOf = (raw: number) =>
|
||||
ty + scale * y(options.elevationMid + (raw - options.elevationMid) * options.exaggeration);
|
||||
// 중심고 상자 자리 — 보이는 범위 안일 때만 · 그 높이 눈금 글자는 상자에 양보
|
||||
const centerZ = options.centerZ;
|
||||
const centerY =
|
||||
typeof centerZ === "number" && centerZ >= rawBottom && centerZ <= rawTop
|
||||
? screenYOf(centerZ)
|
||||
: null;
|
||||
for (let raw = Math.ceil(rawBottom / yStep) * yStep; raw <= rawTop + 1e-9; raw += yStep) {
|
||||
const display = options.elevationMid + (raw - options.elevationMid) * options.exaggeration;
|
||||
const screenY = ty + scale * y(display);
|
||||
const screenY = screenYOf(raw);
|
||||
layer.append(
|
||||
svgElement("line", {
|
||||
x1: plotLeft,
|
||||
@@ -119,13 +133,41 @@ export function createCrossAxes(
|
||||
y2: screenY,
|
||||
class: "b06-chart__grid",
|
||||
}),
|
||||
);
|
||||
if (centerY !== null && Math.abs(screenY - centerY) < CENTER_BOX_H) continue;
|
||||
layer.append(
|
||||
svgText(raw.toFixed(yDecimals), {
|
||||
x: plotLeft - 7,
|
||||
x: plotLeft - Y_LABEL_GAP_PX,
|
||||
y: screenY + 3,
|
||||
"text-anchor": "end",
|
||||
class: "b06-chart__tick",
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (centerY !== null) appendCenterTag(layer, (centerZ as number).toFixed(1), plotLeft, centerY);
|
||||
};
|
||||
}
|
||||
|
||||
/** 중심고 둥근 상자 — 글 오른끝 = 다른 눈금 자리 · 상자는 Y 축선(`axisX`) 4px 앞에서 멈춤 ·
|
||||
* 글 폭은 잴 수 있으면 재고(붙은 뒤) 아니면 어림. */
|
||||
function appendCenterTag(layer: SVGGElement, text: string, axisX: number, centerY: number): void {
|
||||
const textRight = axisX - Y_LABEL_GAP_PX;
|
||||
const box = svgElement("rect", {
|
||||
y: centerY - CENTER_BOX_H / 2,
|
||||
height: CENTER_BOX_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
class: "b06-chart__center-box",
|
||||
});
|
||||
const label = svgText(text, {
|
||||
x: textRight,
|
||||
y: centerY + 3.5,
|
||||
"text-anchor": "end",
|
||||
class: "b06-chart__tick b06-chart__center-text",
|
||||
});
|
||||
layer.append(box, label);
|
||||
const measured = (label as SVGTextElement).getComputedTextLength?.() ?? 0;
|
||||
const width = (measured > 0 ? measured : text.length * 5.8) + CENTER_PAD_X * 2;
|
||||
box.setAttribute("x", String(textRight + CENTER_PAD_X - width));
|
||||
box.setAttribute("width", String(width));
|
||||
}
|
||||
|
||||
@@ -172,11 +172,9 @@ export function appendCardFooter(
|
||||
rockBoundary?: RockBoundaryControl,
|
||||
cutSlope?: CutSlopeControl,
|
||||
): void {
|
||||
// 중심고는 그래프 Y축 라벨 상자로 옮김(318-2 · `_Cross_Axes`) — 하단 띠는 암 제어만 · 비어도 띠 높이 그대로
|
||||
const footer = document.createElement("footer");
|
||||
const center = document.createElement("span");
|
||||
center.textContent = `${L("B06_Profile_View_CenterElevation")} ${section.center_z?.toFixed(2) ?? "-"}m`;
|
||||
footer.append(center);
|
||||
// 암 경계선 제어는 하단 정보 행 가운데(2026-08-02) — 암 지반만.
|
||||
// 암 경계선 제어는 하단 정보 행 왼쪽(318-2) — 암 지반만.
|
||||
if (rockBoundary && section.design?.geometry_preset === "rock") {
|
||||
const rockControl = buildRockBoundaryControl(section, rockBoundary);
|
||||
rockControl.classList.add("b06-cross-card__rockb");
|
||||
|
||||
@@ -346,8 +346,12 @@ export function buildDesignControls(
|
||||
};
|
||||
const bar = document.createElement("div");
|
||||
bar.className = "b06-design";
|
||||
// 컨트롤 상호작용이 카드 선택 클릭으로 전파되지 않게 한다.
|
||||
bar.addEventListener("click", (event) => event.stopPropagation());
|
||||
// 조작(단추 · 입력 · ⋯ 펼침)만 카드 선택 클릭으로 안 넘김 — 빈자리 누름은 카드를 고름(318-1).
|
||||
bar.addEventListener("click", (event) => {
|
||||
if ((event.target as Element).closest("button, input, select, label, details")) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// 경사 방향 선택이 필요한 경우 = 양절(both_cut)·양성(both_fill). 편절은 절토측 자동.
|
||||
// 양성은 측구가 없어도 노면 기울기 방향(생략된 측구 방향)을 여기서 지정한다(N-2-3).
|
||||
|
||||
@@ -387,6 +387,7 @@ export function createCrossSectionCard(
|
||||
displayMax,
|
||||
elevationMid,
|
||||
exaggeration,
|
||||
centerZ: section.center_z ?? null,
|
||||
});
|
||||
renderAxes(cardZoomStates.get(section.station_id) ?? IDENTITY_VIEW);
|
||||
|
||||
|
||||
@@ -599,6 +599,7 @@ export function createSectionView(
|
||||
displayMax: plot.displayMax,
|
||||
elevationMid: plot.elevationMid,
|
||||
exaggeration: Math.max(currentExaggeration, 0.1),
|
||||
centerZ: section.center_z ?? null,
|
||||
};
|
||||
// 칸 맞춤이 넓힌 가로 범위를 처음 배율로 되좁힘 = 카드 반폭(266-1)
|
||||
const homeScale = base && fitted ? base.pixelsPerMeter / fitted.pixelsPerMeter : 1;
|
||||
|
||||
@@ -121,6 +121,8 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(480px, 100%), 1fr));
|
||||
gap: var(--spacing-16);
|
||||
/* 카드 = 내용 높이만큼 — 같은 줄 큰 카드에 맞춰 늘어나 아래 띠 밑이 비던 것(318-3 · 띠 높이 그대로) */
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
/* 알림 줄(138-3) — 카드 칸 맨 위 고정 · 알림 없어도 줄 유지 · 오른쪽 끝 열 수 칸.
|
||||
@@ -344,6 +346,11 @@ html.is-bm-route .b06-section__notice-bar {
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* 빈 띠(토사 측점 · 중심고는 Y축으로 옮김 318-2)도 글 한 줄 높이 그대로 */
|
||||
.b06-cross-card > footer:empty::before {
|
||||
content: "\200b";
|
||||
}
|
||||
|
||||
.b06-section__empty {
|
||||
display: flex;
|
||||
min-height: 180px;
|
||||
@@ -388,6 +395,18 @@ html.is-bm-route .b06-section__notice-bar {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* 중심고 Y축 라벨(318-2) — 눈금과 같은 10px · 둥근 상자 · 테마 강조색 · 축선 앞에서 멈춤 */
|
||||
.b06-chart__center-box {
|
||||
fill: color-mix(in srgb, var(--color-accent) 14%, var(--color-surface-raised));
|
||||
stroke: var(--color-accent);
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.b06-chart__center-text {
|
||||
fill: var(--color-accent);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
/* 구조물(비정규) 측점 이름 — 종단면도 측점선 상단 표기. */
|
||||
.b06-chart__structure-label {
|
||||
font-size: 9px;
|
||||
@@ -607,24 +626,14 @@ html.is-bm-route .b06-section__notice-bar {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* 암 경계선 제어(E-7) — 하단 정보 행 **가운데 맞춤**(2026-08-06 사용자 지시).
|
||||
방위각 삭제로 space-between 3분할 배치가 깨져 좌측에 붙었다 — 절대 배치로
|
||||
행 중앙에 고정한다(중심고·반폭 버튼과 겹치지 않는 폭). */
|
||||
.b06-cross-card > footer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 암 경계선 제어(E-7) — 하단 정보 행 **왼쪽 맞춤**(318-2 · 중심고는 그래프 Y축 라벨 상자로 옮김). */
|
||||
.b06-cross-card__rockb {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
flex: 0 0 auto;
|
||||
border-radius: var(--radius-inputs);
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
/* 측점별 암 절토 경사(2026-09-07) — 암 경계선이 행 가운데를 절대 배치로 쓰므로
|
||||
이 칸은 행의 **오른쪽 끝**에 둔다. 중심고(왼쪽)와 겹치지 않는다. */
|
||||
/* 측점별 암 절토 경사(2026-09-07) — 행의 **오른쪽 끝**(암 경계 = 왼쪽). */
|
||||
.b06-cross-card__cutslope {
|
||||
margin-left: auto;
|
||||
flex: 0 0 auto;
|
||||
|
||||
Reference in New Issue
Block a user