Data & Text

THE SKETCH 

This week’s project utilizes both data files and text to create a sketch that generates thousands of possible Chinese adoptee names. The link to the sketch on the web editor can be found here.

INSPIRATION & DESIGN 

What I love so much about IMA is its power to tell stories, particularly stories of marginalized identities. Being a Chinese adoptee, I had always found that there was enough material about Chinese adoption that was widely available, be it articles, research, books,  films, art, etc. Hence, my goal with my artwork is to always uplift Chinese adoptee voices and shed light on aspects of the industry that have so long been kept secret. For this project, I wanted to highlight a part of the adoptee experience that can be a very emotional, and in some cases controversial, topic: our names.

I discern now that the sketch does not represent any real data on adoptee names; rather, I have created imaginary data on imaginary orphans, including name and gender. The sketch is made to look as if it were an official government document, but the fact that none of the names are real sheds light on the fact that what is available inside and outside of China regarding the adoption industry is extremely limited. One may be able to find charts, graphs, and infographics that include some statistics about Chinese adoption, but it is extremely difficult to find raw data. The reasons behind this are quite complex and beyond the scope of this paper but are important to note.

Not only are there limited resources on adoptee names, but the system for naming real orphans is highly superficial and formulaic. As explained in more detail in the CODING section, the program works by generating 150 names based on randomly selecting characters from various data files. The seemingly arbitrary way of generating names is representative of the sad truth that orphans are not named out of emotional or personal reasons, but out of practicality and convenience.

SOME BACKGROUND ON CHINESE NAMES

Since the 1970s and into the early 2010s, orphanages all over china have had a uniform way of naming children. The first part was the surname, which in Chinese, is the first character of a 3 character name. The surname was if not always the first character of the city, district, or province they were found in. For example, I was born in Maoming (茂) city in Guangdong province, and my name is 茂欢贵. My adoptive brother was born in Wanzhou (万), Sichuan Province, and his surname in chinese is also 万. Since there are only so many cities in China in comparison to the thousand upon thousand different surnames in China, the surname for adoptees is the most generic, and it is often easy to tell who was an orphan once just based on surname.

The second part is the “first” name, which really consists of 2 characters. The first character of the two is only slightly less generic than the surname, often being shared by a handful of babies born in the same year, found in the same part of the city, etc. These characters are often commonly used characters such as 福 (fu), which means good luck, or 喜(xi), which means happy. In my case, all the girls at the orphanage born in 2002 share the second character of their name 欢 (huan).  The second character of the first name is usually the most unique of the three and has the most possibility for variance. Unlike normal Chinese names though, in which the final character is supposed to be representative of the child’s personality, the way they are selected for orphans is quite different. Sometimes orphanages might chose one common or “lucky” character such as 花(hua, flower) and utilize all characters with the same phonetic pronunciation hua. Sometimes they may want all babies of the same year to have charcters with similar meanings, and limit possible names with that. In other cases it may be randomly selected from a dictionary.

CODING

The “data” that was used to generate the names comes from 3 files. The first file is simply a .txt that lists all the cities in the province of Guangdong. I chose 1 province to condense the amount of data and to emphasize the commonality among adoptee names. From this file is where I created an array to hold all possible surnames, as shown below :

//DATA FOR FIRST CHARACTER (least personalized)
//get the first character of all cities in Guangdong province 
for (let i = 0; i < province_outfile.length; i++) { 
  first_char.push(province_outfile[i][0]); 
}

The second and third characters are pulled from a project I found on Github, which has data from the most common Chinese names from 1950 to 2000. The file top50char.year.csv contains the most popular characters used in personal names sorted by year and gender. I sort through each line of the file until reaching the desired indexes and push the values  into separate arrays for male and female middle names, named middle_char_m and middle_char_f respectively.

//DATA FOR MIDDLE CHARACTER (second-most personalized)
//get the top characters for male first names from 1950-2000
for (let i = 1; i < top_50_outfile.length; i++){     
  //read each row individually 
  current_row = split(top_50_outfile[i], ",");
  
  //search through each row and only push the elements which correspond to names into the middle_char_m array 
  for (el = 0; el < current_row.length; el++) {
    //indexes for male character
    if ((el >= 13) && (el <=18)){ 
      middle_char_m.push(current_row[el]); 
    }
    //indexes for female character 
    if ((el >= 25) && (el <=30)) { 
      middle_char_f.push(current_row[el]); 
    }
  }
}

