Skip to content

Instantly share code, notes, and snippets.

@veltman
Last active January 16, 2020 10:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save veltman/e45cc3a2670779a0bc942ba18163228f to your computer and use it in GitHub Desktop.
Save veltman/e45cc3a2670779a0bc942ba18163228f to your computer and use it in GitHub Desktop.
Crossing the streams
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
path {
fill: none;
stroke-width: 2px;
stroke-linejoin: round;
stroke: #444;
}
.red path {
fill: #ba3e2d;
}
.orange path {
fill: #ff7d3e;
}
.teal path {
fill: #83dfc3;
}
.blue path {
fill: #4c8da1;
}
.yellow path {
fill: #ffea60;
}
</style>
</head>
<body>
<div></div>
<script src="
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script>
var margin = { top: 10, right: 10, left: 10, bottom: 10 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
colorOrder = ["teal","red","yellow","orange","blue"];
var line = d3.svg.line();
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate( " + margin.left + " " + margin.top + " )");
// Make some random data
var ribbons = d3.range(1980,2005, 0.5).map(function(year,i){
return({
id: i, // doesn't matter what this is as long as it's unique
color: colorOrder[Math.floor(Math.random()*colorOrder.length)],
startYear: Math.floor(year)
});
});
// For each year, get an array of ribbons shown, in order (IDs only)
var positionsByYear = d3.range(1980,2005).map(function(year){
return ribbons.filter(function(d){
return d.startYear <= year; // ignore ribbons that haven't started
}).sort(sorter).map(function(d){
return d.id;
});
});
// Pick the one you want to keep flat
// Could set this manually to the ID you care about
var baselineId = positionsByYear[0][0],
baselineRow = positionsByYear[positionsByYear.length - 1].indexOf(baselineId);
// Move positions down so the baseline ID is always at the same vertical index
positionsByYear.forEach(function(year){
while (year.indexOf(baselineId) < baselineRow) {
year.unshift(null);
}
});
// Pixel scales
var x = d3.scale.ordinal()
.domain(d3.range(-1,positionsByYear.length))
.rangeBands([0,width]);
var y = d3.scale.ordinal()
.domain(d3.range(ribbons.length + 1))
.rangeBands([0,height]);
// Construct X/Y coordinate pairs per ribbon
ribbons.forEach(function(ribbon){
var top = [], // Line along the top
bottom = []; // Line along the bottom
positionsByYear.forEach(function(year,i){
// Vertical position
var position = year.indexOf(ribbon.id);
if (position >= 0) {
// Add one extra miter-y point at the beginning
if (!top.length) {
if (ribbon.id === baselineId) {
top.push([x(i-1),(y(position) + y(position + 1)) / 2]);
} else if (position > baselineRow) {
// TODO smarter
var numBefore = ribbons.filter(function(r){
var index = year.indexOf(r.id);
return index > baselineRow && index < position && r.startYear === ribbon.startYear;
}).length;
top.push([x(i-1),y(position - numBefore)]);
} else {
// TODO smarter
var numAfter = ribbons.filter(function(r){
var index = year.indexOf(r.id);
return index < baselineRow && index > position && r.startYear === ribbon.startYear;
}).length;
top.push([x(i-1),y(position + 1 + numAfter)]);
}
}
// Add the top and bottom point
top.push([x(i),y(position)]);
bottom.unshift([x(i),y(position + 1)]);
}
});
// Combine the whole path
ribbon.positions = top.concat(bottom);
});
var paths = svg.selectAll("g")
.data(ribbons)
.enter()
.append("g")
.attr("class",function(d){
return d.color;
});
paths.append("path")
.attr("d",function(d){
return line(d.positions) + "Z";
});
// What order should the ribbons be stacked in for any given X?
function sorter(a,b){
var ia = colorOrder.indexOf(a.color),
ib = colorOrder.indexOf(b.color),
ga = a.startYear,
gb = b.startYear;
// sort by color group first
if (ia !== ib) {
return ia - ib;
}
// then within the group
if (ga !== gb) {
return ga - gb;
}
// tiebreaker
return a.id - b.id;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment