Skip to content

Instantly share code, notes, and snippets.

@bunkat
Created July 17, 2012 05:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bunkat/3127459 to your computer and use it in GitHub Desktop.
Save bunkat/3127459 to your computer and use it in GitHub Desktop.
Swimlane with internal data and date axis
<html>
<head>
<title>Swimlane using d3.js</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.js"></script>
<script type="text/javascript" src="randomData.js"></script>
<style>
.chart {
shape-rendering: crispEdges;
}
.mini text {
font: 9px sans-serif;
}
.main text {
font: 12px sans-serif;
}
.month text {
text-anchor: start;
}
.todayLine {
stroke: blue;
stroke-width: 1.5;
}
.axis line, .axis path {
stroke: black;
}
.miniItem {
stroke-width: 6;
}
.item {
stroke: gray;
fill: #ddd;
}
.brush .extent {
stroke: gray;
fill: blue;
fill-opacity: .165;
}
</style>
</head>
<body>
<script type="text/javascript">
// helper function to create dates prior to 1000
var yr = function(year) {
var date = new Date(2000,1,1);
date.setFullYear(year);
return date;
}
// lanes is an array of lane objects that have the following properties
// id: the unique id for this swimlane
// label: the text label for this swimlane
//
// these determine how many horizontal lanes there will be in the chart
// and what their names will be
var lanes = [
{id: 0, label: 'Chinese'},
{id: 1, label: 'Japanese'},
{id: 2, label: 'Korean'}
];
// items is an array of item objects that have the following properties
// id: the unique id for this item
// lane: the id of the lane that this item belongs in
// desc: the description for this item
// start: the starting value for this item
// end: the end value for this item
// class: the css class that should be applied to this item
//
// these define the actual items that are displayed on the chart
var items = [
{id: 0, lane: 0, desc: 'Qin', start: yr(5), end: yr(205), class: 'item'},
{id: 1, lane: 0, desc: 'Jin', start: yr(265), end: yr(420), class: 'item'}
];
// define the chart extents
var margin = {top: 20, right: 15, bottom: 15, left: 70}
, width = 960 - margin.left - margin.right
, height = 500 - margin.top - margin.bottom
, miniHeight = lanes.length * 12 + 50
, mainHeight = height - miniHeight - 50;
var x = d3.time.scale()
.domain([d3.min(items, function(d) { return d.start - 100000; }),
d3.max(items, function(d) { return d.end; })])
.range([0, width]);
var x1 = d3.time.scale().range([0, width]);
var ext = d3.extent(lanes, function(d) { return d.id; });
var y1 = d3.scale.linear().domain([ext[0], ext[1] + 1]).range([0, mainHeight]);
var y2 = d3.scale.linear().domain([ext[0], ext[1] + 1]).range([0, miniHeight]);
var chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart');
chart.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', width)
.attr('height', mainHeight);
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', mainHeight)
.attr('class', 'main');
var mini = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + (mainHeight + 60) + ')')
.attr('width', width)
.attr('height', miniHeight)
.attr('class', 'mini');
// draw the lanes for the main chart
main.append('g').selectAll('.laneLines')
.data(lanes)
.enter().append('line')
.attr('x1', 0)
.attr('y1', function(d) { return d3.round(y1(d.id)) + 0.5; })
.attr('x2', width)
.attr('y2', function(d) { return d3.round(y1(d.id)) + 0.5; })
.attr('stroke', function(d) { return d.label === '' ? 'white' : 'lightgray' });
main.append('g').selectAll('.laneText')
.data(lanes)
.enter().append('text')
.text(function(d) { return d.label; })
.attr('x', -10)
.attr('y', function(d) { return y1(d.id + .5); })
.attr('dy', '0.5ex')
.attr('text-anchor', 'end')
.attr('class', 'laneText');
// draw the lanes for the mini chart
mini.append('g').selectAll('.laneLines')
.data(lanes)
.enter().append('line')
.attr('x1', 0)
.attr('y1', function(d) { return d3.round(y2(d.id)) + 0.5; })
.attr('x2', width)
.attr('y2', function(d) { return d3.round(y2(d.id)) + 0.5; })
.attr('stroke', function(d) { return d.label === '' ? 'white' : 'lightgray' });
mini.append('g').selectAll('.laneText')
.data(lanes)
.enter().append('text')
.text(function(d) { return d.label; })
.attr('x', -10)
.attr('y', function(d) { return y2(d.id + .5); })
.attr('dy', '0.5ex')
.attr('text-anchor', 'end')
.attr('class', 'laneText');
// draw the x axis
var xDateAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
var x1DateAxis = d3.svg.axis()
.scale(x1)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + mainHeight + ')')
.attr('class', 'main axis date')
.call(x1DateAxis);
mini.append('g')
.attr('transform', 'translate(0,' + miniHeight + ')')
.attr('class', 'axis date')
.call(xDateAxis);
// draw the items
var itemRects = main.append('g')
.attr('clip-path', 'url(#clip)');
mini.append('g').selectAll('miniItems')
.data(getPaths(items))
.enter().append('path')
.attr('class', function(d) { return 'miniItem ' + d.class; })
.attr('d', function(d) { return d.path; });
// invisible hit area to move around the selection window
mini.append('rect')
.attr('pointer-events', 'painted')
.attr('width', width)
.attr('height', miniHeight)
.attr('visibility', 'hidden')
.on('mouseup', moveBrush);
// draw the selection area
var brush = d3.svg.brush()
.x(x)
.extent([yr(150), yr(300)])
.on("brush", display);
mini.append('g')
.attr('class', 'x brush')
.call(brush)
.selectAll('rect')
.attr('y', 1)
.attr('height', miniHeight - 1);
mini.selectAll('rect.background').remove();
display();
function display () {
var rects, labels
, minExtent = brush.extent()[0]
, maxExtent = brush.extent()[1]
, visItems = items.filter(function (d) { return d.start < maxExtent && d.end > minExtent});
mini.select('.brush').call(brush.extent([minExtent, maxExtent]));
x1.domain([minExtent, maxExtent]);
// update the axis
main.select('.main.axis.date').call(x1DateAxis);
// upate the item rects
rects = itemRects.selectAll('rect')
.data(visItems, function (d) { return d.id; })
.attr('x', function(d) { return x1(d.start); })
.attr('width', function(d) { return x1(d.end) - x1(d.start); });
rects.enter().append('rect')
.attr('x', function(d) { return x1(d.start); })
.attr('y', function(d) { return y1(d.lane) + .1 * y1(1) + 0.5; })
.attr('width', function(d) { return x1(d.end) - x1(d.start); })
.attr('height', function(d) { return .8 * y1(1); })
.attr('class', function(d) { return 'mainItem ' + d.class; });
rects.exit().remove();
// update the item labels
labels = itemRects.selectAll('text')
.data(visItems, function (d) { return d.id; })
.attr('x', function(d) { return x1(Math.max(d.start, minExtent)) + 2; });
labels.enter().append('text')
.text(function (d) { return 'Item\n\n\n\n Id: ' + d.id; })
.attr('x', function(d) { return x1(Math.max(d.start, minExtent)) + 2; })
.attr('y', function(d) { return y1(d.lane) + .4 * y1(1) + 0.5; })
.attr('text-anchor', 'start')
.attr('class', 'itemLabel');
labels.exit().remove();
}
function moveBrush () {
var origin = d3.mouse(this)
, point = x.invert(origin[0])
, halfExtent = (brush.extent()[1].getTime() - brush.extent()[0].getTime()) / 2
, start = new Date(point.getTime() - halfExtent)
, end = new Date(point.getTime() + halfExtent);
brush.extent([start,end]);
display();
}
// generates a single path for each item class in the mini display
// ugly - but draws mini 2x faster than append lines or line generator
// is there a better way to do a bunch of lines as a single path with d3?
function getPaths(items) {
var paths = {}, d, offset = .5 * y2(1) + 0.5, result = [];
for (var i = 0; i < items.length; i++) {
d = items[i];
if (!paths[d.class]) paths[d.class] = '';
paths[d.class] += ['M',x(d.start),(y2(d.lane) + offset),'H',x(d.end)].join(' ');
}
for (var className in paths) {
result.push({class: className, path: paths[className]});
}
return result;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment