Public
Edited
Dec 29, 2022
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Mandala = async function* ({
design = Designs.Anko,
size = Math.min(width, mutable outerHeight * 0.8)
} = {}) {
const mandala = svg`<svg width=${size} height=${size} />`;
yield mandala;

const { board_fill, num_sectors, piece_types } = design;
const outer_radius = size / 2;
const center = [outer_radius, outer_radius];
const sector_angle_size = (2 * Math.PI) / num_sectors;
const board_points = _.times(num_sectors, (i) =>
fromPolar(i * sector_angle_size, outer_radius, center)
);
const inner_radius =
(11 / 12) * V.dist(center, V.div(V.add(...board_points.slice(0, 2)), 2));

const style = `\
<style>
.mandala-piece:hover { cursor:grab; }
.mandala-piece:active { cursor:grabbing; }
</style>`;

let true_coords = mandala.createSVGPoint();
// debugger;

mandala.append(html`${style}`, svg`${Board()}`);

for await (const piece of Pieces()) {
await Promises.delay(20);
mandala.append(piece);
}

return mandala;

//

function Board() {
return `<path d="M${board_points}z" fill=${board_fill} />`;
}

function Piece(type_info, i) {
const { origin, path } = type_info;
const points = _.map(path, (coords) => {
const scaled_coords = V.mult(coords, 0.97);
const [x, y] = V.add(origin, scaled_coords);
return fromPolar((i + x) * sector_angle_size, y * inner_radius, center);
});
const fill = `hsl(${(i / num_sectors) * 360}deg,65%,50%)`;
return `<g><polygon points=${points} fill=${fill} /></g>`;
}

// generates all piece types for each sector
async function* Pieces() {
for (const type_info of piece_types) {
for (const i of _.times(num_sectors)) {
const polygon = Piece(type_info, i % num_sectors); // create shape
const piece = svg`${polygon}`; // create element
piece.classList += "mandala-piece"; // add style
piece.onmousedown = onMouseDownFn(piece); // listen for events
piece.onmouseup = onMouseUpFn(piece);
yield piece;
}
}
}

function onMouseDownFn(piece) {
const GrabPiece = GrabPieceFn(piece);
return (e) => {
const { buttons } = e;
if (buttons & 1) return GrabPiece(e); // primary-button
};
}

function onMouseUpFn(piece) {
const ReleasePiece = ReleasePieceFn(piece);
return (e) => {
const { buttons } = e;
if (buttons & 1) return ReleasePiece(e); // primary-button
};
}

function GrabPieceFn(piece) {
return (e) => {
// piece.setAttributeNS(null, "pointer-events", "none");

const true_coords = GetTrueCoords(e);

const trans_matrix = e.target.getCTM();

// const grab_point = {
// x: true_coords.x - Number(trans_matrix.e),
// y: true_coords.y - Number(trans_matrix.f)
// };
const grab_point = true_coords;

// debugger;

const DragPiece = DragPieceFn(grab_point);
piece.addEventListener("mousemove", DragPiece);

piece.addEventListener(
"mouseup",
() => {
piece.removeEventListener("mousemove", DragPiece);
},
{ once: true }
);
};
}

function ReleasePieceFn(piece) {
return (e) => {};
}

function DragPieceFn(grab_point) {
return (e) => {
const piece = e.currentTarget;

true_coords = GetTrueCoords(e);

const new_point = {
x: true_coords.x,
y: true_coords.y
};

piece.setAttributeNS(
null,
"transform",
`translate(${new_point.x}, ${new_point.y})`
);
};
}

// compensates for zoom and pan
function GetTrueCoords(evt) {
const { currentScale, translation } = mandala;
return {
x: (evt.clientX - translation.x) / currentScale,
y: (evt.clientY - translation.y) / currentScale
};
}
}
Insert cell
Insert cell
Designs = ({
Anko: {
num_sectors: 10,
board_fill: "rgb(235,215,205)",
board:
'<path d="M 1 0 L 0.809 0.5878 L 0.309 0.9511 L -0.309 0.9511 L -0.809 0.5878 L -1 0 L -0.809 -0.5878 L -0.309 -0.9511 L 0.309 -0.9511 L 0.809 -0.5878 Z" fill="rgb(235,215,205)" />',
piece_types: [
{ origin: [1 / 2, 2 / 6], path: DiamondPoints(-8, 4) },
{ origin: [2 / 2, 3 / 6], path: DiamondPoints(-4, 8) },
{ origin: [3 / 2, 5 / 6], path: DiamondPoints(-8, 4) },
{ origin: [4 / 2, 6 / 6], path: DiamondPoints(-4, 1) }
]
},
Grimms: {
x: 1,
num_sectors: 12,
board:
'<path d="M -1 0 A 1 1 90 0 0 1 0 A 1 1 90 0 0 -1 0" fill="rgb(230,175,130)" />',
board_fill: "rgb(230,175,130)",
piece_types: [
{ origin: [1 / 2, 7 / 24], path: DiamondPoints(-7, 5) },
{ origin: [2 / 2, 12 / 24], path: DiamondPoints(-5, 5) },
{ origin: [3 / 2, 17 / 24], path: DiamondPoints(-5, 3) },
{ origin: [4 / 2, 20 / 24], path: DiamondPoints(-3, 3) },
{ origin: [5 / 2, 23 / 24], path: DiamondPoints(-3, 1) },
{ origin: [6 / 2, 24 / 24], path: DiamondPoints(-1, 0) }
]
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof mandala = {
const mandala = svg`<svg width=${size} height=${size} transform="scale(0.5)"/>`;
yield mandala;

// board
const board = svg`<g transform="scale(${size / 2}) translate(1, 1) ">${
design.board
}</g>`;
mandala.append(board);

// pieces
for (const type_info of design.piece_types) {
for (const i of _.times(design.num_sectors)) {
// const piece = svg`<g><polygon points=${points} fill=${fill} /></g>`;
// await Promises.delay(20);
// mandala.append(piece);
}
}

// cursor
mandala.append(html`<style>
.mandala-piece:hover { cursor:grab; }
.mandala-piece:active { cursor:grabbing; }
</style>`);
}
Insert cell
Paths = ({
Heart: `M 140 20 C 73 20 20 74 20 140 C 20 275 156 310 248 443 C 336 311 477 270 477 140 C 477 74 423 20 357 20 C 309 20 267 48 248 89 C 229 48 188 20 140 20 Z`,
Anko: [
[8, 4],
[4, 8],
[8, 4],
[4, 1]
].map(DiamondPath)
})
Insert cell
// function Piece(type_info, i) {
// const { origin, path } = type_info;
// const points = _.map(path, (coords) => {
// const scaled_coords = V.mult(coords, 0.97);
// const [x, y] = V.add(origin, scaled_coords);
// return fromPolar((i + x) * sector_angle_size, y * inner_radius, center);
// });
// const fill = `hsl(${(i / num_sectors) * 360}deg,65%,50%)`;
// return `<g><polygon points=${points} fill=${fill} /></g>`;
// }
Insert cell
DiamondPath = ([tail, head]) =>
`M 0 0 M -${tail} 0 L 0 -0.5 L ${head} 0 L 0 0.5 L -${tail} 0`
Insert cell
Insert cell
center = [outer_radius, outer_radius]
Insert cell
num_sectors = design.num_sectors
Insert cell
outer_radius = size / 2
Insert cell
sector_angle_size = (2 * Math.PI) / num_sectors
Insert cell
fromPolar(1 * sector_angle_size, 1)
Insert cell
<svg width=${width} height=${height} transform="">${piece}
Insert cell
piece = svg`<g transform="
translate(${width / 2}, ${height / 2})
rotate(0)
scale(${width}, ${height * 0.5})
">
${Piece(Paths.Anko[0])}`
Insert cell
Piece = (path_data) => svg`<path d="${PiecePath(path_data)}">`
Insert cell
PiecePath = (path_data) =>
SVGPath.transformPath(path_data, {
translate: [],
rotate: [],
skew: [],
scale: [1 / 24, 1]
})
Insert cell
Piece(Paths.Anko[0])
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