更改d3强制布局使用的数据

时间:2017-03-20 19:44:32

标签: javascript d3.js 3d

我正在使用Christopher Manning's 3D force layout

的实现

Here是我正在编辑的JSFiddle的链接。我的目标是创建一个三维布局,其中节点和链接是从外部数据创建的。我的问题是,我将在哪里开始将此布局转换为从外部文件获取数据的布局?目前,节点已创建:

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

我已经创建了以这种方式设置的力导向布局,但是一旦引入了第三个维度,我就会陷入困境。这是特别困难的,因为我必须改变多少区域才能获得理想的结果。到目前为止,我所尝试的一切都导致项目破裂。我仍在学习d3,所以任何输入都会有所帮助。谢谢!

1 个答案:

答案 0 :(得分:1)

我对回答这个问题犹豫不决,因为你没有问过任何具体问题。但这里是链接代码的快速重构,它从JSON API外部读取数据。我真的没有在代码中看到任何使数据与传统的力布局不同的东西。像这样:

{
  "nodes": [{
    "x": 100,
    "y": 100,
    "group": 0.5
  },{
    "x": 200,
    "y": 200,
    "group": 0.2
  },{
    "x": 300,
    "y": 300,
    "group": 0.6
  },{
    "x": 400,
    "y": 400,
    "group": 0.1
  },{
    "x": 500,
    "y": 500,
    "group": 0.7
  }],
  "links": [{
    "source": 0,
    "target": 1
  },{
    "source": 1,
    "target": 2
  },{
    "source": 2,
    "target": 3
  },{
    "source": 3,
    "target": 4
  }]
}

其他更改只是为了修复变量范围,因为在node回调中创建了linkd3.json

&#13;
&#13;
<!DOCTYPE html>
<html>

<head>
  <title>Spherical Force-Directed Layout</title>
  <script src="http://d3js.org/d3.v3.min.js"></script>
  <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
  <!--<script src="/js/d3.v3.min.js"></script>-->
  <!--<script src="/js/dat-gui/build/dat.gui.js"></script> -->
  <style type="text/css">
    body {
      padding: 0;
      margin: 0;
    }
    
    path.node {
      stroke-width: 1.5px;
    }
    
    path.link {
      stroke: #999;
      fill-opacity: 0
    }
  </style>
</head>

<body>
  <script type="text/javascript">
    var projections = {
      "Albers": d3.geo.albers(),
      "Azimuthal Equal Area": d3.geo.azimuthalEqualArea(),
      "Azimuthal Eqidistant": d3.geo.azimuthalEquidistant(),
      "Conic Conformal": d3.geo.conicConformal(),
      "Conic Equal Area": d3.geo.conicEqualArea(),
      "Conic Equidistant": d3.geo.conicEquidistant(),
      "Eqirectangular": d3.geo.equirectangular(),
      "Gnomonic": d3.geo.gnomonic(),
      "Mercator": d3.geo.mercator(),
      "Orthographic": d3.geo.orthographic(),
      "Stereographic": d3.geo.stereographic(),
      "Transverse Mercator": d3.geo.transverseMercator(),
    };
    var config = {
      "projection": "Orthographic",
      "clip": true,
      "friction": .9,
      "linkStrength": 1,
      "linkDistance": 20,
      "charge": 30,
      "gravity": .1,
      "theta": .8
    };
    var gui = new dat.GUI();
    //var projectionChanger = gui.add(config, "projection", ['equalarea', 'equidistant', 'gnomonic', 'orthographic', 'stereographic', 'rectangular']);
    var projectionChanger = gui.add(config, "projection", Object.keys(projections));
    //http://stackoverflow.com/a/3417242
    function wrapIndex(i, i_max) {
      return ((i % i_max) + i_max) % i_max;
    }
    projectionChanger.onChange(function(value) {
      projection = projections[value]
        .scale(height / 2)
        .translate([(width / 2) - 125, height / 2])
        .clipAngle(config["clip"] ? 90 : null)

      path.projection(projections[value])
      return
      if (value == 'rectangular') {
        path = d3.geo.path().projection(function(coordinates) {
          console.log(coordinates[0], coordinates[1])
          return [
            wrapIndex(coordinates[0], width),
            wrapIndex(coordinates[1], height),
          ];
        });
        config['clip'] = false
      } else {
        projection.mode(value)
        path = d3.geo.path().projection(projection)
      }

      force.start()
    });

    var clipChanger = gui.add(config, "clip").listen();
    clipChanger.onChange(function(value) {
      projection.clipAngle(value ? 90 : null)
      force.start()
    });

    var fl = gui.addFolder('Force Layout');
    fl.open()

    var frictionChanger = fl.add(config, "friction", 0, 1);
    frictionChanger.onChange(function(value) {
      force.friction(value)
      force.start()
    });

    var linkDistanceChanger = fl.add(config, "linkDistance", 0, 400);
    linkDistanceChanger.onChange(function(value) {
      force.linkDistance(value)
      force.start()
    });

    var linkStrengthChanger = fl.add(config, "linkStrength", 0, 1);
    linkStrengthChanger.onChange(function(value) {
      force.linkStrength(value)
      force.start()
    });

    var chargeChanger = fl.add(config, "charge", 0, 500);
    chargeChanger.onChange(function(value) {
      force.charge(-value)
      force.start()
    });

    var gravityChanger = fl.add(config, "gravity", 0, 1);
    gravityChanger.onChange(function(value) {
      force.gravity(value)
      force.start()
    });

    var thetaChanger = fl.add(config, "theta", 0, 1);
    thetaChanger.onChange(function(value) {
      force.theta(value)
      force.start()
    });

    var width = window.innerWidth,
      height = window.innerHeight - 5,
      fill = d3.scale.category20(),
      nodes = [{
        x: width / 2,
        y: height / 2
      }],
      links = [];

    var projection = projections[config["projection"]]
      .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("body").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);
        }))


    var node, link;
    d3.json('https://jsonblob.com/api/547d877a-0e4e-11e7-a0ba-f11a5a82ba29', function(e, data) {
      if (e) console.warn(e);

      link = svg.selectAll("path.link")
        .data(data.links)
        .enter().append("path").attr("class", "link")

      node = svg.selectAll("path.node")
        .data(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);

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

    });
    
    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'
      });
    }
  </script>
</body>

</html>
&#13;
&#13;
&#13;