Bohdan Shcherbak Bohdan Shcherbak - 3 months ago 30
Javascript Question

D3 js orthographic markers rotation

Currently I have semi-functional orthographic globe made with D3. It is a map with markers. All "land" zones are well displayed can be rotated by draggin , but svg markers doesn't. Here is my current test code:



var width = 960,
height = 500,
sens = 0.25;

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

var path = d3.geo.path().projection(proj).pointRadius(function(d) { return 6 });

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

var g = svg.append('g');

queue()
.defer(d3.json, 'data/world-110m.json')
.defer(d3.json, 'data/generated.json')
.await(ready);

function ready(error, world, locations) {

g.append('path')
.datum({type: 'Sphere'})
.attr('class', 'water')
.attr('d', path);

g.selectAll('g.land')
.data(topojson.feature(world, world.objects.countries).features)
.enter().append('path')
.attr('class', 'land')
.attr('d', path)
.call(d3.behavior.drag()
.origin(function() {
var r = proj.rotate();
return {x: r[0] / sens, y: -r[1] / sens}; })
.on('drag', function(d) {
var rotate = proj.rotate();
proj.rotate([d3.event.x * sens, -d3.event.y * sens, rotate[2]]);
g.selectAll('.land').attr('d', path);
}));

var nodes = g.selectAll('g.node')
.data(locations)
.enter().append('g').attr('class', 'node')
.attr('transform', function(d) {
var proj_pos = proj([d.lon, d.lat]);
return 'translate(' + proj_pos[0] + ',' + proj_pos[1] + ')';
})
.attr('style', 'cursor: pointer')
.attr("d", path);

nodes.append("svg:image")
.attr('transform', 'translate(-24, -20)')
.attr('width', 20)
.attr('height', 24)
.classed('white',true)
.attr("href","data/marker.svg");

console.log(g.selectAll('g.node'));
}





After trying to make it work some way, I only have been able to clip "points" on the globe, but no SVGs. It seems that svg must go into 'g' tag and results my 'nodes' has no path in 'd' tag.

What I need: whole map must rotate with drag action, with SVGs clipped to location, specified in 'generated.json'



[
{
"lat": 62.782176,
"lon": 54.3214
},
{
"lat": -61.007975,
"lon": -5.05281
},
{
"lat": -78.166262,
"lon": 45.320536
}
]





It is kinda frustrating and if someone could help me, I would be very grateful.

Answer

Updating the position of your nodes is pretty simple. The trick here, though, is to determine when the marker has been rotated "off" the globe (around the back of the map). The only way I can figure to do that is to generate a point path to see if it's undefined:

.on('drag', function(d) {

  //update land like you've been doing
  var rotate = proj.rotate();
  proj.rotate([d3.event.x * sens, -d3.event.y * sens, rotate[2]]);
  g.selectAll('.land').attr('d', path);

  // for each node
  nodes.each(function(d, i) {
    var self = d3.select(this),
      lon_lat = [d.lon, d.lat];
      proj_pos = proj(lon_lat);

    // check to see if it's still visible
    var hasPath = path({
      type: "Point",
      coordinates: lon_lat
    }) != undefined;

    // if it is show it and update position
    if (hasPath) {
      self.style("display","inline");
      self.attr("transform", 'translate(' + proj_pos[0] + ',' + proj_pos[1] + ')');
    }
    // otherwise hide it
    else {
      self.style("display","none")
    }
  });
}));

Full running code.