index.html
<!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 + " )");
var ribbons = d3.range(1980,2005, 0.5).map(function(year,i){
return({
id: i,
color: colorOrder[Math.floor(Math.random()*colorOrder.length)],
startYear: Math.floor(year)
});
});
var positionsByYear = d3.range(1980,2005).map(function(year){
return ribbons.filter(function(d){
return d.startYear <= year;
}).sort(sorter).map(function(d){
return d.id;
});
});
var baselineId = positionsByYear[0][0],
baselineRow = positionsByYear[positionsByYear.length - 1].indexOf(baselineId);
positionsByYear.forEach(function(year){
while (year.indexOf(baselineId) < baselineRow) {
year.unshift(null);
}
});
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]);
ribbons.forEach(function(ribbon){
var top = [],
bottom = [];
positionsByYear.forEach(function(year,i){
var position = year.indexOf(ribbon.id);
if (position >= 0) {
if (!top.length) {
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;
}).length;
top.push([x(i-1),y(position - numBefore)]);
} else {
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)]);
}
}
top.push([x(i),y(position)]);
bottom.unshift([x(i),y(position + 1)]);
}
});
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";
});
function sorter(a,b){
var ia = colorOrder.indexOf(a.color),
ib = colorOrder.indexOf(b.color),
ga = a.startYear,
gb = b.startYear;
if (ia !== ib) {
return ia - ib;
}
if (ga !== gb) {
return ga - gb;
}
return a.id - b.id;
}
</script>
</body>
</html>