Week 4 Reading Response Zere

  1. Something that drives me crazy and how it can be improved: “automatic doors” across campus! Primarily, the ones in D2, they drive me crazy every day. These so-called “automatic doors” are not even automatic at times, and are super heavy. A lot of the times they simply do not work, and I have to manually open them or wait until somebody opens them for me. It is especially frustrating when you want to have a nice lunch/dinner outside of D2, but carrying the heavy tray in and out of the cafeteria is made almost impossible if you are by yourself. These automatic doors could be improved by making their behavior consistent and by adding clearer signifiers and feedback to show when they are working. Since people in D2 often carry trays, the doors should be designed to open every time and stay open long enough to accommodate real use. This would provide a clear conceptual model of those “automatic doors”.
  2. How can I apply some of the author’s principles of design to IM?

    The main lesson from the reading is that interactive media should not make users stop and think, “What is going on here?” or “What am I supposed to do here?” Instructions, explanations, or repeated trials directly affect the user experience of what is meant to be interactive and understandable. In my opinion, interactive systems should clearly signal what actions are possible and what will happen after those actions are taken. Design has to be good – buttons clickable, interactions should respond quickly to the user. That is why feedback is important. I believe that IM should work with normal human expectations, that way there would be more people interested in it or pursuing it.

Week 4 – Generative text

Concept:
I was inspired by the idea of an “answer book.” When people feel confused or overwhelmed, they sometimes speak their question out loud and then randomly open a book to any page. Even though the page is chosen by chance, a sentence on that page can feel meaningful or comforting, and it can help the person feel calmer or more encouraged. Based on this idea, I created a simple online version of the answer book. The user brings their own question, then clicks once to receive a randomly generated sentence. The goal is not to give a perfect or “correct” answer, but to offer a small prompt that can shift the user’s mood, support reflection, and help them move forward.
How this was made:
I made this project by following a simple p5.js workflow and using a mix of learning resources to solve specific problems as they came up. I started by watching beginner YouTube tutorials on p5.js to understand the basic structure of a sketch, especially how `setup()` and `draw()` work, and how to display text on the canvas. After I had a basic template running, I used the official p5.js reference website to check the correct syntax for functions like `text()`, `textAlign()`, `textSize()`, `mousePressed()`, and `saveCanvas()`. Next, I built the “answer book” logic using online examples about randomness and arrays. I created several word banks (sentence starters, subjects, verbs, adjectives, and endings) and then used `random()` to pick one item from each list. I combined those parts into a full sentence, so every click generates a new “page” from the book. I tested the output many times and edited the word lists to make sure the sentences sounded smooth and encouraging, not broken or repetitive. When I got stuck or wanted to improve the design, I also used generative AI as a helper. I asked it for simple code patterns (like how to organize arrays, how to avoid repeating the same quote too often, and how to make the layout look cleaner).

The part I am proud of:
Actually, the whole thing is fun and interesting, it indeed took me sometimes to collaborate the part that we use for adding all the words together to form a sentences. But I will say the “word bank” itself is the most chellenging part. Since I have to go through many answer books and find some short sentences to make the whole sentence make sense. (somehow)

// Word banks 
let starters = ["Today I learned", "I keep forgetting", "I wish", "I noticed", "It feels like", "Sometimes","I hope", "I confess"];
let subjects = ["my phone", "the app", "the door", "my brain", "the algorithm", "the website", "this class","the thoughts"];
let verbs = ["is", "acts", "behaves", "works", "fails", "pretends", "keeps trying","destroy"];
let adjectives = ["confusing", "too fast", "too slow", "strangely helpful", "kinda broken", "overdesigned", "simple"];
let endings = [
  "and I blame the design.",
  "but I'm still alive.",
  "so I take a deep breath.",
  "and somehow it makes sense.",
  "and then I start over.",
  "and that’s the whole story."
];

Conclusion:
I think I did a goood job on this assignment. But if the time is longer, I think I can put more phrases inside the coding, make the text not that repeatable.

Week 4 – Generative Text Assignment

Concept:

For this project, I wanted to experiment with text functions while learning and applying different codes to create a scrolling lyrics display similar to Spotify, Apple Music, and YouTube. When I first looked at the assignment, I immediately wanted to recreate the full music interface that includes the album cover and the timeline of the song at the bottom. But instead, I decided to focus only on the scrolling lyrics to make it more creative and manageable.

I chose the song Yellow by Coldplay because it reminds me of my last bus ride after a field trip in senior year. It was our last day of school after finishing our final exams, and on the way back from IMG everyone in my class was saying goodbye to each other at the final stoplight before we all went home. That moment felt nostalgic and emotional which is why I wanted to incorporate that feeling into my work.

Inspo from Spotify: 

Embedded sketch: (try pressing on the screen)

How it was made:

I usually start by building the background and adding simple codes that I know like the colored box and the title text at the top, the background, the size of the canvas, etc. After that, I started off by creating an array to store the lyrics so I could easily reference each line later in the code.

Then I created the scroll variable so the lyrics would begin at the bottom of the screen. Using translate() along with push() and pop(), I was able to make the lyrics scroll upward. While working on this part, I kept experimenting because sometimes the text wouldn’t move the way I wanted it to move. I also had to adjust the speed because it was first set to 0.3 but since I included the full lyrics of the song, I felt that 0.3 was too slow. I decided to change it to 0.6 which made the movement feel more reasonable.

I used this loop:

for (let i = 0; i < lyrics.length; i++) { //going through the lyrics line by line 
  let yPos = i * 40; //each line is 40 pixels below the previous one -using the name yPos for the positioning of lines vertically (y-axis)

This allowed the lyrics to display line by line with 40 pixels of spacing between each one. That spacing helped create a smoother scroll and prevented the lines from looking crowded.

For interactivity, I added the mouseIsPressed function with the if/else functions so that when the mouse is pressed, the scroll speed increases to 30. When it’s not pressed, it goes back to the normal pace of 0.6. I liked adding this because it made the project more engaging instead of just automatically scrolling.

Resources:

Push and pop functions: (all the resources helped me understand how push and pop work through examples and descriptions I was able to make my own transformation)
https://www.youtube.com/watch?v=KSo_VEbsWks 

https://www.youtube.com/watch?v=o9sgjuh-CBM

https://p5js.org/reference/p5/push/

Translate:

https://p5js.org/reference/p5/translate/ (in order for my lyrics to scroll and keep moving up from their starting point I had to use the translate function)

Text:

https://p5js.org/tutorials/loading-and-selecting-fonts/ (learning how to use text fonts and functions)

https://p5js.org/reference/ (Typography section to understand the types of text functions)

I also kept going back to Professor Aya’s slides, Week 4.1 and Week 4.2

I also used Musixmatch to get the lyrics of the song Yellow by Coldplay

Hardest Part of the Code (and the Part I’m Most Proud Of):

 //First layer of the lyrics 
push(); //starting a "transformation state" - using 'push' to save the current canvas settings
translate(10, height - scroll); //starting to move the original position 0,0 to 10 pixels from the left and being close to the bottom of the canvas 

for (let i = 0; i < lyrics.length; i++) { //going through the lyrics line by line 
  let yPos = i * 40; //each line is 40 pixels below the previous one -using the name yPos for the positioning of lines vertically (y-axis)
  
  //black txt if below the middle and white if above
  let currentY = (height - scroll) + yPos;//used to figure out the positions of the line
  if (currentY < height / 2) { //checking if the lyric is above the middle half, if it is it will change color
    fill(255); //the color of the text -white
  } else { //if it is below the screen it will..
    fill(0);
  }
  
  textSize(18); //text size of the lyrics 
  textStyle(BOLD); //adding bold text
  text(lyrics[i], 0, yPos, 280); //in order for the long lyrics to not go off the screen -lyrics is drawn inside this 280 sized pixel 
}
pop(); //concluding the end of the transformation so the other header doesnt move too

The hardest part was understanding how push(), pop(), and translate() all worked without affecting the rest of my design. The push() allowed me to save the current canvas settings before transforming it. Then with [translate(10, height – scroll);] I was able to shift the origin point so the lyrics would start near the bottom and move upward as the scroll value increased.

Inside the loop, [let yPos = i * 40;] this function made each lyric line 40 pixels below the previous one which keeps everything equal and smooth.

Then I used [let currentY = (height – scroll) + yPos;] to calculate the piston of each line on the canvas so that I can check where the lyric is positioned which will determine if it would change from black text to white (similar to Spotify). If the lyric was above the middle of the screen, it changed to white and if not it remained black until it reaches that point.

Similar to the issues I faced before, I had a hard time keeping the text inside the canvas because it would always go out. I decided to use [text(lyrics[i], 0, yPos, 280);] that draws each lyric at the x-position 0 and y-position yPos while being inside a 280-pixel text box so long lines wouldn’t overflow off the screen.

I’m proud of this section because even though it took time to understand, I was able to control the translation without changing the rest of my code. At one point I accidentally deleted the entire section, which was frustrating because it took me a while to feel confident in it, but since I practiced it before, I was able to rewrite it again.

Issues I faced:

At first, the lyrics started in the middle of the canvas and would bleed outside the frame which was a struggle because I couldn’t figure out how to fix it for a second. I remembered Professor Mang explaining how small positioning errors can happen when centering the text exactly in the middle because it will start exactly there instead of being all on the center. I also carefully adjusted the placement using translate() and the text box width: [text(lyrics[i], 0, yPos, 280);] that helped keep everything within the screen and made the scrolling smoother.

Final code: 

let lyrics = [//using the name lyrics for the array/list
  //adding all the lyrics of the song from Yellow -Coldplay
  "Look at the stars",
  "Look how they shine for you",
  "And everything you do",
  "Yeah, they were all yellow",
  "I came along",
  "I wrote a song for you",
  "And all the things you do",
  "And it was called, \"Yellow\"",
  "So, then I took my turn",
  "Oh, what a thing to have done",
  "And it was all yellow",
  "Your skin, oh yeah, your skin, and bones",
  "Turn into something beautiful",
  "And you know, you know I love you so",
  "You know I love you so",
  "I swam across",
  "I jumped across for you",
  "Oh, what a thing to do",
  "'Cause you were all yellow",
  "I drew a line",
  "I drew a line for you",
  "Oh, what a thing to do",
  "And it was all yellow",
  "And your skin, oh yeah, your skin, and bones",
  "Turn into something beautiful",
  "And you know, for you, I'd bleed myself dry",
  "For you I'd bleed myself dry",
  "It's true",
  "Look how they shine for you",
  "Look how they shine for you",
  "Look how they shine for-",
  "Look how they shine for you",
  "Look how they shine for you",
  "Look how they shine",
  "Look at the stars",
  "Look how they shine for you",
  "And all the things that you do"
];

let scroll = 0; //letting the lyrics start scrolling vertically

function setup() {
  createCanvas(300, 500); //canvas size
  textAlign(LEFT, TOP);  //postion of the text
  textFont('Helvetica'); //text font 
}

function draw() {
  background(158, 91, 28); //background color 'burnt orange'

   //First layer of the lyrics 
  push(); //starting a "transformation state" - using 'push' to save the current canvas settings
  translate(10, height - scroll); //starting to move the original position 0,0 to 10 pixels from the left and being close to the bottom of the canvas 
  
  for (let i = 0; i < lyrics.length; i++) { //going through the lyrics line by line 
    let yPos = i * 40; //each line is 40 pixels below the previous one -using the name yPos for the positioning of lines vertically (y-axis)
    
    //black txt if below the middle and white if above
    let currentY = (height - scroll) + yPos;//used to figure out the positions of the line
    if (currentY < height / 2) { //checking if the lyric is above the middle half, if it is it will change color
      fill(255); //the color of the text -white
    } else { //if it is below the screen it will..
      fill(0);
    }
    
    textSize(18); //text size of the lyrics 
    textStyle(BOLD); //adding bold text
    text(lyrics[i], 0, yPos, 280); //in order for the long lyrics to not go off the screen -lyrics is drawn inside this 280 sized pixel 
  }
  pop(); //concluding the end of the transformation so the other header doesnt move too
  
  //The scrolling movement 
  scroll += 0.6; //Speed of the scroll to make it a little faster since its the full song being used

  //Restarting the lyrics loop
  //checking if the scroll distance is greater > than the total height of the list
   if (scroll > (lyrics.length * 40) + height) { //calculation of height from the number of lines multiplied by 40 pixels each
   scroll = 0; //restarting the loop so the lyrics can come back from the bottom
  }

  //The title on the top
  noStroke(); //removing the outline of the rect
  fill(158, 91, 28); //color of background 
  rect(0, 0, width, 100); //adding a rect so that the lyrics can go under it -similar to spotify

  fill(255); //color of the text 'yellow'
  textAlign(CENTER); //adding it to the center of the frame
  textStyle(NORMAL);
  textSize(14); //size of text
  text("Yellow", width / 2, 45); //name of text + size
  
  textSize(12); //size of the second text of the band
  fill(255, 200); //color of the text -making it a little less white
  text("Coldplay", width / 2, 65); //name of text + size

  //Interactivity
  //when the mouse doesn't click, the lyrics move normally 
  //if mouse is clicked and stays on the canvas the lyrics will speed up
  if (mouseIsPressed) { //mouse pressed function
    scroll += 30; //move fast 
  } else {
    scroll += 0.6; //move at the normal speed
  }

}

Future improvements:

Even though this is my first time coding and creating generative text, I’m proud of how it turned out especially because of the scrolling effect and how similar it feels to Spotify’s lyrics feature. In the future, I’d like to add the time duration of the song at the bottom, along with play and pause buttons to make it feel more realistic. I also want to add small music note icons and experiment with the spacing between specific lines to match the original lyrics.

Assignment 4: The Text that Spins.

“You can make anything by writing” – C. S. Lewis

Concept:

I’ve been watching a few of Patt Vira’s videos on coding and one of them about generative text jumped out to me. In this video she made the text rotate in a interesting pattern. So I followed along the video and decided to try and maybe extended it a little bit so it’s a bit more unique. I also will say I’m still a bit new to the use of many of the JS functions and syntax, I’d like to hopefully revisit this in the future. But I’ll save it for the reflection.

I noticed a few things I could improve her code on, such as giving it different fonts. It was a bit stale if only one font was used so I thought maybe we can make it so when the user clicks, the fonts change. Also it was a bit bland seeing the hello text, so why not give the user the option to change around the text. Below is the final product.

How it’s made:

So the code is done with points, where the points are displayed based on the text’s poistion. Then lines are drawn to give that 3D effect of the code. Now this code utilizes the angles, and degrees which I will be honest, I struggle to understand as they were quite, finiky to say the least.

But then the text itself is displayed on the screen, in the rotational motion and in 3D. Again, learning this was quite a challenge but it was interesting nonetheless.

Highlighted bit of Code I’m proud of:

I decided to make it so there are different fonts used when the user clicks the screen.

function mousePressed() {
  if (mouseY > 50) {
      currentFontIndex = (currentFontIndex + 1) % fonts.length;
      font = fonts[currentFontIndex];
      updatePoints();
  }
}

This function above cycles through a list of fonts and goes to the next font in the list. Based on the font, it gives off a different design, ranging in boldness and style. It was a good amount of fun to make but I will say I struggled here a little as initially I thought I could make a list using the loadFont function. But then after a bit more thinking, it is better to dynamically change the font type, using a different variable for a list. So I’ve made a fontList variable which houses the many fonts I’ve added to the file

let fontPaths = [
  "fonts/GoogleSans_17pt-Regular.ttf",
  "fonts/Dosis-Regular.ttf", 
  "fonts/ArchivoBlack-Regular.ttf",
  "fonts/PlaywriteNZBasic-Regular.ttf",
  "fonts/Bungee-Regular.ttf"
];

Reflection

I will say, I wish I could do more with this code. Obviously time constraints and many assignments coming up did hinder the overall product but I feel like some ways I can expand on it is by making different designs of the typography. Or even, finally figuring out how to make the text spin in different directions. However I feel confident in what I learned so far through this assignment so hopefully I can use this to expand on it for the midterm project.

Week 4- Reading

One thing that drives me crazy is QR-code restaurant ordering. I scan the code and it opens a slow website that is hard to use. Important things are hidden, like how to add notes (“no ice,” “no peanuts”), and the buttons are often small or confusing. Sometimes the page refreshes and my order disappears, so I have to start over. It makes me feel like I’m doing extra work just to buy food.

Using Norman’s design ideas, this could be improved in a few simple ways. First, the main actions should be obvious, like “Order,” “Ask for help,” and “Pay.” Second, the steps should match how people actually think: pick items, customize, review, then submit. Third, the system needs clear feedback, like “Added to cart,” “Order sent,” and a confirmation screen, so I know it worked. It should also prevent common mistakes by reminding me if I forgot a required choice, like size or toppings. Finally, it should save my progress if the internet is bad, instead of deleting everything.

We can apply the same principles to interactive media, like games, apps, VR, or interactive art. The most important thing is discoverability: people should quickly understand what they can do. Interactive objects should have clear signs, like a highlight, an icon, or a small animation that hints “touch me” or “pick me up.” Controls should also feel natural. If I move something left, the result should move left, not the opposite, unless the experience clearly explains why.

Feedback is also essential in interactive media. When I tap, grab, or press something, the system should respond right away with sound, vibration, movement, or a visual change. This makes the experience feel reliable. Good design also helps users recover from errors. There should be a clear way to undo, restart, or exit, so users don’t get stuck feeling confused. Overall, Norman’s principles remind us that if many people struggle, the design should change—not the user.

Week 4 – Data Visualization

Concept

I am from Moscow, and recently somehow I had a lot of conversations about metro.  Moscow metro has a lot of different lines and more than 300 stations, and I wanted to visualize it in some way.

© Transport.mos.ru, 2025

I found data on all the stations and lines on official dataset by Moscow Government and used it (link doesn’t open without Russia VPN). Sadlly, even this the fullest dataset didn’t have coordinates on stations, so I decided to adjust the concept. Now, my piece displays all the stations with their respective color in the random places on the canvas, and then slowl draws connections between them. This way, it shows how complex the system is and how many connection there are, because at some point it becomes impossible to even differentiate which line is from which points, and I only draw one connection from one dot. I think it perfectly serves to the idea of showing complexity of the metro at my hometown.

Highlight Code

The part I struggled the most in the coding was extracting data: in the row, it was divided by semicolons, not commas, and the language was Russian, of course, and the writing system was cyrillic. I struggled to understand what is written where in the original document and to clear the data so I can properly extract parts that I need.

//clear the data from garbage that prevents from proper extraction of data
function cleanCell(dirtyData) {
  if (!dirtyData) return "";
  let str = String(dirtyData);
  let match = str.match(/value=(.*?)\}/); //The Regex from GEMINI: see if there's repeated character up to the curly braces
  if (match && match[1]) return match[1]; //return actual data from regex
  return str.replace(/^"|"$/g, '').trim(); //replace the quotations from the beggining and the end of the line and clear up additional spaces
}

This was the part I learned from the most. It uses Regex (.*?)\}/ and /^”|”$/g to check the data. Basically these characters represent some pattern the computer is checking. For instance, /^”|”$/g: /^” matches the quotation mark in the beggining of the line, | serves as logical OR, “$/ matches the mark in the end of the line, g allows the program run even after the first match. I didn’t know about this method but I find it really useful in working with data.

Overall, I created a class for stations, so each station object holds the line name and the station name, and has a method for drawing the node as a small circle with color of its line. All stations are added to the dictionary as values for the key that is their line name. Then, by the line name computer accesses all the stations on this line and slowly draws connections one by one in random order using lerp function. Also, the user can access the code and toggle the names of the stations: choose to show them or see the sketch without them at all. Upon click the user can restart the sketch thanks to mouseClicked() function.

Reflection

I think it would be really nice to somehow find data on stations with actual coordinates, so I can display them on canvas as they positioned in real life. After that, I believe it’s possible to experiment with way of connection all them: from real way, to more sharp or abstract style. I believe that this way it will look more like data respresentation and closer to my original idea.

Also I thought about grouping the stations by district and this way sort them by allocation each district a position on the canvas that will be close to its position on the real map.

Week 4 Generative Text Artwork Zere

Concept: I felt inspired by the generative text artworks we reviewed in our last class. I decided to construct the scene using repeated words, rather than the usual shapes or images.

Process: I really tried to simplify my task of creating this piece, as I think that my skills with JavaScript are quite limited. The challenging part for me was trying to understand how to map out/divide the canvas into regions for text. It’s not a shape with particular coordinates, and that is why it was a little hard for me. Here is an example from the code:

// sky
function drawSky() {
  fill(100, 160, 255);
  textSize(14);

  for (let y = 0; y < height / 2; y += 20) {
    for (let x = -50; x < width; x += 60) {
      text("sky", x + offset % 60, y);

My code:

let offset = 0;

function setup() {
  createCanvas(400, 400);
  textFont('Arial');
}

function draw() {
  background(255);
  offset += 0.5;

  drawSky();
  drawSun();
  drawBuildings(); //  static buildings!!!!!
  drawRoad();
  drawCars();     
}
// sky
function drawSky() {
  fill(100, 160, 255);
  textSize(14);

  for (let y = 0; y < height / 2; y += 20) {
    for (let x = -50; x < width; x += 60) {
      text("sky", x + offset % 60, y);
    }
  }
}
// sun
function drawSun() {
  fill(255, 180, 0);
  textSize(16);

  for (let y = 40; y < 120; y += 18) {
    for (let x = 250; x < 350; x += 40) {
      text("sun", x, y);
    }
  }
}
// bldngs
function drawBuildings() {
  fill(80);
  textSize(12);

  for (let y = height / 2; y < height - 80; y += 18) {
    for (let x = 0; x < width; x += 55) {
      text("building", x, y);
    }
  }
}
// road
function drawRoad() {
  fill(120);
  textSize(14);

  for (let y = height - 80; y < height; y += 20) {
    for (let x = -40; x < width; x += 60) {
      text("road", x - offset % 60, y);
    }
  }
}
//cars 
function drawCars() {
  textSize(16);

  let colors = [
    color(255, 0, 0),     // red
    color(255, 200, 0),   // yellow
    color(0, 180, 0)      // green
  ];

  let roadTop = height - 80;

  for (let i = 0; i < 3; i++) {
    fill(colors[i]);

    let y = roadTop + 25 + i * 15;
    let speed = 6 + i * 2; // FAST
    let x = (frameCount * speed) % (width + 80) - 80;

    text("car", x, y);
  }
}

Reflection: I constantly mention this, but I feel that, due to my limited ability with JavaScript, I’m unable to create pieces that match my imagination, which is why I tend to stick to safer and simpler options for the sake of my sanity. I will try to do more elaborate artworks in the future and expand my coding skills.

Week 4 – Generative Text Assignment (Updated)

Concept:

I sought a degree of inspiration from Camillie Utterback’s Text Rain. One key difference, while Utterback made text fall under ‘gravity’ and react to people’s silhouette, I decided to map text onto continuous mathematical functions (see example below)
*The image above depicts a sine curve with the text ‘this is a simple sin function’.

Core Functionality and Features

The program contains a class appropriately called ‘fun’ (abbreviation for ‘function’) that contains attributes and methods, including a ‘display’ method to draw the curve onto the canvas. ‘fun’ also contains methods for changing the type of function – example from a ‘sin’ to a ‘log’ – and a method for fetching the function’s value from the parameter ‘t’ which in this case, spans the width of the canvas.

The ‘fun’ class once instantiated into an object contains data that alters how the drawn function appears. For example, the ‘sin’ function has the following ‘fun.function_data’:

let SineFunction = new fun("sin");

SineFunction.function_data[0] = 100;    // AMPLITUDE 'a'
SineFunction.function_data[1] = 0.01;   // Frequency 'r'
SineFunction.function_data[2] = 10;     // X Translation 'x'
SineFunction.function_data[3] = 200;    // Y Translation 'y'

/*
SineFunction.getF(t) {
  return a * Math.sin( r * t - x ) + y
}
*/

There are 7 basic types of mathematical functions that ‘fun’ can represent, namely sine (sin), cosine (cos), tangent (tan), hyperbolic sine (sinh), hyperbolic cosine (cosh), hyperbolic tangent (tanh), logarithmic (log).

Additionally, for more advanced generation, there is an additional class ‘algebricF’, that is used in a very similar way to ‘fun’, except that it can store up to two ‘fun’ or ‘algebricF’ classes in it, and performs a variety of specified algebraic operations on the class. Please do not mind the spelling mistake in the name of the class. The operations include addition, subtraction, multiplication, and division.

For interesting visuals, each drawn function’s text is assigned a color. The color is blue near the bottom of the screen, red near the top, and then this is blended with linear interpolation based on its ‘y’ position divided by the canvas ‘height’. A second linearly interpolated color is then chosen for the lateral span, from green at the left and purple (updated 20:20 GST, 16 FEB) at the right. The result of this interpolation is then linearly interpolated with the color of the vertical component, to grant a final color. The vertical color component gets priority because of how the third interpolation is carried out.

Demo

There are 3 demonstrations built into the code itself (courtesy of myself). They can be alternated between by pressing the keys ‘1’, ‘2’, ‘3’, or ‘0’ to disable all demonstrations. Click the screen to toggle whether the drawing moves or is paused.

Settings & Configurable Components

Certain settings and components may be configured in the Configuration section at the absolute top of the sketch. It is recommended that you read the comment or description given to each setting before playing around. It is also highly recommended to NOT alter any code beyond this unless you know what you are doing.

To create your own equations, declare an empty variable just before the setup function, and instantiate the correct ‘fun’ or ‘algebricF’ object inside of the setup function itself. Then, call its display method inside of the draw function. You may need to play around with the various components that constitute towards the function, especially amplitude and the y-translation.

Known Bugs

While there is a toggleable variable called ‘ADJUST_SCREEN’ in the Configuration section – which strives to adjust the screen to capture off-screen parts of the graph – it does not work correctly for all cases, thus it is recommended to use functions that stay within the screen if it is so that you actually want to see stuff.

Code & Additional Credits

As no external media or assistance (exception below) was used, I (Raja Qasim Khan) am the sole author and developer of the project’s code.

(Exception): https://p5js.org/reference/ was used for parts such as fetching date and time (for the canvas capture functionality) and the syntax of certain keywords in the language. No additional sources were employed.

Notes from the developer: I began by starting with the absolute basics of the project in the Processing 3 IDE for Python. Once the basics were done with, I re-wrote the code in JavaScript in p5.js and finished most of the remaining features there.

Please find the code below.

// CONFIGURATION
let TEXT_SIZE = 14; // size of the displayed text
let POINT_INCREMENT = 7; // the increment of character positioning in the display

// The gradient of colors in the Y axis
let MIN_COLOR_Y;
let MAX_COLOR_Y;

// The gradient of colors in the X axis
let MIN_COLOR_X;
let MAX_COLOR_X;

let MOVE_RESISTANCE = 1.2; // The resistance coefficient in moving the graph across the screen. -ve for backwards motion.

let ADJUST_SCREEN = false; // If the screen should translate to include outside points.

let PREBUILT_DISPLAY_NUMBER = 2; /*

Prebuilt by me for demo purposes:

0: don't display any demo.
1 : single 'cos' function.
2: multiple function demo.
3: algebric function demo.
4 show credit information.

*/

let OVERLAY_ENABLED = true; // whether the information overlay is enabled or not.

let BACKGROUND_COLOR = [255, 255, 255, 255]; // the background color RGBA

// GLOBALS
let current_offset = 0;
let isMoving = false;

// MAIN CODE

let F1;
let F2;

let F3;
let F4;

let FUN1;
let FUN2;
let FUN3;

let AF12;
let AF;

let CR;

function setup() {
  frameRate(30);
  createCanvas(800, 400);
  
  background(color(BACKGROUND_COLOR[0], BACKGROUND_COLOR[1], BACKGROUND_COLOR[2], BACKGROUND_COLOR[3]));
  
  F1 = new fun("cos");
  
  F1.function_data[0] = 100;
  F1.function_data[1] = 0.02;
  
  F1.function_data[2] = 0;
  F1.function_data[3] = 200;
  
  F2 = new fun("cos");
  
  F2.function_data[0] = -100;
  F2.function_data[1] = 0.02;
  
  F2.function_data[2] = 0;
  F2.function_data[3] = 200;
  
  F3 = new fun("tan");
  
  F3.function_data[0] = 20;
  F3.function_data[1] = 0.02;
  
  F3.function_data[2] = 0;
  F3.function_data[3] = 370;
  
  F4 = new fun("tan");
  
  F4.function_data[0] = 20;
  F4.function_data[1] = 0.02;
  
  F4.function_data[2] = 0;
  F4.function_data[3] = 30;
  
  FUN1 = new fun("sin");
  FUN1.function_data[0] = 100;
  FUN1.function_data[1] = 0.02;
  
  FUN1.function_data[2] = 0;
  FUN1.function_data[3] = 200/3;
  
  FUN2 = new fun("sin");
  FUN2.function_data[0] = 50;
  FUN2.function_data[1] = 0.04;
  
  FUN2.function_data[2] = 0;
  FUN2.function_data[3] = 200/3;
  
  FUN3 = new fun("cos");
  FUN3.function_data[0] = 100/3;
  FUN3.function_data[1] = 0.06;
  
  FUN3.function_data[2] = 0;
  FUN3.function_data[3] = 200/3;
  
  AF12 = new fun("algebricF");
  AF12.eq.fun1 = FUN1;
  AF12.eq.fun2 = FUN2;
  
  AF = new fun("algebricF");
  AF.eq.fun1 = AF12;
  AF.eq.fun2 = FUN3;
  
  CR = new fun("sin");
  
  CR.function_data[0] = 10;
  CR.function_data[1] = 0.005;
  
  CR.function_data[2] = 0;
  CR.function_data[3] = 200;
  
  MIN_COLOR_Y = color(0, 0, 255);
  MAX_COLOR_Y = color(255, 0, 0);
  
  MIN_COLOR_X = color(0, 255, 0);
  MAX_COLOR_X = color(255, 0, 255)
}

class algebricF {
  constructor() {
    /*
    (None) -> None
    
    A custom computation that MUST be an algebric combination of two 'fun's.
    
    can also be made of two algebricF's, or one algebricF.
    
    Operators: "+", "-", "/", "*"    
    */
    this.operator = "+";
    
    this.fun1 = "null";
    this.fun2 = "null";
  }
  
  getF(t) {
    if (this.operator == "+") {
      return this.fun1.getF(t) + this.fun2.getF(t);
      
    } else if (this.operator == "-") {
      return this.fun1.getF(t) - this.fun2.getF(t);
      
    } else if (this.operator == "/") {
      return this.fun1.getF(t) / this.fun2.getF(t);
      
    } else if (this.operator == "*") {
      
      return this.fun1.getF(t) * this.fun2.getF(t);
    }
  }
}

class fun {
  constructor(function_type) {
    /*
    (str) -> None
    
    Creates a new, displayable function.
    function_type s:
        "sin", "cos", "tan", "sinh", "cosh", "tanh", "log"
    */
    
    this.function_type = function_type;
    this.function_data = [];
    this.eq = new algebricF();
  }
  
  getF(t) {
    /*
    (float) -> float
    
    Returns the result of the function at 't'.
    */
    
    // Please do not mind the atrocity below. That is how I normally code else..if statements before deciding to switch to more readable ones for the sake of this class.
    
    if (this.function_type == "sin") {
      
      let a = this.function_data[0];
      let r = this.function_data[1];
      let x = this.function_data[2];
      let y = this.function_data[3];
      
      return a * Math.sin(r * t - x) - y;
    } else {
      
      if (this.function_type == "cos") {
        let a = this.function_data[0];
        let r = this.function_data[1];
        let x = this.function_data[2];
        let y = this.function_data[3];
      
        return a * Math.cos(r * t - x) - y;
      } else {
        
        if (this.function_type == "sinh") {
          let a = this.function_data[0];
          let r = this.function_data[1];
          let x = this.function_data[2];
          let y = this.function_data[3];
      
          return a * Math.sinh(r * t - x) - y;
        } else {
          
          if (this.function_type == "cosh") {
            let a = this.function_data[0];
            let r = this.function_data[1];
            let x = this.function_data[2];
            let y = this.function_data[3];
      
            return a * Math.cosh(r * t - x) - y;
          } else {
            
            if (this.function_type == "tan") {
              let a = this.function_data[0];
              let r = this.function_data[1];
              let x = this.function_data[2];
              let y = this.function_data[3];
      
              return a * Math.tan(r * t - x) - y;
            } else {
              
              if (this.function_type == "tanh") {
                let a = this.function_data[0];
                let r = this.function_data[1];
                let x = this.function_data[2];
                let y = this.function_data[3];
      
                return a * Math.tanh(r * t - x) - y;
              } else {
                
                if (this.function_type == "log") {
                  let b = this.function_data[0];
                  let r = this.function_data[1];
                  let x = this.function_data[2];
                  let y = this.function_data[3];
      
                  return r * Math.log(t - x) / Math.log(b) - y;
                } else {
                  
                  if (this.function_type == "algebricF") {
                    return this.eq.getF(t);
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  
  changeTo(new_function_type) {
    /*
    (str) -> None
    
    Changes the function type and replaces all function_data with default values.
    */
    
    this.function_type = new_function_type;
    
    if (this.function_type == "sin" || this.function_type == "cos" || this.function_type == "sinh" || this.function_type == "cosh" || this.function_type == "tan" || this.function_type == "tanh" || this.function_type == "log") {
      this.function_data = [
        10,
        0.1,
        0,
        50
      ];
    } else if (this.function_type == "algebricF") {
      this.function_data = [];
      this.eq = new algebricF();
    }
  }
  
  display(txt) {
    /*
    (str) -> None
    
    Draws the function on screen using characters from txt.
    */
    
    let entry = -this.getF(current_offset * MOVE_RESISTANCE);
    let exit = -this.getF(width + current_offset * MOVE_RESISTANCE)
    
    let Y_OFFSET = (entry + exit) / 2
    
    if (Y_OFFSET < 0) {
      // pass
    } else if (Y_OFFSET > height) {
      // pass
    } else {
      Y_OFFSET = 0;
    }
    
    if (!ADJUST_SCREEN) {
      Y_OFFSET = 0;
    }
    
    for (let i = 0; i <= Math.trunc(width / POINT_INCREMENT); i++) {
      
      let Y = -this.getF(i * POINT_INCREMENT + current_offset * MOVE_RESISTANCE) - Y_OFFSET; // Inverted because of the coordinate system.
      
      if (isNaN(Y)) {
        continue;
      }
      
      let selected_char = txt[Math.trunc(i) % txt.length];
      
      fill(lerpColor(lerpColor(MAX_COLOR_Y, MIN_COLOR_Y, abs(Y) / height), lerpColor(MIN_COLOR_X, MAX_COLOR_X, i * POINT_INCREMENT / width), abs(i*Y * POINT_INCREMENT)/(width*height)));
      textSize(TEXT_SIZE);
      text(selected_char, i * POINT_INCREMENT, Math.trunc(Y));
    }
  }
}

function draw() {
  background(color(BACKGROUND_COLOR[0], BACKGROUND_COLOR[1], BACKGROUND_COLOR[2], BACKGROUND_COLOR[3]));
  
  if (PREBUILT_DISPLAY_NUMBER == 2) {
    
    F1.display("from nothing we were raised and with nothing we return");
    F2.display("from nothing we were raised and to everything we built");
  
    F3.display("and return to gardens we may");
    F4.display("and return to flames we will");
  } else if (PREBUILT_DISPLAY_NUMBER == 3) {
    
    AF.display("this is an algebric function");
  } else if (PREBUILT_DISPLAY_NUMBER == 1) {
    
    F1.display("this is a simple sin function");
  } else if (PREBUILT_DISPLAY_NUMBER == 4) {
    
    CR.display("Raja Qasim Khan (rk5260)")
  }
  
  
  
  
  if (isMoving) { // This translates the graph to create a cool moving illusion.
    
    current_offset++; // a tanslation for the lateral movement of the function(s).
  } else if (OVERLAY_ENABLED) {
    
    // display overlay menu.
    fill(color(255-BACKGROUND_COLOR[0], 255-BACKGROUND_COLOR[1], 255-BACKGROUND_COLOR[2])); // the inverse color
    textSize(14);
    
    text("Click to toggle.", 100, 20);
    text("Demo (press key): ", 100, 34);
    text("0: Disable demo.", 100, 48);
    text("1: Single cos demo.", 100, 62);
    text("2: Multi-function demo.", 100, 76);
    text("3: Algebric demo.", 100, 90);
    text("4: Credits info", 100, 104);
    text("T: Disable overlay!", 100, 118);
    text("C: Capture canvas (image).", 100, 132);
  }
}

function mouseClicked() {
  
  isMoving = !isMoving;
}

function keyPressed() {
  
  // Switch displayed prebuilt simulation.
  if (keyCode == 48) {
    
    PREBUILT_DISPLAY_NUMBER = 0;
  } else if (keyCode == 49) {
    
    PREBUILT_DISPLAY_NUMBER = 1;
  } else if (keyCode == 50) {
    
    PREBUILT_DISPLAY_NUMBER = 2;
  } else if (keyCode == 51) {
    
    PREBUILT_DISPLAY_NUMBER = 3;
  } else if (keyCode == 52) {
    
    PREBUILT_DISPLAY_NUMBER = 4;
  } else if (keyCode == 84) {
    
    OVERLAY_ENABLED = !OVERLAY_ENABLED;
  } else if (keyCode == 67) {
    
    // capture screenshot of canvas.
    saveCanvas("rqk_TextualGraphics_HH" + hour().toString() + "_MM" + minute().toString() + "_SS" + second().toString() + "_dd" + day().toString() + "_mm" + month().toString() + "_yyyy" + year().toString())
  }
}

Link to project: https://editor.p5js.org/rk5260/sketches/Y8JIsLCxj

Week 4 – Click to affirm, I guess? (Generative Text and Data Assignment)

Concept:

I really like Frutiger Aero, and I love things that are designed so badly that they just become funny and it actually works. I’ve fallen victim so many times to buying products with designs that were just so ugly and horrendous that it made me laugh. Lately, on my Pinterest, I keep seeing these stupid affirmation memes with really loud and emphatic text and completely random background and this horrendous gradient border. Naturally, I was so drawn to it, that now I have these stuck up on my wall back home. My parents are frustrated. My sister loves it.

I’m not a fan of making the same thing as something I see, but since I’m still a beginner to Javascript, I wanted to make something that I would enjoy making, especially in such a short period of time. So, I decided to make my own version of these. I hope you find this as funny as I do.

Artwork:

(I laughed a lot while making this.)

Process:

I had to make so many sections and so many functions and so many variables and etcetera, etcetera. Firstly, I had to import each image into the program, and I named each image by number so that importing them would be easier. I also made a list of affirmations I found online and through my friends onto a spreadsheet and imported it as a .csv file. Since I wanted the option of inputting your own affirmations, I made another list for user input.

function preload(){
  table = loadTable('Affirmations.csv','csv'); //csv file
  for (let i=0; i < totalImages; i++)
  {
    images.push(loadImage(i+'.jpg'));
  }
}

I loaded each row from the .csv file.

// load from csv file (I'm starting on row 2 because header lol)
 for (let r = 2; r < table.getRowCount(); r++){
   let line = table.getString(r,0);
   if (line) Affirmations.push(line); // prevent empty
)

After adding user input, I made a function to generate a new image every 3 seconds. There were multiple steps I had to take for this, which were:

FIRSTLY! Pick a random nature image:

let nextImgIndex;
  do {
    nextImgIndex = floor(random(images.length));
  } while (nextImgIndex == lastImgIndex);
  currentImg = images[nextImgIndex];
  lastImgIndex = nextImgIndex;

SECONDLY! Pick which list is currently active (the .csv file or the user input list):

let activeList;
if (modeCheckbox.checked() &&
   userAffirmations.length > 0) {
  activeList = userAffirmations;
} else {
  activeList = Affirmations;
}

THIRDLY! Pick a random affirmation from the chosen list:

if (activeList.length > 0){
   let nextTextIndex;
   do {
     nextTextIndex = floor(random(activeList.length));
   } while (nextTextIndex === lastTextIndex && activeList.length > 1);
   
   currentText = activeList[nextTextIndex];
   lastTextIndex = nextTextIndex;
 }

FOURTHLY! Add glow. Yay!

colorMode(HSB, 360, 100, 100);
 glowColor = color(random(360), 85, 100);
 colorMode(RGB);

For drawScene(), I used this code. I realized I could crop the nature images in the code (code is so cool… wow) so I did it in this.

function drawScene() {
  if (!currentImg || currentImg.width <= 1) return;

  // automatic crop image to square size
  let imgAspect = currentImg.width / currentImg.height;
  let canvasAspect = width / height;
  let sx, sy, sw, sh;
  if (imgAspect > canvasAspect) {
    sh = currentImg.height;
    sw = sh * canvasAspect;
    sx = (currentImg.width - sw) / 2;
    sy = 0;
  } else {
    sw = currentImg.width;
    sh = sw / canvasAspect;
    sx = 0;
    sy = (currentImg.height - sh) / 2;
  }
  
  image(currentImg, 0, 0, width, height, sx, sy, sw, sh);
  drawGlowBorders();

  // text style
  let txt = currentText;
  let maxWidth = width * 0.85;
  let fontSize = constrain(map(txt.length, 0, 50, 80, 40), 35, 90);

  push();
  translate(width / 2, height / 2);
  scale(1, 1.7); 
  textAlign(CENTER, CENTER);
  textFont('Arial');
  textStyle(BOLD);
  textSize(fontSize);

  drawingContext.shadowBlur = 30;
  drawingContext.shadowColor = glowColor.toString();
  fill(255);
  noStroke();
  
  text(txt, -maxWidth/2, -height/3.4, maxWidth, height/1.7);
  
  drawingContext.shadowBlur = 0;
  text(txt, -maxWidth/2, -height/3.4, maxWidth, height/1.7);
  pop();
}

I thought the glow borders at the very end were really funny to figure out, but this is what I ended up with.

function drawGlowBorders() {
  let bSize = 45;
  noFill();
  for (let i = 0; i < bSize; i++) {
    let alpha = map(i, 0, bSize, 180, 0);
    stroke(red(glowColor), green(glowColor), blue(glowColor), alpha);
    strokeWeight(1);
    rect(i, i, width - i*2, height - i*2);
  }
}

Reflection:

There’s a lot of things I want to improve. Obviously, with the current skill level and time that I have, I don’t think this would be feasible, but I wanted to make sure you could save your custom affirmations into a file you can download later. I also wanted to let you delete affirmations you didn’t like and add more (database management? I guess?).  I also found out that .csv files cannot store images, so I was limited to using only 21 images for now. I honestly made this so it could double not only as an assignment but also a funny thing for my friends to laugh at, so I think I achieved this, but I would like to play more with the generative aspect and data aspect of this. The text itself on the screen is not interactive (e.g. if I click it, nothing happens) and the data is not really visualized, more being used as a list to remove the need to individually type each affirmation into the code. I’m glad I figured out parts of the code that I know in Python though (like input) so hopefully that should make future projects easier.

Week 4 – Reading Reflection

Thinking after Norman’s ideas, as a student two objects that annoy me is the the AC system in the Baraha common rooms and the tiny sinks. In Baraha, the AC is set around 21°C, and when I press the buttons to increase the temperature, the system promises a gradual change, but you have to wait for a long time and I do not feel that the temperature is increasing (and sometimes it is indeed is not), so the interface gives me an illusion of control instead of real control. The tiny sinks have the same problem in a physical way: For me the sinks we have in the dorms are small. The faucet sits so close to the basin that there is almost no space for my hands, and water splashes everywhere, even though the sink looks normal. Both cases show broken mapping: what I do and what actually happens do not match my expectations, and the design never clearly tells me what is really possible. To improve them, the AC interface should not take so long for temperature change and should show honest information about how the centralized system works, and the sinks should be redesigned with more vertical space for more comfortable hand-washing.

For interactive media, I apply Norman’s principles by treating mapping, and conceptual models as the core of how I design my p5 sketches. As an interactive media student, I know that users understand a piece through the system image in front of them, not through my code, so I need clear signifiers on the screen that show what can be clicked, spoken, or dragged. I design interactions so the layout of elements matches their effects, and I give immediate feedback when the user does something, instead of delaying them, like the AC does. When I build a sketch, I test whether someone new can guess what to do in the first few seconds and form a simple model of how the piece behaves, because for a user that understanding is key for deeper, more emotional experience rather than leaving them stuck in trial .