block by alexmacy 082cb12c8f4d5c0d5c4445c16a3db383

Projection Transitions w/slider and rotation

Full Screen

This is another riff on projection transitions, based on mbostock‘s block: Projection Transitions. This one uses a slider to allow for manually controlled transitions. The map can also be rotated by clicking and dragging.

forked from alexmacy‘s block: Projection Transitions w/slider and rotation

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  background: #fcfcfa;
  margin: 0px;
}

.projection-menu {
  margin: 10px;
  width: 25%;
}

.slider {
  margin: 10px;
  width: 40%;
}

.stroke {
  fill: none;
  stroke: #000;
  stroke-width: 3px;
}

.fill {
  fill: #fff;
}

.graticule {
  fill: none;
  stroke: #777;
  stroke-width: .5px;
  stroke-opacity: .5;
}

.land {
  fill: #222;
}

.boundary {
  fill: none;
  stroke: #fff;
  stroke-width: .5px;
}

</style>
<select id="projection-menu1" class="projection-menu"></select>
<select id="projection-menu2" class="projection-menu"></select>
<input type="range" id="slider" class="slider" min="0" max="10000" value="0" oninput="sliderInput()">

<script src="//d3js.org/d3.v4.min.js"></script>
<script src="//d3js.org/d3-geo-projection.v1.min.js"></script>
<script src="//d3js.org/topojson.v2.min.js"></script>
<script>

var width = 960,
    height = 450;

var options = [
  {name: "Aitoff", projection: d3.geoAitoff()},
  {name: "Albers", projection: d3.geoAlbers().parallels([20, 50])},
  {name: "August", projection: d3.geoAugust()},
  {name: "Baker", projection: d3.geoBaker()},
  {name: "Boggs", projection: d3.geoBoggs()},
  {name: "Bonne", projection: d3.geoBonne()},
  {name: "Bottomley", projection: d3.geoBottomley()},
  {name: "Bromley", projection: d3.geoBromley()},
  {name: "Collignon", projection: d3.geoCollignon()},
  {name: "Conic Equal Area", projection: d3.geoConicEqualArea()},
  {name: "Conic Equidistant", projection: d3.geoConicEquidistant()},
  {name: "Craster Parabolic", projection: d3.geoCraster()},
  {name: "Cylindrical Equal-Area", projection: d3.geoCylindricalEqualArea()},
  {name: "Eckert I", projection: d3.geoEckert1()},
  {name: "Eckert II", projection: d3.geoEckert2()},
  {name: "Eckert III", projection: d3.geoEckert3()},
  {name: "Eckert IV", projection: d3.geoEckert4()},
  {name: "Eckert V", projection: d3.geoEckert5()},
  {name: "Eckert VI", projection: d3.geoEckert6()},
  {name: "Eisenlohr", projection: d3.geoEisenlohr()},
  {name: "Equirectangular (Plate Carrée)", projection: d3.geoEquirectangular()},
  {name: "Fahey", projection: d3.geoFahey()},
  {name: "Foucaut", projection: d3.geoFoucaut()},
  {name: "Gilbert", projection: d3.geoGilbert()},
  {name: "Ginzburg IX", projection: d3.geoGinzburg9()},
  {name: "Goode Homolosine", projection: d3.geoHomolosine()},
  {name: "Gringorten", projection: d3.geoGringorten()},
  {name: "Guyou", projection: d3.geoGuyou()},
  {name: "Hammer", projection: d3.geoHammer()},
  {name: "Hill", projection: d3.geoHill()},
  {name: "Kavrayskiy VII", projection: d3.geoKavrayskiy7()},
  {name: "Lambert cylindrical equal-area", projection: d3.geoCylindricalEqualArea()},
  {name: "Lagrange", projection: d3.geoLagrange()},
  {name: "Larrivée", projection: d3.geoLarrivee()},
  {name: "Laskowski", projection: d3.geoLaskowski()},
  {name: "Loximuthal", projection: d3.geoLoximuthal()},
  {name: "Miller", projection: d3.geoMiller()},
  {name: "McBryde–Thomas Flat-Polar Parabolic", projection: d3.geoMtFlatPolarParabolic()},
  {name: "McBryde–Thomas Flat-Polar Quartic", projection: d3.geoMtFlatPolarQuartic()},
  {name: "McBryde–Thomas Flat-Polar Sinusoidal", projection: d3.geoMtFlatPolarSinusoidal()},
  {name: "Mollweide", projection: d3.geoMollweide()},
  {name: "Natural Earth", projection: d3.geoNaturalEarth()},
  {name: "Nell–Hammer", projection: d3.geoNellHammer()},
  {name: "Orthographic", projection: d3.geoOrthographic()},
  {name: "Patterson", projection: d3.geoPatterson()},
  {name: "Polyconic", projection: d3.geoPolyconic()},
  {name: "Rectangular (War Office) Polyconic", projection: d3.geoRectangularPolyconic()},
  {name: "Robinson", projection: d3.geoRobinson()},
  {name: "Sinusoidal", projection: d3.geoSinusoidal()},
  {name: "Sinu-Mollweide", projection: d3.geoSinuMollweide()},
  {name: "Times", projection: d3.geoTimes()},
  {name: "van der Grinten", projection: d3.geoVanDerGrinten()},
  {name: "van der Grinten IV", projection: d3.geoVanDerGrinten4()},
  {name: "Wagner IV", projection: d3.geoWagner4()},
  {name: "Wagner VI", projection: d3.geoWagner6()},
  {name: "Wagner VII", projection: d3.geoWagner7()},
  {name: "Winkel Tripel", projection: d3.geoWinkel3()}
];

