【问题标题】:line on d3 map not forming a curved3地图上的线没有形成曲线
【发布时间】:2019-10-26 23:16:26
【问题描述】:

我使用 d3.js 创建了一个地图。我想在两个位置之间显示一条曲线。我可以显示一条线,但有时它不会形成完美的曲线。对于某些线,线在地图后面(穿过反子午线)弯曲到它们的目的地。

这是一个演示问题的代码笔: https://codepen.io/peeyush-pant/pen/WqbPax

还有一张图片:

这是我的投影数据:

var projection = d3.geoEquirectangular();

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

我是这样画线的:

  arcGroup.selectAll("myPath")
    .data(links)
    .enter()
    .append("path")
    .attr("class", "line")
    .attr("id", function (d, i) {
      return "line" + i;
    })
    .attr("d", function (d) {
      return path(d)
    })
    .style("fill", "none")
    .style("stroke", '#fff787')
    .style("stroke-width", 1.5);

谢谢。

【问题讨论】:

    标签: javascript d3.js d3.geo


    【解决方案1】:

    D3 geoPath 可用于创建遵循更大圆距离的路径:它们不是为了风格而弯曲,而是根据需要根据投影方式弯曲,以表示地球上连接两点的最短路径。 D3 geoPaths 被动态重新采样以允许这样做。

    这种行为在网络地理地图库中并不常见,其中大多数将纬度和经度视为笛卡尔数据而不是三维数据:纬度和经度是球体上的点。在将数据视为笛卡尔坐标时,连接两点时线是直的。在 d3 中,这可以通过 these 等方法完成。

    如果您想要所有线段的曲线一致,我们会将数据视为笛卡尔并插入曲线。由于我们不会为此使用d3.geoPath,因此无需将您的目的地和来源转换为geojson LineStrings,我们可以直接使用点。

    我们可以为此使用曲线插值器,但如果不在终点和起点之间添加控制点,默认插值器将无法工作。相反,让我们尝试自定义曲线 - 有关自定义曲线的更多信息,请参阅这些答案 (a,b)。

    我们的自定义曲线可以在第一个点之后的任何点找到它和它之前的点之间的中点,并偏移一个点来创建一个控制点,在之前的点和当前点之间形成一个三角形,然后我们就画了它们之间的二次曲线:

    var curve = function(context) {
      var custom = d3.curveLinear(context);
      custom._context = context;
      custom.point = function(x,y) {
        x = +x, y = +y;
        switch (this._point) {
          case 0: this._point = 1; 
            this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
            this.x0 = x; this.y0 = y;        
            break;
          case 1: this._point = 2;
          default: 
            var x1 = this.x0 * 0.5 + x * 0.5;
            var y1 = this.y0 * 0.5 + y * 0.5;
            var m = 1/(y1 - y)/(x1 - x);
            var r = -100; // offset of mid point.
            var k = r / Math.sqrt(1 + (m*m) );
            if (m == Infinity) {
              y1 += r;
            }
            else {
              y1 += k;
              x1 += m*k;
            }     
            this._context.quadraticCurveTo(x1,y1,x,y); 
            this.x0 = x; this.y0 = y;        
            break;
        }
      }
      return custom;
    }
    

    有了这个,我们可以简单地画线:

    d3.line()
     .curve(curve)
     .x(function(d) { return d.lon; })
     .y(function(d) { return d.lat; })
    

    如下图:

    let data = [{
            "source": {
                "lat": 40.712776,
                "lon": -74.005974    
            },
            "destination": {
                "lat": 21.05,
                "lon": 105.55
            }
        },
                 {
            "source": {
                "lat": 40.712776,
                "lon": -74.005974    
            },
            "destination": {
                "lat": -35.15,
                "lon": 149.08
            }
        }]
    
    var curve = function(context) {
      var custom = d3.curveLinear(context);
      custom._context = context;
      custom.point = function(x,y) {
        x = +x, y = +y;
        switch (this._point) {
          case 0: this._point = 1; 
            this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
            this.x0 = x; this.y0 = y;        
            break;
          case 1: this._point = 2;
          default: 
            var x1 = this.x0 * 0.5 + x * 0.5;
            var y1 = this.y0 * 0.5 + y * 0.5;
            var m = 1/(y1 - y)/(x1 - x);
            var r = -100; // offset of mid point.
            var k = r / Math.sqrt(1 + (m*m) );
            if (m == Infinity) {
              y1 += r;
            }
            else {
              y1 += k;
              x1 += m*k;
            }     
            this._context.quadraticCurveTo(x1,y1,x,y); 
            this.x0 = x; this.y0 = y;        
            break;
        }
      }
      return custom;
    }
    
    var projection = d3.geoEquirectangular().translate([250,150]).scale(500/Math.PI/2);
    var path = d3.geoPath(projection);
    
    var svg = d3.select("body")
      .append("svg")
      .attr("width", 500)
      .attr("height", 300);
      
    d3.json("https://unpkg.com/world-atlas@1/world/110m.json").then(function(world) {
     
      var worldOutline = svg.append("path")
        .datum(topojson.mesh(world))
        .attr("d", path );
        
      var line = d3.line()
        .x(function(d) {
          return projection([d.lon,d.lat])[0];
        })
        .y(function(d) {
          return projection([d.lon,d.lat])[1];
        })
        .curve(curve);
        
      svg.selectAll(null)
        .data(data)
        .enter()
        .append("path")
        .datum(function(d) {
          return [d.source,d.destination]; // d3.line expects an array where each item represnts a vertex.
        })
        .attr("d",line)
        .style("stroke","black")
        .style("stroke-width",1.5);
    
    });
    path {
      fill: none;
      stroke: #ccc;
      stroke-width: 1px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <script src="https://unpkg.com/topojson-client@3"></script>

    下面,只是为了好玩,我比较了使用d3.line 的直线,使用自定义曲线插值器的d3.line 的曲线,以及带有一些动画的普通旧d3.geoPath

     let data = [{
            "source": {
                "lat": 40.712776,
                "lon": -74.005974    
            },
            "destination": {
                "lat": 21.05,
                "lon": 105.55
            }
        },
                 {
            "source": {
                "lat": 40.712776,
                "lon": -74.005974    
            },
            "destination": {
                "lat": -35.15,
                "lon": 149.08
            }
        }]
    
    var curve = function(context) {
      var custom = d3.curveLinear(context);
      custom._context = context;
      custom.point = function(x,y) {
        x = +x, y = +y;
        switch (this._point) {
          case 0: this._point = 1; 
            this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
            this.x0 = x; this.y0 = y;        
            break;
          case 1: this._point = 2;
          default: 
            var x1 = this.x0 * 0.5 + x * 0.5;
            var y1 = this.y0 * 0.5 + y * 0.5;
            var m = 1/(y1 - y)/(x1 - x);
            var r = -100; // offset of mid point.
            var k = r / Math.sqrt(1 + (m*m) );
            if (m == Infinity) {
              y1 += r;
            }
            else {
              y1 += k;
              x1 += m*k;
            }     
            this._context.quadraticCurveTo(x1,y1,x,y); 
            this.x0 = x; this.y0 = y;        
            break;
        }
      }
      return custom;
    }
    
    var projection = d3.geoEquirectangular().translate([250,150]).scale(500/Math.PI/2);
    var path = d3.geoPath(projection);
    
    var svg = d3.select("body")
      .append("svg")
      .attr("width", 500)
      .attr("height", 300);
      
    d3.json("https://unpkg.com/world-atlas@1/world/110m.json").then(function(world) {
     
      var worldOutline = svg.append("path")
        .datum(topojson.mesh(world))
        .attr("d", path );
        
      var line = d3.line()
        .x(function(d) {
          return projection([d.lon,d.lat])[0];
        })
        .y(function(d) {
          return projection([d.lon,d.lat])[1];
        })
        .curve(curve);
        
      var fauxArcPaths = svg.selectAll(null)
        .data(data)
        .enter()
        .append("path")
        .datum(function(d) {
          return [d.source,d.destination];
        })
        .attr("d",line)
        .style("stroke","black")
        .style("stroke-width",1.5);
        
      var greatCirclePaths = svg.selectAll(null)
        .data(data)
        .enter()
        .append("path")
        .datum(function(d) {
          return {type:"LineString",coordinates:
            [[d.source.lon,d.source.lat],[d.destination.lon,d.destination.lat]] }
        })
        .attr("d",path)
        .style("stroke","steelblue")
        .style("stroke-width",1.5);
        
      var straightline = d3.line()
        .x(function(d) {
          return projection([d.lon,d.lat])[0];
        })
        .y(function(d) {
          return projection([d.lon,d.lat])[1];
        });
        
      var straightPaths = svg.selectAll(null)
        .data(data)
        .enter()
        .append("path")
        .datum(function(d) {
          return [d.source,d.destination];
        })
        .attr("d",straightline)
        .style("stroke-width",1.5)
        .style("stroke","orange");
       
      // animate:
      d3.interval(function(elapsed) {
          projection.rotate([ -elapsed / 150, elapsed/300 ]);
          straightPaths.attr("d",straightline);
          greatCirclePaths.attr("d",path);
          fauxArcPaths.attr("d",line);
          worldOutline.attr("d",path);
      }, 50);
            
        
      
    });
    path {
      fill: none;
      stroke: #aaa;
      stroke-width: 1px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <script src="https://unpkg.com/topojson-client@3"></script>

    【讨论】:

    • 谢谢@andrew,你的建议真的很有帮助。
    猜你喜欢
    • 2017-08-29
    • 2017-08-27
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多