Skip to content

Instantly share code, notes, and snippets.

@jbalogh
Last active December 18, 2015 17:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbalogh/5820481 to your computer and use it in GitHub Desktop.
Save jbalogh/5820481 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
circle {
fill: steelblue;
}
input {
vertical-align: middle;
}
#toggles {
display: none;
}
</style>
<body>
<div id="toggles">
<form oninput="oa.value=width.valueAsNumber; ob.value=tolerance.valueAsNumber">
<label>Box width <input type="range" name="width" min="2" max="30" value="5"></label>
<output name="oa" for="width"></output>
<label>Hit tolerance <input type="range" name="tolerance" min="1" value="16"></label>
<output name="ob" for="tolerance"></output>
<select name="projection"></select>
</form>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script>
"use strict";
var width = 800,
height = 600;
/*
var visibleCanvas = d3.select('body').append('canvas')
.attr('width', width)
.attr('height', height)
.node().getContext('2d');
*/
var ghostCanvas = d3.select('body').append('canvas')
.attr('width', width)
.attr('height', height)
.style('display', 'none')
.node().getContext('2d');
var projection = d3.geo.equirectangular()
.scale((width + 1) / 2 / Math.PI)
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection)
.context(ghostCanvas);
var world;
var mapPixels;
var hitWidth = 3;
var hitTolerance = 8;
var projection = 'kavrayskiy7';
var widthInput = document.querySelector('[name=width]');
var toleranceInput = document.querySelector('[name=tolerance]');
var projectionInput = document.querySelector('[name=projection]');
widthInput.addEventListener('input', render);
toleranceInput.addEventListener('input', render);
projectionInput.addEventListener('change', project);
d3.json("world-50m.json", function(error, json) {
d3.select('#toggles').style('display', 'block').select('form').node().oninput();
world = json;
project();
});
function project() {
projection = projectionInput.value;
console.log('projecting', projection);
console.time('projection');
var projection = d3.geo[projection]()
.scale((width + 1) / 2 / Math.PI)
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection)
.context(ghostCanvas);
console.timeEnd('projection');
console.time('ghost canvas');
var c = ghostCanvas;
c.clearRect(0, 0, width, height);
c.fillStyle = 'rgb(100, 100, 100)', c.beginPath(), path(topojson.feature(world, world.objects.land)), c.fill();
mapPixels = c.getImageData(0, 0, width, height).data;
console.timeEnd('ghost canvas');
render();
}
var skip = 'area bounds centroid circle distance greatArc interrupt interpolate length path projection rotation stream'.split(' ');
var options = Object.keys(d3.geo).filter(function(d){ return skip.indexOf(d) == -1; });
options.sort();
d3.select('[name=projection]').selectAll('option')
.data(options).enter()
.append('option')
.attr('value', function(d){ return d; })
.text(function(d){ return d; });
d3.select('option[value=kavrayskiy7]').attr('selected', 'selected');
function render() {
console.time('render');
hitWidth = widthInput.valueAsNumber;
hitTolerance = toleranceInput.valueAsNumber;
toleranceInput.max = hitWidth * hitWidth;
//drawCanvas(hitTest());
drawSVG(hitTest());
console.timeEnd('render');
}
function hitTest() {
// Use hit-testing on the hidden canvas.
console.time('hit test');
var half = Math.floor(hitWidth / 2);
var hits = [];
for (var y = 0; y < height; y += hitWidth) {
for (var x = 0; x < width; x += hitWidth) {
if (numHits(rgbsquare(mapPixels, x, y, hitWidth)) > hitTolerance) {
hits.push(x + half);
hits.push(y + half);
}
}
}
console.timeEnd('hit test');
return hits;
}
function drawCanvas(hits) {
console.time('fill canvas');
// Draw the pixelated map.
var c = visibleCanvas;
c.clearRect(0, 0, width, height);
c.fillStyle = 'steelblue';
for (var i = 0, n = hits.length; i < n; i += 2) {
c.beginPath()
c.arc(hits[i], hits[i+1], hitWidth / 2, 0, 2 * Math.PI);
c.fill();
}
console.timeEnd('fill canvas');
}
var SCALE = 1.25;
function drawSVG(hits) {
console.time('fill svg')
var pairs = [];
for (var i = 0; i < hits.length; i+= 2) pairs.push([hits[i], hits[i+1]])
d3.select('svg').remove();
var svg = d3.select('body').append('svg')
.attr('width', width * SCALE)
.attr('height', height * SCALE);
svg.selectAll('circle')
.data(pairs).enter()
.append('circle')
.attr('cx', function(d){ return d[0] * SCALE; })
.attr('cy', function(d){ return d[1] * SCALE; })
.attr('r', hitWidth / 2);
console.timeEnd('fill svg')
}
function numHits(square) {
return sum(square) / 300;
}
function rgbsquare(arr, startX, startY, length) {
var rv = [];
for (var y = startY; y < startY + length; y++) {
for (var x = startX; x < startX + length; x++) {
var i = 4 * (y * width + x);
for (var z = i + 3; i < z; i++) rv.push(arr[i])
}
}
return rv;
}
function sum(arr) {
for (var i = 0, sum = 0, n = arr.length; i < n; sum += arr[i++]);
return sum;
}
function avg(arr) {
return sum(arr) / arr.length
}
</script>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment