Created
December 13, 2013 09:40
-
-
Save akuhn/7942030 to your computer and use it in GitHub Desktop.
Nested layouts for d3.
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
<html> | |
<head> | |
<script src="d3.v3.min.js" charset="utf-8"></script> | |
<style> | |
rect { | |
stroke: blue; | |
fill: #eee; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
// Nested layouts for d3, inspired by http://www.moosetechnology.org/tools/mondrian | |
// Example: arranges a two-dimensional array of elements as centered rows of | |
// bottom-aligned boxes. Uses d.w and d.h as width and height. | |
// | |
// data.layout = my.layout() | |
// .margin(4) | |
// .padding(12) | |
// .belowEachOther() | |
// .alignCenter() | |
// .group() | |
// .padding(4) | |
// .besideEachOther() | |
// .alignBottom(); | |
// | |
// data.layout() | |
var my = {} | |
my.layout = function(node) { | |
var padding = 0, | |
child_layout = null, | |
children = function(self) { return self }, | |
margin = 0, | |
fun = []; | |
// Helper functions | |
function my_layout_width(self) { | |
return d3.max(children(self), function(d) { return d.x + d.w }); | |
} | |
function my_layout_height(self) { | |
return d3.max(children(self), function(d) { return d.y + d.h }); | |
} | |
// The main working horse | |
var layout = function(self) { | |
children(self).forEach(function(d) { | |
child_layout && child_layout(d) | |
}); | |
fun.forEach(function(f) { f(self) }); | |
self.w = my_layout_width(self) + 2 * margin; | |
self.h = my_layout_height(self) + 2 * margin; | |
} | |
// Descent down to the next nesting level | |
layout.group = function() { | |
return child_layout = my.layout(); | |
} | |
// Setters and getters | |
layout.nodes = function(value) { | |
children = value; | |
return layout; | |
} | |
layout.padding = function(value) { | |
padding = value; | |
return layout; | |
} | |
layout.margin = function(value) { | |
margin = value; | |
return layout; | |
} | |
// Arrangement | |
layout.besideEachOther = | |
layout.horizontally = function(value) { | |
fun.push(function(self) { | |
var x = margin; | |
children(self).forEach(function(d) { | |
d.x = x; | |
x += d.w + padding; | |
}) | |
}) | |
return layout; | |
} | |
layout.belowEachOther = | |
layout.vertically = function(value) { | |
fun.push(function(self) { | |
var y = margin; | |
children(self).forEach(function(d) { | |
d.y = y; | |
y += d.h + padding; | |
}) | |
}) | |
return layout; | |
} | |
// Alignment | |
function alignment(aggregation, callback) { | |
return function() { | |
fun.push(function(self) { | |
var a = aggregation && aggregation(self); | |
children(self).forEach(function(d) { | |
callback(d, a) | |
}) | |
}); | |
return layout; | |
} | |
} | |
layout.alignRight = alignment(undefined, function(d) { d.x = margin }); | |
layout.alignCenter = alignment(my_layout_width, function(d,w) { d.x = (w - d.w) / 2 + margin}); | |
layout.alignLeft = alignment(my_layout_width, function(d,w) { d.x = w - d.w + margin }); | |
layout.alignTop = alignment(undefined, function(d) { d.y = margin }); | |
layout.alignMiddle = alignment(my_layout_height, function(d,h) { d.y = (h - d.h) / 2 + margin }); | |
layout.alignBottom = alignment(my_layout_height, function(d,h) { d.y = h - d.h + margin }); | |
// Start with all children aligned to top right. | |
layout.alignTop(); | |
layout.alignRight(); | |
return layout; | |
} | |
my.layout.transform = function(d) { return 'translate('+d.x+','+d.y+')' } | |
my.layout.width = function(d) { return d.w } | |
my.layout.height = function(d) { return d.h } | |
var w = function() { return 10 + Math.random() * 40 } | |
var h = function() { return 15 + Math.random() * 20 } | |
var data = d3.nest() | |
.key(function(d) { return Math.floor(Math.random() * 7) }) | |
.entries(d3.range(40).map(function(n) { return { w: w(), h: h(), n: n }})); | |
var layout = my.layout(); | |
layout | |
.margin(4) | |
.padding(12) | |
.belowEachOther() | |
.alignCenter() | |
.group() | |
.padding(4) | |
.besideEachOther() | |
.alignBottom() | |
.nodes(function(d) { return d.values }); | |
layout(data); | |
d3.select(document.body) | |
.append('svg') | |
.attr('width', data.w) | |
.attr('height', data.h) | |
.selectAll('g') | |
.data(data) | |
.enter() | |
.append('g') | |
.attr('transform', my.layout.transform) | |
.selectAll('g') | |
.data(function(d) { return d.values }) | |
.enter() | |
.append('g') | |
.attr('transform', my.layout.transform) | |
.append('rect') | |
.attr('width', my.layout.width) | |
.attr('height', my.layout.height); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment