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>