block by tmcw 4079745

One-way markers demo

Full Screen

SVG has line markers, but they have many failings. This example emulates markers-along-a-path by filling a textPath with the right number of glyphs.

The original version of this failed because Firefox does not support letter-spacing on SVG elements. The second version failed because it has a bug in getComputedTextLength. The third failed because it does not preserve space according to xml:space.

Hence this is not compatible with Firefox currently. It does work, imperfectly, in Opera & Chrome, though their kerning and interpretation of the getComputedTextLength API is much different than Chrome’s.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>SVG Swarm</title>
<style>

svg {
  position: absolute;
  top: 0;
}

use {
    fill:none;
    stroke:#000;
    stroke-width:2;
}

</style>
<div id="fps">FPS: <span>?</span></div>
<input type='checkbox' id='reverse' /><label for='reverse'>reverse</label>
<script src="//d3js.org/d3.v2.min.js?2.9.1"></script>
<script>

var data = [];
for (var t = 0; t < 12; t += 0.1) {
    data.push([
        Math.cos(t * 0.5),
        Math.sin(t)
    ]);
}

var width = 960,
    height = 500;

var x = d3.scale.linear()
    .domain([-2, 2])
    .range([0, width]);

var y = d3.scale.linear()
    .domain([-2, 2])
    .range([0, height]);

var time0 = Date.now(),
    time1;

var fps = d3.select("#fps span");

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

var defs = svg.append('defs');

var path = defs.selectAll('path')
    .data([data])
    .enter()
    .append('path').attr('id', 'p')
    .attr("d", function(d) {
      return 'M' + d.map(function(a) {
          return [x(a[0]), y(a[1])];
      }).join('L');
    });

var p = svg.selectAll('use')
    .data([data])
    .enter()
    .append("use").attr('xlink:href', '#p');

var arrow = svg.append('text');
var alength = arrow.text('►  ►').node().getComputedTextLength() - arrow.text('►').node().getComputedTextLength();
arrow.remove();

var tp = svg.append("text").attr('dy', 7).append('textPath');

var fpsqueue = [];
var forward = true;

var z = 0.5;

var rev = d3.select('#reverse');

d3.timer(function() {

  data = [];
  var n = 0;
  for (var t = 0; t < 12; t += 0.001, n++) {
      data[n] = [
          Math.cos(t * z),
          Math.sin(t)
      ];
  }
  z += 0.002;
  if (z > 4) z = 0.5;

  if (rev.node().checked) data.reverse();

  tp.attr('xlink:href', '#p').text(function() {
          return (new Array(Math.floor(path.node().getTotalLength() / alength))).join('►  ');
      });

  path.data([data]).attr("d", function(d) {
    return 'M' + d.map(function(a) {
        return [x(a[0]), y(a[1])];
    }).join('L');
  });

  time1 = Date.now();
  if (fpsqueue.length === 200) { fps.text(d3.mean(fpsqueue).toFixed(3)); fpsqueue = []; }
  fpsqueue.push(Math.round(1000 / (time1 - time0)));
  time0 = time1;
});
</script>