Last active
January 13, 2016 17:09
-
-
Save gustavderdrache/f2b29b63d121c5826a9e to your computer and use it in GitHub Desktop.
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 lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>FNS map</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.7/jquery.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script> | |
<style> | |
.fns-map__state { | |
pointer-events: visibleFill; | |
fill: white; | |
stroke: none; | |
transition: fill 250ms; | |
} | |
.fns-map__boundary { | |
pointer-events: none; | |
stroke: black; | |
fill: none; | |
} | |
.fns-map__state--outlying-area { | |
stroke: black; | |
} | |
.fns-map__state:hover { | |
fill: red; | |
} | |
#fns-map svg { | |
width: 960px; | |
height: 600px; | |
border: 1px solid black; | |
overflow: hidden; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="fns-map"></div> | |
<script src="script.js"></script> | |
<script> | |
fnsMap('#fns-map'); | |
</script> | |
</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
(function ($, d3, topojson) { | |
window.fnsMap = fnsMap; | |
// function fnsMap(target: string): void | |
// Note that "target" should be a <div> or similar block container; this function will create an SVG element that it "owns". | |
function fnsMap(target) { | |
/* | |
* Data path constants. | |
* TOPOJSON_PATH: the directory holding the two TopoJSON files | |
* OUTLYING_AREAS: where the outlying-areas.json file lives | |
* US_STATES: where the us-states.json file lives | |
* DATA_PATH: the path to the AJAX endpoint | |
*/ | |
var TOPOJSON_PATH = '', | |
OUTLYING_AREAS = TOPOJSON_PATH + 'outlying-areas.json', | |
US_STATES = TOPOJSON_PATH + 'us-states.json', | |
DATA_PATH = 'states.json'; | |
// Used to set up the view box. | |
// NOTE: this isn't the *actual* height/width of the map; that can be set using CSS. | |
var width = 960, | |
height = 500; | |
var path = d3.geo.path() | |
.projection(albersFns()); | |
var svg = d3.select(target) | |
.append('svg') | |
.classed('fns-map', true) | |
.attr({ | |
viewBox: '0 0 ' + width + ' ' + height, | |
}); | |
$.when($.getJSON(OUTLYING_AREAS), $.getJSON(US_STATES)) | |
.pipe(mapReady); | |
// called when the two map data files are loaded | |
// we're adding an extra parameter (the svg) so we can draw on it | |
function mapReady(outlyingAreas, usStates) { | |
// jQuery gives us all three arguments as an array, so filter some out | |
outlyingAreas = outlyingAreas[0]; | |
usStates = usStates[0]; | |
// convert from TopoJSON to GeoJSON | |
var outlying = topojson.feature(outlyingAreas, outlyingAreas.objects['outlying-areas']), | |
states = topojson.feature(usStates, usStates.objects['us-states']), | |
boundary = topojson.mesh(usStates, usStates.objects['us-states']); | |
// given a feature, return a unique identifier | |
function id(feature) { | |
return 'fns-map-' + feature.id; | |
} | |
// draw our objects | |
// we're surrounding our <path> elements with SVG <a> tags, which makes them clickable links. | |
// in this pass, the links are just "#", but we'll be overwriting them once we have the AJAX callback complete | |
svg.selectAll('a.fns-map__state--continental') | |
.data(states.features) | |
.enter() | |
.append('svg:a') | |
.attr({ | |
id: id, | |
class: 'fns-map__state fns-map__state--continental', | |
'xlink:href': '#', | |
}) | |
.append('svg:path') | |
.attr('d', path); | |
svg.selectAll('a.fns-map__state--outlying-area') | |
.data(outlying.features) | |
.enter() | |
.append('svg:a') | |
.attr({ | |
id: id, | |
class: 'fns-map__state fns-map__state--outlying-area', | |
'xlink:href': '#', | |
}) | |
.append('svg:path') | |
.attr('d', path); | |
// this element should be considered inert. | |
svg.append('svg:path') | |
.datum(boundary) | |
.attr({ | |
d: path, | |
class: 'fns-map__boundary', | |
}); | |
console.log(outlyingAreas, usStates); | |
} | |
} | |
/* | |
* NOTE: Below this line, there be dragons. | |
* | |
* Well, no, but there is a kinda gnarly custom map projection coming up. | |
*/ | |
// hacky map projection | |
// "borrows" heavily from https://gist.github.com/mbostock/5629120 | |
function albersFns() { | |
var eps = 1e-6; | |
var lower48 = d3.geo.albers(); | |
// gua | |
var alaska = d3.geo.conicEqualArea() | |
.rotate([154, 0]) | |
.center([-2, 58.5]) | |
.parallels([55, 65]); | |
// ESRI:102007 | |
var hawaii = d3.geo.conicEqualArea() | |
.rotate([157, 0]) | |
.center([-3, 19.9]) | |
.parallels([8, 18]); | |
// not standard but who cares | |
var puertoRico = d3.geo.conicEqualArea() | |
.rotate([66, 0]) | |
.center([0, 18]) | |
.parallels([8, 18]); | |
// EPSG:3993 | |
var guam = d3.geo.conicEqualArea() | |
.rotate([0, 0]) | |
// hat tip http://www.travelmath.com/country/Guam | |
.center([13.4667, 144.7831]) | |
.parallels([13.2, 13.7]); | |
var point, | |
pointStream = { | |
point: function(x, y) { | |
point = [x, y]; | |
} | |
}, | |
lower48Point, | |
alaskaPoint, | |
hawaiiPoint, | |
puertoRicoPoint, | |
guamPoint; | |
function albersFns(coords) { | |
var x = coords[0], | |
y = coords[1]; | |
// figure out which projection "owns" the (x,y) pair we were given | |
// if they do, they'll set point to a non-null value | |
point = null; | |
(lower48Point(x, y), point) | |
|| (alaskaPoint(x, y), point) | |
|| (hawaiiPoint(x, y), point) | |
|| (puertoRicoPoint(x, y), point) | |
|| (guamPoint(x, y), point); | |
// return what we found (if anything) | |
return point; | |
}; | |
// given coordinates, invert them | |
albersFns.invert = function (coords) { | |
var k = lower48.scale(), | |
t = lower48.translate(), | |
x = (coords[0] - t[0])/k, | |
y = (coords[1] - t[1])/k, | |
proj; | |
// magic numbers ahoy | |
// certain regions are "owned" by various projections, hence the conditional blocks | |
if (y >= 0.120 && y < 0.234 && x >= -0.425 && x < -0.214) { | |
proj = alaska; | |
} else if (y > 0.166 && y < 0.234 && x >= -0.214 && x < -0.115) { | |
proj = hawaii; | |
} else if (y >= 0.204 && y < 0.234 && x >= 0.320 && x < 0.380) { | |
proj = puertoRico; | |
} else { | |
proj = lower48; | |
} | |
return proj.invert(coords); | |
}; | |
// some sort of multi-polygon something-or-other | |
albersFns.stream = function(stream) { | |
var lower48Stream = lower48.stream(stream), | |
alaskaStream = alaska.stream(stream), | |
hawaiiStream = hawaii.stream(stream), | |
puertoRicoStream = puertoRico.stream(stream), | |
guamStream = guam.stream(stream); | |
return { | |
point: function(x, y) { | |
lower48Stream.point(x, y); | |
alaskaStream.point(x, y); | |
hawaiiStream.point(x, y); | |
puertoRicoStream.point(x, y); | |
guamStream.point(x, y); | |
}, | |
sphere: function() { | |
lower48Stream.sphere(); | |
alaskaStream.sphere(); | |
hawaiiStream.sphere(); | |
puertoRicoStream.sphere(); | |
guamStream.sphere(); | |
}, | |
lineStart: function() { | |
lower48Stream.lineStart(); | |
alaskaStream.lineStart(); | |
hawaiiStream.lineStart(); | |
puertoRicoStream.lineStart(); | |
guamStream.lineStart(); | |
}, | |
lineEnd: function() { | |
lower48Stream.lineEnd(); | |
alaskaStream.lineEnd(); | |
hawaiiStream.lineEnd(); | |
puertoRicoStream.lineEnd(); | |
guamStream.lineEnd(); | |
}, | |
polygonStart: function() { | |
lower48Stream.polygonStart(); | |
alaskaStream.polygonStart(); | |
hawaiiStream.polygonStart(); | |
puertoRicoStream.polygonStart(); | |
guamStream.polygonStart(); | |
}, | |
polygonEnd: function() { | |
lower48Stream.polygonEnd(); | |
alaskaStream.polygonEnd(); | |
hawaiiStream.polygonEnd(); | |
puertoRicoStream.polygonEnd(); | |
guamStream.polygonEnd(); | |
} | |
}; | |
}; | |
albersFns.precision = function(precision) { | |
if (!arguments.length) { | |
return lower48.precision(); | |
} | |
lower48.precision(precision); | |
alaska.precision(precision); | |
hawaii.precision(precision); | |
puertoRico.precision(precision); | |
guam.precision(precision); | |
return albersFns; | |
}; | |
albersFns.scale = function (scale) { | |
if (!arguments.length) { | |
return lower48.scale(); | |
} | |
lower48.scale(scale); | |
alaska.scale(scale * 0.35); // "alaska is a very big state", said captain obvious | |
hawaii.scale(scale); | |
puertoRico.scale(scale); | |
guam.scale(scale); | |
// recompute translation for reasons that escape me | |
return albersFns.translate(lower48.translate()); | |
} | |
albersFns.translate = function (translate) { | |
if (!arguments.length) { | |
return lower48.translate(); | |
} | |
var k = lower48.scale(), | |
x = +translate[0], | |
y = +translate[1]; | |
// magic numbers ahoy | |
// clipExtent is the magic that makes each of these areas mutually exclusive: going out of bounds makes `point' null. | |
lower48Point = lower48 | |
.translate(translate) | |
.clipExtent([ | |
[x - 0.455 * k, y - 0.238 * k], | |
[x + 0.455 * k, y + 0.238 * k], | |
]) | |
.stream(pointStream) | |
.point; | |
alaskaPoint = alaska | |
.translate([x - 0.307 * k, y + 0.201 * k]) | |
.clipExtent([ | |
[x - 0.425 * k + eps, y + 0.120 * k + eps], | |
[x - 0.214 * k - eps, y + 0.234 * k - eps], | |
]) | |
.stream(pointStream) | |
.point; | |
hawaiiPoint = hawaii | |
.translate([x - 0.205 * k, y + 0.212 * k]) | |
.clipExtent([ | |
[x - 0.214 * k + eps, y + 0.166 * k + eps], | |
[x - 0.115 * k - eps, y + 0.234 * k - eps], | |
]) | |
.stream(pointStream) | |
.point; | |
puertoRicoPoint = puertoRico | |
.translate([x + 0.350 * k, y + 0.224 * k]) | |
.clipExtent([ | |
[x + 0.320 * k, y + 0.204 * k], | |
[x + 0.380 * k, y + 0.234 * k], | |
]) | |
.stream(pointStream) | |
.point; | |
guamPoint = guam | |
.translate([x + 0.5 * k, y + 0.25 * k]) | |
.clipExtent([ | |
[x + 0.53 * k, y + 0.23 * k], | |
[x + 0.58 * k, y * 0.27 * k], | |
]) | |
.stream(pointStream) | |
.point; | |
return albersFns; | |
}; | |
// ... and engage. | |
return albersFns.scale(1070); | |
} | |
})(jQuery, d3, topojson); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment