Skip to content

Instantly share code, notes, and snippets.

@mxfh
Last active August 29, 2015 14:07
Show Gist options
  • Save mxfh/3660892144a9e984eb39 to your computer and use it in GitHub Desktop.
Save mxfh/3660892144a9e984eb39 to your computer and use it in GitHub Desktop.
... in a Million

Rendering a SVG Chart for showing parts of a whole.

Supported URL query string parameters

  • 'count'
  • 'total' (rounds up to next number with integer root)
  • 'boxsize'
  • 'size' (output size of SVG)
  • 'padding' (between boxes)
  • 'outerPadding' (between outline and):
  • 'outline' (width, in units of output size)
  • 'pixelperfect' (SVG size equals viewbox, flag overrides 'size')

Examples:

Currently you shouldn't use unreasonable count numbers (lets say > 1000) since each box is rendered as svg node, if you want to render more feel free to improve this by rendering into canvas or fill a svg shape with pattern definition

SVG Crowbar recommended http://nytimes.github.io/svg-crowbar/

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>In a Million</title>
<base target="_parent" />
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="script.js" charset="utf-8"></script>
<style>
svg text {fill: white; font-family: sans-serif; font-weight: normal;}
#vis {float: left;}
#text {float: left; padding-left: 10px;}
.boxes rect {stroke: none; fill: black}
</style>
</head>
<body onload="drawBox()">
<div id="vis"></div>
<div id="text">
<p><span id="count"></span> in <span id="total">a million</span></p>
<small>
<p><a href="http://nytimes.github.io/svg-crowbar/">get a crowbar</a></p>
</small>
</div>
</body>
</html>
// url parser http://stackoverflow.com/a/901144/678708
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#/]*)";
var regex = new RegExp(regexS);
var results = regex.exec(parent.window.location.href);
if(results == null) {return "";} else {return decodeURIComponent(results[1].replace(/\+/g, " "));}
}
function isNumber(n) {return !isNaN(parseFloat(n)) && isFinite(n);}
function drawBox() {
// defaults
var data = {
count : 31,
total : 1000000
};
var box = {
size : 1
};
var layout = {
output : 200,
padding : 0,
outerPadding : 0,
outline : 1,
pixelperfect : false
};
input = {
count : getParameterByName("count"),
total : getParameterByName("total"),
boxsize : getParameterByName("boxsize"),
size : getParameterByName("size"),
pixelperfect : getParameterByName("pixelperfect"),
padding : getParameterByName("padding"),
outerPadding : getParameterByName("outerpadding"),
outline : getParameterByName("outline")
}
//console.log("inputs:",input);
if (isNumber(input.count)) {data.count = parseFloat(input.count);}
if (isNumber(input.total)) {data.total = parseInt(input.total);}
if (isNumber(input.size)) {layout.output = parseInt(input.size);}
if (isNumber(input.padding)) {layout.padding = parseFloat(input.padding);}
if (isNumber(input.boxsize)) {box.size = parseFloat(input.boxsize);}
if (input.pixelperfect) {layout.pixelperfect = parseInt(input.pixelperfect);}
if (input.outerPadding) {layout.outerPadding = parseInt(input.outerPadding);}
if (input.outline) {layout.outline = parseFloat(input.outline);}
var remainder = data.count % 1;
layout.dim = Math.ceil(Math.sqrt(data.total));
layout.dimCount = Math.ceil(Math.sqrt(data.count));
if (data.total != layout.dim * layout.dim) {
data.total = layout.dim * layout.dim;
console.info("changing count to next squared integer " + data.total);
}
layout.innerWidth = (box.size * layout.dim) + layout.padding * (layout.dim - 1);
if (layout.pixelperfect) {
layout.scale = 1
} else {
layout.scale = (layout.output - layout.outline * 2) / (layout.innerWidth + layout.outerPadding * 2);
}
layout.outline = layout.outline / layout.scale;
layout.offset = layout.outerPadding + layout.outline;
layout.width = layout.innerWidth + 2 * layout.offset;
if (layout.pixelperfect) {layout.output = layout.width;}
console.log("data:", data, "layout:", layout);
d3.select("#vis").append("svg")
.attr("viewBox", "0 0 " + layout.width + " " + layout.width)
.attr("width", layout.output)
.attr("height", layout.output);
d3.select("svg").append("rect")
.attr("x", layout.outline/2)
.attr("y", layout.outline/2)
.attr("width", layout.width - layout.outline)
.attr("height", layout.width - layout.outline)
.attr("stroke", "black")
.attr("stroke-width", layout.outline)
.attr("fill", "none")
.classed("outline", true);
d3.select("svg")
.append("g")
.classed("boxes", true);
var x = 0, y = 0, c = 0;
var xP;
var step = box.size + layout.padding;
var y0 = layout.width - layout.offset - box.size;
var yP = y0;
var boxes = d3.selectAll(".boxes");
var lastFull = data.count - remainder;
for (y; y < layout.dimCount && y < layout.dim && c < lastFull; y++) {
xP = layout.offset;
for (x = 0; x < layout.dimCount && x < layout.dim && c < lastFull; x++) {
c++;
//console.log(c,xP,yP,y);
boxes.append("rect")
.attr("x", xP)
.attr("y", yP)
.attr("width", box.size)
.attr("height", box.size)
.attr("id", c);
xP += step;
}
yP = yP - step;
}
//console.log(c,xP,yP,x,y,lastFull, remainder, layout.dim);
if (remainder > 0) {
if (x >= layout.dimCount) {
xP = layout.offset;
} else {
yP += step;
}
if (lastFull < 3 && x < layout.dim ) {
yP = y0;
xP = layout.offset + (step * lastFull);
} //no new line
boxes.append("rect")
.attr("x", xP)
.attr("y", yP)
.attr("width", box.size * remainder)
.attr("height", box.size)
.attr("id", data.count);
}
document.getElementById("count").innerHTML = data.count;
if (data.total = 1000000) {
document.getElementById("total").innerHTML = "a million";
} else {
document.getElementById("total").innerHTML = data.total;
}
document.title = (data.count + " in " + data.total);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment