Last active
September 22, 2017 23:39
-
-
Save mmazanec22/9265eff78c47c270e068a93127dc644e to your computer and use it in GitHub Desktop.
Hexbins Relative In Size To Entire Dataset of Objects
This file contains 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
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); | |
} |
This file contains 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
<!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> |
This file contains 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
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