block by harrystevens 75b3eb474527c10055618fa00123ba44

Dynamic Globe Rotation

Full Screen

Rotate the globe by dragging it or by adjusting the sliders. When you rotate, you are adjusting the Orthographic projection’s three Euler angles.

See also:

The world countries polygons were downloaded from ArcGIS.

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
      
    <style>
    body {
      margin: 0;
    }
    .point-mouse {
      fill: steelblue;
    }
    #rotation {
      position: absolute;
      font-family: monospace;
      padding: 10px;
      background: rgba(255, 255, 255, .5);
    }
    #rotation input {
      width: 300px;
    }
    </style>
  </head>
  <body>
    <div id="rotation"></div>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://unpkg.com/topojson@3.0.2/dist/topojson.js"></script>
    <script src="https://unpkg.com/versor@0.0.3/build/versor.min.js"></script>
    <script>
    var angles = ["λ", "φ", "γ"];
    angles.forEach(function(angle, index){
      d3.select("#rotation").append("div")
        .attr("class", "angle-label angle-label-" + index)
        .html(angle + ": <span>0</span>")

      d3.select("#rotation").append("input")
        .attr("type", "range")
        .attr("class", "angle angle-" + index)
        .attr("min", "-180")
        .attr("max", "180")
        .attr("step", "1")
        .attr("value", "0");
    });

    var width = window.innerWidth, height = window.innerHeight;

    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);

    var projection = d3.geoOrthographic()
        .scale(d3.min([width / 2, height / 2]))
        .translate([width / 2, height / 2])
        .precision(1);

    var path = d3.geoPath()
        .projection(projection);

    var graticule = d3.geoGraticule()
        .step([10, 10]);

    var v0, // Mouse position in Cartesian coordinates at start of drag gesture.
      r0, // Projection rotation as Euler angles at start.
      q0; // Projection rotation as versor at start.
    
    svg.append("path")
        .datum(graticule)
        .attr("class", "graticule")
        .attr("d", path)
        .style("fill", "none")
        .style("stroke", "#ccc");

    var drag = d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);

    svg.call(drag);

    function dragstarted(){

      var mouse_pos = d3.mouse(this);

      v0 = versor.cartesian(projection.invert(mouse_pos));
      r0 = projection.rotate();
      q0 = versor(r0);

      svg.insert("path")
        .datum({type: "Point", coordinates: projection.invert(mouse_pos)})
        .attr("class", "point point-mouse")
        .attr("d", path); 

    }

    function dragged(){

      var mouse_pos = d3.mouse(this);

      var v1 = versor.cartesian(projection.rotate(r0).invert(mouse_pos)),
      q1 = versor.multiply(q0, versor.delta(v0, v1)),
      r1 = versor.rotation(q1);

      if (r1){
        update(r1);

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

        svg.selectAll(".point-mouse")
             .datum({type: "Point", coordinates: projection.invert(mouse_pos)});
      }

    }

    function dragended(){
      svg.selectAll(".point").remove();
    }

    d3.selectAll("input").on("input", function(){
      // get all values
      var nums = [];
      d3.selectAll("input").each(function(d, i){
        nums.push(+d3.select(this).property("value"));
      });
      update(nums);
      
      svg.selectAll("path").attr("d", path);  
      
    });

    function update(eulerAngles){
      angles.forEach(function(angle, index){
        d3.select(".angle-label-" + index + " span").html(Math.round(eulerAngles[index]))
        d3.select(".angle-" + index).property("value", eulerAngles[index])
      });

      projection.rotate(eulerAngles);
    }

    d3.json("countries.json", function (error, countries){
      if (error) throw error;

      // JOIN
      svg.selectAll(".subunit")
          .data(topojson.feature(countries, countries.objects.polygons).features)
        .enter().append("path")
          .attr("d", path)
          .style("stroke", "#fff")
          .style("stroke-width", "1px")
    })

    </script>
  </body>
</html>