2013-03-19 10 views
6

Tôi có một số vòng tròn/nút bán kính khác nhau và tôi phải kết nối chúng với đường dẫn có mũi tên kết thúc.các nút liên kết bán kính biến với các mũi tên

Dưới đây là các mã cho điểm đánh dấu:

svg.append("svg:defs").selectAll("marker") 
    .data(["default"]) 
    .enter().append("svg:marker") 
    .attr("id", String) 
    .attr("viewBox", "0 -5 10 10") 
    .attr("refX", 5) 
    .attr("refY", -1.5) 
    .attr("markerWidth", 10) 
    .attr("markerHeight", 10) 
    .attr("orient", "auto") 
    .append("svg:path") 
    .attr("d", "M1,-5L10,0L0,5"); 

tôi đã được lưu trữ bán kính của vòng tròn trong một mảng. Dưới đây là ảnh chụp màn hình:

enter image description here

Mũi tên thực sự là "bên trong" vòng tròn. Làm thế nào để tôi có được các mũi tên ở bề mặt của các vòng tròn?

+0

thực ra, tôi không thể nhìn thấy, mũi tên thực sự là "bên trong" vòng tròn. – Oswald

+0

Làm cho đường dẫn bắt đầu và kết thúc tại bán kính vòng tròn thay vì ở giữa. Một số lượng giác sẽ được yêu cầu. –

+0

Tôi có thể sai, nhưng đường dẫn cho các liên kết tách biệt với các điểm đánh dấu tạo nên các mũi tên. '.attr (" refX ", 5)' đặt độ lệch của mũi tên từ giữa vòng tròn. Trong khi nó được chỉ định là bù đắp 'X', bởi vì đối tượng đang quay, nó không tương ứng với trục x (trái và phải) của màn hình. Điều này cũng đúng với dòng '.attr (" refY ", -1.5)'. Khi tôi cố gắng áp dụng một hàm để giải quyết nó, tôi đã thất bại thảm hại. Tôi có thể bù đắp các mũi tên, nhưng không nhất quán với khoảng cách chính xác. – d3noob

Trả lời

5

Điều này thực sự buồn cười; Tôi vừa giải quyết vấn đề này ngày hôm qua.

Điều tôi đã làm là kết thúc đường dẫn ở cạnh nút, chứ không phải ở giữa. trường hợp của tôi là phức tạp hơn bởi vì tôi sử dụng các đường cong Bezier, đường không thẳng, nhưng điều này có thể giúp bạn:

svg.append("svg:defs").selectAll("marker") 
    .data(["default"]) 
    .enter().append("svg:marker") 
    .attr("id", String) 
    .attr("viewBox", "0 -3 6 6") 
    .attr("refX", 5.0) 
    .attr("refY", 0.0) 
    .attr("markerWidth", 6) 
    .attr("markerHeight", 6) 
    .attr("orient", "auto") 
    .append("svg:path") 
    .attr("d", "M0,-2.0L5,0L0,2.0"); 


    links 
     .attr("fill", "none") 
     .attr("d", function(d) { 
     var tightness = -3.0; 
     if(d.type == "straight") 
      tightness = 1000; 

     // Places the control point for the Bezier on the bisection of the 
     // segment between the source and target points, at a distance 
     // equal to half the distance between the points. 
     var dx = d.target.x - d.source.x; 
     var dy = d.target.y - d.source.y; 
     var dr = Math.sqrt(dx * dx + dy * dy); 
     var qx = d.source.x + dx/2.0 - dy/tightness; 
     var qy = d.source.y + dy/2.0 + dx/tightness; 

     // Calculates the segment from the control point Q to the target 
     // to use it as a direction to wich it will move "node_size" back 
     // from the end point, to finish the edge aprox at the edge of the 
     // node. Note there will be an angular error due to the segment not 
     // having the same direction as the curve at that point. 
     var dqx = d.target.x - qx; 
     var dqy = d.target.y - qy; 
     var qr = Math.sqrt(dqx * dqx + dqy * dqy); 

     var offset = 1.1 * node_size(d.target); 
     var tx = d.target.x - dqx/qr* offset; 
     var ty = d.target.y - dqy/qr* offset; 

     return "M" + d.source.x + "," + d.source.y + "Q"+ qx + "," + qy 
       + " " + tx + "," + ty; // to "node_size" pixels before 
       //+ " " + d.target.x + "," + d.target.y; // til target 
     }); 

Bằng cách này; bạn sẽ phải làm tương tự cho đầu mũi tên 'nguồn' (tôi chỉ có ở đầu mục tiêu)

2

bạn có thể đặt các phần tử svg sao cho vòng tròn sẽ được hiển thị trước, các dòng có mũi tên sau đó (trong d3 có phương pháp .order, see here for details. đối với bản ghi, phần chỉnh sửa của apha raphael là discussed here).

6

