Force chart d3.js inside a triangle

两盒软妹~` 提交于 2019-12-03 16:21:36
Mark

There's really two parts to this question. First, you need your force layout to converge on a different foci then the default width/2, height/2. This new foci should be the centroid of the triangle. Luckily d3 has a helper method to compute this for polygons. Second, now that we are converging on the centroid of the triangle, how do we bound our nodes inside that triangle. The method I use below calculates the intersections between lines drawn from the centroid to the node and the line of the edge of the triangle (intersection calculation from this question). No intersections on all 3 edges means the circle is in the triangle, and an intersection on any edge means we need to bring the circle onto that edge.

UPDATE -- generalized the code a bit and converted it to a block here to work with N sided polygons.

Let's get to the code:

<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>

<body>
  <script>
    var width = 500,
        height = 500,
        radius = 10;

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

    // our polygon
    var trianglePoints = [
      [250, 0],
      [12, 173],
      [250, 250]
    ];

    var tr = svg.append("polygon") // attach a polygon
      .style("stroke", "black") // colour the line
      .style("fill", "none") // remove any fill colour
      .attr("points", trianglePoints.join(" ")); // x,y points 

    var group = svg.append("g");

    var nodes = d3.range(20).map(function(d){ return {} }),
      links = [],
      cent = d3.geom.polygon(trianglePoints).centroid();

    var force = d3.layout.force()
      .size([width, height])
      .nodes(nodes)
      .links(links);

    force.linkDistance(100);
    force.charge(-200);

    var link = group.selectAll('.link')
      .data(links)
      .enter().append('line')
      .attr('class', 'link');

    var node = group.selectAll('.node')
      .data(nodes)
      .enter().append('circle')
      .attr('class', 'node')
      .call(force.drag); //<-- make them draggable to test

    force.on('tick', function(e) {

      node.attr('r', radius)
        .attr('transform', function(d) {

          // change focus to the center of the triangle
          var x = (d.x - (width / 2 - cent[0])),
            y = (d.y - (height / 2 - cent[1]));

          // test intersections on all 3 edges
          var i = 
            getLineIntersection(trianglePoints[0][0], trianglePoints[0][1],
              trianglePoints[1][0], trianglePoints[1][1], cent[0], cent[1], x, y) ||
            getLineIntersection(trianglePoints[1][0], trianglePoints[1][1],
              trianglePoints[2][0], trianglePoints[2][1], cent[0], cent[1], x, y) ||
            getLineIntersection(trianglePoints[0][0], trianglePoints[0][1],
                trianglePoints[2][0], trianglePoints[2][1], cent[0], cent[1], x, y) ||
            false;
          
          // set it to intersection
          if (i){
            x = i.x;
            y = i.y;
          }

          return "translate(" + x + "," + y + ")";

        });

      link.attr('x1', function(d) {
          return d.source.x;
        })
        .attr('y1', function(d) {
          return d.source.y;
        })
        .attr('x2', function(d) {
          return d.target.x;
        })
        .attr('y2', function(d) {
          return d.target.y;
        });

    });

    // from https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
    function getLineIntersection(p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y) {
      var s1_x, s1_y, s2_x, s2_y;
      s1_x = p1_x - p0_x;
      s1_y = p1_y - p0_y;
      s2_x = p3_x - p2_x;
      s2_y = p3_y - p2_y;
      var s, t;
      s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
      t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);

      if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
        var intX = p0_x + (t * s1_x);
        var intY = p0_y + (t * s1_y);
        return {
          x: intX,
          y: intY
        };
      }
      return false;
    }

    force.start();
  </script>
</body>

</html>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!