Skip to content

Instantly share code, notes, and snippets.

@CBasis
Last active January 16, 2018 09:00
Show Gist options
  • Save CBasis/e089a7952ba9048ea891cd87f50fa796 to your computer and use it in GitHub Desktop.
Save CBasis/e089a7952ba9048ea891cd87f50fa796 to your computer and use it in GitHub Desktop.
d3.layout.timeline categorized timelines
license: mit

An example of d3.layout.timeline that shows wars the United States has been involved in during its first 100 years (from Wikipedia). Events have been categorized and rendered in separate lanes for colonial wars, native wars, internal wars, wars involving European powers, and wars in Latin America.

forked from emeeks's block: d3.layout.timeline categorized timelines

(function() {
d3.layout.timeline = function() {
var timelines = [];
var dateAccessor = function (d) {return new Date(d)};
var processedTimelines = [];
var startAccessor = function (d) {return d.start};
var endAccessor = function (d) {return d.end};
var size = [500,100];
var timelineExtent = [-Infinity, Infinity];
var setExtent = [];
var displayScale = d3.scale.linear();
var swimlanes = [];
var padding = 0;
var fixedExtent = false;
var maximumHeight = Infinity;
function processTimelines() {
timelines.forEach(function (band) {
var projectedBand = {};
for (var x in band) {
if (band.hasOwnProperty(x)) {
projectedBand[x] = band[x];
}
}
projectedBand.start = dateAccessor(startAccessor(band));
projectedBand.end = dateAccessor(endAccessor(band));
projectedBand.lane = 0;
processedTimelines.push(projectedBand);
});
}
function projectTimelines() {
if (fixedExtent === false) {
var minStart = d3.min(processedTimelines, function (d) {return d.start});
var maxEnd = d3.max(processedTimelines, function (d) {return d.end});
timelineExtent = [minStart,maxEnd];
}
else {
timelineExtent = [dateAccessor(setExtent[0]), dateAccessor(setExtent[1])];
}
displayScale.domain(timelineExtent).range([0,size[0]]);
processedTimelines.forEach(function (band) {
band.originalStart = band.start;
band.originalEnd = band.end;
band.start = displayScale(band.start);
band.end = displayScale(band.end);
});
}
function fitsIn(lane, band) {
if (lane.end < band.start || lane.start > band.end) {
return true;
}
var filteredLane = lane.filter(function (d) {return d.start <= band.end && d.end >= band.start});
if (filteredLane.length === 0) {
return true;
}
return false;
}
function findlane(band) {
//make the first array
if (swimlanes[0] === undefined) {
swimlanes[0] = [band];
return;
}
var l = swimlanes.length - 1;
var x = 0;
while (x <= l) {
if (fitsIn(swimlanes[x], band)) {
swimlanes[x].push(band);
return;
}
x++;
}
swimlanes[x] = [band];
return;
}
function timeline(data) {
if (!arguments.length) return timeline;
timelines = data;
processedTimelines = [];
swimlanes = [];
processTimelines();
projectTimelines();
processedTimelines.forEach(function (band) {
findlane(band);
});
var height = size[1] / swimlanes.length;
height = Math.min(height, maximumHeight);
swimlanes.forEach(function (lane, i) {
lane.forEach(function (band) {
band.y = i * (height);
band.dy = height - padding;
band.lane = i;
});
});
return processedTimelines;
}
timeline.dateFormat = function (_x) {
if (!arguments.length) return dateAccessor;
dateAccessor = _x;
return timeline;
}
timeline.bandStart = function (_x) {
if (!arguments.length) return startAccessor;
startAccessor = _x;
return timeline;
}
timeline.bandEnd = function (_x) {
if (!arguments.length) return endAccessor;
endAccessor = _x;
return timeline;
}
timeline.size = function (_x) {
if (!arguments.length) return size;
size = _x;
return timeline;
}
timeline.padding = function (_x) {
if (!arguments.length) return padding;
padding = _x;
return timeline;
}
timeline.extent = function (_x) {
if (!arguments.length) return timelineExtent;
fixedExtent = true;
setExtent = _x;
if (_x.length === 0) {
fixedExtent = false;
}
return timeline;
}
timeline.maxBandHeight = function (_x) {
if (!arguments.length) return maximumHeight;
maximumHeight = _x;
return timeline;
}
return timeline;
}
})();
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Categorized Timeline Using Dates</title>
<meta charset="utf-8" />
<style type="text/css">
svg {
height: 1100px;
width: 1100px;
}
div.viz {
height: 1000px;
width: 1000px;
}
</style>
</head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js" charset="utf-8" type="text/javascript"></script>
<script src="d3.layout.timeline.js" charset="utf-8" type="text/javascript"></script>
<script>
types = ["PROD","INT","DEV","SBX"];
colorScale = d3.scale.ordinal()
.domain(types)
.range(["#96abb1", "#313746", "#b0909d", "#687a97", "#292014"]);
d3.csv("wars.csv", function (csv) {
var timeline = d3.layout.timeline()
.size([500,80])
.extent(["12/1/2017", "1/15/2018"])
.padding(3)
.maxBandHeight(20);
types.forEach(function (type, i) {
onlyThisType = csv.filter(function(d) {return d.sphere === type});
theseBands = timeline(onlyThisType);
d3.select("svg").append("g")
.attr("transform", "translate(100," + (35 + (i * 90)) + ")")
.selectAll("rect")
.data(theseBands)
.enter()
.append("rect")
.attr("rx", 2)
.attr("x", function (d) {return d.start})
.attr("y", function (d) {return d.y})
.attr("height", function (d) {return d.dy})
.attr("width", function (d) {return d.end - d.start})
.style("fill", function (d) {return colorScale(d.sphere)})
.style("stroke", "black")
.style("stroke-width", 1);
d3.select("svg").append("text")
.text(type)
.attr("y", 50 + (i * 90))
.attr("x", 20)
})
})
</script>
<body>
<div id="viz">
<svg style="background:white;" height=1100 width=1100>
</svg>
</div>
<footer>
</footer>
</body>
</html>
name start end sphere
DB_PS0 1/1/2018 1/2/2018 PROD
DB_PS1 1/1/2018 1/2/2018 PROD
DB_ZS0 1/1/2018 1/2/2018 PROD
DB_ES0 1/1/2018 1/2/2018 DEV
DB_ES1 1/1/2018 1/2/2018 DEV
DB_VS0 1/1/2018 1/2/2018 DEV
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment