Public
Edited
Oct 19, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const mps = selectedCircles.map((c) => mkMultiplePolygon(c)),
tmps = selectedTraceCircles.map((c) => mkMultiplePolygon(c));

const linkGraphs = mps.map((mp) =>
Plot.link(mp, {
x: "x1",
y: "y1",
x1: "x1",
y1: "y1",
x2: "x2",
y2: "y2",
stroke: "circle",
strokeWidth: 3,
opacity: 0.5
})
);

const linkGraphsTrace = tmps.map((mp) =>
Plot.link(mp, {
x: "x1",
y: "y1",
x1: "x1",
y1: "y1",
x2: "x2",
y2: "y2",
stroke: "idx"
})
);

return Plot.plot({
aspectRatio: 1.0,
grid: true,
x: { domain: [-20, 20] },
y: { domain: [-20, 20] },
color: { legend: true },
marks: [
Plot.frame(),
Plot.dot(circles.allTrace, {
x: "x",
y: "y",
fill: "circle",
stroke: "white",
r: 5,
tip: true
}),
Plot.dot(totalData, {
x: "x",
y: "y",
fill: "idx",
stroke: "gray"
}),
Plot.dot(selectedCircles, {
x: "x",
y: "y",
fill: "circle",
r: 10,
stroke: "black"
}),
linkGraphs,
linkGraphsTrace
]
});
}
Insert cell
selectedTraceCircles = totalData.filter((d) => d.traceId === selectCircle)
Insert cell
selectedCircles = circles.allTrace.filter((d) => d.i === selectCircle)
Insert cell
mkMultiplePolygon = (circle, n = 100) => {
const { x: cx, y: cy, r } = circle,
theta = d3
.scaleLinear()
.domain([0, n])
.range([0, Math.PI * 2]),
pnts = [];

var x1, y1, x2, y2, t;
for (let i = 0; i < n; ++i) {
t = theta(i);
x1 = cx + r * Math.cos(t);
y1 = cy + r * Math.sin(t);

t = theta((i + 1) % n);
x2 = cx + r * Math.cos(t);
y2 = cy + r * Math.sin(t);

pnts.push({ x1, y1, x2, y2, circle: circle.circle, idx: circle.idx });
}

return pnts;
}
Insert cell
totalData = {
const { trace1, trace2, trace3, num } = circles;

var buffer = [],
ct;

for (let i = 0; i < num; ++i) {
ct = computeTri(trace1[i], trace2[i], trace3[i]);
ct.map((d) => Object.assign(d, { traceId: i }));
buffer = buffer.concat(ct.filter((d) => d.r > 0));
}

return buffer;
}
Insert cell
totalData
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
circles = {
const num = numTriCircles,
trace1 = mkCircleTrace(num),
trace2 = mkCircleTrace(num),
trace3 = mkCircleTrace(num),
allTrace = [];

trace1.map((t) => allTrace.push(Object.assign(t, { circle: "Circle1" })));
trace2.map((t) => allTrace.push(Object.assign(t, { circle: "Circle2" })));
trace3.map((t) => allTrace.push(Object.assign(t, { circle: "Circle3" })));

return { trace1, trace2, trace3, num, allTrace };
}
Insert cell
mkCircleTrace = (num = numTriCircles) => {
const noise = new SimplexNoise(),
x0 = num,
y0 = num * 10,
r0 = num * 100,
scaleX = d3.scaleLinear().domain([-1, 1]).range([-20, 20]),
scaleY = d3.scaleLinear().domain([-1, 1]).range([-20, 20]),
scaleR = d3.scaleLinear().domain([-1, 1]).range([2, 10]),
trace = [];

var t,
x,
y,
r,
k = 100;
for (let i = 0; i < num; ++i) {
t = mkTimestamp();
x = scaleX(noise.noise2D(x0, t + i / k));
y = scaleY(noise.noise2D(y0, t + i / k));
r = scaleR(noise.noise2D(r0, t + i / k));
trace.push({ x, y, r, i });
}
return trace;
}
Insert cell
mkTimestamp = () => {
return (performance.now() / 10000) % 10000000;
}
Insert cell
noise.noise3D(0.2, 0.2, mkTimestamp())
Insert cell
noise = new SimplexNoise()
Insert cell
computeTri = (c1, c2, c3) => {
const buffer = [];

var idx = 1;

[-1, 1].map((a) =>
[-1, 1].map((b) =>
[-1, 1].map((c) => {
const ac = apolloniusCircle(
c1.x,
c1.y,
a * c1.r,
c2.x,
c2.y,
b * c2.r,
c3.x,
c3.y,
c * c3.r
);

Object.assign(ac, { idx });
idx += 1;

buffer.push(ac);
})
)
);

return buffer;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function apolloniusCircle(x1, y1, r1, x2, y2, r2, x3, y3, r3) {
/**
The quadratic equation (1):

0 = (x - x1)² + (y - y1)² - (r ± r1)²
0 = (x - x2)² + (y - y2)² - (r ± r2)²
0 = (x - x3)² + (y - y3)² - (r ± r3)²

Use a negative radius to choose a different circle.
We must rewrite this in standard form Ar² + Br + C = 0 to solve for r.
Per //mathworld.wolfram.com/ApolloniusProblem.html
*/

const a2 = 2 * (x1 - x2),
b2 = 2 * (y1 - y2),
c2 = 2 * (r2 - r1),
d2 = x1 * x1 + y1 * y1 - r1 * r1 - x2 * x2 - y2 * y2 + r2 * r2,
a3 = 2 * (x1 - x3),
b3 = 2 * (y1 - y3),
c3 = 2 * (r3 - r1),
d3 = x1 * x1 + y1 * y1 - r1 * r1 - x3 * x3 - y3 * y3 + r3 * r3;

/**
Giving:

x = (b2 * d3 - b3 * d2 + (b3 * c2 - b2 * c3) * r) / (a3 * b2 - a2 * b3)
y = (a3 * d2 - a2 * d3 + (a2 * c3 - a3 * c2) * r) / (a3 * b2 - a2 * b3)

Expand x - x1, substituting definition of x in terms of r.

x - x1 = (b2 * d3 - b3 * d2 + (b3 * c2 - b2 * c3) * r) / (a3 * b2 - a2 * b3) - x1
= (b2 * d3 - b3 * d2) / (a3 * b2 - a2 * b3) + (b3 * c2 - b2 * c3) / (a3 * b2 - a2 * b3) * r - x1
= bd / ab + bc / ab * r - x1
= xa + xb * r

Where:
*/

const ab = a3 * b2 - a2 * b3,
xa = (b2 * d3 - b3 * d2) / ab - x1,
xb = (b3 * c2 - b2 * c3) / ab;

/**
Likewise expand y - y1, substituting definition of y in terms of r.

y - y1 = (a3 * d2 - a2 * d3 + (a2 * c3 - a3 * c2) * r) / (a3 * b2 - a2 * b3) - y1
= (a3 * d2 - a2 * d3) / (a3 * b2 - a2 * b3) + (a2 * c3 - a3 * c2) / (a3 * b2 - a2 * b3) * r - y1
= ad / ab + ac / ab * r - y1
= ya + yb * r

Where:
*/

const ya = (a3 * d2 - a2 * d3) / ab - y1,
yb = (a2 * c3 - a3 * c2) / ab;

/**
Expand (x - x1)², (y - y1)² and (r - r1)²:

(x - x1)² = xb * xb * r² + 2 * xa * xb * r + xa * xa
(y - y1)² = yb * yb * r² + 2 * ya * yb * r + ya * ya
(r - r1)² = r² - 2 * r1 * r + r1 * r1.

Substitute back into quadratic equation (1):

0 = xb * xb * r² + yb * yb * r² - r²
+ 2 * xa * xb * r + 2 * ya * yb * r + 2 * r1 * r
+ xa * xa + ya * ya - r1 * r1

Rewrite in standard form Ar² + Br + C = 0,
then plug into the quadratic formula to solve for r, x and y.
*/

const A = xb * xb + yb * yb - 1,
B = 2 * (xa * xb + ya * yb + r1),
C = xa * xa + ya * ya - r1 * r1,
r = A ? (-B - Math.sqrt(B * B - 4 * A * C)) / (2 * A) : -C / B;

return { x: xa + xb * r + x1, y: ya + yb * r + y1, r };
}
Insert cell
function clamp(x, lo, hi) {
return x < lo ? lo : x > hi ? hi : x;
}
Insert cell
<style>

.circle {
fill-opacity: .5;
}

.ring {
fill: none;
stroke: #000;
pointer-events: none;
}

.ring-inner {
stroke-width: 5px;
stroke-opacity: .25;
}

</style>
Insert cell
SimplexNoise = require("simplex-noise@2.4")
Insert cell
d3 = require("d3")
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