Uses a small inset map to provide context for the larger, zoomed map. The same bounding coordinates for the raster image are used to create the reference box in the inset map.
Last active
September 30, 2019 23:14
-
-
Save ThomasThoren/93745cc190a6b5ef02bd to your computer and use it in GitHub Desktop.
Inset map
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> | |
<head> | |
<title>Louisiana drop shadow</title> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
padding: 0; | |
margin: 0; | |
} | |
.bold { | |
font-weight: bold; | |
} | |
.inset-fill { | |
fill: none; | |
stroke: #777; | |
stroke-opacity: 0.5; | |
stroke-width: 0.5px; | |
} | |
.land { | |
fill: #EFEFEF; | |
} | |
.image { | |
opacity: 0.8; | |
} | |
.boundary { | |
fill: none; | |
stroke: #FFF; | |
stroke-width: 0.5px; | |
} | |
.main-city-label { | |
font-weight: 400; | |
text-anchor: middle; | |
opacity: 0.7; | |
fill: white; | |
font-family: arial, helvetica, sans-serif; | |
font-size: 13px; | |
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.3), | |
1px -1px 0 rgba(0, 0, 0, 0.3), | |
-1px 1px 0 rgba(0, 0, 0, 0.3), | |
-1px -1px 0 rgba(0, 0, 0, 0.3); | |
} | |
.lake-label { | |
font-weight: 300; | |
text-anchor: middle; | |
opacity: 0.5; | |
fill: white; | |
font-family: arial, helvetica, sans-serif; | |
font-size: 14px; | |
letter-spacing: 0.3em; | |
text-shadow: 1px 1px 0 gray, | |
1px -1px 0 gray, | |
-1px 1px 0 gray, | |
-1px -1px 0 gray; | |
} | |
.inset-label { | |
fill: black; | |
font-size: 11px; | |
font-family: arial, helvetica, sans-serif; | |
} | |
.inset-marker { | |
fill: none; | |
stroke: #B43018; | |
stroke-width: 2px; | |
} | |
.breach-text { | |
font-size: 15px; | |
line-height: 18px; | |
font-weight: 500; | |
fill: white; | |
font-family: arial, helvetica, sans-serif; | |
margin: 0; | |
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2), | |
1px -1px 0 rgba(0, 0, 0, 0.2), | |
-1px 1px 0 rgba(0, 0, 0, 0.2), | |
-1px -1px 0 rgba(0, 0, 0, 0.2); | |
} | |
.breach-line { | |
stroke: white; | |
stroke-width: 1; | |
opacity: 0.5; | |
fill: none; | |
} | |
.breach-marker { | |
fill: none; | |
stroke: white; | |
stroke-width: 2; | |
} | |
.inset-state-label { | |
fill: #000; | |
fill-opacity: 0.6; | |
font-family: arial, helvetica, sans-serif; | |
font-size: 11px; | |
font-weight: 600; | |
text-anchor: middle; | |
} | |
.distance-scale { | |
font-size: 12px; | |
line-height: 12px; | |
position: absolute; | |
font-weight: 300; | |
font-family: arial, helvetica, sans-serif; | |
fill: white; | |
} | |
.distance-scale-line { | |
stroke: white; | |
stroke-width: 1; | |
stroke-opacity: 1; | |
opacity: 0.8; | |
fill: white; | |
shape-rendering: crispEdges; | |
} | |
</style> | |
</head> | |
<body> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script src="//d3js.org/topojson.v1.min.js"></script> | |
<script> | |
var width = 834, | |
height = 615, | |
rasterBounds = [[-90.663042, 29.733114], [-89.521611, 30.461964]], // sw, ne | |
breach_coordinates = [-90.069440, 30.008378], | |
new_orleans_coordinates = [-90.094, 29.950]; | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
// Satellite image | |
var raster_projection = d3.geo.mercator() | |
.scale(1) | |
.translate([0, 0]); | |
svg.append("image") | |
.attr("xlink:href", "new-orleans.jpg") | |
.attr("width", width) | |
.attr("height", height) | |
.attr("class", "image"); | |
// Lake Pontchartrain label | |
svg.append("text") | |
.attr("class", "lake-label") | |
.attr("x", width * 0.45) | |
.attr("y", height * 0.38) | |
.attr('text-anchor', 'start') | |
.text('Lake Pontchartrain'); | |
// Lake Maurepas label | |
svg.append("text") | |
.attr("class", "lake-label") | |
.attr("x", width * 0.14) | |
.attr("y", height * 0.28) | |
.style("text-anchor", "middle") | |
.text('Lake'); | |
svg.append('text') | |
.attr('class', 'lake-label') | |
.attr("x", width * 0.14) | |
.attr("y", height * 0.28) | |
.attr("dx", "0") | |
.attr("dy", "1.5em") | |
.style("text-anchor", "middle") | |
.text("Maurepas"); | |
// Lake Borgne label | |
svg.append("text") | |
.attr("class", "lake-label") | |
.attr("x", width * 0.85) | |
.attr("y", height * 0.65) | |
.text('Lake Borgne'); | |
// Breach | |
var b = [ | |
raster_projection(rasterBounds[0]), | |
raster_projection(rasterBounds[1]) | |
]; | |
var s = 1 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height); | |
var t = [ | |
(width - s * (b[1][0] + b[0][0])) / 2, | |
(height - s * (b[1][1] + b[0][1])) / 2 | |
]; | |
raster_projection | |
.scale(s) | |
.translate(t); | |
// Main image city label | |
svg.selectAll(".main-city-label") | |
.data([new_orleans_coordinates]) | |
.enter().append("text") | |
.attr("class", "main-city-label") | |
.attr("x", function(d) { return raster_projection(d)[0]; }) | |
.attr("y", function(d) { return raster_projection(d)[1]; }) | |
.text('New Orleans'); | |
// Circle marker | |
svg.selectAll('.breach-icon') | |
.data([breach_coordinates]) | |
.enter().append('circle') | |
.attr("r", 4) | |
.each(function(d) { | |
// Converts (lon, lat) to window's (x, y) coordinates | |
var lonlat = raster_projection(d); | |
d3.select(this) | |
.attr("cx", lonlat[0]) | |
.attr("cy", lonlat[1]) | |
.attr("class", "breach-marker"); | |
}); | |
// Line path generator | |
var line = d3.svg.line() | |
.x(function(d) { return d.x; }) | |
.y(function(d) { return d.y; }) | |
.interpolate("basis"); | |
var lonlat = raster_projection(breach_coordinates); | |
var lineData = [ | |
{"x": lonlat[0], "y": lonlat[1] - 4}, | |
{"x": lonlat[0], "y": height / 2} | |
]; | |
// Line between breach marker and text | |
svg.append('path') | |
.attr("class", "breach-line") | |
.attr("d", line(lineData)); | |
// Breach text | |
var breachText1 = svg.append('text') | |
.attr("class", "breach-text") | |
.attr("transform", "translate(" + width / 2 + ", " + (height / 2 - 15) + ")") | |
.attr("dx", "-3em") | |
.attr("dy", "-0.5em") | |
.style("text-anchor", "start") | |
.text("The "); | |
breachText1.append("tspan") | |
.text("London Avenue Canal"); | |
var breachText2 = svg.append('text') | |
.attr("class", "breach-text") | |
.attr("transform", "translate(" + width / 2 + ", " + height / 2 + ")") | |
.attr("dx", "-3em") | |
.attr("dy", "-0.5em") | |
.style("text-anchor", "start") | |
.text(" failed at 9:30 a.m."); | |
// Drop shadow for inset state | |
var filter = svg.append("defs") | |
.append("filter") | |
.attr("id", "drop-shadow") | |
.attr("height", "130%"); | |
filter.append("feGaussianBlur") | |
.attr("in", "SourceAlpha") | |
.attr("stdDeviation", 2) | |
.attr("result", "blur"); | |
filter.append("feOffset") | |
.attr("in", "blur") | |
.attr("dx", 2) | |
.attr("dy", 2) | |
.attr("result", "offsetBlur"); | |
var feMerge = filter.append("feMerge"); | |
feMerge.append("feMergeNode") | |
.attr("in", "offsetBlur"); | |
feMerge.append("feMergeNode") | |
.attr("in", "SourceGraphic"); | |
// Inset map | |
var inset_projection = d3.geo.albers() | |
.center([0, 31.2]) | |
.rotate([91.6, 0]) // Rotate CCW (looking down onto North Pole) | |
.parallels([29, 33]) | |
.scale(1500) | |
.translate([70, height - 65]); | |
var inset_path = d3.geo.path() | |
.projection(inset_projection); | |
d3.json("parishes.json", function(error, data) { | |
if (error) throw error; | |
svg.insert("path", ".inset-fill") | |
.datum(topojson.feature(data, data.objects.parishes)) | |
.attr("class", "land") | |
.attr("d", inset_path) | |
.style("filter", "url(#drop-shadow)"); | |
// Inset city marker | |
// rasterBounds = [sw, ne] | |
var raster_bounds_nw = [rasterBounds[0][0], rasterBounds[1][1]]; | |
var raster_bounds_se = [rasterBounds[1][0], rasterBounds[0][1]]; | |
var lonlat_marker_nw = inset_projection(raster_bounds_nw); | |
var lonlat_marker_se = inset_projection(raster_bounds_se); | |
var marker_width = lonlat_marker_se[0] - lonlat_marker_nw[0]; | |
var marker_height = lonlat_marker_se[1] - lonlat_marker_nw[1]; | |
svg.selectAll(".inset-marker") | |
.data([lonlat_marker_nw]) | |
.enter().append("rect") | |
.attr("class", "inset-marker") | |
.attr("x", function (d) { return d[0]; }) | |
.attr("y", function (d) { return d[1]; }) | |
.attr("width", marker_width) | |
.attr("height", marker_height); | |
// Inset label | |
svg.selectAll(".inset-label") | |
.data([topojson.feature(data, data.objects.parishes)]) | |
.enter().append("text") | |
.attr("class", "inset-label") | |
.attr("x", lonlat_marker_nw[0]) | |
.attr("y", lonlat_marker_nw[1]) | |
.attr("dx", "1em") | |
.attr("dy", "-0.2em") | |
.style("text-anchor", "end") | |
.text("Area shown"); | |
// Inset state label | |
svg.selectAll(".inset-state-label") | |
.data([topojson.feature(data, data.objects.parishes)]) | |
.enter().append("text") | |
.attr("class", "inset-state-label") | |
.attr("transform", function(d) { return "translate(" + inset_path.centroid(d) + ")"; }) | |
.attr("dx", "-1.4em") | |
.attr("dy", "-3em") | |
.text('Louisiana'); | |
// Distance scale | |
function pixelLength(topojson, miles) { | |
// Calculates the window pixel length for a given map distance. | |
var actual_map_bounds = d3.geo.bounds(topojson); | |
var radians = d3.geo.distance(actual_map_bounds[0], actual_map_bounds[1]); | |
var earth_radius = 3959; // miles | |
var arc_length = radians * earth_radius; // s = r * theta | |
var projected_map_bounds = [ | |
raster_projection(actual_map_bounds[0]), | |
raster_projection(actual_map_bounds[1]) | |
]; | |
var projected_width = projected_map_bounds[1][0] - projected_map_bounds[0][0]; | |
var projected_height = projected_map_bounds[0][1] - projected_map_bounds[1][1]; | |
var projected_map_hypotenuse = Math.sqrt( | |
(Math.pow(projected_width, 2)) + (Math.pow(projected_height, 2)) | |
); | |
var pixels_per_mile = projected_map_hypotenuse / arc_length; | |
var pixel_distance = pixels_per_mile * miles; | |
return pixel_distance; | |
} | |
var pixels_for_hundred_miles = pixelLength(topojson.feature(data, data.objects['parishes']), 10); | |
var distance_scale = svg.selectAll("#distance-scale") | |
.data([pixels_for_hundred_miles]) | |
.enter().append("g") | |
.attr("class", "distance-scale") | |
.attr("width", function(d) { return d; }); | |
distance_scale.append('text') | |
.attr("x", function(d, i) { return width * 0.8; }) | |
.attr("y", function(d, i) { return (height * 0.95) + (i * 20); }) | |
.text("10 miles"); | |
distance_scale.append('path') | |
.attr("class", "distance-scale-line") | |
.attr("d", function(d, i) { | |
var lineData = [ | |
{"x": width * 0.8, "y": (height * 0.95) + (i * 20) + 3}, | |
{"x": width * 0.8 + d, "y": (height * 0.95) + (i * 20) + 3} | |
]; | |
return line(lineData); | |
}); | |
}); | |
// Allows iframe on bl.ocks.org. | |
d3.select(self.frameElement).style("height", height + "px"); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment