Recreating Sol LeWitt’s Famous Art in JavaScript
By: Varun Maliwal
Tags: JavaScript, Creative Coding, Web Development, Programming, Coding

Point

A Point is a precise location on a surface or a plane, usually designated by a dot. If we consider our 2D plane represented by X & Y axis then in our code we can use array of size 2 to denote a point, for example, const point = [x, y] .

Line

A Line is formed when two points located on a 2D plane joined by a line segment. A straight line is the shortest distance between two points.

P5 has a handy method line(x1, y1, x2, y2) to draw a line between two points on a 2D plane.

We will create a wrapper function that will eventually call p5’s line method.

function drawLine(pointA, pointB) { p5.line(pointA[0], pointA[1], pointB[0], pointB[1]); }

Quadrilateral

Quadrilateral literally means 4 sides. A more exact definition from wikipedia explains it further — “a quadrilateral is a polygon with four edges (or sides) and four vertices or corners”. Some examples include Rectangle, Square, Rhombus, etc.

So what makes Square, Rectangle and other Quadrilaterals different from each other? It’s the interior angle & length of the sides compared with each other. For example, all sides of a Rectangle are joined at an angle of 90 degrees making two parallel sides (width & height) equal, while for square all sides are equal and the interior angle is always 90 degree.

Square is a special case of Rectangle, and therefore even though we will be drawing square we will call it a rectangle.

Drawing a rectangle on canvas in JavaScript

Let’s first setup our canvas and draw a basic rectangle with p5’s rect(x, y, width, height) method.

export default function Sketch(p5) {

p5.setup = () => {

p5.createCanvas(300, 300);

p5.noFill();

p5.noLoop();

}; p5.draw = () => {

p5.background(0);

p5.stroke(255); // leave 10% margin on all sides

const margin = 0.1;

const x = p5.width * margin;

const y = p5.height * margin; // adjust width & height based on margin

let w = p5.width - 2 * x;

let h = p5.height - 2 * y; p5.rect(x, y, w, h);

}; function drawLine(pointA, pointB) {

p5.line(pointA[0], pointA[1], pointB[0], pointB[1]);

}

}

This code will produce a canvas of size 300 x 300 and rectangle (actually square) adjusted by margins.

If we think about it, every edge is a straight line between two points on a plane, and each edge has a start and end coordinate, or corner vertices.

Let’s create a function called Rectangle that can hold values of all the coordinates of vertices of the rectangle. We will represent vertices as points a, b, c, d. Each vertex is a Point hence can be represented as const a = [x,y];

function Rectangle(x, y, width, height) { return {

coordinates,

draw,

} function coordinates() {

// adjust width & height as our scale might not be at zero

const w = x + width;

const h = y + height; return {

a: [x, y],

b: [w, y],

c: [w, h],

d: [x, h],

};

} function draw() {

p5.rect(x, y, width, height);

}

}

Point at a K distance

Our job can be made easier if we can just locate an arbitrary point on each edge of the rectangle and draw a line between the two points. Actually there’s a convenient mathematical formula to find coordinates of a point located exactly at k distance on a line segment, and we can use that as we now know coordinates of the vertices of our rectangle.

P(x, y)=(x1 + k(x2 — x1), y1 + k(y2 — y1))

where k is a fractional part of distance

So to find coordinates of a point located exactly at the midpoint of an edge AB (vertices a & b makes edge AB), we would use k as 1/2 or 0.5.

We can write equivalent Javascript as -

function pointCoordinatesOnLine(pointA, pointB, k) {

const x = pointA[0] + k * (pointB[0] - pointA[0]);

const y = pointA[1] + k * (pointB[1] - pointA[1]); return [x, y];

}

Now using this method we can add another helper method in our Rectangle function to find center coordinates of all the edges of the rectangle.

function Rectangle(x, y, width, height) { return {

centerCoordinates,

...

} ... function centerCoordinates(midpoints = {}) {

// represent center coordinates of a particular side

const midpointMapping = {

ab: 'p',

bc: 'q',

cd: 'r',

da: 's',

ba: 'p',

cb: 'q',

dc: 'r',

ad: 's',

}; const calculateMidpoint = (point1, point2) => {

let [kA, pointA] = point1;

let [kB, pointB] = point2; const edge = `${kA}${kB}`; midpoints[midpointMapping[edge]] = pointCoordinatesOnLine(

pointA,

pointB,

0.5

); return point2;

}; const coordinatesArray = Object.entries(coordinates());

const sideDA = [

coordinatesArray[0],

coordinatesArray[coordinatesArray.length - 1],

]; [...coordinatesArray, ...sideDA].reduce((point1, point2) =>

calculateMidpoint(point1, point2)

); return midpoints;

} ... }

Let’s recreate our rectangle on canvas but let’s use our Rectangle.draw method instead.

Next we add lines to the midpoints using the co-ordinates we calculated, and divide our rectangle into four parts. Midpoint vertices are represented as p, q, r, s, o, in the code & here o simply means the center vertex of the rectangle.

p5.draw = () => { ... const { a, b, c, d } = rectangle.coordinates();

const { p, q, r, s } = rectangle.centerCoordinates();

const o = [x + w / 2, y + h / 2]; drawLine(p, r);

drawLine(q, s); ... }

If we run above code we can see our rectangle perfectly divided into four parts.

Connecting edges

One final step we need to figure out is how to draw horizontal, vertical and diagonal lines between the edges.

Actually, we already have everything in place to draw these lines. We have all the coordinates of the rectangle and we can use our method pointCoordiatnatesOnLine to find coordinates on each side at a certain distance on our rectangle. So let’s create a new function that will help draw multiple lines between two edges.

p5.draw = () => {

... drawLines([a, p], [s, o], 12); } ... // helper function to draw lines between two

// edges at every k distance function drawLines(edge1, edge2, k) {

const [pointA, pointB] = edge1;

const [pointC, pointD] = edge2; for (let i = 0; i < k; ++i) {

const [x1, y1] = pointCoordinatesOnLine(pointA, pointB, i / k);

const [x2, y2] = pointCoordinatesOnLine(pointC, pointD, i / k);

drawLine([x1, y1], [x2, y2]);

}

}

And our sketch looks pretty close. We will need to increase the number of lines to match the original artwork.

Awesome! We are getting there. Let’s draw the rest of the lines and finish our sketch.

p5.draw = () => {

... drawLines([a, p], [s, o], 18);

drawLines([p, o], [b, q], 18); drawLines([s, o], [s, d], 13);

drawLines([d, r], [o, r], 13); drawLines([q, c], [q, o], 13);

drawLines([c, r], [o, r], 13); }

Great! Here’s our final output. It looks pretty close to the original work.

Let’s add some background color and change our stroke color to match the original painting.

So that’s it! This was a fun exercise for me, it required some creative thinking and I am sure there is much more room for improvement. I’ll be honest the most difficult part was adding everything on medium 😅.

The final code is available as github gist.

Namaskar 🙏🏾