https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></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)
.attr("transform","translate( " + margin.left + " " + margin.top + " )");
var ribbons = d3.range(1980,2005, 0.5).map(function(year,i){
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){
// 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) {
var x = d3.scale.ordinal()
.domain(d3.range(-1,positionsByYear.length))
var y = d3.scale.ordinal()
.domain(d3.range(ribbons.length + 1))
// 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){
var position = year.indexOf(ribbon.id);
// Add one extra miter-y point at the beginning
if (ribbon.id === baselineId) {
top.push([x(i-1),(y(position) + y(position + 1)) / 2]);
} else if (position > baselineRow) {
var numBefore = ribbons.filter(function(r){
var index = year.indexOf(r.id);
return index > baselineRow && index < position && r.startYear === ribbon.startYear;
top.push([x(i-1),y(position - numBefore)]);
var numAfter = ribbons.filter(function(r){
var index = year.indexOf(r.id);
return index < baselineRow && index > position && r.startYear === ribbon.startYear;
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")
.attr("class",function(d){
return line(d.positions) + "Z";
// What order should the ribbons be stacked in for any given X?
var ia = colorOrder.indexOf(a.color),
ib = colorOrder.indexOf(b.color),
// sort by color group first