|
|
<script> |
|
|
import * as d3 from "d3"; |
|
|
import { formatAbbrev, smoothMetricData } from "./core/chart-utils.js"; |
|
|
import { |
|
|
generateRunNames, |
|
|
genCurves, |
|
|
Random, |
|
|
Performance, |
|
|
generateMassiveTestDataset, |
|
|
} from "./core/data-generator.js"; |
|
|
import Legend from "./components/Legend.svelte"; |
|
|
import Cell from "./components/Cell.svelte"; |
|
|
import FullscreenModal from "./components/FullscreenModal.svelte"; |
|
|
import { onMount, onDestroy } from "svelte"; |
|
|
import { jitterTrigger } from "./core/store.js"; |
|
|
|
|
|
export let variant = "classic"; |
|
|
export let normalizeLoss = true; |
|
|
export let logScaleX = false; |
|
|
export let smoothing = false; |
|
|
|
|
|
let hostEl; |
|
|
let gridEl; |
|
|
let legendItems = []; |
|
|
const cellsDef = [ |
|
|
{ metric: "epoch", title: "Epoch" }, |
|
|
{ metric: "train_accuracy", title: "Train accuracy" }, |
|
|
{ metric: "train_loss", title: "Train loss" }, |
|
|
{ metric: "val_accuracy", title: "Val accuracy" }, |
|
|
{ metric: "val_loss", title: "Val loss", wide: true }, |
|
|
]; |
|
|
let preparedData = {}; |
|
|
let colorsByRun = {}; |
|
|
|
|
|
|
|
|
let dataByMetric = new Map(); |
|
|
let metricsToDraw = []; |
|
|
let currentRunList = []; |
|
|
let cycleIdx = 2; |
|
|
|
|
|
|
|
|
let dynamicPalette = [ |
|
|
"#0ea5e9", |
|
|
"#8b5cf6", |
|
|
"#f59e0b", |
|
|
"#ef4444", |
|
|
"#10b981", |
|
|
"#f97316", |
|
|
"#3b82f6", |
|
|
"#8b5ad6", |
|
|
]; |
|
|
|
|
|
const updateDynamicPalette = () => { |
|
|
if ( |
|
|
typeof window !== "undefined" && |
|
|
window.ColorPalettes && |
|
|
currentRunList.length > 0 |
|
|
) { |
|
|
try { |
|
|
dynamicPalette = window.ColorPalettes.getColors( |
|
|
"categorical", |
|
|
currentRunList.length, |
|
|
); |
|
|
} catch (e) { |
|
|
console.warn("Failed to generate dynamic palette:", e); |
|
|
// Keep fallback palette |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
const colorForRun = (name) => { |
|
|
const idx = currentRunList.indexOf(name); |
|
|
return idx >= 0 ? dynamicPalette[idx % dynamicPalette.length] : "#999"; |
|
|
}; |
|
|
|
|
|
|
|
|
function jitterData() { |
|
|
console.log( |
|
|
"jitterData called - generating new data with random number of runs", |
|
|
); // Debug log |
|
|
|
|
|
// Generate new random data with weighted probability for fewer runs |
|
|
// Higher probability for 2-3 runs, lower for 4-5-6 runs |
|
|
const rand = Math.random(); |
|
|
let wantRuns; |
|
|
if (rand < 0.4) |
|
|
wantRuns = 2; // 40% chance |
|
|
else if (rand < 0.7) |
|
|
wantRuns = 3; // 30% chance |
|
|
else if (rand < 0.85) |
|
|
wantRuns = 4; // 15% chance |
|
|
else if (rand < 0.95) |
|
|
wantRuns = 5; // 10% chance |
|
|
else wantRuns = 6; // 5% chance |
|
|
// Use realistic ML training step counts |
|
|
const stepsCount = Random.trainingSteps(); |
|
|
const runsSim = generateRunNames(wantRuns, stepsCount); |
|
|
const steps = Array.from({ length: stepsCount }, (_, i) => i + 1); |
|
|
const nextByMetric = new Map(); |
|
|
const TARGET_METRICS = [ |
|
|
"epoch", |
|
|
"train_accuracy", |
|
|
"train_loss", |
|
|
"val_accuracy", |
|
|
"val_loss", |
|
|
]; |
|
|
|
|
|
|
|
|
TARGET_METRICS.forEach((tgt) => { |
|
|
const map = {}; |
|
|
runsSim.forEach((r) => { |
|
|
map[r] = []; |
|
|
}); |
|
|
nextByMetric.set(tgt, map); |
|
|
}); |
|
|
|
|
|
|
|
|
runsSim.forEach((run) => { |
|
|
const curves = genCurves(stepsCount); |
|
|
steps.forEach((s, i) => { |
|
|
nextByMetric.get("epoch")[run].push({ step: s, value: s }); |
|
|
nextByMetric |
|
|
.get("train_accuracy") |
|
|
[run].push({ step: s, value: curves.accTrain[i] }); |
|
|
nextByMetric |
|
|
.get("val_accuracy") |
|
|
[run].push({ step: s, value: curves.accVal[i] }); |
|
|
nextByMetric |
|
|
.get("train_loss") |
|
|
[run].push({ step: s, value: curves.lossTrain[i] }); |
|
|
nextByMetric |
|
|
.get("val_loss") |
|
|
[run].push({ step: s, value: curves.lossVal[i] }); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
nextByMetric.forEach((v, k) => dataByMetric.set(k, v)); |
|
|
metricsToDraw = TARGET_METRICS; |
|
|
currentRunList = runsSim.slice(); |
|
|
updateDynamicPalette(); |
|
|
legendItems = currentRunList.map((name) => ({ |
|
|
name, |
|
|
color: colorForRun(name), |
|
|
})); |
|
|
updatePreparedData(); |
|
|
colorsByRun = Object.fromEntries( |
|
|
currentRunList.map((name) => [name, colorForRun(name)]), |
|
|
); |
|
|
|
|
|
console.log( |
|
|
`jitterData completed - generated ${wantRuns} runs with ${stepsCount} steps`, |
|
|
); // Debug log |
|
|
} |
|
|
|
|
|
// Public API: allow external theme switch |
|
|
function setTheme(name) { |
|
|
variant = name === "oblivion" ? "oblivion" : "classic"; |
|
|
updateThemeClass(); |
|
|
|
|
|
// Debug log for font application |
|
|
if (typeof window !== "undefined") { |
|
|
console.log(`Theme switched to: ${variant}`); |
|
|
if (hostEl) { |
|
|
const computedStyle = getComputedStyle(hostEl); |
|
|
const appliedFont = computedStyle.fontFamily; |
|
|
console.log(`Applied font-family: ${appliedFont}`); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// Public API: allow external log scale X toggle |
|
|
function setLogScaleX(enabled) { |
|
|
logScaleX = enabled; |
|
|
console.log(`Log scale X set to: ${logScaleX}`); |
|
|
} |
|
|
|
|
|
// Public API: allow external smoothing toggle |
|
|
function setSmoothing(enabled) { |
|
|
smoothing = enabled; |
|
|
console.log(`Smoothing set to: ${smoothing}`); |
|
|
// Re-prepare data with smoothing applied |
|
|
updatePreparedData(); |
|
|
} |
|
|
|
|
|
// Public API: generate massive test dataset |
|
|
function generateMassiveDataset(steps = null, runs = 3) { |
|
|
console.log( |
|
|
"🧪 Generating massive test dataset for sampling validation...", |
|
|
); |
|
|
|
|
|
const result = generateMassiveTestDataset(steps, runs); |
|
|
|
|
|
// Update reactive data with massive dataset |
|
|
result.dataByMetric.forEach((v, k) => dataByMetric.set(k, v)); |
|
|
metricsToDraw = [ |
|
|
"epoch", |
|
|
"train_accuracy", |
|
|
"train_loss", |
|
|
"val_accuracy", |
|
|
"val_loss", |
|
|
]; |
|
|
currentRunList = result.runNames.slice(); |
|
|
updateDynamicPalette(); |
|
|
legendItems = currentRunList.map((name) => ({ |
|
|
name, |
|
|
color: colorForRun(name), |
|
|
})); |
|
|
updatePreparedData(); |
|
|
colorsByRun = Object.fromEntries( |
|
|
currentRunList.map((name) => [name, colorForRun(name)]), |
|
|
); |
|
|
|
|
|
console.log( |
|
|
`✅ Massive dataset loaded: ${result.stepCount} steps × ${result.runNames.length} runs`, |
|
|
); |
|
|
console.log(`📊 Total data points: ${result.totalPoints.toLocaleString()}`); |
|
|
console.log(`🎯 Description: ${result.description}`); |
|
|
|
|
|
return result; |
|
|
} |
|
|
|
|
|
// Public API: add live data point for simulation |
|
|
function addLiveDataPoint(runName, dataPoint) { |
|
|
console.log(`Adding live data point for run "${runName}":`, dataPoint); |
|
|
|
|
|
// Add run to currentRunList if it doesn't exist |
|
|
if (!currentRunList.includes(runName)) { |
|
|
currentRunList = [...currentRunList, runName]; |
|
|
updateDynamicPalette(); |
|
|
colorsByRun = Object.fromEntries( |
|
|
currentRunList.map((name) => [name, colorForRun(name)]), |
|
|
); |
|
|
legendItems = currentRunList.map((name) => ({ |
|
|
name, |
|
|
color: colorForRun(name), |
|
|
})); |
|
|
} |
|
|
|
|
|
|
|
|
const TARGET_METRICS = [ |
|
|
"epoch", |
|
|
"train_accuracy", |
|
|
"train_loss", |
|
|
"val_accuracy", |
|
|
"val_loss", |
|
|
]; |
|
|
TARGET_METRICS.forEach((metric) => { |
|
|
if (!dataByMetric.has(metric)) { |
|
|
dataByMetric.set(metric, {}); |
|
|
} |
|
|
const metricData = dataByMetric.get(metric); |
|
|
if (!metricData[runName]) { |
|
|
metricData[runName] = []; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const step = dataPoint.step; |
|
|
|
|
|
|
|
|
const epochData = dataByMetric.get("epoch"); |
|
|
epochData[runName].push({ step, value: step }); |
|
|
|
|
|
|
|
|
if (dataPoint.accuracy !== undefined) { |
|
|
const trainAccData = dataByMetric.get("train_accuracy"); |
|
|
const valAccData = dataByMetric.get("val_accuracy"); |
|
|
|
|
|
// Add some noise between train and val accuracy |
|
|
const trainAcc = dataPoint.accuracy; |
|
|
const valAcc = Math.max( |
|
|
0, |
|
|
Math.min(1, dataPoint.accuracy - 0.01 - Math.random() * 0.03), |
|
|
); |
|
|
|
|
|
trainAccData[runName].push({ step, value: trainAcc }); |
|
|
valAccData[runName].push({ step, value: valAcc }); |
|
|
} |
|
|
|
|
|
|
|
|
if (dataPoint.loss !== undefined) { |
|
|
const trainLossData = dataByMetric.get("train_loss"); |
|
|
const valLossData = dataByMetric.get("val_loss"); |
|
|
|
|
|
// Add some noise between train and val loss |
|
|
const trainLoss = dataPoint.loss; |
|
|
const valLoss = dataPoint.loss + 0.05 + Math.random() * 0.1; |
|
|
|
|
|
trainLossData[runName].push({ step, value: trainLoss }); |
|
|
valLossData[runName].push({ step, value: valLoss }); |
|
|
} |
|
|
|
|
|
|
|
|
metricsToDraw = TARGET_METRICS; |
|
|
|
|
|
|
|
|
updatePreparedData(); |
|
|
|
|
|
console.log( |
|
|
`Live data point added successfully. Total runs: ${currentRunList.length}`, |
|
|
); |
|
|
} |
|
|
|
|
|
// Update prepared data with optional smoothing |
|
|
let preparedRawData = {}; |
|
|
|
|
|
function updatePreparedData() { |
|
|
const TARGET_METRICS = [ |
|
|
"epoch", |
|
|
"train_accuracy", |
|
|
"train_loss", |
|
|
"val_accuracy", |
|
|
"val_loss", |
|
|
]; |
|
|
let dataToUse = {}; |
|
|
let rawDataToStore = {}; |
|
|
|
|
|
TARGET_METRICS.forEach((metric) => { |
|
|
const rawData = dataByMetric.get(metric); |
|
|
if (rawData) { |
|
|
// Store original data |
|
|
rawDataToStore[metric] = rawData; |
|
|
|
|
|
// Apply smoothing if enabled (except for epoch which should stay exact) |
|
|
dataToUse[metric] = |
|
|
smoothing && metric !== "epoch" |
|
|
? smoothMetricData(rawData, 5) // Window size of 5 |
|
|
: rawData; |
|
|
} |
|
|
}); |
|
|
|
|
|
preparedData = dataToUse; |
|
|
preparedRawData = rawDataToStore; |
|
|
console.log(`Prepared data updated, smoothing: ${smoothing}`); |
|
|
} |
|
|
|
|
|
function updateThemeClass() { |
|
|
if (!hostEl) return; |
|
|
hostEl.classList.toggle("theme--classic", variant === "classic"); |
|
|
hostEl.classList.toggle("theme--oblivion", variant === "oblivion"); |
|
|
hostEl.setAttribute("data-variant", variant); |
|
|
} |
|
|
|
|
|
$: updateThemeClass(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let currentFullscreenIndex = 0; |
|
|
let isModalOpen = false; |
|
|
|
|
|
function handleNavigate(newIndex) { |
|
|
currentFullscreenIndex = newIndex; |
|
|
} |
|
|
|
|
|
function openModal(index) { |
|
|
currentFullscreenIndex = index; |
|
|
isModalOpen = true; |
|
|
} |
|
|
|
|
|
function closeModal() { |
|
|
isModalOpen = false; |
|
|
} |
|
|
|
|
|
|
|
|
$: allChartsData = cellsDef.map((c) => ({ |
|
|
metricKey: c.metric, |
|
|
titleText: c.title, |
|
|
metricData: (preparedData && preparedData[c.metric]) || {}, |
|
|
rawMetricData: (preparedRawData && preparedRawData[c.metric]) || {}, |
|
|
})); |
|
|
|
|
|
|
|
|
$: modalColorForRun = (name) => colorsByRun[name] || "#999"; |
|
|
|
|
|
let cleanup = null; |
|
|
onMount(() => { |
|
|
if (!hostEl || !gridEl) return; |
|
|
hostEl.__setTheme = setTheme; |
|
|
|
|
|
// Jitter & Simulate functions |
|
|
function rebuildLegend() { |
|
|
updateDynamicPalette(); // Update colors when adding new data |
|
|
legendItems = currentRunList.map((name) => ({ |
|
|
name, |
|
|
color: colorForRun(name), |
|
|
})); |
|
|
} |
|
|
|
|
|
function simulateData() { |
|
|
// Generate new random data with weighted probability for fewer runs |
|
|
// Higher probability for 2-3 runs, lower for 4-5-6 runs |
|
|
const rand = Math.random(); |
|
|
let wantRuns; |
|
|
if (rand < 0.4) |
|
|
wantRuns = 2; // 40% chance |
|
|
else if (rand < 0.7) |
|
|
wantRuns = 3; // 30% chance |
|
|
else if (rand < 0.85) |
|
|
wantRuns = 4; // 15% chance |
|
|
else if (rand < 0.95) |
|
|
wantRuns = 5; // 10% chance |
|
|
else wantRuns = 6; // 5% chance |
|
|
// Use realistic ML training step counts with cycling scenarios |
|
|
let stepsCount; |
|
|
if (cycleIdx === 0) { |
|
|
stepsCount = Random.trainingStepsForScenario("prototyping"); |
|
|
} else if (cycleIdx === 1) { |
|
|
stepsCount = Random.trainingStepsForScenario("development"); |
|
|
} else if (cycleIdx === 2) { |
|
|
stepsCount = Random.trainingStepsForScenario("production"); |
|
|
} else if (cycleIdx === 3) { |
|
|
stepsCount = Random.trainingStepsForScenario("research"); |
|
|
} else if (cycleIdx === 4) { |
|
|
stepsCount = Random.trainingStepsForScenario("llm"); |
|
|
} else if (cycleIdx === 5) { |
|
|
stepsCount = Random.trainingStepsForScenario("massive"); |
|
|
} else { |
|
|
stepsCount = Random.trainingSteps(); // Full range for variety |
|
|
} |
|
|
cycleIdx = (cycleIdx + 1) % 7; |
|
|
|
|
|
const runsSim = generateRunNames(wantRuns, stepsCount); |
|
|
const steps = Array.from({ length: stepsCount }, (_, i) => i + 1); |
|
|
const nextByMetric = new Map(); |
|
|
const TARGET_METRICS = [ |
|
|
"epoch", |
|
|
"train_accuracy", |
|
|
"train_loss", |
|
|
"val_accuracy", |
|
|
"val_loss", |
|
|
]; |
|
|
const mList = |
|
|
metricsToDraw && metricsToDraw.length ? metricsToDraw : TARGET_METRICS; |
|
|
mList.forEach((tgt) => { |
|
|
const map = {}; |
|
|
runsSim.forEach((r) => { |
|
|
map[r] = []; |
|
|
}); |
|
|
nextByMetric.set(tgt, map); |
|
|
}); |
|
|
runsSim.forEach((run) => { |
|
|
const curves = genCurves(stepsCount); |
|
|
steps.forEach((s, i) => { |
|
|
if (mList.includes("epoch")) |
|
|
nextByMetric.get("epoch")[run].push({ step: s, value: s }); |
|
|
if (mList.includes("train_accuracy")) |
|
|
nextByMetric |
|
|
.get("train_accuracy") |
|
|
[run].push({ step: s, value: curves.accTrain[i] }); |
|
|
if (mList.includes("val_accuracy")) |
|
|
nextByMetric |
|
|
.get("val_accuracy") |
|
|
[run].push({ step: s, value: curves.accVal[i] }); |
|
|
if (mList.includes("train_loss")) |
|
|
nextByMetric |
|
|
.get("train_loss") |
|
|
[run].push({ step: s, value: curves.lossTrain[i] }); |
|
|
if (mList.includes("val_loss")) |
|
|
nextByMetric |
|
|
.get("val_loss") |
|
|
[run].push({ step: s, value: curves.lossVal[i] }); |
|
|
}); |
|
|
}); |
|
|
nextByMetric.forEach((v, k) => dataByMetric.set(k, v)); |
|
|
currentRunList = runsSim.slice(); |
|
|
rebuildLegend(); |
|
|
updatePreparedData(); |
|
|
updateDynamicPalette(); |
|
|
colorsByRun = Object.fromEntries( |
|
|
currentRunList.map((name) => [name, colorForRun(name)]), |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
simulateData(); |
|
|
|
|
|
|
|
|
cleanup = () => { |
|
|
// No cleanup needed for reactive statements |
|
|
}; |
|
|
}); |
|
|
|
|
|
onDestroy(() => { |
|
|
if (cleanup) cleanup(); |
|
|
}); |
|
|
|
|
|
|
|
|
onMount(() => { |
|
|
window.trackioInstance = { |
|
|
jitterData, |
|
|
addLiveDataPoint, |
|
|
generateMassiveDataset, |
|
|
}; |
|
|
if (hostEl) { |
|
|
hostEl.__trackioInstance = { |
|
|
setTheme, |
|
|
setLogScaleX, |
|
|
setSmoothing, |
|
|
jitterData, |
|
|
addLiveDataPoint, |
|
|
generateMassiveDataset, |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
updateDynamicPalette(); |
|
|
|
|
|
|
|
|
const handlePaletteUpdate = () => { |
|
|
updateDynamicPalette(); |
|
|
// Rebuild legend and colors if needed |
|
|
if (currentRunList.length > 0) { |
|
|
legendItems = currentRunList.map((name) => ({ |
|
|
name, |
|
|
color: colorForRun(name), |
|
|
})); |
|
|
colorsByRun = Object.fromEntries( |
|
|
currentRunList.map((name) => [name, colorForRun(name)]), |
|
|
); |
|
|
} |
|
|
}; |
|
|
|
|
|
document.addEventListener("palettes:updated", handlePaletteUpdate); |
|
|
|
|
|
|
|
|
return () => { |
|
|
document.removeEventListener("palettes:updated", handlePaletteUpdate); |
|
|
}; |
|
|
}); |
|
|
|
|
|
|
|
|
$: { |
|
|
console.log( |
|
|
"Reactive statement triggered, jitterTrigger value:", |
|
|
$jitterTrigger, |
|
|
); |
|
|
if ($jitterTrigger > 0) { |
|
|
console.log( |
|
|
"Jitter trigger activated:", |
|
|
$jitterTrigger, |
|
|
"calling jitterData()", |
|
|
); |
|
|
jitterData(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function ghostRun(run) { |
|
|
try { |
|
|
hostEl.classList.add("hovering"); |
|
|
|
|
|
// Ghost the chart lines and points |
|
|
hostEl.querySelectorAll(".cell").forEach((cell) => { |
|
|
cell |
|
|
.querySelectorAll("svg .lines path.run-line") |
|
|
.forEach((p) => |
|
|
p.classList.toggle("ghost", p.getAttribute("data-run") !== run), |
|
|
); |
|
|
cell |
|
|
.querySelectorAll("svg .lines path.raw-line") |
|
|
.forEach((p) => |
|
|
p.classList.toggle("ghost", p.getAttribute("data-run") !== run), |
|
|
); |
|
|
cell |
|
|
.querySelectorAll("svg .points circle.pt") |
|
|
.forEach((c) => |
|
|
c.classList.toggle("ghost", c.getAttribute("data-run") !== run), |
|
|
); |
|
|
}); |
|
|
|
|
|
|
|
|
hostEl.querySelectorAll(".legend-bottom .item").forEach((item) => { |
|
|
const itemRun = item.getAttribute("data-run"); |
|
|
item.classList.toggle("ghost", itemRun !== run); |
|
|
}); |
|
|
} catch (_) {} |
|
|
} |
|
|
function clearGhost() { |
|
|
try { |
|
|
hostEl.classList.remove("hovering"); |
|
|
|
|
|
// Clear ghost from chart lines and points |
|
|
hostEl.querySelectorAll(".cell").forEach((cell) => { |
|
|
cell |
|
|
.querySelectorAll("svg .lines path.run-line") |
|
|
.forEach((p) => p.classList.remove("ghost")); |
|
|
cell |
|
|
.querySelectorAll("svg .lines path.raw-line") |
|
|
.forEach((p) => p.classList.remove("ghost")); |
|
|
cell |
|
|
.querySelectorAll("svg .points circle.pt") |
|
|
.forEach((c) => c.classList.remove("ghost")); |
|
|
}); |
|
|
|
|
|
|
|
|
hostEl.querySelectorAll(".legend-bottom .item").forEach((item) => { |
|
|
item.classList.remove("ghost"); |
|
|
}); |
|
|
} catch (_) {} |
|
|
} |
|
|
</script> |
|
|
|
|
|
<div class="trackio theme--classic" bind:this={hostEl} data-variant={variant}> |
|
|
<div class="trackio__header"> |
|
|
<Legend |
|
|
items={legendItems} |
|
|
on:legend-hover={(e) => { |
|
|
const run = e?.detail?.name; |
|
|
if (!run) return; |
|
|
ghostRun(run); |
|
|
}} |
|
|
on:legend-leave={() => { |
|
|
clearGhost(); |
|
|
}} |
|
|
/> |
|
|
</div> |
|
|
<div class="trackio__grid" bind:this={gridEl}> |
|
|
{#each cellsDef as c, i} |
|
|
<Cell |
|
|
metricKey={c.metric} |
|
|
titleText={c.title} |
|
|
wide={c.wide} |
|
|
{variant} |
|
|
{normalizeLoss} |
|
|
{logScaleX} |
|
|
{smoothing} |
|
|
metricData={(preparedData && preparedData[c.metric]) || {}} |
|
|
rawMetricData={(preparedRawData && preparedRawData[c.metric]) || {}} |
|
|
colorForRun={(name) => colorsByRun[name] || "#999"} |
|
|
{hostEl} |
|
|
currentIndex={i} |
|
|
onOpenModal={openModal} |
|
|
/> |
|
|
{/each} |
|
|
</div> |
|
|
<div class="trackio__footer"> |
|
|
<small> |
|
|
Built with <a |
|
|
href="https://github.com/huggingface/trackio" |
|
|
target="_blank" |
|
|
rel="noopener noreferrer">TrackIO</a |
|
|
> |
|
|
<span class="separator">•</span> |
|
|
<a |
|
|
href="https://huggingface.co/docs/hub/spaces-sdks-docker" |
|
|
target="_blank" |
|
|
rel="noopener noreferrer">Use via API</a |
|
|
> |
|
|
</small> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Centralized Fullscreen Modal --> |
|
|
<FullscreenModal |
|
|
visible={isModalOpen} |
|
|
title={allChartsData[currentFullscreenIndex]?.titleText || ""} |
|
|
metricData={allChartsData[currentFullscreenIndex]?.metricData || {}} |
|
|
rawMetricData={allChartsData[currentFullscreenIndex]?.rawMetricData || {}} |
|
|
colorForRun={modalColorForRun} |
|
|
{variant} |
|
|
{logScaleX} |
|
|
{smoothing} |
|
|
{normalizeLoss} |
|
|
metricKey={allChartsData[currentFullscreenIndex]?.metricKey || ""} |
|
|
titleText={allChartsData[currentFullscreenIndex]?.titleText || ""} |
|
|
currentIndex={currentFullscreenIndex} |
|
|
totalCharts={cellsDef.length} |
|
|
onNavigate={handleNavigate} |
|
|
on:close={closeModal} |
|
|
/> |
|
|
|
|
|
<style> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@import url("https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;600;700&display=swap"); |
|
|
|
|
|
|
|
|
@font-face { |
|
|
font-family: "Roboto Mono Fallback"; |
|
|
src: url("https://fonts.gstatic.com/s/robotomono/v23/L0xuDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vq_ROW4AJi8SJQt.woff2") |
|
|
format("woff2"); |
|
|
font-weight: 400; |
|
|
font-style: normal; |
|
|
font-display: swap; |
|
|
} |
|
|
|
|
|
|
|
|
.trackio { |
|
|
position: relative; |
|
|
--z-tooltip: 50; |
|
|
--z-overlay: 99999999; |
|
|
|
|
|
/* Typography */ |
|
|
--trackio-font-family: var( |
|
|
--font-mono, |
|
|
ui-monospace, |
|
|
SFMono-Regular, |
|
|
Menlo, |
|
|
monospace |
|
|
); |
|
|
--trackio-font-weight-normal: 400; |
|
|
--trackio-font-weight-medium: 600; |
|
|
--trackio-font-weight-bold: 700; |
|
|
|
|
|
/* Apply font-family to root element */ |
|
|
font-family: var(--trackio-font-family); |
|
|
|
|
|
/* Base color system for Classic theme */ |
|
|
--trackio-base: #323232; |
|
|
--trackio-primary: var(--trackio-base); |
|
|
--trackio-dim: color-mix(in srgb, var(--trackio-base) 28%, transparent); |
|
|
--trackio-text: color-mix(in srgb, var(--trackio-base) 60%, transparent); |
|
|
--trackio-subtle: color-mix(in srgb, var(--trackio-base) 8%, transparent); |
|
|
|
|
|
/* Chart rendering */ |
|
|
--trackio-chart-grid-type: "lines"; /* 'lines' | 'dots' */ |
|
|
--trackio-chart-axis-stroke: var(--trackio-dim); |
|
|
--trackio-chart-axis-text: var(--trackio-text); |
|
|
--trackio-chart-grid-stroke: var(--trackio-subtle); |
|
|
--trackio-chart-grid-opacity: 1; |
|
|
} |
|
|
|
|
|
|
|
|
:global([data-theme="dark"]) .trackio.theme--classic { |
|
|
--trackio-base: #ffffff; |
|
|
--trackio-primary: var(--trackio-base); |
|
|
--trackio-dim: color-mix(in srgb, var(--trackio-base) 25%, transparent); |
|
|
--trackio-text: color-mix(in srgb, var(--trackio-base) 60%, transparent); |
|
|
--trackio-subtle: color-mix(in srgb, var(--trackio-base) 8%, transparent); |
|
|
|
|
|
/* Cell background for dark mode */ |
|
|
--trackio-cell-background: rgba(255, 255, 255, 0.03); |
|
|
} |
|
|
|
|
|
.trackio.theme--classic { |
|
|
/* Cell styling */ |
|
|
--trackio-cell-background: rgba(0, 0, 0, 0.02); |
|
|
--trackio-cell-border: var(--border-color, rgba(0, 0, 0, 0.1)); |
|
|
--trackio-cell-corner-inset: 0px; |
|
|
--trackio-cell-gap: 12px; |
|
|
|
|
|
/* Typography */ |
|
|
--trackio-text-primary: var(--text-color, rgba(0, 0, 0, 0.9)); |
|
|
--trackio-text-secondary: var(--muted-color, rgba(0, 0, 0, 0.6)); |
|
|
--trackio-text-accent: var(--primary-color); |
|
|
|
|
|
/* Tooltip */ |
|
|
--trackio-tooltip-background: var(--surface-bg, white); |
|
|
--trackio-tooltip-border: var(--border-color, rgba(0, 0, 0, 0.1)); |
|
|
--trackio-tooltip-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); |
|
|
|
|
|
/* Legend */ |
|
|
--trackio-legend-text: var(--text-color, rgba(0, 0, 0, 0.9)); |
|
|
--trackio-legend-swatch-border: var(--border-color, rgba(0, 0, 0, 0.1)); |
|
|
} |
|
|
|
|
|
|
|
|
:global([data-theme="dark"]) .trackio { |
|
|
--trackio-chart-axis-stroke: rgba(255, 255, 255, 0.18); |
|
|
--trackio-chart-axis-text: rgba(255, 255, 255, 0.6); |
|
|
--trackio-chart-grid-stroke: rgba(255, 255, 255, 0.08); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.trackio.theme--classic { |
|
|
/* Keep default values - no overrides needed */ |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.trackio.theme--oblivion { |
|
|
/* Core oblivion color system - Light mode: darker colors for visibility */ |
|
|
--trackio-oblivion-base: #2a2a2a; |
|
|
--trackio-oblivion-primary: var(--trackio-oblivion-base); |
|
|
--trackio-oblivion-dim: color-mix( |
|
|
in srgb, |
|
|
var(--trackio-oblivion-base) 30%, |
|
|
transparent |
|
|
); |
|
|
--trackio-oblivion-subtle: color-mix( |
|
|
in srgb, |
|
|
var(--trackio-oblivion-base) 8%, |
|
|
transparent |
|
|
); |
|
|
--trackio-oblivion-ghost: color-mix( |
|
|
in srgb, |
|
|
var(--trackio-oblivion-base) 4%, |
|
|
transparent |
|
|
); |
|
|
|
|
|
/* Chart rendering overrides */ |
|
|
--trackio-chart-grid-type: "dots"; |
|
|
--trackio-chart-axis-stroke: var(--trackio-oblivion-dim); |
|
|
--trackio-chart-axis-text: var(--trackio-oblivion-primary); |
|
|
--trackio-chart-grid-stroke: var(--trackio-oblivion-dim); |
|
|
--trackio-chart-grid-opacity: 0.6; |
|
|
} |
|
|
|
|
|
|
|
|
:global([data-theme="dark"]) .trackio.theme--oblivion { |
|
|
--trackio-oblivion-base: #ffffff; |
|
|
--trackio-oblivion-primary: var(--trackio-oblivion-base); |
|
|
--trackio-oblivion-dim: color-mix( |
|
|
in srgb, |
|
|
var(--trackio-oblivion-base) 25%, |
|
|
transparent |
|
|
); |
|
|
--trackio-oblivion-subtle: color-mix( |
|
|
in srgb, |
|
|
var(--trackio-oblivion-base) 8%, |
|
|
transparent |
|
|
); |
|
|
--trackio-oblivion-ghost: color-mix( |
|
|
in srgb, |
|
|
var(--trackio-oblivion-base) 4%, |
|
|
transparent |
|
|
); |
|
|
} |
|
|
|
|
|
.trackio.theme--oblivion { |
|
|
/* Cell styling overrides */ |
|
|
--trackio-cell-background: var(--trackio-oblivion-subtle); |
|
|
--trackio-cell-border: var(--trackio-oblivion-dim); |
|
|
--trackio-cell-corner-inset: 6px; |
|
|
--trackio-cell-gap: 0px; |
|
|
|
|
|
/* HUD-specific variables */ |
|
|
--trackio-oblivion-hud-gap: 10px; |
|
|
--trackio-oblivion-hud-corner-size: 8px; |
|
|
--trackio-oblivion-hud-bg-gradient: radial-gradient( |
|
|
1200px 200px at 20% -10%, |
|
|
var(--trackio-oblivion-ghost), |
|
|
transparent 80% |
|
|
), |
|
|
radial-gradient( |
|
|
900px 200px at 80% 110%, |
|
|
var(--trackio-oblivion-ghost), |
|
|
transparent 80% |
|
|
); |
|
|
|
|
|
/* Typography overrides */ |
|
|
--trackio-text-primary: var(--trackio-oblivion-primary); |
|
|
--trackio-text-secondary: var(--trackio-oblivion-dim); |
|
|
--trackio-text-accent: var(--trackio-oblivion-primary); |
|
|
|
|
|
/* Tooltip overrides */ |
|
|
--trackio-tooltip-background: var(--trackio-oblivion-subtle); |
|
|
--trackio-tooltip-border: var(--trackio-oblivion-dim); |
|
|
--trackio-tooltip-shadow: 0 8px 32px |
|
|
color-mix(in srgb, var(--trackio-oblivion-base) 8%, transparent), |
|
|
0 2px 8px color-mix(in srgb, var(--trackio-oblivion-base) 6%, transparent); |
|
|
|
|
|
/* Legend overrides */ |
|
|
--trackio-legend-text: var(--trackio-oblivion-primary); |
|
|
--trackio-legend-swatch-border: var(--trackio-oblivion-dim); |
|
|
|
|
|
/* Font styling overrides */ |
|
|
--trackio-font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace, |
|
|
SFMono-Regular, Menlo, monospace; |
|
|
font-family: var(--trackio-font-family) !important; |
|
|
color: var(--trackio-text-primary); |
|
|
} |
|
|
|
|
|
|
|
|
.trackio.theme--oblivion, |
|
|
.trackio.theme--oblivion * { |
|
|
font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace, |
|
|
SFMono-Regular, Menlo, monospace !important; |
|
|
} |
|
|
|
|
|
|
|
|
.trackio.theme--oblivion .cell-title, |
|
|
.trackio.theme--oblivion .legend-bottom, |
|
|
.trackio.theme--oblivion .legend-title, |
|
|
.trackio.theme--oblivion .item { |
|
|
font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace, |
|
|
SFMono-Regular, Menlo, monospace !important; |
|
|
} |
|
|
|
|
|
|
|
|
:global([data-theme="dark"]) .trackio.theme--oblivion { |
|
|
--trackio-oblivion-base: #ffffff; |
|
|
--trackio-oblivion-hud-bg-gradient: radial-gradient( |
|
|
1400px 260px at 20% -10%, |
|
|
color-mix(in srgb, var(--trackio-oblivion-base) 6.5%, transparent), |
|
|
transparent 80% |
|
|
), |
|
|
radial-gradient( |
|
|
1100px 240px at 80% 110%, |
|
|
color-mix(in srgb, var(--trackio-oblivion-base) 6%, transparent), |
|
|
transparent 80% |
|
|
), |
|
|
linear-gradient( |
|
|
180deg, |
|
|
color-mix(in srgb, var(--trackio-oblivion-base) 3.5%, transparent), |
|
|
transparent 45% |
|
|
); |
|
|
|
|
|
--trackio-tooltip-shadow: 0 8px 32px |
|
|
color-mix(in srgb, var(--trackio-oblivion-base) 5%, transparent), |
|
|
0 2px 8px color-mix(in srgb, black 10%, transparent); |
|
|
|
|
|
background: #0f1115; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.trackio__grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(2, minmax(0, 1fr)); |
|
|
gap: var(--trackio-cell-gap); |
|
|
} |
|
|
|
|
|
@media (max-width: 980px) { |
|
|
.trackio__grid { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
} |
|
|
|
|
|
.trackio__header { |
|
|
display: flex; |
|
|
align-items: flex-start; |
|
|
justify-content: center; |
|
|
gap: 12px; |
|
|
margin: 0 0 10px 0; |
|
|
flex-wrap: wrap; |
|
|
width: 100%; |
|
|
} |
|
|
|
|
|
|
|
|
.trackio .axes path, |
|
|
.trackio .axes line { |
|
|
stroke: var(--trackio-chart-axis-stroke); |
|
|
} |
|
|
|
|
|
.trackio .axes text { |
|
|
fill: var(--trackio-chart-axis-text); |
|
|
font-family: var(--trackio-font-family); |
|
|
} |
|
|
|
|
|
|
|
|
.trackio.theme--oblivion .axes text { |
|
|
font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace, |
|
|
SFMono-Regular, Menlo, monospace !important; |
|
|
} |
|
|
|
|
|
.trackio .grid line { |
|
|
stroke: var(--trackio-chart-grid-stroke); |
|
|
opacity: var(--trackio-chart-grid-opacity); |
|
|
} |
|
|
|
|
|
|
|
|
.trackio .grid-dots { |
|
|
display: none; |
|
|
} |
|
|
.trackio.theme--oblivion .grid { |
|
|
display: none; |
|
|
} |
|
|
.trackio.theme--oblivion .grid-dots { |
|
|
display: block; |
|
|
} |
|
|
.trackio.theme--oblivion .cell-bg, |
|
|
.trackio.theme--oblivion .cell-corners { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.trackio__footer { |
|
|
display: flex; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
margin-top: 12px; |
|
|
padding-top: 6px; |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.trackio__footer small { |
|
|
font-size: 10px; |
|
|
color: var(--trackio-text-secondary); |
|
|
font-family: var(--trackio-font-family); |
|
|
opacity: 0.7; |
|
|
} |
|
|
|
|
|
.trackio__footer a { |
|
|
color: var(--trackio-text-secondary); |
|
|
text-decoration: none; |
|
|
border-top: 1px solid var(--trackio-chart-grid-stroke); |
|
|
font-weight: var(--trackio-font-weight-normal); |
|
|
transition: opacity 0.15s ease; |
|
|
} |
|
|
|
|
|
.trackio__footer a:hover { |
|
|
text-decoration: none; |
|
|
} |
|
|
|
|
|
.trackio__footer .separator { |
|
|
margin: 0 6px; |
|
|
} |
|
|
|
|
|
|
|
|
.trackio.theme--oblivion .trackio__footer { |
|
|
border-top-color: var(--trackio-oblivion-dim); |
|
|
} |
|
|
|
|
|
.trackio.theme--oblivion .trackio__footer small { |
|
|
font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace, |
|
|
SFMono-Regular, Menlo, monospace !important; |
|
|
} |
|
|
</style> |
|
|
|