简体   繁体   English

拼接数组去除力有向图中的错误节点

[英]Splicing array removes wrong node in force-directed graph

I got a D3 graph in place.我得到了一个 D3 图。 Where a click on a node should delete this node and the related link.单击节点应删除this节点和相关链接。 A hover on each node will display the name of it.将鼠标悬停在每个节点上将显示它的名称。 Further a console.log confirms the name of the node, which was deleted.此外, console.log确认已删除的节点名称。 The problem I got is, as soon as I try to delete a node which is not the last one from the array, the console confirms the deletion but the node just replaces another one.我遇到的问题是,一旦我尝试从数组中删除不是最后一个节点的节点,控制台就会确认删除,但该节点只是替换了另一个节点。 Where is my thinking mistake?我的思维错误在哪里?

在此处输入图片说明

在此处输入图片说明

 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Playground</title> <!-- favcon --> <link rel="icon" href="https://networkrepository.com/favicon.png"> <!-- call external d3.js framework --> <script src="https://d3js.org/d3.v4.js"></script> <!-- import multiselection framework --> <script src="https://d3js.org/d3-selection-multi.v1.js"></script> <!-- import "font awesome" stylesheet https://fontawesome.com/ --> <script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script> </head> <style> body { overflow: hidden; margin: 0px; } .canvas { background-color: rgb(220, 220, 220); } .link { stroke: rgb(0, 0, 0); stroke-width: 1px; } circle { background-color: whitesmoke; } </style> <body> <!-- create svg root element as a canvas --> <svg id="svg"></svg> <!-- call script where the main application is written --> <script> var graph = { "nodes": [ { "id": 0, "type": "company", "name": "Company", "context": [ { "name": "Company" } ], "icon": "\" }, { "id": 1, "type": "software", "name": "Software_1", "context": [ { "name": "Software_1" } ], "icon": "\", "parent": 1 }, { "id": 2, "type": "software", "name": "Software_2", "context": [ { "name": "Software_2" } ], "icon": "\", "parent": 1 }, { "id": 3, "type": "software", "name": "Software_3", "context": [ { "name": "Software_3" } ], "icon": "\", "parent": 1 }, { "id": 4, "type": "software", "name": "Software_4", "context": [ { "name": "Software_4" } ], "icon": "\", "parent": 1 }, { "id": 5, "type": "software", "name": "Software_5", "context": [ { "name": "Software_5" } ], "icon": "\", "parent": 4 }, { "id": 6, "type": "software", "name": "Software_6", "context": [ { "name": "Software_6" } ], "icon": "\", "parent": 4 }, { "id": 7, "type": "software", "name": "Software_7", "context": [ { "name": "Software_7" } ], "icon": "\", "parent": 5 }, { "id": 8, "type": "software", "name": "Software_8", "context": [ { "name": "Software_8" } ], "icon": "\", "parent": 5 } ], "links": [ { "source": 1, "target": 0, "type": "uses" }, { "source": 2, "target": 0, "type": "uses" }, { "source": 3, "target": 0, "type": "uses" }, { "source": 4, "target": 0, "type": "uses" }, { "source": 5, "target": 3, "type": "uses" }, { "source": 6, "target": 3, "type": "uses" }, { "source": 7, "target": 4, "type": "uses" }, { "source": 8, "target": 4, "type": "uses" } ] } // declare initial variables var svg = d3.select("svg") width = window.innerWidth height = window.innerHeight node = null link = 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 () { svg.attr("transform", d3.event.transform) })) .append("g") // remove zoom on dblclick listener d3.select("svg").on("dblclick.zoom", null) // append markers to svg svg.append('defs').append('marker') .attrs({ 'id': 'arrowhead', 'viewBox': '-0 -5 10 10', 'refX': 14, 'refY': 0, 'orient': 'auto', 'markerWidth': 30, 'markerHeight': 30, 'xoverflow': 'visible' }) .append('svg:path') .attr('d', 'M 0,-2 L 4 ,0 L 0,2') .attr('fill', 'black') .style('stroke', 'none'); var linksContainer = svg.append("g").attr("class", "linksContainer") var nodesContainer = svg.append("g").attr("class", "nodesContainer") // iniital force simulation var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function (d) { return d.id; }).distance(100)) .force("charge", d3.forceManyBody().strength(-400)) .force("center", d3.forceCenter(width / 2, height / 2)) .force("attraceForce", d3.forceManyBody().strength(70)); //create links link = linksContainer.selectAll(".link") .data(graph.links) .enter() .append("line") .attr("class", "link") .style("pointer-events", "none") .attr('marker-end', 'url(#arrowhead)') linkPaths = linksContainer.selectAll(".linkPath") .data(graph.links) .enter() .append('path') .style("pointer-events", "none") .attrs({ 'class': 'linkPath', 'id': function (d, i) { return 'linkPath' + i } }) linkLabels = linksContainer.selectAll(".linkLabel") .data(graph.links) .enter() .append('text') .style("pointer-events", "none") .attrs({ 'class': 'linkLabel', 'id': function (d, i) { return 'linkLabel' + i }, 'font-size': 12, 'fill': 'black' }) linkLabels.append('textPath') .attr('xlink:href', function (d, i) { return '#linkPath' + i }) .style("text-anchor", "middle") .style("pointer-events", "none") .attr("startOffset", "50%") .text(function (d) { return d.type }) node = nodesContainer.selectAll(".node") .data(graph.nodes) .enter() .append("g") .attr("class", "node") .attr("stroke", "white") .attr("stroke-width", "2px") .call(d3.drag() .on("start", dragStarted) .on("drag", dragged) .on("end", dragEnded) ) node.append("circle") .attr("r", 30) .style("fill", "whitesmoke") .on("click", removeNode) node.append("title") .text(function (d) { return d.name }) node.append("text") .style("class", "icon") .attr("font-family", "FontAwesome") .attr("dominant-baseline", "central") .attr("text-anchor", "middle") .attr("font-size", 30) .attr("fill", "black") .attr("stroke-width", "0px") .attr("pointer-events", "none") .text(function (d) { return d.icon }) simulation .nodes(graph.nodes) .on("tick", ticked); simulation .force("link") .links(graph.links) function addNode(d) { var newid = graph.nodes.length + 1 graph.links.push({ source: newid, target: d.id, type: "uses" }) graph.nodes.push({ "id": newid, "type": "software", "name": "Node", "context": [ { "name": d.name } ], "icon": "\", "parent": d.id, }) link = linksContainer.selectAll(".link") .data(graph.links) .enter() .append("line") .attr("class", "link") .style("pointer-events", "none") .attr('marker-end', 'url(#arrowhead)') .style("display", "block") .merge(link) linkPaths = linksContainer.selectAll(".linkPath") .data(graph.links) .enter() .append('path') .style("pointer-events", "none") .attrs({ 'class': 'linkPath', 'fill-opacity': 1, 'stroke-opacity': 1, 'id': function (d, i) { return 'linkPath' + i } }) .merge(linkPaths) linkLabels = linksContainer.selectAll(".linkLabel") .data(graph.links) .enter() .append('text') .style("pointer-events", "none") .attrs({ 'class': 'linkLabel', 'id': function (d, i) { return 'linkLabel' + i }, 'font-size': 12, 'fill': 'black' }) .merge(linkLabels) linkLabels.append('textPath') .attr('xlink:href', function (d, i) { return '#linkPath' + i }) .style("text-anchor", "middle") .style("pointer-events", "none") .attr("startOffset", "50%") .text(function (d) { return d.type }) .merge(linkLabels) node = nodesContainer.selectAll(".node") .data(graph.nodes) .enter() .append("g") .attr("class", "node") .attr("stroke", "white") .attr("stroke-width", "2px") .call(d3.drag() .on("start", dragStarted) .on("drag", dragged) .on("end", dragEnded) ) .merge(node) node.append("circle") .attr("r", 30) .style("fill", "whitesmoke") .on("click", removeNode) .merge(node) node.append("text") .style("class", "icon") .attr("font-family", "FontAwesome") .attr("dominant-baseline", "central") .attr("text-anchor", "middle") .attr("font-size", 30) .attr("fill", "black") .attr("stroke-width", "0px") .attr("pointer-events", "none") .text(function (d) { return d.icon }) .merge(node) simulation.nodes(graph.nodes); simulation.force("link").links(graph.links); //reheat the simulation simulation.alpha(0.3).restart() } function removeNode(d) { var indexOfNodes = graph.nodes.indexOf(d) var indexOfLinks = graph.links.findIndex(element => element.source.id == d.id) graph.links.splice(indexOfLinks, 1) linksContainer.selectAll(".link") .data(graph.links) .exit() .remove() linkPaths .data(graph.links) .exit() .remove() graph.nodes.splice(indexOfNodes, 1) nodesContainer.selectAll(".node") .data(graph.nodes) .exit() .remove() simulation.nodes(graph.nodes); simulation.force("link").links(graph.links); //reheat the simulation simulation.alpha(0.3).restart() console.log("Node: " + d.name + " deleted.") } function ticked() { // update link positions link .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; }); // update node positions node .attr("transform", function (d) { return "translate(" + dx + ", " + dy + ")"; }); linkPaths.attr('d', function (d) { return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; }); linkLabels.attr('transform', function (d) { if (d.target.x < d.source.x) { var bbox = this.getBBox(); rx = bbox.x + bbox.width / 2; ry = bbox.y + bbox.height / 2; return 'rotate(180 ' + rx + ' ' + ry + ')'; } else { return 'rotate(0)'; } }); } function dragStarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = dx; d.fy = dy; } 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 = undefined; d.fy = undefined; } </script> </body> </html>

