How can I assign a random color to each circle in a grid, then get each circle to progress through the color spectrum from the start color?

Viewed 250

I am trying to assign a random color to each circle once, and then get that color to progress through the color spectrum. I am a beginner and cannot work out how to stop the random color in draw function from resetting every loop without pulling it into setup function which then makes all the circles start from the same random color. Code below of what I have so far. It also seems to be changing the colors in a slight wave at the moment, I assume this is to do with the 'for'. All i want is a grid of circles that are assigned a random color once and then go on to progress their color through the color wheel at the same speed. Thanks in advance!

let intervals = [10];
let count;
let frameWidth;
let distance;
let diam1;
let col1;
let sat1;
let bri1;
let speed;

function setup() {
  
  createCanvas(800, 800);  
  
  colorMode(HSB, 360, 100, 100, 1);
  
//initiate draw variables
  
  count = random(intervals);
  
  frameWidth = (width*0.8);
  
  distance = (frameWidth/count);
  
  col1 = random(360);
  
  diam1 = distance*0.5;  
  
  speed = 0.005;
  
}

function draw() {
  
  background(0, 0, 0, 1);

// draw grid of circles
  
  for (let x = width * 0.1; x <= width * 0.9; x += distance) {
    
    for (let y = height * 0.1; y <= height * 0.9; y += distance) {
      
      sat1 = 100;
      
      bri1 = 100;
      
      noStroke();
      
      fill(col1, sat1, bri1, 1);
      
      ellipse(x,y,diam1);
      
      col1 = col1 + speed
      
      if (col1 >= 360) {
        
      col1 = 0
      
      }
  
      }
    }
  }  
2 Answers

You have to create a grid of random colors in setup:

let speed, colors, rows, columns;

function setup() {
    // [...]

    columns = Math.round(frameWidth / distance) + 1; 
    rows = Math.round(frameWidth / distance) + 1; 
    colors = [];
    for (let i = 0; i < columns; i ++) {
        colors.push([]);
        for (let j = 0; j < rows; j ++) {
            colors[i].push(random(360));
        }
    }
    speed = 1;
}

Use the the colors in the draw function. Complete example:

let intervals = [10];
let count, frameWidth, distance;
let sat1, bri1;
let speed, colors, rows, columns, diameters;

function setup() {
    createCanvas(800, 800);  
    colorMode(HSB, 360, 100, 100, 1);
    //initiate draw variables
    count = random(intervals);
    frameWidth = (width*0.8);
    distance = (frameWidth/count);  
    
    columns = Math.round(frameWidth / distance) + 1; 
    rows = Math.round(frameWidth / distance) + 1; 
    colors = [];
    diameters = []
    for (let i = 0; i < columns; i ++) {
        colors.push([]);
        diameters.push([]);
        for (let j = 0; j < rows; j ++) {
            colors[i].push(random(360));
            diameters[i].push(random(TWO_PI));
        }
    }
    speed = 1;
}

function draw() {
    background(0, 0, 0, 1);

    // draw grid of circles
    for (let i = 0; i < columns; i ++) {
        for (let j = 0; j < rows; j ++) {
            sat1 = 100;
            bri1 = 100;
            noStroke();
            fill(colors[i][j], sat1, bri1, 1);
            let d = distance * (0.5 + 0.4 * sin(diameters[i][j]));
            ellipse(width * 0.1 + i * distance, height * 0.1 + j * distance, d);
            colors[i][j] = (colors[i][j] + speed) % 360;
            diameters[i][j] = diameters[i][j] + 0.05;
        }
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>

The ui will be a 2d grid of circles.

The data structure will be a 2d array of colour values. The data structure never needs to be updated; to get a circle's current colour we only need to know its initial colour, and the amount of time that has passed!

I'm going to use the hsla() css colour directive to define the colours, as it makes shifting the hue trivial.

The colourShiftingCircles function takes a canvas context and some display parameters, and will maintain an animating (colour-shifting) grid of circles on the provided canvas context.

The function signature is:

colourShiftingCircles({ ctx, rad, numX, numY, saturation=100, lightness=50, huePerSec=30 })
  • ctx: the canvas context
  • rad: circle radius in pixels
  • numX: number of circles across
  • numY: number of circles down (total of numX x numY circles)
  • saturation: control how "colourful" or "nongrey" the colours are
  • lightness: control how shifted towards black/white the colours are
  • huePerSec: how quickly to cycle hues

let canvas = document.querySelector('canvas');

// This just ensures the canvas is a reasonable size; it's optional
let resize = () => {
  let { width, height } = canvas.parentNode.getBoundingClientRect();
  if (width.toString() !== canvas.getAttribute('width')) canvas.setAttribute('width', width);
  if (height.toString() !== canvas.getAttribute('height')) canvas.setAttribute('height', height);
};
resize();
window.addEventListener('resize', resize);

let colourShiftingCircles = ({ ctx, rad, numX, numY, saturation=100, lightness=50, huePerSec=30 }) => {
  
  // This value allows the animation to be ended at some point in the future
  let control = { run: true, end: () => control.run = false };
  
  // Animation loop runs inside an `async` function:
  (async () => {
    
    // An array of size `numX` x `numY` items, defining initial hue values for circles
    let data = [ ...new Array(numX) ].map(() => [ ...new Array(numY) ].map(() => {
      return { hue: Math.random() * 360 };
    }));
    
    // Mark the start time; this will allow us to tell how much time has passed later
    let startTime = performance.now();
    
    while (control.run) {
      
      // Wait for animation frame; this is a basic principle of canvas animations
      await new Promise(r => requestAnimationFrame(r));
      
      // Figure out how much the hue has shifted by at this point in time
      let totalHueShift = ((performance.now() - startTime) / 1000) * huePerSec;
      
      // Draw every circle:
      for (let y = 0; y < numY; y++) { for (let x = 0; x < numX; x++) {
        
        // Calculate the hue based on the circle's initial colour, and the amount
        // of hue shifted due to passage of time
        let hue = data[x][y].hue + totalHueShift;
        
        // Simply draw a circle with an hsla fill colour
        ctx.beginPath();
        ctx.fillStyle = `hsla(${Math.floor(hue) % 360}deg ${saturation}% ${lightness}%)`;
        ctx.arc((rad * 2 * x) + rad, (rad * 2 * y) + rad, rad, 0, Math.PI * 2);
        ctx.fill();
        
      }}
      
    }
    
  })();
  
  return control;
  
};

let { end } = colourShiftingCircles({ ctx: canvas.getContext('2d'), rad: 20, numX: 5, numY: 5 });

// If you ever want to end the animation just call `end()`!
/* Just getting the canvas to display in a reasonable way; this is optional */
html, body, canvas { position: absolute; width: 100%; height: 100%; margin: 0; padding: 0; }
canvas { outline: 1px solid black; }
<canvas></canvas>

Related