The Beauty of Mistakes: My Julia Set "Fail"
I set out to draw a Julia set, but a complete misunderstanding of the math led me somewhere entirely different. While I’m not even sure what to call the resulting visuals, I’m excited to share my "mistakes" along with the p5.js code.
Don't worry—I'll also show you the (presumably) correct way to render the Julia set later in this post.
The Math: Attempting the Julia Set
"The Julia set consists of values such that an arbitrarily small perturbation can cause drastic changes in the sequence of iterated function values."
Wikipedia: Julia set
To be honest, the formal definition went right over my head. But as a creative coder, I wanted to see it visually. After some digging, I found these recurrence equations:
Xn+1 = Xn * Xn - Yn * Yn + a
Yn+1 = 2 * Xn * Yn + b
"A-ha!" I thought. "So I just calculate (x, y) through iterations and plot each point, right?" (Spoiler: I was wrong.)
A Happy Accident: Shapes I Didn’t Expect
I plotted the points using parameters a = -0.3 and b = -0.623. Here is what happened:
/**
* I misunderstood the Julia set.
* calculate (x, y) using a recurrence equation and then plot it.
*
* @author @deconbatch
* @version 0.1
* @license CC0
* p5.js 1.6.0
* created 2023.05.06
*/
const w = 640;
const h = w;
const a = -0.3;
const b = -0.623;
function setup() {
createCanvas(w, h);
noLoop();
translate(w * 0.8, h * 0.8);
background(240);
noFill();
let xn = 0;
let yn = 0;
for (let i = 0; i < 10000; i++) {
let x = pow(xn, 2) - pow(yn, 2) + a;
let y = 2 * xn * yn + b;
point(x * w, y * h);
xn = x;
yn = y;
}
}
Hmm... this doesn't look like any fractal I've seen before. Is this even a Julia set anymore? Probably not, but I was fascinated by the output.
Embracing the Glitch: From Fractals to Moire
I tried tweaking the parameters, but it still looked nothing like a Julia set. Even famous constants for 'a' and 'b' didn't yield the expected results.
I realized this was happening because I was plotting the trajectory of the point rather than calculating the escape time for each pixel. But you know what? In creative coding, I prioritize the joy of discovery over mathematical accuracy. That's my approach to 'Creative Coding'.
By chance, I connected the points using curveVertex() and discovered these "creepy" yet beautiful moire patterns.
/**
* I misunderstood the Julia set.
* calculate (x, y) using a recurrence equation and then draw the curve with it.
*
* @author @deconbatch
* @version 0.1
* @license CC0
* p5.js 1.6.0
* created 2023.05.06
*/
const w = 640;
const h = w;
const a = -0.3;
const b = -0.6225;
function setup() {
createCanvas(w, h);
noLoop();
translate(w * 0.8, h * 0.8);
background(240);
noFill();
let xn = 0;
let yn = 0;
beginShape();
for (let i = 0; i < 5000; i++) {
let x = pow(xn, 2) - pow(yn, 2) + a;
let y = 2 * xn * yn + b;
if (i % 3 == 0) {
curveVertex(x * w, y * h);
}
xn = x;
yn = y;
}
endShape();
}
The trick here was subsampling the points to draw:
if (i % 3 == 0) { ... }
Back to Reality: The "Correct" Julia Set
The real secret to drawing a Julia set lies in how you use the recurrence equation. Instead of plotting (x, y) as coordinates, we check how many iterations it takes for the value to "escape" (diverge) and use that count to determine the color of each pixel.
Success! This is the iconic look I was originally searching for.
Here is the code to draw some Julia set. I used noise() to change the hue value and alpha value. The saturation and brightness are fixed values. You can make something more with these example codes.
p5.js
/**
* The "Real" Julia Set
*
* @author @deconbatch
* @version 0.1
* @license CC0
* p5.js 1.6.0
* created 2023.05.06
*/
const w = 640;
const h = w;
const calcMax = 100;
const paramA = -0.7015;
const paramB = -0.3845;
const zoom = 1.5;
function setup() {
createCanvas(w, h);
colorMode(HSB, 360, 100, 100, 100);
noLoop();
background(0.0, 0.0, 90.0, 100.0);
noStroke();
for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
// calc
let x0 = map(x, 0, w, -zoom, zoom);
let y0 = map(y, 0, h, -zoom, zoom);
let diverCnt = 0;
for (let calcCnt = 0; calcCnt < calcMax; calcCnt++) {
let x1 = pow(x0, 2) - pow(y0, 2) + paramA;
let y1 = 2 * x0 * y0 + paramB;
if (pow(x1, 2) + pow(y1, 2) > 4.0) {
break;
}
x0 = x1;
y0 = y1;
diverCnt++;
}
// draw
let nParam = map(diverCnt, 0, calcMax, 0.0, 1.0);
let hueVal = noise(100.0, nParam * 50.0) * 240.0;
let satVal = 90.0;
let briVal = 30.0;
let alpVal = noise(200.0, nParam * 20.0) * 100.0;
fill(hueVal % 360.0, satVal, briVal, alpVal);
rect(x, y, 1.0, 1.0);
}
}
}
Processing
/**
* The "Real" Julia Set
*
* @author @deconbatch
* @version 0.1
* @license CC0
* Processing 3.5.3
* created 2023.05.06
*/
public void setup() {
size(640, 640);
colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
smooth();
noLoop();
int calcMax = 100;
float paramA = -0.7015;
float paramB = -0.3845;
float zoom = 1.5;
background(0.0, 0.0, 90.0, 100.0);
noStroke();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
// calc
float x0 = map(x, 0, width, -zoom, zoom);
float y0 = map(y, 0, height, -zoom, zoom);
int diverCnt = 0;
for (int calcCnt = 0; calcCnt < calcMax; calcCnt++) {
float x1 = pow(x0, 2) - pow(y0, 2) + paramA;
float y1 = 2 * x0 * y0 + paramB;
if (pow(x1, 2) + pow(y1, 2) > 4.0) {
break;
}
x0 = x1;
y0 = y1;
diverCnt++;
}
// draw
float nParam = map(diverCnt, 0, calcMax, 0.0, 1.0);
float hueVal = noise(100.0, nParam * 50.0) * 240.0;
float satVal = 90.0;
float briVal = 30.0;
float alpVal = noise(200.0, nParam * 20.0) * 100.0;
fill(hueVal % 360.0, satVal, briVal, alpVal);
rect(x, y, 1.0, 1.0);
}
}
}
The Freedom to Fail
You can create something even with flawed code. It might be one of the joys of creative coding.
You are free to attempt drawing the Julia set and end up with completely different results. You are allowed to get lost and wander toward a fascinating path. This time, I was able to experience the joy of such creative coding.





