<h3>SVG path (red) versus canvas path (black) performance</h3>
<p>This test draws 100 - 5000 bars using both APIs and measures the inter-frame timings.</p>
<p>Results are shown on a linear axis, bar count increases left-to-right.</p>
<p>Solid triangle indicates testing in progress.</p>

<svg width="640" height="480">
  <path/>
</svg>

<canvas width="640" height="480"></canvas>

body {
  font-family: 'Open Sans', sans-serif;
  font-size: 14px;
}

svg, canvas {
  position: absolute;
  top: 25px;
  left: 25px;
  opacity: 0.5;
}

canvas {
  left: 30px;
}

path {
  stroke: red;
}
const select = d3_selection.select;
const range = d3_array.range;
const extent = d3_array.extent;
const linear = d3_scale.scaleLinear;
const barGenerator = fc_shape.bar;

const render = (svgData, canvasData) => 
  new Promise((resolve, reject) => {
    const timings = {
      count: svgData.length
    };

    const x = linear()
      .range([10, 630])
      .domain(extent(svgData, (d, i) => i));

    const y = linear()
      .range([470, 10])
      .domain(extent(svgData, (d, i) => d));

    const path = select('svg')
      .selectAll('path')
      .data([svgData]);

    const svgBar = barGenerator()
      .x((d, i) => x(i))
      .y((d) => y(d))
      .height((d) => y(0) - y(d))
      .width(1)
      .verticalAlign('top');

    timings.startSvg = performance.now();
    path.attr('d', svgBar);
    requestAnimationFrame(() => {
      timings.endSvg = performance.now();

      const canvas = document.querySelector('canvas');

      const ctx = canvas.getContext('2d');

      const canvasBar = barGenerator()
        .context(ctx)
        .x((d, i) => x(i))
        .y((d) => y(d))
        .height((d) => y(0) - y(d))
        .width(1)
        .verticalAlign('top');

      timings.startCanvas = performance.now();
      canvas.width = canvas.width;
      canvasBar(canvasData);
      ctx.stroke();
      requestAnimationFrame(() => {
        timings.endCanvas = performance.now();
        resolve(timings);
      });
    });
  });

let promise = Promise.resolve();
let results = [];
const recordResults = (results, timings) => 
  [...results, {
    count: timings.count,
    canvas: timings.endCanvas - timings.startCanvas,
    svg: timings.endSvg - timings.startSvg
  }];
for (let i = 100; i <= 5000; i+=100) {
  promise = promise.then((timings) => {
    if (timings) {
      results = recordResults(results, timings);
    }
    const data = range(0, i)
    return render(data, data);
  });
}
promise.then((timings) => {
  results = recordResults(results, timings);
  render(results.map((d) => d.svg), results.map((d) => d.canvas));
});
View Compiled

External CSS

  1. https://fonts.googleapis.com/css?family=Open+Sans

External JavaScript

  1. https://npmcdn.com/d3-array@0.7.1
  2. https://npmcdn.com/d3-interpolate@0.7.0
  3. https://npmcdn.com/d3-scale@0.6.4
  4. https://npmcdn.com/d3-selection@0.7.0
  5. https://npmcdn.com/d3-path@0.1.5
  6. https://npmcdn.com/d3fc-shape@3.0.0