How to place multiple evenly-spaced arrowheads along an SVG line?
Asked Answered
S

2

59

I am new to SVG and I am trying to draw a straight line between two points. I managed so far by using this command:

<line x1="50" y1="50" x2="150" y2="150" style="stroke:rgb(255,255,0); stroke-width:2" stroke-dasharray="5,3" />"

What is the simplest way to add tiny triangles or arrow heads (evenly spaced) over this line in order to indicate the direction?

Edit 1:

Just to be more clear, I am not after an arrow at the end of the line, but multiple triangles (evenly spaced) along the whole line. If possible, I would like to replace each dash in the dashed line with a triangle pointing in the direction of the line.

Edit 2

Based on Phrogz' suggestion, I created a page as shown below, but nothing is showing up. What am I doing wrong?

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
<link href="css/com.css" rel="stylesheet" type="text/css" />
</head>
<body style="background:none;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 70 90">
<defs>
<marker id="t" markerWidth="4" markerHeight="4"
        orient="auto" refY="2">
  <path d="M0,0 L4,2 0,4" />
</marker>
</defs>
<polyline points="0,0 0,50 20,70 40,10 42,8 44,10, 46,14 50,50" />
</svg>
<script type="text/javascript">
midMarkers(document.querySelector('polyline'),6);

    // Given a polygon/polyline, create intermediary points along the
    // "straightaways" spaced no closer than `spacing` distance apart.
    // Intermediary points along each section are always evenly spaced.
    // Modifies the polygon/polyline in place.
    function midMarkers(poly,spacing){
        var svg = poly.ownerSVGElement;
        for (var pts=poly.points,i=1;i<pts.numberOfItems;++i){
            var p0=pts.getItem(i-1), p1=pts.getItem(i);
            var dx=p1.x-p0.x, dy=p1.y-p0.y;
            var d = Math.sqrt(dx*dx+dy*dy);
            var numPoints = Math.floor( d/spacing );
            dx /= numPoints;
            dy /= numPoints;
            for (var j=numPoints-1;j>0;--j){
                var pt = svg.createSVGPoint();
                pt.x = p0.x+dx*j;
                pt.y = p0.y+dy*j;
                pts.insertItemBefore(pt,i);
            }
            if (numPoints>0) i += numPoints-1;
        }
    }
</script>
</body>
</html>
Smarmy answered 4/8, 2012 at 12:57 Comment(2)
See also How do you draw an arrow at the end of a Bézier curve in SVG/Raphaël?Eager
It's important to note that the marker-mid attribute only has effect if your path has vertexes between start/end.Reiterate
E
174

Based on a clarification of the question, here's an implementation of creating intermediary points along a <polyline> element such that the marker-mid="url(#arrowhead)" attribute will work. See below that for an introduction to markers and arrowheads.

Demo: http://jsfiddle.net/Zv57N/

midMarkers(document.querySelector('polyline'),6);

// Given a polygon/polyline, create intermediary points along the
// "straightaways" spaced no closer than `spacing` distance apart.
// Intermediary points along each section are always evenly spaced.
// Modifies the polygon/polyline in place.
function midMarkers(poly,spacing){
  var svg = poly.ownerSVGElement;
  for (var pts=poly.points,i=1;i<pts.numberOfItems;++i){
    var p0=pts.getItem(i-1), p1=pts.getItem(i);
    var dx=p1.x-p0.x, dy=p1.y-p0.y;
    var d = Math.sqrt(dx*dx+dy*dy);
    var numPoints = Math.floor( d/spacing );
    dx /= numPoints;
    dy /= numPoints;
    for (var j=numPoints-1;j>0;--j){
      var pt = svg.createSVGPoint();
      pt.x = p0.x+dx*j;
      pt.y = p0.y+dy*j;
      pts.insertItemBefore(pt,i);
    }
    if (numPoints>0) i += numPoints-1;
  }
}

The above code modifies an existing <polyline> element to add points every spacing units along each straight edge. Combine this with marker-mid to place a rotated marker at every vertex, and you have the ability to draw arbitrarily complex shapes/graphics consistently along your path.

Bird Tracks

Although the code spaces out the points evenly along each segment (so that no unsightly 'bunching up' occurs at corners) as the above demo shows the code does not remove any points that you already have in your path that are closer together than the spacing value.


(Original "Intro to Markers" answer follows.)


You want to define an SVG <marker> element and add the marker-start="…" and/or marker-end="…" attributes to your line. Using a marker copies any arbitrary shape onto the end(s) of your path, and (with orient="auto") rotates the shape to match.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 -100 200 200">
  <defs>
    <marker id='head' orient='auto' markerWidth='2' markerHeight='4'
            refX='0.1' refY='2'>
      <path d='M0,0 V4 L2,2 Z' fill='red' />
    </marker>
  </defs>    
  <path
    marker-end='url(#head)'
    stroke-width='5' fill='none' stroke='black'  
    d='M0,0 C45,45 45,-45 90,0'
    />    
</svg>​

Demo: http://jsfiddle.net/Z5Qkf/1/

Curved line with arrowhead

