For a new visualization I'm working on, I wanted to use D3's force layout to position nodes. I've used D3 force before, but never in React. I was curious how to apply the force to the DOM nodes in React, in a way that would be performant and not cause React to re-render the nodes on every tick.
I got some solid advice for Bia who had worked on a similar problem before. In her Lego Chart project, she uses a useState hook to rerender the nodes when the force layout ticks. This is a great solution, resulted in the following:
The interesting this solution is that the setRenderRounter
is used to trigger
the render of the component on every tick. This is a bit of a hack, but it
works. The downside is that it causes the component to re-render on every tick,
which is not ideal.
The useEffect triggers the initial simulation and applies the correct forces
based on the data. The important part is that the nodes
are creates and set to
the items before being passed to the .nodes()
method of the simulation. This
is what allows the simulation to update the items with new x and y coordinates
without rerendering the component. Then with the .alpha()
method, the
simulation is stopped once the elements settle to save performance.
Finally, the component returns a div
that contains an SVG element. The SVG
element contains a circle
element for each Node object in the 'items' array.
The cx
, cy
, and r
attributes of each circle
element are set to the x
,
y
, and count
properties of the corresponding Node object, respectively.
Alternative
This second approach is a little cleaner with the nodes from the simulation
being updated a layout property which can be mapped over. I've noticed that this
resulted in a little more type issues as I needed to extend the
SimulationNodeDatum with the Node type of the data point.
This is a little more complex, but I think it's a little cleaner.
In the useEffect there is now only a scoped instance of the simulation and callback functions for the tick are applied on initial load here. There is also a cleanup function for when the component unmounts.