D3 图表在 React.js 组件上复制

D3 chart is duplicating on React.js component

这是在 React 应用程序上,可能出了什么问题?每次都会附加一个新的 svg,而不是存在更新的 svg... 我不确定我应该添加哪些其他详细信息,我已经包含了您可能需要帮助我的所有信息,请让我知道是否还有其他我应该添加的内容。谢谢你。

这是我的组件,我正在通过 props 传递数据。

export const FourDirectionsTimeChart = ({data}) => {
    useEffect(() => {
        const width = document.getElementById("container").clientWidth;
        const height = document.getElementById("container").clientHeight;
        const R = (width + height) / 8;
        const CX = width / 2;
        const CY = height / 2;
        const smallR = R * 0.1;
        const circleColor = "bisque";
        const itemColor = "#3F4200";

        let svg = d3.select("#container")
            .append("svg")
            .attr("preserveAspectRatio", "xMinYMin meet")
            .attr("viewBox", `0 0 ${width} ${height}`)

        let mainCircle = svg.append("circle")
            .attr("fill", circleColor)
            .attr("r", R)
            .attr("cx", CX)
            .attr("cy", CY);

        let centerCircle = svg.append("circle")
            .attr("fill", "white")
            .attr("r", smallR)
            .attr("cx", CX)
            .attr("cy", CY);

        const timePercentage = (time) => {
            const percentage = (time * 100 / 23) / 100;
            return percentage;
        };
        function timeToRadius(time) {
            return (smallR + timePercentage(time) * (R - smallR))
        }

        // Times concentric circles ---
        for (let i = 0; i <= 23; i += 4) {
            svg.append("circle")
                .attr("fill", "none")
                .attr("cx", CX)
                .attr("cy", CY)
                .attr("stroke-dasharray", "4 20")
                .attr("stroke", "gray")
                .attr("stroke-width", 1)
                .attr("r", timeToRadius(i))
        }

        // Cardinal points ---
        const textTime = 25;
        const fontSize = R * 0.25;
        svg.append("text")
            .attr("dx", getPosition(0, textTime).x)
            .attr("dy", getPosition(0, textTime).y)
            .text("N")
            .attr("fill", "black")
            .attr("font-size", fontSize)
            .attr("text-anchor", "middle")
            .style("font-family", "serif")
        svg.append("text")
            .attr("dx", getPosition(180, textTime).x)
            .attr("dy", getPosition(180, textTime).y)
            .text("S")
            .attr("fill", "black")
            .attr("font-size", fontSize)
            .attr("text-anchor", "middle")
            .style("font-family", "serif")
            .attr("alignment-baseline", "hanging")
        svg.append("text")
            .attr("dx", getPosition(-90, textTime).x)
            .attr("dy", getPosition(-90, textTime).y)
            .text("E")
            .attr("fill", "black")
            .attr("font-size", fontSize)
            .attr("text-anchor", "start")
            .attr("alignment-baseline", "middle")
            .style("font-family", "serif");
        svg.append("text")
            .attr("dx", getPosition(90, textTime).x)
            .attr("dy", getPosition(90, textTime).y)
            .text("O")
            .attr("fill", "black")
            .attr("font-size", fontSize)
            .attr("text-anchor", "end")
            .attr("alignment-baseline", "middle")
            .style("font-family", "serif")

        // Ships positions --- 
        function getPosition(degrees, time) {
            const getRadians = (degrees) => degrees * Math.PI / 180 + Math.PI / 2;
            let x = (smallR + timePercentage(time) * (R - smallR)) * Math.cos(getRadians(degrees)) + CX;
            let y = (smallR + timePercentage(time) * (R - smallR)) * Math.sin(getRadians(degrees)) * -1 + CY;
            return { x, y };
        }

        // Data mapping ---
        let parsedData = [];
        (() => {
            data?.forEach((x) => {
                parsedData.push({
                    id: x.Patente,
                    course: x.Rumbo,
                    speed: x.Velocidad,
                    time: new Date(x.Fecha_gps).getHours()
                })
            });

            let position;
            parsedData.map(d => {
                position = getPosition(d.course, d.time);
                svg.append("circle")
                    .attr("fill", () => {
                        if (d.speed == 0) return "brown";
                        else return "brown";
                    })
                    .attr("r", R * 0.015)
                    .attr("cx", position.x)
                    .attr("cy", position.y)
                    .attr("opacity", 0.4);
            });
        })();

    }, [data]);

    return (
        <div id="container" style={{ width: '100%', height: '100%' }}></div>
    )
}

您认为 let svg = d3.select("#container").append("svg") 行的作用是什么?它选择 ID 为 "container" 的元素并向其附加一个新的 SVG 元素。

您基本上可以做以下两件事之一:

  1. Select 和 remove 容器中所有现有的 SVG 元素,然后在更新时从头开始绘制图表:d3.select("#container svg").remove(),然后 d3.select("#container").append("svg")
  2. 将初始化(附加 svg、设置大小、绘制轴)逻辑与可能需要 运行 多次(更新值)的逻辑分开,并确保在更新时仅调用第二部分.这更复杂,但也更有效,尤其是在像 React 这样已经 DOM 的生态系统中。