Arranging Items in a Grid (Processing)

Two Strategies

There are two strategies for arranging items in a grid:

  1. Use a single loop to iterate over the number of items; derive the x and y position from the item number.
  2. image
  3. Use a nested loop to iterate over the x positions and, for each x, iterate over the y positions (left sketches). If the item number is required, derive it from the values of x and y (right sketch).
  4. image

Strategy 1: Deriving the x and y positions from the index

Review: Arranging items in a line

This builds on Arranging a Line of Items. Here is a sketch from that page:

void setup() {
	size(600, 600);
	background(100);
}

void draw() {
	for (int i = 0; i < 30; i++) {
		int x = 30 + 50 * i;
		circle(x, 100, 40);
	}
}
image

The shape position is derived from the loop index, using the function x=10+50ix = 10 + 50i used here.

Note: From here on, the definition of setup() is not shown. Each of the following code samples assumes that the sketch also contains a setup() function:

void setup() {
	size(600, 600);
	background(100);
}

Derive y from i as well.

void draw() {
	for (int i = 0; i < 30; i++) {
		float x = 30 + 50 * i;
		float y = 30 + 10 * i;
		circle(x, y, 40);
	}
}
image

Deriving the column (x position)

Let’s start with the angled line from above, and just look at the x position.

void draw() {
	for (int i = 0; i < 30; i++) {
		float x = 30 + 50 * i;
		float y = 30 + 50 * i;
		circle(x, y, 20);
	}
}
image

Instead of a diagonal line, where each x is greater than the previous x and each y is greater than the previous y, we would like to arrange the items on a grid. Here, each item is labelled with its index number. The x value increases from items 0 to 4, and then it resets back to the left side and increases again to 9, and so on.

image

We can use the modulo (remainder) operator % to chop it into pieces. For an input sequence i = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 etc., the modulo operator i % 5 produces 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, etc. The sketch then multiples this by 50 to make the result more visible: 0, 50, 100, 150, 200, 0, 50, 100, 150, 200, 0, 50, etc.

Note: A mnemonic for % is that it is like division / except that its value is what’s left over (the °s).

void draw() {
	for (int i = 0; i < 20; i++) {
    int column = i % 5;
		float x = 30 + 50 * column;
		float y = 30 + 50 * i;
		circle(x, y, 20);
	}
}
image

Deriving the row (y position)

Now let’s compute the y position. Since there are five items per row, the column is just the number of the item divided by five, and rounded down. For an input sequence i = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 etc., dividing by 5 produces 0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, etc. Putting this into an int type variable produces 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, etc.

void draw() {
  for (int i = 0; i < 20; i++) {
    int row = i/5;
    float x = 30 + 50 * i;
    float y = 30 + 50 * row;
    circle(x, y, 20);
  }
}
image

Putting this together:

void draw() {

	for (int i = 0; i < 20; i++) {
		int row = i / 5;
    int column = i % 5;

		float x = 30 + 50 * column;
		float y = 30 + 50 * row;

		circle(x, y, 20);
	}
}
image

Varying the items

We can use the row and column in other ways. This sketch gives each circle a size that is a function of its row and column.

void draw() {
  background(100);

  for (int i = 0; i < 30; i++) {
		int row = i / 5;
    int column = i % 5;
    
    float x = 30 + 50 * column;
    float y = 40 + 50 * row;
    
    float size = map(row - column, 0, 10, 10, 50);
    circle(x, y, size);
  }
}
image

Using a function to draw each cell

Instead of calling circle(), we can use our own function.

void draw() {
  background(100);

  for (int i = 0; i < 30; i++) {
		int row = i / 5;
    int column = i % 5;
    
    float x = 30 + 50 * column;
    float y = 40 + 50 * row;
    
    myShape(x, y, row, column);
  }
}

void myShape(float x, float y, int row, int column) {
  float size = map(row - column, 0, 10, 10, 50);
  circle(x, y, size);
}
image

The following code replaces just the implementation of the myShape function. The code in draw() remains exactly the same.

void myShape(float x, float y, int row, int column) {
  float size = map(row - column, 0, 10, 30, 40);
  circle(x, y, size / 2);
  square(x, y, size);
  circle(x + size, y, size / 2);
  circle(x, y + size, size / 2);
  circle(x + size, y + size, size / 2);
}
image

Strategy 2: Nested loops

An alternative to deriving the row and column from the linear index i, is to use a nested loop (one loop inside of another loop) to step through the x and y values, or the row and column numbers, directly.

void draw() {
  for (int row = 0; row < 5; row++) {
    for (int column = 0; column < 6; column++) {
      float x = 30 + 50 * column;
      float y = 40 + 50 * row;

      circle(x, y, 20);
    }
  }
}

If we need i (the index number), we can derive it from the row and column numbers.

void setup() {
  size(600, 600);
  background(100);
}

void draw() {
  for (int row = 0; row < 5; row++) {
    for (int column = 0; column < 6; column++) {
      int i = 5 * row + column;
      float x = 30 + 50 * column;
      float y = 40 + 50 * row;

      fill(255);
      circle(x, y, 30);
      fill(0);
      text(i, x, y);
    }
  }
}
image

Or we can initialize it 0, and increment it each time through the (inner) loop, to keep count of how many items we've drawn.

void draw() {
  int i = 0;
  for (int row = 0; row < 5; row++) {
    for (int column = 0; column < 6; column++) {
      float x = 30 + 50 * column;
      float y = 40 + 50 * row;
      i++;

      fill(255);
      circle(x, y, 30);
      fill(0);
      text(i, x, y);
    }
  }
}
image

Drawing different items at different grid positions

Above, the same shape is drawn at each grid position. We will add a second function, that draws a second shape. First, rename myShape to decoratedSquare. This doesn’t change the behavior of the sketch, but it will make it more readable once we add the second shape, since then we can use descriptive names instead of decoratedSquare1 and decoratedSquare2.

void draw() {
  for (int row = 0; row < 5; row++) {
    for (int column = 0; column < 6; column++) {
      float x = 30 + 100 * column;
      float y = 40 + 100 * row;

      decoratedSquare(x, y);
    }
  }
}

void decoratedSquare(float x, float y) {
  push();
  translate(x, y);
  
  circle(0, 0, 25);
  square(0, 0, 50);
  circle(50, 0, 25);
  circle(0, 50, 25);
  circle(50, 50, 25);
  pop();
}
image

Add a second drawing function circles() and call it instead.

void draw() {
  for (int row = 0; row < 5; row++) {
    for (int column = 0; column < 6; column++) {
      float x = 30 + 100 * column;
      float y = 40 + 100 * row;

      circles(x, y);
    }
  }
}


void circles(float x, float y) {
  push();
  translate(x, y);

  fill(255); // white
  circle(0, 10, 25);
  fill(color(#F8FC05));  // yellow
  circle(10, 10, 25);
  fill(color(#4D980C));  // green
  circle(10, 0, 25);

  pop();
}
image

Now, we can call circles() for some cells and decoratedSquare() for others.

void draw() {
  
  for (int row = 0; row < 5; row++) {
    for (int column = 0; column < 6; column++) {
      float x = 30 + 100 * column;
      float y = 40 + 100 * row;

      if ((row + column) % 3 > 0) {
        circles(x, y);
      } else {
        decoratedSquare(x, y);
      }
    }
  }
}

void decoratedSquare(float x, float y) {
  push();
  translate(x, y);
  
  circle(0, 0, 25);
  square(0, 0, 50);
  circle(50, 0, 25);
  circle(0, 50, 25);
  circle(50, 50, 25);
  pop();
}

void circles(float x, float y) {
  push();
  translate(x, y);

  fill(255); // white
  circle(0, 10, 25);
  fill(color(#F8FC05));  // yellow
  circle(10, 10, 25);
  fill(color(#4D980C));  // green
  circle(10, 0, 25);

  pop();
}
image

Animate the circles, by incorporating data from millis(). Note that you need to use a decimal 500.0 in the division to keep dx smooth.

void circles(float x, float y) {
  push();
  translate(x, y);
  
  float dx = map(cos(millis() / 500.0), -1, 1, 0, 10);
  float size = 25;

  fill(255); // white
  circle(0, dx, size);
  fill(color(#F8FC05));  // yellow
  circle(dx, dx, size);
  fill(color(#4D980C));  // green
  circle(dx, 0, size);

  pop();
}
image

Extracting grid() into a function

Let’s extract the code that draws the grid into its own function, so that we can call it several times in order to create several grids.

void draw() {

  for (int row = 0; row < 5; row++) {
    for (int column = 0; column < 6; column++) {
      float x = 30 + 100 * column;
      float y = 40 + 100 * row;

      circles(x, y);
    }
  }
}

void draw() {

  grid();
}

void grid() {
  for (int row = 0; row < 5; row++) {
    for (int column = 0; column < 6; column++) {
      float x = 30 + 100 * column;
      float y = 40 + 100 * row;

      circles(x, y);
    }
  }
}

Change rows and columns from literal numbers 5 to parameters. This allows the function that calls grid() to specify the number of rows and columns.

void draw() {

  grid(5, 6);
}

void grid(int rows, int columns) {
  for (int row = 0; row < rows; row++) {
    for (int column = 0; column < columns; column++) {
      float x = 30 + 100 * column;
      float y = 40 + 100 * row;

      circles(x, y);
    }
  }
}

Call grid() twice, to draw two grids of different sizes.

void draw() {
  grid(5, 6);

  translate(width / 2, height / 2);
  grid(3, 4);
}

void grid(int rows, int columns) {
  for (int row = 0; row < rows; row++) {
    for (int column = 0; column < columns; column++) {
      float x = 30 + 100 * column;
      float y = 40 + 100 * row;

      circles(x, y);
    }
  }
}
image

Original p5.js version ©2020–2022 by Oliver Steele. Updated for Processing by Margaret Minsky.