Published
Edited
Feb 2, 2020
1 fork
10 stars
Insert cell
Insert cell
Insert cell
{
const duration = 3000, draw = render({duration});
// Render a gif.
if(gif) return yield* renderGif(invalidation, t => draw(t), duration, {
fps: 40, preview: draw(0),
filename: `six-seven-eight-${Date.now()/1000|0}`,
});
// Draw a live preview. We offset the date so that animations
// will always start at the same offset.
const to = Date.now();
while(true) yield draw(((Date.now() - to) / duration) % 1);
}
Insert cell
function render(options = {}) {
const {
size = 600,
stroke = 10,
strokeColor = 'hsl(30,40%,30%)',
fillColor = 'hsl(30,40%,98%)',
duration = 3000,
ease = easeInOut(3),
} = options;
const boxSize = 50 - stroke / 2 / (2**.5) - .5, boxOffset = 100;

const PI = Math.PI, PIQ = PI / 2;
const ctx = DOM.context2d(size, size, 1);
const illo = new Zdog.Illustration({
element: ctx.canvas,
zoom: 2,
rotate: { x: -PI/5.1 , y:-PI/4 },
});
// Force a pixel ratio of 1 and reset canvas size.
illo.pixelRatio = 1;
ctx.canvas.width = illo.width = size;
ctx.canvas.height = illo.height = size;
// Override Zdog inline styles to make canvas responsive.
ctx.canvas.style.maxWidth = '100%';
ctx.canvas.style.height = 'auto';
// Creates a set of two boxes (one for the outline, one for the fill).
function createCube([x, y, z]) {
const origin = new Zdog.Anchor({
translate: {x: x*boxOffset, y: y*boxOffset, z: z*boxOffset}});
// The groups are required because internally Zdog boxes are rect shapes.
new Zdog.Box({
addTo: new Zdog.Group({addTo: origin, updateSort: true}),
width: boxSize, height: boxSize, depth: boxSize,
stroke, fill: false, color: strokeColor,
});
new Zdog.Box({
addTo: new Zdog.Group({addTo: origin, updateSort: true}),
width: boxSize, height: boxSize, depth: boxSize,
color: fillColor,
});
return origin;
}
// Turns a list of translation offsets into a set of cubes.
function createCubeSet(offsets) {
const root = new Zdog.Anchor();
const boxes = offsets.map(createCube);
for(let b of boxes) root.addChild(b);
return {root, boxes};
}
// Our two box sets, each having a "root" element, a list of "boxes", and a rotation vector.
const sets = [
{
rotate: [0, -1, 0],
// 7 Boxes, 2 on opposite ends of each axis, one at center.
...createCubeSet([-1, 1].map(s => [0, 1, 2].map(o => [0, 1, 2].map(i => i === o ? s : 0))).flat().concat([[0,0,0]])),
},
{
rotate: [-1, 0, 0],
// 8 Boxes, each on a corner.
...createCubeSet([-.5, .5].map(x => [-.5, .5].map(y => [-.5, .5].map(z => [x, y, z]))).flat(2)),
},
];
// Container to which we'll attach "invisible" sets during animation.
const inactive = new Zdog.Anchor();
// Zdog clears the canvas on every draw. Instead of setting a rect in the background,
// we simply override our context's clearRect to instead perform a fill.
ctx.clearRect = function(x, y, w, h) {
this.fillStyle = fillColor;
this.fillRect(x, y, w, h);
};
// Main render callback.
return t => {
// Remove all sets from the scene.
for(let s of sets) inactive.addChild(s.root);
// Fetch the current cube set.
const {root, rotate} = sets[t * sets.length | 0];
// Reattach the current set to the scene.
illo.addChild(root);
// Rotate the cube set anchor.
const [x, y, z] = rotate, a = ease((t * sets.length) % 1) * PIQ;
root.rotate = { x: x * a, y: y * a, z: z * a };
illo.updateRenderGraph();
return illo.element;
}
}
Insert cell
function easeInOut(a) {
return t => t ** a / (t ** a + (1 - t) ** a);
}
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