Javascript 在d3地理绘图的固定位置附加链接图形

Javascript 在d3地理绘图的固定位置附加链接图形,javascript,d3.js,svg,topojson,Javascript,D3.js,Svg,Topojson,我正在尝试使用d3 geo将链接图形树附加到旋转的球体上。我已经修改了看到和看到的旋转地球演示,并成功地添加了我发现的节点/链接的强制导向布局 是我到目前为止所拥有的一把小提琴。力图出现在南极附近,为跳跃链接道歉我认为这只是一个css问题,因为它在我的模拟中显示正确(我暂时不使用样式表) 由于我希望节点固定在特定的纬度/经度,所以我希望完全取消力模拟。然而,在保留节点和链接的同时删除它的所有尝试都会导致它们完全消失。我还努力修复它们的位置,并将节点覆盖在地图图形上(你可以看到节点位于陆地的后面)

我正在尝试使用d3 geo将链接图形树附加到旋转的球体上。我已经修改了看到和看到的旋转地球演示,并成功地添加了我发现的节点/链接的强制导向布局

是我到目前为止所拥有的一把小提琴。力图出现在南极附近,为跳跃链接道歉我认为这只是一个css问题,因为它在我的模拟中显示正确(我暂时不使用样式表)

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

总而言之,我想:

  • 删除强制布局,但保留节点/链接
  • 在旋转期间将节点固定在特定的纬度/经度
  • 在地理地图要素顶部覆盖节点/链接
如能在上述任何一点上提供帮助,将不胜感激

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));
  }
})();
(函数(){
变量配置={
“投影”:“正交”,
“clip”:正确,“摩擦力”:1,
“联系强度”:1,
“链接距离”:20,
“费用”:50,
“重力”:1,
“θ”:.8};
变量宽度=window.innerWidth,
高度=窗内高度-5,
fill=d3.scale.category20(),
特写,
原点=[0,-90],
速度=[0.01,0],
t0=日期。现在(),
节点=[{x:width/2,y:height/2}],
链接=[];
var projection=d3.geo.orthographic()
.比例(高度/2)
.translate([(宽度/2)-125,高度/2])
.clipAngle(config.clip?90:null)
var path=d3.geo.path()
.投影(投影);
var-force=d3.layout.force()
.linkDistance(配置.linkDistance)
.linkStrength(config.linkStrength)
.gravity(配置重力)
.尺寸([宽度、高度])
.charge(-config.charge);
var svg=d3.选择(#vis”).追加(“svg”)
.attr(“宽度”,宽度)
.attr(“高度”,高度)
.call(d3.behavior.drag())
.origin(函数(){var r=projection.rotate();返回{x:2*r[0],y:-2*r[1]};})
.on(“拖动”,函数(){force.start();var r=[d3.event.x/2,-d3.event.y/2,projection.rotate()[2]];t0=Date.now();origin=r;projection.rotate(r);})

对于(x=0;x假设您使用了力,以便可以添加点和链接,让我们后退一步,让我们删除任何与力相关的、没有节点和链接的内容。在这种情况下,这两种情况都不需要使用力布局。让我们从地球仪开始,通过动画和拖动(并在进行此操作时移动到d3v5):

var宽度=500,
高度=500,
t0=日期。现在(),
速度=[0.01,0],
原点=[0,-45];
var projection=d3.geoporthographic()
.比例尺(高度/2.1)
.translate([宽度/2,高度/2])
clipAngle先生(90)
var path=d3.geoPath()
.投影(投影);
var svg=d3.选择(“正文”).追加(“svg”)
.attr(“宽度”,宽度)
.attr(“高度”,高度)
.call(d3.drag()
.subject(函数(){var r=projection.rotate();返回{x:2*r[0],y:-2*r[1]};})
.on(“拖动”,函数(){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”)。然后(函数(topo){
var land=topojson.feature(topo,topo.objects.land);
追加(“路径”)
.基准(土地)
.attr(“类别”、“土地”)
.attr(“d”,路径);
d3.定时器(函数(){
var dt=Date.now()-t0;
投影。旋转([速度[0]*dt+原点[0],速度[1]*dt+原点[1]]);
svg.selectAll(“路径”)
.attr(“d”,路径);
});
});


移动到D3 v4/5。然后,使用
forceX
forceY
设置位置。完成。我假设您已经知道要显示的节点的纬度和经度?是的,为了在示例中方便起见,我现在只是随机放置它们
(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));
  }
})();