I am attempting to draw a figure on a website, as an SVG. I am using d3 with react in order to draw the figure.
This is working pretty well in terms of defining the SVG as a react component, but I am not completely happy with the resizing behavior. For instance, if I have a figure which resizes width-wise, because of the way I am handing the resizing, there’s a slight delay between when the page resizes and when the chart is redrawn, giving a sort of gummy/rubber band-like effect.
The sizing of my SVG is handled by this component:
import { ReactNode, useMemo } from "react";
import * as d3 from "d3";
type ChartProps = {
width: number;
height: number;
};
export const Chart = (
props: {
children?: ReactNode;
} & ChartProps,
) => {
const { children, width, height } = props;
return (
<svg
viewBox={`0 0 ${width} ${height}`}
preserveAspectRatio="xMidYMid meet"
style={{
width: "100%",
height: "100%",
}}
>
{children}
</svg>
);
};
And the width and height are passed from the parent component, which uses a ResizeObserver
to retrieve them:
const containerRef = useRef<HTMLDivElement>(null);
const [sizes, setSizes] = useState<{
containerWidth: number;
containerHeight: number;
}>({
containerWidth: initialDimension.width,
containerHeight: initialDimension.height,
});
const setContainerSize = useCallback(
(newWidth: number, newHeight: number) => {
setSizes((prevState) => {
const roundedWidth = Math.round(newWidth);
const roundedHeight = Math.round(newHeight);
if (
prevState.containerWidth === roundedWidth &&
prevState.containerHeight === roundedHeight
) {
return prevState;
}
return {
containerWidth: roundedWidth,
containerHeight: roundedHeight,
};
});
},
[],
);
useEffect(() => {
let callback = (entries: ResizeObserverEntry[]) => {
const { width: containerWidth, height: containerHeight } =
entries[0].contentRect;
setContainerSize(containerWidth, containerHeight);
};
const observer = new ResizeObserver(callback);
const { width: containerWidth, height: containerHeight } =
containerRef.current.getBoundingClientRect();
setContainerSize(containerWidth, containerHeight);
observer.observe(containerRef.current);
return () => {
observer.disconnect();
};
}, [setContainerSize]);
return (
<div
ref={containerRef}
>
<Chart width={size. containerWidth} height={containerHeight}> ... </Chart>
</div>
);
So I think what is happening is, the resize observer is getting the update event, and then the state is being set, triggering the re-render of the Chart component, but because of the way the state update works this is not happening within the same frame as the size update.
Is there a way to redraw the SVG exactly in the frame when the size of the container changes?