d3.js - 在 d3 力定向网络图中更新带有标签的节点和链接未正确删除节点

标签 d3.js d3-force-directed

我正在尝试创建一个 d3 力定向网络图,其中基于给定的网络,我可以通过修改链接和节点来更新网络,并在 svg 中重新更新它们。

我调整了代码,以便可以创建一个包含每个节点圆的 g 元素,以便我可以在同一个 g 元素内添加文本。

但是现在标签工作正常,但是当我通过先单击按钮 3 然后单击按钮 1 从图 3 转换到图 1 时,冗余的链接(a->d、a->f)被完美删除,但是冗余节点(e 和 f)保留在 svg 中。

我无法弄清楚这是否是一个错误的选择,或者我是否需要在 tick() 函数中进行一些调整?

这是代码:

    var height = 200;
    var width = 200;
    
    const graph = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 }
        ]
    }
    
    const graph2 = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 },
            { "source": "a", "target": "d", "value": 1 }
        ]
    }
    
    const graph3 = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 },
            { "name": "e", "group": 4 },
            { "name": "f", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 },
            { "source": "a", "target": "d", "value": 1 },
            { "source": "f", "target": "a", "value": 1 }
        ]
    }
    
    var simulation = d3.forceSimulation()
        .force("ct", d3.forceCenter(height / 2, width / 2))
        .force("link", d3.forceLink().id(function(d) { return d.name; })
            .distance(50).strength(2))
        .force("charge", d3.forceManyBody().strength(-240))
        // use forceX and forceY instead to change the relative positioning
        // .force("centering", d3.forceCenter(width/2, height/2))
        .force("x", d3.forceX(width / 2))
        .force("y", d3.forceY(height / 2))
        .on("tick", tick);
    
    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);
    
    svg.append("g").attr("class", "links");
    svg.append("g").attr("class", "nodes");
    
    function start(graph) {
    
        var linkElements = svg.select(".links").selectAll(".link").data(graph.links);
    
        linkElements.enter().append("line").attr("class", "link");
        linkElements.exit().remove();
    
        var nodeElements = svg.select(".nodes").selectAll(".node")
            .data(graph.nodes, function(d) { return d.name })
            .enter().append("g")
            .attr("class", "node");
    
        var circles = nodeElements.append("circle")
            .attr("r", 8);
    
        var labels = nodeElements.append("text")
            .text(function(d) { return d.name; })
            .attr("x", 10)
            .attr("y", 10);
    
        nodeElements.exit().remove();
    
        simulation.nodes(graph.nodes);
        simulation.force("link").links(graph.links);
        simulation.alphaTarget(0.1).restart();
    }
    
    function tick() {
        var nodeElements = svg.select(".nodes").selectAll(".node");
        var linkElements = svg.select(".links").selectAll(".link");
    
        nodeElements.attr("transform", function(d) {
                return "translate(" + d.x + "," + d.y + ")";
            })
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));
    
        linkElements.attr("x1", function(d) { return d.source.x; })
            .attr("y1", function(d) { return d.source.y; })
            .attr("x2", function(d) { return d.target.x; })
            .attr("y2", function(d) { return d.target.y; });
    }
    
    function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.1).restart();
        d.fx = d.x;
        d.fy = d.y;
    }
    
    function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
    }
    
    function dragended(d) {
        if (!d3.event.active) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
    }
    
    start(graph);
    
    
    document.getElementById('btn1').addEventListener('click', function() {
        start(graph);
    });
    
    document.getElementById('btn2').addEventListener('click', function() {
        start(graph2);
    });
    
    document.getElementById('btn3').addEventListener('click', function() {
        start(graph3);
    });
    .link {
            stroke: #000;
            stroke-width: 1.5px;
        }

    .node {
        stroke-width: 1.5px;
    }

    text {
          font-family: sans-serif;
          font-size: 10px;
          fill: #000000;
        }
    <body>
    <div>
        <button id='btn1'>1</button>
        <button id='btn2'>2</button>
        <button id='btn3'>3</button>
    </div>
    </body>
    <script src="https://d3js.org/d3.v5.min.js"></script>

这是代码的 jsfiddle 版本:https://jsfiddle.net/syedarehaq/myd0h5w1/

最佳答案

在提供的代码中,变量nodeElements包含输入选择,而不是整个数据绑定(bind)选择。

var nodeElements = svg.select(".nodes").selectAll(".node")
      .data(graph.nodes, function(d) { return d.name })
      .enter().append("g")
        .attr("class", "node");

nodeElements 声明不应包含 .enter 位 - 它应该类似于 linkSelection 变量声明:

