2013-03-19 8 views
6

Ich habe einige Kreise/Knoten mit unterschiedlichem Radius und ich muss sie mit Pfaden mit Pfeilenden verbinden.Verknüpfen von Knoten mit variablem Radius mit Pfeilen

Hier ist der Code für die Markierung:

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"); 

ich den Radius der Kreise in einem Array gespeichert sind. Hier ist der Screenshot:

enter image description here

Der Pfeil ist eigentlich „innen“ die Kreise. Wie bekomme ich die Pfeile an die Oberfläche der Kreise?

+0

eigentlich kann ich nicht sehen, der Pfeil ist eigentlich „innen“ die Kreise. – Oswald

+0

Stellen Sie sicher, dass die Pfade am Radius der Kreise und nicht am Mittelpunkt beginnen und enden. Einige Trigonometrie wird benötigt. –

+0

Ich kann falsch liegen, aber die Pfade für die Links sind getrennt von den Markierungen, aus denen die Pfeile bestehen. Die '.attr (" refX ", 5)' setzt den Offset des Pfeils vom Mittelpunkt des Kreises. Während es als X-Versatz bezeichnet wird, entspricht es nicht der x-Achse (links und rechts) des Bildschirms, da sich das Objekt dreht. Das gleiche gilt für die Zeile .attr ("refY", -1.5). Als ich versuchte, eine Funktion anzuwenden, um es auszuarbeiten, scheiterte ich kläglich. Ich könnte die Pfeile ausgleichen, aber nicht konsequent auf die richtige Entfernung. – d3noob

Antwort

5

Das ist wirklich lustig; Ich habe dieses Problem gerade gestern gelöst.

Ich habe den Pfad am Rand des Knotens beendet, nicht in der Mitte. Mein Fall ist komplizierter, weil ich Bezier-Kurven verwenden, keine geraden Linien, aber dies könnte Ihnen helfen:

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 
     }); 

Durch die Art und Weise; Sie müssen das gleiche für die "Quelle" -Pfeil Kopf tun (ich habe es nur auf das Ziel)

2

Sie können die SVG-Elemente so bestellen, dass die Kreise zuerst gerendert werden, die Zeilen mit Pfeilen danach (in d3 gibt es eine .order Methode, see here for details. Für die Aufzeichnung ist der entsprechende Teil der raphael api discussed here).

6

Dies ist eine alte Frage, aber hier ist meine Lösung, wenn Sie möchten, dass Ihre Pfeilspitzen an der Kante Ihrer Knoten liegen und nicht auf oder unter ihnen. Mein Ansatz war auch, den Weg zwischen den Knoten so zu zeichnen, dass die Endpunkte an den Kanten der Knoten lagen und nicht an den Knotenpunkten. Ausgehend von dem Mobile Patent Anzüge Beispiel (http://bl.ocks.org/mbostock/1153292), ersetzt die linkArc I-Methode mit:

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; 
} 

anzumerken, dass dieser Code einen erwartet „r“ oder Radius Attribute in den Knotendatum zu sein. Um die Punkte der Pfeile an den richtigen Positionen zu platzieren, änderte es die REFX und REFY Attribute, so dass die Spitze des Pfeils am Rande des Knotens war:

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

es funktioniert für Demo-Beispiel auf einmal –

0

ich online suchen, keiner der Antwort gearbeitet , so habe ich meine eigenen:

Hier ist der Code:

//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 
}