【问题标题】:Append linked graphics at fixed locations on d3 geo plot在 d3 地理图上的固定位置附加链接图形
【发布时间】:2018-11-16 19:07:37
【问题描述】:

我正在尝试使用 d3 geo 将链接图形树附加到旋转的地球。我已经改编了看到 here (sans drag and drop)here 的旋转地球演示,并设法附加了我发现 here 的节点/链接的强制定向布局。

This 是我迄今为止所拥有的。力图出现在南极附近,为跳跃链接道歉我认为这只是一个 css 问题,因为它在我的模拟中正确显示(我现在已经离开了样式表)。

由于我希望节点固定在特定的纬度/经度,我想完全摆脱力模拟。然而,在保留节点和链接的同时删除它的所有尝试都会导致它们完全消失。我还在努力修复它们的位置并将节点覆盖地图图形(您可以看到节点在陆地后面)

总而言之,我想:

  • 移除强制布局但保留节点/链接
  • 在旋转期间修复特定纬度/经度的节点
  • 在地理地图特征之上覆盖节点/链接

我们将非常感谢您就这些方面的任何一点提供帮助。

HTML

<!doctype html>
<html lang="en">
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<div id="vis"></div>
</body>
</html>

脚本

(function (){
  var config = {
    "projection": "Orthographic",
    "clip": true, "friction": 1,
    "linkStrength": 1,
    "linkDistance": 20,
    "charge": 50,
    "gravity": 1,
    "theta": .8 };

  var width = window.innerWidth,
      height = window.innerHeight - 5,
      fill = d3.scale.category20(),
      feature,
      origin = [0, -90],
      velocity = [0.01, 0],
      t0 = Date.now(),
      nodes = [{x: width/2, y: height/2}],
      links = [];

  var projection = d3.geo.orthographic()
      .scale(height/2)
      .translate([(width/2)-125, height/2])
      .clipAngle(config.clip ? 90 : null)

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

  var force = d3.layout.force()
     .linkDistance(config.linkDistance)
     .linkStrength(config.linkStrength)
     .gravity(config.gravity)
     .size([width, height])
     .charge(-config.charge);

  var svg = d3.select("#vis").append("svg")
      .attr("width", width)
      .attr("height", height)
      .call(d3.behavior.drag()
        .origin(function() { var r = projection.rotate(); return {x: 2 * r[0], y: -2 * r[1]}; })
        .on("drag", function() { force.start(); var r = [d3.event.x / 2, -d3.event.y / 2, projection.rotate()[2]]; t0 = Date.now(); origin = r; projection.rotate(r); }))

  for(x=0;x<20;x++){
    source = nodes[~~(Math.random() * nodes.length)]
    target = {x: source.x + Math.random(), y: source.y + Math.random(), group: Math.random()}
    links.push({source: source, target: target})
    nodes.push(target)
  }

  var node = svg.selectAll("path.node")
      .data(nodes)
      .enter().append("path").attr("class", "node")
      .style("fill", function(d) { return fill(d.group); })
      .style("stroke", function(d) { return d3.rgb(fill(d.group)).darker(); })
      .call(force.drag);
  console.log(node)
  var link = svg.selectAll("path.link")
      .data(links)
      .enter().append("path").attr("class", "link")

  force
     .nodes(nodes)
     .links(links)
     .on("tick", tick)
     .start();

  var url = "https://raw.githubusercontent.com/d3/d3.github.com/master/world-110m.v1.json";
  d3.json(url, function(error, topo) {
    if (error) throw error;

    var land = topojson.feature(topo, topo.objects.land);

    svg.append("path")
     .datum(land)
     .attr("class", "land")
     .attr("d", path)

    d3.timer(function() {
      force.start();
      var dt = Date.now() - t0;
      projection.rotate([velocity[0] * dt + origin[0], velocity[1] * dt + origin[1]]);
      svg.selectAll("path")
        .filter(function(d) {
          return d.type == "FeatureCollection";})
        .attr("d", path);
    });
  });

  function tick() {
    node.attr("d", function(d) { var p = path({"type":"Feature","geometry":{"type":"Point","coordinates":[d.x, d.y]}}); return p ? p : 'M 0 0' });
    link.attr("d", function(d) { var p = path({"type":"Feature","geometry":{"type":"LineString","coordinates":[[d.source.x, d.source.y],[d.target.x, d.target.y]]}}); return p ? p : 'M 0 0' });
  }

  function clip(d) {
    return path(circle.clip(d));
  }
})();

【问题讨论】:

  • 移至 D3 v4/5。然后,使用forceXforceY 设置位置。完成。
  • 我假设您已经知道要显示的节点的纬度和经度?
  • 是的,为了在示例中方便,我现在只是随机放置它们

标签: javascript d3.js svg topojson


【解决方案1】:

假设您使用力来添加点和链接,让我们退后一步,让我们删除任何与力相关的东西,没有节点和链接。在这种情况下,都不需要强制布局。让我们从您的地球开始,使用动画和拖动(并在我们使用它的同时移动到 d3v5):

  var width = 500,
      height = 500,
	  t0 = Date.now(),
	  velocity = [0.01, 0],
	  origin = [0, -45];
  
  var projection = d3.geoOrthographic()
      .scale(height/2.1)
      .translate([width/2, height/2])
      .clipAngle(90)

  var path = d3.geoPath()
      .projection(projection);
  
  var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height)
      .call(d3.drag()
      .subject(function() { var r = projection.rotate(); return {x: 2 * r[0], y: -2 * r[1]}; })
      .on("drag", function() { var r = [d3.event.x / 2, -d3.event.y / 2, projection.rotate()[2]]; t0 = Date.now(); origin = r; projection.rotate(r); }))

  d3.json("https://unpkg.com/world-atlas@1/world/110m.json").then(function(topo) {
    var land = topojson.feature(topo, topo.objects.land);
    
    svg.append("path")
     .datum(land)
     .attr("class", "land")
     .attr("d", path);

    d3.timer(function() {
      var dt = Date.now() - t0;
      projection.rotate([velocity[0] * dt + origin[0], velocity[1] * dt + origin[1]]);
      svg.selectAll("path")
         .attr("d", path);
    });

	
  });
<script type="text/javascript" src="https://d3js.org/d3.v5.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>

除了迁移到 v5 之外,我做了一些小的修改以优化 sn-p 视图(例如:大小)或简洁性(例如:硬编码剪辑角度),但代码基本上是相同的减去力/节点/链接

我认为这检查了您的第一个要求“删除强制布局但保留节点/链接”的一半。这也为我们提供了更简单的代码来满足其余的要求。

好的,现在我们有了一张底图,我们可以添加点,然后我们可以添加线。但是,让我们分解一下,首先我们添加点,然后我们添加链接。

添加地理参考点

让我们采用数据格式,我将使用我们要显示的点/节点的字典:

  var points = {
     "Vancouver":[-123,49.25],
     "Tokyo":[139.73,35.68],
     "Honolulu":[-157.86,21.3],
     "London":[0,50.5],
     "Kampala":[32.58,0.3]
  }

由于我们正在处理正交投影,因此明智的做法是使用带有 d3.geoPath 的 geojson 点,因为这将自动裁剪位于地球另一端的那些点。 geojson 点看起来像这样(正如您在小提琴中创建的那样):

{ type: "Point", geometry: [long,lat] }

所以,我们可以得到一个 geojson 点数组:

var geojsonPoints = d3.entries(points).map(function(d) {
    return {type: "Point", coordinates: d.value}
})

d3.entries 在输入对象时返回一个数组。数组中的每一项代表原始对象{key: key, value: value} 的一个键值对,更多信息请参见the docs

现在我们可以将 geojson 点添加到 svg:

svg.selectAll()
  .data(geojsonPoints)
  .enter()
  .append("path")
  .attr("d",path)
  .attr("fill","white")
  .attr("stroke-width",2)
  .attr("stroke","steelblue");

由于这些是点,我们需要设置路径的点半径:

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

最后,由于我删除了您在计时器函数中应用的过滤器,所有路径将在每次旋转时一起更新,这稍微简化了代码。

好吧,总而言之,这给了我们:

var width = 500,
    height = 500,
	  t0 = Date.now(),
	  velocity = [0.01, 0],
	  origin = [0, -45];
    
  var points = {
   "Vancouver":[-123,49.25],
	 "Tokyo":[139.73,35.68],
	 "Honolulu":[-157.86,21.3],
	 "London":[0,50.5],
	 "Kampala":[32.58,0.3]
  }    

  
  var projection = d3.geoOrthographic()
      .scale(height/2.1)
      .translate([width/2, height/2])
      .clipAngle(90)

  var path = d3.geoPath()
      .projection(projection)
      .pointRadius(5);
  
  var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height)
      .call(d3.drag()
      .subject(function() { var r = projection.rotate(); return {x: 2 * r[0], y: -2 * r[1]}; })
      .on("drag", function() { var r = [d3.event.x / 2, -d3.event.y / 2, projection.rotate()[2]]; t0 = Date.now(); origin = r; projection.rotate(r); }))

  d3.json("https://unpkg.com/world-atlas@1/world/110m.json").then(function(topo) {
    var land = topojson.feature(topo, topo.objects.land);
    
    svg.append("path")
     .datum(land)
     .attr("class", "land")
     .attr("d", path);
     
  	var geojsonPoints = d3.entries(points).map(function(d) {
		  return {type: "Point", coordinates: d.value}
	  });
    
	  svg.selectAll(null)
	  .data(geojsonPoints)
	  .enter()
	  .append("path")
	  .attr("d",path)
	  .attr("fill","white")
	  .attr("stroke-width",2)
	  .attr("stroke","steelblue");
   

    d3.timer(function() {
      var dt = Date.now() - t0;
      projection.rotate([velocity[0] * dt + origin[0], velocity[1] * dt + origin[1]]);
      svg.selectAll("path")
         .attr("d", path);
    });

	
  });
