Skip to content

Instantly share code, notes, and snippets.

@mmazanec22
Last active September 22, 2017 23:39
Show Gist options
  • Save mmazanec22/9265eff78c47c270e068a93127dc644e to your computer and use it in GitHub Desktop.
Save mmazanec22/9265eff78c47c270e068a93127dc644e to your computer and use it in GitHub Desktop.
Hexbins Relative In Size To Entire Dataset of Objects
document.addEventListener('DOMContentLoaded', () => {
const parentDiv = d3.select('.parent-div');
const width = 960;
const height = 500;
const svg = parentDiv.append('svg')
.attr('width', width)
.attr('height', height);
const options = {
start: 'green',
middle: 'dodgerblue',
end: 'purple',
};
const keys = Object.keys(options);
const dataset = [];
for (let i = 0; i < 500; i++) {
dataset.push({
start: [getRandomInt(width * 0.1, width * 0.15), getRandomInt(height * 0.1, height * 0.9)],
middle: [getRandomInt(width * 0.15, width * 0.35), getRandomInt(height * 0.1, height * 0.9)],
end: [getRandomInt(width * 0.35, width * 0.9), getRandomInt(height * 0.1, height * 0.9)],
});
}
const minRadius = 5;
const maxRadius = 30;
const hexbin = hackedBin(keys)
.radius(maxRadius);
const hexData = hexbin(dataset);
const radius = d3.scale.linear()
.domain([0, hexData.length / 2])
.range([minRadius, maxRadius]);
const hexGroup = svg.append('g');
const tooltip = makeToolTip();
hexGroup.selectAll('path')
.data(hexData)
.enter().append('path')
.attr('transform', d => `translate(${d.x}, ${d.y})`)
.attr('d', (d) => {
const thisRadius = radius(d.length);
return hexbin.hexagon(thisRadius);
})
.attr('fill', d => options[d.key])
.attr('stroke', d => options[d.key])
.attr('fill-opacity', 0.5)
.attr('stroke-opacity', 1)
.on('mouseover', function(d) {
tooltip
.text(`${d.key}: ${d.length}`)
.style('font-weight', 'bolder')
.style('color', 'navy');
moveToolTip(tooltip);
d3.select(this).style('fill-opacity', 1);
})
.on('mouseout', function() {
d3.select(this).style('fill-opacity', 0.5);
tooltip.style('display', 'none');
});
});
function getRandomInt(min, max) {
return Math.random() * (max - min + 1) + min;
}
function makeToolTip() {
const svg = d3.select('svg');
const tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('z-index', '1');
tooltip.append('text')
.attr('text-anchor', 'middle');
return tooltip;
}
function moveToolTip(tooltip) {
const svg = d3.select('svg');
tooltip.style('display', 'unset');
const svgDimensions = svg.node().getBoundingClientRect();
const eventXRelToScroll = d3.event.pageX - window.scrollX;
const eventYRelToScroll = d3.event.pageY - window.scrollY;
let tipX = (eventXRelToScroll) + 15;
let tipY = (eventYRelToScroll) + 10;
const tooltipDimensions = tooltip.node().getBoundingClientRect();
tipX = (eventXRelToScroll + tooltipDimensions.width + 10 > svgDimensions.right) ?
tipX - tooltipDimensions.width - 15 : tipX;
tipY = (eventYRelToScroll + tooltipDimensions.height + 10 > svgDimensions.bottom) ?
tipY - tooltipDimensions.height - 10 : tipY;
tooltip
.transition()
.duration(10)
.style('top', `${tipY}px`)
.style('left', `${tipX}px`);
}
function hackedBin(keys) {
let self = this;
var d3_hexbinAngles = d3.range(0, 2 * Math.PI, Math.PI / 3),
d3_hexbinX = function(d) { return d[0]; },
d3_hexbinY = function(d) { return d[1]; };
var width = 1,
height = 1,
r,
x = d3_hexbinX,
y = d3_hexbinY,
dx,
dy;
function hexbin(points) {
// for each point, loop through and do this with both the source and dest (checking for existence first)
var binsById = {};
points.forEach(function(point, i) {
keys.forEach(function(key) {
const pointSvgCoords = point[key]
if (!pointSvgCoords) { return; }
var py = y.call(hexbin, pointSvgCoords, i) / dy, pj = Math.round(py),
px = x.call(hexbin, pointSvgCoords, i) / dx - (pj & 1 ? .5 : 0), pi = Math.round(px),
py1 = py - pj;
if (Math.abs(py1) * 3 > 1) {
var px1 = px - pi,
pi2 = pi + (px < pi ? -1 : 1) / 2,
pj2 = pj + (py < pj ? -1 : 1),
px2 = px - pi2,
py2 = py - pj2;
if (px1 * px1 + py1 * py1 > px2 * px2 + py2 * py2) pi = pi2 + (pj & 1 ? 1 : -1) / 2, pj = pj2;
}
var id = pi + "-" + pj + "-" + key;
var bin = binsById[id];
if (bin) {
bin.id = id;
bin.push(point)
} else {
bin = binsById[id] = [point];
bin.id = id;
bin.key = key
bin.i = pi;
bin.j = pj;
bin.x = (pi + (pj & 1 ? 1 / 2 : 0)) * dx;
bin.y = pj * dy;
}
})
});
return d3.values(binsById);
}
function hexagon(radius) {
var x0 = 0, y0 = 0;
return d3_hexbinAngles.map(function(angle) {
var x1 = Math.sin(angle) * radius,
y1 = -Math.cos(angle) * radius,
dx = x1 - x0,
dy = y1 - y0;
x0 = x1, y0 = y1;
return [dx, dy];
});
}
hexbin.x = function(_) {
if (!arguments.length) return x;
x = _;
return hexbin;
};
hexbin.y = function(_) {
if (!arguments.length) return y;
y = _;
return hexbin;
};
hexbin.hexagon = function(radius) {
if (arguments.length < 1) radius = r;
return "m" + hexagon(radius).join("l") + "z";
};
hexbin.centers = function() {
var centers = [];
for (var y = 0, odd = false, j = 0; y < height + r; y += dy, odd = !odd, ++j) {
for (var x = odd ? dx / 2 : 0, i = 0; x < width + dx / 2; x += dx, ++i) {
var center = [x, y];
center.i = i;
center.j = j;
centers.push(center);
}
}
return centers;
};
hexbin.mesh = function() {
var fragment = hexagon(r).slice(0, 4).join("l");
return hexbin.centers().map(function(p) { return "M" + p + "m" + fragment; }).join("");
};
hexbin.size = function(_) {
if (!arguments.length) return [width, height];
width = +_[0], height = +_[1];
return hexbin;
};
hexbin.radius = function(_) {
if (!arguments.length) return r;
r = +_;
dx = r * 2 * Math.sin(Math.PI / 3);
dy = r * 1.5;
return hexbin;
};
return hexbin.radius(1);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src='./hackedBin.js'></script>
<link rel="stylesheet" type="text/css" href="./styles.css">
</head>
<body>
<div class='parent-div'></div>
</body>
</html>
html, body {
height: 100%;
width: 100%;
overflow: hidden;
}
.parent-div {
height: 100%;
width: 100%;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment