【问题标题】:d3.js. Spinning globe with barsd3.js。带酒吧的旋转地球仪
【发布时间】:2015-07-17 15:51:33
【问题描述】:

我正在尝试用this example 中的条形创建旋转地球。你可以看到我的例子here。一切都很好,直到酒吧越过地平线。当它们在地球的另一边时,我不知道如何从底部切割条。有人可以建议我怎么做吗?

 /*
 * Original code source
 * http://codepen.io/teetteet/pen/Dgvfw
 */

var width = 400;
var height = 400;
var scrollSpeed = 50;
var current = 180;

var longitudeScale = d3.scale.linear()
  .domain([0, width])
  .range([-180, 180]);

var planetProjection = d3.geo.orthographic()
  .scale(200)
  .rotate([longitudeScale(current), 0])
  .translate([width / 2, height / 2])
  .clipAngle(90);
var barProjection = d3.geo.orthographic()
  .scale(200)
  .rotate([longitudeScale(current), 0])
  .translate([width / 2, height / 2])
  .clipAngle(90);

var path = d3.geo.path()
  .projection(planetProjection);

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

d3.json("https://dl.dropboxusercontent.com/s/4hp49mvf7pa2cg2/world-110m.json?dl=1", function(error, world) {
  if (error) throw error;

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

  d3.csv("https://dl.dropboxusercontent.com/s/v4kn2hrnjlgx1np/data.csv?dl=1", function(error, data) {
    if (error) throw error;

    var max = d3.max(data, function(d) {
      return parseInt(d.Value);
    })

    var lengthScale = d3.scale.linear()
      .domain([0, max])
      .range([200, 250])

      var bars = svg.selectAll(".bar")
        .data(data)
        .enter()
        .append("line")
        .attr("class", "bar")
        .attr("stroke", "red")
        .attr("stroke-width", "2");

    function bgscroll() {

      current += 1;

      planetProjection.rotate([longitudeScale(current), 0]);
      barProjection.rotate([longitudeScale(current), 0]);

      planet.attr("d", path);

      bars.attr("x1", function(d) {
         return planetProjection([d.Longitude, d.Latitude])[0];
       }).attr("y1", function(d) {
         return planetProjection([d.Longitude, d.Latitude])[1];
       }).attr("x2", function(d) {
         barProjection.scale(lengthScale(d.Value));
         return barProjection([d.Longitude, d.Latitude])[0];
       }).attr("y2", function(d) {
         barProjection.scale(lengthScale(d.Value));
         return barProjection([d.Longitude, d.Latitude])[1];
       });
    }

//    bgscroll();
     setInterval(bgscroll, scrollSpeed);  
  })
})

【问题讨论】:

    标签: d3.js map-projections orthographic


    【解决方案1】:

    为了剪掉地平线上的条,我们添加了一个以地球 2D 中心为中心的蒙版,并带有它的半径。然后我们仅在底部边缘穿过地平线(通过跟踪经度)时应用此蒙版。

    创建蒙版

    // get the center of the circle
    var center = planetProjection.translate();
    // edge point
    var edge = planetProjection([-90, 90])
    // radius
    var r = Math.pow(Math.pow(center[0] - edge[0], 2) + Math.pow(center[1] - edge[1], 2), 0.5);
    
    svg.append("defs")
        .append("clipPath")
        .append("circle")
        .attr("id", "edgeCircle")
        .attr("cx", center[0])
        .attr("cy", center[1])
        .attr("r", r)
    
    var mask = svg.append("mask").attr("id", "edge")
    mask.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", "100%")
        .attr("height", "100%")
        .attr("fill", "white");
    mask.append("use")
        .attr("xlink:href", "#edgeCircle")
        .attr("fill", "black");
    

    应用蒙版

    .... bars ....
    .attr("mask", function (d) {
        // make the range from 0 to 360, so that it's easier to compare
        var longitude = Number(d.Longitude) + 180;
        // +270 => -90 => the position of the left edge when the center is at 0
        // -value because a rotation to the right => left edge longitude is reducing
        // 360 because we want the range from 0 to 360
        var startLongitude = 360 - ((longitudeScale(current) + 270) % 360);
        // the right edge is start edge + 180
        var endLongitude = (startLongitude + 180) % 360;
        if ((startLongitude < endLongitude && longitude > startLongitude && longitude < endLongitude) ||
            // wrap around
            (startLongitude > endLongitude && (longitude > startLongitude || longitude < endLongitude)))
            return null;
        else
            return "url(#edge)";
    });
    

    我们也可以通过测量距离来做到这一点。


    小提琴 - http://jsfiddle.net/gp3wvm8o/


    【讨论】:

    • 就是这样!谢谢。
    【解决方案2】:

    只需跟踪可见经度的范围,如果它们不在该范围内,则隐藏条形

    .attr("display", function(d) {
        // make the range from 0 to 360, so that it's easier to compare
        var longitude = Number(d.Longitude) + 180;
        // +270 => -90 => the position of the left edge when the center is at 0
        // -value because a rotation to the right => left edge longitude is reducing
        // 360 because we want the range from 0 to 360
        var startLongitude = 360 - ((longitudeScale(current) + 270) % 360);
        // the right edge is start edge + 180
        var endLongitude = (startLongitude + 180) % 360;
        if ((startLongitude < endLongitude && longitude > startLongitude && longitude < endLongitude) ||
            // wrap around
            (startLongitude > endLongitude && (longitude > startLongitude || longitude < endLongitude)))
            return "block";
        else
            return "none";
    })
    

    小提琴 - http://jsfiddle.net/b12ryhda/

    【讨论】:

    • 谢谢。这是个好主意,但我不想一次隐藏栏,而是逐渐隐藏,就像我在顶部提供的示例一样
    • @zeleniy - 已将其作为单独的答案发布,因为核心部分本质上是不同的(显示与掩码)
    【解决方案3】:

    使用画布的更简单的方法是:

    1. 绘制所有条形而不进行剪切
    2. 绘制地图
    3. 仅绘制前景条(即使用剪辑)

    此剪辑不必手动关闭,但可以利用path.centroid 方法,该方法尊重clipAngle 在投影上设置的剪辑。伪代码可能如下所示:

    let projection = d3.geoOrthographic()
                   .clipAngle(90)
                   ...
    let barProjection = d3.geoOrthographic()
                   .clipAngle(90)
                   ...
    
    let path = d3.geoPath()
             .projection(projection)
             .context(canvasCtx)
    let barPath = d3.geoPath()
             .projection(barProjection)
    
    let renderBar = function(isBgLayer = false) {
        let barLengthAsScale = ...
        barProjection.scale(barLengthAsScale)
        let barStart, barEnd
        if (isBgLayer) {
            barStart = projection([ lon, lat ])
            barEnd = barProjection([ lon, lat ])
        } else {
            let geoJs = { type: 'Point', coordinates: [ lon, lat ] }
            barStart = path.centroid(geoJs)
            barEnd = barPath.centroid(geoJs)
        }
        // draw line from start to end using canvasCtx
    };
    
    let renderMap = function(topology) {
        // normal map drawing to canvas
    };
    
    // then to render a frame
    renderBar(true)
    renderMap(topoJsonTopology)
    renderBar()
    

    有些条会被绘制两次,但我发现画布的速度足以跟上绘图的速度,并且至少 200 多个条可以保持动画流畅。

    例如,查看code on GitHublive page

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-10
      • 1970-01-01
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 2018-04-18
      相关资源
      最近更新 更多