block by veltman 2ffca9dfddbfe67dc8e70a08fa429d6b

Directional arrows #4

Full Screen

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

This method is definitely overkill. It calculates one offset point per three pixels along the original path, and then walks that list of points one at a time to ensure that the resulting arrows are roughly evenly spaced and roughly the same length, regardless of the angles of their curves. You could make this slower + more precise, or faster + less precise, by changing the number of pixels per step.

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

index.html

<!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="//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 = 15,
      numArrows = 20;

  var line = d3.line();

  d3.select("svg").selectAll(".arrow")
    .data(arrows(20))
    .enter()
    .append("path")
      .attr("class", "arrow")
      .attr("marker-end", "url(#arrowhead)")
      .attr("d", line);

  function arrows(numArrows) {

    var points = [],
        point,
        previousLength = outerLength = 0;

    test.setAttribute("d", "");

    for (var i = 0; i < length; i += 3) {
      points.push(point = offsetPoint(i));
      test.setAttribute("d", line(points));
      previousLength = outerLength;
      outerLength = test.getTotalLength();
      point.push(outerLength - previousLength);
    }

    var interval = outerLength / numArrows,
        arrows = [[]],
        position = 0;

    points.forEach(function(point){

      position += point[2];

      if (position >= interval) {
        arrows.push([]);
        position = point[2];
      }

      if (position <= desiredArrowLength) {
        arrows[arrows.length - 1].push(point);
      }

    });

    return arrows.slice(0, numArrows);

  }

  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>