D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
veltman
Full window
Github gist
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>