Skip to content

Instantly share code, notes, and snippets.

@tomcardoso
Last active January 10, 2020 09:22
Show Gist options
  • Save tomcardoso/1d44732cc7f3d97d6bf7 to your computer and use it in GitHub Desktop.
Save tomcardoso/1d44732cc7f3d97d6bf7 to your computer and use it in GitHub Desktop.
Reusable waffle charts

Reusable waffle charts

Basic waffle charts using Mike Bostock's [http://bost.ocks.org/mike/chart/](reusable charts) convention. Waffles are configurable, as you can see below:

var waffle = new WaffleChart()
  .selector(".chart")
  .data(data)
  .useWidth(false)
  .label("Value of producers' sales in 2013, in thousands of dollars")
  .size(12)
  .gap(2)
  .rows(20)
  .columns(60)
  .rounded(true)();

Each configuration parameter is as follows:

  • selector: The container in which to draw a waffle chart.
  • data: The data to use when drawing the waffle.
  • useWidth: Whether to constrain the waffle to the container's width.
  • label: A label to pass to the waffle. Optional.
  • size: Width and height of each waffle "block", in pixels. Optional. Default: 6.
  • gap: Gap between each block, in pixels. Optional. Default: 2.
  • rows: Number of rows of blocks. Optional. Default: 50.
  • columns: Number of columns of blocks. Optional. Default: 100.
  • rounded: Whether or not to draw the blocks as circles instead of squares. Optional. Default: false.
source value
Crude oil and condensate 44383284
Oil sands 56811903
Natural gas 15595630
NGLs 10172706
Ethane 1428471
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
body {
font-family: Helvetica, sans serif;
font-size: 16px;
color: #333;
}
.chart {
margin: 40px;
width: 620px;
}
.label {
font-size: 18px;
margin-bottom: 10px;
font-weight: bold;
}
.legend {
padding-bottom: 7px;
}
.legend_item {
display: inline-block;
padding-right: 10px;
margin-bottom: 2px;
}
.legend_item_icon, .legend_item_text {
display: inline-block;
vertical-align: middle;
}
.legend_item_icon {
margin-right: 5px;
height: 10px;
width: 10px;
}
</style>
</head>
<body>
<div class="chart"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="./waffle.js"></script>
<script type="text/javascript">
d3.csv("./data.csv", function(err, data) {
if (err) {
console.error(err);
} else {
var waffle = new WaffleChart()
.selector(".chart")
.data(data)
.useWidth(false)
.label("Value of producers' sales in 2013, in thousands of dollars")
.size(12)
.gap(2)
.rows(20)
.columns(50)
.rounded(true)();
}
});
// source: http://statshb.capp.ca/SHB/Sheet.asp?SectionID=4&SheetID=265
</script>
</body>
</html>
var WaffleChart = function() {
var $_selector,
$_data,
$_label,
$_cellSize,
$_cellGap,
$_rows,
$_columns,
$_rounded,
$_keys,
$_useWidth;
var defaults = {
size: 6,
rows: 50,
columns: 100,
rounded: false,
gap: 2
};
function generatedWaffleChart() {
$_keys = d3.keys($_data[0]);
var obj = {
selector: $_selector,
data: $_data,
label: $_label,
size: $_cellSize,
gap: $_cellGap,
rows: $_rows,
columns: $_columns,
rounded: $_rounded
};
drawWaffleChart(obj);
}
function drawWaffleChart(_obj) {
if (!_obj.size) { _obj.size = defaults.size; }
if (!_obj.rows) { _obj.rows = defaults.rows; }
if (!_obj.columns) { _obj.columns = defaults.columns; }
if (_obj.gap === undefined) { _obj.gap = defaults.gap; }
if (_obj.rounded === undefined) { _obj.columns = defaults.rounded; }
var formattedData = [];
var domain = [];
var value = $_keys[$_keys.length - 1];
var total = d3.sum(_obj.data, function(d) { return d[value]; });
if ($_useWidth) {
var forcedWidth = d3.select(_obj.selector).node().getBoundingClientRect().width;
_obj.columns = Math.floor(forcedWidth / (_obj.size + _obj.gap));
}
var squareVal = total / (_obj.rows * _obj.columns);
_obj.data.forEach(function(d, i) {
d[value] = +d[value];
d.units = Math.floor(d[value] / squareVal);
Array(d.units + 1).join(1).split('').map(function() {
formattedData.push({
squareVal: squareVal,
units: d.units,
value: d[value],
groupIndex: i
});
});
domain.push(d[$_keys[0]]);
});
var red = "#CE2A23";
var color = d3.scale.linear()
.domain([1, _obj.data.length - 1])
.interpolate(d3.interpolateRgb)
.range(["#555", "#EEE"]);
// add label
if (_obj.label) {
d3.select(_obj.selector)
.append("div")
.attr("class", "label")
.text(_obj.label);
}
// add legend
var legend = d3.select($_selector)
.append("div")
.attr("class", "legend");
var legendItem = legend.selectAll("div")
.data(_obj.data);
legendItem.enter()
.append("div")
.attr("class", function(d, i) {
return "legend_item legend_item_" + (i + 1);
});
var legendIcon = legendItem.append("div")
.attr("class", "legend_item_icon")
.style("background-color", function(d, i) {
if (i === 0) {
return red;
} else {
return color(i);
}
});
if (_obj.rounded) {
legendIcon.style("border-radius", "50%");
}
legendItem.append("span")
.attr("class", "legend_item_text")
.text(function(d) { return d[$_keys[0]]; });
// set up the dimensions
var width = (_obj.size * _obj.columns) + (_obj.columns * _obj.gap) - _obj.gap;
var height = (_obj.size * _obj.rows) + (_obj.rows * _obj.gap) - _obj.gap;
if ($_useWidth) {
width = d3.select(_obj.selector).node().getBoundingClientRect().width;
}
var svg = d3.select(_obj.selector)
.append("svg")
.attr("class", "waffle")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("transform", "translate(0,0)");
// insert dem items
var item = g.selectAll(".unit")
.data(formattedData);
item.enter()
.append("rect")
.attr("class", "unit")
.attr("width", _obj.size)
.attr("height", _obj.size)
.attr("fill", function(d) {
if (d.groupIndex === 0) {
return red;
} else {
return color(d.groupIndex);
}
})
.attr("x", function(d, i) {
var col = Math.floor(i / _obj.rows);
return (col * (_obj.size)) + (col * _obj.gap);
})
.attr("y", function(d, i) {
var row = i % _obj.rows;
return (_obj.rows * (_obj.size + _obj.gap)) - ((row * _obj.size) + (row * _obj.gap)) - _obj.size - _obj.gap;
})
.append("title")
.text(function (d, i) {
return _obj.data[d.groupIndex][$_keys[0]] + ": " + Math.round((d.units / formattedData.length) * 100) + "%";
});
if (_obj.rounded) {
item
.attr("rx", (_obj.size / 2))
.attr("ry", (_obj.size / 2));
}
}
generatedWaffleChart.selector = function(value){
if (!arguments.length) { return $_selector; }
$_selector = value;
return generatedWaffleChart;
}
generatedWaffleChart.data = function(value){
if (!arguments.length) { return $_data; }
$_data = value;
return generatedWaffleChart;
}
generatedWaffleChart.useWidth = function(value){
if (!arguments.length) { return $_useWidth; }
$_useWidth = value;
return generatedWaffleChart;
}
generatedWaffleChart.label = function(value){
if (!arguments.length) { return $_label; }
$_label = value;
return generatedWaffleChart;
}
generatedWaffleChart.size = function(value){
if (!arguments.length) { return $_cellSize; }
$_cellSize = value;
return generatedWaffleChart;
}
generatedWaffleChart.gap = function(value){
if (!arguments.length) { return $_cellGap; }
$_cellGap = value;
return generatedWaffleChart;
}
generatedWaffleChart.rows = function(value){
if (!arguments.length) { return $_rows; }
$_rows = value;
return generatedWaffleChart;
}
generatedWaffleChart.columns = function(value){
if (!arguments.length) { return $_columns; }
$_columns = value;
return generatedWaffleChart;
}
generatedWaffleChart.rounded = function(value){
if (!arguments.length) { return $_rounded; }
$_rounded = value;
return generatedWaffleChart;
}
return generatedWaffleChart;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment