Arrow at Link Target

3,909 views
Skip to first unread message

Sebbi007

unread,
Aug 10, 2011, 7:25:52 AM8/10/11
to d3-js
Hi there,

i want to show the links of a Force Directed Graph like Arrows, how
can i append a Triangle to a Link ?

i have no idea :-/

Thanks for helping

Sebbi007

unread,
Aug 10, 2011, 8:44:35 AM8/10/11
to d3-js
i've created this :

var arrowLinks = linksG.selectAll("path.linkArrow")
.data(linksTree);

var arrowLinksEnter = arrowLinks
.enter().append("svg:path")
.attr("d", function(d){ return "M"+d.target.x+","+d.target.y+" L"+
(d.target.x-10)+","+(d.target.y+10)+" L"+(d.target.x-10)+","+
(d.target.y-10); });

it creates a Triangle at the Link Target, now the Problem is the angle
of the link Line that the triangle will move with the Link Line

Ricardo

unread,
Aug 10, 2011, 10:08:13 AM8/10/11
to d3-js
Maybe you can use a translate and rotate on the path so that the arrow
turns the way you want. Something like so

var arrowLinks = linksG.selectAll('g.linkArrow')
.data(linksTree);

var arrowLinksEnter = arrowLinks
.enter().append('svg:g')
.attr('class', 'linkArrow')
.attr('transform', function(d) {
var dx = d.target.x - d.source.y,
dy = d.target.y - d.source.y,
da = Math.atan2(dy, dx) * 180 / Math.PI;
return 'translate('+ d.target.x +','+ d.target.y +')'
+ 'rotate('+ da +')';
});

// draw the arrow 0 based since it is translated

arrowLinksEnter.append('svg:path').attr('d', function(d) ....);

I haven't tried this and you might have to redo the math on the angle
but it should me something like that. It becomes a bit tricky if you
want to offset the arrow from the target location. If you have a node
(say a circle at the target location) you would want to have the arrow
end up not at the target location but at a fixed distance (the circle
radius). I have worked up the math for this case somewhere but can't
find it right now. If you would like to do this let me know and I can
do a better search inside my examples.

- R

Mike Bostock

unread,
Aug 10, 2011, 12:55:28 PM8/10/11
to d3...@googlegroups.com
You have a few options here. You could use an svg:g element that
contains an svg:line and an svg:polygon, or perhaps you might use an
svg:path with d3.svg.symbol to create the triangle. You could design
your own custom svg:path that fills both the line and draws the arrow
head at the end, perhaps using the "transform" attribute to rotate the
path. Another option would be to use an svg:marker, as in this
example:

http://www.w3.org/TR/SVG/painting.html#Markers

Mike

Sebbi007

unread,
Aug 11, 2011, 3:34:52 AM8/11/11
to d3-js
Hi Mike and Ricardo!

thank you for this solutions i will try it with the Markers!

another Question from me:

if i call force.drag, can i only bind this to the left mouse click ?
because on the right mouse click i will expand a context menu

Sebbi007

Sebbi007

unread,
Aug 11, 2011, 7:28:31 AM8/11/11
to d3...@googlegroups.com
Hi Guys!

i've solved the Arrow Problem with the Markers from Mike!

here's my Code for it :

var link = vis.selectAll("path.link")
.data(linksTree);
var linkEnter = link
.enter()
.append("svg:g");
var linkPath = linkEnter
.append("svg:path")
.attr("class", "link") 
.attr("id", 
function(d) { 
return "path"+d.source.index+"_"+d.target.index; 
}) 
.attr("d", 
function(d) { 
return moveto(d) + lineto(d); 
}) 
.attr("marker-end", function(d){ return "url(#marker"+d.source.index+"_"+d.target.index+")"} );
var linkMarker = linkEnter
.append("svg:marker")
.attr("id",function(d){ return "marker"+d.source.index+"_"+d.target.index} )
.attr("viewBox","0 0 20 20")
.attr("refX","30")
.attr("refY","10")
.attr("markerUnits","strokeWidth")
.attr("markerWidth","11")
.attr("markerHeight","7")
.attr("orient","auto")
var linkMarkerPath = linkMarker
.append("svg:path")
.attr("d","M 0 0 L 20 10 L 0 20 z");
var linkLabel = linkEnter
.append("svg:text")
.attr("text-anchor", "start")
.attr("font-size", 10) 
.append("svg:textPath") 
.attr("startOffset", "25")
.attr("xlink:href", 
function(d) { return "#path"+d.source.index+"_"+d.target.index;
}) 

.text(function(d){ var ourput = ""; if(d.weight) {output = " Weight: "+d.weight +" "+ d.HEName + " ->" ;} return output; });


the Output looks like this :



Greets!
Sebbi007

Mike Bostock

unread,
Aug 11, 2011, 12:27:36 PM8/11/11
to d3...@googlegroups.com
Nice work, and thank you for sharing!

Mike

Sebbi007

unread,
Aug 11, 2011, 1:52:17 PM8/11/11
to d3-js
Hi Mike,

Did you have a solution for my other question bevore my Post?

Thanks
Sebbi007

Mike Bostock

unread,
Aug 11, 2011, 2:00:40 PM8/11/11
to d3...@googlegroups.com
> if i call  force.drag, can i only bind this to the left mouse click ?
> because on the right mouse click i will expand a context menu

I wouldn't recommend changing the behavior of right click; that's
standard browser functionality for the context menu. It is possible to
preventDefault on right-click events and change the behavior, but it's
not supported by the force layout's drag behavior. You might be able
to add a mousedown handler and do it yourself, though.

Mike

Dae Il Kim

unread,
Aug 12, 2011, 1:18:38 PM8/12/11
to d3-js
Sebbi007, Would you be willing to post a link to the full working
online visualization? It would be of great help to beginners such as
myself. Also, there are certain functions such as moveto(d) and
lineto(d) that I'm not entirely sure where they come from. Thanks for
your hard work on this.

Best,
Dae Il

Mike Bostock

unread,
Aug 18, 2011, 12:34:11 AM8/18/11
to d3...@googlegroups.com
I finally found a good excuse to create a force layout of a directed
graph. Here's a visualization showing patent-related suits in the
mobile industry:

http://bl.ocks.org/1153292

It demonstrates how to use markers and curved paths for directed
edges. (If you wanted to be totally accurate, then the edges would
start and end at each circle's circumference rather than the center,
but, close enough!)

Mike

Roberto Minelli

unread,
Jun 5, 2013, 12:12:48 PM6/5/13
to d3...@googlegroups.com, mbos...@cs.stanford.edu
I run into this post while trying to do the same thing...

Actually what I would need is the marker at the circumference and not on the center of the node. My nodes have also different sizes (i.e., the r attribute of the circle is computed using a d3.linear.scale).

I read the SVG specification for markers and paths, but I coudn't figure out how to set neither the viewBox, nor the refX and refY to do that. I also tried modifying the tick function to draw the edge on the circumference with no success. 

Any intuition here?

Thanks in advance,
R

Jim McCusker

unread,
Jun 5, 2013, 9:31:57 PM6/5/13
to d3...@googlegroups.com, mbos...@cs.stanford.edu
I implemented this in VRAER by drawing the triangles separately, against arbitrary-sized foreignObjects. Feel free to peruse the code, although I don't have a separate demo/example for it:


Jim
--
You received this message because you are subscribed to the Google Groups "d3-js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to d3-js+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 


--
Jim McCusker
Programmer Analyst
Krauthammer Lab, Pathology Informatics
Yale School of Medicine
james.m...@yale.edu | (203) 785-4436
http://krauthammerlab.med.yale.edu

PhD Student
Tetherless World Constellation
Rensselaer Polytechnic Institute
mcc...@cs.rpi.edu
http://tw.rpi.edu

Johnson Chetty

unread,
Jun 6, 2013, 1:18:29 AM6/6/13
to d3...@googlegroups.com, mbos...@cs.stanford.edu

Hi, 

Maybe you can try this? This plots a straight line with an arrowhead... Still needs some improvement though..


    // Update links                                                                                         
    link = d3.select("#chart g.edges").selectAll("line.link").select(this.arrowhead)
        .data(edges, function (e) {return e.from + "-" + e.to + "-" + e.type; });

    // Enter any new links.                                                                                     
    link.enter().append("svg:line")
        .attr("class", "link")
.style("stroke-width", 2 )
        .attr("x1", function (d) { return d.source.x; })
        .attr("y1", function (d) { return d.source.y; })
.attr("x2", function (d) { return d.target.x; })
        .attr("y2", function (d) { return d.target.y; })
.attr("text", function (d) {return d.type;    })
        .attr("marker-end", "url(#arrowhead)");


Cheers

Regards,
Johnson Chetty




Roberto Minelli

unread,
Jun 6, 2013, 4:22:54 AM6/6/13
to d3...@googlegroups.com, mbos...@cs.stanford.edu
Hi Jim, Hi Johnson,

Thanks for your pointers! 

Btw, Johnson, I imagine that your this.arrowhead looks something like 

svg.append("svg:defs")
  .append("svg:marker")
    .attr("id", "arrowhead")
    .attr('viewBox', '0 -5 10 10')
    .attr('refX', 7)
    .attr('refY', 0)
    .attr("markerWidth", 3)
    .attr("markerHeight", 3)
    .attr("orient", "auto")
  .append("svg:path")
    .attr('d', 'M0,-5L10,0L0,5');

Am I right? 

However, yesterday I managed to have a more-or-less working version with curved lines but I've a couple of problems understanding the real mechanics of paths and markers...
In particular, I cannot understand, neither by reading the SVG doc neither by trying to modify them, the parameters 'viewBox','refX', and 'refY'. Any intuition?

For curved lines you will have to play with them to perfectly align markers..

p.s.: Jim, could you point me directly at the place in the code when you're drawing triangles?? Thanks.

Cheers,
R

Jim
To unsubscribe from this group and stop receiving emails from it, send an email to d3-js+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.
 
 


--
Jim McCusker
Programmer Analyst
Krauthammer Lab, Pathology Informatics
Yale School of Medicine
james.m...@yale.edu | (203) 785-4436
http://krauthammerlab.med.yale.edu

PhD Student
Tetherless World Constellation
Rensselaer Polytechnic Institute
mcc...@cs.rpi.edu
http://tw.rpi.edu

--
You received this message because you are subscribed to the Google Groups "d3-js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to d3-js+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Regards,
Johnson Chetty




Johnson Chetty

unread,
Jun 6, 2013, 6:15:45 AM6/6/13
to d3...@googlegroups.com, mbos...@cs.stanford.edu
On 6 June 2013 13:52, Roberto Minelli <r.min...@gmail.com> wrote:
Hi Jim, Hi Johnson,

Thanks for your pointers! 

Btw, Johnson, I imagine that your this.arrowhead looks something like 

svg.append("svg:defs")
  .append("svg:marker")
    .attr("id", "arrowhead")
    .attr('viewBox', '0 -5 10 10')
    .attr('refX', 7)
    .attr('refY', 0)
    .attr("markerWidth", 3)
    .attr("markerHeight", 3)
    .attr("orient", "auto")
  .append("svg:path")
    .attr('d', 'M0,-5L10,0L0,5');

Am I right? 

Hi, 
Its something like this:

    svgmarker = new_nodes.append("svg:marker")
        .attr("id", "arrowhead")
        .attr("viewBox","0 0 10 10")
        .attr("refX","20")
        .attr("refY","5")
        .attr("markerUnits","strokeWidth")
        .attr("markerWidth","9")
        .attr("markerHeight","5")
.attr("orient","auto")
        .append("svg:path")
        .attr("d","M 0 0 L 10 5 L 0 10 z")
.attr("fill", "#6D6666");


 
However, yesterday I managed to have a more-or-less working version with curved lines but I've a couple of problems understanding the real mechanics of paths and markers...
In particular, I cannot  understand, neither by reading the SVG doc neither by trying to modify them, the parameters 'viewBox','refX', and 'refY'. Any intuition?


from speculation, viewBox is the viewpane that the marker will get clipped to.
eg. If a circle/object is larger than the viewBox, it gets clipped.
refX, refY -> the reference offset from the line end point (which calls the marker) .

But its still voodoo to me... 

We should make a nice d3 doc on svg manipulation.. 
Anyone know of any posts/resources on this?



--
Regards,
Johnson Chetty




Johnson Chetty

unread,
Jun 6, 2013, 7:27:56 AM6/6/13
to d3...@googlegroups.com
Correction! Sorry, the above select should not contain the (this.arrowhead) at all... it isn't needed..

Should be:
    // Update links                                                                                         
    link = d3.select("#chart g.edges").selectAll("line.link")
        .data(edges, function (e) {return e.from + "-" + e.to + "-" + e.type; });

That would work just as fine.



--
Regards,
Johnson Chetty




Jim McCusker

unread,
Jun 6, 2013, 10:34:32 AM6/6/13
to d3-js, mbos...@cs.stanford.edu
The JS is here: https://github.com/jimmccusker/rdfviewer/blob/master/rdf.js

The important parts are in two sections. The first section (line 841) draws the arrowheads and computes the rotation:

        links.arrowhead.attr("points", function(d) {
            return [[d.x2,d.y2].join(","),
                    [d.x2-3,d.y2+8].join(","),
                    [d.x2+3,d.y2+8].join(",")].join(" ");
        })
            .attr("transform",function(d) {
                angle = Math.atan2(d.y2-d.y1, d.x2-d.x1)*180/Math.PI + 90;
                return "rotate("+angle+", "+d.x2+", "+d.y2+")";
            });

The second part adjusts the line ends to not hit the box bounds (line 771). It assumes rectangles, but circles would be even easier. It needs the functions makeCenteredBox() (the boxes get drawn with the x,y at the center) and computes the edge point along the same angle approach using edgePoint().

Reply all
Reply to author
Forward
0 new messages