Skip to content

fix(tps-chart): restore smooth line render without per-frame recharts re-renders - #118

Merged
Camillebzd merged 2 commits into
mainfrom
fix/tps-chart-smooth-render
Aug 31, 2026
Merged

fix(tps-chart): restore smooth line render without per-frame recharts re-renders#118
Camillebzd merged 2 commits into
mainfrom
fix/tps-chart-smooth-render

Conversation

@Camillebzd

@Camillebzd Camillebzd commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Problem

#112 (fbcabf2) throttled the sliding clock from per-frame to 10 Hz to stop recharts re-rendering on every animation frame. That was the right diagnosis, but the clock drives two different things and throttling only one of them is harmless:

  1. The x-domain slide. At the full 5-minute window, 100 ms ≈ 0.3 px — invisible. But windowStart clamps to MIN_WINDOW_MS = 3s, so for the first ~30 s after load the window is a few seconds wide and 100 ms is tens of pixels per step.
  2. The head vertex in drawHistory. Broken at every zoom level. Blocks arrive every ~300 ms, so the head sweeps from the previous TPS value to the new one in 3 steps instead of ~18. The line body scrolls while the tip twitches — that's the stutter.

Per-frame motion is genuinely required. The real cost was that every setNow re-rendered the whole AreaChart — ~1000 points at the 5-minute window (300 ms blocks × 5 min), plus all axes, ticks and layers, 60× a second.

Fix

Split the two: recharts owns what only changes with data, and the per-frame animation leaves React entirely.

  • New TpsSeries layer rendered via recharts' <Customized>. It receives the plot rect (offset) and the real y-scale (yAxisMap) from the chart, renders two empty <path> elements once, and a requestAnimationFrame loop rewrites their d attributes each frame with the live now. Head interpolation is unchanged, so the tip animation is what it was before perf(frontend): throttle TPS chart updates to 10 Hz #112.
  • <Area> stays but is transparent (strokeOpacity={0} fillOpacity={0}). It still drives the y-domain, the tooltip payload and the hover dot, so interaction is unchanged. It keeps stroke="#6E54FF" because recharts derives the active dot's colour from that prop, not from opacity.
  • useSlidingNow is now adaptive rather than fixed-100 ms: it publishes when the domain has advanced ~1/1000 of the visible window, clamped to [16ms, 100ms]. This inverts the tradeoff correctly — the narrow window renders per frame but holds ~10 points; the wide window holds ~1000 points but publishes at 10 Hz. Cost per second is roughly flat across zoom levels.

Net recharts re-renders are lower than before #112 at every zoom level, and the line is smooth again.

Notes for the reviewer

  • tsc, Biome and next build all pass, but the visual result has not been checked in a browser. Worth eyeballing the first 30 s after load (narrow window) and the gradient fill, which is reproduced with fillOpacity={0.6} to match recharts' <Area> default.
  • offset and yAxisMap are recharts v2 internal props passed through <Customized>. Stable in 2.x, but recharts 3 reworked the internal state model — this layer will need revisiting on that upgrade. There's a comment on TpsSeries explaining why it isn't just an <Area>.
  • Behavioural change: the y-axis max is now computed from the raw history rather than the interpolated data, so it expands when a new peak arrives instead of mid-sweep. That removes a rescale-during-animation wobble, but it is a difference.

🤖 Generated with Claude Code

Greptile Summary

The PR separates high-frequency TPS line animation from lower-frequency Recharts rendering while preserving Recharts-owned axes, scaling, tooltip data, and hover behavior.

  • Adds a custom TpsSeries layer that updates SVG paths on every animation frame.
  • Makes the sliding React clock adaptive to the visible time window.
  • Supplies interpolated chart data to the transparent Recharts area so the tooltip and active dot remain aligned with the displayed line.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
frontend/components/network-activity-tracker/tps-chart.tsx Separates per-frame SVG animation from throttled Recharts updates and resolves the previously reported tooltip/head mismatch without introducing a blocking failure.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  H[Raw TPS history] --> C[Adaptive React clock]
  C --> D[Interpolated chart data]
  D --> R[Transparent Recharts Area]
  R --> Y[Y-axis domain]
  R --> T[Tooltip and active dot]
  H --> S[Custom TpsSeries]
  S --> F[Per-frame SVG path updates]
  R --> S
Loading

Reviews (2): Last reviewed commit: "fix(tps-chart): keep the tooltip attache..." | Re-trigger Greptile

…ts renders

fbcabf2 throttled the sliding clock to 10 Hz to stop recharts re-rendering
every animation frame. That also throttled the head-vertex interpolation, so
the newest segment now draws in ~3 steps per 300ms block instead of ~18, and
the x-domain steps by tens of pixels while the window is still clamped to
MIN_WINDOW_MS. Both read as stutter.

Split the two concerns instead:

- Draw the line and its fill in a <Customized> layer that rewrites the two
  <path> d attributes from a rAF loop, outside React. The head animation is
  back at full frame rate and costs no reconciliation.
- Keep a transparent <Area> so recharts still owns the y-domain, the tooltip
  payload and the hover dot.
- Make the sliding clock adaptive: publish when the domain has advanced ~1/1000
  of the visible window, clamped to [16ms, 100ms]. A narrow window renders per
  frame but holds few points; the full 5-minute window holds ~1000 points but
  publishes at 10 Hz.

Net recharts re-renders are lower than before fbcabf2 at every zoom level.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 31, 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 Aug 31, 2026 8:02am

Request Review

Comment thread frontend/components/network-activity-tracker/tps-chart.tsx Outdated
Feeding recharts the raw history left the tooltip and active dot on the newest
raw sample while TpsSeries was still sweeping the head towards it, so the dot
sat up to one inter-arrival interval (~300ms) ahead of the visible tip, and a
full sample's worth of TPS above or below it.

Feed recharts the drawn history again. TpsSeries still receives the raw array
and still interpolates every frame, so smoothness is unaffected; the residual
mismatch collapses to the sliding-clock gap, which CLOCK_STEP_FRACTION already
bounds at roughly a pixel.

Trades back the stable y-domain: the axis max can rescale mid-sweep again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@Im-Madhur-Gupta Im-Madhur-Gupta left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, confirmed smooth on the deployment.

@Camillebzd
Camillebzd merged commit f5ef949 into main Aug 31, 2026
12 checks passed
@Camillebzd
Camillebzd deleted the fix/tps-chart-smooth-render branch August 31, 2026 09:37
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.

2 participants