From b73d3a509d893c553c8817a1ea6c62bab772ea53 Mon Sep 17 00:00:00 2001 From: jschoi Date: Wed, 22 Oct 2025 19:05:42 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B9=84=EA=B5=90=EC=B0=BD=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EC=88=98=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atoms/organisms/CompareRunDialog.vue | 293 ++++++++++-------- 1 file changed, 169 insertions(+), 124 deletions(-) diff --git a/src/components/atoms/organisms/CompareRunDialog.vue b/src/components/atoms/organisms/CompareRunDialog.vue index 05a748a..d8a7ba2 100644 --- a/src/components/atoms/organisms/CompareRunDialog.vue +++ b/src/components/atoms/organisms/CompareRunDialog.vue @@ -9,14 +9,14 @@ import { } from "vue"; import Plotly from "plotly.js-dist-min"; -/** ===== Types ===== */ +/* ===== Types ===== */ type MetricKV = { key: string; value: number }; type RunDetailType = { info: any; data: { metrics: MetricKV[]; params?: any[]; tags?: any[] }; }; -/** ===== Props / Emits ===== */ +/* ===== Props / Emits ===== */ const props = withDefaults( defineProps<{ modelValue: boolean; @@ -46,7 +46,7 @@ const emit = defineEmits<{ (e: "update:selectedMetricKeys", v: string[]): void; }>(); -/** ===== local refs ===== */ +/* ===== local refs ===== */ const dialogOpen = computed({ get: () => props.modelValue, set: (v: boolean) => emit("update:modelValue", v), @@ -63,7 +63,7 @@ const selectedMetricKeysProxy = computed({ const elCompare = ref(null); const loading = ref(false); -/** ===== helpers ===== */ +/* ===== helpers ===== */ const fmtNumber = (v: number | null, digits = 3) => { if (v == null || !Number.isFinite(v)) return ""; const abs = Math.abs(v); @@ -75,8 +75,8 @@ const fmtNumber = (v: number | null, digits = 3) => { const normalizeArray = (vals: (number | null)[]) => { const xs = vals.filter((v): v is number => Number.isFinite(v as number)); if (xs.length === 0) return vals; - const min = Math.min(...xs), - max = Math.max(...xs); + const min = Math.min(...xs); + const max = Math.max(...xs); if (max === min) return vals.map((v) => (v == null ? v : 1)); return vals.map((v) => (v == null ? v : (v - min) / (max - min))); }; @@ -86,7 +86,7 @@ const valueOf = (run: RunDetailType, key: string): number | null => { return Number.isFinite(m as number) ? Number(m) : null; }; -/** ===== derived ===== */ +/* ===== derived ===== */ const compareRuns = computed( () => selectedRunIdsProxy.value @@ -109,7 +109,25 @@ const activeMetricKeys = computed(() => : commonMetricKeys.value, ); -/** ===== chart ===== */ +/* ===== responsive widths for horizontal scroll ===== + - 그래프: 막대 개수에 비례해 내부 너비를 크게 잡아 가로 스크롤 허용 + - 테이블: (메트릭 수 + Run 컬럼 1개) * 160px 정도로 최소 너비 설정 +*/ +const chartInnerWidth = computed(() => { + // byMetric: X축 = metric 개수, byRun: X축 = run 개수 + const xCount = + props.compareChartMode === "byMetric" + ? activeMetricKeys.value.length + : compareRuns.value.length; + // 막대 간격 여유를 위해 140px씩, 최소 900px 확보 + return Math.max(900, xCount * 140 + 240); +}); +const tableInnerWidth = computed(() => { + const cols = 1 + activeMetricKeys.value.length; // Run 컬럼 + metric 수 + return Math.max(900, cols * 160); +}); + +/* ===== chart ===== */ const commonLayout: Partial = { autosize: true, margin: { t: 28, r: 12, b: 56, l: 56 }, @@ -181,6 +199,7 @@ function drawCompareChart() { ? createTracesByMetric(metricKeys, runsData) : createTracesByRun(metricKeys, runsData); + // byMetric 모드에서 분산 큰 순으로 정렬(가독성) if (props.compareChartMode === "byMetric") { const varianceOrder = metricKeys .map((k, idx) => { @@ -216,7 +235,7 @@ function drawCompareChart() { ); } -/** ===== load & watch ===== */ +/* ===== load & watch ===== */ async function loadCompareData() { if (!dialogOpen.value) return; loading.value = true; @@ -250,9 +269,9 @@ onBeforeUnmount(() => window.removeEventListener("resize", onResize)); - +