Đây là một câu hỏi cũ, nhưng đây là giải pháp của tôi nếu bạn muốn đầu mũi tên của bạn ở cạnh các nút thay vì ở trên cùng hoặc bên dưới chúng. Cách tiếp cận của tôi cũng là để vẽ đường dẫn kết nối các nút sao cho các điểm kết thúc nằm trên các cạnh của các nút hơn là tại các trung tâm của các nút. Bắt đầu từ Mobile bằng sáng chế Suits dụ (http://bl.ocks.org/mbostock/1153292), tôi thay thế các phương pháp linkArc với:

function linkArc(d) { 
    var sourceX = d.source.x; 
    var sourceY = d.source.y; 
    var targetX = d.target.x; 
    var targetY = d.target.y; 

    var theta = Math.atan((targetX - sourceX)/(targetY - sourceY)); 
    var phi = Math.atan((targetY - sourceY)/(targetX - sourceX)); 

    var sinTheta = d.source.r * Math.sin(theta); 
    var cosTheta = d.source.r * Math.cos(theta); 
    var sinPhi = d.target.r * Math.sin(phi); 
    var cosPhi = d.target.r * Math.cos(phi); 

    // Set the position of the link's end point at the source node 
    // such that it is on the edge closest to the target node 
    if (d.target.y > d.source.y) { 
     sourceX = sourceX + sinTheta; 
     sourceY = sourceY + cosTheta; 
    } 
    else { 
     sourceX = sourceX - sinTheta; 
     sourceY = sourceY - cosTheta; 
    } 

    // Set the position of the link's end point at the target node 
    // such that it is on the edge closest to the source node 
    if (d.source.x > d.target.x) { 
     targetX = targetX + cosPhi; 
     targetY = targetY + sinPhi;  
    } 
    else { 
     targetX = targetX - cosPhi; 
     targetY = targetY - sinPhi; 
    } 

    // Draw an arc between the two calculated points 
    var dx = targetX - sourceX, 
     dy = targetY - sourceY, 
     dr = Math.sqrt(dx * dx + dy * dy); 
    return "M" + sourceX + "," + sourceY + "A" + dr + "," + dr + " 0 0,1 " + targetX + "," + targetY; 
} 

Lưu ý rằng mã này hy vọng một "r", hoặc bán kính, thuộc tính có trong dữ liệu nút. Để đặt điểm của các mũi tên ở vị trí chính xác, tôi đã thay đổi refX và refY thuộc tính do đó điểm của mũi tên là ở rìa của nút:

svg.append("defs").selectAll("marker") 
    .data(["suit", "licensing", "resolved"]) 
    .enter().append("marker") 
    .attr("id", function(d) { return d; }) 
    .attr("viewBox", "0 -5 10 10") 
    .attr("refX", 10) 
    .attr("refY", 0) 
    .attr("markerWidth", 6) 
    .attr("markerHeight", 6) 
    .attr("orient", "auto") 
    .append("path") 
    .attr("d", "M0,-5L10,0L0,5"); 
+0

nó hoạt động cho ví dụ demo cùng một lúc –

0

Tôi tìm kiếm trên mạng, không ai trong số các câu trả lời làm việc , vì vậy tôi làm của riêng tôi:

đây là mã:

//arrows 
svg.append("defs").selectAll("marker") 
    .data(["suit", "licensing", "resolved"]) 
    .enter().append("marker") 
    .attr("id", function(d) { return d; }) 
    .attr("viewBox", "0 -5 10 10") 
    .attr("refX", 9) 
    .attr("refY", 0) 
    .attr("markerWidth", 10) 
    .attr("markerHeight", 10) 
    .attr("orient", "auto") 
    .append("path") 
    .attr("d", "M0,-5L10,0L0,5 L10,0 L0, -5") 
    .style("stroke", "#4679BD") 
    .style("opacity", "0.6"); 

    //Create all the line svgs but without locations yet 
var link = svg.selectAll(".link") 
    .data(forceData.links) 
    .enter().append("line") 
    .attr("class", "link") 
    .style("marker-end", "url(#suit)"); 

//Set up the force layout 
var force = d3.layout.force() 
    .nodes(forceData.nodes) 
    .links(forceData.links) 
    .charge(-120) 
    .linkDistance(200) 
    .size([width, height]) 
    .on("tick", tick) 
    .start(); 

function tick(){ 
    link.attr("x1", function (d) { return d.source.x; }) 
     .attr("y1", function (d) { return d.source.y; }) 
     .attr("x2", function (d) { 
      return calculateX(d.target.x, d.target.y, d.source.x, d.source.y, d.target.radius); 
     }) 
     .attr("y2", function (d) { 
      return calculateY(d.target.x, d.target.y, d.source.x, d.source.y, d.target.radius); 
     }); 

    d3.selectAll("circle") 
     .attr("cx", function (d) { return d.x; }) 
     .attr("cy", function (d) { return d.y; }); 

    d3.select("#forcelayoutGraph").selectAll("text") 
     .attr("x", function (d) { return d.x; }) 
     .attr("y", function (d) { return d.y; }); 
} 
function calculateX(tx, ty, sx, sy, radius){ 
    if(tx == sx) return tx;     //if the target x == source x, no need to change the target x. 
    var xLength = Math.abs(tx - sx); //calculate the difference of x 
    var yLength = Math.abs(ty - sy); //calculate the difference of y 
    //calculate the ratio using the trigonometric function 
    var ratio = radius/Math.sqrt(xLength * xLength + yLength * yLength); 
    if(tx > sx) return tx - xLength * ratio; //if target x > source x return target x - radius 
    if(tx < sx) return tx + xLength * ratio; //if target x < source x return target x + radius 
} 
function calculateY(tx, ty, sx, sy, radius){ 
    if(ty == sy) return ty;     //if the target y == source y, no need to change the target y. 
    var xLength = Math.abs(tx - sx); //calculate the difference of x 
    var yLength = Math.abs(ty - sy); //calculate the difference of y 
    //calculate the ratio using the trigonometric function 
    var ratio = radius/Math.sqrt(xLength * xLength + yLength * yLength); 
    if(ty > sy) return ty - yLength * ratio; //if target y > source y return target x - radius 
    if(ty < sy) return ty + yLength * ratio; //if target y > source y return target x - radius 
}