3

I'm looking for a way to zoom from place to place on a globe in D3 v4 (v4 is important). What I'm looking for is basically exactly this: https://www.jasondavies.com/maps/zoom/ My problem is that Jason Davies obfuscated his code, so I can't read it, and I can't find a bl.ock containing that project or anything similar to it. I'll provide a link to what I've got here: http://plnkr.co/edit/0mjyR3ovTfkDXB8FTG0j?p=preview The relevant is probably inside the .tween():

.tween("rotate", function () {
                var p = d3.geoCentroid(points[i]),
                    r = d3.geoInterpolate(projection.rotate(), [-p[0], -p[1]]);

                return function (t) {
                    projection.rotate(r(t));
                    convertedLongLats = [projection(points[0].coordinates), projection(points[1].coordinates)]
                    c.clearRect(0, 0, width, height);
                    c.fillStyle = colorGlobe, c.beginPath(), path(sphere), c.fill();
                    c.fillStyle = colorLand, c.beginPath(), path(land), c.fill();
                    for (var j = 0; j < points.length; j++) {
                        var textCoords = projection(points[j].coordinates);
                        c.fillStyle = textColors, c.textAlign = "center", c.font = "18px FontAwesome", c.fillText(points[j].icon, textCoords[0], textCoords[1]);
                        textCoords[0] += 15;
                        c.textAlign = "left", c.font = " 12px Roboto", c.fillText(points[j].location, textCoords[0], textCoords[1]);
                    }
                    c.strokeStyle = textColors, c.lineWidth = 4, c.setLineDash([10, 10]), c.beginPath(), c.moveTo(convertedLongLats[0][0], convertedLongLats[0][1]), c.lineTo(convertedLongLats[1][0], convertedLongLats[1][1]), c.stroke();
                };
            })

Basically, I want what I've got now but I want it to zoom in, pretty much exactly like it is in the Animated World Zoom example I provided above. I'm not really looking for code, I'd rather someone point me in the right direction with an example or something (it's worth noting that I'm fairly new to d3 and that this project is heavily based on World Tour by mbostock, so it uses canvas). Thank you all in advance!

  • You want to zoom on an orthographic projection, try [this question & answer](https://stackoverflow.com/questions/45595422/d3-world-map-with-country-click-and-zoom-almost-working-not-quite/45604307#45604307) which achieves the effect in your example, or this [block](https://bl.ocks.org/andrew-reid/d95e59b71544706515632c4b7fb0402a) (click to zoom). – Andrew Reid Aug 25 '17 at 16:15
  • @AndrewReid That example uses bounding boxes, the problem with my code is that I need to zoom in on points, so the solution is probably to find a way to make artificial bounding boxes around points, but that's just from my limited d3 knowledge. – WubbaLubbaDubbDubb Aug 26 '17 at 14:40
  • The bounding box is used only to get a scale, if you are using points, you could set it manually to some fixed number, the centroid for rotation is just a point though. As the scale should be the same for all locations if you are just using points, are you looking to have any scale changes from the initial state (whole globe?), and/or during transition (zoom out between points) ? – Andrew Reid Aug 26 '17 at 16:17
  • @AndrewReid I seem to have solved the problem of bounding boxes and zooming, my only problem now is that I wanted to zoom out to the full globe by the middle of the `tween()` and then zoom back in again to the point by the end of the `tween()`. I've created a new plunk with my updated code: http://plnkr.co/edit/y4vn3L8QV0fwyCBBmgIM?p=preview The current problem is that when it reaches the level i want it to switch at, it jumps to a scale of 2000, but I get the feeling that I'm going about this entirely wrongly. – WubbaLubbaDubbDubb Aug 28 '17 at 15:30

1 Answers1

2

Based on your plunker and comment, a challenge in zooming out between two points in a transition is that the interpolator will only interpolate between two values. The solution in your plunker relies on two interpolators, one for zooming in and zooming out. This method has added un-needed complexity and somewhere along the line, as you note, it jumps to an incorrect scale. You could simplify this:

Take an interpolator that interpolates between -1 and 1, and weight each scale according to the absolute value of the interpolator. At zero, the zoom should be out all the way, while at -1,1, you should be zoomed in:

var s = d3.interpolate(-1,1);

// get the appropriate scale:
scale = Math.abs(0-s(t))*startEndScale + (1-Mat.abs(0-s(t)))*middleScale

This is a little clunky as it goes from zooming out to zooming in rather abruptly, so you could ease it with a sine type easing:

var s = d3.interpolate(0.0000001,Math.PI);

// get the appropriate scale:    
scale = (1-Math.abs(Math.sin(s(t))))*startEndScale + Math.abs(Math.sin(s(t)))*middleScale

I've applied this to your plunker here.

For a simple and minimal example using the example that I suggested and your two points and path (and using your plunkr as a base), stripping out the animated line and icons, I would probably put together something like (plunker, snippet below best viewed on full screen):

var width = 600,
    height = 600;

var points = [{
    type: "Point",
    coordinates: [-74.2582011, 40.7058316],
    location: "Your Location",
    icon: "\uF015"
}, {
    type: "Point",
    coordinates: [34.8887969, 32.4406351],
    location: "Caribe Royale Orlando",
    icon: "\uF236"
}];

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

var c = canvas.node().getContext("2d");

var point = points[0].coordinates;

var projection = d3.geoOrthographic()
    .translate([width / 2, height / 2])
    .scale(width / 2)
    .clipAngle(90)
    .precision(0.6)
    .rotate([-point[0], -point[1]]);

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

var colorLand = "#4d4f51",
    colorGlobe = "#2e3133",
    textColors = "#fff";

d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function (error, world) {
    if (error) throw error;

    var sphere = { type: "Sphere" };
    var land = topojson.feature(world, world.objects.land);
    var i = 0;
    
    var scaleMiddle = width/2;
    var scaleStartEnd = width * 2;
    
    loop();
    
    function loop() {
      d3.transition()
        .tween("rotate",function() {
            var flightPath = {
                type: 'Feature',
                geometry: {
                    type: "LineString",
                    coordinates: [points[i++%2].coordinates, points[i%2].coordinates]
                }
            };

          // next point:
          var p = points[i%2].coordinates;
          // current rotation:
          var currentRotation = projection.rotate();  
          // next rotation:
          var nextRotation = projection.rotate([-p[0],-p[1]]).rotate();
          
          // Interpolaters:
          var r = d3.geoInterpolate(currentRotation,nextRotation);
          var s = d3.interpolate(0.0000001,Math.PI);
          
          return function(t) {
            // apply interpolated values
            projection.rotate(r(t))
              .scale(  (1-Math.abs(Math.sin(s(t))))*scaleStartEnd + Math.abs(Math.sin(s(t)))*scaleMiddle  ) ;          

            c.clearRect(0, 0, width, height);
            c.fillStyle = colorGlobe, c.beginPath(), path(sphere), c.fill();
            c.fillStyle = colorLand, c.beginPath(), path(land), c.fill();
            c.beginPath(), path(flightPath), c.globalAlpha = 0.5, c.shadowColor = "#fff", c.shadowBlur = 5, c.lineWidth = 0.5, c.strokeStyle = "#fff", c.stroke(), c.shadowBlur = 0, c.globalAlpha = 1;

          }
        })
        .duration(3000)
        .on("end", function() {  loop();  })
      
      
    }
    
});
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
Andrew Reid
  • 37,021
  • 7
  • 64
  • 83