I'm having some issues with my forceSimulation in React (using Hooks). The problem is that whenever data is changed the position of each bubble is translated into the middle of the screen, rather than considering its previous position (see gif), making the bubbles jump in an annoying way.
I do not have much experience with forceSimulation, d3 and react-hooks in conjunction, and its hard to find any similar examples; so I would really appreciate any guidance.
import React, { useEffect, useRef } from 'react'
import { forceSimulation, forceX, forceY, forceCollide, select } from 'd3'
export default function Bubbles({ data, width, height }) {
  const ref = useRef()
  const svg = select(ref.current)
  useEffect(() => {
    const simulation = forceSimulation(data)
      .force('x', forceX().strength(0.02))
      .force('y', forceY().strength(0.02))
      .force(
        'collide',
        forceCollide((d) => {
          return d.value * 20 + 3
        }).strength(0.3)
      )
    simulation.on('tick', () =>
      svg
        .selectAll('circle')
        .data(data)
        .join('circle')
        .style('fill', () => 'red')
        .attr('cx', (d) => d.x)
        .attr('cy', (d) => d.y)
        .attr('r', (d) => d.value * 20)
    )
  }, [data])
  return (
    <svg
      viewBox={`${-width / 2} ${-height / 2} ${height} ${width}`}
      width={width}
      height={height}
      ref={ref}
      style={{
        marginRight: '0px',
        marginLeft: '0px'
      }}
    ></svg>
  )
}

I think the problem here is that React renders the SVG, but D3 renders the circles. When React re-renders on update it creates the SVG and discards any children because it doesn't know about them.
Thus the data can't be bound to the DOM in the usual D3 way, but should be stored in your React component's state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With