Published
Edited
Sep 29, 2022
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
drawPoints = (state, type, color = palette.fg) =>
state.points.map(({ u, v, filled }) => {
const cx = marginX + scaleXY(u);
const cy = marginY + scaleXY(v);
const l = 10;
if ((type === undefined || type === "emtpy") && !filled) {
return svg`<line
x1=${cx - l} y1=${cy - l}
x2=${cx + l} y2=${cy + l}
stroke-width=${strokeWidth}
stroke=${color}
></line>
<line
x1=${cx - l} y1=${cy + l}
x2=${cx + l} y2=${cy - l}
stroke-width=${strokeWidth}
stroke=${color}
></line>`;
}

if ((type === undefined || type === "filled") && filled)
return svg`<circle cx=${cx} cy=${cy} r=${strokeWidth} fill=${color}></rect>`;
})
Insert cell
drawLines = (state) =>
state.lines.map((l) => {
const p1 = state.points[l[0]];
const p2 = state.points[l[1]];

const x1 = marginX + scaleXY(p1.u);
const y1 = marginY + scaleXY(p1.v);
const x2 = marginX + scaleXY(p2.u);
const y2 = marginY + scaleXY(p2.v);

return svg`<line
x1=${x1} y1=${y1}
x2=${x2} y2=${y2}
stroke-width=${strokeWidth}
stroke=${palette.fg}
stroke-linecap="round"
></line>`;
})
Insert cell
state = {
redraw;

let max = n * n;
let iteration = 0;
const initialState = math
.linspace(n, true)
.map((u) =>
math.linspace(n, true).map((v) => {
return {
u,
v,
filled: true
};
})
)
.flat();

let points = initialState.slice();
let lines = [];
let triangles = [];

while (iteration < max) {
// Remove next point
const nextPt = indexOfNextPointToRemove(points);
points = markPointAsRemoved(points, nextPt);

// Check if any triangles can be made
const newLines = generateLines(points, lines, triangles, nextPt);
lines = [...lines, ...newLines];

// Returen new state
yield Promises.tick(delay, {
iteration,
points,
lines,
triangles
});

iteration++;
}
}
Insert cell
generateLines = (points, lines, triangles, currentPt) => {
let newLines = [];
const filledIndices = indicesByFilled(points, true);
const emptyIndices = indicesByFilled(points, false);

emptyIndices.forEach((ei) => {
// Ignore current point
if (ei === currentPt) return;

const nextLine = [currentPt, ei];

if (
crossesAFilledPoint(nextLine, points) ||
crossesOtherLines(nextLine, lines, points)
)
return;

newLines.push(nextLine);
});

return newLines;
}
Insert cell
crossesOtherLines = (line, otherLines, points) => {
const lineUV = line.map((i) => indexToUV(points, i));
return Boolean(
otherLines.find((line2) => {
const line2UV = line2.map((i) => indexToUV(points, i));
return doLinesIntersect(lineUV[0], lineUV[1], line2UV[0], line2UV[1]);
})
);
}
Insert cell
crossesAFilledPoint = (line, points) => {
const filledIndices = indicesByFilled(points, true);
const lineUV = line.map((i) => indexToUV(points, i));
return Boolean(
filledIndices.find((index) => {
const pt = indexToUV(points, index);
return isPointOnLine(
pt[0],
pt[1],
[lineUV[0][0], lineUV[0][1]],
[lineUV[1][0], lineUV[1][1]]
);
})
);
}
Insert cell
indexToUV = (points, index) => {
const { u, v } = points[index];
return [u, v];
}
Insert cell
indexOfNextPointToRemove = (pts) => {
const filledPointIndices = pts
.map((p, i) => (p.filled === true ? i : undefined))
.filter((p) => p !== undefined);

return random.pick(filledPointIndices);
}
Insert cell
markPointAsRemoved = (pts, index) => {
const nextPts = pts.slice();
nextPts[index].filled = false;
return nextPts;
}
Insert cell
indicesByFilled = (pts, filled = true) =>
pts
.map((p, index) => (p.filled === filled ? index : undefined))
.filter((p) => p !== undefined)
Insert cell
palette = ({
bg: `hsl(0,0%,95%)`,
fg: `hsl(0,0%,5%)`,
debug: `hsl(300, 100%, 50%)`
})
Insert cell
Insert cell
strokeWidth = 6
Insert cell
height = width
Insert cell
maxSize = Math.min(width, height)
Insert cell
maxMargin = maxSize / 5
Insert cell
size = maxSize - 2 * maxMargin
Insert cell
marginX = (width - size) / 2
Insert cell
marginY = (height - size) / 2
Insert cell
svg = htl.svg
Insert cell
// https://stackoverflow.com/a/11908158
isPointOnLine = (x, y, [x1, y1], [x2, y2]) => {
const dxc = x - x1;
const dyc = y - y1;

const dxl = x2 - x1;
const dyl = y2 - y1;

const cross = dxc * dyl - dyc * dxl;

if (cross != 0) return false;

if (Math.abs(dxl) >= Math.abs(dyl))
return dxl > 0 ? x1 <= x && x <= x2 : x2 <= x && x <= x1;
else return dyl > 0 ? y1 <= y && y <= y2 : y2 <= y && y <= y1;
}
Insert cell
doLinesIntersect = ([a, b], [c, d], [p, q], [r, s]) => {
var det, gamma, lambda;
det = (c - a) * (s - q) - (r - p) * (d - b);
if (det === 0) {
return false;
} else {
lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
return 0 < lambda && lambda < 1 && 0 < gamma && gamma < 1;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
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