Filtering items in a grid layout.
forked from herrstucki's block: Filtering the Grid
Filtering items in a grid layout.
forked from herrstucki's block: Filtering the Grid
| (function() { | |
| d3.comparator = function() { | |
| var cmps = [], accessors = []; | |
| var comparator = function(a, b) { | |
| var i = -1, | |
| n = cmps.length, | |
| result; | |
| while (++i < n) { | |
| result = cmps[i](accessors[i](a), accessors[i](b)); | |
| if (result !== 0) return result; | |
| } | |
| return 0; | |
| } | |
| comparator.order = function(cmp, accessor) { | |
| cmps.push(cmp); | |
| accessors.push(accessor || identity); | |
| return comparator; | |
| } | |
| return comparator; | |
| } | |
| function identity(d) { return d; } | |
| })(); |
| (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, sans-serif; | |
| } | |
| svg { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| z-index: -1; | |
| } | |
| .filter-btn.active { | |
| font-weight: bold; | |
| } | |
| </style> | |
| <body> | |
| Color | |
| <a href="#" class="filter-btn" data-filter="color" data-value="all">all</a> / | |
| <a href="#" class="filter-btn" data-filter="color" data-value="pink">pink</a> / | |
| <a href="#" class="filter-btn" data-filter="color" data-value="blue">blue</a> | |
| <br> | |
| Shape | |
| <a href="#" class="filter-btn" data-filter="shape" data-value="all">all</a> / | |
| <a href="#" class="filter-btn" data-filter="shape" data-value="square">square</a> / | |
| <a href="#" class="filter-btn" data-filter="shape" data-value="circle">circle</a> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script src="d3-grid.js"></script> | |
| <script src="d3-comparator.js"></script> | |
| <script> | |
| var width = 960, | |
| height = 500; | |
| var filters = { | |
| color: "all", | |
| shape: "all" | |
| } | |
| var grid = d3.layout.grid() | |
| .size([400, 400]); | |
| var color = d3.scale.ordinal() | |
| .domain(["pink", "blue"]) | |
| .range(["#F66A96", "#3E6E9C"]); | |
| var size = d3.scale.linear() | |
| .domain([0, 9]) | |
| .range([0, 2000]); | |
| var symbol = d3.svg.symbol(); | |
| var sortBySize = d3.comparator() | |
| .order(d3.descending, function(d) { return d.size; }); | |
| var data = d3.range(64).map(function(d) { | |
| return { | |
| id: d, | |
| size: 1 + Math.floor(Math.random() * 9), | |
| color: Math.random() > 0.5 ? "pink" : "blue", | |
| shape: Math.random() > 0.5 ? "square" : "circle" | |
| }; | |
| }); | |
| var svg = d3.select("body").append("svg") | |
| .attr({ | |
| width: width, | |
| height: height | |
| }) | |
| .append("g") | |
| .attr("transform", "translate(280,50)"); | |
| var filterButtons = d3.selectAll(".filter-btn") | |
| .on("click", function(d) { | |
| d3.event.preventDefault(); | |
| filters[this.dataset.filter] = this.dataset.value; | |
| update(); | |
| }); | |
| update(); | |
| function update() { | |
| var node = svg.selectAll(".node") | |
| .data(grid(data.filter(applyFilters).sort(sortBySize)), function(d) { return d.id; }); | |
| node.enter().append("path") | |
| .attr("class", "node") | |
| .attr("d", function(d) { return symbol.type(d.shape).size(1e-9)(); }) | |
| .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) | |
| .style("fill", function(d) { return color(d.color); }); | |
| node.transition().duration(1000).delay(function(d, i) { return i * 20; }) | |
| .attr("d", function(d) { return symbol.type(d.shape).size(size(d.size))(); }) | |
| .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); | |
| node.exit().transition() | |
| .attr("d", function(d) { return symbol.type(d.shape).size(1e-9)(); }) | |
| .remove(); | |
| filterButtons | |
| .classed("active", function(d) { return this.dataset.value === filters[this.dataset.filter]; }); | |
| } | |
| function applyFilters(d) { | |
| for (var f in filters) { | |
| if (filters[f] === "all") continue; | |
| if (filters[f] !== d[f]) return false; | |
| } | |
| return true; | |
| } | |
| </script> |