feat(frontend): smooth TPS chart transitions on new datapoints#102
feat(frontend): smooth TPS chart transitions on new datapoints#102dak-agent[bot] wants to merge 2 commits into
Conversation
Tween the trailing tip of the TPS line from the previous value to the newly received value over ~400ms (one block) using requestAnimationFrame instead of letting recharts snap the chart whenever useTps emits a new point. If a new datapoint arrives mid-tween, the animation restarts from the current interpolated position so the line stays continuous.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds smooth animation to the TPS chart by introducing
Confidence Score: 5/5Safe to merge — the animation logic is self-contained and the chart falls back gracefully before data arrives. The animation hook correctly handles mid-tween restarts, RAF cancellation on unmount, and the edge case of a single-point history. The XAxis domain and tick-generation logic is straightforward arithmetic. The one notable gap is that minTickGap={80} may silently drop some of the explicitly computed 30-second ticks on narrower viewports, which is a cosmetic inconsistency rather than a functional defect. The XAxis tick display in tps-chart.tsx deserves a quick check at the minimum chart width to confirm the intended 30-second tick intervals actually render.
|
| Filename | Overview |
|---|---|
| frontend/hooks/use-tps.ts | Exports TPS_HISTORY_DURATION_MS so the chart's visible window stays in sync; no logic changes. |
| frontend/hooks/use-smoothed-tps-history.ts | New hook that RAF-tweens the trailing tip; animation restart-from-current-position logic is correct and cleanup is sound, with minor style issues around ref types (previously flagged). |
| frontend/components/network-activity-tracker/tps-chart.tsx | Switches XAxis to type="number" with explicit 30-second-boundary ticks; minTickGap={80} is still present and may drop some explicit ticks on narrower viewports. |
Sequence Diagram
sequenceDiagram
participant WS as WebSocket
participant useTps as useTps hook
participant useSmoothed as useSmoothedTpsHistory
participant RAF as requestAnimationFrame
participant Chart as TpsChart (recharts)
WS->>useTps: TPS event
useTps->>useTps: "filter history > 5 min, push {timestamp: Date.now(), tps}"
useTps->>useSmoothed: new history[] reference
useSmoothed->>useSmoothed: detect new targetTimestamp
useSmoothed->>useSmoothed: compute tip from animRef (or prev point)
useSmoothed->>useSmoothed: "set animRef = {start to target}"
useSmoothed->>RAF: cancelAnimationFrame(old), requestAnimationFrame(tick)
loop ~24 frames at 60 fps (400 ms)
RAF->>useSmoothed: tick()
useSmoothed->>useSmoothed: pointAt(animRef, performance.now()) via easeOutCubic
useSmoothed->>Chart: setRendered([...head, animated_tip])
Chart->>Chart: "xDomain = [latest - 5min, latest]"
Chart->>Chart: buildTicks(domain) → 30s-boundary ticks
Chart->>Chart: re-render AreaChart + sliding XAxis
end
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
frontend/components/network-activity-tracker/tps-chart.tsx:120-124
`minTickGap={80}` is still present alongside the explicit `ticks` prop. Recharts applies `minTickGap` even when ticks are supplied explicitly — any two adjacent ticks whose pixel distance is less than 80 will have the second dropped. With a 5-minute window and 10 thirty-second ticks, the chart needs at least ~800 px before all ticks appear; at the `min-w-2xl` (672 px) floor, recharts silently drops roughly every other tick, showing ~5 labels at 60-second gaps instead of the intended 30-second boundaries. The ticks still slide smoothly, but the interval becomes inconsistent. Removing `minTickGap` (or lowering it to match the 30-second pixel spacing at minimum width) lets the explicit tick set always render as designed.
```suggestion
ticks={xTicks}
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={0}
```
Reviews (2): Last reviewed commit: "feat(frontend): slide TPS chart x-axis t..." | Re-trigger Greptile
| const animRef = useRef<AnimationState | null>(null) | ||
| const rafRef = useRef<number | null>(null) |
There was a problem hiding this comment.
Both refs use explicit union types (
| null) which violates the team convention of letting React infer the nullability. The rule calls for useRef<AnimationState>(null) and useRef<number>(null) — React's overload resolution already makes .current nullable when null is passed as the initial value.
| const animRef = useRef<AnimationState | null>(null) | |
| const rafRef = useRef<number | null>(null) | |
| const animRef = useRef<AnimationState>(null) | |
| const rafRef = useRef<number>(null) |
Rule Used: When using useRef with a default value in TypeScri... (source)
Learned From
monad-developers/monapp#178
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/hooks/use-smoothed-tps-history.ts
Line: 44-45
Comment:
Both refs use explicit union types (`| null`) which violates the team convention of letting React infer the nullability. The rule calls for `useRef<AnimationState>(null)` and `useRef<number>(null)` — React's overload resolution already makes `.current` nullable when `null` is passed as the initial value.
```suggestion
const animRef = useRef<AnimationState>(null)
const rafRef = useRef<number>(null)
```
**Rule Used:** When using useRef with a default value in TypeScri... ([source](https://app.greptile.com/monad-foudnation/-/custom-context?memory=e5b39c00-8ef7-4612-a56a-d956ea833db7))
**Learned From**
[monad-developers/monapp#178](https://github.com/monad-developers/monapp/pull/178)
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Switch the XAxis to a continuous numeric scale and pin both edges of the domain to the smoothly-advancing tip — left edge at `latest - 5min`, right edge at `latest`. With the previous category axis, every new datapoint re-spaced the entire tick set; now ticks are placed at fixed 30-second absolute boundaries within the window, so their domain values are stable and their pixel positions slide continuously with the tween. Anchoring the left edge to `latest - 5min` (instead of the oldest history point) also removes the jolt that happened every time `useTps` dropped an expired point. Old data outside the window is clipped via `allowDataOverflow`. Export TPS_HISTORY_DURATION_MS from use-tps so the chart's visible window stays in sync with the hook's retention.
Motivation
The TPS chart in
network-activity-tracker/tps-chart.tsxcurrently has two snap behaviors when a new datapoint arrives from the WebSocket:Both make the chart feel jittery even when transaction volume is steady.
Modifications
Smooth line tip (commit 42cebfa):
frontend/hooks/use-smoothed-tps-history.ts, which wrapsuseTps's history and usesrequestAnimationFrameto tween the trailing tip from the previously rendered position toward the newly received datapoint. The tween animates both the timestamp (x) and tps (y), useseaseOutCubic, and lasts ~400ms (one Monad block, the natural inter-arrival time of TPS updates).Sliding x-axis (commit c9f7b2e):
<XAxis>totype="number"so x-positions are a continuous function of timestamp rather than a categorical index.[latest - 5min, latest]. Anchoring the left edge tolatest - 5min(instead of the oldest history point) removes the jolt that previously happened each block whenuseTpsdropped an expired point. Data outside that window is clipped viaallowDataOverflow.TPS_HISTORY_DURATION_MSfromuse-tpsso the chart's visible window stays in sync with the hook's retention.Unchanged:
isAnimationActivestays off — all motion is driven by our hook.currentTps/peakTpsin the stats block continue to reflect the actual received value.Result
The chart line and the x-axis ticks both slide smoothly as each new datapoint arrives, instead of snapping. "Smoothing" here refers to the animation of the trailing edge and the axis — the underlying
type="linear"stroke style is unchanged.