Public
Edited
Apr 27
6 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
regularPolygon = (n, shift) => {
const angles = [...Array(n)].map((_, i) => (i * 2 * Math.PI) / n + shift);
const pts = angles.map((angle) => [Math.cos(angle), -Math.sin(angle)]);
return polyline(pts, true);
}
Insert cell
visitTiling = (tiling, nb, cb) => {
const sameValue = (v1, v2) => Math.abs(v1 - v2) < 1e-10;
const samePt = ([x1, y1], [x2, y2]) => sameValue(x1, x2) && sameValue(y1, y2);
const add = ([x1, y1], [x2, y2]) => [x1 + x2, y1 + y2];
const origin = [0, 0];
const nodes = [origin];
const vertices = [{ coords: origin, rotation: 0 }];
for (let i = 0; i < nb; i++) {
const { coords, rotation: rot, ...info } = vertices.shift();
const { segments, nextNodes } = tiling({ rotation: rot, ...info });
cb(coords, segments);
nextNodes.forEach(({ vector, rotation, ...info }) => {
const pt = add(coords, vector);
if (!nodes.find((node) => samePt(node, pt))) {
vertices.push({ coords: pt, rotation: rotation + rot, ...info });
nodes.push(pt);
}
});
}
}
Insert cell
plotTiling = (tiling, nb) => {
const plot = Plot();
visitTiling(tiling, nb, (coords, edges) => {
const vx = edges.reduce((v, vec) => v.insert(Plot().draw(...vec)), Plot());
plot.insert(translate(vx, coords));
});
return optimize(plot);
}
Insert cell
plotDual = (tiling, nb) => {
const plot = Plot();
const centroid = ([x1, y1], [x2, y2]) => {
const [n1, n2] = [(x1 * x1 + y1 * y1) / 2, (x2 * x2 + y2 * y2) / 2];
const x = (y1 * n2 - y2 * n1) / (x2 * y1 - x1 * y2);
const y = Math.abs(y1) < 1e-5 ? (n2 - x * x2) / y2 : (n1 - x * x1) / y1;
return [x, y];
};
visitTiling(tiling, nb, (coords, edges) => {
const centroids = pairs(edges).map(([v1, v2]) => centroid(v1, v2));
plot.insert(translate(polyline(centroids, true), coords));
});
return optimize(plot);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
UniformTiling = (types) =>
({ type, rotation }) => {
const atypes = typeof types == "string" ? parseTiling(types) : types;
const ratio = (2 * Math.PI) / 24;
const geom = atypes[type || 0]
.map((c) => (c.length == 3 ? c : [c[0], 0, c[1]]))
.map(([ang, type, rot = 0]) => {
const aa = ang * ratio + rotation;
return [[Math.cos(aa), Math.sin(aa)], type, rot * ratio];
});
const a2o = ([vector, type, rotation]) => ({ vector, type, rotation });
return {
segments: geom.map(([vector]) => vector),
nextNodes: geom.map(a2o)
};
}
Insert cell
Insert cell
UniformTilings = {
const D = (...items) => items;
return mapObject(
{
triangular: "A0AC0AE0AG0AI0AK0A",
square: [D([0, 0], [6, 0], [12, 0], [18, 0])],
hexagonal: [D([0, 12], [8, 12], [16, 12])],
truncatedHexagonal: [D([0, 8], [4, 16], [14, 12])],
trihexagonal: [D([4, 8], [8, 4], [16, 8], [20, 4])],
snubHexagonal: [D([0, 20], [4, 12], [8, 8], [12, 16], [16, 4])],
truncatedTrihexagonal: "A1AD1GH1C|D0GG0AL0K",
rhombiTrihexagonal: [D([4, 8], [8, 16], [14, 4], [22, 20])],
elongatedTriangular: [D([0, 0], [6, 12], [12, 0], [16, 12], [20, 12])],
truncatedSquare: [D([3, 12], [12, 6], [18, 18])],
snubSquare: [D([0, 12], [4, 6], [10, 18], [14, 6], [20, 18])],
"[3.3.3.3.3.3;3.3.4.12]": [
D([0, 2, 12], [10, 2, 8], [14, 1, 0], [18, 2, 0]),
D(
[2, 0, 0],
[6, 2, 8],
[10, 0, 8],
[14, 2, 16],
[18, 0, 16],
[22, 2, 0]
),
D([0, 0, 12], [6, 0, 0], [10, 1, 0], [14, 0, 16])
],
"[3.6.3.6;3.3.6.6]": [
D([0, 0, 12], [8, 1, 0], [12, 0, 12], [16, 1, 0]),
D([4, 0, 0], [8, 0, 12], [16, 0, 12], [20, 0, 0])
],
"[3.10.10;3.4.3.10]": [
D([8, 1, 6], [12, 0, 6], [18, 0, 18], [22, 1, 0]),
D([0, 1, 12], [10, 0, 0], [14, 0, 18])
],
"[3.3.3.3.3.3;3.3.6.6]": [
D(
[0, 1, 0],
[4, 1, 4],
[8, 1, 8],
[12, 1, 12],
[16, 1, 16],
[20, 1, 20]
),
D([0, 1, 12], [8, 1, 4], [12, 0, 0], [16, 1, 20])
],
"[3.4.4.6;3.6.3.6]1": [
D([0, 1, 0], [8, 2, 0], [12, 1, 0], [18, 0, 12]),
D([0, 0, 0], [4, 2, 0], [12, 0, 0], [18, 1, 12]),
D([4, 1, 12], [8, 0, 12], [16, 1, 0], [20, 0, 0])
],
"[3.3.3.3.3.3;3.3.4.3.4]": [
D(
[0, 1, 0],
[4, 1, 4],
[8, 1, 8],
[12, 1, 12],
[16, 1, 16],
[20, 1, 20]
),
D([2, 1, 16], [8, 1, 4], [12, 0, 0], [16, 1, 20], [22, 1, 8])
],
"[3.3.3.3.3.3;3.3.3.4.4]1": [
D([0, 0, 0], [4, 1, 0], [8, 1, 0], [12, 0, 0], [18, 0, 12]),
D([0, 1, 0], [4, 0, 12], [8, 0, 12], [12, 1, 0], [16, 0, 0], [20, 0, 0])
],
"[3.3.3.3.3.3;3.3.3.4.4]2": [
D(
[0, 0, 0],
[4, 0, 12],
[8, 0, 12],
[12, 0, 0],
[16, 1, 0],
[20, 1, 0]
),
D([0, 1, 0], [4, 0, 0], [8, 0, 0], [12, 1, 0], [18, 1, 12])
],
"[3.3.3.3.6;3.3.6.6]": [
D([2, 1, 0], [10, 1, 0], [14, 0, 12], [18, 1, 12], [22, 0, 12]),
D([6, 1, 12], [14, 0, 0], [18, 0, 12], [22, 0, 0])
],
"[3.4.4.6;3.6.3.6]2": [
D([0, 1, 0], [8, 2, 0], [12, 1, 0], [18, 1, 12]),
D([0, 0, 0], [4, 2, 0], [12, 0, 0], [18, 0, 12]),
D([4, 1, 12], [8, 0, 12], [16, 1, 0], [20, 0, 0])
],
"[3.3.3.4.4;3.3.4.3.4]1": [
D([0, 1, 0], [4, 1, 18], [8, 2, 6], [12, 2, 0], [18, 0, 12]),
D([2, 1, 6], [8, 1, 18], [12, 0, 0], [18, 2, 12], [22, 0, 6]),
D([0, 0, 0], [4, 2, 6], [10, 2, 18], [14, 0, 18], [18, 1, 12])
],
"[3.3.3.3.3.3;3.3.3.3.6]1": [
D(
[0, 1, 0],
[4, 1, 12],
[8, 1, 8],
[12, 1, 20],
[16, 1, 16],
[20, 1, 4]
),
D([0, 1, 20], [4, 0, 12], [8, 1, 12], [12, 0, 0], [16, 1, 4])
],
"[3.4.6.4;3.4.4.6]": [
D([4, 1, 0], [8, 2, 0], [14, 0, 4], [22, 0, 20]),
D([4, 2, 8], [12, 2, 0], [16, 0, 0], [22, 2, 20]),
D([0, 1, 0], [8, 1, 16], [14, 1, 4], [20, 0, 0])
],
"[3.3.3.4.4;3.3.4.3.4]2": [
D([0, 1, 0], [4, 2, 12], [8, 0, 12], [12, 2, 0], [18, 0, 12]),
D([2, 3, 0], [8, 2, 12], [12, 0, 0], [18, 2, 12], [22, 3, 12]),
D([0, 0, 0], [4, 0, 12], [8, 1, 12], [14, 3, 0], [18, 1, 12]),
D([2, 2, 0], [8, 3, 12], [14, 1, 0], [18, 3, 12], [22, 1, 12])
],
"[3.3.3.4.4;4.4.4.4]1": [
D([0, 0, 0], [4, 0, 12], [8, 0, 12], [12, 0, 0], [18, 1, 0]),
D([0, 1, 0], [6, 0, 0], [12, 1, 0], [18, 1, 12])
],
"[3.3.3.3.3.3;3.3.3.3.6]2": [
D(
[0, 0, 8],
[4, 0, 16],
[8, 1, 0],
[12, 0, 12],
[16, 1, 12],
[20, 1, 8]
),
D([0, 0, 16], [4, 1, 4], [12, 1, 20], [16, 0, 12], [20, 0, 0])
],
"[3.4.6.4;4.6.12]": [
D([0, 0, 16], [6, 1, 0], [14, 2, 0], [20, 0, 8]),
D([0, 2, 16], [10, 2, 12], [18, 0, 0]),
D([2, 0, 0], [10, 1, 12], [20, 1, 8])
],
"[3.3.3.4.4;4.4.4.4]2": [
D([0, 0, 0], [6, 1, 0], [12, 0, 0], [18, 1, 12]),
D([0, 1, 0], [4, 1, 12], [8, 1, 12], [12, 1, 0], [18, 0, 0])
],
"[3.3.4.3.4;3.4.6.4]": [
D([2, 1, 0], [6, 0, 12], [10, 1, 12], [16, 0, 8], [20, 0, 16]),
D([4, 1, 20], [10, 0, 12], [14, 0, 0], [20, 1, 4])
],
"[3.3.3.4.4;3.4.6.4]": [
D([2, 1, 0], [6, 1, 16], [12, 0, 4], [20, 0, 20]),
D([2, 0, 8], [6, 1, 8], [10, 1, 16], [14, 0, 0], [20, 1, 12])
]
},
UniformTiling
);
}
Insert cell
Insert cell
{
const [tiling, nb] = [UniformTilings["[3.3.3.4.4;3.4.6.4]"], 50];
const plot = Plot()
.insert(plotTiling(tiling, nb))
.insert(plotDual(tiling, nb));
return viewer().view(scale(plot, 20));
}
Insert cell
Insert cell
Insert cell
Insert cell
plot = {
// utilitary function that finds a frame in the base drawing
const inVertex = (vertexId, cb) => {
const vertex = baseDiagram.vertices.find(({ id }) => id.endsWith(vertexId));
if (vertex) plot.insert(translate(cb(vertex.dims), vertex.pos));
};

// base drawing
const plot = Plot().insert(pivot2plot(baseDiagram));

// regular polygons
Object.entries({
"KRy-1": [3, Math.PI / 2],
"KRy-2": [4, Math.PI / 4],
"KRy-3": [6, Math.PI / 2],
"KRy-4": [8, Math.PI / 2],
"KRy-5": [12, Math.PI / 2]
}).forEach(([id, [nb, shift]]) => {
inVertex(id, () => scale(regularPolygon(nb, shift), 10));
});

// uniform tilings and their duals
Object.entries({
triangular: ["4HH-2", "", 40, 17],
square: ["4HH-3", "", 50, 15],
hexagonal: ["4HH-4", "", 50, 12, [-0.5, sq32]],
truncatedHexagonal: ["4HH-9", "udf-12", 120, 7, [-0.5, 1 + sq32]],
truncatedTrihexagonal: ["4HH-10", "udf-10", 160, 7, [-0.5, 1 + sq32]],
truncatedSquare: ["4HH-11", "udf-15", 150, 8, [-(1 + sq2) / 2, 0.5]],
trihexagonal: ["4HH-18", "udf-13", 150, 10, [-1, 0]],
rhombiTrihexagonal: ["4HH-19", "udf-11", 250, 9, [0, 1]],
snubHexagonal: ["4HH-24", "udf-9", 90, 12, [-0.5, sq32]],
elongatedTriangular: ["4HH-25", "udf-8", 90, 14, [0.5, -0.5]],
snubSquare: ["4HH-26", "udf-14", 80, 12, [-0.5, 0]],
"[3.3.3.3.3.3;3.3.3.3.6]1": ["4HH-78", "b75-9", 190, 10, [1.5, sq32]],
"[3.3.3.3.3.3;3.3.3.3.6]2": ["4HH-87", "b75-16", 190, 10, [0, sq3]],
"[3.3.3.3.3.3;3.3.3.4.4]1": ["4HH-86", "b75-15", 100, 15, [0.5, 0.5]],
"[3.3.3.3.3.3;3.3.3.4.4]2": ["4HH-83", "b75-13", 100, 14, [0, 0.5 + sq32]],
"[3.3.3.3.3.3;3.3.4.3.4]": ["4HH-80", "b75-10", 120, 10],
"[3.3.3.3.3.3;3.3.4.12]": ["4HH-70", "b75-2", 220, 7, [-0.5, -1 - sq32]],
"[3.3.3.3.3.3;3.3.6.6]": ["4HH-75", "b75-6", 110, 10],
"[3.3.3.4.4;3.3.4.3.4]1": ["4HH-72", "b75-4", 150, 10, [0, 0.5]],
"[3.3.3.4.4;3.3.4.3.4]2": ["4HH-82", "b75-12", 150, 10, [0, 0.5]],
"[3.3.3.3.6;3.3.6.6]": ["4HH-85", "b75-14", 110, 10, [0, -1]],
"[3.6.3.6;3.3.6.6]": ["4HH-71", "b75-3", 100, 10, [-0.5, -sq32]],
"[3.10.10;3.4.3.10]": ["4HH-76", "b75-7", 190, 6, [0.5, 0.5]],
"[3.4.4.6;3.6.3.6]1": ["4HH-81", "b75-11", 140, 9, [-0.5, -sq32]],
"[3.4.4.6;3.6.3.6]2": ["4HH-73", "b75-5", 130, 9, [-0.5, -sq32]],
"[3.4.6.4;3.4.4.6]": ["4HH-77", "b75-8", 100, 10, [0, 1]],
"[3.3.3.4.4;4.4.4.4]1": ["4HH-88", "b75-17", 150, 12, [0.5, 1.5]],
"[3.3.3.4.4;4.4.4.4]2": ["4HH-134", "b75-18", 100, 12],
"[3.4.6.4;4.6.12]": ["4HH-136", "b75-20", 200, 8, [0.5 + sq3, 1 + sq32]],
"[3.3.4.3.4;3.4.6.4]": ["4HH-137", "b75-21", 190, 10, [0, 1 + sq3]],
"[3.3.3.4.4;3.4.6.4]": ["4HH-135", "b75-19", 160, 10, [0.5, sq32]]
}).forEach(([tilingId, [uniform, dual, iters, ratio, offset = [0, 0]]]) => {
const tiling = UniformTilings[tilingId];
const insert = (plot, dims) => {
const [w, h] = dims.map((v) => v / 2 / ratio);
return scale(crop(translate(plot, offset), [-w, w], [-h, h]), ratio);
};
inVertex(uniform, (dims) => insert(plotTiling(tiling, iters), dims));
if (dual) inVertex(dual, (dims) => insert(plotDual(tiling, iters), dims));
});

// planigons
Object.entries({
"KRy-27": UniformTilings.square,
"KRy-25": UniformTilings.triangular,
"KRy-26": UniformTilings.hexagonal,
"KRy-16": UniformTilings.truncatedSquare,
"KRy-17": UniformTilings.snubSquare,
"KRy-18": UniformTilings.trihexagonal,
"KRy-19": UniformTilings.truncatedHexagonal,
"KRy-20": UniformTilings.rhombiTrihexagonal,
"KRy-22": UniformTilings.truncatedTrihexagonal,
"KRy-23": UniformTilings.snubHexagonal,
"KRy-24": UniformTilings.elongatedTriangular,
"udf-53": UniformTilings["[3.3.3.3.3.3;3.3.4.12]"],
"udf-54": UniformTilings["[3.6.3.6;3.3.6.6]"],
"udf-55": UniformTilings["[3.10.10;3.4.3.10]"],
"udf-56": UniformTilings["[3.4.4.6;3.6.3.6]1"]
}).forEach(([id, type]) => {
inVertex(id, () => {
const tiling = plotTiling(type, 1);
const dual = plotDual(type, 1);
return scale(dash(tiling, [0.05, 0.2]).chain(dual), 10);
});
});

return plot;
}
Insert cell
viewer().view(plot, { "stroke-width": 1 })
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