Skip to content

perf(charts): skip identity zoom-transform replay on chart rebuild#445

Open
Oseltamivir wants to merge 1 commit into
masterfrom
perf/skip-identity-zoom-replay
Open

perf(charts): skip identity zoom-transform replay on chart rebuild#445
Oseltamivir wants to merge 1 commit into
masterfrom
perf/skip-identity-zoom-replay

Conversation

@Oseltamivir

@Oseltamivir Oseltamivir commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Context

Second of the INP fixes (field CrUX INP 273 ms p75, failing CWV). The Firefox profile of the live inference tab shows every chart rebuild renders everything twice: once directly (axes, grid, all layers at base scales) and once more via setupZoom's unconditional svg.call(zoom.transform, stored) — which synchronously dispatches a zoom event whose handler re-renders axes + grid + every layer again. The profiled hot path:

layout effect → setupZoom → zoom.transform → emit('zoom') → onZoom
  → renderAxes + renderGrid + updateLayerOnZoom(all layers) + user onZoom

When the user has never zoomed (identity transform — the overwhelmingly common case) the replay repositions nothing: it just repeats the full render for pixel-identical output, doubling every ~250–490 ms rebuild long task.

Changes

  • useChartZoom.setupZoom — replay the stored transform only when it (or the node's internal __zoom state, as a defensive desync check) is non-identity. At identity, attaching the behavior already leaves d3's internal state consistent with the freshly drawn base-scale DOM.
  • useD3ChartRenderer — the identity replay had one observable side effect: its emit dismissed a pinned tooltip on every rebuild. That dismissal is now performed explicitly when the replay is skipped, so observable behavior is unchanged.

Zoom preservation is untouched (docs/d3-charts.md "Zoom Transform Preservation" / pitfalls "D3 Zoom Transform Loss"): non-identity transforms — user zooms and defaultZoomK charts — replay exactly as before, verified by tests.

Impact

Halves the main-thread cost of every chart rebuild while not zoomed: initial mounts, data loads, comparison-date loads, legend/precision toggles, metric switches — across all D3Chart consumers (inference scatter, GPU timeline, evaluation/reliability bars, trends, calculator). Stacks with #444.

Tests

New useChartZoom.setup.test.ts (jsdom, real d3-zoom on a real SVG):

  • identity stored transform → no zoom emit on setup (regression test: fails against old implementation)
  • non-identity transform → replayed exactly once with the right k/x/y on re-setup (zoom preservation)
  • zoomTransformRef stays in sync after replay
  • node/ref desync → defensive replay normalizes the node
  • defaultZoomK ≠ 1 → initial transform still applied

Full unit suite passes (2037 tests). pnpm typecheck / lint / fmt clean.

Applies uniformly to official and ?unofficialrun= overlay rendering — both go through the same rebuild path; overlay rooflines/points reposition via the same non-identity replay when zoomed.


Note

Medium Risk
Touches shared chart zoom and renderer paths used by all D3 charts; behavior is guarded by new tests but incorrect identity/replay logic could affect zoom state or tooltips.

Overview
Skips redundant identity zoom replay on chart rebuild so charts are not fully re-rendered twice when the user has not zoomed.

useChartZoom.setupZoom now calls zoom.transform only when the stored transform or the SVG node’s internal __zoom state is non-identity; user zoom and defaultZoomK replay behavior is unchanged. useD3ChartRenderer explicitly dismisses pinned tooltips on identity rebuilds, matching the old side effect of the identity replay’s zoom emit.

Adds useChartZoom.setup.test.ts (jsdom + real d3-zoom) covering no emit at identity, zoom preservation, ref sync, defensive node/ref desync, and non-identity defaultZoomK.

Reviewed by Cursor Bugbot for commit 1c344a9. Bugbot is set up for automated code reviews on this repo. Configure here.

setupZoom unconditionally replayed the stored zoom transform after
re-attaching the zoom behavior. zoom.transform dispatches start/zoom/end
synchronously, and the chart's zoom handler answers with a full
axes + grid + every-layer re-render — so every chart rebuild rendered
everything twice, even when the user had never zoomed (identity
transform, the overwhelmingly common case). Profiling the live site
shows this doubles the cost of every ~250-490ms rebuild long task.

Replay now happens only when there is actually a zoom to restore
(stored transform or node state non-identity). Zoom preservation across
rebuilds — the reason the replay exists (docs/d3-charts.md 'Zoom
Transform Preservation') — is unchanged: non-identity transforms replay
exactly as before, including charts with a non-1 defaultZoomK.

The identity replay had one observable side effect: its emit dismissed
a pinned tooltip on every rebuild. useD3ChartRenderer now performs that
dismissal explicitly when the replay is skipped, so behavior is
identical.
@Oseltamivir Oseltamivir requested a review from adibarra as a code owner June 12, 2026 04:24
@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
inferencemax-app Ready Ready Preview, Comment Jun 12, 2026 4:25am

Request Review

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