2013-03-19 4 views
6

У меня есть несколько кругов/узлов разного радиуса, и я должен соединить их с путями, имеющими концы стрелок.Связывание узлов с переменным радиусом со стрелками

Вот код для маркеров:

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

Я сохранил радиус кругов в массиве. Вот снимок экрана:

enter image description here

Стрелка на самом деле «внутри» кругов. Как получить стрелки на поверхности кругов?

+0

на самом деле, я не могу видеть, стрелка на самом деле «внутри» кругов. – Oswald

+0

Сделать дорожки начинаться и заканчиваться радиусом окружности, а не центром. Требуется некоторая тригонометрия. –

+0

Возможно, я ошибаюсь, но пути для ссылок отделены от маркеров, которые составляют стрелки. '.attr (" refX ", 5)" устанавливает смещение стрелки от центра круга. Хотя он обозначается как смещение 'X', потому что объект вращается, он не соответствует оси x (слева и справа) экрана. То же самое относится к строке '.attr (« refY », -1.5)». Когда я попытался применить функцию для ее работы, я потерпел неудачу. Я мог бы смещать стрелки, но не последовательно на правильное расстояние. – d3noob

ответ

5

Это действительно смешно; Я только что решил эту проблему вчера.

То, что я сделал, это закончить путь по краю узла, а не по центру. Мой случай является более сложным, потому что я использую кривые Безье, а не прямые линии, но это может помочь вам:

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

Кстати, вам нужно будет сделать то же самое для головы стрелки «источник» (у меня есть только у цели)

2

вы можете заказать элементы svg так, чтобы круги были визуализированы первыми, после этого линии со стрелками (в d3 существует метод .order, see here for details. Для записи соответствующая часть raphael api равна discussed here).

6

Это старый вопрос, но вот мое решение, если вы хотите, чтобы ваши наконечники стрелок находились на краю ваших узлов, а не сверху или снизу. Мой подход заключался также в том, чтобы нарисовать путь, соединяющий узлы таким образом, чтобы конечные точки находились на ребрах узлов, а не в центрах узлов. Начиная с мобильного патента Подходит пример (http://bl.ocks.org/mbostock/1153292), я заменил метод linkArc с:

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

Обратите внимание, что этот код ожидает «R», или радиуса, атрибут, чтобы быть в данных узлов. Для того, чтобы разместить точки стрелки на правильной позиции, я изменил REFx и refY атрибуты так, чтобы точка стрелки была на краю узла:

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

он работает для демонстрационного примера сразу –

0

Я поиск в Интернете, ни один из ответа не работал , так что я сделал свой собственный:

Вот код:

//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 
} 
Смежные вопросы