Concept:
My mom is a Pathologist, and when I was younger, she was fascinated in showing me cells under a microscope from blogposts on Facebook (her feed was a very nice spectrum of plant care and medical studies). I never took biology in school (I’m more of a physics person), but I always found these images really fascinating. So, I thought I would try and recreate cells (very unrealistically) using Javascript. In middle school, when we were taught about cells, we saw cells as this blobby, almost round, jelly-looking circle with things inside of them. While I only remember a few organelles such as the mitochondria and the nucleus, I thought it would be interesting to include these when making the cells. These say skin cells but it’s the closest I can get to explaining what I have in mind:
Artwork:
(Click the screen to generate new cells!)
Process:
I experimented a lot this time, especially with the motion and shapes of the cells. I had two classes, one for the organelles and one for the cells themselves. This is the class code for the organelles:
class Organelle {
constructor(type, relX, relY, hue) {
this.type = type;
this.relX = relX;
this.relY = relY;
this.hue = hue;
this.angle = random(TWO_PI);
this.spin = random(-0.02, 0.02);
}
display(px, py, radius) {
push();
translate(px, py);
let floatX = this.relX + sin(frameCount * 0.02 + this.angle) * 5;
let floatY = this.relY + cos(frameCount * 0.02 + this.angle) * 5;
translate(floatX, floatY);
rotate(frameCount * this.spin);
noStroke();
if (this.type === "nucleus") {
// Hematoxylin stain: Deep purples/blues
fill(280, 70, 40, 0.9);
ellipse(0, 0, radius * 0.4);
fill(280, 80, 20, 0.9);
ellipse(0, 0, radius * 0.15);
} else if (this.type === "mito") {
// Eosin stain: Deeper pink
fill(340, 60, 70, 0.8);
ellipse(0, 0, 14, 8);
} else if (this.type === "crystal") {
// Hexagon shape in vibrant magenta
fill(320, 80, 80, 0.7);
beginShape();
for (let a = 0; a < TWO_PI; a += PI / 3) {
vertex(cos(a) * 8, sin(a) * 8);
}
endShape(CLOSE);
} else if (this.type === "ring") {
// Ring shape in light purple/pink
noFill();
stroke(300, 40, 60, 0.7);
strokeWeight(2);
circle(0, 0, 16);
}
pop();
}
}
I used H&E staining as a reference for the color scheme to mimic cell scans. For this, there were multiple shapes I used:
- Nucleus (filled in circle at the center of the cell)
- Mitochondria (filled in ellipses)
- Rings and Hexagons for random organelles
These are then randomly generated in each cell.
This is the class code for the cells:
class Cell {
constructor(x, y) {
this.pos = createVector(x, y);
this.baseRadius = random(40, 90);
this.radius = this.baseRadius;
// Constraint to Eosin spectrum (Pink/Magenta)
this.hue = random(325, 350);
this.noiseOffset = random(1000);
this.organelles = [];
this.pulseSpeed = random(0.01, 0.03);
this.rotation = random(TWO_PI);
this.spin = random(-0.002, 0.002);
this.organelles.push(new Organelle("nucleus", 0, 0, this.hue));
let count = floor(random(3, 8));
let types = ["mito", "crystal", "ring"];
for (let i = 0; i < count; i++) {
let angle = random(TWO_PI);
let dist = random(this.baseRadius * 0.2, this.baseRadius * 0.7);
let rx = cos(angle) * dist;
let ry = sin(angle) * dist;
this.organelles.push(new Organelle(random(types), rx, ry, this.hue));
}
}
move() {
this.pos.x += map(noise(this.noiseOffset), 0, 1, -0.8, 0.8);
this.pos.y += map(noise(this.noiseOffset + 100), 0, 1, -0.8, 0.8);
this.noiseOffset += 0.005;
this.rotation += this.spin;
// Breathing pulse
this.radius = this.baseRadius + sin(frameCount * this.pulseSpeed) * this.baseRadius * 0.1;
// Screen Wrap
if (this.pos.x > width + this.radius) this.pos.x = -this.radius;
if (this.pos.x < -this.radius) this.pos.x = width + this.radius;
if (this.pos.y > height + this.radius) this.pos.y = -this.radius;
if (this.pos.y < -this.radius) this.pos.y = height + this.radius;
}
display() {
push();
translate(this.pos.x, this.pos.y);
rotate(this.rotation);
// Soft Eosin cytoplasm layers
for (let layer = 1.2; layer > 0.8; layer -= 0.1) {
noStroke();
fill(this.hue, 30, 95, 0.15);
beginShape();
for (let a = 0; a < TWO_PI; a += 0.1) {
let xoff = map(cos(a), -1, 1, 0, 1.5);
let yoff = map(sin(a), -1, 1, 0, 1.5);
let offset = map(noise(xoff, yoff, frameCount * 0.01), 0, 1, -10, 10);
let r = (this.radius + offset) * layer;
vertex(cos(a) * r, sin(a) * r);
}
endShape(CLOSE);
}
// Main Cell Membrane Outline
noFill();
stroke(this.hue, 50, 70, 0.6);
strokeWeight(2);
beginShape();
for (let a = 0; a < TWO_PI; a += 0.1) {
let xoff = map(cos(a), -1, 1, 0, 1.5);
let yoff = map(sin(a), -1, 1, 0, 1.5);
let offset = map(noise(xoff, yoff, frameCount * 0.01), 0, 1, -10, 10);
let r = this.radius + offset;
vertex(cos(a) * r, sin(a) * r);
}
endShape(CLOSE);
pop();
for (let o of this.organelles) {
o.display(this.pos.x, this.pos.y, this.radius);
}
}
}
For the cells to breathe, move and change shape, I used Perlin Noise and sin() functions. I also included screen wrapping to make sure no cell would wander off screen. This part was difficult for me, so I did get some help from my friends. Making the constantly changing shape and boundary of the cell was also especially difficult for me but I’m glad it turned out the way it did.
Reflection:
I liked working with classes! It makes it much more organized (I think… as of now…) and I’m able to separate parts. It was definitely hard though, because I’m primarily a Python user so this threw me off, but it was a good challenge. I want to try and create more projects using OOP now, and try to link more of my interests to making projects like this. I did ask ask my mom for help with the colors and stains, so hopefully she approves of this! I want to experiment more with cell lifespans and cell shapes, maybe set a certain amount of time before a cell “dies” and disappears or set a condition to generate new cells every once in a while too.
