|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<title>hex</title> |
|
<meta charset="utf-8"> |
|
|
|
<script src="//d3js.org/d3.v4.js"></script> |
|
<script src="//d3js.org/topojson.v1.min.js"></script> |
|
<script src="//d3js.org/d3-color.v1.min.js"></script> |
|
<script src="//d3js.org/d3-interpolate.v1.min.js"></script> |
|
<script src="//d3js.org/d3-scale-chromatic.v1.min.js"></script> |
|
<script src="//d3js.org/d3-hexbin.v0.2.min.js"></script> |
|
|
|
<style type="text/css"> |
|
|
|
body { |
|
font-family: Avenir, sans-serif; |
|
} |
|
|
|
</style> |
|
|
|
</head> |
|
|
|
<body> |
|
|
|
|
|
<div id="container"></div> |
|
|
|
<script> |
|
|
|
|
|
/* Globals and SVG */ |
|
/* --------------- */ |
|
|
|
var projection, |
|
hexRadius, |
|
hexbin, |
|
colourScale; |
|
|
|
|
|
var margin = { top: 30, right: 30, bottom: 30, left: 30 }, |
|
width = 850 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var 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 + ')'); |
|
|
|
|
|
|
|
/* Functions */ |
|
/* --------- */ |
|
|
|
function drawHexmap(points) { |
|
|
|
var hexes = svg.append('g').attr('id', 'hexes') |
|
.selectAll('.hex') |
|
.data(points) |
|
.enter().append('path') |
|
.attr('class', 'hexes') |
|
.attr('transform', function(d) { return 'translate(' + d.x + ', ' + d.y + ')'; }) |
|
.attr('d', hexbin.hexagon()) |
|
.style('fill', '#ddd') |
|
.style('fill', function(d) { return colourScale(d.datapoints); }) |
|
.style('stroke', '#999') |
|
.style('stroke-width', 1); |
|
|
|
} // drawHexmap() |
|
|
|
|
|
function getHexpointsWithCount(data) { |
|
|
|
var maxCount = 0; // for colourScale |
|
|
|
data.forEach(function(el) { |
|
|
|
var count = 0; |
|
|
|
el.forEach(function(elt) { |
|
if (elt.datapoint === 1) count++; |
|
}) |
|
|
|
el.datapoints = count; |
|
|
|
maxCount = Math.max(maxCount, count); // for colourScale |
|
|
|
}); |
|
|
|
// create colourScale as soon as maximum number of datapoints is determined |
|
|
|
colourScale = d3.scaleLinear().domain([0, maxCount]).range(['#fff', '#0085DB']).interpolate(d3.interpolateHcl); |
|
|
|
return data; |
|
|
|
} // getHexpointsWithCount() |
|
|
|
|
|
function getHexpoints(points) { |
|
|
|
hexbin = d3.hexbin() // note: global |
|
.radius(hexRadius) |
|
.x(function(d) { return d.x; }) |
|
.y(function(d) { return d.y; }); |
|
|
|
var hexPoints = hexbin(points); |
|
|
|
return hexPoints; |
|
|
|
} // getHexpoints() |
|
|
|
|
|
function getDatapoints(data) { |
|
|
|
var dataPoints = [] |
|
data.forEach(function(el) { |
|
|
|
var obj = {}; |
|
obj.x = projection([+el.x, +el.y])[0]; |
|
obj.y = projection([+el.x, +el.y])[1]; |
|
obj.datapoint = 1; |
|
|
|
dataPoints.push(obj); |
|
|
|
}); |
|
|
|
return dataPoints; |
|
|
|
} // getDatapoints() |
|
|
|
|
|
function keepPointsInPolygon(points, polygon) { |
|
|
|
var pointsInPolygon = []; |
|
points.forEach(function(el) { |
|
|
|
var inPolygon = d3.polygonContains(polygon, [el.x, el.y]); |
|
if (inPolygon) pointsInPolygon.push(el); |
|
|
|
}); |
|
|
|
return pointsInPolygon; |
|
|
|
} // keepPointsInPolygon() |
|
|
|
|
|
function drawPointGrid(data) { |
|
|
|
svg.append('g').attr('id', 'circles') |
|
.selectAll('.dot') |
|
.data(data) |
|
.enter().append('circle') |
|
.attr('cx', function(d) { return d.x; }) |
|
.attr('cy', function(d) { return d.y; }) |
|
.attr('r', 1) |
|
.attr('fill', 'tomato'); |
|
|
|
} // drawPointGrid() |
|
|
|
|
|
function getPolygonPoints(data) { |
|
|
|
var features = data.features[0].geometry.coordinates[7][0]; |
|
|
|
var polygonPoints = [] |
|
features.forEach(function(el) { |
|
polygonPoints.push(projection(el)); |
|
}); |
|
|
|
return polygonPoints; |
|
|
|
} // getPolygonPoints() |
|
|
|
|
|
function createPointGrid(rowNum, colNum) { |
|
|
|
var rows = rowNum, |
|
columns = colNum; |
|
|
|
hexRadius = Math.min(width/((columns + 0.5) * Math.sqrt(3)), height/((rows + 1/3) * 1.5)); // note: global |
|
|
|
var points = [], |
|
index = -1; |
|
|
|
d3.range(rows).forEach(function(row) { |
|
d3.range(columns).forEach(function(col) { |
|
obj = {}; |
|
// obj.x = hexRadius * col * Math.sqrt(3); |
|
obj.x = hexRadius * col * 1.7; |
|
obj.y = hexRadius * row * 1.5; |
|
obj.datapoint = 0; |
|
points.push(obj) |
|
}); |
|
}); |
|
|
|
return points; |
|
|
|
} // createPointGrid() |
|
|
|
|
|
function drawGeo(data) { |
|
|
|
projection = d3.geoAlbersUsa() // note: global |
|
.scale(1000).translate([width/2, height/2]); |
|
|
|
var geoPath = d3.geoPath() |
|
.projection(projection); |
|
|
|
svg |
|
.append('path') |
|
.datum(data) |
|
.attr('d', geoPath) |
|
.attr('fill', 'none') |
|
|
|
} // drawGeo() |
|
|
|
|
|
function prepData(topo) { |
|
|
|
var geo = topojson.feature(topo, topo.objects.us); |
|
|
|
return geo; |
|
|
|
} // prepData() |
|
|
|
|
|
|
|
/* Load and algorithm */ |
|
/* ------------------ */ |
|
|
|
// Run algoritm |
|
function ready(error, us, waltest) { |
|
if (error) throw error; |
|
|
|
var us = prepData(us); |
|
|
|
drawGeo(us); |
|
|
|
var points = createPointGrid(90, 140); |
|
|
|
var polygonPoints = getPolygonPoints(us); |
|
|
|
var usPoints = keepPointsInPolygon(points, polygonPoints); |
|
|
|
var dataPoints = getDatapoints(waltest) |
|
|
|
var mergedPoints = usPoints.concat(dataPoints) |
|
|
|
var hexPoints = getHexpoints(mergedPoints); |
|
|
|
var hexPointsWithCount = getHexpointsWithCount(hexPoints); |
|
|
|
// drawPointGrid(mergedPoints); |
|
|
|
drawHexmap(hexPoints); |
|
|
|
} |
|
|
|
// Load data sources |
|
d3.queue() |
|
.defer(d3.json, 'us.json') |
|
.defer(d3.tsv, 'waltest.tsv') |
|
.await(ready); |
|
|
|
|
|
</script> |
|
|
|
</html> |