Skip to content

Instantly share code, notes, and snippets.

@pcarleton
Created May 20, 2014 23:29
Show Gist options
  • Save pcarleton/52cb4cecbd9a26353c0d to your computer and use it in GitHub Desktop.
Save pcarleton/52cb4cecbd9a26353c0d to your computer and use it in GitHub Desktop.
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