Skip to content

Instantly share code, notes, and snippets.

@gustavderdrache
Last active January 13, 2016 17:09
Show Gist options
  • Save gustavderdrache/f2b29b63d121c5826a9e to your computer and use it in GitHub Desktop.
Save gustavderdrache/f2b29b63d121c5826a9e to your computer and use it in GitHub Desktop.
<!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>
Display the source blob
Display the rendered blob
Raw
{"type":"Topology","transform":{"scale":[0.002066775542324029,0.0011701819154256406],"translate":[-67.93706054687496,13.257519531249997]},"arcs":[[[102904,2],[-21,-2],[-18,29],[-6,19],[0,98],[68,84],[23,82],[18,-7],[17,-13],[15,-25],[-78,-136],[-18,-129]],[[31,4104],[-4,-1],[-5,1],[-2,2],[-3,8],[-14,13],[-3,12],[3,12],[6,5],[28,2],[3,-2],[5,-8],[0,-6],[-2,-6],[-5,-15],[-2,-4],[-2,-4],[-1,-7],[-2,-2]],[[1215,4143],[-38,-5],[-24,7],[-9,25],[46,24],[54,-4],[31,-14],[3,-9],[-63,-24]],[[875,4433],[15,-17],[14,3],[-12,35],[11,0],[93,-22],[60,-36],[61,-17],[4,-119],[-48,-48],[-30,-49],[-26,-61],[-66,-71],[-79,-21],[-53,-2],[-20,2],[-19,12],[-40,-11],[-50,31],[-42,-8],[-84,7],[-32,-27],[-30,-6],[-30,5],[-25,12],[-62,-1],[-27,24],[11,135],[1,61],[-15,51],[-17,32],[-12,37],[24,25],[20,36],[7,54],[22,14],[26,6],[119,-25],[302,-15],[17,-4],[12,-22]],[[1534,3877],[41,-38],[49,0],[-51,-37],[-98,-4],[2,60],[57,19]],[[1586,4356],[-32,-23],[-22,3],[-8,8],[17,27],[45,-15]],[[1496,4335],[-36,-8],[-50,40],[39,15],[26,-9],[21,-38]]],"objects":{"outlying-areas":{"type":"GeometryCollection","geometries":[{"arcs":[[0]],"type":"Polygon","id":"GU"},{"arcs":[[[1]],[[2]],[[3]]],"type":"MultiPolygon","id":"PR"},{"arcs":[[[4]],[[5]],[[6]]],"type":"MultiPolygon","id":"VI"}]}}}
(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);
Display the source blob
Display the rendered blob
Raw
Loading
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