Create a gist now

Instantly share code, notes, and snippets.

@diiaann /flare.json forked from mbostock/.block
Last active Oct 23, 2015

What would you like to do?
Elbow tree with node removal

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

{
"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"}
]
}
]
}
]
}
<!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