2017-05-07 66 views
0

我有兩個問題。首先我使用這個例子(http://bl.ocks.org/curran/9b73eb564c1c8a3d8f3ab207de364bf4)創建d3圖,並嘗試創建selfnode鏈接到同一節點,但我不知道如何。使用d3.forcesimulation D3的自我節點鏈接()

var width = 960, 
 
      height = 500, 
 
      nodeSize = 20, 
 
      arrowWidth = 8, 
 
      svg = d3.select("body") 
 
      .append("svg") 
 
      .attr("width", width) 
 
      .attr("height", height) 
 
      linkG = svg.append("g") 
 
      nodeG = svg.append("g") 
 
      // Arrows are separate from link lines so that their size 
 
      // can be controlled independently from the link lines. 
 
      arrowG = svg.append("g"); 
 
     
 
     // Arrowhead setup. 
 
     // Draws from Mobile Patent Suits example: 
 
     // http://bl.ocks.org/mbostock/1153292 
 
     svg.append("defs") 
 
     .append("marker") 
 
      .attr("id", "arrow") 
 
      .attr("orient", "auto") 
 
      .attr("preserveAspectRatio", "none") 
 
      // See also http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute 
 
      //.attr("viewBox", "0 -" + arrowWidth + " 10 " + (2 * arrowWidth)) 
 
      .attr("viewBox", "0 -5 10 10") 
 
      // See also http://www.w3.org/TR/SVG/painting.html#MarkerElementRefXAttribute 
 
      .attr("refX", 10) 
 
      .attr("refY", 0) 
 
      .attr("markerWidth", 10) 
 
      .attr("markerHeight", arrowWidth) 
 
     .append("path") 
 
      .attr("d", "M0,-5L10,0L0,5"); 
 
     
 
     var simulation = d3.forceSimulation() 
 
     .force("link", d3.forceLink()) 
 
     .force("charge", d3.forceManyBody()) 
 
     .force("center", d3.forceCenter(width/2, height/2)); 
 
     
 
     simulation.force("link") 
 
     .distance(140); 
 
     
 
     var drag = d3.drag() 
 
     .on("start", dragstarted) 
 
     .on("drag", dragged) 
 
     .on("end", dragended); 
 
     
 
     function dragstarted(d) { 
 
     if (!d3.event.active) simulation.alphaTarget(0.3).restart() 
 
     simulation.fix(d); 
 
     } 
 
     
 
     function dragged(d) { 
 
     simulation.fix(d, d3.event.x, d3.event.y); 
 
     } 
 
     
 
     function dragended(d) { 
 
     if (!d3.event.active) simulation.alphaTarget(0); 
 
     } 
 
     
 
     function render(graph){ 
 
     
 
     var link = linkG.selectAll("line").data(graph.links); 
 
     var linkEnter = link.enter().append("line") 
 
      .attr("class", "link-line"); 
 
     link.exit().remove(); 
 
     link = link.merge(linkEnter); 
 
     
 
     var arrow = arrowG.selectAll("line").data(graph.links); 
 
     var arrowEnter = arrow.enter().append("line") 
 
      .attr("class", "arrow") 
 
      .attr("marker-end", "url(#arrow)"); 
 
     arrow.exit().remove(); 
 
     arrow = arrow.merge(arrowEnter); 
 
     
 
     var node = nodeG.selectAll("g").data(graph.nodes); 
 
     var nodeEnter = node.enter().append("g").call(drag); 
 
     node.exit().remove(); 
 
     
 
     nodeEnter.append("rect") 
 
      .attr("class", "node-rect") 
 
      .attr("y", -nodeSize) 
 
      .attr("height", nodeSize * 2) 
 
      .attr("rx", nodeSize) 
 
      .attr("ry", nodeSize) 
 
      .on("click", function (d){ 
 
      simulation.unfix(d); 
 
      }); 
 
     
 
     nodeEnter.append("text") 
 
      .attr("class", "node-text"); 
 
     
 
     node = node.merge(nodeEnter); 
 
     
 
     node.select(".node-text") 
 
      .text(function (d){ return d.name; }) 
 
      .each(function (d) { 
 
      
 
      var circleWidth = nodeSize * 2, 
 
       textLength = this.getComputedTextLength(), 
 
       textWidth = textLength + nodeSize; 
 
      
 
      if(circleWidth > textWidth) { 
 
       d.isCircle = true; 
 
       d.rectX = -nodeSize; 
 
       d.rectWidth = circleWidth; 
 
      } else { 
 
       d.isCircle = false; 
 
       d.rectX = -(textLength + nodeSize)/2; 
 
       d.rectWidth = textWidth; 
 
       d.textLength = textLength; 
 
      } 
 
      }); 
 
     
 
     node.select(".node-rect") 
 
      .attr("x", function(d) { return d.rectX; }) 
 
      .attr("width", function(d) { return d.rectWidth; }); 
 
     
 
     simulation.force("link").links(graph.links); 
 
     
 
     simulation.nodes(graph.nodes).on("tick", function(){ 
 
      
 
      graph.nodes.forEach(function (d) { 
 
      if(d.isCircle){ 
 
       d.leftX = d.rightX = d.x; 
 
      } else { 
 
       d.leftX = d.x - d.textLength/2 + nodeSize/2; 
 
       d.rightX = d.x + d.textLength/2 - nodeSize/2; 
 
      } 
 
      }); 
 
      
 
      link.call(edge); 
 
      arrow.call(edge); 
 
      
 
      node.attr("transform", function(d) {  
 
      return "translate(" + d.x + "," + d.y + ")"; 
 
      }); 
 
     }); 
 
     } 
 
     
 
     // Sets the (x1, y1, x2, y2) line properties for graph edges. 
 
     function edge(selection){ 
 
     selection 
 
      .each(function (d) { 
 
      var sourceX, targetX, midX, dy, dy, angle; 
 
      
 
      // This mess makes the arrows exactly perfect. 
 
      if(d.source.rightX < d.target.leftX){ 
 
       sourceX = d.source.rightX; 
 
       targetX = d.target.leftX; 
 
      } else if(d.target.rightX < d.source.leftX){ 
 
       targetX = d.target.rightX; 
 
       sourceX = d.source.leftX; 
 
      } else if (d.target.isCircle) { 
 
       targetX = sourceX = d.target.x; 
 
      } else if (d.source.isCircle) { 
 
       targetX = sourceX = d.source.x; 
 
      } else { 
 
       midX = (d.source.x + d.target.x)/2; 
 
       if(midX > d.target.rightX){ 
 
       midX = d.target.rightX; 
 
       } else if(midX > d.source.rightX){ 
 
       midX = d.source.rightX; 
 
       } else if(midX < d.target.leftX){ 
 
       midX = d.target.leftX; 
 
       } else if(midX < d.source.leftX){ 
 
       midX = d.source.leftX; 
 
       } 
 
       targetX = sourceX = midX; 
 
      } 
 
      
 
      dx = targetX - sourceX; 
 
      dy = d.target.y - d.source.y; 
 
      angle = Math.atan2(dx, dy); 
 
      
 
      // Compute the line endpoint such that the arrow 
 
      // is touching the edge of the node rectangle perfectly. 
 
      d.sourceX = sourceX + Math.sin(angle) * nodeSize; 
 
      d.targetX = targetX - Math.sin(angle) * nodeSize; 
 
      d.sourceY = d.source.y + Math.cos(angle) * nodeSize; 
 
      d.targetY = d.target.y - Math.cos(angle) * nodeSize; 
 
      }) 
 
      .attr("x1", function(d) { return d.sourceX; }) 
 
      .attr("y1", function(d) { return d.sourceY; }) 
 
      .attr("x2", function(d) { return d.targetX; }) 
 
      .attr("y2", function(d) { return d.targetY; }); 
 
     } 
 
     
 
     var graph = {"nodes":["socks","shoes","shirt","belt","tie","jacket","pants","underpants"],"links":[{"source":0,"target":1},{"source":2,"target":3},{"source":2,"target":4},{"source":3,"target":5},{"source":4,"target":5},{"source":6,"target":1},{"source":6,"target":3},{"source":7,"target":6}]}; 
 
     
 
     graph.nodes = graph.nodes.map(function (d){ 
 
     return { name: d }; 
 
     }); 
 
     graph.links = graph.links.map(function (d){ 
 
     d.source = graph.nodes[d.source]; 
 
     d.target = graph.nodes[d.target]; 
 
     return d; 
 
     }); 
 
     render(graph);
.node-rect { 
 
     fill: white; 
 
     stroke: black; 
 
     stroke-width: 1.5; 
 
     cursor: move; 
 
     } 
 
     
 
     .node-text { 
 
     font-family: "Roboto", sans-serif; 
 
     font-size: 2em; 
 
     text-anchor: middle; 
 
     alignment-baseline: middle; 
 
     pointer-events: none; 
 
     /* Disable text selection 
 
      from http://stackoverflow.com/questions/826782/css-rule-to-disable-text-selection-highlighting */ 
 
     -webkit-touch-callout: none; /* iOS Safari */ 
 
     -webkit-user-select: none; /* Chrome/Safari/Opera */ 
 
     -khtml-user-select: none; /* Konqueror */ 
 
     -moz-user-select: none;  /* Firefox */ 
 
     -ms-user-select: none;  /* Internet Explorer/Edge */ 
 
     user-select: none; 
 
     } 
 
     
 
     .link-line { 
 
     stroke: black; 
 
     stroke-width: 1.5; 
 
     } 
 
     
 
     /* Set the arrowhead size. */ 
 
     .arrow { 
 
     stroke-width: 1.5px; 
 
     }
<script src="https://d3js.org/d3.v4.0.0-alpha.40.min.js"></script> 
 
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet" type="text/css">

我想是這樣的https://jsfiddle.net/dhxc6fr3/(selfnode具有紅色) 或(這是第二個問題)是任何方式對所有的畫布更改怪異的運動元素(節點和鏈接)區?我的意思是,該圖將在一個地方沒有這種奇怪的運動(我思故是,座標實時計算)產生

對不起我的英文不好

回答

0

你的第一個問題是,你的工作示例使用line元素繪製鏈接,而您的第二個示例(繪製自節點鏈接)正在使用路徑。您需要先修改工作示例才能使用path(因爲您無法使用line繪製曲線)。然後,這是相當瑣碎,檢查是否與if (d.source === d.target)自節點,並得出相應的鏈接:從試驗和錯誤

if (d.source === d.target){ 
return "M" + sourceX + "," + sourceY + "A" + 40 + "," + 40 + " " + -45 + "," + 1 + "," + 0 + " " + (sourceX - 1) + "," + (sourceY + 1); 
} else { 
    return "M" + sourceX + "," + sourceY + "L" + targetX + "," + targetY; 
} 

「神奇」的數字我選擇繪製曲線大多推斷做出自我 - 節點曲線看起來不錯。

完整的示例:

<!DOCTYPE html> 
 
<html> 
 
    <head> 
 
    <meta charset="utf-8"> 
 

 
    <!-- 
 
    
 
     This program is a tool for visualizing small directed graphs. 
 
     Inspired by: 
 
     Force Dragging I 
 
     http://bl.ocks.org/mbostock/2675ff61ea5e063ede2b5d63c08020c7 
 
     Reactive Flow Diagram 
 
     http://bl.ocks.org/curran/5905182da50a4667dc00 
 
      
 
     Curran Kelleher May 2016 
 
    --> 
 

 
    <meta name="viewport" content="width=device-width"> 
 
    <title>Graph Editor</title> 
 
    <script src="https://d3js.org/d3.v4.0.0-alpha.40.min.js"></script> 
 
    <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet" type="text/css"> 
 
    <style> 
 
     
 
     .node-rect { 
 
     fill: white; 
 
     stroke: black; 
 
     stroke-width: 1.5; 
 
     cursor: move; 
 
     } 
 
     
 
     .node-text { 
 
     font-family: "Roboto", sans-serif; 
 
     font-size: 2em; 
 
     text-anchor: middle; 
 
     alignment-baseline: middle; 
 
     pointer-events: none; 
 
     /* Disable text selection 
 
      from http://stackoverflow.com/questions/826782/css-rule-to-disable-text-selection-highlighting */ 
 
     -webkit-touch-callout: none; /* iOS Safari */ 
 
     -webkit-user-select: none; /* Chrome/Safari/Opera */ 
 
     -khtml-user-select: none; /* Konqueror */ 
 
     -moz-user-select: none;  /* Firefox */ 
 
     -ms-user-select: none;  /* Internet Explorer/Edge */ 
 
     user-select: none; 
 
     } 
 
     
 
     .link-line { 
 
     stroke: black; 
 
     stroke-width: 1.5; 
 
     fill: none; 
 
     } 
 
     
 
     /* Set the arrowhead size. */ 
 
     .arrow { 
 
     stroke-width: 1.5px; 
 
     fill: none; 
 
     } 
 
    </style> 
 
    </head> 
 
    <body> 
 
    <script> 
 
     
 
     var width = 960, 
 
      height = 500, 
 
      nodeSize = 20, 
 
      arrowWidth = 8, 
 
      svg = d3.select("body") 
 
      .append("svg") 
 
      .attr("width", width) 
 
      .attr("height", height) 
 
      linkG = svg.append("g") 
 
      nodeG = svg.append("g") 
 
      // Arrows are separate from link lines so that their size 
 
      // can be controlled independently from the link lines. 
 
      arrowG = svg.append("g"); 
 
     
 
     // Arrowhead setup. 
 
     // Draws from Mobile Patent Suits example: 
 
     // http://bl.ocks.org/mbostock/1153292 
 
     svg.append("defs") 
 
     .append("marker") 
 
      .attr("id", "arrow") 
 
      .attr("orient", "auto") 
 
      .attr("preserveAspectRatio", "none") 
 
      // See also http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute 
 
      //.attr("viewBox", "0 -" + arrowWidth + " 10 " + (2 * arrowWidth)) 
 
      .attr("viewBox", "0 -5 10 10") 
 
      // See also http://www.w3.org/TR/SVG/painting.html#MarkerElementRefXAttribute 
 
      .attr("refX", 10) 
 
      .attr("refY", 0) 
 
      .attr("markerWidth", 10) 
 
      .attr("markerHeight", arrowWidth) 
 
     .append("path") 
 
      .attr("d", "M0,-5L10,0L0,5"); 
 
     
 
     var simulation = d3.forceSimulation() 
 
     .force("link", d3.forceLink()) 
 
     .force("charge", d3.forceManyBody()) 
 
     .force("center", d3.forceCenter(width/2, height/2)); 
 
     
 
     simulation.force("link") 
 
     .distance(140); 
 
     
 
     var drag = d3.drag() 
 
     .on("start", dragstarted) 
 
     .on("drag", dragged) 
 
     .on("end", dragended); 
 
     
 
     function dragstarted(d) { 
 
     if (!d3.event.active) simulation.alphaTarget(0.3).restart() 
 
     simulation.fix(d); 
 
     } 
 
     
 
     function dragged(d) { 
 
     simulation.fix(d, d3.event.x, d3.event.y); 
 
     } 
 
     
 
     function dragended(d) { 
 
     if (!d3.event.active) simulation.alphaTarget(0); 
 
     } 
 
     
 
     function render(graph){ 
 
     
 
     var link = linkG.selectAll("path").data(graph.links); 
 
     var linkEnter = link.enter().append("path") 
 
      .attr("class", "link-line"); 
 
     link.exit().remove(); 
 
     link = link.merge(linkEnter); 
 
     
 
     var arrow = arrowG.selectAll("path").data(graph.links); 
 
     var arrowEnter = arrow.enter().append("path") 
 
      .attr("class", "arrow") 
 
      .attr("marker-end", "url(#arrow)"); 
 
     arrow.exit().remove(); 
 
     arrow = arrow.merge(arrowEnter); 
 
     
 
     var node = nodeG.selectAll("g").data(graph.nodes); 
 
     var nodeEnter = node.enter().append("g").call(drag); 
 
     node.exit().remove(); 
 
     
 
     nodeEnter.append("rect") 
 
      .attr("class", "node-rect") 
 
      .attr("y", -nodeSize) 
 
      .attr("height", nodeSize * 2) 
 
      .attr("rx", nodeSize) 
 
      .attr("ry", nodeSize) 
 
      .on("click", function (d){ 
 
      simulation.unfix(d); 
 
      }); 
 
     
 
     nodeEnter.append("text") 
 
      .attr("class", "node-text"); 
 
     
 
     node = node.merge(nodeEnter); 
 
     
 
     node.select(".node-text") 
 
      .text(function (d){ return d.name; }) 
 
      .each(function (d) { 
 
      
 
      var circleWidth = nodeSize * 2, 
 
       textLength = this.getComputedTextLength(), 
 
       textWidth = textLength + nodeSize; 
 
      
 
      if(circleWidth > textWidth) { 
 
       d.isCircle = true; 
 
       d.rectX = -nodeSize; 
 
       d.rectWidth = circleWidth; 
 
      } else { 
 
       d.isCircle = false; 
 
       d.rectX = -(textLength + nodeSize)/2; 
 
       d.rectWidth = textWidth; 
 
       d.textLength = textLength; 
 
      } 
 
      }); 
 
     
 
     node.select(".node-rect") 
 
      .attr("x", function(d) { return d.rectX; }) 
 
      .attr("width", function(d) { return d.rectWidth; }); 
 
     
 
     simulation.force("link").links(graph.links); 
 
     
 
     simulation.nodes(graph.nodes).on("tick", function(){ 
 
      
 
      graph.nodes.forEach(function (d) { 
 
      if(d.isCircle){ 
 
       d.leftX = d.rightX = d.x; 
 
      } else { 
 
       d.leftX = d.x - d.textLength/2 + nodeSize/2; 
 
       d.rightX = d.x + d.textLength/2 - nodeSize/2; 
 
      } 
 
      }); 
 
      
 
      link.call(edge); 
 
      arrow.call(edge); 
 
      
 
      node.attr("transform", function(d) {  
 
      return "translate(" + d.x + "," + d.y + ")"; 
 
      }); 
 
     }); 
 
     } 
 
     
 
     // Sets the (x1, y1, x2, y2) line properties for graph edges. 
 
     function edge(selection){ 
 
     selection 
 
      .attr("d", function (d) {   
 
      
 
       var sourceX, targetX, midX, dy, dy, angle; 
 

 
       // This mess makes the arrows exactly perfect. 
 
       if(d.source.rightX < d.target.leftX){ 
 
       sourceX = d.source.rightX; 
 
       targetX = d.target.leftX; 
 
       } else if(d.target.rightX < d.source.leftX){ 
 
       targetX = d.target.rightX; 
 
       sourceX = d.source.leftX; 
 
       } else if (d.target.isCircle) { 
 
       targetX = sourceX = d.target.x; 
 
       } else if (d.source.isCircle) { 
 
       targetX = sourceX = d.source.x; 
 
       } else { 
 
       midX = (d.source.x + d.target.x)/2; 
 
       if(midX > d.target.rightX){ 
 
        midX = d.target.rightX; 
 
       } else if(midX > d.source.rightX){ 
 
        midX = d.source.rightX; 
 
       } else if(midX < d.target.leftX){ 
 
        midX = d.target.leftX; 
 
       } else if(midX < d.source.leftX){ 
 
        midX = d.source.leftX; 
 
       } 
 
       targetX = sourceX = midX; 
 
       } 
 

 
       dx = targetX - sourceX; 
 
       dy = d.target.y - d.source.y; 
 
       angle = Math.atan2(dx, dy); 
 

 
       // Compute the line endpoint such that the arrow 
 
       // is touching the edge of the node rectangle perfectly. 
 
       sourceX = sourceX + Math.sin(angle) * nodeSize; 
 
       targetX = targetX - Math.sin(angle) * nodeSize; 
 
       sourceY = d.source.y + Math.cos(angle) * nodeSize; 
 
       targetY = d.target.y - Math.cos(angle) * nodeSize; 
 
       
 
      \t if (d.source === d.target){ 
 
       return "M" + sourceX + "," + sourceY + "A" + 40 + "," + 40 + " " + -45 + "," + 1 + "," + 0 + " " + (sourceX - 1) + "," + (sourceY + 1); 
 
      } else { 
 
       return "M" + sourceX + "," + sourceY + "L" + targetX + "," + targetY; 
 
      }  
 
      \t 
 
      }); 
 
     } 
 
     
 
     var graph = {"nodes":["socks","shoes","shirt","belt","tie","jacket","pants","underpants"],"links":[{"source":0,"target":1},{"source":2,"target":3},{"source":2,"target":4},{"source":3,"target":5},{"source":4,"target":5},{"source":6,"target":1},{"source":6,"target":3},{"source":7,"target":6},{"source":7,"target":7},{"source":1,"target":1},{"source":2,"target":2},{"source":3,"target":3},{"source":4,"target":4},{"source":5,"target":5},{"source":6,"target":6}]}; 
 
     
 
     graph.nodes = graph.nodes.map(function (d){ 
 
     return { name: d }; 
 
     }); 
 
     graph.links = graph.links.map(function (d){ 
 
     d.source = graph.nodes[d.source]; 
 
     d.target = graph.nodes[d.target]; 
 
     return d; 
 
     }); 
 
     render(graph); 
 
    </script> 
 
    </body> 
 
</html>


關於第二個問題,看到這個例子here。關鍵線是:

// See https://github.com/d3/d3-force/blob/master/README.md#simulation_tick 
for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin())/Math.log(1 - simulation.alphaDecay())); i < n; ++i) { 
    simulation.tick(); 
} 

這基本上只是在渲染圖之前運行模擬完成。