Note that again, the data from this file contains the top names from Chinese citizens, not orphans, but because this dataset was on the smaller end (only about 300 characters total), I decided to use this to generate the second character.

The final characters were pulled from a file called givenname.csv, which contains popular names that correspond to all phonetic possibilities for a character. This file was not sorted by gender however, but by the number of times it was used in a male and female. By iterating over each line in the file and comparing the values, names are sorted into arrays last_char_f for female names, and last_char_m for male names.

//DATA FOR LAST CHARACTER (most personalized)
for (let i = 0; i < givenname_outfile.length; i++) { 
  //read each row 
  current_row = split(givenname_outfile[i], ","); 
  //determine whether it is male or female name based on     number of occurence 
  if (current_row[3] > current_row[4]) { 
    last_char_m.push(current_row[0]); //push to male
  }
  
  else { 
    last_char_f.push(current_row[0]); //push to female 
  }
}

All of the above arrays were created in the setup() function, while each name is generated in the draw() function.

Inside draw(), each unique name is generated using a two step process. The first step is to determine the gender of the name, which is decided by a function called male_or_female(), shown below:

function male_or_female() { 
  let num = random(); 
  if (num <=0.6) { 
    return 'f'; 
  }
  else { 
    return 'm'; 
  }
}

The condition if (num <=0.6) is used so that names will be female 60% of the time. This is based on the logic that 60% of orphans in China are baby girls.

The second step is to randomly pick surnames and personal names from the arrays built in setup. This is done using a simple for loop:

