Frage

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?

War es hilfreich?

Lösung 2

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...

Andere Tipps

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);

I know this is an old question but would like to add a new solution. You can use projection.stream to filter, parse, and then return the projected x,y in pixels. This method also works on filtering out points that outside of current (zoomed) extent.

Note this will filter (clip) out all points that are invisible under current projection. Add another loop to compare two arrays if you really want to retain all points. But this works very well in my case of using reactive properties.

let newStream = []
let stream = youCurrentProjection.stream({
  point: function (x, y) {
    newStream.push([x, y])
  }
})

arrayOfPoints.forEach(point => {
  stream.point(point.x, point.y)
})

Or build a closure to pass the index

let streamWrapper = function (x, y, index) {
  let stream = youCurrentProjection.stream({
    point: function (x, y) {
      arrayOfPoints[index].isVisible = true
    }
  })
  stream.point(x, y)
}

arrayOfPoints.forEach((point, index) => {
  streamWrapper(point.x, point.y, index)
})
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top