options.forEach(function(o) {
   o.projection.rotate([0, 0]).center([0, 0]);
});

d3.selectAll('.projection-menu')
    .on('change', projectionMorph)
  .selectAll("option")
    .data(options)
  .enter().append("option")
    .text(function(d) { return d.name; });

var menu1 = d3.select("#projection-menu1")
    .property("value", options[Math.floor(Math.random()*options.length)].name);

var menu2 = d3.select("#projection-menu2")
    .property("value", options[Math.floor(Math.random()*options.length)].name);

var posX = 0, posY = 0;

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
    .call(d3.drag().on("drag", dragged))

var defs = svg.append("defs")

defs.append("path")
    .datum({type: "Sphere"})
    .attr("id", "sphere")

svg.append("use")
    .attr("class", "stroke")
    .attr("xlink:href", "#sphere");

svg.append("use")
    .attr("class", "fill")
    .attr("xlink:href", "#sphere");

svg.append("path")
    .datum(d3.geoGraticule())
    .attr("class", "graticule")

var t = d3.timer(function() {});

d3.json("world-110m.json", function(error, world) {
  if (error) throw error;

  svg.insert("path", ".graticule")
      .datum(topojson.feature(world, world.objects.land))
      .attr("class", "land")

  t.restart(function(elapsed) {
    document.getElementById('slider').value = elapsed * 2
    projectionMorph()
    if (elapsed > 5000) t.stop()
  }, 100)
});

function sliderInput() {
  t.stop();
  projectionMorph();
}

function dragged() {
  posX = (posX + d3.event.dx/4) % 360
  posY = (posY - d3.event.dy/4) % 360
  projectionMorph()
}

function projectionMorph() {
  var t = d3.select('#slider').property('value')/10000;

  var projections = [menu1, menu2].map(function(p) {
    return options.filter(function(d) {
      return d.name == p.property('value')
    })[0].projection
  });

  svg.selectAll("path")
      .attr("d", getProjection)

  function getProjection(d) {
    var projection = d3.geoProjection(project)
        .rotate([posX, posY])
        .fitExtent([[10, 10], [width - 10, height - 10]], {
          type: "Sphere"
        });

    var path = d3.geoPath(projection);

    function project(λ, φ) {
      λ *= 180 / Math.PI, 
      φ *= 180 / Math.PI;

      var p0 = projections[0]([λ, φ]), 
          p1 = projections[1]([λ, φ]);

      return [
        (1 - t) * p0[0] + t * p1[0], 
        (1 - t) * -p0[1] + t * -p1[1]
        ];
    }

    return path(d)
  }

}

</script>