定位圆和箭头

时间:2018-06-12 23:28:17

标签: javascript d3.js svg

我试图移动箭头,使它们从边缘而不是圆心开始。查看从中心开始的示例: screenshot

更改第111-112行
  // x and y distances from center to outside edge of target node
  var offsetX = (diffX * d.target.radius) / pathLength;
  var offsetY = (diffY * d.target.radius) / pathLength;

为:

  // x and y distances from center to outside edge of target node
  var offsetX = (diffX * 2*d.target.radius) / pathLength;
  var offsetY = (diffY * 2*d.target.radius) / pathLength;

这应该将箭头缩短到正确的长度。结果如下: enter image description here

现在,如果我们可以将箭头沿着路径移动到边缘......这就是我认为可以使用以下代码执行的操作。

我添加103-108以找到相对x,y来移动起点。

  // Calculates angle of a right-angle triangle in radians
  var rad=Math.atan(diffY, diffX);    // calc angle

  // relative move from center of circle to edge (along arrow)
  var relX = d.target.radius * Math.cos(rad);    // relX is ajecent side
  var relY = d.target.radius * Math.sin(rad);    // relY is opposite side

然后我交换第114/115行来改变svg的起点:

  // return "M" + (d.source.x + relX) + "," + (d.source.y + relY) + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
  return "M" + (d.source.x ) + "," + (d.source.y) + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);

为:

  return "M" + (d.source.x + relX) + "," + (d.source.y + relY) + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
  // return "M" + (d.source.x ) + "," + (d.source.y) + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);

结果是: enter image description here

我以为自己走在正确的轨道上......我没有经验,希望有人可以提供帮助。也许它是对我起作用的力量。 这是一个Fiddle来测试

1 个答案:

答案 0 :(得分:3)

使用Math.atan2()代替Math.atan()

var rad = Math.atan2(diffY, diffX);

并删除offsetXoffsetY,因为您已经拥有relXrelY(除非您打算使用不同大小的圈子):

return "M" + (d.source.x + relX) + "," + (d.source.y + relY) + 
    "L" + (d.target.x - relX) + "," + (d.target.y - relY);

以下是包含这些更改的代码:

var dataset = {
  "nodes": [{
      "id": "192.168.10.140",
      "type": "attacker",
      "value": 144
    }, {
      "id": "192.168.10.127",
      "type": "deception",
      "value": 65
    },
    {
      "id": "192.168.10.151",
      "type": "attacker",
      "value": 72
    }, {
      "id": "192.168.10.9",
      "type": "deception",
      "value": 62
    },
    {
      "id": "192.168.10.162",
      "type": "deception",
      "value": 48
    }, {
      "id": "192.168.10.5",
      "type": "deception",
      "value": 18
    },
    {
      "id": "192.168.10.7",
      "type": "deception",
      "value": 18
    }, {
      "id": "192.168.10.121",
      "type": "deception",
      "value": 5
    },
    {
      "id": "192.168.10.1",
      "type": "attacker",
      "value": 7
    }, {
      "id": "192.168.10.105",
      "type": "deception",
      "value": 3
    },
    {
      "id": "192.168.10.125",
      "type": "deception",
      "value": 3
    }, {
      "id": "192.168.10.131",
      "type": "deception",
      "value": 1
    }
  ],
  "edges": [{
      "id": 0,
      "from": "192.168.10.127",
      "valuea": 65,
      "value": 65,
      "to": "192.168.10.140"
    },
    {
      "id": 0,
      "from": "192.168.10.9",
      "valuea": 62,
      "value": 48,
      "to": "192.168.10.151"
    },
    {
      "id": 0,
      "from": "192.168.10.9",
      "valuea": 62,
      "value": 14,
      "to": "192.168.10.140"
    },
    {
      "id": 0,
      "from": "192.168.10.162",
      "valuea": 48,
      "value": 48,
      "to": "192.168.10.140"
    },
    {
      "id": 0,
      "from": "192.168.10.5",
      "valuea": 18,
      "value": 12,
      "to": "192.168.10.140"
    },
    {
      "id": 0,
      "from": "192.168.10.5",
      "valuea": 18,
      "value": 6,
      "to": "192.168.10.151"
    },
    {
      "id": 0,
      "from": "192.168.10.7",
      "valuea": 18,
      "value": 18,
      "to": "192.168.10.151"
    },
    {
      "id": 0,
      "from": "192.168.10.121",
      "valuea": 5,
      "value": 5,
      "to": "192.168.10.140"
    },
    {
      "id": 0,
      "from": "192.168.10.105",
      "valuea": 3,
      "value": 3,
      "to": "192.168.10.1"
    },
    {
      "id": 0,
      "from": "192.168.10.125",
      "valuea": 3,
      "value": 3,
      "to": "192.168.10.1"
    },
    {
      "id": 0,
      "from": "192.168.10.131",
      "valuea": 1,
      "value": 1,
      "to": "192.168.10.1"
    }
  ]
}


// Compute the distinct nodes from the links.


var height = 0 + window.innerHeight;
var width = 0 + window.innerWidth;

var svg = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height)
  .style("background-color", "#222"); // same as dark Kibana background

var force = d3.layout.force()
  .size([width, height])
  .gravity(0.2)
  .linkDistance(height / 6)
  .charge(function(node) {
    if (node.type !== 'attacker') return -2000;
    return -30;
  });

// build the arrow.
svg.append("svg:defs").selectAll("marker")
  .data(["end"]) // Different link/path types can be defined here
  .enter().append("svg:marker") // This section adds in the arrows
  .attr("id", function(d) {
    return d;
  })
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 10)
  .attr("refY", 0)
  .attr("markerWidth", 9)
  .attr("markerHeight", 5)
  //.attr("orient", "180")
  .attr("orient", "auto") // orig
  //.attr("orient", "auto-start-reverse")
  .attr("class", "arrow")
  .append("svg:path")
  .attr("d", "M0,-5L10,0L0,5");


var json = dataset;
var edges = [];

json.edges.forEach(function(e) {
  var sourceNode = json.nodes.filter(function(n) {
      return n.id === e.from;
    })[0],
    targetNode = json.nodes.filter(function(n) {
      return n.id === e.to;
    })[0];

  edges.push({
    source: sourceNode,
    target: targetNode,
    value: e.Value
  });
});


for (var i = 0; i < json.nodes.length; i++) {
  json.nodes[i].collapsing = 0;
  json.nodes[i].collapsed = false;
}

var link = svg.selectAll(".link");
var node = svg.selectAll(".node");

force.on("tick", function() {
  // make sure the nodes do not overlap the arrows
  link.attr("d", function(d) {

    // Total difference in x and y from source to target
    var diffX = d.target.x - d.source.x;
    var diffY = d.target.y - d.source.y;

    // Length of path from center of source node to center of target node
    var pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));

    // Calculates angle of a right-angle triangle in radians
    var rad = Math.atan2(diffY, diffX); // calc angle

    // relative move from center of circle to edge (along arrow)
    var relX = d.target.radius * Math.cos(rad); // relX is ajecent side
    var relY = d.target.radius * Math.sin(rad); // relY is opposite side

    return "M" + (d.source.x + relX) + "," + (d.source.y + relY) + "L" + (d.target.x - relX) + "," + (d.target.y - relY);
    //return "M" + (d.source.x ) + "," + (d.source.y) + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
  });

  node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  });
});

update();

function update() {
  var nodes = json.nodes.filter(function(d) {
    return d.collapsing == 0;
  });

  var links = edges.filter(function(d) {
    return d.source.collapsing == 0 && d.target.collapsing == 0;
  });

  force
    .nodes(nodes)
    .links(links)
    .start();

  link = link.data(links)

  link.exit().remove();

  link.enter().append("path")
    .attr("class", "link")
    .attr("marker-end", "url(#end)");

  node = node.data(nodes);

  node.exit().remove();

  node.enter().append("g")
    .attr("class", function(d) {
      return "node " + d.type
    });

  node.append("circle")
    .attr("class", "circle")
    .attr("fill", getcol)
    .style("opacity", .2)
    .attr("r", function(d) {
      d.radius = 35;
      return d.radius;
    }); // return a radius for path to use 

  function getcol(d) {
    if (d.type === "deception") return "#F60";
    return "#666666";
  }

  node.append("text")
    .text(function(d) {
      return d.id;
    })
    .style("font-size", adaptLabelFontSize)
    // .style("font-size", function(d) { return Math.min(2 * d.radius, (2 * d.radius - 8) / this.getComputedTextLength() * 24) + "px"; })
    .attr("text-anchor", "middle")
    .attr("class", "text")
    .attr("dy", ".35em");

  function adaptLabelFontSize(d) {
    var xPadding, diameter, labelAvailableWidth, labelWidth;

    xPadding = 8;
    diameter = 2 * d.radius;
    labelAvailableWidth = diameter - xPadding;

    labelWidth = this.getComputedTextLength();

    // There is enough space for the label so leave it as is.
    if (labelWidth < labelAvailableWidth) {
      return null;
    }

    return (labelAvailableWidth / labelWidth) + 'em';
  }


  // On node hover, examine the links to see if their
  // source or target properties match the hovered node.
  node.on('mouseover', function(d) {
    link.attr('class', function(l) {
      if (d === l.source || d === l.target)
        return "link active";
      else
        return "link inactive";
    });
  });

  // Set the stroke width back to normal when mouse leaves the node.
  node.on('mouseout', function() {
      link.attr('class', "link");
    })
    .on('click', click);

  function click(d) {
    if (!d3.event.defaultPrevented) {
      var inc = d.collapsed ? -1 : 1;
      recurse(d);

      function recurse(sourceNode) {
        //check if link is from this node, and if so, collapse
        edges.forEach(function(l) {
          if (l.source.id === sourceNode.id) {
            l.target.collapsing += inc;
            recurse(l.target);
          }
        });
      }
      d.collapsed = !d.collapsed;
    }
    update();
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<meta http-equiv="content-type" content="application/json" ; charset="UTF-8" />
<html>

  <head>
    <title>Rolf-test</title>
  </head>

  <style>
    .node {
      cursor: pointer;
      font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
      font-weight: 300;
    }

    .node .text {
      fill: black;
      font-size: 8px;
    }

    .ORG .circle {
      fill: #1d3649;
    }

    .EMR .circle {
      fill: #B2D0F5;
      stroke: #5596e6;
      stroke-dasharray: 3px, 3px;
      opacity: .5;
    }

    .EMR .circle:hover {
      fill: #5596e6;
    }

    .link {
      fill: none;
      stroke: #eee;
      stroke-width: 1.5px;
      font: 10px sans-serif;
    }

    .link.active {
      stroke: #ddd;
      stroke-width: 2;
    }

    .arrow {
      fill: #808080;
    }

    .arrow.active {
      stroke-width: 0 !important;
    }

  </style>

  <body>
  </body>

</html>

这是更新的JSFiddle,比SO片段更好看:http://jsfiddle.net/p8s0o4ha/1/