强制图表d3.js在三角形内

时间:2016-04-30 00:39:12

标签: javascript d3.js svg polygon force-layout

我正在研究d3.js力图,我有一个问题。 是否可以在具有某些坐标的三角形内制作力图?

这是我的代码:

var width = 500;
var height = 500;
//margin
var marginLeft = 10;
var marginTop = 10;
var marginRight = 10;
var marginBottom = 10;
var margin = { left: marginLeft , top: marginTop, right: marginRight, bottom: marginBottom};

//size of canvas
var innerWidth = width - margin.left - margin.right;
var innerHeight = height - margin.top - margin.bottom;

var radius = 10;

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

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

var group = svg.append("g")
    .attr("transform",  "translate(" + margin.left + "," + margin.top + ")");

var graph = {
    "nodes": [  { "x": 0, "y": 0 },
                { "x": 100,  "y":  100 },
                { "x": 500, "y": 500 },
                     { "x": 300, "y": 0 },
                   { "x": 300, "y": 0 },
                  { "x": 100,  "y":  100 },
                { "x": 500, "y": 500 },
                     { "x": 300, "y": 0 },
                   { "x": 300, "y": 0 },
                  { "x": 100,  "y":  100 },
                { "x": 500, "y": 500 },
                     { "x": 300, "y": 0 },
                   { "x": 300, "y": 0 },
                  { "x": 100,  "y":  100 },
                { "x": 500, "y": 500 },
                     { "x": 300, "y": 0 },
                   { "x": 300, "y": 0 },
                  { "x": 100,  "y":  100 },
                { "x": 500, "y": 500 },
                     { "x": 300, "y": 0 },
                   { "x": 300, "y": 0 },
            ],
    "links": []
    };


var nodes = graph.nodes,
    links = graph.links;

var force = d3.layout.force()
    .size([innerWidth, innerHeight])
    .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');

force.on('tick', function() {
    node.attr('r', radius)
        .attr('cx', function(d) { return d.x; })
        .attr('cy', function(d) { return d.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; });

});


force.start();

完整代码在此处:http://codepen.io/Balzzac/pen/vGWXdQ。现在它是" group"里面的力图,我需要让它在三角形里面" tr"所以没有一个节点在我的三角形的边界之外。

感谢您的帮助!

PS抱歉我的英文=)

1 个答案:

答案 0 :(得分:3)

这个问题确实有两个部分。首先,您需要使用力布局来收敛于默认的width/2, height/2不同的焦点。这个新的焦点应该是三角形的质心。幸运的是d3a helper method来计算多边形。其次,既然我们正在收敛三角形的质心,我们如何将节点绑定在该三角形内。我在下面使用的方法计算从质心到节点绘制的线与三角形边缘线之间的交点(从此question计算的交点)。所有3条边上没有交点意味着圆形在三角形中,任何边上的交点意味着我们需要将圆圈带到该边缘。

更新 - 稍微概括了代码并将其转换为block here以使用N边多边形。

让我们来看看代码:

<!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>