Reputation: 341
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>
Upvotes: 2
Views: 137
Reputation: 102194
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>
Upvotes: 1