Fancy Visualization with Canvas Sketch

Yudhisteer Chintaram
4 min readOct 13, 2022

--

Continuing on my journey to discover creative visualization with JS, I stumbled onto this amazing library: Canvas Sketch. It is a collection of tools and modules which offers great opportunities for creating generative art.

I want to share how I started with a simple bouncing ball to finish with a collection of interlinking nodes.

  1. We start by fixing our canvas: 2048 x 2048. Basically, we want a box for our ball to bounce in.
  2. Next, we want to draw a ball on our canvas. We need to supply the (x, y) coordinates for the ball, the radius, the start angle, and the end angle.
context.fillStyle = 'black';
context.beginPath();
context.arc(1000, y, 20, 0, Math.PI*2); // x, y, radius, startAngle, endAngle
context.fill();

Note that we want the ball to bounce only in the vertical plane which means we keep the x-coordinate fixed and have a variable for the y-coordinate.

3. We then initialize the start position of the ball and add a velocity variable which will increment the position of the ball with each frame.

let y = 100; //position of ball
let velocity = 10; // velocity of ball
y += velocity; // increment position

4. With the code above, our ball will just start at y-position = 100 and fall down exiting the canvas. However, we want it to bounce so we need to set some limits.

if (y <= 100 || y >= 2000) {
velocity *= -1 ; //bounce
}

With an “if” condition, we set the boundaries for the ball. We reverse the sign of the velocity at the boundaries which equals changing direction as velocity is a vector quantity.

5. Now, we are more interested in not one ball but 100 balls moving randomly and bouncing the walls. Hence, we create a “Class” for balls that will draw, move and bounce:

let circles = [];
for (let i = 0; i < 100; i++) {
circles.push(new Circle(Math.random()*2048, Math.random()*2048, Math.random()*20))
}
// class to create circles
class Circle {
constructor(x,y,radius) {
this.x = x;
this.y = y;
this.radius = radius;
this.velocityX = Math.random()*4 - 2; // [-2, 2]
this.velocityY = Math.random()*4 - 2; // [-2, 2]
}
// draw function
draw(context){
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, Math.PI*2);
context.stroke();
}
//move function
move (){
this.x += this.velocityX; //position x changes
this.y += this.velocityY; //position y changes
}
// bounce function
bounce(width,height){
if (this.x <= 0 || this.x >= width) {
this.velocityX *= -1; //bounce in x-direction
}
if (this.y <= 0 || this.y >= height) {
this.velocityY *= -1; //bounce in y-direction
}}}

Notice that the x-y coordinates, radius, and velocity in both directions of the balls are made random.

6. Next, we want to draw lines connecting the balls. We first need to write a “for” loop to assign variable names to two balls and we do that for all balls in the array. Using the x-y position of these two balls, we draw a line between them.

// Draw line between circlesfor (let i = 0; i < circles.length; i++) {
const circl1 = circles[i];
for (let j = i+1; j < circles.length; j++) {
const circl2 = circles[j];
context.lineWidth = 10 line width
context.beginPath();
context.moveTo(circl1.x,circl1.y);
context.lineTo(circl2.x,circl2.y);
context.stroke();
}}

Notice that each ball is now connected to every ball in our box. Though impressive, this is not what we want.

I reduced that line width to see how the visualization differs. Still nice so far.

7. What we want is for the balls to be connected only if they are close enough. This means we need to calculate the distance between them using the Euclidean formula:

// function to calculate distance using Euclideanconst getDistance = (x1, x2, y1, y2) => {
const x= x1-x2;
const y= y1-y2;
return Math.sqrt(x*x + y*y);
}

8. Using the function, we use it to calculate the distance and set some threshold for drawing the lines:

//calculate distance
const dist = getDistance(circl1.x, circl2.x, circl1.y, circl2.y);
if (dist < 250) {
context.lineWidth = 10
context.beginPath();
context.moveTo(circl1.x,circl1.y);
context.lineTo(circl2.x,circl2.y);
context.stroke();
}

9. Finally, we want the line width to be responsive. That is, the closer the balls are to each other, the thicker the line will be, and vice versa. We use the distance to set the line width:

context.lineWidth = 10 - dist/25; //responsive line width

Et Voilà!

This is #day6 of my #100dataviz projects on data science and storytelling with data. Your feedback is welcome. Full code on GitHub. Thank you for reading!

--

--