Created
March 28, 2018 19:01
-
-
Save rbelew/cec923c451ba4478684cc0a977f6ac42 to your computer and use it in GitHub Desktop.
editHierNode: adding and editing nodes in d3.hierarchy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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