Unless you use a key function...除非您使用按键功能...

.data(graph.nodes, d => d.id)

...the data() method will join data points by their indices. ... data()方法将按索引连接数据点。

Here is your code with that change only:这是您仅进行该更改的代码:

 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Playground</title> <!-- favcon --> <link rel="icon" href="https://networkrepository.com/favicon.png"> <!-- call external d3.js framework --> <script src="https://d3js.org/d3.v4.js"></script> <!-- import multiselection framework --> <script src="https://d3js.org/d3-selection-multi.v1.js"></script> <!-- import "font awesome" stylesheet https://fontawesome.com/ --> <script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script> </head> <style> body { overflow: hidden; margin: 0px; } .canvas { background-color: rgb(220, 220, 220); } .link { stroke: rgb(0, 0, 0); stroke-width: 1px; } circle { background-color: whitesmoke; } </style> <body> <!-- create svg root element as a canvas --> <svg id="svg"></svg> <!-- call script where the main application is written --> <script> var graph = { "nodes": [{ "id": 0, "type": "company", "name": "Company", "context": [{ "name": "Company" }], "icon": "\" }, { "id": 1, "type": "software", "name": "Software_1", "context": [{ "name": "Software_1" }], "icon": "\", "parent": 1 }, { "id": 2, "type": "software", "name": "Software_2", "context": [{ "name": "Software_2" }], "icon": "\", "parent": 1 }, { "id": 3, "type": "software", "name": "Software_3", "context": [{ "name": "Software_3" }], "icon": "\", "parent": 1 }, { "id": 4, "type": "software", "name": "Software_4", "context": [{ "name": "Software_4" }], "icon": "\", "parent": 1 }, { "id": 5, "type": "software", "name": "Software_5", "context": [{ "name": "Software_5" }], "icon": "\", "parent": 4 }, { "id": 6, "type": "software", "name": "Software_6", "context": [{ "name": "Software_6" }], "icon": "\", "parent": 4 }, { "id": 7, "type": "software", "name": "Software_7", "context": [{ "name": "Software_7" }], "icon": "\", "parent": 5 }, { "id": 8, "type": "software", "name": "Software_8", "context": [{ "name": "Software_8" }], "icon": "\", "parent": 5 } ], "links": [{ "source": 1, "target": 0, "type": "uses" }, { "source": 2, "target": 0, "type": "uses" }, { "source": 3, "target": 0, "type": "uses" }, { "source": 4, "target": 0, "type": "uses" }, { "source": 5, "target": 3, "type": "uses" }, { "source": 6, "target": 3, "type": "uses" }, { "source": 7, "target": 4, "type": "uses" }, { "source": 8, "target": 4, "type": "uses" } ] } // declare initial variables var svg = d3.select("svg") width = window.innerWidth height = window.innerHeight node = null link = 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() { svg.attr("transform", d3.event.transform) })) .append("g") // remove zoom on dblclick listener d3.select("svg").on("dblclick.zoom", null) // append markers to svg svg.append('defs').append('marker') .attrs({ 'id': 'arrowhead', 'viewBox': '-0 -5 10 10', 'refX': 14, 'refY': 0, 'orient': 'auto', 'markerWidth': 30, 'markerHeight': 30, 'xoverflow': 'visible' }) .append('svg:path') .attr('d', 'M 0,-2 L 4 ,0 L 0,2') .attr('fill', 'black') .style('stroke', 'none'); var linksContainer = svg.append("g").attr("class", "linksContainer") var nodesContainer = svg.append("g").attr("class", "nodesContainer") // iniital force simulation var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100)) .force("charge", d3.forceManyBody().strength(-400)) .force("center", d3.forceCenter(width / 2, height / 2)) .force("attraceForce", d3.forceManyBody().strength(70)); //create links link = linksContainer.selectAll(".link") .data(graph.links) .enter() .append("line") .attr("class", "link") .style("pointer-events", "none") .attr('marker-end', 'url(#arrowhead)') linkPaths = linksContainer.selectAll(".linkPath") .data(graph.links) .enter() .append('path') .style("pointer-events", "none") .attrs({ 'class': 'linkPath', 'id': function(d, i) { return 'linkPath' + i } }) linkLabels = linksContainer.selectAll(".linkLabel") .data(graph.links) .enter() .append('text') .style("pointer-events", "none") .attrs({ 'class': 'linkLabel', 'id': function(d, i) { return 'linkLabel' + i }, 'font-size': 12, 'fill': 'black' }) linkLabels.append('textPath') .attr('xlink:href', function(d, i) { return '#linkPath' + i }) .style("text-anchor", "middle") .style("pointer-events", "none") .attr("startOffset", "50%") .text(function(d) { return d.type }) node = nodesContainer.selectAll(".node") .data(graph.nodes, d => d.id) .enter() .append("g") .attr("class", "node") .attr("stroke", "white") .attr("stroke-width", "2px") .call(d3.drag() .on("start", dragStarted) .on("drag", dragged) .on("end", dragEnded) ) node.append("circle") .attr("r", 30) .style("fill", "whitesmoke") .on("click", removeNode) node.append("title") .text(function(d) { return d.name }) node.append("text") .style("class", "icon") .attr("font-family", "FontAwesome") .attr("dominant-baseline", "central") .attr("text-anchor", "middle") .attr("font-size", 30) .attr("fill", "black") .attr("stroke-width", "0px") .attr("pointer-events", "none") .text(function(d) { return d.icon }) simulation .nodes(graph.nodes) .on("tick", ticked); simulation .force("link") .links(graph.links) function addNode(d) { var newid = graph.nodes.length + 1 graph.links.push({ source: newid, target: d.id, type: "uses" }) graph.nodes.push({ "id": newid, "type": "software", "name": "Node", "context": [{ "name": d.name }], "icon": "\", "parent": d.id, }) link = linksContainer.selectAll(".link") .data(graph.links) .enter() .append("line") .attr("class", "link") .style("pointer-events", "none") .attr('marker-end', 'url(#arrowhead)') .style("display", "block") .merge(link) linkPaths = linksContainer.selectAll(".linkPath") .data(graph.links) .enter() .append('path') .style("pointer-events", "none") .attrs({ 'class': 'linkPath', 'fill-opacity': 1, 'stroke-opacity': 1, 'id': function(d, i) { return 'linkPath' + i } }) .merge(linkPaths) linkLabels = linksContainer.selectAll(".linkLabel") .data(graph.links) .enter() .append('text') .style("pointer-events", "none") .attrs({ 'class': 'linkLabel', 'id': function(d, i) { return 'linkLabel' + i }, 'font-size': 12, 'fill': 'black' }) .merge(linkLabels) linkLabels.append('textPath') .attr('xlink:href', function(d, i) { return '#linkPath' + i }) .style("text-anchor", "middle") .style("pointer-events", "none") .attr("startOffset", "50%") .text(function(d) { return d.type }) .merge(linkLabels) node = nodesContainer.selectAll(".node") .data(graph.nodes) .enter() .append("g") .attr("class", "node") .attr("stroke", "white") .attr("stroke-width", "2px") .call(d3.drag() .on("start", dragStarted) .on("drag", dragged) .on("end", dragEnded) ) .merge(node) node.append("circle") .attr("r", 30) .style("fill", "whitesmoke") .on("click", removeNode) .merge(node) node.append("text") .style("class", "icon") .attr("font-family", "FontAwesome") .attr("dominant-baseline", "central") .attr("text-anchor", "middle") .attr("font-size", 30) .attr("fill", "black") .attr("stroke-width", "0px") .attr("pointer-events", "none") .text(function(d) { return d.icon }) .merge(node) simulation.nodes(graph.nodes); simulation.force("link").links(graph.links); //reheat the simulation simulation.alpha(0.3).restart() } function removeNode(d) { var indexOfNodes = graph.nodes.indexOf(d) var indexOfLinks = graph.links.findIndex(element => element.source.id == d.id) graph.links.splice(indexOfLinks, 1) linksContainer.selectAll(".link") .data(graph.links) .exit() .remove() linkPaths .data(graph.links) .exit() .remove() graph.nodes.splice(indexOfNodes, 1) nodesContainer.selectAll(".node") .data(graph.nodes, d => d.id) .exit() .remove() simulation.nodes(graph.nodes); simulation.force("link").links(graph.links); //reheat the simulation simulation.alpha(0.3).restart() console.log("Node: " + d.name + " deleted.") } function ticked() { // update link positions link .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; }); // update node positions node .attr("transform", function(d) { return "translate(" + dx + ", " + dy + ")"; }); linkPaths.attr('d', function(d) { return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; }); linkLabels.attr('transform', function(d) { if (d.target.x < d.source.x) { var bbox = this.getBBox(); rx = bbox.x + bbox.width / 2; ry = bbox.y + bbox.height / 2; return 'rotate(180 ' + rx + ' ' + ry + ')'; } else { return 'rotate(0)'; } }); } function dragStarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = dx; d.fy = dy; } 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 = undefined; d.fy = undefined; } </script> </body> </html>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM