Created
March 26, 2015 20:14
-
-
Save DForshner/0c4084983eeaba0a485d to your computer and use it in GitHub Desktop.
D3.js Linear Heat Map w/ Clickable Tooltips
This file contains hidden or 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 _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