var nodeElements = svg.select(".nodes").selectAll(".node")
      .data(graph.nodes, function(d) { return d.name })

然后,为了仅将新的圆圈和文本附加到输入的 g 元素,请进行如下调整:

var enterSelection = nodeElements.enter().append("g")
      .attr("class", "node");

var circles = enterSelection.append("circle")
      .attr("r", 8);

var labels = enterSelection.append("text")
      .text(function(d) { return d.name; })
      .attr("x", 10)
      .attr("y", 10);

exit 函数调用现在按预期工作。

下面代码片段中的演示。

var height = 200;
    var width = 200;
    
    const graph = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 }
        ]
    }
    
    const graph2 = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 },
            { "source": "a", "target": "d", "value": 1 }
        ]
    }
    
    const graph3 = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 },
            { "name": "e", "group": 4 },
            { "name": "f", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 },
            { "source": "a", "target": "d", "value": 1 },
            { "source": "f", "target": "a", "value": 1 }
        ]
    }
    
    var simulation = d3.forceSimulation()
        .force("ct", d3.forceCenter(height / 2, width / 2))
        .force("link", d3.forceLink().id(function(d) { return d.name; })
            .distance(50).strength(2))
        .force("charge", d3.forceManyBody().strength(-240))
        // use forceX and forceY instead to change the relative positioning
        // .force("centering", d3.forceCenter(width/2, height/2))
        .force("x", d3.forceX(width / 2))
        .force("y", d3.forceY(height / 2))
        .on("tick", tick);
    
    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);
    
    svg.append("g").attr("class", "links");
    svg.append("g").attr("class", "nodes");
    
    function start(graph) {
    
        var linkElements = svg.select(".links").selectAll(".link").data(graph.links);
    
        linkElements.enter().append("line").attr("class", "link");
        linkElements.exit().remove();
    
        var nodeElements = svg.select(".nodes").selectAll(".node")
            .data(graph.nodes, function(d) { return d.name })
        
        var enterSelection = nodeElements.enter().append("g")
            .attr("class", "node");
    
        var circles = enterSelection.append("circle")
            .attr("r", 8);
    
        var labels = enterSelection.append("text")
            .text(function(d) { return d.name; })
            .attr("x", 10)
            .attr("y", 10);
    
        nodeElements.exit().remove();
    
        simulation.nodes(graph.nodes);
        simulation.force("link").links(graph.links);
        simulation.alphaTarget(0.1).restart();
    }
    
    function tick() {
        var nodeElements = svg.select(".nodes").selectAll(".node");
        var linkElements = svg.select(".links").selectAll(".link");
    
        nodeElements.attr("transform", function(d) {
                return "translate(" + d.x + "," + d.y + ")";
            })
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));
    
        linkElements.attr("x1", function(d) { return d.source.x; })
            .attr("y1", function(d) { return d.source.y; })
            .attr("x2", function(d) { return d.target.x; })
            .attr("y2", function(d) { return d.target.y; });
    }
    
    function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.1).restart();
        d.fx = d.x;
        d.fy = d.y;
    }
    
    function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
    }
    
    function dragended(d) {
        if (!d3.event.active) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
    }
    
    start(graph);
    
    
    document.getElementById('btn1').addEventListener('click', function() {
        start(graph);
    });
    
    document.getElementById('btn2').addEventListener('click', function() {
        start(graph2);
    });
    
    document.getElementById('btn3').addEventListener('click', function() {
        start(graph3);
    });
.link {
            stroke: #000;
            stroke-width: 1.5px;
        }

    .node {
        stroke-width: 1.5px;
    }

    text {
          font-family: sans-serif;
          font-size: 10px;
          fill: #000000;
        }
<body>
    <div>
        <button id='btn1'>1</button>
        <button id='btn2'>2</button>
        <button id='btn3'>3</button>
    </div>
    </body>
    <script src="https://d3js.org/d3.v5.min.js"></script>

关于d3.js - 在 d3 力定向网络图中更新带有标签的节点和链接未正确删除节点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61200013/

相关文章:

javascript - 在 D3 JS 中显示半圆环饼图的值

javascript - D3.js 缩放从数据集中返回错误值

javascript - 如何在 D3 map 上添加线条箭头(标记)?

javascript - 如何更改 d3JS 力定向图中所有突出显示节点的颜色?

javascript - 使用 svg 变换更新位置时,力模拟会出现抖动

javascript - D3 缩放不起作用

javascript - 如何使d3折线图中的线条更平滑?

d3.js - D3 强制渲染速度

javascript - 使用 D3.js 修复力图节点的位置

javascript - D3 调整linkText位置