Skip to content

Instantly share code, notes, and snippets.

@rbelew
Created March 28, 2018 19:01
Show Gist options
  • Save rbelew/cec923c451ba4478684cc0a977f6ac42 to your computer and use it in GitHub Desktop.
Save rbelew/cec923c451ba4478684cc0a977f6ac42 to your computer and use it in GitHub Desktop.
editHierNode: adding and editing nodes in d3.hierarchy
<!DOCTYPE html>
<!-- https://bl.ocks.org/mbostock/1093025 -->
<meta charset="utf-8">
<style>
.node rect {
cursor: pointer;
fill: #fff;
fill-opacity: 0.5;
stroke: #3182bd;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
pointer-events: none;
}
.link {
fill: none;
stroke: #9ecae1;
stroke-width: 1.5px;
}
</style>
<body>
<div id='editMoveSVG'>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 30, right: 20, bottom: 30, left: 20},
width = 960,
barHeight = 20,
barWidth = (width - margin.left - margin.right) * 0.8;
var i = 0,
duration = 400,
root;
var diagonal = d3.linkHorizontal()
.x(function(d) { return d.y; })
.y(function(d) { return d.x; });
var svg = d3.select("#editMoveSVG").append("svg")
.attr("width", width) // + margin.left + margin.right)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// "https://github.com/d3/d3-hierarchy/blob/master/test/data/flare.json"
d3.json("flare.json", function(error, flare) {
if (error) throw error;
root = d3.hierarchy(flare);
root.x0 = 0;
root.y0 = 0;
update(root);
});
function update(source) {
// Compute the flattened node list.
var nodes = root.descendants();
var height = Math.max(500, nodes.length * barHeight + margin.top + margin.bottom);
d3.select("svg").transition()
.duration(duration)
.attr("height", height);
d3.select(self.frameElement).transition()
.duration(duration)
.style("height", height + "px");
// Compute the "layout". TODO https://github.com/d3/d3-hierarchy/issues/67
var index = -1;
root.eachBefore(function(n) {
n.x = ++index * barHeight;
n.y = n.depth * 20;
if (n.id > 1000) {
console.log('update1: '+n.id +' '+n.data.name);
}
});
function idNameKey(d) {
var nid = d.id || (d.id = ++i);
var nname = d.data.name;
var key = nid.toString() + '_' + nname
if (d.id > 1000) {
console.log('idNameKey: '+ nid +' '+ nname);
}
return key;
}
// Update the nodes…
var node = svg.selectAll(".node").data(nodes, idNameKey);
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.style("opacity", 0);
// Enter any new nodes at the parent's previous position.
nodeEnter.append("rect")
.attr("y", -barHeight / 2)
.attr("height", barHeight)
.attr("width", barWidth)
.style("fill", color)
.on("click", click);
nodeEnter.append("text")
.attr("dy", 3.5)
.attr("dx", 5.5)
.text(function(d) {
if (d.id > 1000) {
console.log('update nodeText: '+d.id +' '+d.data.name);
}
return d.data.name; });
// Transition nodes to their new position.
nodeEnter.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
.style("opacity", 1);
node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
.style("opacity", 1)
.select("rect")
.style("fill", color);
// Transition exiting nodes to the parent's new position.
node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.style("opacity", 0)
.remove();
// Update the links…
var link = svg.selectAll(".link")
.data(root.links(), function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
})
.transition()
.duration(duration)
.attr("d", diagonal);
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
root.each(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
var e = d3.event;
if (e.shiftKey) {
// console.log('shiftClick');
if (d.depth <= 2) {
addNode(d);
} else {
console.log('Cant add sub-exercises!');
}
} else if (e.altKey) {
// console.log('cntlClick');
var newName = editName(d);
} else {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
update(d);
}
function color(d) {
return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
}
function addNode(parent) {
// https://stackoverflow.com/questions/43140325/add-node-to-d3-tree-v4
//Adding a new node (as a child) to selected Node (code snippet)
var newNode = {
type: 'node-type',
name: 'initialNodeName' ,
children: []
};
//Creates a Node from newNode object using d3.hierarchy(.)
var newNode = d3.hierarchy(newNode);
//later added some properties to Node like child,parent,depth
newNode.depth = parent.depth + 1;
newNode.height = parent.height - 1;
newNode.parent = parent;
newNode.id = Date.now();
//Selected is a node, to which we are adding the new node as a child
//If no child array, create an empty array
if(!parent.children){
parent.children = [];
parent.data.children = [];
}
//Push it to parent.children array
parent.children.push(newNode);
parent.data.children.push(newNode.data);
console.log('addNode: '+newNode.id +' '+ newNode.data.name);
//Update tree
update(parent);
}
function editName(d) {
// http://bl.ocks.org/GerHobbelt/2653660
// inject a HTML form to edit the content here...
var parDOM = d3.select('svg');
var parent = d.parent;
var frm = parDOM.append("foreignObject");
var inp = frm
.attr("x", d.x)
.attr("y", d.y)
.attr("width", 300)
.attr("height", 25)
.append("xhtml:form")
.append("input")
.attr("value", function() {
// nasty spot to place this call, but here we are sure that the <input> tag is available
// and is handily pointed at by 'this':
this.focus();
return "(give new name to node)";
})
.attr("style", "width: 294px;")
// make the form go away when you hit ENTER:
.on("keypress", function() {
// console.log("keypress", this, arguments);
var ev = d3.event;
if (ev.keyCode == 13)
{
if (typeof(ev.cancelBubble) !== 'undefined') // IE
ev.cancelBubble = true;
if (ev.stopPropagation)
ev.stopPropagation();
ev.preventDefault();
var txt = inp.node().value;
d.data.name = txt;
// odd. Should work in Safari, but the debugger crashes on this instead.
// Anyway, it SHOULD be here and it doesn't hurt otherwise.
parDOM.select("foreignObject").remove();
console.log('editName: '+d.id +' '+ d.data.name);
update(parent);
}
});
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment