refactor: simplify chart view logic by removing pan controls and enhancing window size handling
This commit is contained in:
parent
2d90210a69
commit
4e2b567c29
5 changed files with 63 additions and 45 deletions
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue