使用 d3-geo 在 "dark side of the moon" 上渲染

Render on the "dark side of the moon" using d3-geo

使用 d3-geo 中的正交投影,我希望能够在投影球体的不可见侧渲染点。具体来说,我正在制作一个动画,其中一列点绕着一条子午线行进,但当它们到达边缘时,就好像它们掉落了一样(当然,直到它们到达另一侧)。请在下面查看我的代码片段。

目前,路径生成器(path 在我的例子中)只是 returns null 如果你试图在一个隐藏的点上评估它。这当然是它应该做的,但是否可以对其进行调整,使其在前表面上给出等效点,这样我就可以用较浅的颜色渲染它,就好像我们正在看一样球体?

let width = 400;
let points = [...Array(20).keys()].map(k => 2 * k).map(k => [k, 50]);

let svg = d3.select("#d3-example")
  .attr("height", 400)
  .attr("width", width)
  .style("shape-rendering", "crispEdges");

let projection = d3.geoOrthographic()
  .rotate([0, -30])

projection
  .translate([width / 2, 150])
  .scale(width / 960 * projection.scale())

let path = d3.geoPath().projection(projection);

svg.append("path")
  .attr("d", path(d3.geoGraticule10()))
  .attr("stroke", "#AAAAAA")
  .attr("stroke-width", 0.5)
  .attr("fill", "none");

let pt = svg.append("g")
  .attr("id", "loc-points")
  .selectAll("path")
  .data(points)
  .enter()
  .append("path")
  .attr("d", d => path(convertPointToGeoJSON(d)))
  .attr("stroke", "black")
  .attr("fill", "black")
  .attr("opacity", (d, i) => (i + 1) / 20)
  .attr("stroke-width", 1)

pt.transition()
  .duration(5000)
  .ease(d3.easeLinear)
  .on("start", function repeat() {
    d3.active(this)
      .attrTween("d", d => {
        return t => {
          return pointpos(t)(d)
        }
      })
      .transition()
      .on("start", repeat)
  })

function pointpos(t) {
  return function(d) {
    return path(convertPointToGeoJSON([d[0] + 360 * t, d[1]]))
  }
}

function convertPointToGeoJSON(point) {
  return {
    type: "Point",
    coordinates: [point[0], point[1]]
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="d3-example"></svg>

一种非常懒惰和愚蠢的方法是在不剪切任何东西的情况下创建另一个投影(使用 projection.clipAngle(0)),然后根据您的原始投影更改描边和填充。

这是一个演示(我将圆圈移近了赤道,因此更容易看到隐藏的一面):

let width = 400;
let points = [...Array(20).keys()].map(k => 2 * k).map(k => [k, 20]);

let svg = d3.select("#d3-example")
  .attr("height", 400)
  .attr("width", width)
  .style("shape-rendering", "crispEdges");

let projection = d3.geoOrthographic()
  .rotate([0, -30])

let projection2 = d3.geoOrthographic()
  .rotate([0, -30])
  .clipAngle(0)

projection
  .translate([width / 2, 150])
  .scale(width / 960 * projection.scale())

projection2
  .translate([width / 2, 150])
  .scale(width / 960 * projection2.scale())

let path = d3.geoPath().projection(projection);
let path2 = d3.geoPath().projection(projection2);

svg.append("path")
  .attr("d", path(d3.geoGraticule10()))
  .attr("stroke", "#AAAAAA")
  .attr("stroke-width", 0.5)
  .attr("fill", "none");

let pt = svg.append("g")
  .attr("id", "loc-points")
  .selectAll("path")
  .data(points)
  .enter()
  .append("path")
  .attr("d", d => path2(convertPointToGeoJSON(d)))
  .attr("stroke", "black")
  .attr("fill", "black")
  .attr("opacity", (d, i) => (i + 1) / 20)
  .attr("stroke-width", 1)

pt.transition()
  .duration(5000)
  .ease(d3.easeLinear)
  .on("start", function repeat() {
    d3.active(this)
      .styleTween("fill", d => {
        return t => {
          return pointpos(t)(d) ? "black" : "#ccc"
        }
      })
      .styleTween("stroke", d => {
        return t => {
          return pointpos(t)(d) ? "black" : "#aaa"
        }
      })
      .attrTween("d", d => {
        return t => {
          return pointpos2(t)(d)
        }
      })
      .transition()
      .on("start", repeat)
  })

function pointpos(t) {
  return function(d) {
    return path(convertPointToGeoJSON([d[0] + 360 * t, d[1]]))
  }
}

function pointpos2(t) {
  return function(d) {
    return path2(convertPointToGeoJSON([d[0] + 360 * t, d[1]]))
  }
}

function convertPointToGeoJSON(point) {
  return {
    type: "Point",
    coordinates: [point[0], point[1]]
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="d3-example"></svg>

我相信很快就会有人 post 找到合适的解决方案。