In the above:

  • orient="auto" causes the marker to rotate with the line
  • markerWidth and markerHeight define a bounding box (like a viewBox) to use for the marker.
    • Note that these are then scaled by the stroke-width of the final line; having a height of "4" causes it to be 20 units wide in the final drawing (4×5).
  • refX and refY define where the 'origin' is when placing the shape on the end of the path
    • I've used refX="0.1" to ensure that the marker overlaps the end of the line slightly
  • For the auto orientation to work correctly you want the "forward" direction of the marker to be in the +x direction. (The red path used for the marker looks like this ▶ when unrotated.)
  • You can adjust the fill-opacity and stroke-opacity of the marker and/or line independently, but changes to the opacity of the line will affect the drawn marker, as well.
    • Since the line and marker are overlapping, if you lower the fill-opacity of the marker you would see the overlap; however, if you lower the opacity of the line itself then the marker is composited fully-opaque over the line and the combination of the two are then lowered in opacity.
      enter image description here

If you want arrows along the length of the line, you will need to use marker-mid="…" with either <path> or <polyline> and interim points along the line.

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

enter image description here

The only problem is that any point that changes direction along the line messes up the orientation; this is why in the demo I've used a Bézier curve to round the corner so that the midpoint on the line is along a straight section.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 -100 200 200">
<defs>
  <marker id='mid' orient="auto"
    markerWidth='2' markerHeight='4'
    refX='0.1' refY='1'>
    <!-- triangle pointing right (+x) -->
    <path d='M0,0 V2 L1,1 Z' fill="orange"/>
  </marker>
  <marker id='head' orient="auto"
    markerWidth='2' markerHeight='4'
    refX='0.1' refY='2'>
    <!-- triangle pointing right (+x) -->
    <path d='M0,0 V4 L2,2 Z' fill="red"/>
  </marker>
</defs>

<path
  id='arrow-line'
  marker-mid='url(#mid)'
  marker-end='url(#head)'
  stroke-width='5'
  fill='none' stroke='black'  
  d='M0,0 L20,20 C40,40 40,40 60,20 L80,0'
  />

</svg>​

To do this procedurally, you can use JavaScript and the getPointAtLength() command for a path to sample the path.

Eager answered 4/8, 2012 at 15:40 Comment(12)
Thanks for the detailed information, but I am afraid I was not clear enough. Sorry about that. What I am after is multiple arrow heads along the length of the line.Smarmy
@Smarmy Ah, I see. See my edit. The answer is, sadly, not as simple or clear-cut.Eager
The good news is that it will always be a straight line for which I have the starting X,Y co-ordinates and the ending X,Y co-ordinates. The bad news is that I do not understand how to draw a straight line using "M0,0 L20,20....etc" Any pointers to how to translate a set of starting and ending co-ordinates into what appear to be path defining commands? Remember, I am new to SVG. Thanks!Smarmy
@Smarmy Courtesy of a cross-country flight, I had time to make a demo for you. See the top of the now-updated answer.Eager
Thanks again Phrogz. I tried to use the code you provided as shown in Edit 2, but nothings is coming up. What am I doing wrong?Smarmy
@Smarmy error on line 27 at column 57: error parsing attribute name. Your problem is that you've saved the document as XHTML, but the JavaScript has a literal < in it. This makes for invalid XHTML. You need to either wrap your script contents in <![CDATA[ … ]]> or express the SVG as pure HTML5. In the future, when you run into problems you should be sure to validate your markup.Eager
Oh, and you forgot to include the CSS from the fiddle in your page, which is where the marker-mid was being applied, as well as other important stylings. You can start with this standalone demo as a working point.Eager
OK. Finally got it to work (had some trouble loading it with ajax inside my page). One last thing before I mark this question as "answered"....how do I add more poly lines? Repeating the line <polyline points="0,100 200,100" /> with different co-ordinates does not seem to work.Smarmy
You're the man! Thanks! I was getting an error of the type "polys.forEach is not a function" which was fixed by replacing polys.forEach(function(p){ midMarkers(p,10); }); with for(i=0; i<polys.length; i++) {midMarkers(polys[i],10);}.Smarmy
@Smarmy How silly of me. Alternatively: [].forEach.call( polys, function(p){ midMarkers(p,10) } );Eager
best explanation of marker orientation and refX, refY I could find, thanksKetty
just as a reference, here is the marker for left arrow <defs> <marker id='starthead' orient="auto" markerWidth='2' markerHeight='4' refX='2' refY='2'> <!-- triangle pointing left (+x) --> <path d='M0,2 L2,0 V4 Z ' fill="red"/> </marker> </defs>Physiography
S
11

Just want to add some helpful links and examples:

1. The arrow can be quadratic enter image description here

2. Cubic curve enter image description here

Documentation: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

Demo: both kinds of arrows implemented here:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>

  
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 -100 200 200">
<defs>
  <marker id='head' orient="auto"
    markerWidth='2' markerHeight='4'
    refX='0.1' refY='2'>
    <!-- triangle pointing right (+x) -->
    <path d='M0,0 V4 L2,2 Z' fill="black"/>
  </marker>
</defs>

<path
  id='arrow-line'
  marker-end='url(#head)'
  stroke-width='1'
  fill='none' stroke='black'  
  d='M0,0 Q45,-20 90,0'
  />
    
<path
  id='arrow-line'
  marker-end='url(#head)'
  stroke-width='1'
  fill='none' stroke='black'  
  d='M0,50 C10,30 80,30 90,50'
  />

</svg>
</body>
</html>
Swartz answered 20/9, 2018 at 21:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.