Skip to content

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

Merged
adibarra merged 2 commits into
masterfrom
perf/skip-identity-zoom-replay
Jun 16, 2026
Merged

perf(charts): skip identity zoom-transform replay on chart rebuild#445
adibarra merged 2 commits 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
Changes the shared D3 chart zoom setup path used on every rebuild; risk is mitigated by focused tests and preserved behavior for non-identity zoom and tooltip dismissal.

Overview
Stops redundant full chart re-renders when zoom is at identity (the common case) by not calling zoom.transform during setupZoom unless the stored transform or the SVG node’s internal __zoom state is non-identity.

Non-identity zoom (user pan/zoom, defaultZoomK) still replays exactly as before. useD3ChartRenderer explicitly dismisses a pinned tooltip on rebuild when that identity replay is skipped, matching the old side effect of the zoom handler firing.

Adds useChartZoom.setup.test.ts covering identity skip, zoom preservation, ref sync, defensive node/ref desync, and defaultZoomK initial apply.

Reviewed by Cursor Bugbot for commit 0561024. 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 16, 2026 12:43am

Request Review

@adibarra adibarra merged commit 1a31abd into master Jun 16, 2026
18 checks passed
@adibarra adibarra deleted the perf/skip-identity-zoom-replay branch June 16, 2026 00:47
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