Skip to content

Instantly share code, notes, and snippets.

@larsvers
Last active September 12, 2018 10:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save larsvers/ec4f4c96941b0fa97869184ab9a9fb5b to your computer and use it in GitHub Desktop.
Save larsvers/ec4f4c96941b0fa97869184ab9a9fb5b to your computer and use it in GitHub Desktop.
Farmers Markets II - with d3-hexgrid
license: mit
height: 500
border: no
function ready(geo, userData) {
// Container SVG.
const margin = { top: 30, right: 30, bottom: 30, left: 30 },
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
const svg = d3
.select('#container')
.append('svg')
.attr('width', width + margin.left + margin.top)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left} ${margin.top})`);
// Projection and path.
const projection = d3.geoAlbers().fitSize([width, height], geo);
const geoPath = d3.geoPath().projection(projection);
// Prep user data.
userData.forEach(site => {
const coords = projection([+site.lng, +site.lat]);
site.x = coords[0];
site.y = coords[1];
});
// Hexgrid generator.
const hexgrid = d3.hexgrid()
.extent([width, height])
.geography(geo)
.pathGenerator(geoPath)
.projection(projection)
.hexRadius(5);
// Hexgrid instance.
const hex = hexgrid(userData);
// Create exponential colorScale.
const scaleExponent = 10;
const colourScale = d3.scaleSequential(t => {
var tNew = Math.pow(t, scaleExponent);
return d3.interpolateViridis(tNew);
})
.domain([...hex.grid.extentPointDensity].reverse());
// Draw the hexes.
svg
.append('g')
.selectAll('.hex')
.data(hex.grid.layout)
.enter()
.append('path')
.attr('class', 'hex')
.attr('d', hex.hexagon())
.attr('transform', d => `translate(${d.x} ${d.y})`)
.style('fill', d => (!d.pointDensity ? '#fff' : colourScale(d.pointDensity)))
.style('stroke', '#F7E76E')
.style('stroke-opacity', 0.5);
// Tooltip.
const formatNum = d3.format('.2');
const tip = d3.select('.tooltip');
d3.selectAll('.hex')
.on('mouseover', mouseover)
.on('mouseout', mouseout);
// Handler.
function mouseover(d) {
tip
.style('opacity', 1)
.style('top', `${d3.event.pageY - 20}px`)
.style('left', `${d3.event.pageX + 10}px`);
tip.html(`cover: ${formatNum(d.cover)}<br>
points: ${d.datapoints}<br>
points wt: ${formatNum(d.datapointsWt)}<br>
density: ${formatNum(d.pointDensity)}`);
}
function mouseout() {
tip.style('opacity', 0);
}
// Legend...
// Values.
const legendScale = 8 / hex.radius();
// Get legend data.
const equalRange = n => d3.range(n).map(d => d / (n - 1));
const densityDist = hex.grid.layout
.map(d => d.pointDensity)
.sort(d3.ascending)
.filter(d => d);
const splitRange = equalRange(11);
const indeces = splitRange.map(d => Math.floor(d * (densityDist.length - 1)));
const densityPick = indeces.map(d => densityDist[d]);
const legendData = densityPick.map(d => ({
density: d,
colour: colourScale(d)
}));
// Build legend.
const gLegend = svg
.append('g')
.attr('class', 'legend')
.attr('transform', `translate(0, ${height})`);
gLegend
.append('text')
.text(`Point density (scale exponent: ${scaleExponent})`)
.attr('fill', '#555')
.attr('font-family', 'sans-serif')
.attr('font-size', '0.55rem')
.attr('font-weight', 'bold')
.attr('dy', 19)
.attr('dx', -4);
const legend = gLegend
.selectAll('.legend__key')
.data(legendData)
.enter()
.append('g')
.attr('class', 'legend__key')
.attr('transform', (d, i) => `translate(${i * Math.sqrt(3) * hexgrid.hexRadius() * legendScale}, 0)`);
legend
.append('g')
.attr('transform', `scale(${legendScale})`)
.append('path')
.attr('d', hex.hexagon())
.style('fill', d => d.colour)
.style('stroke-width', 0.5)
.style('stroke', '#fff');
legend
.append('text')
.text(
(d, i, n) => (i == 0 || i == n.length - 1 ? formatNum(d.density) : '')
)
.attr('fill', '#555')
.attr('font-family', 'sans-serif')
.attr('font-size', '0.7rem')
.attr('font-weight', 'bold')
.attr('text-anchor', 'middle')
.attr('dy', -10);
}
// Data load.
const geoData = d3.json(
'https://raw.githubusercontent.com/larsvers/map-store/master/us_mainland_geo.json'
);
const points = d3.json(
'https://raw.githubusercontent.com/larsvers/data-store/master/farmers_markets_us.json'
);
Promise.all([geoData, points]).then(res => {
let [geoData, userData] = res;
ready(geoData, userData);
});
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Farmers Markets</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- d3-hexgrid script comes first. -->
<script src="//unpkg.com/d3-hexgrid"></script>
<script src="//unpkg.com/d3"></script>
<style type="text/css">
.tooltip {
position: absolute;
opacity: 0;
font-family: Nunito, sans-serif;
pointer-events: none;
background-color: #eee;
padding: 0.5em;
box-shadow: 1px 2px 4px #888;
}
</style>
</head>
<body>
<div id="container"></div>
<div class="tooltip"></div>
<script src="app.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment