Created
May 20, 2014 23:29
-
-
Save pcarleton/52cb4cecbd9a26353c0d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var lanesChart = function() { | |
var margin = {top: 20, right: 2, bottom: 15, left: 120}, | |
width = 960, | |
height = 600, | |
miniLaneHeight = 8, | |
xMiniScale = d3.time.scale(), | |
yMiniScale = d3.scale.linear(), | |
xMainScale = d3.time.scale(), | |
yMainScale = d3.scale.linear(), | |
xMainAxis = d3.svg.axis().scale(xMainScale).orient("bottom") | |
xMiniAxis = d3.svg.axis().scale(xMiniScale).orient("bottom") | |
colorFunc = function(_) { return "#666"; }, | |
brush = d3.svg.brush(), | |
timeFormat = "%-I:%M%p"; | |
var h = function() {return height - margin.top - margin.bottom}; | |
var w = function() {return width - margin.left - margin.right}; | |
function chart(selection) { | |
selection.each(function(data) { | |
console.log(data); | |
// Data is a map of strings to intervals | |
// We extract the list of strings and a separate list of items | |
// with the label data and indices. | |
var laneLabels = Object.keys(data); | |
var items = []; | |
laneLabels.forEach(function(label, index) { | |
data[label].forEach(function(interval, intervalIndex) { | |
items.push({"lane":index, | |
"id": label + "-" + intervalIndex, | |
"start":new Date(interval.start), | |
"end": new Date(interval.end)}); | |
}); | |
}); | |
var miniHeight = laneLabels.length * miniLaneHeight + 50; // why 50? | |
var mainHeight = h() - miniHeight - 50; // why 50? | |
// Mini scales | |
xMiniScale | |
.domain([d3.min(items, function(d) { return d.start; }), | |
d3.max(items, function(d) { return d.end; })]) | |
.range([0, w()]); | |
yMiniScale | |
.domain([0, laneLabels.length+1]) | |
.range([0, miniHeight]); | |
// Main scales | |
xMainScale.range([0, w()]); | |
yMainScale | |
.domain([0, laneLabels.length+1]) | |
.range([0, mainHeight]); | |
// Axes | |
xMainAxis | |
.ticks(d3.time.hours, 1) | |
.tickFormat(d3.time.format(timeFormat)) | |
.tickSize(mainHeight, 0); | |
xMiniAxis | |
.ticks(d3.time.hours, 2) | |
.tickFormat(d3.time.format(timeFormat)) | |
.tickSize(6, 0); | |
// Select svg element if it exists | |
var svg = d3.select(this).selectAll("svg").data([data]); | |
// Otherwise create the chart. | |
var gEnter = svg.enter().append("svg").append("g"); | |
gEnter.append("defs").append("clipPath") | |
.attr("id", "clip") | |
.append("rect") | |
.attr("width", w()) | |
.attr("height", mainHeight); | |
gEnter.append("g").attr("class", "mini"); | |
gEnter.append("g").attr("class", "main"); | |
var main = d3.select(this).select(".main").data([]).transition() | |
.attr("width", w()) | |
.attr("height", mainHeight) | |
.attr("class", "main"); | |
var mini = d3.select(this).select(".mini") | |
.attr("transform", "translate(0," + (margin.top + mainHeight) + ")") | |
.attr("width", w) | |
.attr("height", miniHeight) | |
.attr("class", "mini"); | |
// Update outer dimensions | |
svg.attr("width", width) | |
.attr("height", height); | |
// Update inner dimensions | |
var g = svg.select("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
// Main lanes setup | |
// Axis | |
gEnter.select(".main").append("g") | |
.attr('transform', 'translate(0,'+yMainScale(lanes.length)+')') | |
.attr("class", "main axis hour") | |
.call(xMainAxis) | |
.selectAll('text') | |
.attr('dx', 5) | |
.attr('dy', 12); | |
gEnter.select(".main").append("g").attr("class", "mainLabels"); | |
// Mini setup | |
// Axis | |
gEnter.select(".mini").append("g") | |
.attr("class", "mini axis miniAxis"); | |
d3.select(this).select(".miniAxis") | |
.attr('transform', 'translate(0,'+miniHeight+ ')') | |
.call(xMiniAxis) | |
.selectAll('text') | |
.attr('dx', 5) | |
.attr('dy', 12); | |
gEnter.select(".mini").append("g").attr("class", "miniLabels"); | |
// Lane labels | |
var miniLabels = d3.select(this).select(".miniLabels").selectAll(".laneText") | |
.data(laneLabels); | |
miniLabels | |
.attr("y", function(d, i) {return yMiniScale(i + .5);}) | |
miniLabels | |
.enter().append("text") | |
.text(function(d) {return d;}) | |
.attr("x", 0) | |
.attr("y", function(d, i) {return yMiniScale(i + .5);}) | |
.attr("dy", ".5ex") | |
.attr("text-anchor", "end") | |
.attr("class", "laneText"); | |
miniLabels.exit().transition().remove(); | |
// Mini rects | |
gEnter.select(".mini").append("g").attr("class", "miniRects"); | |
var miniRects = d3.select(this).select(".miniRects").selectAll(".miniItem") | |
.data(items, function(d) { return d.id; }); | |
miniRects | |
.attr("x", function(d) {return xMiniScale(d.start);}) | |
.attr("y", function(d) {return yMiniScale(d.lane + .5) - 5;}) // Why 5? | |
.attr("width", function(d) { return xMiniScale(d.end) - xMiniScale(d.start);}); | |
miniRects | |
.enter().append("rect") | |
.style("fill", function(d) { return colorFunc(laneLabels[d.lane]) }) | |
.attr("x", function(d) {return xMiniScale(d.start);}) | |
.attr("y", function(d) {return yMiniScale(d.lane + .5) - 5;}) // Why 5? | |
.attr("width", function(d) { return xMiniScale(d.end) - xMiniScale(d.start);}) | |
.attr("id", function(d) { return d.id;}) | |
.attr("class", "miniItem") | |
.attr("height", 10); // Make 10 configurable? | |
miniRects.exit().remove(); | |
//brush | |
brush.x(xMiniScale).on("brush", display); | |
gEnter.select(".mini").append("g") | |
.attr("class", "x brush"); | |
mini.select(".brush") | |
.call(brush) | |
.selectAll("rect") | |
.attr("y", 1) | |
.attr("height", miniHeight - 1); | |
// Start off with the last hour selected withthebrush. | |
console.log(brush.extent()); | |
if ((brush.extent()[0] - brush.extent()[1]) == 0) { | |
var endTime = d3.max(items, function(d) { return d.end; }); | |
var startTime = endTime - 60 * 60 * 1000; | |
mini.select(".brush") | |
.call(brush.extent([startTime, endTime])); | |
} | |
gEnter.select(".main").append("g") | |
.attr("clip-path", "url(#clip)") | |
.attr("class", "mainClip"); | |
display(); | |
// Brush update function | |
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;}); | |
var visLanes = visItems.map(function(d) { return d.lane }).filter(function(d, index, its) { return its.indexOf(d) === index}); | |
var visLaneLabels = visLanes.map(function(d) { return laneLabels[d]; }); | |
mini.select(".brush") | |
.call(brush.extent([minExtent, maxExtent])); | |
xMainScale.domain([minExtent, maxExtent]); | |
yMainScale.domain([0, visLanes.length]); | |
var brushDur = maxExtent - minExtent; | |
var minute = 60*1000; | |
if (brushDur < 30*minute) { | |
xMainAxis.ticks(d3.time.minutes, 5); | |
} else if (brushDur < 90*minute) { | |
xMainAxis.ticks(d3.time.minutes, 15); | |
} else if (brushDur < 150*minute) { | |
xMainAxis.ticks(d3.time.minutes, 30); | |
} else { | |
xMainAxis.ticks(d3.time.hours, 1) | |
} | |
d3.select('.main.axis.hour').call(xMainAxis); | |
// Update labels | |
var labels = d3.select(".mainLabels").selectAll(".laneText") | |
.data(visLaneLabels, function(d) { return d }); | |
labels | |
.transition() | |
.attr("y", function(d, i) {return yMainScale(i + .5);}); | |
// create new | |
labels | |
.enter() | |
.append("text") | |
.text(function(d) {return d;}) | |
.attr("x", 0) // This was -margin.right | |
.attr("y", function(d, i) {return yMainScale(i + .5);}) | |
.attr("dy", ".5ex") | |
.attr("text-anchor", "end") | |
.attr("class", "laneText"); | |
// Remove old | |
labels.exit().transition().style("opacity", 0).remove(); | |
// Update lane lines for number present | |
var mainLanes = d3.select(".main").selectAll(".laneLine") | |
.data(visLanes, function(d) { return d; }); | |
mainLanes | |
.transition() | |
.attr("class", "laneLine") | |
.attr("y1", function(d) {return yMainScale(visLanes.indexOf(d));}) | |
.attr("y2", function(d) {return yMainScale(visLanes.indexOf(d));}) | |
mainLanes | |
.enter().append("line") | |
.attr("y1", function(d) {return yMainScale(visLanes.indexOf(d));}) | |
.attr("y2", function(d) {return yMainScale(visLanes.indexOf(d));}) | |
.attr("class", "laneLine") | |
.attr("x1", margin.right) | |
.attr("x2", w()) | |
.attr("stroke", "lightgray"); | |
mainLanes.exit().remove(); | |
//update main item rects | |
// should be select this? | |
rects = d3.select(".main").select(".mainClip").selectAll("rect") | |
.data(visItems, function(d) { return d.id; }); | |
rects | |
.attr("x", function(d) {return xMainScale(d.start);}) | |
.attr("width", function(d) {return xMainScale(d.end) - xMainScale(d.start);}); | |
rects | |
.transition() | |
.attr("y", function(d) {return yMainScale(visLanes.indexOf(d.lane))+5;}) // why 5? | |
.attr("height", function(d) {return .8 * yMainScale(1);}); | |
rects.enter().append("rect") | |
.style("fill", function(d) { return colorFunc(laneLabels[d.lane]) }) | |
.attr("class", "semi") | |
.attr("x", function(d) {return xMainScale(d.start);}) | |
.attr("y", function(d) {return yMainScale(visLanes.indexOf(d.lane))+5;}) // why 5? | |
.attr("width", function(d) {return xMainScale(d.end) - xMainScale(d.start);}) | |
.attr("height", function(d) {return .8 * yMainScale(1);}); | |
rects.exit().remove(); | |
} | |
}); | |
} | |
chart.colors = function(_) { | |
if (!arguments.length) return colorFunc; | |
colorFunc = _; | |
return chart; | |
}; | |
chart.height = function(_) { | |
if (!arguments.length) return height; | |
height = _; | |
return chart; | |
}; | |
chart.width = function(_) { | |
if (!arguments.length) return width; | |
width = _; | |
return chart; | |
}; | |
return chart; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment