This is a more stylized version of the indented tree. Features elbows instead of diagonals. Indicates whether an item can be expanded, collapsed, or 'do nothing'. Also includes the ability to remove nodes in the view. Based off: https://gist.github.com/mbostock/1093025
-
-
Save diiaann/6ffa81a1d8da2c5a53fc to your computer and use it in GitHub Desktop.
Elbow tree with node removal
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
{ | |
"name": "San Francisco", | |
"children": [ | |
{ | |
"name": "SOMA", | |
"children": [ | |
{ | |
"name": "Marlowe", | |
"children": [ | |
{"name": "Brussel Sprouts"}, | |
{"name": "Burger"}, | |
{"name": "Deviled Eggs"} | |
] | |
}, | |
{ | |
"name": "Garaje", | |
"children": [ | |
{ | |
"name": "Surer Leo Zapato", | |
"children": [ | |
{"name": "Avocado"}, | |
{"name": "Tilapia"}, | |
{"name": "French Fries"}, | |
{"name": "Tortilla"} | |
] | |
} | |
] | |
} | |
] | |
}, | |
{ | |
"name": "Potrero Hill", | |
"children": [ | |
{"name": "Sunflower"}, | |
{"name": "Aperto"}, | |
{ | |
"name": "Chez Maman", | |
"children": [ | |
{"name": "Hachis Parmentier"}, | |
{"name": "Les Moules Poulette"} | |
] | |
} | |
] | |
} | |
] | |
} |
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> | |
<meta charset="utf-8"> | |
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400' rel='stylesheet' type='text/css'> | |
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"> | |
<style> | |
.node.fixed rect { | |
cursor: initial; | |
} | |
.node.fixed .expand-box { | |
fill: #ddd; | |
} | |
.node rect { | |
cursor: pointer; | |
fill: #fff; | |
stroke: #999; | |
stroke-width: 1px; | |
} | |
.node .action text { | |
font-size: 13px; | |
font-weight: 300; | |
cursor: pointer; | |
fill: #3498db; | |
} | |
.node .action text:hover { | |
text-decoration: underline; | |
} | |
.node a text { | |
font-family: 'Open Sans', Helvetica, Arial; | |
font-weight: 400; | |
pointer-events: auto; | |
} | |
.node text { | |
font-size: 16px; | |
pointer-events: none; | |
} | |
.node .expand-text { | |
font-size: 10px; | |
font-family: "FontAwesome"; | |
fill: #585858; | |
} | |
path.link { | |
fill: none; | |
stroke: #9ecae1; | |
stroke-width: 1.5px; | |
} | |
</style> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script> | |
var margin = {top: 50, right: 20, bottom: 30, left: 20}; | |
var width = 960 - margin.left - margin.right; | |
var barHeight = 50; | |
var spaceBetween = barHeight / 2; | |
var barWidth = width * 0.8; | |
var checkboxHeight = 15; | |
var i = 0, | |
duration = 400, | |
root; | |
var tree = d3.layout.tree().nodeSize([0, 20]); | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
function elbow(d) { | |
return 'M' + d.source.y + ',' + (d.source.x + checkboxHeight / 2) | |
+ 'V' + d.target.x + 'H' + (d.target.y - checkboxHeight / 2); | |
} | |
d3.json("flare.json", function(error, flare) { | |
flare.x0 = 0; | |
flare.y0 = 0; | |
update(root = flare); | |
}); | |
function update(source) { | |
var nodes = tree.nodes(root); | |
nodes = nodes.slice(1); | |
var height = Math.max(500, nodes.length * (barHeight + spaceBetween) + 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". | |
nodes.forEach(function(n, i) { | |
n.x = i * (barHeight + spaceBetween); | |
}); | |
// Update the nodes… | |
var node = svg.selectAll("g.node") | |
.data(nodes, function(d) { return d.id || (d.id = ++i); }); | |
var nodeEnter = node.enter().append('g') | |
.attr('class', expandBoxClass) | |
.attr('transform', function() { return 'translate(' + source.y0 + ',' + source.x0 + ')'; }); | |
var checkbox = nodeEnter.append('g'); | |
checkbox | |
.append('rect') | |
.attr('class', 'expand-box') | |
.attr('height', checkboxHeight) | |
.attr('width', checkboxHeight) | |
.attr('y', checkboxHeight / 2 * -1) | |
.attr('x', checkboxHeight / 2 * -1) | |
.on('click', handleExpand); | |
checkbox | |
.append('text') | |
.attr('class', 'expand-text') | |
.attr('dx', -4) | |
.attr('dy', 4) | |
.text(expandSymbol); | |
// Enter any new nodes at the parent's previous position. | |
nodeEnter.append('rect') | |
.attr('class', 'snapshot') | |
.attr('y', -barHeight / 2) | |
.attr('x', barHeight / 2) | |
.attr('height', barHeight) | |
.attr('width', barWidth) | |
.on('click', handleExpand); | |
var nodeText = nodeEnter.append('g'); | |
nodeText.append('svg:a') | |
.attr('xlink:href', function(){ return '#'; }) | |
.append('svg:text') | |
.attr('dx', 40) | |
.attr('dy', -4) | |
.text(function(d) { return d.name; }); | |
nodeEnter.append('svg:a') | |
.attr('class', 'action') | |
.on('click', removeNode) | |
.append('svg:text') | |
.attr('dx', 40) | |
.attr('dy', 18) | |
.text('delete'); | |
// 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('.snapshot') | |
.attr('rx', 5) | |
.attr('ry', 5); | |
// 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", 1e-6) | |
.remove(); | |
// Update the links… | |
var link = svg.selectAll("path.link") | |
.data(tree.links(nodes), function(d) { return d.target.id; }); | |
// Enter any new links at the parent's previous position. | |
link.enter().append('svg:path', 'g') | |
.attr('d', elbow) | |
.attr('class', 'link') | |
.style('fill', 'none') | |
.style('stroke-width', 1) | |
.style('stroke', '#c2c2c2'); | |
// Transition links to their new position. | |
link.transition() | |
.duration(duration) | |
.attr('d', elbow); | |
// Transition exiting nodes to the parent's new position. | |
link.exit().transition() | |
.duration(duration) | |
.attr('d', function() { | |
var o = {x: source.x, y: source.y}; | |
return elbow({source: o, target: o}); | |
}) | |
.remove(); | |
// Stash the old positions for transition. | |
nodes.forEach(function(d) { | |
d.x0 = d.x; | |
d.y0 = d.y; | |
}); | |
} | |
function removeNode(d){ | |
var index = d.parent.children.indexOf(d); | |
if (d.children){ | |
[].splice.apply(d.parent.children, [index, 1].concat(d.children)); | |
} | |
else { | |
[].splice.apply(d.parent.children, [index, 1]); | |
} | |
update(d.parent); | |
} | |
// Toggle children on click. | |
function handleExpand(d) { | |
if (d.children) { | |
d._children = d.children; | |
d.children = null; | |
} else { | |
d.children = d._children; | |
d._children = null; | |
} | |
d3.select(this.parentNode).select('text').text(expandSymbol(d)); | |
update(d); | |
} | |
function expandBoxClass(d){ | |
if (d.children || d._children){ | |
return 'node'; | |
} else { | |
return 'fixed node'; | |
} | |
} | |
function expandSymbol(d){ | |
if (d.children){ | |
return '\uf068'; | |
} else if (d._children){ | |
return '\uf067'; | |
} else { | |
return ''; | |
} | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment