September 28 (Arrays)

Announcement: The Points-Back Packet

For a limited time only, through the end of your lab/class session on Tuesday October 6th, we are offering you the chance to get some points back on your first exam.

To do this, complete the points-back-packet-1 linked here. You must complete all of the questions in the packet and submit them by the end of your October 6th session to get up to 50% of your lost points back on each exam.”

In other words, this take-home test is not timed. It’s open book, you can take it home, etc. But you are recommended to do it alone by hand first before you consult other references.


More about Arrays

Consider the following program, which draws a star. This star has 10 vertex points, each with 2 values, for a total of 20 data elements.

sketch

var x0 = 50;
var y0 = 18;
var x1 = 61;
var y1 = 37;
var x2 = 83;
var y2 = 43;
var x3 = 69;
var y3 = 60;
var x4 = 71;
var y4 = 82;
var x5 = 50;
var y5 = 73;
var x6 = 29;
var y6 = 82;
var x7 = 31;
var y7 = 60;
var x8 = 17;
var y8 = 43;
var x9 = 39;
var y9 = 37;

function setup() {
  createCanvas(200, 200);
}

function draw() {
  background(255, 200, 200);

  beginShape();
  vertex (x0, y0);
  vertex (x1, y1);
  vertex (x2, y2);
  vertex (x3, y3);
  vertex (x4, y4);
  vertex (x5, y5);
  vertex (x6, y6);
  vertex (x7, y7);
  vertex (x8, y8);
  vertex (x9, y9);
  endShape();
}

That’s a lot of variables! Providing this data to a program requires either creating 20 variables or using an array that can somehow store these 20 numbers. In the next example, we group these data elements together in two arrays: one for the x-coordinates and one for the y-coordinates. (Note that we also use the CLOSE property in endShape to fix the visual bug.)

sketch

var x = [ 50, 61, 83, 69, 71, 50, 29, 31, 17, 39 ];
var y = [ 18, 37, 43, 60, 82, 73, 82, 60, 43, 37 ];

  
function setup() {
  createCanvas(200,200); 
}

function draw() {
  background (255, 200, 200); 

  beginShape();
  vertex (x[0],y[0]);  
  vertex (x[1],y[1]); 
  vertex (x[2],y[2]); 
  vertex (x[3],y[3]); 
  vertex (x[4],y[4]); 
  vertex (x[5],y[5]); 
  vertex (x[6],y[6]); 
  vertex (x[7],y[7]); 
  vertex (x[8],y[8]); 
  vertex (x[9],y[9]); 
  endShape(CLOSE);
}

That’s tidier, but we can do even better to abstract this code. Notice any patterns? How about the fact that we are counting our way through the array elements? This counting should be an immediate tip-off to use an iterative code structure, like a for() loop, as shown below.

sketch

var x = [50, 61, 83, 69, 71, 50, 29, 31, 17, 39];
var y = [18, 37, 43, 60, 82, 73, 82, 60, 43, 37];


function setup() {
  createCanvas(200, 200);
  noLoop();
}

function draw() {
  background(255, 200, 200);
  var nPoints = x.length;

  beginShape();
  for (var i = 0; i < nPoints; i++) {
    vertex(x[i], y[i]);
  }
  endShape(CLOSE);

}

Now we can do all sorts of tricks. For example, on each frame, we can draw a star in which each point is computed from one of the stored coordinates, to which has been added a random number:

sketch

var x = [50, 61, 83, 69, 71, 50, 29, 31, 17, 39];
var y = [18, 37, 43, 60, 82, 73, 82, 60, 43, 37];

function setup() {
  createCanvas(200, 200);
}

function draw() {
  background(255, 200, 200);
  var nPoints = x.length;
  
  beginShape();
  for (var i = 0; i < nPoints; i++) {
    var px = x[i] + random(-3,3);
    var py = y[i] + random(-3,3);
    vertex(px,py);
  }
  endShape(CLOSE);
}

We can also progressively modify these arrays. Here, each point does a “random walk“:

sketch

var x = [50, 61, 83, 69, 71, 50, 29, 31, 17, 39];
var y = [18, 37, 43, 60, 82, 73, 82, 60, 43, 37];

function setup() {
  createCanvas(200, 200);
}

function draw() {
  background(255, 200, 200);
  var nPoints = x.length;
  
  // Progressively modify the arrays with a small amount of randomness.
  // Each time draw() is called, the more randomness is added. 
  var amount = 0.2;
  for (var i = 0; i < nPoints; i++) {
    x[i] += random(-amount, amount);
    y[i] += random(-amount, amount);
  }
  
  // Now render the star from those arrays
  fill(255);
  stroke(0);
  beginShape();
  for (var i = 0; i < nPoints; i++) {
    vertex(x[i], y[i]);
  }
  endShape(CLOSE);
}

Incidentally, with a little extra work, it’s possible to use the same data to render the star with curves instead of straight lines:

sketch

var x = [50, 61, 83, 69, 71, 50, 29, 31, 17, 39];
var y = [18, 37, 43, 60, 82, 73, 82, 60, 43, 37];


function setup() {
  createCanvas(200, 200);
}

function draw() {
  background(255, 200, 200);
  var nPoints = x.length;
  
  fill(255); 
  stroke(0); 
  beginShape();
  curveVertex(x[nPoints-1], y[nPoints-1]);
  for (var i = 0; i < nPoints; i++) {
    curveVertex(x[i], y[i]);
  }
  curveVertex(x[0], y[0]);
  curveVertex(x[1], y[1]);
  endShape();
}


Arrays that Grow

Consider the following sketch. Each time the user clicks, another point is accreted into the background image:

sketch

var ex;
var ey;

//---------------------------------
function setup() {
  createCanvas(300, 300);
  background(255, 200, 200);
}

//---------------------------------
function draw() {
  fill(0);
  ellipse(ex, ey, 5, 5);
}

//---------------------------------
function mousePressed() {
  ex = mouseX;
  ey = mouseY;
}

But what if one wanted to have an animated scene? You can’t do that if you don’t refresh the background on every frame. Here’s the beginning of a solution: store the user’s mouse clicks in an array, and draw all of the points each frame:

sketch

var x = [];
var y = [];

//---------------------------------
function setup() {
  createCanvas(300,300); 
}

//---------------------------------
function draw() {
  background (255,200,200); 
  
  fill(0); 
  for (var i=0; i<x.length; i++){
    var ex = x[i];
    var ey = y[i]; 
    ellipse(ex,ey, 5, 5); 
  }
}

//---------------------------------
function mousePressed(){
  x.push(mouseX);
  y.push(mouseY); 
}

Looks the same, right? Well, no. Now we can again do all sorts of tricks with the data we are progressively storing. For example, we can draw the points with some animation, and meanwhile change the color of the background:

sketch

var x = [];
var y = [];

//---------------------------------
function setup() {
  createCanvas(300,300); 
}

//---------------------------------
function draw() {
  var r = 230 + 25*sin(millis()/1000.0 );
  var g = 230 + 25*sin(millis()/1000.0 + HALF_PI);
  var b = 230 + 25*sin(millis()/1000.0 - HALF_PI);
  background (r,g,b);
  fill(0);
  
  if (x.length < 1){
    text("Click to begin", 5,15); 
  }
  
  for (var i=0; i<x.length; i++){
    var ex = x[i] + random(-3,3);
    var ey = y[i] + random(-3,3); 
    ellipse(ex,ey, 5, 5); 
  }
}

//---------------------------------
function mousePressed(){
  x.push(mouseX);
  y.push(mouseY); 
}

We can perform computations on these arrays, in order to calculate new information about the collection of user-added points. For example, in the sketch below, we compute the “centroid” or geometric center of the collection by calculating the average X-coordinate and the average Y-coordinate. (The centroid is also known as the center of mass.)

sketch

var x = [];
var y = [];

//---------------------------------
function setup() {
  createCanvas(300, 300);
}

//---------------------------------
function draw() {
  background(255, 200, 200);
  fill(0);
  noStroke();

  var nPoints = x.length;
  if (nPoints < 1) {
    text("Click to begin", 5, 15);
  }

  // Draw the dots
  for (var i = 0; i < nPoints; i++) {
    ellipse (x[i],y[i], 5,5);
  }

  // Compute their centroid point: (xAverage, yAverage)
  var xAverage = 0;
  var yAverage = 0;
  for (var i = 0; i < nPoints; i++) {
    xAverage += x[i];
    yAverage += y[i];
  }
  xAverage /= nPoints;
  yAverage /= nPoints;

  // Draw lines from the centroid to every point
  strokeWeight(1);
  stroke(0, 0, 0, 50);
  for (var i = 0; i < nPoints; i++) {
    line (x[i],y[i], xAverage,yAverage);
  }

  // Draw the centroid
  fill(255);
  stroke(0);
  strokeWeight(2);
  ellipse(xAverage, yAverage, 10, 10);
}

//---------------------------------
function mousePressed() {
  x.push(mouseX);
  y.push(mouseY);
}

Notice how the centroid is recalculated when the data is progressively modified on-the-fly:

sketch

var x = [];
var y = [];

//---------------------------------
function setup() {
  createCanvas(300, 300);
  for (var i = 0; i < 7; i++) {
    var t = map(i,0,7, 0,TWO_PI);
    x.push(width/2 + 75*cos(t));
    y.push(height/2 + 75*sin(t));
  }
}

//---------------------------------
function draw() {
  background(255, 200, 200);
  fill(0);
  noStroke();

  // Progressively modify the data
  var nPoints = x.length;
  for (var i = 0; i < nPoints; i++) {
    x[i] += (noise(i * 1 + millis() / 1000.0) - 0.5);
    y[i] += (noise(i * 2 + millis() / 1000.0) - 0.5);
    ellipse(x[i], y[i], 5, 5);
  }
  
  // Draw the dots
  for (var i = 0; i < nPoints; i++) {
    ellipse(x[i], y[i], 5, 5);
  }

  // Compute their centroid point: (xAverage, yAverage)
  var xAverage = 0;
  var yAverage = 0;
  for (var i = 0; i < nPoints; i++) {
    xAverage += x[i];
    yAverage += y[i];
  }
  xAverage /= nPoints;
  yAverage /= nPoints;

  // Draw lines from the centroid to every point
  strokeWeight(1);
  stroke(0, 0, 0, 50);
  for (var i = 0; i < nPoints; i++) {
    line(x[i], y[i], xAverage, yAverage);
  }

  // Draw the centroid
  fill(255);
  stroke(0);
  strokeWeight(2);
  ellipse(xAverage, yAverage, 10, 10);
}

//---------------------------------
function mousePressed() {
  x.push(mouseX);
  y.push(mouseY);
}

It is possible both to add as well as remove points. The following “drawing program” sketch deletes the last point and inserts a new one, when the number of points exceeds a certain constant. Notice also how the arrays are cleared in mousePressed().

sketch

var x = [];
var y = [];
var maxNPoints = 10;

//---------------------------------
function setup() {
  createCanvas(300, 300);
}

//---------------------------------
function draw() {
  background(255, 200, 200);

  var nPoints = x.length;
  if (nPoints < 1) {
    fill(0);
    noStroke();
    text("Click to begin", 5, 15);
  }

  // Draw little ellipses on each point
  fill(0);
  noStroke();
  for (var i = 0; i < nPoints; i++) {
    var xa = x[i];
    var ya = y[i];
    ellipse(xa, ya, 5, 5);
  }
  
  // Connect the dots with a line
  stroke(0, 0, 0, 200);
  strokeWeight(2);
  noFill(); 
  beginShape();
  for (var i = 0; i<nPoints; i++) {
    var xa = x[i];
    var ya = y[i];
    vertex(x[i], y[i]);
  }
  endShape(); 
}

//---------------------------------
function mousePressed() {
  if (x.length > maxNPoints){
    x.shift();
    y.shift();
  }
  x.push(mouseX);
  y.push(mouseY);
}

//---------------------------------
function keyPressed() {
  x = [];
  y = [];
}

In the next sketch, points are continually added (and removed) when the mouse button is pressed:

sketch

var x = [];
var y = [];
var maxNPoints = 40;

//---------------------------------
function setup() {
  createCanvas(300, 300);
}

//---------------------------------
function draw() {
  background(255, 200, 200);

  var nPoints = x.length;
  if (nPoints < 1) {
    fill(0);
    noStroke();
    text("Click to begin", 5, 15);
  }

  // Draw little ellipses on each point
  fill(0);
  noStroke();
  for (var i = 0; i < nPoints; i++) {
    var xa = x[i];
    var ya = y[i];
    ellipse(xa, ya, 5, 5);
  }

  // Connect the dots with a line
  stroke(0, 0, 0, 200);
  strokeWeight(2);
  noFill();
  beginShape();
  for (var i = 0; i < nPoints; i++) {
    var xa = x[i];
    var ya = y[i];
    vertex(x[i], y[i]);
  }
  endShape();

  if (mouseIsPressed) {
    if (x.length > maxNPoints) {
      x.shift();
      y.shift();
    }
    x.push(mouseX);
    y.push(mouseY);
  }
}

It doesn’t take too much more to create some very stimulating experiences. In the next sketch, the distances between neighboring points are progressively exaggerated:

sketch

var x = [];
var y = [];
var maxNPoints = 40;
var mouseReleasedTime = 0;

//---------------------------------
function setup() {
  createCanvas(300, 300);
}

//---------------------------------
function draw() {
  background(255, 200, 200);

  var nPoints = x.length;
  if (nPoints < 1) {
    fill(0);
    noStroke();
    text("Click to begin", 5, 15);
  }

  // Connect the dots with a line
  stroke(0, 0, 0, 128);
  strokeWeight(2);
  noFill();
  var amount = 0.01;
  beginShape();
  curveVertex(x[0], y[0]);
  for (var i = 1; i < nPoints; i++) {
    var xa = x[i - 1];
    var ya = y[i - 1];
    var xb = x[i];
    var yb = y[i];
    x[i] += amount * (xb - xa);
    y[i] += amount * (yb - ya);
    curveVertex(x[i], y[i]);
  }
  curveVertex(x[nPoints - 1], y[nPoints - 1]);
  endShape();


  // Draw little ellipses on each point
  fill(0);
  noStroke();
  for (var i = 1; i < nPoints; i++) {
    var xa = x[i];
    var ya = y[i];
    ellipse(xa, ya, 5, 5);
  }

  // Add points if the mouse button is pressed
  if (mouseIsPressed) {
    // And if the mouse has moved since the previous frame
    if ((mouseX != pmouseX) || (mouseY != pmouseY)) {
      if (x.length > maxNPoints) {
        x.shift();
        y.shift();
      }
      x.push(mouseX);
      y.push(mouseY);
    }
  } else {
    // Clear the screen if it has been a while 
    if ((millis() - mouseReleasedTime) > 12000) {
      x = [];
      y = [];
    }
  }
}

// Clear the line when we click. 
function mousePressed() {
  x = [];
  y = [];
  x.push(mouseX);
  y.push(mouseY);
}

function mouseReleased() {
  mouseReleasedTime = millis();
}

Watch Zach Lieberman discuss drawing with code at the 2015 Eyeo Festival (watch from 8:28 – 16:40):


More Information

Some other JavaScript array methods that may be useful:

  • push() — adds a new element to an array (at the end)
  • pop() — deletes the last element, and provides it to you
  • shift()  — deletes the first element and shifts the rest down
  • unshift() — adds a new element to an array (at the beginning)
  • reverse() — reverses the elements in an array

There’s lots more. We therefore also recommend you check out: