playgrdstar
creative coding space
5 min readAug 17, 2018

--

Generating Art — d3.js vs. p5.js

[Post is also available at quaintitative.com]

As I had previously covered in one of my 3 Days of Hand Coding Visualisations posts, there are a number of differences between d3.js and p5.js libraries. The most notable difference would be that d3.js is mainly based on SVG elements, while p5.js uses HTML canvas elements. I caveat that d3.js uses SVG elements ‘mainly’ as it is possible to use HTML canvas elements in d3.js.

SVG elements don’t get pixellated as shapes and lines in SVG are stored as basic components in code; while HTML canvas elements are raster images plotted pixel by pixel to the screen.

Not the best or most accurate explanation, but just know that if you want sharp images, use SVG elements, but if you want speed, use HTML canvas elements.

Coding and using such elements in d3.js, and p5.js are not that different though.

We shall draw the two spirals you saw at the start of this post. The one on the left is drawn in d3.js, while the one on the right is drawn in p5.js.

I won’t go through the boilerplate HTML and CSS code again, and shall focus on the scripts used to draw the spirals.

Drawing with d3.js

The first part of the script (right at the end) draws the spiral using d3.js.

We set all the basic variables and values we need. The ones below are self explanatory, or have been covered before.

var colorsPalette = ['pink', 'skyblue', 'deepskyblue', 'royalblue', 'steelblue', 'lavender', 'indigo', 'violet', 'pink', 'deeppink'];
var width = 500;
var height = 500;

We then find the section (‘div’) with an id of ‘d3canvas’ and create a SVG element there.

var svg = d3.select('#d3canvas').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate('+width/2+','+height/2+')');

The next set of variables are needed for us to draw the spirals. Basically, the number of arcs (‘lines’), the arc thickness (‘line thickness’), the total circumference we want to cover with the arcs (i.e. how many rounds), and the space between each of the arcs.

var lines = 1000;
var linethickness = 0.1;
var circumference = Math.PI * 20;
var space = circumference/lines;

Then we create an array of 1000 arcs with varying properties.

var linesdata = d3.range(lines).map(function(d,i){
return{
startAngle: (i*space),
endAngle: (i*space)+linethickness*0.5,
outerRadius: width/20 * i/100,
strokeWidth: 0.05,
fillopacity: 0.1,
fillcolor: colorsPalette[i%10]
}
});

If you want to see what is in the linesdata array, just open the console in Chrome or any other browser, and type console.log(linesdata).

Next we set up the arc function in d3.js.

var d3arc = d3.arc().innerRadius(50);

And we then start adding the arcs, one by one, each corresponding to a datapoint in the linesdata array, and also set the properties of each of the arcs to the properties we set when creating the array linesdata.

var segment = svg.selectAll('segment')
.data(linesdata)
.enter()
.append('path')
.attr('class', 'segment')
.style('fill', function(d){
return d.fillcolor;
})
.style('stroke', 'steelblue')
.style('fill-opacity', function(d){
return d.fillopacity;
})
.style('stroke-width', function(d){
return d.strokeWidth;
})
.transition()
.duration(2000)
.delay(function(d,i){
return i*20;
})
.attrTween('d', aTween());

The last line .attrTween('d', aTween()) is not absolutely necessary. More of a flourish at the end. It basically allows us to morph each of the arcs smoothly between their outerRadius properties, instead of jumping to each step abruptly, using the function -

function aTween(){
return function(d){
var interpolate = d3.interpolateNumber(1, d.outerRadius);
return function(t){
d.outerRadius = interpolate(t);
return d3arc(d);
};
};

We shall however not go into this at this point, as tweening deserves a post (or more than one post) by itself.

Drawing with p5.js

We’ve covered the basics of p5.js in the 3 Days of Hand Coding Visualisations posts. But just as a quick recap. p5.js was created for artists. Each ‘sketch’ in p5.js is made up of one ‘setup’ function and one ‘draw’ function. The basic attributes (such as canvas size) are set in ‘setup’, and we draw in the ‘draw’ function. The ‘draw’ function is always drawing as long as the browser is on the webpage.

In this script, we first set the palette (in colorsPalette), and other attributes such as the width and height of the canvas. We also state that we want the canvas to be within the section with an id of ‘p5canvas’.

function setup() {
colorsPalette = [color(146, 167, 202,30),
color(186, 196, 219,30),
color(118, 135, 172,30),
color(76, 41, 81,30),
color(144, 62, 92,30),
color(178, 93, 119,30),
color(215, 118, 136,30),
color(246, 156, 164,30),];
var width = 500;
var height = 500;
// var width = d3.select('#p5canvas').node().getBoundingClientRect().width;
// var height = width;
var canvas = createCanvas(width, height);
canvas.parent('p5canvas');
// createCanvas(800, 800);
background(0,0);
ellipseMode(CENTER);
}

The line ellipseMode(CENTER) basically sets the mode we will be working in for ellipses or arcs. CENTER means that the x and y coordinates that we set refer to the center of the circle or arc we draw (rather than say the upper left corner).

Next, we draw the arcs. We use the same linesdata array that we created earlier to plot each and every one of these arcs. Each arc function takes the following inputs to set the properties of the arc — arc(x, y, w, h, start, stop, mode)

where:

  • x: x-coordinate of the arc’s ellipse
  • y: y-coordinate of the arc’s ellipse
  • w: width of the arc’s ellipse by default
  • h: height of the arc’s ellipse by default
  • start: angle to start the arc, specified in radians
  • stop: angle to stop the arc, specified in radians
  • mode: optional parameter to determine the way of drawing the arc. either CHORD, PIE or OPEN

Experiment with these properties and you will get a good feel of how it works.

function draw() {
// rotate(20);
fill(colorsPalette[floor(random(8))],100);
stroke(colorsPalette[floor(random(8))],255);
strokeWeight(0.5);

arc(width/2,height/2,linesdata[i].outerRadius*2,linesdata[i].outerRadius*2,
linesdata[i].startAngle,linesdata[i].endAngle,PIE)

# Stop drawing (or looping) when we have gone through all elements in the linesdata array
if(i<linesdata.length-1){
i++;
} else {
noLoop();
}
}

When the draw function has drawn out each and every data point in the linesdata array, the noLoop function stops the draw function from looping anymore.

My sense is that you would find the p5.js way to do this more intuitive (as it was created with artists in mind).

However, looking at them both side by side does give one a better sense of how d3.js works.

The visualisation can be viewed at this link, and the code here.

--

--

playgrdstar
creative coding space

ming // gary ang // illustration, coding, writing // portfolio >> playgrd.com | writings >> quaintitative.com