React with D3 - 只渲染新项目而不更新或删除旧项目

React with D3 - Only rendering new items without updating or removing old ones

我创建了一个尺寸图例,除了当我对其进行更改时(例如更新数据——旧版本保持渲染状态,新版本在顶部渲染。如何按照 D3 的预期实现更新、添加和删除元素?

我知道 React 和 D3 都想控制 DOM 所以很难一起工作,到目前为止我的代码来自非反应教程 - 所以我相信错误一定是与我一起使用 React 和 D3 的方式有关。

这是我的代码

import React, { useRef, useEffect } from 'react';
import { select, scaleSqrt } from 'd3';

function D3SizeBar(props) {

    const svgRef = useRef();

    useEffect(() => {

        const sizeLegend = (selection, props) => {
            const {
                sizeScale,
                spacing,
                textOffset,
                numTicks,
                circleFill
            } = props;
        
        const ticks = sizeScale.ticks(numTicks).filter(d => d !== 0)
        
        const groups = selection.selectAll('g').data(ticks);
    
        const groupsEnter = groups.enter().append('g');
    
        groupsEnter
            .merge(groups)
            .attr(`transform`, (d, i) =>
                `translate(0, ${i * spacing})`
            );
    
        groups
            .exit()
            .remove();
        
        groupsEnter
            .append('circle')
            .merge(groups.select('circle'))
            .attr('r', sizeScale)
            .attr('fill', circleFill)
        
        groupsEnter
            .append('text')
            .merge(groups.select('text'))
            .text(d => d)
            .attr('dy', '0.32em')
            .attr('x', d => sizeScale(d) + textOffset)
        }

        const data = [0, 0]
        data.splice(0, 1, Math.max.apply(Math, props.sValueArray))
        data.splice(1, 1, Math.min.apply(Math, props.sValueArray))
        console.log(data)

        const sizeScale = scaleSqrt()
            .domain(data)
            .range([20, 5])

        const svg = select(svgRef.current);
        svg
            .append('g')
            .attr('transform', `translate(30,20)`)
            .call(sizeLegend, {
                sizeScale,
                spacing: 25,
                textOffset: 10,
                numTicks: 3,
                circleFill: 'rgba(0, 0, 0, 0.5)'
            }
        );
    }, [props.sValueArray]);

    return (
        <svg ref={svgRef}/>
    )
}

export default D3SizeBar;

如果有人足够精通将 React 和 D3 一起使用,我将非常感谢您对我出错的地方的帮助。 `sValueArray' 只是一个最大值的数组。价值 = 50 和分钟。值 = 10.

这里是一个示例,它正在将间距从 25 更改为 30。

这是因为您在添加其他元素之前没有清除该元素的内容。

您从 <svg /> 元素中取出 ref,并通过 d3.select (ref.current) 选择元素,但 ref 每次都保持不变 useEffect 是 运行 ,就在你 运行 select (ref.current) 时它已经有了 children。因为每次 React 发送一个新的网络更新, useEffect 被更新而 ref 保持不变。

解决方案

在添加新的 object:

之前,您可以简单地清除 parent svg 中存在的所有 children
svg.selectAll("*").remove();

代码

import React, { useRef, useEffect } from 'react';
import { select, scaleSqrt } from 'd3';

function D3SizeBar(props) {

    const svgRef = useRef();

    useEffect(() => {

        const sizeLegend = (selection, { sizeScale,
            spacing,
            textOffset,
            numTicks,
            circleFill }) => {

            const ticks = sizeScale.ticks(numTicks).filter(d => d !== 0)

            const groups = selection.selectAll('g').data(ticks);

            const groupsEnter = groups.enter().append('g');

            groupsEnter
                .merge(groups)
                .attr(`transform`, (d, i) =>
                    `translate(0, ${i * spacing})`
                );

            groups
                .exit()
                .remove();

            groupsEnter
                .append('circle')
                .merge(groups.select('circle'))
                .attr('r', sizeScale)
                .attr('fill', circleFill)

            groupsEnter
                .append('text')
                .merge(groups.select('text'))
                .text(d => d)
                .attr('dy', '0.32em')
                .attr('x', d => sizeScale(d) + textOffset)
        }

        const data = [0, 0]
        data.splice(0, 1, Math.max.apply(Math, props.sValueArray))
        data.splice(1, 1, Math.min.apply(Math, props.sValueArray))
        console.log(data)

        const sizeScale = scaleSqrt()
            .domain(data)
            .range([20, 5])

        const svg = select(svgRef.current);
        svg.selectAll("*").remove() // add this before the append.
        svg
            .append('g')
            .attr('transform', `translate(30,20)`)
            .call(sizeLegend, {
                sizeScale,
                spacing: 50,
                textOffset: 10,
                numTicks: 3,
                circleFill: 'rgba(0, 0, 0, 0.5)'
            }
            );
    }, [props.sValueArray]);

    return (
        <svg ref={svgRef} />
    )
}

export default D3SizeBar;