Skip to content

Commit afe2959

Browse files
abidlabsclaudegradio-pr-botCopilot
authored
Fix run list order and legend overflow (#516)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 0a242b8 commit afe2959

8 files changed

Lines changed: 219 additions & 26 deletions

File tree

.changeset/shy-places-wish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trackio": minor
3+
---
4+
5+
feat:Fix run list order and legend overflow
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import math
2+
import random
3+
import time
4+
5+
import trackio as wandb
6+
7+
EPOCHS = 20
8+
NUM_RUNS = 20
9+
PROJECT_ID = random.randint(100000, 999999)
10+
11+
12+
def generate_loss_curve(epoch, max_epochs, base_loss=2.5, min_loss=0.1):
13+
progress = epoch / max_epochs
14+
base_curve = base_loss * math.exp(-3 * progress) + min_loss
15+
16+
noise_scale = 0.3 * (1 - progress * 0.7)
17+
noise = random.gauss(0, noise_scale)
18+
19+
return max(min_loss * 0.5, base_curve + noise)
20+
21+
22+
def generate_accuracy_curve(epoch, max_epochs, max_acc=0.95, min_acc=0.1):
23+
progress = epoch / max_epochs
24+
base_curve = max_acc / (1 + math.exp(-6 * (progress - 0.5))) + min_acc
25+
26+
noise_scale = 0.08 * (1 - progress * 0.5)
27+
noise = random.gauss(0, noise_scale)
28+
29+
return max(0, min(max_acc, base_curve + noise))
30+
31+
32+
def generate_grad_norm_curve(epoch, max_epochs):
33+
if epoch == 0:
34+
return float("inf")
35+
elif epoch == 1:
36+
return 1000.0
37+
elif epoch == 2:
38+
return 100.0
39+
else:
40+
progress = (epoch - 2) / (max_epochs - 2)
41+
base_value = 50 * math.exp(-4 * progress) + 1.0
42+
noise = random.gauss(0, 0.5)
43+
return max(0.1, base_value + noise)
44+
45+
46+
for run in range(NUM_RUNS):
47+
wandb.init(
48+
project=f"fake-training-many-{PROJECT_ID}",
49+
name=f"test-run-{run}",
50+
config=dict(
51+
epochs=EPOCHS,
52+
learning_rate=0.001,
53+
batch_size=32,
54+
),
55+
)
56+
57+
for epoch in range(EPOCHS):
58+
train_loss = generate_loss_curve(
59+
epoch,
60+
EPOCHS,
61+
base_loss=random.uniform(2.5, 3.5),
62+
min_loss=random.uniform(0.05, 0.15),
63+
)
64+
val_loss = generate_loss_curve(
65+
epoch,
66+
EPOCHS,
67+
base_loss=random.uniform(2.5, 3.5),
68+
min_loss=random.uniform(0.05, 0.15),
69+
)
70+
71+
train_accuracy = generate_accuracy_curve(
72+
epoch,
73+
EPOCHS,
74+
max_acc=random.uniform(0.7, 0.9),
75+
min_acc=random.uniform(0.1, 0.3),
76+
)
77+
val_accuracy = generate_accuracy_curve(
78+
epoch,
79+
EPOCHS,
80+
max_acc=random.uniform(0.7, 0.9),
81+
min_acc=random.uniform(0.1, 0.3),
82+
)
83+
84+
grad_norm = generate_grad_norm_curve(epoch, EPOCHS)
85+
86+
if epoch > 2 and random.random() < 0.3:
87+
val_loss *= 1.1
88+
val_accuracy *= 0.95
89+
90+
wandb.log(
91+
{
92+
"train/loss": round(train_loss, 4),
93+
"train/accuracy": round(train_accuracy, 4),
94+
"train/rewards/reward1": random.random(),
95+
"train/rewards/reward2": random.random(),
96+
"val/loss": round(val_loss, 4),
97+
"val/accuracy": round(val_accuracy, 4),
98+
"grad_norm": grad_norm,
99+
}
100+
)
101+
102+
time.sleep(0.05)
103+
104+
wandb.finish()

trackio/frontend/src/App.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
}
125125
try {
126126
const data = await getRunsForProject(selectedProject);
127-
const newRuns = data || [];
127+
const newRuns = [...(data || [])].reverse();
128128
const newRunIds = new Set(newRuns.map(runKey));
129129
130130
if (JSON.stringify(runs) !== JSON.stringify(newRuns)) {
@@ -396,6 +396,7 @@
396396
<SystemMetrics
397397
project={selectedProject}
398398
selectedRuns={selectedRunRecords}
399+
allRuns={runs}
399400
{smoothing}
400401
{appBootstrapReady}
401402
{realtimeEnabled}

trackio/frontend/src/components/BarPlot.svelte

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@
4242
4343
let colorSpecKey = $derived(buildColorSpecKey(data, colorField, colorMap));
4444
45+
const LEGEND_COLLAPSED_COUNT = 6;
46+
let legendExpanded = $state(false);
47+
let legendExpandedFs = $state(false);
48+
let visibleLegendEntries = $derived(
49+
legendExpanded || legendEntries.length <= LEGEND_COLLAPSED_COUNT
50+
? legendEntries
51+
: legendEntries.slice(0, LEGEND_COLLAPSED_COUNT),
52+
);
53+
let visibleLegendEntriesFs = $derived(
54+
legendExpandedFs || legendEntries.length <= LEGEND_COLLAPSED_COUNT
55+
? legendEntries
56+
: legendEntries.slice(0, LEGEND_COLLAPSED_COUNT),
57+
);
58+
4559
function getBarData() {
4660
const runValues = new Map();
4761
for (const d of data) {
@@ -387,12 +401,23 @@
387401
{#if legendEntries.length > 0}
388402
<div class="custom-legend">
389403
<span class="legend-title">{colorField}</span>
390-
{#each legendEntries as entry}
404+
{#each visibleLegendEntries as entry}
391405
<span class="legend-item">
392406
<span class="legend-dot" style="background: {entry.color}"></span>
393407
<span class="legend-label">{entry.name}</span>
394408
</span>
395409
{/each}
410+
{#if legendEntries.length > LEGEND_COLLAPSED_COUNT}
411+
<button
412+
type="button"
413+
class="legend-toggle"
414+
onclick={(e) => { e.stopPropagation(); legendExpanded = !legendExpanded; }}
415+
>
416+
{legendExpanded
417+
? "Show less"
418+
: `+${legendEntries.length - LEGEND_COLLAPSED_COUNT} more`}
419+
</button>
420+
{/if}
396421
</div>
397422
{/if}
398423
{/if}
@@ -451,12 +476,23 @@
451476
{#if legendEntries.length > 0}
452477
<div class="custom-legend fullscreen-legend">
453478
<span class="legend-title">{colorField}</span>
454-
{#each legendEntries as entry}
479+
{#each visibleLegendEntriesFs as entry}
455480
<span class="legend-item">
456481
<span class="legend-dot" style="background: {entry.color}"></span>
457482
<span class="legend-label">{entry.name}</span>
458483
</span>
459484
{/each}
485+
{#if legendEntries.length > LEGEND_COLLAPSED_COUNT}
486+
<button
487+
type="button"
488+
class="legend-toggle"
489+
onclick={(e) => { e.stopPropagation(); legendExpandedFs = !legendExpandedFs; }}
490+
>
491+
{legendExpandedFs
492+
? "Show less"
493+
: `+${legendEntries.length - LEGEND_COLLAPSED_COUNT} more`}
494+
</button>
495+
{/if}
460496
</div>
461497
{/if}
462498
</div>
@@ -634,4 +670,16 @@
634670
font-size: 11px;
635671
color: var(--body-text-color-subdued, #6b7280);
636672
}
673+
.legend-toggle {
674+
font-size: 11px;
675+
color: var(--body-text-color-subdued, #6b7280);
676+
background: none;
677+
border: none;
678+
padding: 0 4px;
679+
cursor: pointer;
680+
text-decoration: underline;
681+
}
682+
.legend-toggle:hover {
683+
color: var(--body-text-color, #1f2937);
684+
}
637685
</style>

trackio/frontend/src/components/LinePlot.svelte

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@
5858
5959
let colorSpecKey = $derived(buildColorSpecKey(data, colorField, colorMap));
6060
61+
const LEGEND_COLLAPSED_COUNT = 6;
62+
let legendExpanded = $state(false);
63+
let legendExpandedFs = $state(false);
64+
let visibleLegendEntries = $derived(
65+
legendExpanded || legendEntries.length <= LEGEND_COLLAPSED_COUNT
66+
? legendEntries
67+
: legendEntries.slice(0, LEGEND_COLLAPSED_COUNT),
68+
);
69+
let visibleLegendEntriesFs = $derived(
70+
legendExpandedFs || legendEntries.length <= LEGEND_COLLAPSED_COUNT
71+
? legendEntries
72+
: legendEntries.slice(0, LEGEND_COLLAPSED_COUNT),
73+
);
74+
6175
let dashLegendEntries = $derived.by(() => {
6276
if (!dashField || !data || data.length === 0) return [];
6377
const seen = new Set();
@@ -638,12 +652,23 @@
638652
{#if legendEntries.length > 0}
639653
<div class="custom-legend">
640654
<span class="legend-title">{resolvedColorLabel}</span>
641-
{#each legendEntries as entry}
655+
{#each visibleLegendEntries as entry}
642656
<span class="legend-item">
643657
<span class="legend-dot" style="background: {entry.color}"></span>
644658
<span class="legend-label">{entry.name}</span>
645659
</span>
646660
{/each}
661+
{#if legendEntries.length > LEGEND_COLLAPSED_COUNT}
662+
<button
663+
type="button"
664+
class="legend-toggle"
665+
onclick={(e) => { e.stopPropagation(); legendExpanded = !legendExpanded; }}
666+
>
667+
{legendExpanded
668+
? "Show less"
669+
: `+${legendEntries.length - LEGEND_COLLAPSED_COUNT} more`}
670+
</button>
671+
{/if}
647672
</div>
648673
{/if}
649674
{#if dashLegendEntries.length > 0}
@@ -740,12 +765,23 @@
740765
{#if legendEntries.length > 0}
741766
<div class="custom-legend fullscreen-legend">
742767
<span class="legend-title">{resolvedColorLabel}</span>
743-
{#each legendEntries as entry}
768+
{#each visibleLegendEntriesFs as entry}
744769
<span class="legend-item">
745770
<span class="legend-dot" style="background: {entry.color}"></span>
746771
<span class="legend-label">{entry.name}</span>
747772
</span>
748773
{/each}
774+
{#if legendEntries.length > LEGEND_COLLAPSED_COUNT}
775+
<button
776+
type="button"
777+
class="legend-toggle"
778+
onclick={(e) => { e.stopPropagation(); legendExpandedFs = !legendExpandedFs; }}
779+
>
780+
{legendExpandedFs
781+
? "Show less"
782+
: `+${legendEntries.length - LEGEND_COLLAPSED_COUNT} more`}
783+
</button>
784+
{/if}
749785
</div>
750786
{/if}
751787
{#if dashLegendEntries.length > 0}
@@ -983,4 +1019,16 @@
9831019
font-size: 11px;
9841020
color: var(--body-text-color-subdued, #6b7280);
9851021
}
1022+
.legend-toggle {
1023+
font-size: 11px;
1024+
color: var(--body-text-color-subdued, #6b7280);
1025+
background: none;
1026+
border: none;
1027+
padding: 0 4px;
1028+
cursor: pointer;
1029+
text-decoration: underline;
1030+
}
1031+
.legend-toggle:hover {
1032+
color: var(--body-text-color, #1f2937);
1033+
}
9861034
</style>

trackio/frontend/src/lib/api.js

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,13 @@ export async function getLogsBatch(project, runs) {
9494
const out = [];
9595
for (const run of runs) {
9696
const logs = await staticApi.getLogs(project, run);
97-
out.push({
98-
run: run?.name ?? null,
99-
run_id: run?.id ?? null,
100-
logs,
101-
});
97+
out.push({ ...normalizeRun(run), logs });
10298
}
10399
return out;
104100
}
105101
const payload = {
106102
project,
107-
runs: runs.map((run) => ({
108-
run: run?.name ?? null,
109-
run_id: run?.id ?? null,
110-
})),
103+
runs: runs.map((run) => normalizeRun(run)),
111104
};
112105
return await callApi("/get_logs_batch", payload);
113106
}
@@ -146,20 +139,13 @@ export async function getSystemLogsBatch(project, runs) {
146139
const out = [];
147140
for (const run of runs) {
148141
const logs = await staticApi.getSystemLogs(project, run);
149-
out.push({
150-
run: run?.name ?? null,
151-
run_id: run?.id ?? null,
152-
logs,
153-
});
142+
out.push({ ...normalizeRun(run), logs });
154143
}
155144
return out;
156145
}
157146
return await callApi("/get_system_logs_batch", {
158147
project,
159-
runs: runs.map((run) => ({
160-
run: run?.name ?? null,
161-
run_id: run?.id ?? null,
162-
})),
148+
runs: runs.map((run) => normalizeRun(run)),
163149
});
164150
}
165151

trackio/frontend/src/pages/SystemMetrics.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
let {
2020
project = null,
2121
selectedRuns = [],
22+
allRuns = [],
2223
smoothing = 5,
2324
appBootstrapReady = false,
2425
realtimeEnabled = true,
@@ -38,7 +39,7 @@
3839
let rawDataCache = new Map();
3940
let refreshTimer = null;
4041
41-
let runColorMap = $derived(buildColorMap(selectedRuns));
42+
let runColorMap = $derived(buildColorMap(allRuns.length ? allRuns : selectedRuns));
4243
4344
let metricGroups = $derived(groupMetricsByPrefix(metricNames));
4445
let groupNames = $derived(Object.keys(metricGroups));

trackio/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,8 @@ def print_write_token_instructions(full_url: str) -> None:
462462
print()
463463
print(f"* Trackio dashboard opened in browser with write access at: {full_url}")
464464
print(
465-
"* Warning: anyone with access to your dashboard with the write_token can "
466-
"write logs, rename/delete runs, and connect MCP tools"
465+
"* Only share this write_token with trusted users, as it allows them to write logs, "
466+
"rename/delete runs, and connect MCP tools."
467467
)
468468

469469

0 commit comments

Comments
 (0)