19

I'm using D3 to draw a force-directed graph, which is very similar to this example: http://bl.ocks.org/mbostock/1153292

I'm trying to place the arrowheads in the middle of the links instead of the end.

Playing with the attr("refX", 0) of the marker doesn't help much, because it's absolute and not relative to the link length - my links have varying lengths.

I've been googling around, and my best idea was to replace link.attr("marker-end", ...) with link.attr("marker-segment", ...) according to this example (look for the crosses in the middle of the graphs). But this doesn't seem to work.. I'm guessing because it's part of SVG2 draft only but my browser supports a lower version? (I'm using the most recent ver of Chrome btw).

How can I place the arrowheads in the middle of the links?

VividD
  • 10,456
  • 6
  • 64
  • 111
talkol
  • 12,564
  • 11
  • 54
  • 64

3 Answers3

19

Instead of placing the marker on an end, create your lines (<polyline> or <path>) with a single point in the middle of the line and use marker-mid to apply the arrowheads.

This demo for this answer of mine uses this technique to create multiple interior points along the length of a path along with marker-mid. You would simply be creating a single 'waypoint' instead of many.

Edit: Here's a simple hack to the existing demo showing what I mean:

Demo: http://jsfiddle.net/tk7Wv/2/

Arrowheads in the middle of curves

The only changes are:

  1. Changing marker-end to marker-mid, and
  2. Modifying the path generation code to create two arcs (connected in the middle) instead of one.

It will require some simple trigonometry like Superboggly illustrates to pick an appropriate midpoint based on the amount of bowing you want in your lines.

Community
  • 1
  • 1
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • Thanks, excellent answer. My graph actually had some straight links too (done with line). According to your suggestion, I changed them into polylines and added a point in the middle. Then marker-mid and worked like a charm. – talkol Apr 02 '13 at 07:34
  • Added another answer to reflect this for others :) – talkol Apr 02 '13 at 07:56
  • What about diagonal or banzir curve ? – Amit Rana Nov 24 '14 at 08:35
  • @AmitRana What about them? I do not understand your question or concern. I suggest that you [ask a new question](http://stackoverflow.com/questions/ask) if you have additional needs. Show what you have tried, perhaps link to this question, and clearly describe what you want. – Phrogz Nov 24 '14 at 14:11
  • @Phrogz check out this image http://i.stack.imgur.com/tY4bA.png at bottom right last path . I get how we manage so split path in two for rounded but when I have two rounds than how can we manage or is there any way so we can put that market on path on some specific distance like 50% or 30% of total path length – Amit Rana Dec 11 '14 at 09:42
  • @AmitRana I do no understand your question, likely because you are constrained by the space and formatting allowed in a comment. Instead of carrying on this discussion in a comment on an answer to someone else's question, I suggest that you [ask a new question](http://stackoverflow.com/questions/ask) directly so that you can clearly express what you mean. – Phrogz Dec 11 '14 at 15:41
  • Did the fiddle get modified? There are arrowheads that flash on and off at the quarter and three-quarters mark of every link. – LazerSharks Aug 22 '16 at 22:44
  • 1
    @TrailMix Specific versions of JSFiddle's cannot be modified; saving changes always creates a new link. It's likely that the D3 library that I was loading has been updated, and the implementation adds additional segments instead of a single arc now. – Phrogz Aug 22 '16 at 22:46
  • 1
    Note that this does not work on Version 61.0.3163.100 of Chrome. It renders multiple arrows. – Thomas Clowes Oct 05 '17 at 15:51
17

I'm the OP, this answer is just an addition to the excellent answers above for the sake of others with the same question.

The answers show how to achieve this for a graph with arc links. If you have straight links, the accepted answer needs to be modified slightly. This is how:

Your straight links are probably implemented with line, they need to be converted into polyline. Like so:

// old: svg.selectAll(".link").data(links).enter().append("line")
svg.selectAll(".link").data(links).enter().append("polyline")

Then we have to encode the polyline according to this example, so your original code that encodes the line:

force.on("tick", function() {
   link.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;});

Changes into:

force.on("tick", function() {
   link.attr("points", function(d) {
      return d.source.x + "," + d.source.y + " " + 
             (d.source.x + d.target.x)/2 + "," + (d.source.y + d.target.y)/2 + " " +
             d.target.x + "," + d.target.y; });

And last, don't forget the convert marker-end to marker-mid:

// old: link.attr("marker-end",
link.attr("marker-mid",

Credits to @Phrogz for showing the way.

talkol
  • 12,564
  • 11
  • 54
  • 64
10

I took a slightly different approach from Phrogz. I tried removing the path's marker and instead draw it using a new path that goes to the arc's midpoint and whose stroke is invisible. Computing the midpoint is a little finicky so if you want to change the characteristics of the arc you might be better served with Phrogz's approach, or some hybrid.

In this case the trig is not so bad because if you look closely you will notice the arc used to generate the links are from a circle with the same radius and the distance between the nodes. So you have an equilateral triangle and all you need to do really is compute the distance passed the midpoint of the link line segment to the arc midpoint.

I think I need a diagram to explain:

Find arc midpoint So triangle ABC is equilateral and EC bisect AB. Since we have coordinates for A and B finding M is easy (average the coordinates). It means we also know the slope from A to B (delta y / delta x) so the slope from M to E is the negative inverse. Knowing M and the slope to E means we are almost there, we just need to know how far to go.

To do that we use the fact that ACM is a 30-60-90 triangle and so

|CM| = sqrt(3) * |AM|

but |AM| = |AB| / 2 and |CE| = |AB|

So

|ME| = |AB| - sqrt(3) * |AB| / 2

In order for this length to make sense we will have to normalize it with the length of the slope vector, which we already know is the same as the circle's radius.

Anyway, putting it all together you get:

var markerPath = svg.append("svg:g").selectAll("path.marker")
  .data(force.links())
  .enter().append("svg:path")
  .attr("class", function(d) { return "marker_only " + d.type; })
  .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });


... later in the tick handler

markerPath.attr("d", function(d) {
  var dx = d.target.x - d.source.x,
      dy = d.target.y - d.source.y,
      dr = Math.sqrt(dx * dx + dy * dy);

  // We know the center of the arc will be some distance perpendicular from the
  // link segment's midpoint. The midpoint is computed as:
  var endX = (d.target.x + d.source.x) / 2;
  var endY = (d.target.y + d.source.y) / 2;

  // Notice that the paths are the arcs generated by a circle whose 
  // radius is the same as the distance between the nodes. This simplifies the 
  // trig as we can simply apply the 30-60-90 triangle rule to find the difference
  // between the radius and the distance to the segment midpoint from the circle 
  // center.
  var len = dr - ((dr/2) * Math.sqrt(3));

  // Remember that is we have a line's slope then the perpendicular slope is the 
  // negative inverse.
  endX = endX + (dy * len/dr);
  endY = endY + (-dx * len/dr);

  return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + endX + "," + endY;
});

Check it out here. Note that my path css for the marker path's is set to a transparent red, normally you would want to set stroke to 'none' to hide the lines.

Superboggly
  • 5,804
  • 2
  • 24
  • 27
  • 1
    Great explanation and effort to provide a working example. I still prefer using markers, but I'd give you more than +1 if I could. – Phrogz Apr 01 '13 at 22:39
  • Thanks! Really I think the polyline is more generally flexible, but I like being able to use the arc path. So I feel a combination of our solutions could work really well. – Superboggly Apr 01 '13 at 22:47
  • Oh, but you could easily use an arc path with mid marker: just split it into two arcs going halfway each. – Phrogz Apr 01 '13 at 22:50
  • Yes! - but then you still have to compute that arc midpoint, which is really most of what my explanation was about. That's why I think it would work well together with your solution. – Superboggly Apr 02 '13 at 00:20
  • I second the great explanation and diagram! Awesome work! I ended up choosing the alternative method (with marker-mid) but this was an awesome answer nonetheless. – talkol Apr 02 '13 at 07:53
  • Isn't it `|ME| = |AB| - sqrt(3) * |AB| / 2`? – cwarny Jan 13 '15 at 19:28
  • You are correct cwarny! At least I got it right in the code! – Superboggly Jan 15 '15 at 18:26