Public
Edited
Mar 25, 2024
Insert cell
Insert cell
function getK4(k1, k2, k3) {
let a = k1 * k2 + k1 * k3 + k2 * k3;
// remember k is signed so a we get from above might be negative
a = Math.abs(a);
let k4A = k1 + k2 + k3 + 2 * Math.sqrt(a);
let k4B = k1 + k2 + k3 - 2 * Math.sqrt(a);
return [k4A, k4B]
}
Insert cell
Insert cell
class Complex{
static add(a, b){
return new Complex(a.x + b.x, a.y + b.y);
}

static sub(a, b){
return new Complex(a.x - b.x, a.y - b.y);
}

static scale(c, f) {
return new Complex(c.x * f, c.y * f);
}

static mult(a, b) {
let x = a.x * b.x - a.y * b.y;
let y = a.x * b.y + a.y * b.x;
return new Complex(x, y);
}

static eq(a,b){
return a.x === b.x && a.y === b.y;
}

static sqrt(c) {
const m = Math.sqrt(c.x * c.x + c.y * c.y);
const newM = Math.sqrt(m);
const a = Math.atan2(c.y,c.x);
const newA = a / 2;
return new Complex(Math.cos(newA) * newM, Math.sin(newA) * newM);
}
constructor(x, y){
this.x = x; // real component
this.y = y; // imaginary component
}
}
Insert cell
Insert cell
function getK4Z4(k1, z1, k2, z2, k3, z3, k4) {
const k1z1 = Complex.scale(z1, k1), k2z2 = Complex.scale(z2, k2), k3z3 = Complex.scale(z3, k3);
const sum = Complex.add(Complex.add(k1z1, k2z2), k3z3);
const m12 = Complex.mult(k1z1, k2z2), m13 = Complex.mult(k1z1, k3z3), m23 = Complex.mult(k2z2, k3z3);
let root = Complex.add(Complex.add(m12, m13), m23);
root = Complex.sqrt(root);
root = Complex.scale(root, 2);
const k4z4 = [Complex.add(sum, root), Complex.sub(sum, root)];
return k4z4;
}
Insert cell
Insert cell
class Circle {
constructor(x, y, k) {
if (x instanceof Complex){
k = y;
y = x.y;
x = x.x;
}
this.center = new Complex(x, y);
this.k = k;
this.r = Math.abs(1 / this.k);
}
}
Insert cell
Insert cell
function descartes(c1, c2, c3){
const k4 = getK4(c1.k, c2.k, c3.k);
const k4z4 = getK4Z4(c1.k, c1.center, c2.k, c2.center, c3.k, c3.center, k4);
if (k4[0] === k4[1]) {
if (Complex.eq(k4z4[0],k4z4[1])) {
let z4 = Complex.scale(k4z4[0], 1 / k4[0]);
return [new Circle(z4, k4[0])];
} else {
let z4 = [Complex.scale(k4z4[0], 1 / k4[0]), Complex.scale(k4z4[1], 1/ k4[0])];
return [new Circle(z4[0], k4[0]), new Circle(z4[1], k4[0])];
}
} else if (Complex.eq(k4z4[0],k4z4[1])) {
let z4 = [Complex.scale(k4z4[0], 1 / k4[0]), Complex.scale(k4z4[0], 1 / k4[1])];
return [new Circle(z4[0], k4[0]), new Circle(z4[1], k4[1])];
} else {
let z4 = [Complex.scale(k4z4[0], 1/ k4[0]), Complex.scale(k4z4[0], 1 / k4[1]), Complex.scale(k4z4[1], 1 / k4[0]), Complex.scale(k4z4[1], 1 / k4[1])];
return [
new Circle(z4[0], k4[0]),
new Circle(z4[1], k4[1]),
new Circle(z4[2], k4[0]),
new Circle(z4[3], k4[1]),
];
}
}
Insert cell
Insert cell
p5(s => {
s.setup = function(){
s.createCanvas(400,400);
s.noFill();
s.stroke(0);
// the big circle
let x1 = s.width/2, y1 = s.height/2, r1 = s.width/2;
let c1 = new Circle(x1, y1, -(1/r1));
s.circle(c1.center.x, c1.center.y, c1.r * 2);
// randomly place smaller circles inside the big one
let r2 = random(50,150), r3 = r1 - r2;
let a2 = random(Math.PI * 2), a3 = a2 + Math.PI;
let d2 = (r1 - r2), d3 = (r1 - r3);
let c2 = new Circle(x1 + s.cos(a2) * d2, y1 + s.sin(a2) * d2, 1/r2);
let c3 = new Circle(x1 + s.cos(a3) * d3, y1 + s.sin(a3) * d3, 1/r3);
s.circle(c2.center.x, c2.center.y, c2.r * 2);
s.circle(c3.center.x, c3.center.y, c3.r * 2);
//get new circles
let newCircles = descartes(c1, c2, c3);
s.stroke("red");
newCircles.forEach(c => s.circle(c.center.x, c.center.y, c.r * 2))
}
})
Insert cell
Insert cell
p5(s => {
s.setup = function(){
s.createCanvas(400,400);
s.noFill();
s.stroke(0);
// the big circle
let x1 = s.width/2, y1 = s.height/2, r1 = s.width/2;
let c1 = new Circle(x1, y1, -(1/r1));
s.circle(c1.center.x, c1.center.y, c1.r * 2);
// randomly place smaller circles inside the big one
let r2 = random(50,150), r3 = r1 - r2;
let a2 = random(Math.PI * 2), a3 = a2 + Math.PI;
let d2 = (r1 - r2), d3 = (r1 - r3);
let c2 = new Circle(x1 + s.cos(a2) * d2, y1 + s.sin(a2) * d2, 1/r2);
let c3 = new Circle(x1 + s.cos(a3) * d3, y1 + s.sin(a3) * d3, 1/r3);
s.circle(c2.center.x, c2.center.y, c2.r * 2);
s.circle(c3.center.x, c3.center.y, c3.r * 2);
//get new circles
let newCircles = descartes(c1, c2, c3);
s.stroke("red");
newCircles.forEach(c => s.circle(c.center.x, c.center.y, c.r * 2));
let c4A = newCircles[0], c4B = newCircles[1];
s.stroke("blue");
newCircles = descartes(c2,c3,c4A);
newCircles.forEach(c => s.circle(c.center.x, c.center.y, c.r * 2));
newCircles = descartes(c2,c3,c4B);
newCircles.forEach(c => s.circle(c.center.x, c.center.y, c.r * 2));
newCircles = descartes(c1,c3,c4A);
newCircles.forEach(c => s.circle(c.center.x, c.center.y, c.r * 2));
newCircles = descartes(c1,c3,c4B);
newCircles.forEach(c => s.circle(c.center.x, c.center.y, c.r * 2));
newCircles = descartes(c1,c2,c4A);
newCircles.forEach(c => s.circle(c.center.x, c.center.y, c.r * 2));
newCircles = descartes(c1,c2,c4B);
newCircles.forEach(c => s.circle(c.center.x, c.center.y, c.r * 2));
}
})
Insert cell
Insert cell
function validateCircle(c, circles, minR = 2, epsilon = 0.1) {
if (c.r < minR) return false;
for (const cc of circles) {
let dx = c.center.x - cc.center.x, dy = c.center.y - cc.center.y;
if (dx*dx + dy*dy < epsilon * epsilon && Math.abs(cc.r - c.r) < epsilon) return false;
}
return true;
}
Insert cell
function isTangent(c1, c2, e = 0.1){
let dsq = (c1.center.x - c2.center.x) * (c1.center.x - c2.center.x) + (c1.center.y - c2.center.y) * (c1.center.y - c2.center.y);
let d = Math.sqrt(dsq);
let s1 = c1.r + c2.r
let s2 = Math.abs(c2.r - c1.r)
return Math.abs(d - s1) < e || Math.abs(d - s2) < e;
}
Insert cell
placeCircles = function (bigCircle, ratio, angle) {
if (!ratio) ratio = random(0.2, 0.8);
if (!angle) angle = random(Math.PI * 2);
let c1 = bigCircle, r1 = c1.r, x1 = c1.center.x, y1 = c1.center.y;
let r2 = r1 * ratio, r3 = r1 - r2;
let a2 = angle, a3 = a2 + Math.PI;
let d2 = (r1 - r2), d3 = (r1 - r3);
let c2 = new Circle(x1 + Math.cos(a2) * d2, y1 + Math.sin(a2) * d2, 1/r2);
let c3 = new Circle(x1 + Math.cos(a3) * d3, y1 + Math.sin(a3) * d3, 1/r3);
return [c2, c3];
}
Insert cell
function random(a, b) {
if (!b){
b = a;
a = 0;
}
return a + Math.random() * (b - a);
}
Insert cell
Insert cell
p5(s => {
let circles = [];
let queue = [];
s.setup = function(){
s.createCanvas(400,400);
s.noFill();
s.stroke(0);
// the big circle
let x1 = s.width/2, y1 = s.height/2, r1 = s.width/2;
circles.push(new Circle(x1, y1, -(1/r1)));
// randomly place smaller circles inside the big one
circles.push(...placeCircles(circles[0]));
circles.forEach(c => s.circle(c.center.x, c.center.y, c.r * 2));
queue.push([circles[0], circles[1], circles[2]]);
//get new circles
generateNew();
}

function generateNew () {
let newQueue = [];
for (const triplet of queue) {
let newCircles = descartes(...triplet);
newCircles.forEach(c => {
if (validateCircle(c,circles) && isTangent(c,triplet[0]) && isTangent(c,triplet[1]) && isTangent(c,triplet[2])) {
circles.push(c);
s.circle(c.center.x, c.center.y, c.r * 2)
newQueue.push([c, triplet[0], triplet[1]]);
newQueue.push([c, triplet[2], triplet[1]]);
newQueue.push([c, triplet[0], triplet[2]]);
}
})
}
queue = newQueue;
}

s.mouseClicked = function () {
if (s.mouseX > 0 && s.mouseX < s.width && s.mouseY > 0 && s.mouseY < s.height) {
generateNew();
}
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more