So I've been trying to migrate this great d3.js tree (http://bl.ocks.org/robschmuecker/7880033) from d3.js version 3 to version 6. Currently I am trying to get the drag and drop working (see below for the code). But It just doesn't work the way it is supposed to be. After rearranging a node via drag and drop, the tree isn't properly drawn. What's more, drag and drop doesn't seem to work for all nodes (except for the root node, which is intended). I couldn't find out so far why this happens. I suspect that the problem could be that the tree data is not properly updated after updating the children.
Unfortunately, not many tutorials for d3.js are based on version 6 (at least to my knowledge) so I wasn't really able to find much useful information.
Any tips would be appreciated. Thanks!
<!DOCTYPE html>
<html lang="en">
<head>
<title>d3.js tree V6</title>
<meta charset="utf-8"/>
<script src="https://d3js.org/d3.v6.min.js"></script>
<style>
body {
font-family: Arial;
}
#title {
font-size: 30px;
margin-top: 20px;
}
.container {
margin: 0 auto;
}
.text-center {
text-align: center;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
svg {
display: block;
margin: auto;
}
.node {
cursor: pointer;
}
.ghostCircle.show{
display:block;
}
.ghostCircle, .activeDrag .ghostCircle{
display: none;
}
</style>
</head>
<body>
<div class="container">
<!-- title -->
<h1 class="text-center" id="title">d3.js tree V6</h1>
<div id="concept-tree">
</div>
</div>
<script>
var treeData = {
"name": "Top Level",
"parent": "null",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A"
},
{
"name": "Daughter of A",
"parent": "Level 2: A"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level"
}
]
};
// +++ INITIALIZATIONS +++ //
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 1500 - margin.left - margin.right,
height = 750 - margin.top - margin.bottom;
var duration = 750;
const svg = d3.select("#concept-tree").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
root = d3.hierarchy(treeData, function(d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
var i = 0;
var selectedNode = null;
var draggingNode = null;
// +++ FUNCTIONS +++ //
function diagonal(s, d) {
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
return path
}
svg.call(d3.zoom()
.extent([[0, 0], [width, height]])
.scaleExtent([1, 8])
.on("zoom", zoomed));
function zoomed({transform}) {
g.attr("transform", transform);
}
// collapse the node and all it's children
function collapse(d) {
if(d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function expand(d) {
if (d._children) {
d.children = d._children;
d.children.forEach(expand);
d._children = null;
}
}
var overCircle = function(d) {
selectedNode = d;
//updateTempConnector();
};
var outCircle = function(d) {
selectedNode = null;
//updateTempConnector();
};
function centerNode(source) {
/*scale = zoomEvent.scale();
x = -source.y0;
y = -source.x0;
x = x * scale + viewerWidth / 2;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
zoomListener.scale(scale);
zoomListener.translate([x, y]);*/
}
// toggle children on click
function click(event, d) {
if (event.defaultPrevented) return;
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
centerNode(d);
}
// for dragging nodes
function dragEvent() {
function dragstarted(event, d) {
if (d == root) {
return
}
event.sourceEvent.stopPropagation();
}
function dragged(event, d) {
var nodes = root.descendants();
if (d == root) {
return
}
var domNode = this;
initiateDrag(d, domNode);
// move node
d.x0 += event.dy;
d.y0 += event.dx;
var node = d3.select(this);
node.attr("transform", "translate(" + d.y0 + "," + d.x0 + ")");
}
function dragended(event, d) {
if (d == root) {
return
}
var domNode = this;
function endDrag() {
selectedNode = null;
d3.selectAll('.ghostCircle').attr('class', 'ghostCircle');
d3.select(domNode).attr('class', 'node');
// now restore the mouseover event or we won't be able to drag a 2nd time
d3.select(domNode).select('.ghostCircle').attr('pointer-events', '');
//updateTempConnector();
if (draggingNode !== null) {
update(draggingNode);
centerNode(draggingNode);
draggingNode = null;
}
}
if (selectedNode) {
console.log(selectedNode);
// now remove the element from the parent, and insert it into the new elements children
var index = draggingNode.parent.children.indexOf(draggingNode);
if (index > -1) {
draggingNode.parent.children.splice(index, 1);
}
if (typeof selectedNode.children !== 'undefined' || typeof selectedNode._children !== 'undefined') {
if (typeof selectedNode.children !== 'undefined') {
selectedNode.children.push(draggingNode);
} else {
selectedNode._children.push(draggingNode);
}
} else {
selectedNode.children = [];
selectedNode.children.push(draggingNode);
}
// make sure that the node being added to is expanded so user can see added node is correctly moved
expand(selectedNode);
//sortTree();
endDrag();
} else {
endDrag();
}
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
function initiateDrag(d, domNode) {
var nodes = d.descendants().slice(1),
links = d.descendants();
draggingNode = d;
d3.select(domNode).select('.ghostCircle').attr('pointer-events', 'none');
d3.selectAll('.ghostCircle').attr('class', 'ghostCircle show');
d3.select(domNode).attr('class', 'node activeDrag');
// remove link paths
g.selectAll("path.link")
.data(links, function(d) {
return d.id;
}).remove();
// remove child nodes
g.selectAll("g.node")
.data(nodes, function(d) {
return d.id;
}).remove();
}
// main function
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// +++ NODES +++ //
// update nodes
var node = g.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// enter nodes
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(dragEvent())
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("circle")
.attr('class', 'nodeCircle')
.attr("r", 5)
.style("fill", function(d) {
return d._children ? "blue" : "lightsteelblue";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.att