Public
Edited
Dec 4, 2022
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
T = 1 // 0b0001
Insert cell
R = 2 // 0b0010
Insert cell
B = 4 // 0b0100
Insert cell
L = 8 // 0b1000
Insert cell
Insert cell
// Store "in" in the upper four bits, and "out" in the bottom four
TR = T * 16 + R
Insert cell
TB = T * 16 + B
Insert cell
// Note that curves cannot leave in the direction where they entered,
// so can skip TT. We'll have 3 * 4 = 12 combinations in total.
TL = T * 16 + L
Insert cell
RB = R * 16 + B // you get the idea (if you're comfy with thinking in bits)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
makeHGrid = {
// Note: the curve walks clockwise along the H-shape
// ┏━┓ ┏━┓ BR LB BR LB
// ┃ ┗━┛ ┃ BT TR LT TB
// ┃ ┏━┓ ┃ BT RB BL TB
// ┗━┛ ┗━┛ RT TL RT TL
const lookup = [
BR, LB, BR, LB,
BT, TR, LT, TB,
BT, RB, BL, TB,
RT, TL, RT, TL,
];

return function makeHGrid(k) {
const size = 4 * 2 ** k;
const grid = [];
for(let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
grid.push(lookup[(y%4)*4 + (x%4)]);
}
}
return grid;
};
}
Insert cell
makeHGrid(8)
Insert cell
makeLoops = {
// Note: the curve walks clockwise along the loop
// ┏━┓ BR LB
// ┗━┛ RT TL
const lookup = [
BR, LB,
RT, TL,
];

return function makeHGrid(k) {
const size = 4 * 2 ** k;
const grid = [];
for(let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
grid.push(lookup[(y%2)*2 + (x%2)]);
}
}
return grid;
};
}
Insert cell
Insert cell
boxdrawing = {
// Look up table to create ASCII drawings
const LUT = {
[TR]: "┗━",
[TB]: "┃ ",
[TL]: "┛ ",
[RB]: "┏━",
[RL]: "━━",
[RT]: "┗━",
[BL]: "┓ ",
[BT]: "┃ ",
[BR]: "┏━",
[LT]: "┛ ",
[LR]: "━━",
[LB]: "┓ "
};
return function boxdrawing(grid, returnRawString) {
const size = Math.sqrt(grid.length);
let output = "";
for (let i = 0; i < grid.length; i++) {
if (i % size === 0) output += "\n ";
output += LUT[grid[i]];
}
return returnRawString ? output : box`${output}`;
};
}
Insert cell
boxdrawing(makeHGrid(2))
Insert cell
Insert cell
function gridToHCurve(grid, x, y, subgridsize) {
const gridsize = Math.sqrt(grid.length);
const idx = x + y * gridsize;
// The basic idea is this: first make a grid of H's
// ┏━┓ ┏━┓ ┏━┓ ┏━┓
// ┃ ┗━┛ ┃ ┃ ┗━┛ ┃
// ┃ ┏━┓ ┃ ┃ ┏━┓ ┃
// ┗━┛ ┗━┛ ┗━┛ ┗━┛
// ┏━┓ ┏━┓ ┏━┓ ┏━┓
// ┃ ┗━┛ ┃ ┃ ┗━┛ ┃
// ┃ ┏━┓ ┃ ┃ ┏━┓ ┃
// ┗━┛ ┗━┛ ┗━┛ ┗━┛
// Then in the middle of the grid, connect the H's like so:
// ┏━┓ ┏━┓ ┏━┓ ┏━┓
// ┃ ┗━┛ ┃ ┃ ┗━┛ ┃
// ┃ ┏━┓ ┃ ┃ ┏━┓ ┃
// ┗━┛ ┃ ┗━┛ ┃ ┗━┛
// ┏━┓ ┃ ┏━┓ ┃ ┏━┓
// ┃ ┗━┛ ┃ ┃ ┗━┛ ┃
// ┃ ┏━┓ ┃ ┃ ┏━┓ ┃
// ┗━┛ ┗━┛ ┗━┛ ┗━┛
// This function does only that last part, converting:
// ┗━┛ ┗━┛ => ┃ ┗━┛ ┃
// ┏━┓ ┏━┓ => ┃ ┏━┓ ┃
// Or:
// RT TL RT TL => BT TR LT TB
// BR LB BR LB BT RB BL TB
grid[idx] = BT;
grid[idx+1] = TR;
grid[idx+2] = LT;
grid[idx+3] = TB;
grid[idx+gridsize] = BT;
grid[idx+gridsize+1] = RB;
grid[idx+gridsize+2] = BL;
grid[idx+gridsize+3] = TB;
if (subgridsize > 4) {
subgridsize /= 2;
gridToHCurve(grid, x-subgridsize, y-subgridsize, subgridsize)
gridToHCurve(grid, x+subgridsize, y-subgridsize, subgridsize)
gridToHCurve(grid, x-subgridsize, y+subgridsize, subgridsize)
gridToHCurve(grid, x+subgridsize, y+subgridsize, subgridsize)
}
return grid;
}
Insert cell
function hcurve(k) {
const size = 4 * 2 ** k;
return gridToHCurve(makeHGrid(k), size / 2 - 2, size / 2 - 1, size / 2)
}
Insert cell
boxdrawing(hcurve(1))
Insert cell
boxdrawing(hcurve(2))
Insert cell
boxdrawing(hcurve(3))
Insert cell
Insert cell
function *canvasDrawing(grid, scale, steps = 1){
const step = scale * 5;
const size = Math.sqrt(grid.length);
const strokeCtx = DOM.context2d(size * step, size * step, 1);
const gradientCtx = DOM.context2d(size * step, size * step, 1);
const outCtx = DOM.context2d(size * step, size * step, 1);
gradientCtx.fillStyle = "white";
gradientCtx.fillRect(0, 0, size, size);

strokeCtx.lineWidth = Math.floor(scale * 2);
strokeCtx.lineCap = "round"
let x = 0, y = 0, px = 0, py = 0;;
for(let i = 0, idx = 0; i < grid.length; i++) {
gradientCtx.fillStyle = d3.interpolateSpectral(i / grid.length);
gradientCtx.fillRect(x, y, step, step);
strokeCtx.strokeStyle = d3.interpolateSpectral((grid.length - i) / grid.length);
strokeCtx.beginPath();
strokeCtx.moveTo(px + step/2, py + step/2);
strokeCtx.lineTo(x + step/2, y + step/2);
strokeCtx.stroke();
px = x;
py = y;
const direction = grid[idx] & 0b1111;
switch (direction) {
case T:
y -= step; idx -= size; break;
case R:
x += step; idx += 1; break
case B:
y += step; idx += size; break;
case L:
x -= step; idx -= 1; break;
}
if (i%steps === 0) {
outCtx.drawImage(gradientCtx.canvas, 0, 0);
outCtx.drawImage(strokeCtx.canvas, 0, 0);
yield outCtx.canvas
}
}
outCtx.drawImage(gradientCtx.canvas, 0, 0);
outCtx.drawImage(strokeCtx.canvas, 0, 0);
return outCtx.canvas;
}
Insert cell
canvasDrawing(hcurve(5), 1, 256)
Insert cell
Insert cell
function gridToIndices(grid) {
const size = Math.sqrt(grid.length);
const gridToLine = [];
const lineToGrid = [];
// initiate a numerical array without holes.
for(let i = 0; i < grid.length; i++) {
gridToLine.push(0);
lineToGrid.push(0);
}
// Now we basicaly do the same as in the canvasDrawing function above,
// but instead of drawing to a context we save the indices
let x = 0, y = 0
const points = []
for(let i = 0, idx = 0; i < grid.length; i++) {
gridToLine[idx] = i;
lineToGrid[i] = idx;
const direction = grid[idx] & 0b1111;
switch (direction) {
case T:
y -= 1; idx -= size; break;
case R:
x += 1; idx += 1; break
case B:
y += 1; idx += size; break;
case L:
x -= 1; idx -= 1; break;
}
points.push([x, y])
}
return {
gridToLine,
lineToGrid,
points
};
}
Insert cell
function hcurveIndices(k) {
return gridToIndices(hcurve(k));
}
Insert cell
Insert cell
Insert cell
d3 = require('d3-scale-chromatic', 'd3-color')
Insert cell
import {
initBoxFont,
boxFonts,
box
} from "@jobleonard/unicode-box-drawing-formatter"
Insert cell
boxFonts
Insert cell
initBoxFont()
Insert cell
// Background palette taken from:
// https://colorbrewer2.org/#type=qualitative&scheme=Paired&n=4
cbrewer = [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c"
]
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