I got a problem with my D3v4 graph, each time I drag a node seems the DOM wants to redraw this node faster as it should be. I am initializing the svg area and the simulation. Further I put most of the enter().exit().remove() logic in an own function, to avoid redundancy.
I appreciated any comment and hint.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Playground D3v4</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 { fill: whitesmoke } .node { stroke: white; stroke-width: 2px } .tooltip { font-family: "Open Sans", sans-serif; position: absolute; text-align: left; background: rgb(245, 245, 245); border: 2px; border-radius: 6px; border-color: rgb(255, 255, 255); border-style: solid; pointer-events: none; line-height: 150%; padding: 8px 10px; } #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> <!-- call script where the main application is written --> <script> var graph = { "nodes": [{ "id": 0, "name": "Company", }, { "id": 1, "name": "1", }, { "id": 2, "name": "2", }, { "id": 3, "name": "3", }, { "id": 4, "name": "4", } ], "links": [{ "id": 0, "source": 1, "target": 0, }, { "id": 1, "source": 2, "target": 0, }, { "id": 2, "source": 3, "target": 0, }, { "id": 3, "source": 4, "target": 0, }, ] } // declare initial variables var svg = d3.select("svg") width = window.innerWidth height = window.innerHeight thisNode = null; // define cavnas area to draw everything svg = d3.select("svg") .attr("class", "canvas") .attr("width", width) .attr("height", height) .append("g") // iniital force simulation var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function (d) { return d.id; }).distance(100)) .force("charge", d3.forceManyBody().strength(-80)) .force("center", d3.forceCenter(width / 2, height / 2)) .force("attraceForce", d3.forceManyBody().strength(70)); var node_group = null var link_group = null update() /* console.log("Initial Nodes") console.log(graph.nodes) console.log("------------------") */ function update() { //define group and join node_group = svg.selectAll(".node_group") .data(graph.nodes, d => d.id) //exit, remove node_group.exit().remove(); //enter var enter = node_group.enter() .append("g").attr("class", "node_group"); //append - as many items as you need enter.append("circle") .attr("class", "node_circle") .attr("r", 20) .on("contextmenu", contextMenu) .call(d3.drag() .on("start", dragStarted) .on("drag", dragged) .on("end", dragEnded) ) enter.append("text") .attr("class", "node_label") .text(function (d) { return d.name }) //merge node_group = node_group.merge(enter); simulation .nodes(graph.nodes) .on("tick", ticked); simulation.alphaTarget(0.3).restart() } function contextMenu(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", addNode) document.getElementById("removeObject").addEventListener("click", removeNodeClicked) } function addNodeClicked() { addNode(thisNode) } function removeNodeClicked() { removeNode(thisNode) } function addNode() { var newID = Math.floor(Math.random() * 100000) /* console.log("Before adding Node") console.log(graph.nodes) console.log("------------------") */ graph.nodes.push({ id: newID, name: "Software_" + newID }) /* console.log("After adding Node") console.log(graph.nodes) console.log("------------------") */ update() } function removeNode(thisNode) { var indexOfNode = graph.nodes.indexOf(thisNode) /* console.log("Before removing Node") console.log(graph.nodes) console.log("------------------") */ graph.nodes.splice(indexOfNode, 1) /* console.log("After removing Node") console.log(graph.nodes) console.log("------------------") */ update() } function ticked() { // update link positions // update node positions node_group .attr("transform", function (d) { return "translate(" + d.x + ", " + d.y + ")"; }); } function dragStarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).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 = undefined; d.fy = undefined; } </script> </body> </html>
Advertisement
Answer
In the ticked
function you are translating the groups, not the circles. Therefore, you should call d3.drag
on the same groups:
node_group.call(d3.drag() .on("start", dragStarted) .on("drag", dragged) .on("end", dragEnded) )
Here is your code with that change:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Playground D3v4</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 { fill: whitesmoke } .node { stroke: white; stroke-width: 2px } .tooltip { font-family: "Open Sans", sans-serif; position: absolute; text-align: left; background: rgb(245, 245, 245); border: 2px; border-radius: 6px; border-color: rgb(255, 255, 255); border-style: solid; pointer-events: none; line-height: 150%; padding: 8px 10px; } #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> <!-- call script where the main application is written --> <script> var graph = { "nodes": [{ "id": 0, "name": "Company", }, { "id": 1, "name": "1", }, { "id": 2, "name": "2", }, { "id": 3, "name": "3", }, { "id": 4, "name": "4", } ], "links": [{ "id": 0, "source": 1, "target": 0, }, { "id": 1, "source": 2, "target": 0, }, { "id": 2, "source": 3, "target": 0, }, { "id": 3, "source": 4, "target": 0, }, ] } // declare initial variables var svg = d3.select("svg") width = window.innerWidth height = window.innerHeight thisNode = null; // define cavnas area to draw everything svg = d3.select("svg") .attr("class", "canvas") .attr("width", width) .attr("height", height) .append("g") // iniital force simulation var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100)) .force("charge", d3.forceManyBody().strength(-80)) .force("center", d3.forceCenter(width / 2, height / 2)) .force("attraceForce", d3.forceManyBody().strength(70)); var node_group = null var link_group = null update() /* console.log("Initial Nodes") console.log(graph.nodes) console.log("------------------") */ function update() { //define group and join node_group = svg.selectAll(".node_group") .data(graph.nodes, d => d.id) //exit, remove node_group.exit().remove(); //enter var enter = node_group.enter() .append("g").attr("class", "node_group"); //append - as many items as you need enter.append("circle") .attr("class", "node_circle") .attr("r", 20) .on("contextmenu", contextMenu) enter.append("text") .attr("class", "node_label") .text(function(d) { return d.name }) //merge node_group = node_group.merge(enter); node_group.call(d3.drag() .on("start", dragStarted) .on("drag", dragged) .on("end", dragEnded) ) simulation .nodes(graph.nodes) .on("tick", ticked); simulation.alphaTarget(0.3).restart() } function contextMenu(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", addNode) document.getElementById("removeObject").addEventListener("click", removeNodeClicked) } function addNodeClicked() { addNode(thisNode) } function removeNodeClicked() { removeNode(thisNode) } function addNode() { var newID = Math.floor(Math.random() * 100000) /* console.log("Before adding Node") console.log(graph.nodes) console.log("------------------") */ graph.nodes.push({ id: newID, name: "Software_" + newID }) /* console.log("After adding Node") console.log(graph.nodes) console.log("------------------") */ update() } function removeNode(thisNode) { var indexOfNode = graph.nodes.indexOf(thisNode) /* console.log("Before removing Node") console.log(graph.nodes) console.log("------------------") */ graph.nodes.splice(indexOfNode, 1) /* console.log("After removing Node") console.log(graph.nodes) console.log("------------------") */ update() } function ticked() { // update link positions // update node positions node_group .attr("transform", function(d) { return "translate(" + d.x + ", " + d.y + ")"; }); } function dragStarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).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 = undefined; d.fy = undefined; } </script> </body> </html>