Skip to content

Instantly share code, notes, and snippets.

@veltman
Created August 5, 2016 16:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save veltman/ebc18f6101169705f8e64aaadae36185 to your computer and use it in GitHub Desktop.
Save veltman/ebc18f6101169705f8e64aaadae36185 to your computer and use it in GitHub Desktop.
Directional arrows #3

Placing some offset directional arrows along a path, take 3.

This method is total overkill. At each desired arrow point along the path, it calculates points along the path every 2 pixels and its corresponding offset point for an arrow, until the arrow is roughly the desired length, and then uses an interpolator to smooth it out. This ensures that arrows in tight curves and straight arrows still end up roughly the same length even though the points offset from the path end up closer together.

marker-segment and marker-pattern should make this easier eventually.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
path {
fill: none;
stroke: #fc0;
stroke-width: 2px;
}
.arrow {
stroke-width: 1px;
stroke: #444;
}
#arrowhead path {
stroke: none;
fill: black;
}
</style>
</head>
<body>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="960" height="500">
<path id="loop" d="M636.5,315c-0.4-18.7,1.9-27.9-5.3-35.9
c-22.7-25-107.3-2.8-118.3,35.9c-7,24.4,20.6,37.2,16,71c-4,29.6-30.8,60.7-56.5,61.1c-30.8,0.4-32.9-43.8-81.7-70.2
c-50.9-27.6-110.1-12.9-125.2-9.2c-66.1,16.4-82.2,56.9-109.2,47.3c-38-13.6-55.9-112.1-19.8-143.5c39-34,121.2,27.7,148.1-3.8
c18-21.1,3.1-74.3-25.2-105.3c-31.1-34.1-70.1-32.4-105.3-76.3c-8.2-10.2-16.9-23.8-15.3-39.7c1.2-11.4,7.5-23.3,15.3-29
c33.8-25,101.6,62.6,193.1,59.5c40.1-1.3,38.7-18.5,99.2-38.9c126.2-42.6,242.4-4.9,297.7,13c54.7,17.7,105.4,35,129.8,82.4
c13,25.3,22.9,67.7,4.6,87c-11.6,12.3-25.1,5.1-46.6,20.6c-2.8,2-28.9,21.4-32.1,49.6c-3.1,27.4,18.7,35,29,70.2
c8.8,30.1,8.5,77.8-18.3,99.2c-32.3,25.8-87,0.6-100-5.3c-69.6-32-67.2-88.4-73.3-109.2z"/>
<defs>
<marker id="arrowhead" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
<path id="test" />
</defs>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var path = document.getElementById("loop"),
test = document.getElementById("test"),
length = path.getTotalLength(),
desiredArrowLength = 40,
offset = 10,
numArrows = 20;
var line = d3.line()
.curve(d3.curveCardinal.tension(0.5));
d3.select("svg").selectAll(".arrow")
.data(d3.range(numArrows))
.enter()
.append("path")
.attr("class", "arrow")
.attr("marker-end", "url(#arrowhead)")
.attr("d", curve);
function curve(d) {
var l = length * d / numArrows,
points = [],
i = 0;
test.setAttribute("d", "");
// Could get even more exact by varying the step...
while (test.getTotalLength() < desiredArrowLength) {
points.push(offsetPoint(l + i * 2));
test.setAttribute("d", line(points) + "L" + offsetPoint(l + i * 2 + 0.1))
i++;
}
return test.getAttribute("d");
}
function offsetPoint(l) {
var angle = angleAtLength(l) - Math.PI / 2,
point = pointAtLength(l);
return [
point[0] + offset * Math.cos(angle),
point[1] + offset * Math.sin(angle)
];
}
function pointAtLength(l) {
var xy = path.getPointAtLength(l);
return [xy.x, xy.y];
}
// Approximate tangent
function angleAtLength(l) {
var a = pointAtLength(Math.max(l - 0.01,0)), // this could be slightly negative
b = pointAtLength(l + 0.01); // browsers cap at total length
return Math.atan2(b[1] - a[1], b[0] - a[0]);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment