1

I would like to visualize a directed graph using the forced layout algorithm from d3. The nodes should displayed as their names inside a rectangle like this. My problem is to calculate the position outside the rectangle where the arrows should point to.

I think it should be done inside the tick() function. To avoid extreme effort like angle calculations i could use the Intercept theorem.

I dont know how to get the parameters for the edges which will be set using attributes and I couldnt find an example for this.

var force = d3.layout.force().nodes(dataset.nodes).links(dataset.edges)...
var edges = svg.selectAll("line").data(dataset.edges).enter().append("line")...
var nodes = svg.selectAll("text").data(dataset.nodes).enter().append("text")...

force.on("tick", function(){
    // Why does the following function not work? How to implement this correctly?
    edges.attr(function(d){ 
        var x1 = d.source.x
        var y1 = d.source.y
        var x2 = d.target.x
        var y2 = d.target.y
        // ... calulate new source and targetcoordinates ... I can do this myself 
        return {
            "x1": newSourceX,
            "y1": newSourceY,
            "x2": newTargetX,
            "y2": newTargetY
        }
    });
});

How to implement the function for each edge to extract any source- and target-parameter and save the new positions?

Do you think this is the best performing solution?

VividD
  • 10,456
  • 6
  • 64
  • 111
fabi
  • 53
  • 5
  • Not sure what you mean. You shouldn't need to calculate where the arrow is pointing yourself, see e.g. [this example](http://bl.ocks.org/mbostock/1153292). – Lars Kotthoff Oct 17 '13 at 08:15
  • I would like to use the text as nodes like [this example](http://arborjs.org/halfviz/#/a-new-hope) shows. The arrows have to point outside the text-element but each textelement has its own witdh. The exact problem is to get the source and target coordinate from each edge inside the tick() function. – fabi Oct 18 '13 at 04:09
  • Have you seen [this question](http://stackoverflow.com/questions/15495762/linking-nodes-of-variable-radius-with-arrows)? – Lars Kotthoff Oct 18 '13 at 08:10

2 Answers2

1

Try using the following as a starting point. Calling updateLines(".links") within tick() sets the lines to only reach the radii of the given nodes, rather than the edge of a rectangle.

var updateLines = function (selection) {
    selection.each( function(d) {
        var x1 = d.source.x,
            y1 = d.source.y,
            x2 = d.target.x,
            y2 = d.target.y,
            rad = Math.atan((y2-y1)/(x2-x1)),
            offsetX1 = d.source.r * -Math.cos(rad),
            offsetY1 = d.source.r * Math.sin(rad),
            offsetX2 = d.target.r * Math.cos(rad),
            offsetY2 = d.target.r * -Math.sin(rad);
        d3.select(this)
        .attr("x1", function() {
            if (x2 - x1 >= 0) {
                return x1 - offsetX1;
            } else {
                return x1 + offsetX1;
            }                
        })
        .attr("y1", function() {
            if (x2 - x1 >= 0) {
                return y1 + offsetY1;
            } else {
                return y1 -  offsetY1;
            }
        })
        .attr("x2", function() {
            if (x2 - x1 >= 0) {
                return x2 - offsetX2;
            } else {
                return x2 + offsetX2;
            }
        })
        .attr("y2", function() {
            if (x2 - x1 >= 0) {
                return y2 + offsetY2;
            } else {
                return y2 -  offsetY2;
            }
        })
    })
}

I know that you're trying to avoid using angles, but I don't think it would be too convoluted to modify the above function to solve your needs. When comparing the coordinates of one node to those of another, picture the "offset" x and y values between them forming a triangle, thus a hypotenuse. If you look at a node's text box and imagine a neighbor node's relationship line passing through this text box, the triangular shape made there is the same as that of the triangle formed by the offset values of the two nodes in question.

So all three angles are the same, which means that as long as you know the dimensions of your textbox, you should easily be able to find the two values that you need to apply to the given node's coordinates.

Another piece of information that you would need to make this work is to take into account whether the angle created between two nodes' coordinates is larger or smaller than the angle created between the coordinates of the node's center (0,0) and the coordinates of the text box's edge relative to the center:

var yourBool = Math.atan(offsetX/offsetY) > Math.atan( textbox.width / 2 / textbox.height / 2 )
// or Math.atan(y/x) and so on...

I don't think implementing this would result in noticeable lag for your graph, however, to answer your last question, I'm sure there is a better-performing way to implement this functionality.

Walter Roman
  • 4,621
  • 2
  • 32
  • 36
  • Thank you. This function is not exactly what I need but it will help me to calculate the edgepositions from the nodetext. I Will post my result later :) – fabi Oct 18 '13 at 11:30
0

Walters answer needs some changes to handle the line end adjustment correctly since both the x and y co-ordinates of both the source and target need to be included in the determination of adding or subtracting the calculated adjustment. For example, to calculate the X2 adjustment:

  .attr("x2", function() {
    if ((y2 >= y1) && (x2 >= x1)) return  x2 - offsetX2;
    if ((y2 >= y1) && (x2 < x1)) return  x2 + offsetX2;
    if ((y2 < y1) && (x2 >= x1)) return  x2 - offsetX2;
    if ((y2 < y1) && (x2 < x1)) return  x2 + offsetX2;
  }
SteveC
  • 644
  • 7
  • 12