Published
Edited
Oct 15, 2019
1 fork
41 stars
Insert cell
Insert cell
Insert cell
viewof context = {
restart;
const context = DOM.context2d(dimx, dimy);
context.canvas.style.maxWidth = "100%";
context.canvas.value = context;
return context.canvas;
}
Insert cell
dimx = 954
Insert cell
dimy = dimx
Insert cell
maxnum = 200
Insert cell
grains = 64
Insert cell
color = () => d3.rgb(d3.interpolateWarm(Math.random()))
Insert cell
cracks = {
context;
return [];
}
Insert cell
cgrid = {
context;
const cgrid = new Array(dimx * dimy);
for (let y = 0; y < dimy; ++y) {
for (let x = 0; x < dimx; ++x) {
cgrid[y * dimx + x] = 10001;
}
}
return cgrid;
}
Insert cell
setup = {
for (let k = 0; k < 16; ++k) {
let i = Math.floor(Math.random() * (dimx * dimy - 1));
cgrid[i] = Math.floor(Math.random() * 360);
}
cracks.length = 0;
for (let k = 0; k < 3; ++k) {
Crack.makeCrack();
}
}
Insert cell
draw = {
for (let i = 1600; i >= 0; --i) {
for (const crack of cracks) {
crack.move();
}
yield i;
}
}
Insert cell
class Crack {
constructor() {
this.findStart();
this.sp = new SandPainter();
}
static makeCrack() {
if (cracks.length < maxnum) {
cracks.push(new Crack());
}
}
findStart() {
let px = 0, py = 0;
let found = false;
let timeout = 0;
while (!found || (timeout++ > 1000)) {
px = Math.floor(Math.random() * dimx);
py = Math.floor(Math.random() * dimy);
if (cgrid[py * dimx + px] < 10000) {
found = true;
}
}
if (found) {
let a = cgrid[py * dimx + px];
if (Math.random() < 0.5) {
a -= 90 + Math.floor(Math.random() * 4.1 - 2);
} else {
a += 90 + Math.floor(Math.random() * 4.1 - 2);
}
this.startCrack(px, py, a);
}
}
startCrack(X, Y, T) {
this.x = X;
this.y = Y;
this.t = T;
this.x += 0.61 * Math.cos(this.t * Math.PI / 180);
this.y += 0.61 * Math.sin(this.t * Math.PI / 180);
}
move() {
this.x += 0.42 * Math.cos(this.t * Math.PI / 180);
this.y += 0.42 * Math.sin(this.t * Math.PI / 180);
let z = 0.33;
let cx = Math.floor(this.x + Math.random() * 2 * z - z);
let cy = Math.floor(this.y + Math.random() * 2 * z - z);
this.regionColor();
context.globalAlpha = 0.85;
context.fillStyle = "#000";
context.fillRect(
this.x + Math.random() * 2 * z - z,
this.y + Math.random() * 2 * z - z,
1, 1
);
if ((cx >= 0) && (cx < dimx) && (cy >= 0) && (cy < dimy)) {
if ((cgrid[cy * dimx + cx] > 10000) || (Math.abs(cgrid[cy * dimx + cx] - this.t) < 5)) {
cgrid[cy * dimx + cx] = Math.floor(this.t);
} else if (Math.abs(cgrid[cy * dimx + cx] - this.t) > 2) {
this.findStart();
Crack.makeCrack();
}
} else {
this.findStart();
Crack.makeCrack();
}
}
regionColor() {
let rx = this.x;
let ry = this.y;
let openspace = true;
while (openspace) {
rx += 0.81 * Math.sin(this.t * Math.PI / 180);
ry -= 0.81 * Math.cos(this.t * Math.PI / 180);
let cx = Math.floor(rx);
let cy = Math.floor(ry);
if ((cx >= 0) && (cx < dimx) && (cy >= 0) && (cy < dimy)) {
if (cgrid[cy * dimx + cx] <= 10000) {
openspace = false;
}
} else {
openspace = false;
}
}
this.sp.render(rx, ry, this.x, this.y);
}
}
Insert cell
class SandPainter {
constructor() {
this.c = color();
this.g = Math.random() * (0.1 - 0.01) + 0.01;
}
render(x, y, ox, oy) {
this.g = Math.max(0, Math.min(1, this.g + Math.random() * 0.1 - 0.05));
let w = this.g / (grains - 1);
for (let i = 0; i < grains; ++i) {
context.globalAlpha = 0.1 - i / (grains * 10);
context.fillStyle = this.c;
context.fillRect(
ox + (x - ox) * Math.sin(Math.sin(i * w)),
oy + (y - oy) * Math.sin(Math.sin(i * w)),
1, 1
);
}
}
}
Insert cell
d3 = require("d3-color@1", "d3-scale-chromatic@1")
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