Assignment # 4 Magnetic Effect Text

Concept

The magnetic effects and trails that I have come across a lot while looking for inspiration for this project served as my motivation. After doing some research, I came to Professor Aron’s website, which featured several interactive elements related to text manipulation. And it was these pictures on Pinterest that inspired me. The primary concept was kinetic typography, which used particles to smoothly transition between words to depict the unpredictable character of the natural world. It demonstrates how basic components can come together to create meaningful expressions when they are directed by physics and a little bit of randomness.

Code

The part of the assignment I’m most proud of is the creation of the Vehicle class. This piece of code is special because it makes each dot on the screen move like it has its mind, heading towards a goal. It combines simple rules of movement and direction to bring letters on the screen. What makes this class stand out is how it turns basic coding concepts into something that feels alive and interactive.

let font;
let vehicles = [];
let originalPoints = [];
let cycleDuration = 5000; // Duration of one cycle in milliseconds
let lastResetTime = 0; // Tracks the last time the positions were reset
let words = ["SHOK", "SHAK", "SHOK"]; // Array of words to cycle through
let currentWordIndex = 0; // Index of the current word in the array

function preload() {
  font = loadFont('myfont.otf');
}

function setup() {
  createCanvas(800, 300);
  background(255);
  textFont(font);
  textSize(192);
  setupWord(words[currentWordIndex]);
}

function draw() {
  let currentTime = millis();
  if (currentTime - lastResetTime > cycleDuration) {
    // Move to the next word in the array, cycling back to the start if necessary
    currentWordIndex = (currentWordIndex + 1) % words.length;
    setupWord(words[currentWordIndex]); // Setup points for the new word
    resetPositions();
    lastResetTime = currentTime;
  } else {
    background(255, 20); // Semi-transparent background for trail effect
  }

  for (let v of vehicles) {
    let noiseForce = getNoise(v.pos.x, v.pos.y);
    v.applyForce(noiseForce);
    v.update();
    v.show();
  }
}

function setupWord(word) {
  vehicles = []; // Clear the current vehicles
  let bounds = font.textBounds(word, 0, 0, 192);
  let posX = width / 2 - bounds.w / 2;
  let posY = height / 2 + bounds.h / 4;

  let points = font.textToPoints(word, posX, posY, 192, {
    sampleFactor: 0.5
  });

  originalPoints = points;

  for (let pt of points) {
    let vehicle = new Vehicle(pt.x, pt.y);
    vehicles.push(vehicle);
  }
}

function getNoise(x, y) {
  let noiseVal = noise(x * 0.01, y * 0.01);
  let angle = map(noiseVal, 0, 1, 0, TWO_PI);
  let force = p5.Vector.fromAngle(angle);
  force.mult(0.1);
  return force;
}

function resetPositions() {
  for (let i = 0; i < vehicles.length; i++) {
    vehicles[i].pos = createVector(originalPoints[i].x, originalPoints[i].y);
    vehicles[i].vel = p5.Vector.random2D().mult(0); // Reset velocity
  }
}

class Vehicle {
  constructor(x, y) {
    this.pos = createVector(random(width), random(height)); // Start with random positions
    this.target = createVector(x, y);
    this.vel = p5.Vector.random2D();
    this.acc = createVector();
    this.r = 4;
    this.maxspeed = 4;
    this.maxforce = 1;
  }

  applyForce(force) {
    this.acc.add(force);
  }

  update() {
    this.vel.add(this.acc);
    this.vel.limit(this.maxspeed);
    this.pos.add(this.vel);
    this.acc.mult(0);
  }

  show() {
    stroke(0);
    strokeWeight(this.r);
    point(this.pos.x, this.pos.y);
  }
}

Embedded Sketch

Reflection and ideas for future work or improvements

Reflecting on this assignment, I’m considering the introduction of interactivity, allowing viewers to influence the flow and form of the animation in real-time. Additionally, refining the transitions between words to be even smoother would elevate the visual fluidity, creating a seamless blend from one word to the next. These improvements aim to turn the animation into a more immersive one.

Week 4 Reading Reflection

I enjoyed reading Don Normam’s “The Design of Everyday Things.” After reading Crawford’s definition’s of interactivity in “The Art of Interactive Design,” it was necessary to understand the fundamentals of interactions themselves, which go beyond the definition and importance. To me, the fundamental principles of interactivity were very well put in Don Norman’s book. I liked the text, probably because the examples were very grounded in our day-to-day interactions.  The way Norman advocated for human-centric design seemed very similar to Myron Krueger’s philosophy that technology should serve human needs and desires (What Should You Wear to an Artificial Reality?). This also prompted the benchmark of calling a design good or bad.  The reading really helped me to understand the importance of intuitive design. I always wondered why all the cars, phones, and computers looked similar.  Norman’s examples of the learning curves of the users and intuitive design pretty much answered that question. However, I felt the examples lacked the presence of age groups. It would have been interesting to explore how intuitive design changes through time and generations. Norman’s illustration of the distinction between affordances and signifiers was very thought-provoking for me, as I had never explored the dimension of interactivity this much.

 

Reading Reflection | Week 4 | Aadil

In the design of everyday things, Don Norman discussed many interesting ideas and perspectives of design thinking. His example of doors – was quite thought provoking. Last week’s reading had this idea of ‘form and function’ in interactivity design. Thinking about something as simple as doors made this concept clear to me – both form and function are essential to develop a good design.

Norman’s idea of Human Centered Design that focuses on making machines discoverable and understandable was quite interesting too. He stated several things that I relate with – particularly about us taking shortcuts when a product is too complex. Thinking about this, I thought that maybe there is an ideal level of complexity for all products and the designer has to think about how much complexity is enough? In general, I would say that there should be as little complexity as possible and as much complexity as necessary.

The ideas of affordance, signifiers, constraints, mapping, feedback were all new to me. I had heard about affordance previously but never though t of it as a “relationship” between people and objects. This reading compelled me to think about some designs I encounter in everyday life (such as a lift or a chair). I hope to integrate these principles into any future design projects that I undertake , so that I can find solutions that are user-friendly and intuitive.

Assignment 4: Generative Text Output

Concept

The concept I aimed to replicate the captivating streams of 1s and 0s that are frequently shown in movies or GIFs, which represent the fundamentals of digital computing and communication. I wanted to achieve something similar to seeing a complex dance of binary digits, therefore I represented these streams with random characters.

Inspiration 

Sketch


Code

function draw() {
  background(0); 
  
  // Display and animate characters
  for (let x = 0; x < cols; x++) {
    for (let y = 0; y < rows; y++) {
      let yOffset = (millis() * speed + x * 50 + y * 50) % (height + 200) - 100;
      
      if (grid[x][y].bright) {
        // Set bright neon blue color for highlighted characters
        fill(0, 255, 255); 
      } else {
        // Set light neon blue color for other characters
        fill(0, 150, 255); 
      }
      
      text(grid[x][y].char, x * charSize, y * charSize + yOffset);
    }
  }
}

In the draw() method, I have a nested loop that iterates over each cell of the 2D grid array. The location of the characters are calculated and its appearance based on its coordinates and a time-based offset (yOffset). This offset is computed given the cell coordinates, a predetermined speed value, and the current value of millis(), which indicates the milliseconds since the sketch began operating.

if (grid[x][y].bright) {
        // Set bright neon blue color for highlighted characters
        fill(0, 255, 255); 
      } else {
        // Set light neon blue color for other characters
        fill(0, 150, 255); 
      }

Each character’s color is chosen according to the entry that corresponds to it in the grid array. fill() method applies a bright neon blue color to a cell if its bright attribute is set to true. Otherwise, ordinary characters are shown in a lighter neon blue hue.

Full Code

// 2D array to display the characters
let grid;
// the font size of the characters
let charSize = 20;
// columns and rows for the 2D array (grid)
let cols, rows;
// speed of the characters falling
let speed = 0.2;

function setup() {
  print(windowWidth,windowHeight)
  createCanvas(windowWidth, windowHeight);
  
  //creating the 2D array
  cols = floor(width / charSize);
  rows = floor(height / charSize);
  
  grid = create2DArray(cols, rows);
  //  initializing the characters font size 
  textSize(charSize);
  
  // Initialize grid with random characters
  for (let x = 0; x < cols; x++) {
    for (let y = 0; y < rows; y++) {
      grid[x][y] = {
        // calling the characters randomly      
        char: randomChar(),
        // Randomly determine if the character should be brighter or not
        bright: random(1) > 0.8 
      };
    }
  }
}

function draw() {
  background(0); 
  
  // Display and animate characters
  for (let x = 0; x < cols; x++) {
    for (let y = 0; y < rows; y++) {
      let yOffset = (millis() * speed + x * 50 + y * 50) % (height + 200) - 100;
      
      if (grid[x][y].bright) {
        // Set bright neon blue color for highlighted characters
        fill(0, 255, 255); 
      } else {
        // Set light neon blue color for other characters
        fill(0, 150, 255); 
      }
      
      text(grid[x][y].char, x * charSize, y * charSize + yOffset);
    }
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  cols = floor(width / charSize);
  rows = floor(height / charSize);
  
  grid = create2DArray(cols, rows);
  
  // Reinitialize grid with random characters
  for (let x = 0; x < cols; x++) {
    for (let y = 0; y < rows; y++) {
      grid[x][y] = {
        char: randomChar(),
        // Randomly determine if the character should be brighter or not
        bright: random(1) > 0.8 
      };
    }
  }
}

// function to create the 2D array grid
function create2DArray(cols, rows) {
  let arr = new Array(cols);
  for (let i = 0; i < arr.length; i++) {
    arr[i] = new Array(rows);
  }
  return arr;
}

// function to generate a random character
function randomChar() {
  return String.fromCharCode(floor(random(65, 91)));
}

 

Challenges

The significant challenge was how to keep the characters’ animation in a fluid and synced motion along with the shifting grid placements while maintaining readability and visual consistency.

Another challenge was assigning different brightness levels to different characters. This required careful coordination to make sure the highlighted characters shone out without overshadowing other characters or creating visual clutter.

Improvements

There is one improvement that I would like to implement in the future. I would want to improve the animation algorithm’s efficiency that may result in more fluid and flawless visual transitions, particularly when working with bigger grids or faster animation rates.

Another improvement could be the inclusion of user interactivity. The user could disrupt the falling characters using mouse hover and the characters trying to get back into the original stream.

Reading Reflection 4 – Pavly Halim

Reflecting on Chapter One of “The Psychopathology of Everyday Things,” the text really made me think differently about the stuff we use every day. It’s like, we’ve all been there, struggling with something that’s supposed to be simple, like a door that doesn’t make it clear if you should push or pull. The author talks about how these design fails aren’t just annoying, but they actually show a more significant problem: designers sometimes focus more on making things look cool or high-tech instead of making them easy for us to use.

It made me question why design often misses the mark on user-friendliness. Is it because designers are trying too hard to impress other designers instead of thinking about regular people? Reading this has definitely changed how I look at things around me. Now, I catch myself wondering how something could be designed better to avoid confusion or frustration. It’s interesting to think about how much smoother our day could be if more things were designed with the user in mind. This chapter has sparked a lot of questions for me about what else in my daily life could be improved with better design.

Assignment 4 – Pavly Halim

The Concept:
My project is centered around the visualization of stock market data, specifically designed to help users visually track the fluctuations in stock prices over time. The idea was to create an interactive graph that could display a moving window of stock prices, allowing users to see trends and changes as they happen.

A Highlight of the Code:
One snippet of code that stands out to me for its simplicity and effectiveness is the drawGraph function. This function renders the graph on the screen, mapping the stock prices to visual elements that can be easily understood.

function drawGraph(startIndex) {
  const graphWidth = 700;
  const graphHeight = 300;
  const startY = height - graphHeight - 50;
  const endY = startY + graphHeight;
  const startX = (width - graphWidth) / 2;
  const endX = startX + graphWidth;

  //Calculate the subset of prices to display
  let displayedPrices = prices.slice(startIndex, Math.min(startIndex + windowSize, prices.length));
  const minValue = Math.min(...displayedPrices);
  const maxValue = Math.max(...displayedPrices);
  const latestPrice = displayedPrices[displayedPrices.length - 1]; // The last price in the displayed subset

  push();
  stroke(0);
  noFill();
  rect(startX - 10, startY - 10, graphWidth + 20, graphHeight + 20);
  pop();

  push();
  stroke(25, 118, 210);
  strokeWeight(2);
  noFill();
  beginShape();
  for (let i = 0; i < displayedPrices.length; i++) {
    let x = map(i, 0, displayedPrices.length - 1, startX, endX);
    let y = map(displayedPrices[i], minValue, maxValue, endY, startY);
    vertex(x, y);
  }
  endShape();

Embedded Sketch:

Showcasing the dynamic visualization of stock prices over time

 

Code:

let stockData;
let currentIndex = 0;
let windowSize = 15; //Data points to display
let prices = [];

function preload() {
  stockData = loadTable('stock_prices.csv', 'csv', 'header');
}

function setup() {
  createCanvas(800, 400);
  frameRate(1); // Framerate 
  loadPrices(); // Load data
}

function draw() {
  background(255);
  drawGraph(currentIndex);
  currentIndex++; // Move the window forward
  if (currentIndex > prices.length - windowSize) currentIndex = 0; // Loop back if we reach the end
}

function loadPrices() {
  //Extract prices and store it in array
  for (let r = 0; r < stockData.getRowCount(); r++) {
    let price = stockData.getNum(r, 'price');
    prices.push(price);
  }
}

function drawGraph(startIndex) {
  const graphWidth = 700;
  const graphHeight = 300;
  const startY = height - graphHeight - 50;
  const endY = startY + graphHeight;
  const startX = (width - graphWidth) / 2;
  const endX = startX + graphWidth;

  //Calculate the subset of prices to display
  let displayedPrices = prices.slice(startIndex, Math.min(startIndex + windowSize, prices.length));
  const minValue = Math.min(...displayedPrices);
  const maxValue = Math.max(...displayedPrices);
  const latestPrice = displayedPrices[displayedPrices.length - 1]; // The last price in the displayed subset

  push();
  stroke(0);
  noFill();
  rect(startX - 10, startY - 10, graphWidth + 20, graphHeight + 20);
  pop();

  push();
  stroke(25, 118, 210);
  strokeWeight(2);
  noFill();
  beginShape();
  for (let i = 0; i < displayedPrices.length; i++) {
    let x = map(i, 0, displayedPrices.length - 1, startX, endX);
    let y = map(displayedPrices[i], minValue, maxValue, endY, startY);
    vertex(x, y);
  }
  endShape();
  pop();
 push();
  textSize(17);
  fill(0);
  noStroke();
  textAlign(CENTER);
  text(`Current Price: $${latestPrice.toFixed(2)}`, width / 2, startY - 25);
  pop();
}

Reflection and Future Work:
This project has allowed me to refine my skills in processing and presenting data in an informative and aesthetically pleasing way. The feedback loop provided by visually tracking the effectiveness of the code in real time has been incredibly rewarding.

Moving forward, I see several avenues for improvement and expansion. For instance, integrating real-time data feeds to allow the graph to update with live market data would significantly enhance its utility. Additionally, incorporating user input to select different stocks or adjust the window size and time frame could make the tool more versatile and user-friendly.

Week 4: Reading response

Norman’s chapter on the design of everyday things was very relatable to me as I completely understand his frustration of poorly designed daily objects, especially doors that don’t clearly indicate a way of opening them. On the topic of doors, I also think that they should have some sort of design that one can immediately tell how they function. One thing I kept looking forward to in this reading was for Norman to propose a solution to this, by showing us a prototype or just writing down a way in which designers can make doors better. However, he never gives us a clear idea of how he would want a door to be designed like.

On another note, I also understand his struggle with instruction manuals as first of all, humans usually try to mess around with the object rather than actually reading what is written in the manual. Secondly, most instruction manuals are not really that clear. Overall, I agree with a lot of what Norman is telling us in this chapter.

Week 4: Generative Text

For this assignment, I decided to explore generating text in p5.js. I created a CSV file that contains words that are part of a meal such as the topping, side, drink and main. The blackboard generates a new and random “special meal” on every mouse click.

A part that I struggled with is making every meal generate randomly, which I tackled by writing the code below:

Row = split(strings[int(random(strings.length))], ',');

For improvements, I want to add more visuals. In addition, I want the topping to be randomized for every part of the meal rather than having the same topping for the main, side and drink.

Reading Response: The Psychopathology of Everyday Things, #Week4

Reflecting on Norman’s insights from “The Psychopathology of Everyday Things, Chapter:1” I find myself deeply considering the complexity hidden within simple everyday objects like doors, refrigerators, and watches. Norman points out that the designs meant to simplify our lives can sometimes make them more complicated instead. This resonates with me because I’ve often found myself frustrated by appliances and gadgets (like smart watches, bluetooth coffee maker) that have too many features. These features, instead of making the device better, can make it harder to use. Norman’s argument makes me rethink the belief that having more features means better functionality. My own experiences support this idea, as I’ve struggled with devices that seem to prioritize complexity over ease of use.

Furthermore, Norman’s critique makes me wonder about the true purpose of design. Is it more important for a product to look good and have lots of features than to be easy to use? This challenges the current trend in design, where form and complexity often take precedence over simplicity and functionality. Norman seems to favor designs that are straightforward and user-friendly, which makes me question my own ideas about what makes a design “good.” Previously, I might have thought that the more technologically advanced a product is, the better it is. However, Norman’s perspective has made me see the value in designs that balance functionality, simplicity, and elegance. This shift in thinking opens up new questions about how designers can create products that are both advanced and accessible. As technology evolves, finding the right balance will be crucial for creating products that enhance our lives without adding unnecessary complexity.

Yaakulya’s Assignment 4: Loading Data & Displaying text

Concept: I always wanted to understand the graphs and math revolve behind Real Estates, so for my Data Visualization assignment, I created a data visualization using the Boston Housing dataset. (Dataset URL: https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.csv)
This Dataset is available to public.

The resultant visualization displays the relationship between the average number of rooms per dwelling (RM) and the median home value ($1000s) for various suburbs in Boston. Each data point on the graph represents a suburb, with the x-axis indicating the average number of rooms and the y-axis indicating the median home value. Additionally, I implemented an interactive tooltip that provides detailed information about each data point when the user hovers over it, including the suburb name, crime rate, and median home value.

To make my concept work, I found inspiration from these sources and moreover I learnt a lot about attributes and functions used for loading the required data from the datasets precisely:

Highlight of Code: One part of the code that I’m particularly proud of is the implementation of the interactive tooltip, initially I found it very difficult. This feature enhances the user experience by providing additional information about the data points in a user-friendly manner. Here’s the code snippet responsible for displaying the tooltip:

function draw() {
  // Check if mouse is over a data point
  let overPoint = false;
  for (let i = 0; i < data.getRowCount(); i++) {
    let rm = map(data.getNum(i, 5), 3, 9, 100, width - 100);
    let medv = map(data.getNum(i, 13), 5, 50, height - 100, 100);
    let d = dist(mouseX, mouseY, rm, medv);
    if (d < 5) {
      overPoint = true;
      let suburb = data.getString(i, 0);
      let crimeRate = data.getNum(i, 1);
      let medianValue = data.getNum(i, 13);
      tooltip.html(`Suburb: ${suburb}<br>Crime Rate: ${crimeRate.toFixed(2)}<br>Median Home Value: $${medianValue.toFixed(2)}k`);
      tooltip.position(rm + 20, medv - 20);
      tooltip.show();
      break;
    }
  }
  if (!overPoint) {
    tooltip.hide();
  }
}

Explanation – How this Interactive feature works?: In the draw() function, which updates the canvas continuously, the code iterates through each data point in the dataset. It calculates the distance between the mouse cursor position and the position of each data point using the dist() function. If this distance is within a threshold of 5 pixels, indicating that the mouse is hovering over a data point, the code retrieves information such as suburb name, crime rate, and median home value using functions like getString() and getNum(). These functions extract specific values from the dataset based on row and column indices. The tooltip’s content is then updated to display this information, and its position is adjusted slightly from the data point using the position() function. Finally, the tooltip is shown to the user with show(). If the mouse is not over any data point, the tooltip is hidden using hide(). This process allows for interactive display of detailed information when the user hovers over data points on the visualization.

Complete Embedded Sketch:

let data;
let tooltip;

function preload() {
  // Load the Boston Housing dataset
  data = loadTable('https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.csv', 'csv', 'header');
}

function setup() {
  createCanvas(800, 600);
  background(240);

  // Plot the data points with black color
  for (let i = 0; i < data.getRowCount(); i++) {
    let rm = map(data.getNum(i, 5), 3, 9, 100, width - 100);
    let medv = map(data.getNum(i, 13), 5, 50, height - 100, 100);

    fill('rgb(197,134,88)'); // Change color to black
    noStroke();
    ellipse(rm, medv, 10, 10);
  }

  // Add x and y axes
  drawAxes();

  // Add interactive tooltip
  tooltip = createDiv('');
  tooltip.style('position', 'absolute');
  tooltip.style('background-color', 'rgba(255, 255, 255, 0.8)');
  tooltip.style('padding', '8px');
  tooltip.style('border-radius', '4px');
  tooltip.hide();


}

function drawAxes() {
  // Draw x-axis
  stroke(0);
  line(100, height - 100, width - 100, height - 100);
  for (let i = 4; i <= 9; i++) {
    let x = map(i, 3, 9, 100, width - 100);
    textAlign(CENTER, CENTER);
    textSize(12);
    fill(0);
    text(i, x, height - 80);
  }
  textAlign(CENTER, CENTER);
  textSize(16);
  fill(0);
  text('Average Number of Rooms per Dwelling (RM)', width / 2, height - 40);

  // Draw y-axis
  stroke(0);
  line(100, 100, 100, height - 100);
  for (let i = 5; i <= 50; i += 5) {
    let y = map(i, 5, 50, height - 100, 100);
    textAlign(RIGHT, CENTER);
    textSize(12);
    fill(0);
    text(i, 90, y);
  }
  textAlign(CENTER, CENTER);
  textSize(19);
  fill(0)
  text('Boston Housing Dataset Visualization (Year:1970-2006)', 400, 40);
  rotate(-HALF_PI);
  textSize(16);
  text('Median Home Value ($1000s)', -height / 2, 50);
}

function draw() {
  // Check if mouse is over a data point
  let overPoint = false;
  for (let i = 0; i < data.getRowCount(); i++) {
    let rm = map(data.getNum(i, 5), 3, 9, 100, width - 100);
    let medv = map(data.getNum(i, 13), 5, 50, height - 100, 100);
    let d = dist(mouseX, mouseY, rm, medv);
    if (d < 5) {
      overPoint = true;
      let suburb = data.getString(i, 0);
      let crimeRate = data.getNum(i, 1);
      let medianValue = data.getNum(i, 13);
      tooltip.html(`Suburb: ${suburb}<br>Crime Rate: ${crimeRate.toFixed(2)}<br>Median Home Value: $${medianValue.toFixed(2)}k`);
      tooltip.position(rm + 20, medv - 20);
      tooltip.show();
      break;
    }
  }
  if (!overPoint) {
    tooltip.hide();
  }
}

Generated Output:

Reflection and Ideas for Future Work: Overall, I’m satisfied with how the assignment turned out. The visualization effectively communicates the relationship between the number of rooms in a dwelling and the median home value in different suburbs of Boston. The interactive tooltip adds an extra layer of interactivity and information, enhancing the user experience.

For future work or improvements, I could consider adding more features to the visualization, such as:

1. Implementing color encoding for an additional variable, such as crime rate or pupil-teacher ratio, to provide more insights into the data.

2. Enhancing the design of the visualization by adding axis labels, a title, and a legend to improve clarity and understanding.

3. Exploring different types of visualizations, such as scatter plots or line charts, to represent the data in alternative ways and uncover additional patterns or trends in addition working with latest datasets.

Overall, this assignment has been a valuable learning experience, and I look forward to applying these skills to future assignments ad projects.

Related P5 References:
1. https://p5js.org/reference/#/p5/map
2. https://p5js.org/reference/#/p5.Element/style
3. https://p5js.org/reference/#/p5.Table/getNum
4. https://p5js.org/reference/#/p5.Element/hide
5. https://p5js.org/reference/#/p5.Element/position
6. https://p5js.org/reference/#/p5.Table/getString