首页 > 解决方案 > D3 selection.join() 实例

问题描述

我试图了解join()D3v6 中的功能。显然我误解了官方的 D3 join() 文档。https://github.com/d3/d3-selection/blob/v2.0.0/README.md#selection_join,因为我认为我可以使用 join() 而不是典型的 append()、enter()、remove() , exit() 模式并节省大量代码。

下面的代码在开始时创建了 3 个节点,并带有一个上下文菜单。我可以添加和删除数据initialize()再次调用该函数来重绘节点。问题是,“旧节点”仍然存在。不是只添加 1 个节点,而是重新绘制整个节点组。

我错过了什么?

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>D3 JOIN Test</title>
    <!-- call external d3.js framework -->
    <script src="https://d3js.org/d3.v6.js"></script>
</head>

<style>
    body {
        overflow: hidden;
        margin: 0px;
    }

    .canvas {
        background-color: rgb(220, 220, 220);
    }

    #context-menu {
        font-family: "Open Sans", sans-serif;
        position: fixed;
        z-index: 10000;
        width: 190px;
        background: whitesmoke;
        border: 2px;
        border-radius: 6px;
        border-color: white;
        border-style: solid;
        transform: scale(0);
        transform-origin: top left;
    }

    #context-menu.active {
        transform: scale(1);
        transition: transform 200ms ease-in-out;
    }

    #context-menu .item {
        padding: 8px 10px;
        font-size: 15px;
        color: black;
    }

    #context-menu .item i {
        display: inline-block;
        margin-right: 5px;
    }

    #context-menu hr {
        margin: 5px 0px;
        border-color: whitesmoke;
    }

    #context-menu .item:hover {
        background: lightblue;
    }
</style>

<body>
    <!-- right click context menu -->
    <div id="context-menu">
        <div id="addObject" class="item">
            <i class="fa fa-plus-circle"></i> Add Node
        </div>
        <div id="removeObject" class="item">
            <i class="fa fa-minus-circle"></i> Remove Node
        </div>
    </div>

    <svg id="svg"> </svg>

    <script>
        var nodes = [
            {
                "id": 1,
            },
            {
                "id": 2,
            },
            {
                "id": 3,
            }
        ]

        // declare initial variables
        var svg = d3.select("svg")
            width = window.innerWidth
            height = window.innerHeight
            thisNode = null;
            d = null;

        // define cavnas area to draw everything
        svg = d3.select("svg")
            .attr("class", "canvas")
            .attr("width", width)
            .attr("height", height)
            .call(d3.zoom().on("zoom", function (event) {
                svg.attr("transform", event.transform)
            }))
            .append("g")

        // remove zoom on dblclick listener
        d3.select("svg").on("dblclick.zoom", null)

        var nodesContainer = svg.append("g").attr("class", "nodesContainer")

        // iniital force simulation
        var simulation = d3.forceSimulation()
            .force("charge", d3.forceManyBody().strength(-100))
            .force("center", d3.forceCenter(width / 2, height / 2))
            .force("attraceForce", d3.forceManyBody().strength(70));

        initialze()

        function initialze() {
            
            node = nodesContainer.selectAll(".node")
            .data(nodes, d => d.id)
                .join("circle")
                .attr("r", 30)
                .attr("fill", "whitesmoke")
                .attr("stroke", "white")
                .call(d3.drag()
                    .on("start", dragStarted)
                    .on("drag", dragged)
                    .on("end", dragEnded)
                )
                .on("mouseenter", mouseEnter)
                .on("mouseleave", mouseLeave)
                .on("contextmenu", contextMenu)
                
                simulation
                .nodes(nodes)
                .on("tick", ticked);
                
        }
                

        function mouseEnter(event, d) {
            thisNode = d

            d3.select(this).style("fill", "lightblue")
        }

        function mouseLeave(d) {
            d3.select(this).style("fill", "whitesmoke")
        }

        function contextMenu(event, d) {
            thisNode = d

            event.preventDefault()

            var contextMenu = document.getElementById("context-menu")
            contextMenu.style.top = event.clientY + "px"
            contextMenu.style.left = event.clientX + "px"
            contextMenu.classList.add("active")

            window.addEventListener("click", function () {
                contextMenu.classList.remove("active")
            })

            document.getElementById("addObject").addEventListener("click", addNodeClicked)
            document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
        }

        function addNodeClicked() {

            addNode(thisNode)
        }

        function addNode(d) {
            var newID = Math.floor(Math.random() * 100000)

            nodes.push({ "id": newID, })

            initialze()
        }

        function removeNodeClicked() {
            removeNode(thisNode)
        }

        function removeNode(d) {      
            var indexOfNodes = nodes.indexOf(d)

            nodes.splice(indexOfNodes, 1)

            initialze()
        }

        function ticked() {
            node
                .attr("transform", function (d) {
                    return "translate(" + d.x + ", " + d.y + ")";
                });
        }

        function dragStarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }

        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }

        function dragEnded(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = undefined;
            d.fy = undefined;
        }

    </script>
