1

I have been teaching myself JavaScript, and I like the idea of code-generated art. I came across this design that is made up of circles changing in size and overlapped by different colors.

How would something like this reference image: a grid of many circles of 3 colours: green, yellow and white. the circle's radii change based on proximity to what appears to be denser/darker perlin noise values

Can this be coded in JavaScript? I'm guessing for loops? But I am not sure how to set them to gradually change sizes throughout the line.

I'm imagining something like this?

let spaceX = 25,
  spaceY = 25,
  dial = 20;

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  background(220);

  // horizontal row
  for (let x = 0; x <= width; x += spaceX) {
    // vertical column
    for (let y = 0; y <= height; y += spaceY) {
      ellipse(x, y, diam);
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>
George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • 2
    Welcome to the site, Brian! But what is your question exactly? Do you want to know [how to draw things to a canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial)? Or do you need help with the logic of the code? Without a more specific question, this can't really be answered. Please review [how to ask](https://stackoverflow.com/help/how-to-ask) and then [edit] your question with more details about your about to solve the problem. Then we may be able ot give you a better answer. – Alex Wayne Mar 05 '23 at 02:54
  • Typo: `dial` -> `diam`. – ggorlen Mar 05 '23 at 03:45
  • 1
    Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. – Community Mar 05 '23 at 04:21

2 Answers2

3

The technique you are looking for to achieve the gradually changing sizes is the use of a two dimensional noise function such as Perlin Noise. P5.js provides this as a utility function: noise(). I've previously written a tutorial on using the noise() function over on OpenProcessing.

In this case it looks like you will want to draw the circles in multiple passes with different noise seeds. During each pass you draw yellow or blue circles with varying diameters based on the noise value for the current x, y coordinate (it is a little hard to tell if green circles are also being drawn, or if they green is just the result of mixing colors).

Unfortunately color mixing with pigments is different than color mixing with the RGB model. In RGB, mixing Yellow and Blue doesn't get you Green, it gets you Grey/White. You can enable simple mixing by using blendMode(ADD), but the results aren't great with the original color scheme. One of the interesting color mixes in the RGB model is Red + Green which does get you yellow. So that is what I've gone with in my example. Note: when using blendMode(ADD) it is important not to draw a light background! If you do that can throw off the color blending. Instead leave the canvas background blank and make the HTML element that contains it have your desired background.

let spaceX = 6,
  spaceY = 6;

function setup() {
  createCanvas(400, 400);
  noFill();
  strokeWeight(2);
  ellipseMode(CENTER);
  // comment this out for animation
  noLoop();
}

function draw() {
  clear();
  // Set the seed for the random noise
  noiseSeed(37);
  // Color: red
  stroke(255, 0, 0);
  // Draw circles with max stroke 2 and max diam 8
  pass(3, 8, 0);
  
  // Change the seed for the random noise
  noiseSeed(73);
  // Set the blendMode to add so that when the red and green circles overlapp their color is combined (instead of the second circle replacing the first).
  blendMode(ADD);
  stroke(0, 255, 0);
  pass(2, 10, 1);
}

// note: I've parameterized the stroke weight, diameter, and a position offset
// This makes it possible to tweak the different color circles to get different effects
function pass(weight, diam, offset) {
  // horizontal row
  for (let x = 0; x <= width; x += spaceX) {
    // vertical column
    for (let y = 0; y <= height; y += spaceY) {
      // Note: the use of millis() as a third parameter makes this animated if you re-run this over time
      let n = noise(x / 200, y / 200, millis() / 10000);
      strokeWeight(n * weight);
      ellipse(x + offset, y + offset, n * diam);
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>

Another option for achieving the variation between the circles of the two different colors would be to use a slight offset in the Z dimension input to the noise function.

let spaceX = 6,
  spaceY = 6;

function setup() {
  createCanvas(400, 400);
  noFill();
  strokeWeight(2);
  ellipseMode(CENTER);
  // comment this out for animation
  noLoop();
}

function draw() {
  clear();
  // Set the seed for the random noise
  noiseSeed(37);
  // Color: red
  stroke(255, 0, 0);
  // Draw circles with max stroke 2 and max diam 8
  pass(3, 8, 0, 0);
  
  // Change the seed for the random noise
  // noiseSeed(73);
  // Set the blendMode to add so that when the red and green circles overlapp their color is combined (instead of the second circle replacing the first).
  blendMode(ADD);
  stroke(0, 255, 0);
  pass(2, 10, 1, 0.3);
}

// note: I've parameterized the stroke weight, diameter, and a position offset
// This makes it possible to tweak the different color circles to get different effects
function pass(weight, diam, offset, zoff) {
  // horizontal row
  for (let x = 0; x <= width; x += spaceX) {
    // vertical column
    for (let y = 0; y <= height; y += spaceY) {
      // Note: the use of millis() as a third parameter makes this animated if you re-run this over time
      let n = noise(x / 200, y / 200, zoff + millis() / 10000);
      strokeWeight(n * weight);
      ellipse(x + offset, y + offset, n * diam);
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>
Paul Wheeler
  • 18,988
  • 3
  • 28
  • 41
-1

Here's the pseudo-code translated to JavaScript with Canvas

const spaceX = 25,
  spaceY = 25,
  dial = 20,
  width = 400,
  height = 400;

const canvas = document.createElement('canvas');
document.body.append(canvas);
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');


function circle(x, y, diam = 10) {
  ctx.beginPath();
  ctx.arc(x, y, diam / 2, 0, 2 * Math.PI);
  ctx.fill();
}

function draw() {
  for (let x = 0; x <= width; x += spaceX)
    for (let y = 0; y <= height; y += spaceY)
      circle(x, y);
}

draw()
Eric Fortis
  • 16,372
  • 6
  • 41
  • 62
  • 1
    Thanks for the answer. OP posted working (other than a small typo) p5.js code, not pseudocode. I think they're asking how to implement the generative art in the image, beyond laying out a grid of dots. – ggorlen Mar 05 '23 at 03:42
  • 1
    Adding to this, to recreate the reference image, you could have an underlying grayscale Perlin noise texture (the green blobs in the reference image) that controls the size of each dot in the grid. Then you'd render said noise texture underneath the dots, with a color ramp from white to yellow to green (for the green/yellow appearance in the reference image). – KobeSystem Mar 05 '23 at 08:09