Published
Edited
Jan 6, 2022
22 stars
Insert cell
Insert cell
Insert cell
canvas = {
replay;
const size = 1152;
const inf = size * 10; // for some definition of infinity
const random = d3.randomLcg(42);

const canvas = document.createElement("canvas");
const scale = window.devicePixelRatio;
canvas.width = size * scale;
canvas.height = size * scale;
canvas.style = `max-width: 100%; width: ${size}px; height: auto;`;
const context = canvas.getContext("2d");
context.scale(scale, scale);

let square = [
[
[-size / 2, -size / 2],
[size / 2, -size / 2],
[size / 2, size / 2],
[-size / 2, size / 2],
[-size / 2, -size / 2]
]
];

let polygons = [square];
context.beginPath();
pathPolygons(context, polygons);
context.stroke();

for (let i = 0; i < 100; ++i) {

// Compute a line going through the center of the square at a random angle.
const a = random() * Math.PI;
const c = Math.cos(a);
const s = Math.sin(a);
let x1 = c * size * 1.2;
let y1 = s * size * 1.2;
let x2 = -x1;
let y2 = -y1;

// Offset the line from the center with a vector perpendicular to the line.
const o = (random() - 0.5) * size / 2;
let tx = -s * o;
let ty = c * o;

// Computing an “infinite” bounding region above the slice line.
const p1 = [x1 + tx, y1 + ty];
const p2 = [x2 + tx, y2 + ty];
const ring = [p1, p2];
const clip = [ring];
if (a < Math.PI / 2) ring.push([p2[0], -inf], [inf, -inf], [inf, p1[1]]);
else ring.push([inf, p2[1]], [inf, inf], [p1[0], inf]);
ring.push(p1);

// Slice the polygons in half!
const left = polygon.intersection(polygons, clip);
const right = polygon.difference(polygons, clip);

// Offset the resulting polygons away from the slice line.
// Some of the slices will be thicker (more violent?) than others.
const k = random() * 3 + 1;
offsetPolygons(left, k * s, -k * c);
offsetPolygons(right, -k * s, k * c);

// Merge the polygons back together.
polygons = [...left, ...right];

context.save();
context.clearRect(0, 0, size, size);
context.translate(size / 2, size / 2);
context.scale(0.75, 0.75);

context.beginPath();
pathPolygons(context, polygons);
context.fillStyle = "#eee";
context.fill();
context.stroke();

context.restore();
yield canvas;
}
}
Insert cell
function pathPolygons(context, polygons) {
for (const polygon of polygons) {
pathPolygon(context, polygon);
}
}
Insert cell
function pathPolygon(context, rings) {
for (const [[x1, y1], ...points] of rings) {
context.moveTo(x1, y1);
for (const [xi, yi] of points) {
context.lineTo(xi, yi);
}
}
}
Insert cell
function offsetPolygons(polygons, kx, ky) {
for (const polygon of polygons) {
offsetPolygon(polygon, kx, ky);
}
}
Insert cell
function offsetPolygon(rings, kx, ky) {
for (const points of rings) {
for (const point of points) {
point[0] += kx;
point[1] += ky;
}
}
}
Insert cell
polygon = require("polygon-clipping@0.15")
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