Published
Edited
Apr 1, 2022
Importers
13 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function drawPeano(cb, x, y, w, h, direction, randomize) {
// Our exit condition
if (w <= 2 && h <= 2) {
tiles[direction][w][h](cb, x, y);
return;
}

const { LT, TR, BL, RB } = directions;
const { round } = Math;
const w0 = round(w / 3);
const w1 = round((w * 2) / 3) - w0;
const w2 = w - w0 - w1;
const h0 = round(h / 3);
const h1 = round((h * 2) / 3) - h0;
const h2 = h - h0 - h1;
const x1 = x + w0;
const x2 = x + w0 + w1;
const x3 = x + w - 1;
const y1 = y + h0;
const y2 = y + h0 + h1;
const y3 = y + h - 1;

const splitHorizontal =
randomize && w === h ? Math.random() > 0.5 : h > 2 && h > w;

if (splitHorizontal) {
// divide vertically
let d0, d1, d2;
if (direction === LT) {
cb.line(x3, y1, x3, y1 - 1, true);
cb.line(x, y2, x, y2 - 1, true);
d0 = LT;
d1 = TR;
d2 = LT;
} else if (direction === TR) {
cb.line(x, y1, x, y1 - 1, true);
cb.line(x3, y2, x3, y2 - 1, true);
d0 = TR;
d1 = LT;
d2 = TR;
} else if (direction === BL) {
cb.line(x, y1 - 1, x, y1, true);
cb.line(x3, y2 - 1, x3, y2, true);
d0 = BL;
d1 = RB;
d2 = BL;
} else {
// RB
cb.line(x3, y1 - 1, x3, y1, true);
cb.line(x, y2 - 1, x, y2, true);
d0 = RB;
d1 = BL;
d2 = RB;
}
drawPeano(cb, x, y, w, h0, d0, randomize);
drawPeano(cb, x, y1, w, h1, d1, randomize);
drawPeano(cb, x, y2, w, h2, d2, randomize);
} else {
// if (w > 2) // (is implied)
// divide horizontally
let d0, d1, d2;
if (direction === LT) {
cb.line(x1, y3, x1 - 1, y3, true);
cb.line(x2, y, x2 - 1, y, true);
d0 = LT;
d1 = BL;
d2 = LT;
} else if (direction === TR) {
cb.line(x1 - 1, y, x1, y, true);
cb.line(x2 - 1, y3, x2, y3, true);
d0 = TR;
d1 = RB;
d2 = TR;
} else if (direction === BL) {
cb.line(x1, y, x1 - 1, y, true);
cb.line(x2, y3, x2 - 1, y3, true);
d0 = BL;
d1 = LT;
d2 = BL;
} else {
// RB
cb.line(x1 - 1, y3, x1, y3, true);
cb.line(x2 - 1, y, x2, y, true);
d0 = RB;
d1 = TR;
d2 = RB;
}
drawPeano(cb, x, y, w0, h, d0, randomize);
drawPeano(cb, x1, y, w1, h, d1, randomize);
drawPeano(cb, x2, y, w2, h, d2, randomize);
}
}
Insert cell
Insert cell
Insert cell
Insert cell
function makePeano(w, h, randomize) {
const cb = new CurveBuilder(w, h);
drawPeano(cb, 0, 0, w, h, directions.RB, randomize);
const scale = Math.max(1, Math.floor(Math.log2(width / (w * 2))));
return cb.toCurve().draw(scale, w * h);
}
Insert cell
makePeano(7, 5)
Insert cell
makePeano(17, 11);
Insert cell
makePeano(5 * 7, 2 * 11)
Insert cell
makePeano(2 * 3 ** 4, 2 * 3 ** 2)
Insert cell
Insert cell
Insert cell
Insert cell
makePeano(3 ** 3, 3 ** 3, true)
Insert cell
makePeano(3 ** 3, 3 ** 3, true)
Insert cell
Insert cell
Insert cell
function makeChaoticPeano(w, h) {
const cb = new CurveBuilder(w, h);
drawChaoticPeano(cb, 0, 0, w, h, directions.RB);
const scale = Math.max(1, Math.floor(Math.log2(width / (w * 2))));
return cb.toCurve().draw(scale, 2 ** (6 - scale));
}
Insert cell
makeChaoticPeano(180, 180)
Insert cell
makeChaoticPeano(180, 180)
Insert cell
makeChaoticPeano(180, 180)
Insert cell
Insert cell
Insert cell
import { directions, Curve } from "@jobleonard/skewed-curves"
Insert cell
import { CurveBuilder, hcurveTest } from "@jobleonard/arbitrary-hamiltonian-curves"
Insert cell
examplePath = [[10, 0], [0, 10], [-9, 0], [3, 3], [0, -6], [ -3, 3]]
Insert cell
new CurveBuilder(15, 15)
.drawPolygon(1, 1, examplePath)
.toCurve()
.line(4, 2)
Insert cell
Insert cell
// Basically an Array that assumes it is filled
// with [x, y] pairs and that adds some helper methods
class CurvePolygon extends Array {
constructor(...args) {
super(...args);
}
from(...args) {
const cp = new this();
for (const [x,y] of args) cp.push([x, y]);
return cp;
}
transpose() {
return this.map(([x, y]) => [y, x]);
}
reflectX() {
return this.map(([x, y]) => [-x, y]);
}
reflectY() {
return this.map(([x, y]) => [x, -y]);
}
}
Insert cell
new CurveBuilder(15, 15)
.drawPolygon(1, 1, CurvePolygon.from(examplePath).transpose())
.toCurve()
.line(4, 2)
Insert cell
new CurveBuilder(15, 15)
.drawPolygon(13, 1, CurvePolygon.from(examplePath).reflectX())
.toCurve()
.line(4, 2)
Insert cell
new CurveBuilder(15, 15)
.drawPolygon(1, 13, CurvePolygon.from(examplePath).reflectY())
.toCurve()
.line(4, 2)
Insert cell
new CurveBuilder(15, 15)
.drawPolygon(
13,
13,
CurvePolygon.from(examplePath)
.reflectX()
.reflectY()
)
.toCurve()
.line(4, 2)
Insert cell
Insert cell
tiles = {
// we'll have four tiles: 1x1, 1x2, 2x1 and 2x2.
// Since we'll look that up in a zero-indexed array
// we have to pad it with an extra null
const { TL, TR, BL, BR } = directions;
const tiles = {
[TL]: [null, [null, "1x1", "1x2"], [null, "2x1", "2x2"]],
[TR]: [null, [null, "1x1", "1x2"], [null, "2x1", "2x2"]],
[BL]: [null, [null, "1x1", "1x2"], [null, "2x1", "2x2"]],
[BR]: [null, [null, "1x1", "1x2"], [null, "2x1", "2x2"]]
};

function generateTiles(cp, w, h) {
const cp_BR = cp;
const cp_BL = cp_BR.reflectX();
const cp_TL = cp_BL.reflectY();
const cp_TR = cp_BR.reflectY();

// I want to be able to draw tiles with x,y representing the top-left corner,
// so giving each tile a a function that handles the offset is the easiest solution
tiles[BR][w][h] = (cb, x, y) => cb.drawPolygon(x, y, cp_BR);
tiles[BL][w][h] = (cb, x, y) => cb.drawPolygon(x + w - 1, y, cp_BL);
tiles[TL][w][h] = (cb, x, y) => cb.drawPolygon(x + w - 1, y + h - 1, cp_TL);
tiles[TR][w][h] = (cb, x, y) => cb.drawPolygon(x, y + h - 1, cp_TR);
// if width and height are identical we don't need to transpose the tile,
// since it would only overwrite itself
if (w !== h) {
const BR_tp = cp.transpose();
tiles[BR][h][w] = (cb, x, y) => cb.drawPolygon(x, y, BR_tp);
const BL_tp = BR_tp.reflectX();
tiles[BL][h][w] = (cb, x, y) => cb.drawPolygon(x + h - 1, y, BL_tp);
const TL_tp = BL_tp.reflectY();
tiles[TL][h][w] = (cb, x, y) =>
cb.drawPolygon(x + h - 1, y + w - 1, TL_tp);
const TR_tp = cp.transpose().reflectY();
tiles[TR][h][w] = (cb, x, y) => cb.drawPolygon(x, y + w - 1, TR_tp);
}
}

// 1 x 1 - just stay in the current tile!
generateTiles(CurvePolygon.from([]), 1, 1);

// 2 x 1
generateTiles(CurvePolygon.from([[1, 0]]), 2, 1);

// 2 x 2
generateTiles(CurvePolygon.from([[0, 1], [1, -1], [0, 1]]), 2, 2);

// We could create more, but it turns out these are enough to build arbitrary "Peano"-ish curves!

return tiles;
}
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