refactor: simplify chart view logic by removing pan controls and enhancing window size handling

This commit is contained in:
Shine1i 2026-02-17 19:32:32 +01:00
commit 4e2b567c29
5 changed files with 63 additions and 45 deletions

View file

@ -10,6 +10,7 @@ import {
applyOutlierCap,
buildStepTicks,
buildYDomain,
clamp,
compressSeries,
ema,
toLog1p,
@ -24,8 +25,9 @@ export function ChartsContent({
const [showRaw, setShowRaw] = useState(true);
const [showSmoothed, setShowSmoothed] = useState(true);
const [showAvgLine, setShowAvgLine] = useState(true);
const [windowSize, setWindowSize] = useState(DEFAULT_VISIBLE_POINTS);
const [panOffset, setPanOffset] = useState(0);
const [windowSize, setWindowSize] = useState<number | null>(
Math.max(24, Math.floor(DEFAULT_VISIBLE_POINTS / 2)),
);
const [lossScale, setLossScale] = useState<ScaleMode>("linear");
const [lrScale, setLrScale] = useState<ScaleMode>("linear");
@ -65,19 +67,18 @@ export function ChartsContent({
return Array.from(set).sort((a, b) => a - b);
}, [reducedGradNormData, reducedLossData, reducedLrData]);
const effectiveWindowSize = Math.min(
Math.max(1, Math.round(windowSize)),
Math.max(1, allSteps.length),
);
const maxPanOffset = Math.max(0, allSteps.length - effectiveWindowSize);
const effectivePanOffset = Math.min(Math.max(0, Math.round(panOffset)), maxPanOffset);
const stepCount = Math.max(1, allSteps.length);
const effectiveWindowSize =
windowSize == null
? stepCount
: clamp(Math.round(windowSize), 1, stepCount);
const visibleStepDomain = useMemo<[number, number]>(() => {
if (allSteps.length === 0) {
return [0, 1];
}
const endIndex = Math.max(0, allSteps.length - 1 - effectivePanOffset);
const endIndex = allSteps.length - 1;
const startIndex = Math.max(0, endIndex - effectiveWindowSize + 1);
const minStep = allSteps[0] ?? 0;
const startStep = allSteps[startIndex] ?? minStep;
@ -90,7 +91,7 @@ export function ChartsContent({
return [Math.max(minStep, endStep - 6), endStep];
}
return [startStep, endStep];
}, [allSteps, effectivePanOffset, effectiveWindowSize]);
}, [allSteps, effectiveWindowSize]);
const xAxisTicks = useMemo(
() => buildStepTicks(visibleStepDomain[0], visibleStepDomain[1]),
@ -222,10 +223,14 @@ export function ChartsContent({
effectiveWindowSize,
minWindow,
allStepsLength: allSteps.length,
effectivePanOffset,
maxPanOffset,
setWindowSize: (value) => setWindowSize(value),
setPanOffset: (value) => setPanOffset(value),
setWindowSize: (value) => {
const clampedWindow = clamp(Math.round(value), 1, Math.max(1, allSteps.length));
if (clampedWindow >= allSteps.length) {
setWindowSize(null);
return;
}
setWindowSize(clampedWindow);
},
};
return (

View file

@ -17,6 +17,8 @@ export function SharedChartSettings({
outlierMode: OutlierMode;
setOutlierMode: (value: OutlierMode) => void;
}): ReactElement {
const showingAll = view.allStepsLength > 0 && view.effectiveWindowSize >= view.allStepsLength;
return (
<>
<DropdownMenuSeparator />
@ -25,7 +27,7 @@ export function SharedChartSettings({
<div className="flex items-center justify-between">
<Label className="text-xs">Window (steps)</Label>
<span className="text-xs tabular-nums text-muted-foreground">
{view.effectiveWindowSize}
{showingAll ? "All" : view.effectiveWindowSize}
</span>
</div>
<Slider
@ -35,21 +37,9 @@ export function SharedChartSettings({
max={Math.max(view.minWindow, view.allStepsLength)}
step={1}
/>
</div>
<div className="flex flex-col gap-1.5 px-2 py-1.5">
<div className="flex items-center justify-between">
<Label className="text-xs">Pan</Label>
<span className="text-xs tabular-nums text-muted-foreground">
{view.effectivePanOffset}
</span>
</div>
<Slider
value={[view.effectivePanOffset]}
onValueChange={([v]) => view.setPanOffset(Math.max(0, Math.round(v)))}
min={0}
max={Math.max(0, view.maxPanOffset)}
step={1}
/>
<span className="text-[10px] text-muted-foreground">
Always follows latest steps
</span>
</div>
<DropdownMenuSeparator />
<DropdownMenuLabel className="text-xs">Y Scale</DropdownMenuLabel>

View file

@ -15,8 +15,5 @@ export interface ViewSettingsState {
effectiveWindowSize: number;
minWindow: number;
allStepsLength: number;
effectivePanOffset: number;
maxPanOffset: number;
setWindowSize: (value: number) => void;
setPanOffset: (value: number) => void;
}

View file

@ -50,6 +50,20 @@ export function compressSeries<T>(data: T[], maxPoints: number): T[] {
);
}
export function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
export function getDefaultWindowSize(totalSteps: number): number {
if (totalSteps <= 1) {
return Math.max(totalSteps, 1);
}
if (totalSteps <= DEFAULT_VISIBLE_POINTS) {
return clamp(Math.floor(totalSteps * 0.6), 1, totalSteps);
}
return DEFAULT_VISIBLE_POINTS;
}
export function buildStepTicks(min: number, max: number, targetCount = 6): number[] {
if (!Number.isFinite(min) || !Number.isFinite(max)) {
return [0, 1];

View file

@ -102,18 +102,19 @@ export function ProgressSection(): ReactElement {
? runtime.currentStep / elapsed
: null;
const stoppedLoss =
!runtime.isTrainingRunning
? lastNonZeroValue(runtime.lossHistory) ?? runtime.currentLoss
: runtime.currentLoss;
const stoppedLr =
!runtime.isTrainingRunning
? lastNonZeroValue(runtime.lrHistory) ?? runtime.currentLearningRate
: runtime.currentLearningRate;
const stoppedGradNorm =
!runtime.isTrainingRunning
? lastNonZeroValue(runtime.gradNormHistory) ?? runtime.currentGradNorm
: runtime.currentGradNorm;
const stoppedLoss = getDisplayMetric(
runtime.isTrainingRunning,
runtime.currentLoss,
runtime.lossHistory,
);
const stoppedLr = getDisplayMetric(
runtime.isTrainingRunning,
runtime.currentLearningRate,
runtime.lrHistory,
);
const stoppedGradNorm = runtime.isTrainingRunning
? runtime.currentGradNorm
: lastNonZeroValue(runtime.gradNormHistory) ?? runtime.currentGradNorm;
const configItems = [
{
@ -352,6 +353,17 @@ function lastNonZeroValue(points: { value: number }[]): number | null {
return null;
}
function getDisplayMetric(
isTrainingRunning: boolean,
currentValue: number,
history: { value: number }[],
): number {
if (isTrainingRunning) {
return currentValue;
}
return lastNonZeroValue(history) ?? currentValue;
}
function GpuStat({
label,
icon,