1
votes

I have a problem with the dynamic change of cx and cy attributes depending on the size of window.innerHeight/window.innerWidth. I am sending to component window.innerHeight/window.innerWidth as hight/width and it look like this:

const SomeChart = ({width, height}) => {

    const BubbleChartRef = useRef(null)
    const InitialData = [{'r':2,'x':2,'y':1},
                        {'r':4,'x':3,'y':2},
                        {'r':5,'x':7,'y':10},
                        {'r':7,'x':5,'y':3},
                        {'r':3,'x':8,'y':9}]

    useEffect(() => {
        
        const svg = d3.select(BubbleChartRef.current)
        let yScale = d3.scaleLinear().domain([0, 20]).range([0,height])
        let xScale = d3.scaleLinear().domain([0, 20]).range([0,width])
        svg.selectAll("circle")
          .data(InitialData)
          .enter()
          .append("circle")
          .attr('r', (d)=>d.r)
          .attr('cx', (d, i)=>xScale(d.x))
          .attr('cy', (d, i)=>yScale(d.y))
          .attr('stroke', 'black')
          .attr('fill', 'red')
          .style('stroke-width', '1px')
    }, [width, height])

    return <svg ref={BubbleChartRef} className='bubble-chart-svg'/>
}

this is bubble-chart-svg css class:

.bubble-chart-svg{
    width: 50vw;
    height: 50vh;
}

When I add console.log(xScale(4)) in I get information about the new position cx (after resize) but circle element in svg does not change.

Is it possible to change the position of these elements on my svg after changing the window size?

EDIT

Here is my controling window size component:

const BubbleChart = () => {
    const [height, setHeight] = useState(window.innerWidth);
    const [width, setWidth] = useState(window.innerHeight);

    const updateDimensions = useCallback(() => {
        setHeight(window.innerHeight);
        setWidth(window.innerWidth);     
    },[])

    useEffect(() => {
        window.addEventListener('resize', updateDimensions);
    }, []);

    useEffect(() => {
        updateDimensions();
        return () => window.removeEventListener('resize', updateDimensions);
    }, [])

    return <SomeChart width={width/2} height={height/2} ></SomeChart>
}
1

1 Answers

3
votes

The circles are not updating because the code inside useEffect is referencing only the enter selection, which doesn't update already rendered elements when the code runs multiple times. Note that, In React, re-renders don't delete the previous SVG, even though the SVG in the JSX is empty. This means that when there is a re-render, the circles are still in the SVG on the browser.

In earlier versions of D3, you could fix this by learning a coding pattern, called the general update pattern, a.k.a enter, update, exit pattern. This pattern was not trivial to understand, so in January 2019, the D3 team updated the API to simplify this process with a new method: selection.join. This method is available since d3 v5.8.0, more specifically d3-selection v1.4.0.

With selection.join, instead of using:

    svg.selectAll("circle")
          .data(InitialData)
          .enter()
          .append("circle")
          .attr('r', (d)=>d.r)
          .attr('cx', (d, i)=>xScale(d.x))
          .attr('cy', (d, i)=>yScale(d.y))

You can write:

    svg.selectAll("circle")
          .data(InitialData)
          .join("circle")
          .attr('r', (d)=>d.r)
          .attr('cx', (d, i)=>xScale(d.x))
          .attr('cy', (d, i)=>yScale(d.y))

With selection.join, every time this code runs, it will make sure that the rendered elements are in sync with the data and parameters: If a new data point appears, a new circle will be rendered (the "enter"); If the cx and cy changes, the circles will update (the "update"); If a data point is removed, the associated circle will be deleted (the "exit"). Due to the simplicity, D3 contributors have expressed on social media that selection.join is the preferred way to write D3 code.

If you still want to understand how the old enter, update, exit pattern used to work, which is useful for reading code examples from versions before selection.join, you can check this tutorial notebook by Mike Bostock.