p5/D3 Cookbook

Combining the power of D3.js with the simplicity of p5.js

Generating a Line with D3 and drawing with p5

D3 Concepts Used

d3.svg.line(), d3.range()

This example shows you how to use D3 to generate a variety of paths through control points.

D3 offers a powerful path generation tool. This tool takes sets of points [[x1,y1],[x2,y2],...] and translates them into an SVG Path string. Newer browsers offer a path2d(SVGpathString) that enables the drawing of SVG defined paths in the canvas window.

SVG defines its paths with a text string. These text strings look like:

<path d="M10 10 C 20 20, 40 20, 50 10" />
<path d="M70 10 C 70 20, 120 20, 120 10" />
<path d="M130 10 C 120 20, 180 20, 170 10" />

D3.svg.line() takes an array and converts the items in the array into the path string. To do this, we must first create the line generating function, then run the function with the array of points. This function will return the path string that we then give to p5.

In this example, the array of data that will become our line is:

[3,5,3,6,7,8,2,3]

The lineGenerator function is created and stored in the variable, lineGeneratorBasis.

var lineGeneratorBasis = d3.svg.line()
  .x(function(data, index) { return map(i,0,8,0,width-margin*2); })
  .y(function(data, index) { return map(d,0,10,0,height-margin*2); })
  .interpolate('basis');

When we run the lineGeneratorBasis function passing in the array of data, the function will loop through each item in our data array. For each point, it will look at the functions we added in .x() and .y() to understand how our data array should be translated into (x,y) points.

Let's see how the lineGeneratorBasis translates the first item—the integer 3— into (x,y) points. lineGeneratorBasis sees that we have set the function in .x() to:

function(data, index) { return map(index,0,8,0,width-margin*2); });

lineGeneratorBasis takes that function and runs it for the first item in our array replacing data with the item's data—the integer 3—and replacing index, with the location in the array of our item.

function(3, 1) { return map(1,0,8,0,width-margin*2); });

lineGeneratorBasis repeats this for all the items in the data array. It does this for both an x value and a y value. Once the function has all the (x,y) positions calculated, it then produces the path string.

var svgPathString = lineGeneratorBasis(dataArray) // eg "M70 10 C 70 20, 120 20, 120 10"

D3.svg.line() can be customized to produce different interpolations between the (x,y) points. Stepped lines, gently sloping lines, straight lines can all be achieved by changing the .interpolate('interpolationType') function. See the full list of interpolation types supported by D3.

Once we have the path string stored, we can now draw it to canvas using path2d(). p5 does not yet have an interface for this handy function, but by accessing the <canvas> element directly, we can use it in p5 along with all its useful commands.

To do this, we first must store in a variable a referencev to the <canvas>:

var c = createCanvas(width, height);
var canvas = c.canvas;

With that reference stored, we can then draw the path:

canvas.getContext('2d').stroke(svgPathString);

The Complete Code

function setup() {
  var margin = 30;
  width = 700;
  height = 300;
 
  var c = createCanvas(width, height);
  var canvas = c.canvas;

  push();
  translate(margin,margin);
  
  var data = d3.range(8).map(function() { return 1+Math.random() * 10; });
  
  var lineGeneratorBasis = d3.svg.line()
    .x(function(data,index) { return map(index,0,8,0,width-margin*2); })
    .y(function(data,index) { return map(data,0,10,0,height-margin*2); })
    .interpolate('basis');
  
  var lineGeneratorLinear = d3.svg.line()
    .x(function(data,index) { return map(index,0,8,0,width-margin*2); })
    .y(function(data,index) { return map(data,0,10,0,height-margin*2); })
    .interpolate('linear');
  
  var lineGeneratorStep = d3.svg.line()
    .x(function(data,index) { return map(index,0,8,0,width-margin*2); })
    .y(function(data,index) { return map(data,0,10,0,height-margin*2); })
    .interpolate('step-before');
  

  stroke('black');
  var cPathBasis = new Path2D(lineGeneratorBasis(data));
  var cPathLinear = new Path2D(lineGeneratorLinear(data));
  var cPathStep = new Path2D(lineGeneratorStep(data));
  
  strokeWeight(3);
  stroke('#033E8C');
  canvas.getContext('2d').stroke(cPathBasis);
  stroke('#F2B705');
  canvas.getContext('2d').stroke(cPathLinear);
  stroke('#00D96F');
  canvas.getContext('2d').stroke(cPathStep);
  
  fill('#fff');
  stroke('#000');
  strokeWeight(1);
  for(var i = 0; i < data.length; i++) {
    ellipse(map(i,0,8,0,width-margin*2),map(data[i],0,10,0,height-margin*2), 10, 10)
  }
     
}