quaintitative

I write about my quantitative explorations in visualisation, data science, machine and deep learning here, as well as other random musings.

For more about me and my other interests, visit playgrd or socials below


Categories
Subscribe

d3.js vs. p5.js

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 two spirals in this post. The visualisation can be viewed at this link.

I won’t go through the boilerplate HTML and CSS code, but will 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:

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.


Articles

Comparing Prompts for Different Large Language Models (Other than ChatGPT)
AI and UIs
Listing NFTs
Extracting and Processing Wikidata datasets
Extracting and Processing Google Trends data
Extracting and Processing Reddit datasets from PushShift
Extracting and Processing GDELT GKG datasets from BigQuery
Some notes relating to Machine Learning
Some notes relating to Python
Using CCapture.js library with p5.js and three.js
Introduction to PoseNet with three.js
Topic Modelling
Three.js Series - Manipulating vertices in three.js
Three.js Series - Music and three.js
Three.js Series - Simple primer on three.js
HTML Scraping 101
(Almost) The Simplest Server Ever
Tweening in p5.js
Logistic Regression Classification in plain ole Javascript
Introduction to Machine Learning Right Inside the Browser
Nature and Math - Particle Swarm Optimisation
Growing a network garden in D3
Data Analytics with Blender
The Nature of Code Ported to Three.js
Primer on Generative Art in Blender
How normal are you? Checking distributional assumptions.
Monte Carlo Simulation of Value at Risk in Python
Measuring Expected Shortfall in Python
Style Transfer X Generative Art
Measuring Market Risk in Python
Simple charts | crossfilter.js and dc.js
d3.js vs. p5.js for visualisation
Portfolio Optimisation with Tensorflow and D3 Dashboard
Setting Up a Data Lab Environment - Part 6
Setting Up a Data Lab Environment - Part 5
Setting Up a Data Lab Environment - Part 4
Setting Up a Data Lab Environment - Part 3
Setting Up a Data Lab Environment - Part 2
Setting Up a Data Lab Environment - Part 1
Generating a Strange Attractor in three.js
(Almost) All the Most Common Machine Learning Algorithms in Javascript
3 Days of Hand Coding Visualisations - Day 3
3 Days of Hand Coding Visualisations - Day 2
3 Days of Hand Coding Visualisations - Day 1
3 Days of Hand Coding Visualisations - Introduction