for (let i = 0; i < 155; i++ ){ 
  //decide gender of the name to be generated 
  gender = male_or_female();
  //randomly get a surname
  surname = first_char[int(random(first_char.length))];

  //if name is female, pull characters from f arrays
  if (gender == 'f') {
    middle = middle_char_f[int(random(middle_char_f.length))]; 
    last = last_char_f[int(random(last_char_f.length))]; 
    personal_name = middle+last; 
    gender= '(女)';  
  } 

  //otherwise pull characters from m arrays 
  else { 
    middle = middle_char_m[int(random(middle_char_m.length))]; 
    last = last_char_m[int(random(last_char_m.length))]; 
    personal_name = middle+last; 
    gender = '(男)'; 
  }
  let full_name = surname+personal_name
  results.push([full_name, gender]);

In the last line, we push the full name and gender into a results array, which is what is used to display the names on the canvas. Hence, each time the program is run, a new set of names is created.

REFLECTIONS

While the code of this project is not particularly challenging, it was challenging finding data to work with. Once I found material to work with though, I thoroughly enjoyed every step of this project. Reading through the names the program generated was a very self-reflective experience for me, it made me think about how my name simultaneously holds so much and so little meaning, and how any one of the names generated could be the name of a real-life adoptee somewhere in the world. While I understand that most people know little to nothing about Chinese adoption, let alone the naming system, my hopes are that this piece can open the conversation to all who are interested in thinking critically about adoption.

 

REFERENCES

https://github.com/psychbruce/ChineseNames

 

oop & arrays

THE SKETCH 

The completed sketch that utilizes both arrays and object oriented programming is shown below. The link to the sketch on the p5.js web editor can be found here.

A screen capture that I particularly like is also shown below:

 

INSPIRATION & DESIGN: 

The inspiration for this piece came to me when I came across a time lapse video showcasing the growth of different crystals. I was surprised at how how beautiful and calming the video was; one would think that there isn’t anything too special about the process of crystallization, but when looked at closely, it is revealed how mesmerizing and almost fantastical it is, and you can’t help but admire how amazing nature is. I also think that crystal growth works quite well for being translated to something that is generative: crystals tend to start off small and exponentially grow, but even after they are fully “bloomed”, the process still continues so long the environment remains unchanged.

For this project, I wanted to create something that mimicked the process of water turning into ice or snowflakes forming. I found this sketch to be very challenging in terms of making the crystallization to look as biologically accurate as possible, as well just generally making the sketch look aesthetically pleasing. However, I was really interested and committed to the sketch because using code to mimic processes found in nature, such as the golden ratio, trees, and Fibonacci numbers, has always sparked my curiosity.

 

CODING SPECIFICS

The sketch is built using a user defined Crystal class, and instances of this class are put into a single array (in the setup() function), in which methods of the class are applied on each object in the array (in the draw() function).

In setup(): 

function setup() {
  createCanvas(700, 500); 
  
 //initializing an array of little crystals 
 for (let i = 0; i < 300; i++) {
    start_x = random(-100, 100);   //give each line random points
    start_y = random(-100, 100);
    end_x = random(-50, 50); 
    end_y = random(-50, 50);
    max_opacity = random(20, 100);  //set each line to have a maximum opacity to make it look more natural  
    cluster.push(new Crystal(start_x,start_y, end_x, end_y, max_opacity));
  }

}

In draw(): 

function draw() {
  translate(width/2, height/2);
  background(116, 150, 183);
  //background(0); 

  //iterate over each object in the array and apply methods to it
  for (let i = 0; i < cluster.length; i++) {
    cluster[i].move();
    cluster[i].display();
  }
  
}

Each individual crystal is actually just a single line, with semi-randomized starting and ending (x,y) positions, a randomized opacity, a jitter variable, and a direction variable (this.direction). These variables define where each line starts and ends, how opaque the stroke color is, and how much each line moves, and in what direction the line lengthens over time. It also has a a time keeper value (this.age) and a boolean value that defines when to draw more lines (this.has_child). These variables are what creates the “growth” effect are described in more detail later on in this post.

Crystal class constructor:

constructor(star_x, start_y, end_x, end_y, max_opacity) { 
  this.start_x = start_x;  //start x pos
  this.start_y = start_y; //start y pos
  this.end_x = end_x; //end x pos
  this.end_y = end_y; //end y pos 
  
  this.max_opacity = max_opacity; 
  this.jitter=0.2;  //variable that causes it to move slightly
  this.opacity = 0; 
  
  //variable that helps lengthen the lines 
  this.direction = atan2(this.end_y-this.start_y, this.end_x-this.start_x);
  this.age = frameCount; //time keeper 
  this.has_child = false; //boole value 
}

In the beginning of the draw() function, we iterate over the array of objects and call methods move() and display(), which do exactly as they sound: they move each line based on the jitter value and display them by increasing the opacity from 0 to the max opacity value each frame.

However, I created an additional method called grow_children() that is only called inside the Crystal class, which I think is the most interesting part of the code. This method essentially keeps initializing and adding new objects into the cluster array, where the move(), display(), and grow_children() functions are then applied to these new objects, and so on. Here is how it works:

Inside move() method, we create an if statement that evaluates to true only when the object that was just created has not drawn any children and when 20 frames have passed since the object was initialized (defined by this.age). If both these conditions are met, we call grow_children():

if(!this.hasChild && frameCount-this.age > 20){
  this.grow_children(); 
}

Then, inside grow_children(), we redefine the parameters start_x, start_y, end_x, and end_y so that the growth of the lines will spread outwards towards the edge of the canvas. Using these new parameters, we then push a new Crystal object into the cluster array:

grow_children(){ 
  //adjusting the x and y positions of the child lines
  start_x = this.end_x; 
  start_y = this.end_y;
  end_x = random(start_x-100, start_x+100); 
  end_y = random(start_y-100, start_y+100);
 
  max_opacity = random(20, 100); 
    
    //initiate new objects and set this.hasChild to True
    cluster.push(new Crystal(start_x,start_y, end_x, end_y, max_opacity));
    this.hasChild = true;
}

Finally, this.hasChild is set to true, and the process repeats.

REFLECTIONS

As I was making this sketch, I realize that what I was envisioning in my head was a little ambitious given the time frame. I became very wrapped up in trying to make it look realistic as possible, and I still don’t believe I have accomplished that. I would love to continue working on this project, and possibly make different variations of it too. Some improvements that I would like to work on are the shapes of the crystals. I chose to use lines for simplicity, but I would like to experiment with long triangles, skinny rectangles, or even my own custom shape. Another issue I want to tackle is how there is only 1 array of objects. Initially I envisioned having several clusters that would appear on different parts of the screen at different times, which would more accurately mirror how water crystallizes. Unfortunately, making one “cluster” was already very complex, and in the end I decided to just focus on one.

Loops

THE SKETCH 

For this project, I made two similar sketches: one that is dynamic, and one that is a still image.

I. The dynamic sketch:

The link to my code in the online editor for version 1 can be found  here. 

 

II. The still sketch:

The link to my code for the second version can be found here.

INSPIRATION & DESIGN: 

The idea for this project came to me rather slowly. Usually with any new artwork I have a very clear idea about what I want to create and can visualize it on paper before I start coding. However, I had a lot of difficulty because I personally don’t love artwork that heavily relies on randomness, and I don’t love overly abstract/geometric designs (conveniently what loops are good for!).  After hours of trying to come up with a uniform idea, I decided to just start coding. I knew that I wanted to challenge myself by working with noise(), and I enjoy working with sin() and cos() functions. I used these two things as my starting point.

In the end, I ended up creating a sort of wavy sun pattern that stretches and contracts with the help of the noise() function. I later wanted to have several of these patterns on the same canvas, but this was too much for my computer to handle and resulted in very slow and glitchy animation (likely due to the nested for loop and calling noise() with each iteration). Consequently, I created a second version of a sketch that does not animate and simply consists of 5 sun patterns randomly placed on the canvas. While I do wish this one also animated, I think it still looks beautiful.

 

CODING SPECIFICS

Each sun pattern is composed of of several sine waves that are rotated about an invisible center point. In the first version, that point is the center of the canvas; in the second version, 5 points were chosen at random.  The first version uses a simple for loop inside the draw() block, while I create a separate function called spirals() in the second version that is called 5 times within a for loop in draw(). To make the sketch s Regardless, the integrity of the algorithm for drawing these shapes is the same in both. A snippet of the code for function_squiggles() is shown below:

for (let j = 0; j < 16; j++) { 
beginShape();
  rotate(PI/8); 
  for (let i = -800; i < 800; i++) { 
    y= noise(xoff) * amp * sin(i/(period)); 
    vertex(i, y);
    
    xoff+=0.00001; 
  }
endShape();

 

The waves are drawn using a custom shape, which basically plots each point (vertex) on the sin wave and connects them together to create a uniform curve.  Each vertex has an x-coordinate that increments by 1 with each iteration. An upper bound  800 is used to ensure that even shapes whose center point is at the edge of the canvas can extend all the way to the edge. The y coordinate is dependent on the sin() function, who’s amplitude changes every frame based on the constant variable amp and the changing variable xoff. This variable begins at 0.0 and increments by 0.0001 with each iteration; as a result, each frame when the noise() function is called it returns a different number.

You may notice that some of the waves appear thicker than others and some lines appear to overlap. This was done by layering a second for loop in addition to the first one shown above . Continuing in the function_squiggles() we have:

for (let j = 0; j < 12; j++) { 
beginShape()
  rotate(PI/12); 
  for (let i = -800; i < 800; i++) { 
    y= noise(xoff) * amp * sin(i/(period));

    vertex(i, y);
    xoff+=0.00001; 

  }
endShape();

Note that the degree to which each wave is rotated is now PI/12 and iterates 12 times, in contrast to the first for loop, which has each wave rotating at PI/8 and iterates 16 times. This slight difference allowed for the shape to have some irregularity, and to look more “organic” versus geometric.

REFLECTIONS: 

While I feel that the actual code of this project was far easier than that of last weeks, I experienced much more difficulty creating the design for this one. Still, I’m not quite sure what I’ve created and how I feel about it; however I’ve stepped outside of my IMA comfort zone in that I created something without any pre-planning, and rather relied on experimenting and improvising. Being a lover of control, these are skills that I’ve always struggled with as an artist, but I am happy that I was able to have a different approach for this project.

 

Self-Portrait

THE SKETCH
Shown below is my self-portrait coded with p5.js. The link to the code online can be found here.

 

INSPIRATION & DESIGN 

For this piece, I was inspired by the human face sketches that we are commonly taught in beginner art classes that are meant to teach us proportions/anatomy/ratios. The sketch is realistic in this sense, but it also purposefully lacks detail when it comes to the eyes, ears, and nose for example. The portrait has been drawn exclusively black and white, and the features consist of several of the same shape layered on top of each other. All of these aspects I chose intentionally to mimic real pencil strokes and the process of re-outlining and re-tracing that happens when sketching on paper. Finally, the sketch is dynamic in that the face shifts slightly and the eyes blink according to the frame count. This was a feature that was later added on because it reminded me of primitive 2D animations.

 

CODING SPECIFICS

The code itself is not particularly complex; I made separate functions to draw each of the features: face shape, eyebrows, eyes, nose, ears, and mouth. A method I found particularly helpful for this project was curveVertex(); I used this to create custom shapes for the chin, ears, and mouth. The eyes, eyebrows, and nose were drawn using simple lines and arcs. The “sketch” effect is created by layering shapes/line of different sizes and positions 6 times using a for loop, as shown in the code snippet below:

for (let i=0; i<=6; i++) {
  x = random(-8, 8); 
  y = random(-8, 8);
  b = random(-8, 8); 
  draw_face(x, y, b);
  draw_ears(x, y, b);

The variation of shapes is determined by variables x, y, and b that are passed through the functions draw_face(), draw_ears(), etc. The x and y variables were used mainly to shift the overall position of a shape, as it was used in the translate() function, and b was used as an additional variable to manipulate proportions such as an arc’s width or a line’s length or intensity of a curve.  For example, in the draw_ears, the x and y variables manipulate the position of  the ears on the canvas.

function draw_ears(x, y) { 
  push(); 
  translate(x, y); 
  scale(1.25);

  //right
  beginShape();
  curveVertex(140, -10);
  curveVertex(140,-10); 
  curveVertex(160,-20);
  curveVertex(170,10);
  curveVertex(155,53);
  curveVertex(140,50);
  curveVertex(140,50);
  endShape();

In the case of the eyebrows, b is used to draw eyebrows of varying “arched-ness”.

function draw_eyebrow(x, y, b) { 
  translate(x, y); 
  push(); 
  rotate(8); 
  arc(-90, 0, 90+b, 12+b, 180, 360); 
  pop(); 
  push()

The lines that bisect the center of the face, the mouth, and eyebrows were created in a separate for loop that executed only 4 times and calculated random x, y and b values from a smaller range (see below). The decision behind making a second for loop was for visual purposes only.

for (let i=0; i<=5; i++) {
  x = random(-3, 3); 
  y = random(-3, 3);
  buffer = random(-6, 6);
  draw_eyebrow(x, y, b); 
  // draw_mouth(x, y);
  draw_mouth(x, y, b); 
  draw_outlines(x, y); 
}

 

Using the two loops and the randomized variables allowed every line, curve, and shape on the face to be drawn differently with each frame, giving the face its movement.  While p5.js’ standard frame rate is 60, I also altered the frame rate to be slower so that the sketch would look like real hand drawn animation. Contrarily, I played around using randomSeed() to create a still image, but in the end I prefer the dynamic one.

My favorite feature of this sketch are the blinking eyes. This was achieved by having two functions draw_eyes() and draw_eyes_closed, which were called upon depending on an if block in the for loop:

if (frameCount%6==0) { 
  draw_eyes_closed(x,y); 
}
else { 
   draw_eyes(x,y);

In short, the if statement inside the for loop executes every 6 frames and calls draw_eyes_closed(); the shape of a closed eye was simply the bottom half of the parallelogram that is the open eye. Otherwise, draw_eyes() is called and thus shows its eyes open.

REFLECTIONS

Overall I am happy with this first sketch; I think I was able to explore my creativity and create something that was uniquely my style. Personally I do not enjoy drawing myself, but I think choosing this style allowed me to not be so focused on details and instead accentuate features on myself that I like, such as the eye shape and mouth. I challenged myself by making the sketch dynamic and including the blinking feature. One thing I wish I could have included is eyelashes and pupils that moved with each blink, and I would love to try to expand the sketch to a full body self-portrait.