Public
Edited
Oct 1, 2023
Fork of Spectre
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
WIDTH = Math.min(800, width)
Insert cell
HEIGHT = 500
Insert cell
ORIGIN = [0,0,-250,0]
Insert cell
Insert cell
Insert cell
Insert cell
FINETUNE_DEGREES = 9
Insert cell
Insert cell
t = {
while (true) {
yield Math.sin(Date.now() / 4000 * Math.PI)**2;
}
}
Insert cell
function rotate(n, [a,b,c,d]) {
n = ((n % 12) + 12) % 12;
for (let i=0; i < n; i++) {
[a,b,c,d] = [
(3*b - c) / 2,
(a - d) / 2,
(a + 3*d) / 2,
(b + c) / 2,
];
}
return [a,b,c,d];
}
Insert cell
HEX_SIDE = Math.sqrt((2 + 2/SQRT3) * (1 + 1 / (Math.sqrt(15) + 4)));
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function interpolate(t, a, b) {
const result = Array(a.length);
for (let i = 0; i < a.length; ++i) {
result[i] = (1-t) * a[i] + t*b[i];
}
return result;
}
Insert cell
function tile(type, [x1, x2, y1, y2], i, n, r) {
const points = [];

const spectre_deltas = SPECTRE.map(v => rotate(n+1+r, v));
if (!(type in HEX_DELTAS)) console.trace(`${type} not in hex_deltas`);
const hex_deltas = HEX_DELTAS[type].map(v => rotate(n+r, v));

let deltas = Array(14);
for (let j = 0; j < 14; ++j) {
deltas[j] = interpolate(t, spectre_deltas[j], hex_deltas[j]);
}
deltas = deltas.slice(i).concat(deltas.slice(0,i));
const scale = L/2 * ((1-t) + t * HEX_SIDE);
for (const v of deltas) {
const [dx1, dx2, dy1, dy2] = v;
x1 += dx1 * scale; x2 += dx2 * scale;
y1 += dy1 * scale; y2 += dy2 * scale;
points.push([x1, x2, y1, y2]);
}

return points.slice(13 - i).concat(points.slice(0, 13 - i));
}
Insert cell
function moveTo(ct, [x1, x2, y1, y2]) {
let x = x1 + x2 * SQRT3,
y = y1 + y2 * SQRT3;

ct.moveTo(x, y);
}
Insert cell
function lineTo(ct, [x1, x2, y1, y2]) {
let x = x1 + x2 * SQRT3,
y = y1 + y2 * SQRT3;

ct.lineTo(x, y);
}
Insert cell
function draw(ct, points) {
ct.beginPath();
moveTo(ct, points[points.length - 1]);
for (const pt of points) {
lineTo(ct, pt);
}
ct.stroke();
}
Insert cell
function make8cluster(type, pt, rot) {
const tiles = [];

let pts1, pts3, pts5, pts6, pts7;
tiles.push(pts5 = tile('Γ1', pt, 0, 3, rot)); // 5
tiles.push(tile('Γ2', pts5[9], 5, 4, rot)); // 8
tiles.push(pts6 = tile(TILE6[type], pts5[1], 11, 0, rot)); // 6
tiles.push(tile('Δ', pts5[1], 13, 2, rot)); // 2
tiles.push(pts1 = tile(TILE1[type], pts5[5], 11, 4, rot)); // 1
tiles.push(tile('Σ', pts5[5], 13, 6, rot)); // 4
tiles.push(pts7 = tile(TILE7[type], pts5[9], 11, 8, rot)); // 7
tiles.push(pts3 = tile(TILE3[type], pts6[6], 0, 2, rot)); // 3

const control_points = [
pts1[4],
pts7[6],
pts6[4],
pts3[6],
];
return {
tiles,
control_points
};
}
Insert cell
function make9cluster(type, pt, rot) {
const { tiles, control_points } = make8cluster(type, pt, rot, true);
tiles.push(tile(TILE9[type], tiles[4][6], 0, 6, rot)); // 9
return { tiles, control_points };
}
Insert cell
function dotAt(ct, [x1, x2, y1, y2]) {
ct.beginPath();
ct.ellipse(x1 + x2 * SQRT3, y1 + y2 * SQRT3, 10, 10, 0, 0, 2 * Math.PI);
ct.fillStyle = 'red';
ct.fill();
}
Insert cell
function translate(from, to, cluster) {
const [ from_x1, from_x2, from_y1, from_y2 ] = from;
const [ to_x1, to_x2, to_y1, to_y2 ] = to;
const dx1 = to_x1 - from_x1,
dx2 = to_x2 - from_x2,
dy1 = to_y1 - from_y1,
dy2 = to_y2 - from_y2;

for (const tile of cluster.tiles) {
for (let i = 0; i < tile.length; i++) {
const [ x1, x2, y1, y2 ] = tile[i];
tile[i] = [ x1 + dx1, x2 + dx2, y1 + dy1, y2 + dy2 ];
}
}

for (let i = 0; i < cluster.control_points.length; ++i) {
const [ x1, x2, y1, y2 ] = cluster.control_points[i];
cluster.control_points[i] = [ x1 + dx1, x2 + dx2, y1 + dy1, y2 + dy2 ];
}
}
Insert cell
function makeSupercluster(type, pt, r) {
const c1 = make8cluster('Γ', pt, 11 + r);
const c2 = make9cluster(TILE6[type][0], c1.control_points[1], 3 + r);
translate(c2.control_points[1], c1.control_points[1], c2);

const c3 = make9cluster(TILE7[type], c1.control_points[2], 7 + r);
translate(c3.control_points[2], c1.control_points[2], c3);

const c4 = make9cluster('Δ', c3.control_points[1], 9 + r);
translate(c4.control_points[3], c3.control_points[1], c4);

// If type == 'Γ' then this cluster is not real, but we need to
// make some random 9-cluster just to get the control point,
// so I chose Ξ arbitrarily.
//
// We could avoid this by starting the chain in a different place.
const c5 = make9cluster(type == 'Γ' ? 'Ξ' : TILE9[type], c4.control_points[0], 9 + r);
translate(c5.control_points[2], c4.control_points[0], c5);

const c6 = make9cluster(TILE1[type][0], c5.control_points[1], 11 + r);
translate(c6.control_points[3], c5.control_points[1], c6);

const c7 = make9cluster('Σ', c6.control_points[1], 1 + r);
translate(c7.control_points[3], c6.control_points[1], c7);

const c8 = make9cluster(TILE3[type], c7.control_points[0], 1 + r);
translate(c8.control_points[2], c7.control_points[0], c8);

let tiles;
if (type == 'Γ') {
tiles = c1.tiles.concat(
c2.tiles,
c3.tiles,
c4.tiles,
c6.tiles,
c7.tiles,
c8.tiles,
);
}
else {
tiles = c1.tiles.concat(
c2.tiles,
c3.tiles,
c4.tiles,
c5.tiles,
c6.tiles,
c7.tiles,
c8.tiles,
);
}

return {
control_points: [
c6.control_points[0],
c3.control_points[3],
c2.control_points[0],
c8.control_points[3],
],
tiles
};
}
Insert cell
{
const ct = canvas.ct;

ct.clearRect(-canvas.width/2, -canvas.height/2, canvas.width, canvas.height);
ct.strokeStyle = 'white';

const type = 'Λ';

const s1 = makeSupercluster('Γ', ORIGIN, 0);

const s2 = makeSupercluster(TILE6[type][0], s1.control_points[1], 8);
translate(s2.control_points[1], s1.control_points[1], s2);

const s3 = makeSupercluster(TILE7[type], s1.control_points[2], 4);
translate(s3.control_points[2], s1.control_points[2], s3);

const s4 = makeSupercluster('Δ', s3.control_points[1], 2);
translate(s4.control_points[3], s3.control_points[1], s4);

const s5 = makeSupercluster(type == 'Γ' ? 'Ξ' : TILE9[type], s4.control_points[0], 2);
translate(s5.control_points[2], s4.control_points[0], s5);

const s6 = makeSupercluster(TILE1[type][0], s5.control_points[1], 0);
translate(s6.control_points[3], s5.control_points[1], s6);

const s7 = makeSupercluster('Σ', s6.control_points[1], 10);
translate(s7.control_points[3], s6.control_points[1], s7);

const s8 = makeSupercluster(TILE3[type], s7.control_points[0], 10);
translate(s8.control_points[2], s7.control_points[0], s8);

ct.strokeStyle = 'white';
for (const spectre of s1.tiles) draw(ct, spectre);
for (const spectre of s2.tiles) draw(ct, spectre);
for (const spectre of s3.tiles) draw(ct, spectre);
for (const spectre of s4.tiles) draw(ct, spectre);
for (const spectre of s5.tiles) draw(ct, spectre);
for (const spectre of s6.tiles) draw(ct, spectre);
for (const spectre of s7.tiles) draw(ct, spectre);
for (const spectre of s8.tiles) draw(ct, spectre);


return this || html`(<i>the drawing function</i>)`;
}
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