diff --git a/.changeset/stupid-lions-speak.md b/.changeset/stupid-lions-speak.md new file mode 100644 index 000000000..0dcf02554 --- /dev/null +++ b/.changeset/stupid-lions-speak.md @@ -0,0 +1,5 @@ +--- +"trackio": minor +--- + +feat:Bar chart single point diff --git a/tests/ui/test_ui_display.py b/tests/ui/test_ui_display.py index e9cdd9ca9..427d93bcd 100644 --- a/tests/ui/test_ui_display.py +++ b/tests/ui/test_ui_display.py @@ -160,6 +160,7 @@ def test_multiple_runs_display_multiple_plots(temp_dir): trackio.init(project="test_multi", name=f"run-{i}") for j in range(5): trackio.log(metrics={"loss": 0.1 * (j + 1), "acc": 0.9 - 0.1 * j}) + trackio.log(metrics={"val_loss": 0.05 * (i + 1)}) trackio.finish() app, url, _, _ = trackio.show(block_thread=False, open_browser=False) @@ -176,7 +177,15 @@ def test_multiple_runs_display_multiple_plots(temp_dir): expect(run_items).to_have_count(2) plots = page.locator(".vega-embed") - expect(plots).to_have_count(2) + expect(plots).to_have_count(3) + + line_marks = page.locator(".vega-embed .mark-line.role-mark") + expect(line_marks.first).to_be_visible() + + bar_plots = page.locator(".bar-plot") + expect(bar_plots).to_have_count(1) + bar_vega = bar_plots.first.locator(".vega-embed") + expect(bar_vega).to_be_visible() runs_label = page.get_by_text("Runs (2)", exact=True) expect(runs_label).to_be_visible() diff --git a/trackio/frontend/src/components/BarPlot.svelte b/trackio/frontend/src/components/BarPlot.svelte new file mode 100644 index 000000000..6458d0147 --- /dev/null +++ b/trackio/frontend/src/components/BarPlot.svelte @@ -0,0 +1,624 @@ + + + + + +
+
+ + + +
+ {#if draggable} +
+ + + + + +
+ {/if} + {#if !fullscreen} +
+
+
+ {#if legendEntries.length > 0} +
+ {colorField} + {#each legendEntries as entry} + + + {entry.name} + + {/each} +
+ {/if} + {/if} +
+ +{#if fullscreen} +
+
+ + + +
+
+
+
+
+
+ {#if legendEntries.length > 0} +
+ {colorField} + {#each legendEntries as entry} + + + {entry.name} + + {/each} +
+ {/if} +
+{/if} + + diff --git a/trackio/frontend/src/components/LinePlot.svelte b/trackio/frontend/src/components/LinePlot.svelte index c26559c1f..f60903c95 100644 --- a/trackio/frontend/src/components/LinePlot.svelte +++ b/trackio/frontend/src/components/LinePlot.svelte @@ -199,7 +199,7 @@ if (originals.length === 0) return; const cols = Object.keys(originals[0]).filter((k) => k !== "data_type"); - const header = cols.join(","); + const header = cols.map((c) => /[,"]/.test(c) ? `"${c.replace(/"/g, '""')}"` : c).join(","); const rows = originals.map((row) => cols.map((c) => { const v = row[c]; diff --git a/trackio/frontend/src/pages/Metrics.svelte b/trackio/frontend/src/pages/Metrics.svelte index d7daac444..7d1bc1db6 100644 --- a/trackio/frontend/src/pages/Metrics.svelte +++ b/trackio/frontend/src/pages/Metrics.svelte @@ -2,6 +2,7 @@ import { onMount } from "svelte"; import { getQueryParam } from "../lib/router.js"; import LinePlot from "../components/LinePlot.svelte"; + import BarPlot from "../components/BarPlot.svelte"; import Accordion from "../components/Accordion.svelte"; import LoadingTrackio from "../components/LoadingTrackio.svelte"; import { getLogs } from "../lib/api.js"; @@ -31,6 +32,7 @@ let masterData = $state([]); let xColumn = $state("step"); let metrics = $state([]); + let singlePointMetrics = $state(new Set()); let xLim = $state(null); let hasLoaded = $state(false); let metricOrder = $state({}); @@ -114,6 +116,23 @@ (c) => c !== xColumn && c !== "run" && c !== "data_type" && c !== "x_axis", ); metrics = cols; + + const countPerRunMetric = new Map(); + for (const r of originals) { + const run = r.run; + for (const col of cols) { + if (r[col] == null) continue; + const key = `${col}\0${run}`; + countPerRunMetric.set(key, (countPerRunMetric.get(key) || 0) + 1); + } + } + const sp = new Set(cols); + for (const [key, count] of countPerRunMetric) { + if (count > 1) { + sp.delete(key.split("\0")[0]); + } + } + singlePointMetrics = sp; } async function fetchNewRuns() { @@ -221,6 +240,7 @@ const result = downsample(relevant, xColumn, metric, "run", xLim); return result.data; } +
@@ -266,21 +286,35 @@
{#each orderedDirect as metric, i} {@const plotData = getPlotData(metric)} + {@const useBar = singlePointMetrics.has(metric)} {#if plotData.length > 0} - handleDragStart(directKey, i, e)} - ondragover={(e) => handleDragOver(directKey, i, e)} - ondrop={(e) => handleDrop(directKey, i, orderedDirect, e)} - /> + {#if useBar} + handleDragStart(directKey, i, e)} + ondragover={(e) => handleDragOver(directKey, i, e)} + ondrop={(e) => handleDrop(directKey, i, orderedDirect, e)} + /> + {:else} + handleDragStart(directKey, i, e)} + ondragover={(e) => handleDragOver(directKey, i, e)} + ondrop={(e) => handleDrop(directKey, i, orderedDirect, e)} + /> + {/if} {/if} {/each}
@@ -297,21 +331,35 @@
{#each orderedSub as metric, i} {@const plotData = getPlotData(metric)} + {@const useBar = singlePointMetrics.has(metric)} {#if plotData.length > 0} - handleDragStart(subKey, i, e)} - ondragover={(e) => handleDragOver(subKey, i, e)} - ondrop={(e) => handleDrop(subKey, i, orderedSub, e)} - /> + {#if useBar} + handleDragStart(subKey, i, e)} + ondragover={(e) => handleDragOver(subKey, i, e)} + ondrop={(e) => handleDrop(subKey, i, orderedSub, e)} + /> + {:else} + handleDragStart(subKey, i, e)} + ondragover={(e) => handleDragOver(subKey, i, e)} + ondrop={(e) => handleDrop(subKey, i, orderedSub, e)} + /> + {/if} {/if} {/each}