Published
Edited
Sep 29, 2022
10 stars
Also listed in…
re(plicas + mixes)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// draw bg
ctx.fillStyle = palette.bg;
ctx.fillRect(0, 0, width, height);

drawPts(ctx, pixelData, { fill: palette.fg });
}
Insert cell
pixelData = {
randomize;
let data = shapes;
data = debug ? applyDoF(data, { e: 0.1, m: 0.1 }) : applyDoF(data);
return data;
}
Insert cell
function drawPts(ctx, data, opts = {}) {
const { fill, r } = Object.assign(
{
fill: "#fff",
r: 0.125
},
opts
);
ctx.beginPath();
ctx.globalCompositeOperation = "screen";
ctx.fillStyle = fill;
data.forEach((pt) => {
const [x0, y0] = pt.position;

const x = x0 + width / 2;
const y = y0 + height / 2;
ctx.moveTo(x, y);
const R = random.range(r * 0.25, r * 1.5);
ctx.arc(x, y, R, 0, 2 * Math.PI);
});
ctx.fill();
ctx.globalCompositeOperation = "source-over";
}
Insert cell
function applyDoF(pts, opts = {}) {
const { m, e, pixelsPerPoint, frustum } = Object.assign(
{
m: 0.9, // 0.99
e: 0.6, // 0.99
pixelsPerPoint: 100,
frustum: 2000
},
opts
);

const c = camera.slice();
const f = dst(focusPoint, c);

return pts
.map((v) => {
const d = dst(v, c);
const r = m * Math.pow(Math.abs(f - d), e);

return d3.range(pixelsPerPoint).map(() => {
const w = GLVec3.add([], v, rndSphere(r));
const vec3Projected = projectPointOnPlane(w, projectionPlane);
const distToProjectPlane = dst(w, vec3Projected);

const [x, y, z] = vec3Projected;

if (x < -width / 2 || x > width / 2) return false;
if (y < -height / 2 || y > height / 2) return false;
if (distToProjectPlane > frustum) return false;
if (projectionPlane[2] < z) return false;

return {
position: [x, y]
};
});
})
.flat()
.filter(Boolean);
}
Insert cell
function applyRotations(points, opts = {}) {
const { rotateX, rotateY } = Object.assign(
{
rotateX: 1 / 18,
rotateY: 1 / 12
},
opts
);
let pts = points.slice();
pts = pts.map((pt) => GLVec3.rotateX([], pt, [0, 0, 0], -rotateX));
pts = pts.map((pt) => GLVec3.rotateY([], pt, [0, 0, 0], -rotateY));
return pts;
}
Insert cell
shapes = {
randomize;

let shapes = packSphere({
maxCount: 150,
minRadius: 0.1,
maxRadius: 0.75
})
.map((s) => {
const typeOfShape = random.weightedSet(typeOfShapes);
const { position, radius } = s;
if (typeOfShape === "cube") return cube(position, radius);
if (typeOfShape === "square") return square(position, radius);
if (typeOfShape === "line") return line(position, radius);
if (typeOfShape === "triangle") return triangle(position, radius);
return false;
})
.filter(Boolean)
.flat();

return shapes;
}
Insert cell
function line(pos, radius) {
const position = GLVec3.scale([], pos, scaling);
const r = radius * scaling;

const theta = random.range(Math.PI);
const phi = random.range(2 * Math.PI);

const dx = r * Math.sin(theta) * Math.cos(phi);
const dy = r * Math.sin(theta) * Math.sin(phi);
const dz = r * Math.cos(theta);

const p1 = [position[0] + dx, position[1] + dy, position[2] + dz];
const p2 = [position[0] - dx, position[1] - dy, position[2] - dz];

return interpolateBetweenPoints(p1, p2);
}
Insert cell
function triangle(pos, radius) {
const h = Math.sqrt(3) / 2;
const dy = Math.sqrt(3) / 6;
const unitTriangle = [
[-0.5, 0, 0],
[0.5, 0, 0],
[0, h, 0]
].map((p) => GLVec3.add([], p, [0, -dy, 0]));

const position = GLVec3.scale([], pos, scaling);
const r = radius * scaling;
const a = (r * 2) / Math.sqrt(3);

const theta = random.range(Math.PI);
const phi = random.range(2 * Math.PI);

const shiftedTriangle = unitTriangle
.map((p) => GLVec3.scale([], p, a / 2))
.map((p) => GLVec3.rotateX([], p, [0, 0, 0], theta))
.map((p) => GLVec3.rotateZ([], p, [0, 0, 0], phi))
.map((p) => GLVec3.add([], p, position));

const [p1, p2, p3] = shiftedTriangle;

return [
interpolateBetweenPoints(p1, p2),
interpolateBetweenPoints(p2, p3),
interpolateBetweenPoints(p3, p1)
].flat();
}
Insert cell
function square(pos, radius) {
const unitSquare = [
[1, 1, 0],
[1, -1, 0],
[-1, -1, 0],
[-1, 1, 0]
];
const position = GLVec3.scale([], pos, scaling);
const r = radius * scaling;
const a = (r * 2) / Math.sqrt(3);

const theta = random.range(Math.PI);
const phi = random.range(2 * Math.PI);

const shiftedSquare = unitSquare
.map((p) => GLVec3.scale([], p, a / 2))
.map((p) => GLVec3.rotateX([], p, [0, 0, 0], theta))
.map((p) => GLVec3.rotateZ([], p, [0, 0, 0], phi))
.map((p) => GLVec3.add([], p, position));

const [p1, p2, p3, p4] = shiftedSquare;

return [
interpolateBetweenPoints(p1, p2),
interpolateBetweenPoints(p2, p3),
interpolateBetweenPoints(p3, p4),
interpolateBetweenPoints(p4, p1)
].flat();
}
Insert cell
function cube(pos, radius) {
const unitCube = [
[0.5, 0.5, 0.5],
[0.5, -0.5, 0.5],
[-0.5, -0.5, 0.5],
[-0.5, 0.5, 0.5],
[0.5, 0.5, -0.5],
[0.5, -0.5, -0.5],
[-0.5, -0.5, -0.5],
[-0.5, 0.5, -0.5]
];
const position = GLVec3.scale([], pos, scaling);
const r = radius * scaling;
const a = (r * 2) / Math.sqrt(3);

const theta = random.range(Math.PI);
const phi = random.range(2 * Math.PI);

const shiftedSquare = unitCube
.map((p) => GLVec3.scale([], p, a / 2))
.map((p) => GLVec3.rotateX([], p, [0, 0, 0], theta))
.map((p) => GLVec3.rotateZ([], p, [0, 0, 0], phi))
.map((p) => GLVec3.add([], p, position));

const [p1, p2, p3, p4, p5, p6, p7, p8] = shiftedSquare;

return [
interpolateBetweenPoints(p1, p2),
interpolateBetweenPoints(p2, p3),
interpolateBetweenPoints(p3, p4),
interpolateBetweenPoints(p4, p1),

interpolateBetweenPoints(p5, p6),
interpolateBetweenPoints(p6, p7),
interpolateBetweenPoints(p7, p8),
interpolateBetweenPoints(p8, p5),

interpolateBetweenPoints(p1, p5),
interpolateBetweenPoints(p2, p6),
interpolateBetweenPoints(p3, p7),
interpolateBetweenPoints(p4, p8)
].flat();
}
Insert cell
typeOfShapes = [
{ value: "cube", weight: 1 },
{ value: "line", weight: 1 },
{ value: "square", weight: 19 },
{ value: "triangle", weight: 19 }
]
Insert cell
focusPoint = [0, 0, 0]
Insert cell
camera = [0, 0, Math.max(width, height) * 2]
Insert cell
Insert cell
scaling = Math.max(width, height) / 2
Insert cell
palette = {
if (colorScheme === "bw") {
return {
bg: `hsl(0,0%,5%)`,
fg: opacify(`hsl(0,0%,95%)`, 0.85)
};
}

return colors;
}
Insert cell
colors = {
randomize;
const bg = getRandomColor(0.4);
let fg = getRandomContrastColor(bg);
fg = opacify(fg, 0.8);

return { bg, fg };
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function rnd() {
return random.value();
}
Insert cell
lerp = math.lerp
Insert cell
dst = distance
Insert cell
function rndSphere(r) {
return random.insideSphere(r);
}
Insert cell
function degToRad(deg) {
return (Math.PI / 180) * deg;
}
Insert cell
function interpolateBetweenPoints(p1, p2) {
const d = distance(p1, p2);

return math
.linspace(d * 2, true)
.map((u) => [
math.lerp(p1[0], p2[0], u),
math.lerp(p1[1], p2[1], u),
math.lerp(p1[2], p2[2], u)
]);
}
Insert cell
function opacify(color, alpha = 1.0) {
const obj = culori.rgb(color);

obj.alpha = alpha;

return culori.formatRgb(obj);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import { getRandomColor, getRandomContrastColor } from "@saneef/random-colors"
Insert cell
import { footer } from "@saneef/notebooks-footer"
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