Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I determine if a point is hidden on a projection?

I'm working of a fairly simple world globe interface using D3 and the D3.geo.projection to create a spinning globe with data points on it.

Everything worked fine (i.e. the points "eclipsed" when they rotated away behind the horizon) when I was just plotting the points with circles:

svg.append("g")
    .attr("class","points")
    .selectAll("text")
    .data(places.features)
  .enter()

  //for circle-point------------------------------
  .append("path")
  .attr("d", path.pointRadius(function(d) {
      if (d.properties)
        return 3+(4*d.properties.scalerank);
                    }))
    .attr("d", path)

    .attr("class", "point")
    .on("click",pointClick)
;

But now I'm trying to plot symbols instead of circles:

svg.append("g")
    .attr("class","points")
    .selectAll("text")
    .data(places.features)
  .enter()
    //for image-------------------------------------
    .append("image")
    .attr("xlink:href", "img/x_symbol.png")
    .attr("x", -12)
    .attr("y", -12)
    .attr("width", 24)
    .attr("height", 24)
    .attr("transform", function(d) {
        return "translate(" + projection([
          d.properties.longitude,
          d.properties.latitude
        ]) + ")"
      })

    .attr("class", "point")
    .on("click",pointClick)
;

And while this works, and the symbols plot in the right place on the globe, they persist even when they wrap to the back of the globe. I can hide them with a visibility property if I had a way to determine if they were eclipsed, but I don't see a method in d3.geo.projection to do that. Any ideas?

like image 819
Craig Soich Avatar asked Mar 13 '14 00:03

Craig Soich


2 Answers

Calculate if the point is visible

If you have your projection:

const chartProjection = d3.geo.orthographic();

You can turn it into a path function:

const path = d3.geo.path()
  .projection(chartProjection);

Then you can evaluate each point for visibility. path will return undefined for values behind the projection.

function getVisibility(d) {
  const visible = path(
    {type: 'Point', coordinates: [d.longitude, d.latitude]});

  return visible ? 'visible' : 'hidden';
}

// Update all text elements.
svg.selectAll('text')
  .attr('visibility', getVisibility);
like image 58
James Avatar answered Nov 14 '22 18:11

James


OK, so I was able to at least simulate the images hiding around the back of the globe, by calculating the great circle distance between the projection center and the point in question. If the distance was greater than π/2, the point would be beyond the horizon:

        .attr("opacity", function(d) {
            var geoangle = d3.geo.distance(
                    d.geometry.coordinates,
                    [
                        -projection.rotate()[0],
                        projection.rotate()[1]
                    ]);
            if (geoangle > 1.57079632679490)
            {
                return "0";
            } else {
                return "1.0";
            }
        })

I'm sure I can get a bit fancier by fading them out as they approach the edge, disabling clicks, etc. but now I can move on...

like image 23
Craig Soich Avatar answered Nov 14 '22 17:11

Craig Soich