block by thomasthoren 93745cc190a6b5ef02bd

Inset map

Full Screen

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.

index.html

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