Skip to content

Instantly share code, notes, and snippets.

@DForshner
Created March 26, 2015 20:14
Show Gist options
  • Select an option

  • Save DForshner/0c4084983eeaba0a485d to your computer and use it in GitHub Desktop.

Select an option

Save DForshner/0c4084983eeaba0a485d to your computer and use it in GitHub Desktop.
D3.js Linear Heat Map w/ Clickable Tooltips
var _numBuckets = 24;
var start = 1000;
var end = 5000;
var _width = 150;
var _height = 40;
var _margin = {
top: 1,
right: 1,
bottom: 1,
left: 1
};
var _borderWidth = 1;
var _timeSliceHeight = (_height - _margin.top - _margin.bottom - 2 * _borderWidth);
var _timeSliceWidth = (_width - _margin.left - _margin.right - 2 * _borderWidth) / (_numBuckets + 1);
var events = [{
EventId: "Event A",
Timestamp: 1000
}, {
EventId: "Event B",
Timestamp: 2000
}, {
EventId: "Event C",
Timestamp: 4000
}, {
EventId: "Event D",
Timestamp: 5000
}];
// Create an array of buckets each representing one section of the time range.
var buckets = [];
for (var i = 0; i < _numBuckets + 1; i++) {
buckets[i] = {
sequence: i,
events: [],
meta: ""
};
}
// Distribute events into their appropriate buckets.
var timePerBucket = (end - start) / _numBuckets;
console.assert(timePerBucket * _numBuckets + start === end);
console.assert(end - timePerBucket * _numBuckets === start);
events.forEach(function (e) {
if (e.Timestamp < start || e.Timestamp > end) {
throw Error("Event timestamp " + e.Timestamp.toString() + " out of range " + start.toString() + "-" + end.toString());
}
var bucketIndex = Math.floor((e.Timestamp - start) / timePerBucket);
console.assert(bucketIndex < buckets.length);
var bucket = buckets[bucketIndex];
bucket.events.push(e);
bucket.html =
'<table id="tiptable">' +
'<tr><td><a href= "' + e.sequence + '" target="_blank">' + "Event A" + '"</a></td></tr>' +
'<tr><td><a href= "' + e.sequence + '" target="_blank">' + "Event B" + '"</a></td></tr>' +
"</table>";
});
var xScale = d3.scale.linear()
.domain([0, (_numBuckets)])
.range(0, _width);
// Construct svg container inside of el element.
var svg = d3.select('svg').append("svg")
.attr("class", "svg-container")
.attr("width", _width + _margin.left + _margin.right)
.attr("height", _height + _margin.top + _margin.bottom);
// Translate top-left of outermost group to top-left margin corner.
var heatMap = svg.append("g")
.attr("class", "heat-map")
.attr("transform", "translate(" + _margin.left + "," + _margin.top + ")");
var border = heatMap.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", _height)
.attr("width", _width)
.style("stroke", "black")
.style("fill", "none")
.style("fill-opacity", 0)
.style("stroke-width", _borderWidth);
var timeSlicesContainer = heatMap.append("g")
.attr("class", "time-slices")
.attr("transform", "translate(" + (_margin.left + _borderWidth) + "," + (_margin.left + _borderWidth) + ")");
var timeSliceBackround = timeSlicesContainer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", _timeSliceHeight)
.attr("width", _timeSliceWidth * (_numBuckets + 1))
.style("fill", "cyan");
var timeSlices = timeSlicesContainer.selectAll("div")
.data(buckets)
.enter()
.append("rect");
// Define 'div' for tooltips
var toolTip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("text-align", "center")
.style("padding", "30px")
.style("background", "white")
.style("border", "0px")
.style("border-radius", "8px")
.style("opacity", 0);
var timeSliceAttributes = timeSlices.attr("x", function (s) {
return s.sequence * _timeSliceWidth;
})
.attr("y", 0)
.attr("height", _timeSliceHeight)
.attr("width", _timeSliceWidth)
.style("fill", function (s) {
return (s.events.length === 0) ? "cyan" : "red";
})
// This is quite the mess...
.on("mouseover", function (d) {
if (d.events.length === 0) {
return;
}
toolTip.transition()
.duration(200)
.style("opacity", .9);
toolTip.html(d.html)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY + _height) + "px");
}).on("mouseout", function (d) {
if (d.events.length === 0) {
return;
}
// User has moved off the heat-map and onto the tool-tip
toolTip.on("mouseover", function (t) {
toolTip.on("mouseleave", function (t) {
toolTip.transition().duration(500).style("opacity", 0);
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment