Skip to content

feat(frontend): smooth TPS chart transitions on new datapoints#102

Closed
dak-agent[bot] wants to merge 2 commits into
mainfrom
smooth-tps-chart-updates
Closed

feat(frontend): smooth TPS chart transitions on new datapoints#102
dak-agent[bot] wants to merge 2 commits into
mainfrom
smooth-tps-chart-updates

Conversation

@dak-agent

@dak-agent dak-agent Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Motivation

The TPS chart in network-activity-tracker/tps-chart.tsx currently has two snap behaviors when a new datapoint arrives from the WebSocket:

  1. The trailing line segment appears instantly, making the area jump.
  2. The XAxis is a category axis — every new datapoint is a new discrete slot, so the entire tick set re-spaces each block.

Both make the chart feel jittery even when transaction volume is steady.

Modifications

Smooth line tip (commit 42cebfa):

  • Added frontend/hooks/use-smoothed-tps-history.ts, which wraps useTps's history and uses requestAnimationFrame to tween the trailing tip from the previously rendered position toward the newly received datapoint. The tween animates both the timestamp (x) and tps (y), uses easeOutCubic, and lasts ~400ms (one Monad block, the natural inter-arrival time of TPS updates).
  • If a new datapoint arrives mid-tween, the next animation restarts from the current interpolated position so the line never jumps.

Sliding x-axis (commit c9f7b2e):

  • Switched <XAxis> to type="number" so x-positions are a continuous function of timestamp rather than a categorical index.
  • Pinned both edges of the domain to the smoothly-advancing tip: [latest - 5min, latest]. Anchoring the left edge to latest - 5min (instead of the oldest history point) removes the jolt that previously happened each block when useTps dropped an expired point. Data outside that window is clipped via allowDataOverflow.
  • Replaced auto-picked ticks with explicit ticks at fixed 30-second absolute boundaries within the window. Their domain values are stable, so their pixel positions are purely a function of the smoothly-advancing domain — ticks slide left continuously rather than being re-picked when the data set grows. Once a minute the leftmost tick slides off-screen and a new one appears at the right edge.
  • Exported TPS_HISTORY_DURATION_MS from use-tps so the chart's visible window stays in sync with the hook's retention.

Unchanged:

  • recharts' built-in isAnimationActive stays off — all motion is driven by our hook.
  • currentTps / peakTps in 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.

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.
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
monode Ready Ready Preview, Comment Jun 9, 2026 6:04pm

Request Review

@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds smooth animation to the TPS chart by introducing useSmoothedTpsHistory, which uses requestAnimationFrame and an easeOutCubic tween to advance the trailing data-point tip, and by switching the XAxis to type="number" with an explicit set of sliding 30-second-boundary ticks.

  • New useSmoothedTpsHistory hook tweens the chart's trailing tip at ~60 fps over 400 ms (one block interval), restarting mid-tween from the current interpolated position when a new datapoint arrives, with correct RAF cleanup on unmount.
  • XAxis overhaul pins the domain to [smoothed_tip − 5 min, smoothed_tip] and provides explicit absolute-boundary ticks so pixel positions are purely a function of the smoothly advancing domain, eliminating tick re-spacing on every new block.
  • TPS_HISTORY_DURATION_MS export from use-tps keeps the chart's visible window in sync with the hook's retention window.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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
Loading
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

Comment on lines +44 to +45
const animRef = useRef<AnimationState | null>(null)
const rafRef = useRef<number | null>(null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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!

Comment thread frontend/hooks/use-smoothed-tps-history.ts
Comment thread frontend/components/network-activity-tracker/tps-chart.tsx
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant