Responsive D3 Charts in React: ResizeObserver or the viewBox attribute?

Viewed 773

I recently came across this video from a tutorial series on D3 + React. In this video, to resize the graph, a custom hook is created that uses ResizeObserver:

const useResizeObserver = ref => {
  const [dimensions, setDimensions] = useState(null);
  useEffect(() => {
    const observeTarget = ref.current;
    const resizeObserver = new ResizeObserver(entries => {
      entries.forEach(entry => {
        setDimensions(entry.contentRect);
      });
    });
    resizeObserver.observe(observeTarget);
    return () => {
      resizeObserver.unobserve(observeTarget);
    };
  }, [ref]);
  return dimensions;
};

I've always seen previously / used before the viewBox attribute to make SVG's responsive, as this article (and many others) discusses. My question is: is there a reason (performance, simplicity) to use this useResizeObserver custom hook over simply using viewBox? With viewBox, I don't need to create a custom hook, or have a wrapper div around my SVGs (as he explains in the video), or have to use refs... viewBox seems simpler. However, apparently ResizeObserver is a new API to get the dimensions of elements, and perhaps there are advantages to using it over viewBox that are not obvious.

1 Answers

Issues with viewBox

Although viewBox is the quickest solution, it has several downsides:

  • viewBox tries to satisfy the aspect ratio. If your chart doesn't have an aspect ratio of 1:1, this happens: Bar chart with perfect container aspect ratio and unperfect, boxing the chart in The chart didn't fit itself to the container. Instead viewBox kept the aspect ratio and added padding. We can stretch with preserveAspectRatio=none:Stretched bar chart with stretched text
  • Chart (and text) scaling. Not preserving aspect ratio introduces another problem. If you use height and width, you can define your text to be a certain size in CSS - but not with viewBox. Take the case of a 0 0 600 300 viewBoxed chart fitting a 1200*600 page. In this case, everything gets scaled, including text, as you can see above: you can no longer set the font size. This won't happen just to text: other elements that don't stretch perfectly, like circles, will have the same issue.

At first glance, viewBox seems much simpler. But because of the footguns highlighted above, viewBox becomes very complex to use in practice.

Manual scaling

The solution is to size the chart manually using height and width, avoiding all the above issues. Say we have a chart component that renders itself based on height and width. The process becomes:

  • set the width and height on the first render, fit to the container. The container needs its own width and height set.
  • Use a ResizeObserver on the container, which will notify us when its size changes.
  • When the chart is resized, get the new height and width, and use them to render the chart.

When the height and width are set manually, we size only what needs to be scaled. For example, with the above bar chart:

  • We could set the bars' size relative to the height and width of the chart.
  • If we have text, we could position it relative to the height and width, but make the text size fixed, which would be impossible with viewBox, since it scales everything:Fixed size text

Conclusion

  • Use viewBox as a quick solution if you have a chart with elements that don't get affected by stretching: for example, with rectangles, or if you don't care about the above issues.
  • Use manual scaling if you have elements that cannot be stretched, like text or circles, and need text to stay the size you tell it to.

If you don't care about the aspect ratio issue, and can work around your text being scaled up or down, then viewBox can be the right choice, as long as you're aware of its pitfalls.

However, in practice, manual scaling ends up being the better choice in the vast majority of cases, because there are very little cases where you have only stretchable elements in a chart.

Sources

Images taken from Responsive SVG charts — viewBox may not be the answer.

Related