<script type="text/javascript" src="https://d3js.org/d3.v5.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>

我们可以附加圆,但这引入了一个新问题:我们需要通过查看当前旋转中心与点之间的角度是否为大于 90 度。因此,为了方便起见,我使用了 geojson 并依靠投影和路径来隐藏地球另一端的这些点。

路径

我更喜欢上述点数格式的原因是它允许我们提供人类可读的链接列表:

var links = [
  { source: "Vancouver",    target: "Tokyo" },
  { source: "Tokyo",        target: "Honolulu" },
  { source: "Honolulu",     target: "Vancouver" },
  { source: "Tokyo",        target: "London" },
  { source: "London",       target: "Kampala" }
]

现在,如上所述,我们需要将其转换为 geojson。 geojson 行看起来像(正如您在小提琴中创建的那样):

{type:"LineString", coordinates: [[long,lat],[long,lat], ... ]

所以,我们可以创建一个 geojson 行数组:

var geojsonLinks = links.map(function(d) {
    return {type: "LineString", coordinates: [points[d.source],points[d.target]] }
})

这利用了点的字典数据结构。

现在您可以像这样附加它们:

svg.selectAll(null)
  .data(geojsonLinks)
  .enter()
  .append("path")
  .attr("d", path)
  .attr("stroke-width", 2)
  .attr("stroke", "steelblue")
  .attr("fill","none")

与积分一样,它们会在每个计时器滴答声中更新:

var width = 500,
    height = 500,
	  t0 = Date.now(),
	  velocity = [0.01, 0],
	  origin = [0, -45];
    
  var points = {
   "Vancouver":[-123,49.25],
	 "Tokyo":[139.73,35.68],
	 "Honolulu":[-157.86,21.3],
	 "London":[0,50.5],
	 "Kampala":[32.58,0.3]
  }    
  
  var links = [
	{ source: "Vancouver",target: "Tokyo" },
    { source: "Tokyo", 		target: "Honolulu" },
	{ source: "Honolulu", target: "Vancouver" },
	{ source: "Tokyo", 		target: "London" },
	{ source: "London",		target: "Kampala" }
  ]  

  
  var projection = d3.geoOrthographic()
      .scale(height/2.1)
      .translate([width/2, height/2])
      .clipAngle(90)

  var path = d3.geoPath()
      .projection(projection)
      .pointRadius(5);
  
  var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height)
      .call(d3.drag()
      .subject(function() { var r = projection.rotate(); return {x: 2 * r[0], y: -2 * r[1]}; })
      .on("drag", function() { var r = [d3.event.x / 2, -d3.event.y / 2, projection.rotate()[2]]; t0 = Date.now(); origin = r; projection.rotate(r); }))

  d3.json("https://unpkg.com/world-atlas@1/world/110m.json").then(function(topo) {
    var land = topojson.feature(topo, topo.objects.land);
    
    svg.append("path")
     .datum(land)
     .attr("class", "land")
     .attr("d", path);
     
  	var geojsonPoints = d3.entries(points).map(function(d) {
		  return {type: "Point", coordinates: d.value}
	  });
    
	  var geojsonLinks = links.map(function(d) {
		  return {type: "LineString", coordinates: [points[d.source],points[d.target]] }
	  })
    
    svg.selectAll(null)
	  .data(geojsonLinks)
	  .enter()
	  .append("path")
	  .attr("d",path)
	  .attr("fill","none")
	  .attr("stroke-width",2)
	  .attr("stroke","steelblue");
    
	  svg.selectAll(null)
	  .data(geojsonPoints)
	  .enter()
	  .append("path")
	  .attr("d",path)
	  .attr("fill","white")
	  .attr("stroke-width",2)
	  .attr("stroke","steelblue");
   

    d3.timer(function() {
      var dt = Date.now() - t0;
      projection.rotate([velocity[0] * dt + origin[0], velocity[1] * dt + origin[1]]);
      svg.selectAll("path")
         .attr("d", path);
    });

	
  });
<script type="text/javascript" src="https://d3js.org/d3.v5.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>

现在请记住,svg 路径的分层是按照它们附加的顺序完成的 - 第一个附加将在第二个附加之后。因此,如果您希望链接位于点之上,只需交换它们附加的顺序即可。您也可以使用 g 组来管理订购 - gs 也是分层的。

【讨论】:

    猜你喜欢
    • 2017-06-12
    • 1970-01-01
    • 1970-01-01
    • 2014-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-31
    • 2013-07-19
    相关资源
    最近更新 更多