Grid layout demo.
In response to this question.
Grid layout demo.
In response to this question.
(function() { | |
d3.layout.grid = function() { | |
var mode = "equal", | |
layout = _distributeEqually, | |
x = d3.scale.ordinal(), | |
y = d3.scale.ordinal(), | |
size = [1, 1], | |
actualSize = [0, 0], | |
nodeSize = false, | |
bands = false, | |
padding = [0, 0], | |
cols, rows; | |
function grid(nodes) { | |
return layout(nodes); | |
} | |
function _distributeEqually(nodes) { | |
var i = -1, | |
n = nodes.length, | |
_cols = cols ? cols : 0, | |
_rows = rows ? rows : 0, | |
col, row; | |
if (_rows && !_cols) { | |
_cols = Math.ceil(n / _rows) | |
} else { | |
_cols || (_cols = Math.ceil(Math.sqrt(n))); | |
_rows || (_rows = Math.ceil(n / _cols)); | |
} | |
if (nodeSize) { | |
x.domain(d3.range(_cols)).range(d3.range(0, (size[0] + padding[0]) * _cols, size[0] + padding[0])); | |
y.domain(d3.range(_rows)).range(d3.range(0, (size[1] + padding[1]) * _rows, size[1] + padding[1])); | |
actualSize[0] = bands ? x(_cols - 1) + size[0] : x(_cols - 1); | |
actualSize[1] = bands ? y(_rows - 1) + size[1] : y(_rows - 1); | |
} else if (bands) { | |
x.domain(d3.range(_cols)).rangeBands([0, size[0]], padding[0], 0); | |
y.domain(d3.range(_rows)).rangeBands([0, size[1]], padding[1], 0); | |
actualSize[0] = x.rangeBand(); | |
actualSize[1] = y.rangeBand(); | |
} else { | |
x.domain(d3.range(_cols)).rangePoints([0, size[0]]); | |
y.domain(d3.range(_rows)).rangePoints([0, size[1]]); | |
actualSize[0] = x(1); | |
actualSize[1] = y(1); | |
} | |
while (++i < n) { | |
col = i % _cols; | |
row = Math.floor(i / _cols); | |
nodes[i].x = x(col); | |
nodes[i].y = y(row); | |
} | |
return nodes; | |
} | |
grid.size = function(value) { | |
if (!arguments.length) return nodeSize ? actualSize : size; | |
actualSize = [0, 0]; | |
nodeSize = (size = value) == null; | |
return grid; | |
} | |
grid.nodeSize = function(value) { | |
if (!arguments.length) return nodeSize ? size : actualSize; | |
actualSize = [0, 0]; | |
nodeSize = (size = value) != null; | |
return grid; | |
} | |
grid.rows = function(value) { | |
if (!arguments.length) return rows; | |
rows = value; | |
return grid; | |
} | |
grid.cols = function(value) { | |
if (!arguments.length) return cols; | |
cols = value; | |
return grid; | |
} | |
grid.bands = function() { | |
bands = true; | |
return grid; | |
} | |
grid.points = function() { | |
bands = false; | |
return grid; | |
} | |
grid.padding = function(value) { | |
if (!arguments.length) return padding; | |
padding = value; | |
return grid; | |
} | |
return grid; | |
}; | |
})(); |
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
font-family: Helvetica; | |
font-size: 10px; | |
background-color: #333333; | |
margin: 0; | |
padding: 0; | |
overflow: hidden; | |
} | |
.rect { | |
fill: #222; | |
} | |
</style> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="./d3-grid.js"></script> | |
<script> | |
var color = d3.scale.ordinal() | |
.domain(["error", "nominal", "unknown"]) | |
.range(["#bc1919", "#428bca", "#777070"]); | |
var data = [].concat( | |
d3.range(1).map(maker("error")), | |
d3.range(1).map(maker("nominal")), | |
d3.range(2).map(maker("unknown")) | |
); | |
var padding = { | |
top: 50, | |
right: 50, | |
bottom: 50, | |
left: 50 | |
}, | |
docEl = document.documentElement, | |
width = docEl.clientWidth - padding.left - padding.right, | |
height = docEl.clientHeight - padding.top - padding.bottom, | |
iconSize = 16; | |
var icon; | |
var grid = d3.layout.grid() | |
.bands() | |
.size([width, height]); | |
var buttons = d3.select("body").selectAll("button") | |
.data(color.domain()) | |
.enter().insert("button", ":first-child") | |
.text(function(d){ return "Add " + d;}) | |
.on("click", function(d){ | |
data.push(maker(d)()); | |
update(); | |
}); | |
var svg = d3.select("body").append("div").append("svg") | |
.attr({ | |
width: width + padding.left + padding.right, | |
height: height + padding.top + padding.bottom | |
}) | |
.append("g") | |
.attr({ | |
transform: "translate(" + padding.left + " " + padding.top +")" | |
}); | |
d3.xml("./person.svg", "image/svg+xml", function(error, frag) { | |
var node = frag.getElementsByTagName("g")[0]; | |
icon = function(){ | |
return node.cloneNode(true); | |
} | |
//use plain Javascript to extract the node | |
update(); | |
}); | |
function update(){ | |
data.sort(function(a, b){ | |
return a.state.localeCompare(b.state) || a.id - b.id; | |
}); | |
grid(data); | |
var dot = svg.selectAll(".icon") | |
.data(data, function(d){ return d.id; }); | |
dot.enter().append(icon) | |
.attr({ | |
"class": "icon", | |
transform: tx_xy | |
}) | |
.style({opacity: 1e-6}) | |
.each(colorize) | |
.on("click", function(d, i){ | |
data.splice(data.indexOf(d), 1); | |
update(); | |
}); | |
dot.transition() | |
.delay(function(d, i){ return i / 10; }) | |
.attr({ | |
transform: tx_xy | |
}) | |
.style({opacity: 1}) | |
.each(colorize); | |
dot.exit().transition() | |
.style({opacity: 1e-6}) | |
.remove(); | |
} | |
function colorize(d){ | |
d3.select(this).selectAll("*") | |
.style({fill: function(){return color(d.state);}}); | |
} | |
function tx_xy(d){ | |
var scale = Math.min.apply(null, | |
grid.nodeSize().map(function(d){ return d/iconSize; })); | |
var tx = "translate(" + d.x + "," + d.y + ")" + " scale(" + scale + ") "; | |
return tx; | |
} | |
function maker(state){ | |
maker._ID = maker._ID ? maker._ID : 1; | |
return function(){ | |
return {state: state, id: maker._ID++}; | |
} | |
} | |
</script> |