Published
Edited
Feb 3, 2020
13 stars
Insert cell
Insert cell
Insert cell
{
const duration = 5000, draw = render({duration});
if(gif) return yield* renderGif(invalidation, t => draw(t), duration, {
fps: 40, preview: draw(0),
filename: `seven-eight-fourteen-${Date.now()/1000|0}`,
});
const to = Date.now();
while(true) yield draw(((Date.now() - to) / duration) % 1);
}
Insert cell
function render(options = {}) {
const {
size = 600,
outerStroke = 15,
innerStroke = 5,
fillColor = 'hsl(30,40%,98%)',
outerColor = 'hsl(30,40%,80%)',
innerColor = 'hsl(30,40%,30%)',
duration = 5000,
ease = easeInOut(2),
} = options;
const boxSize = 50, 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: size/300,
rotate: { x: -PI/5.1 , y:-PI/4 },
});
illo.pixelRatio = 1;
ctx.canvas.width = illo.width = size;
ctx.canvas.height = illo.height = size;
ctx.canvas.style.maxWidth = '100%';
ctx.canvas.style.height = 'auto';
function createCube([x, y, z]) {
const origin = new Zdog.Anchor({translate: {x: x*boxOffset, y: y*boxOffset, z: z*boxOffset}});
const box = new Zdog.Box({
addTo: new Zdog.Group({addTo: origin, updateSort: true}),
width: boxSize, height: boxSize, depth: boxSize, fill: false
});
return {origin, box};
}

function createCubeSet(offsets) {
const root = new Zdog.Anchor();
const boxes = offsets.map(createCube);
for(let b of boxes) root.addChild(b.origin);
return {root, boxes};
}
const axesOffsets = offsets => offsets.map(s => [0, 1, 2].map(o => [0, 1, 2].map(i => i === o ? s : 0))).flat();
const cornerOffsets = offsets => offsets.map(x => [-.5, .5].map(y => [-.5, .5].map(z => [x, y, z]))).flat(2);
const sets = [
{
rotate: [-1, 0, 0],
...createCubeSet(axesOffsets([-1, 1]).concat([[0,0,0]])),
},
{
rotate: [0, 0, 1],
...createCubeSet(cornerOffsets([-.5, .5])),
},
{
rotate: [0, -1, 0],
...createCubeSet(cornerOffsets([-.5, .5]).concat(axesOffsets([-.5, .5]))),
},
];
const inactive = new Zdog.Anchor();
const noop = () => {}, clear = function(x, y, w, h) {
this.fillStyle = fillColor;
this.fillRect(x, y, w, h);
};
return t => {
for(let s of sets) inactive.addChild(s.root);
const {root, rotate, boxes} = sets[t * sets.length | 0];
illo.addChild(root);
const [x, y, z] = rotate, a = ease((t * sets.length) % 1) * PIQ;
root.rotate = { x: x * a, y: y * a, z: z * a };

for(let {box} of boxes) box.color = outerColor, box.stroke = outerStroke;
ctx.clearRect = clear; illo.updateRenderGraph();
for(let {box} of boxes) box.color = innerColor, box.stroke = innerStroke;
ctx.clearRect = noop; 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