</body>

</html>

标签: javascriptd3.js

解决方案


您正在选择一个您从未设置过的班级。因此,只需执行以下操作:

.attr("class", "node")

这是您进行更改的代码:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>D3 JOIN Test</title>
    <!-- call external d3.js framework -->
    <script src="https://d3js.org/d3.v6.js"></script>
</head>

<style>
    body {
        overflow: hidden;
        margin: 0px;
    }

    .canvas {
        background-color: rgb(220, 220, 220);
    }

    #context-menu {
        font-family: "Open Sans", sans-serif;
        position: fixed;
        z-index: 10000;
        width: 190px;
        background: whitesmoke;
        border: 2px;
        border-radius: 6px;
        border-color: white;
        border-style: solid;
        transform: scale(0);
        transform-origin: top left;
    }

    #context-menu.active {
        transform: scale(1);
        transition: transform 200ms ease-in-out;
    }

    #context-menu .item {
        padding: 8px 10px;
        font-size: 15px;
        color: black;
    }

    #context-menu .item i {
        display: inline-block;
        margin-right: 5px;
    }

    #context-menu hr {
        margin: 5px 0px;
        border-color: whitesmoke;
    }

    #context-menu .item:hover {
        background: lightblue;
    }
</style>

<body>
    <!-- right click context menu -->
    <div id="context-menu">
        <div id="addObject" class="item">
            <i class="fa fa-plus-circle"></i> Add Node
        </div>
        <div id="removeObject" class="item">
            <i class="fa fa-minus-circle"></i> Remove Node
        </div>
    </div>

    <svg id="svg"> </svg>

    <script>
        var nodes = [
            {
                "id": 1,
            },
            {
                "id": 2,
            },
            {
                "id": 3,
            }
        ]

        // declare initial variables
        var svg = d3.select("svg")
            width = window.innerWidth
            height = window.innerHeight
            thisNode = null;
            d = null;

        // define cavnas area to draw everything
        svg = d3.select("svg")
            .attr("class", "canvas")
            .attr("width", width)
            .attr("height", height)
            .call(d3.zoom().on("zoom", function (event) {
                svg.attr("transform", event.transform)
            }))
            .append("g")

        // remove zoom on dblclick listener
        d3.select("svg").on("dblclick.zoom", null)

        var nodesContainer = svg.append("g").attr("class", "nodesContainer")

        // iniital force simulation
        var simulation = d3.forceSimulation()
            .force("charge", d3.forceManyBody().strength(-100))
            .force("center", d3.forceCenter(width / 2, height / 2))
            .force("attraceForce", d3.forceManyBody().strength(70));

        initialze()

        function initialze() {
            
            node = nodesContainer.selectAll(".node")
            .data(nodes, d => d.id)
                .join("circle")
                .attr("r", 30)
                .attr("class", "node")
                .attr("fill", "whitesmoke")
                .attr("stroke", "white")
                .call(d3.drag()
                    .on("start", dragStarted)
                    .on("drag", dragged)
                    .on("end", dragEnded)
                )
                .on("mouseenter", mouseEnter)
                .on("mouseleave", mouseLeave)
                .on("contextmenu", contextMenu)
                
                simulation
                .nodes(nodes)
                .on("tick", ticked);
                
        }
                

        function mouseEnter(event, d) {
            thisNode = d

            d3.select(this).style("fill", "lightblue")
        }

        function mouseLeave(d) {
            d3.select(this).style("fill", "whitesmoke")
        }

        function contextMenu(event, d) {
            thisNode = d

            event.preventDefault()

            var contextMenu = document.getElementById("context-menu")
            contextMenu.style.top = event.clientY + "px"
            contextMenu.style.left = event.clientX + "px"
            contextMenu.classList.add("active")

            window.addEventListener("click", function () {
                contextMenu.classList.remove("active")
            })

            document.getElementById("addObject").addEventListener("click", addNodeClicked)
            document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
        }

        function addNodeClicked() {

            addNode(thisNode)
        }

        function addNode(d) {
            var newID = Math.floor(Math.random() * 100000)

            nodes.push({ "id": newID, })

            initialze()
        }

        function removeNodeClicked() {
            removeNode(thisNode)
        }

        function removeNode(d) {      
            var indexOfNodes = nodes.indexOf(d)

            nodes.splice(indexOfNodes, 1)

            initialze()
        }

        function ticked() {
            node
                .attr("transform", function (d) {
                    return "translate(" + d.x + ", " + d.y + ")";
                });
        }

        function dragStarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }

        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }

        function dragEnded(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = undefined;
            d.fy = undefined;
        }

    </script>
</body>

</html>


推荐阅读