Skip to content

Instantly share code, notes, and snippets.

@zachmargolis
Created April 1, 2015 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zachmargolis/4940182928ee60aa7099 to your computer and use it in GitHub Desktop.
Save zachmargolis/4940182928ee60aa7099 to your computer and use it in GitHub Desktop.
Line Comparison
<!DOCTYPE html>
<html>
<head>
<title>WOO</title>
<style>
body {
font: 10pt/12pt "Helvetica", sans-serif;
}
.axis path {
fill: none;
stroke: #555;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke-width: 3px;
}
.line.a {
stroke: #333;
}
.line.b {
stroke: #888;
}
.area {
stroke: none;
fill: none;
fill-opacity: 0.25;
}
.area.upper {
fill: #0f0;
}
.area.lower {
fill: #f00;
}
</style>
</head>
<body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script type="text/javascript">
var n = 100,
a = bumpLayer(n, .1),
b = bumpLayer(n, .1);
var margin = { top: 10, right: 10, bottom: 25, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
var x = d3.scale.linear()
.domain([0, n])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, d3.max(a.concat(b), function(d) { return d.y })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.orient('bottom')
.scale(x);
var xAxisElem = svg.append('g')
.attr('class', 'x axis')
.attr('transform', translate(margin.left, height + margin.top + 5));
xAxisElem.call(xAxis);
var yAxis = d3.svg.axis()
.orient('left')
.scale(y);
var yAxisElem = svg.append('g')
.attr('class', 'y axis')
.attr('transform', translate(margin.left - 5, margin.top));
yAxisElem.call(yAxis);
addIntersections(a, b)
var linefn = d3.svg.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
var upperAreaFn = d3.svg.area()
.x(function(d) { return x(d.a.x) })
.y0(function(d) { return y(d.b.y) })
.y1(function(d) { return y(Math.max(d.a.y, d.b.y)) });
var lowerAreaFn = d3.svg.area()
.x(function(d) { return x(d.a.x) })
.y0(function(d) { return y(d.b.y) })
.y1(function(d) { return y(Math.min(d.a.y, d.b.y)) });
var zipped = zip({ a: a, b: b });
data = [ zipped ];
var lines = svg.append('g')
.attr('transform', translate(margin.left, margin.top))
.selectAll('g.group')
.data(data);
var enterLines = lines.enter()
.append('g')
.attr('class', 'group');
// add them in backwards on purpose
enterLines.append('path').attr('class', 'lower area')
enterLines.append('path').attr('class', 'upper area')
enterLines.append('path').attr('class', 'b line');
enterLines.append('path').attr('class', 'a line');
lines.selectAll('path.a.line')
.attr('d', function(d) { return linefn(d.map(function(d) { return d.a; })) });
lines.selectAll('path.b.line')
.attr('d', function(d) { return linefn(d.map(function(d) { return d.b; })) });
lines.selectAll('path.upper.area')
.attr('d', upperAreaFn)
lines.selectAll('path.lower.area')
.attr('d', lowerAreaFn);
// Inspired by Lee Byron's test data generator.
// Borrowed from http://bl.ocks.org/mbostock/3943967
function bumpLayer(n, o) {
function bump(a) {
var x = 1 / (.1 + Math.random()),
y = 2 * Math.random() - .5,
z = 10 / (.1 + Math.random());
for (var i = 0; i < n; i++) {
var w = (i / n - y) * z;
a[i] += x * Math.exp(-w * w);
}
}
var a = [], i;
for (i = 0; i < n; ++i) a[i] = o + o * Math.random();
for (i = 0; i < 5; ++i) bump(a);
return a.map(function(d, i) { return {x: i, y: Math.max(0, d)}; });
}
function translate(x, y) {
return "translate(" + x + ", " + y + ")";
}
function zip(o) {
var keys = d3.keys(o);
var lengths = d3.values(o).map(function(d) { return d.length });
var length = lengths[0];
lengths.forEach(function(d) {
if (d != length) {
throw new TypeError("lengths of values to zip() must be equal")
}
});
var results = [];
for (var i = 0; i < length; i++) {
var result = {};
keys.forEach(function(k) {
result[k] = o[k][i];
});
results.push(result);
}
return results;
}
function addIntersections(a, b) {
if (a.length != b.length) {
throw new TypeError("a.length != b.length");
}
for (var i = 0; i < a.length - 1; i++) {
var a1 = a[i],
b1 = b[i],
a2 = a[i + 1],
b2 = b[i + 1];
if ((a1.y > b1.y && a2.y < b2.y) ^ (a1.y < b1.y && a2.y > b2.y)) {
// calculate line segment intersection
var s = (((b2.x - b1.x) * (a1.y - b1.y)) - ((b2.y - b1.y) * (a1.x - b1.x))) /
(((b2.y - b1.y) * (a2.x - a1.x)) - ((b2.x - b1.x) * (a2.y - a1.y)));
var t = {
x: a1.x + (s * (a2.x - a1.x)),
y: a1.y + (s * (a2.y - a1.y))
}
var spliceIndex = ++i;
a.splice(spliceIndex, 0, t);
b.splice(spliceIndex, 0, t);
}
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment