How to keep d3 orthographic projection centered when zooming
Asked Answered
C

0

6

I am trying to replicate the zoom functionality shown in Jason Davies "Rotate the World" visualization (https://www.jasondavies.com/maps/rotate/)

I am able to rotate and zoom, however, if I zoom after rotating, my projection is zoomed at an angle (meaning, if I turn the globe 15 degrees to the left and then zoom, the globe no longer stays centered in the canvas). Below is my code, any help would be greatly appreciated!

d3.select(window)
    .on("mousemove", mousemove)
    .on("mouseup", mouseup);

var width = 960,
height = 500;

var proj = d3.geo.orthographic()
    .scale(220)
    .translate([width / 2, height / 2])
    .clipAngle(90);


var path = d3.geo.path().projection(proj).pointRadius(1.5);

var graticule = d3.geo.graticule();

var svg = d3.select("#content").append("svg")
            .attr("width", width)
            .attr("height", height)
            .on("mousedown", mousedown);


var zoom = d3.behavior.zoom()
    .center([width / 2, height / 2])
    //.scaleExtent([.5, 10])
    .on("zoom", zoomed);

svg.call(zoom);


queue()
    .defer(d3.json, "/static/json/world.json")
    .await(ready);

function ready(error, world) {
    /*
        Define gradients
     */

    // ocean
    var ocean_fill = svg.append("defs").append("radialGradient")
        .attr("id", "ocean_fill")
        .attr("cx", "75%")
        .attr("cy", "25%");
    ocean_fill.append("stop").attr("offset", "5%").attr("stop-color", "#777");
    ocean_fill.append("stop").attr("offset", "100%").attr("stop-color", "#555");

    // globe highlight
    var globe_highlight = svg.append("defs").append("radialGradient")
        .attr("id", "globe_highlight")
        .attr("cx", "75%")
        .attr("cy", "25%");
    globe_highlight.append("stop")
        .attr("offset", "5%").attr("stop-color", "#bbb")
        .attr("stop-opacity","0.6");
    globe_highlight.append("stop")
        .attr("offset", "100%").attr("stop-color", "#999")
        .attr("stop-opacity","0.2");

    // globe shadow
    var globe_shading = svg.append("defs").append("radialGradient")
        .attr("id", "globe_shading")
        .attr("cx", "50%")
        .attr("cy", "40%");
    globe_shading.append("stop")
        .attr("offset","50%").attr("stop-color", "#333")
        .attr("stop-opacity","0");
    globe_shading.append("stop")
        .attr("offset","100%").attr("stop-color", "#111")
        .attr("stop-opacity","0.3");

    // drop shadow
    var drop_shadow = svg.append("defs").append("radialGradient")
        .attr("id", "drop_shadow")
        .attr("cx", "50%")
        .attr("cy", "50%");
    drop_shadow.append("stop")
        .attr("offset","20%").attr("stop-color", "#000")
        .attr("stop-opacity",".5");
    drop_shadow.append("stop")
        .attr("offset","100%").attr("stop-color", "#000")
        .attr("stop-opacity","0");

    /*
        Draw globe objects
     */

    // drop shadow
    svg.append("ellipse")
        .attr("cx", 440).attr("cy", 450)
        .attr("rx", proj.scale()*.90)
        .attr("ry", proj.scale()*.25)
        .attr("class", "noclicks")
        .style("fill", "url(#drop_shadow)");

    // globe
    svg.append("circle")
        .attr("cx", width / 2).attr("cy", height / 2)
        .attr("r", proj.scale())
        .attr("class", "noclicks")
        .style("fill", "url(#ocean_fill)");

    // land
    svg.append("path")
        .datum(topojson.feature(world, world.objects.land))
        .attr("class", "land")
        .attr("d", path);

    svg.append("path")
        .datum(graticule)
        .attr("class", "graticule noclicks")
        .attr("d", path);

    svg.append("circle")
        .attr("cx", width / 2).attr("cy", height / 2)
        .attr("r", proj.scale())
        .attr("class","noclicks")
        .style("fill", "url(#globe_highlight)");

    svg.append("circle")
        .attr("cx", width / 2).attr("cy", height / 2)
        .attr("r", proj.scale())
        .attr("class","noclicks")
        .style("fill", "url(#globe_shading)");

/*    svg.append("g").attr("class","points")
        .selectAll("text").data(places.features)
        .enter().append("path")
        .attr("class", "point")
        .attr("d", path);

    svg.append("g").attr("class","labels")
        .selectAll("text").data(places.features)
        .enter().append("text")
        .attr("class", "label")
        .text(function(d) { return d.properties.name })*/

    svg.append("g").attr("class","countries")
      .selectAll("path")
        .data(topojson.feature(world2, world2.objects.countries).features)
      .enter().append("path")
        .attr("d", path);
}

// modified from http://bl.ocks.org/1392560
var m0, o0;

function mousedown() {
    m0 = [d3.event.pageX, d3.event.pageY];
    o0 = proj.rotate();
    d3.event.preventDefault();
}
function mousemove() {
    if (m0) {
        var m1 = [d3.event.pageX, d3.event.pageY]
            , o1 = [o0[0] + (m1[0] - m0[0]) / 6, o0[1] + (m0[1] - m1[1]) / 6];
        o1[1] = o1[1] > 30  ? 30  :
            o1[1] < -30 ? -30 :
            o1[1];
        proj.rotate(o1);
        refresh();
    }
}
function mouseup() {
    if (m0) {
        mousemove();
        m0 = null;
    }
}

function refresh() {
    svg.selectAll(".land").attr("d", path);
    svg.selectAll(".countries path").attr("d", path);
    svg.selectAll(".graticule").attr("d", path);
    svg.selectAll(".point").attr("d", path);
    //position_labels();
}

var slast = 1;

function zoomed() {
    if (slast != d3.event.scale) {
        svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
        slast = d3.event.scale;
    };
}
Cruciform answered 30/6, 2014 at 19:7 Comment(1)
Centering and rotating an orthographic projection,see also: #45595922Vociferation

© 2022 - 2024 — McMap. All rights reserved.