Skip to content

Instantly share code, notes, and snippets.

@ThomasThoren
Last active September 30, 2019 23:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ThomasThoren/93745cc190a6b5ef02bd to your computer and use it in GitHub Desktop.
Save ThomasThoren/93745cc190a6b5ef02bd to your computer and use it in GitHub Desktop.
Inset map

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.

<!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>
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