From 4e2b567c29ee3d896818ee3008f87a595adccbee Mon Sep 17 00:00:00 2001 From: Shine1i Date: Tue, 17 Feb 2026 19:32:32 +0100 Subject: [PATCH] refactor: simplify chart view logic by removing pan controls and enhancing window size handling --- .../studio/sections/charts-content.tsx | 33 +++++++++-------- .../sections/charts/shared-chart-settings.tsx | 22 ++++-------- .../features/studio/sections/charts/types.ts | 3 -- .../features/studio/sections/charts/utils.ts | 14 ++++++++ .../studio/sections/progress-section.tsx | 36 ++++++++++++------- 5 files changed, 63 insertions(+), 45 deletions(-) diff --git a/studio/frontend/src/features/studio/sections/charts-content.tsx b/studio/frontend/src/features/studio/sections/charts-content.tsx index 4d35d23c87..26b88e6bc8 100644 --- a/studio/frontend/src/features/studio/sections/charts-content.tsx +++ b/studio/frontend/src/features/studio/sections/charts-content.tsx @@ -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( + Math.max(24, Math.floor(DEFAULT_VISIBLE_POINTS / 2)), + ); const [lossScale, setLossScale] = useState("linear"); const [lrScale, setLrScale] = useState("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 ( diff --git a/studio/frontend/src/features/studio/sections/charts/shared-chart-settings.tsx b/studio/frontend/src/features/studio/sections/charts/shared-chart-settings.tsx index 22526abc90..dd1b438deb 100644 --- a/studio/frontend/src/features/studio/sections/charts/shared-chart-settings.tsx +++ b/studio/frontend/src/features/studio/sections/charts/shared-chart-settings.tsx @@ -17,6 +17,8 @@ export function SharedChartSettings({ outlierMode: OutlierMode; setOutlierMode: (value: OutlierMode) => void; }): ReactElement { + const showingAll = view.allStepsLength > 0 && view.effectiveWindowSize >= view.allStepsLength; + return ( <> @@ -25,7 +27,7 @@ export function SharedChartSettings({
- {view.effectiveWindowSize} + {showingAll ? "All" : view.effectiveWindowSize}
- -
-
- - - {view.effectivePanOffset} - -
- view.setPanOffset(Math.max(0, Math.round(v)))} - min={0} - max={Math.max(0, view.maxPanOffset)} - step={1} - /> + + Always follows latest steps +
Y Scale diff --git a/studio/frontend/src/features/studio/sections/charts/types.ts b/studio/frontend/src/features/studio/sections/charts/types.ts index dc28933e64..5543868ac3 100644 --- a/studio/frontend/src/features/studio/sections/charts/types.ts +++ b/studio/frontend/src/features/studio/sections/charts/types.ts @@ -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; } diff --git a/studio/frontend/src/features/studio/sections/charts/utils.ts b/studio/frontend/src/features/studio/sections/charts/utils.ts index ca9b3bc3ca..482ca00f76 100644 --- a/studio/frontend/src/features/studio/sections/charts/utils.ts +++ b/studio/frontend/src/features/studio/sections/charts/utils.ts @@ -50,6 +50,20 @@ export function compressSeries(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]; diff --git a/studio/frontend/src/features/studio/sections/progress-section.tsx b/studio/frontend/src/features/studio/sections/progress-section.tsx index a98e34d269..76282af813 100644 --- a/studio/frontend/src/features/studio/sections/progress-section.tsx +++ b/studio/frontend/src/features/studio/sections/progress-section.tsx @@ -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,