Skip to content

Instantly share code, notes, and snippets.

@herkulano
Last active March 24, 2017 19:08
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save herkulano/4f43dbf3473dc5503052 to your computer and use it in GitHub Desktop.
Save herkulano/4f43dbf3473dc5503052 to your computer and use it in GitHub Desktop.
2 Dimensional Histogram
license: apache-2
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js"></script>
<script src="https://unpkg.com/d3-hist2d@1.0.6/build/d3-hist2d.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="content">
<div id="tooltip"><div></div></div>
<div id="loader" class="loader"></div>
</div>
<script src="main.js"></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-67903380-3', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>
var t0, t1, t2, t3, t4, t5;
var body = document.querySelector('body');
var margin = {top: 20, right: 200, bottom: 30, left: 220};
var width = Math.floor(body.clientWidth - margin.left - margin.right);
var height = Math.floor(body.clientHeight - margin.top - margin.bottom);
t0 = performance.now();
var data = new Array(2000000);
var di = data.length;
var now = 0;
var then = 0;
var createData = function() {
if (di > 0) {
now = then = Date.now();
while (then - now < 14 && di-- > 0) {
data[di] = [di, Math.random() * 2 - 1];
}
then = Date.now();
requestAnimationFrame(createData);
} else {
createHist();
}
};
requestAnimationFrame(createData);
var x = d3.scale.linear()
.domain([0, data.length - 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([-1, 1])
.range([height, 0]);
var t1;
var h;
var hist2d;
var createHist = function() {
t1 = performance.now();
// create Hist2D
hist2d = d3.hist2d()
.bins(40)
.indices([0, 1])
.domain([x.domain(), y.domain()])
(data, draw);
};
var colors = [
'rgb(215,25,28)',
'rgb(253,174,97)',
'rgb(255,255,191)',
'rgb(171,217,233)',
'rgb(44,123,182)'
];
var colorDomain = new Array(colors.length);
for (var i = colorDomain.length - 1; i >= 0; i--) {
colorDomain[i] = height / (colorDomain.length - 1) * i;
}
var color = d3.scale.linear()
.domain(colorDomain)
.range(colors);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.tickSize(6, -height);
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.tickSize(6, -width);
var svg = d3.select('.content').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
var histW;
var histH;
var circle;
var draw = function(hist) {
t2 = performance.now();
d3.select('#loader').remove();
hist2d.size([width, height]);
histW = hist2d.size()[0];
histH = hist2d.size()[1];
var histExtent = d3.extent(hist, function(d) {
return d.length;
});
var radius = Math.min(histW, histH) / 2;
var rScale = d3.scale.linear()
.domain(histExtent)
.range([radius * 0.4, radius * 1.6]);
var g = svg.append('g')
.attr('transform', 'translate(' +
(this.histW * 0.5) + ',' +
(-this.histH * 0.5) + ')');
circle = g.selectAll('circle')
.data(hist)
.enter().append('circle')
.attr('r', function(d) { return rScale(d.length); })
.attr('cx', function(d) { return histW * d.x; })
.attr('cy', function(d) { return height - (histH * d.y); })
.attr('fill', function(d) { return color(histH * d.y); });
requestAnimationFrame(setupEventListeners);
t3 = performance.now();
};
var setupEventListeners = function() {
t4 = performance.now();
var ttw = 110;
var tth = 100;
var ttt = 0;
var ttl = 0;
var tt = d3.select('#tooltip')
.style('width', ttw + 'px')
.style('height', tth + 'px');
var ttc = d3.select('#tooltip > div');
circle
.on('click', function(d) {
console.log(d, d.x, d.y, d.length);
})
.on('mouseover', function(d) {
var fn = function(dd) {
return dd[1];
};
var median = d3.median(d, fn);
var extent = d3.extent(d, fn);
var html = '<p>Count: ' + d.length + '</p>';
html += '<p>Med: ' + roundValue(median) + '</p>';
html += '<p>Min: ' + roundValue(extent[0]) + '</p>';
html += '<p>Max: ' + roundValue(extent[1]) + '</p>';
ttc.html(html);
tt.transition()
.duration(100)
.style('opacity', 1);
ttt = height - histH * d.y + margin.top - tth - histH;
ttl = histW * d.x + margin.left + histW / 2 - ttw / 2;
if (ttt - margin.top > 0) {
ttt = ttt - 8;
tt.classed({
'bottom': true,
'top': false
})
.style('left', ttl + 'px')
.style('top', ttt + 'px');
} else {
ttt = ttt + tth + histH + 8;
tt.classed({
'bottom': false,
'top': true
})
.style('left', ttl + 'px')
.style('top', ttt + 'px');
}
})
.on('mouseout', function() {
tt.transition()
.duration(300)
.style('opacity', 0);
});
t5 = performance.now();
console.log('data(): ' + Math.trunc(t1 - t0) + 'ms');
console.log('hist(): ' + Math.trunc(t2 - t1) + 'ms');
console.log('draw(): ' + Math.trunc(t3 - t2) + 'ms');
console.log('listeners(): ' + Math.trunc(t5 - t4) + 'ms');
console.log('TOTAL: ' + Math.trunc(t5 - t0) + 'ms');
};
var roundValue = function(value) {
return Number(Math.round(value + 'e4') + 'e-4');
};
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
font-family: sans-serif;
font-size: 14px;
background-color: #eee;
}
.content {
position: relative;
width: 100%;
height: 100%;
}
text {
font-size: 12px;
color: #888;
}
.axis path,
.axis line {
fill: none;
stroke: #888;
shape-rendering: crispEdges;
}
circle {
stroke: #222;
stroke-width: 1;
stroke-opacity: 0.15;
}
#tooltip {
box-sizing: border-box;
position: absolute;
pointer-events: none;
display: flex;
align-items: center;
background-color: #333;
color: white;
border-radius: 5px;
opacity: 0;
}
#tooltip p {
margin: 4px;
}
#tooltip > div {
margin: 8px;
}
#tooltip:after {
position: absolute;
pointer-events: none;
content: " ";
left: 50%;
border: solid transparent;
height: 0;
width: 0;
border-color: transparent;
border-width: 8px;
margin-left: -8px;
}
#tooltip.bottom:after {
top: 100%;
border-top-color: #333;
}
#tooltip.top:after {
bottom: 100%;
border-bottom-color: #333;
}
/**
* CSS Loader Credits:
* Luke Haas (@lukehaas)
* http://projects.lukehaas.me/css-loaders/
*/
.loader,
.loader:before,
.loader:after {
background: steelblue;
-webkit-animation: load1 1s infinite ease-in-out;
animation: load1 1s infinite ease-in-out;
width: 1em;
height: 4em;
}
.loader:before,
.loader:after {
position: absolute;
top: 0;
content: '';
}
.loader:before {
left: -1.5em;
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.loader {
text-indent: -9999em;
margin: -2em auto 0;
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
.loader:after {
left: 1.5em;
}
@-webkit-keyframes load1 {
0%,
80%,
100% {
box-shadow: 0 0 steelblue;
height: 4em;
}
40% {
box-shadow: 0 -2em steelblue;
height: 5em;
}
}
@keyframes load1 {
0%,
80%,
100% {
box-shadow: 0 0 steelblue;
height: 4em;
}
40% {
box-shadow: 0 -2em steelblue;
height: 5em;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment