Skip to content

Instantly share code, notes, and snippets.

@diiaann
Forked from mbostock/.block
Last active October 23, 2015 06:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save diiaann/6ffa81a1d8da2c5a53fc to your computer and use it in GitHub Desktop.
Save diiaann/6ffa81a1d8da2c5a53fc to your computer and use it in GitHub Desktop.
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