How to place multiple evenly-spaced arrowheads along an SVG line?
Solution 1:
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.
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/
In the above:
-
orient="auto"
causes the marker to rotate with the line -
markerWidth
andmarkerHeight
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).
- Note that these are then scaled by the
-
refX
andrefY
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
- I've used
- 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
andstroke-opacity
of the marker and/or line independently, but changes to theopacity
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 theopacity
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.
-
Since the line and marker are overlapping, if you lower the
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/
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.
Solution 2:
Just want to add some helpful links and examples:
1. The arrow can be quadratic
2. Cubic curve
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>