Skip to content

Commit 5bcc299

Browse files
JordanNanosclaudeadibarra
authored
fix: use useLayoutEffect for D3 chart rendering to prevent frame mismatches (#175)
When switching the y-axis metric on the GPU chart, dots and roofline paths could briefly appear out of sync. The useEffect + setTimeout(0) pattern deferred D3 rendering to after the browser paint, allowing a stale frame to be visible. Switching to useLayoutEffect ensures all D3 updates (dots, lines, axes, zoom restore) are applied atomically before the browser paints. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Alec Ibarra <93070681+adibarra@users.noreply.github.com>
1 parent 81a87d9 commit 5bcc299

1 file changed

Lines changed: 6 additions & 6 deletions

File tree

packages/app/src/lib/d3-chart/D3Chart/useD3ChartRenderer.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useRef } from 'react';
1+
import { useLayoutEffect, useRef } from 'react';
22
import * as d3 from 'd3';
33

44
import { computeTooltipPosition } from '../layers/scatter-points';
@@ -90,7 +90,9 @@ export function useD3ChartRenderer<T>(props: D3ChartProps<T>, deps: RendererDeps
9090
const prevDataRef = useRef(data);
9191
const prevScalesRef = useRef({ xScaleConfig, yScaleConfig });
9292

93-
useEffect(() => {
93+
// useLayoutEffect ensures D3 renders synchronously before browser paint,
94+
// preventing a frame where dots and lines are out of sync during y-axis metric changes.
95+
useLayoutEffect(() => {
9496
if (!svgRef.current || !tooltipRef.current || dimensions.width === 0) return;
9597
if (data.length === 0 && layers.every((l) => l.type !== 'custom')) return;
9698

@@ -102,7 +104,7 @@ export function useD3ChartRenderer<T>(props: D3ChartProps<T>, deps: RendererDeps
102104
prevDataRef.current = data;
103105
prevScalesRef.current = { xScaleConfig, yScaleConfig };
104106

105-
const timeoutId = setTimeout(() => {
107+
{
106108
if (!svgRef.current || !tooltipRef.current) return;
107109

108110
// Preserve zoom transform before structure rebuild
@@ -470,9 +472,7 @@ export function useD3ChartRenderer<T>(props: D3ChartProps<T>, deps: RendererDeps
470472

471473
// ── User onRender callback ──
472474
onRender?.(ctx);
473-
}, 0);
474-
475-
return () => clearTimeout(timeoutId);
475+
}
476476
// We intentionally list specific deps rather than the entire props object.
477477
}, [
478478
data,

0 commit comments

Comments
 (0)