diff --git a/frontend/components/network-activity-tracker/tps-chart.tsx b/frontend/components/network-activity-tracker/tps-chart.tsx index 1394b5a..2b58aef 100644 --- a/frontend/components/network-activity-tracker/tps-chart.tsx +++ b/frontend/components/network-activity-tracker/tps-chart.tsx @@ -1,6 +1,7 @@ 'use client' import Image from 'next/image' +import { useMemo } from 'react' import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { type ChartConfig, @@ -8,8 +9,9 @@ import { ChartTooltip, ChartTooltipContent, } from '@/components/ui/chart' +import { useSmoothedTpsHistory } from '@/hooks/use-smoothed-tps-history' import { useTotalTransactions } from '@/hooks/use-total-transactions' -import { useTps } from '@/hooks/use-tps' +import { TPS_HISTORY_DURATION_MS, useTps } from '@/hooks/use-tps' import { formatRelativeTime, formatTimeHMS } from '@/lib/timestamp' import { formatIntNumber } from '@/lib/ui' import { NetworkActivityStats } from './network-activity-stats' @@ -21,10 +23,46 @@ const chartConfig = { }, } satisfies ChartConfig +/** + * Pin tick labels to absolute 30-second boundaries within the visible domain. + * Because each tick has a fixed timestamp, its pixel position is purely a + * function of the smoothly-advancing domain — so ticks slide left continuously + * with the chart instead of being re-picked by recharts when the data set + * grows. Once a minute the leftmost tick slides off-screen and a new one + * appears at the right edge. + */ +const TICK_INTERVAL_MS = 30_000 + +function buildTicks(minTimestamp: number, maxTimestamp: number): number[] { + const ticks: number[] = [] + let t = Math.floor(maxTimestamp / TICK_INTERVAL_MS) * TICK_INTERVAL_MS + while (t >= minTimestamp) { + ticks.push(t) + t -= TICK_INTERVAL_MS + } + return ticks.reverse() +} + export function TpsChart() { const { currentTps, peakTps, history } = useTps() + const smoothedHistory = useSmoothedTpsHistory(history) const totalTransactions = useTotalTransactions() - const hasData = history.length > 0 + const hasData = smoothedHistory.length > 0 + + // Anchor both edges of the domain to the smoothly-advancing tip so the + // axis scrolls continuously. Anchoring the left edge to `latest - window` + // (instead of the oldest history point) prevents a jump at the left every + // time `useTps` drops an expired point. + const xDomain = useMemo<[number, number] | undefined>(() => { + if (smoothedHistory.length === 0) return undefined + const latest = smoothedHistory[smoothedHistory.length - 1].timestamp + return [latest - TPS_HISTORY_DURATION_MS, latest] + }, [smoothedHistory]) + + const xTicks = useMemo(() => { + if (xDomain === undefined) return undefined + return buildTicks(xDomain[0], xDomain[1]) + }, [xDomain]) return (