Public
Edited
Jan 29, 2023
Importers
Insert cell
Insert cell
scene = [
[
new DivPlane({
width: 1600,
height: 300,
transform: '',
color: 'linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(255,255,185,1) 100%)',
border: '1px solid black',
position: [0, 0, -800],
}),
new DivPlane({
width: 1600,
height: 300,
transform: '',
color: 'linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(255,255,185,1) 100%)',
border: '1px solid black',
position: [0, 0, 800],
}),
new DivPlane({
width: 1600,
height: 300,
transform: 'rotateY(90deg)',
color: 'linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(255,255,185,1) 100%)',
border: '1px solid black',
position: [-800, 0, 0],
}),
new DivPlane({
width: 1600,
height: 300,
transform: 'rotateY(90deg)',
color: 'linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(255,255,185,1) 100%)',
border: '1px solid black',
position: [800, 0, 0],
}),
new DivPlane({
width: 1600,
height: 1600,
transform: 'rotateX(90deg)',
color: 'rgba(255,255,255,1)',
border: '1px solid black',
position: [0, 150, 0],
}),
new DivPlane({
width: 1600,
height: 1600,
transform: 'rotateX(90deg)',
color: 'rgba(255,255,185,1)',
border: '1px solid black',
position: [0, -150, 0],
}),
],
// new DivCube({
// position: [10, 10, 10],
// side: 10,
// }),
// new DivCube({
// position: [50, 0, 0],
// side: 3000,
// }),
new PlaneLookingAtViewer({
position: [0, 0, 300],
width: 100,
height: 100,
}),
new StaticImage({
url: images.a,
height: 300,
position: [0, 0, 800],
transform: ''
}),
new StaticImage({
url: images.b,
position: [800, 0, 0],
height: 300,
transform: 'rotateY(90deg)',
}),
]
Insert cell
root = new Composer({scene, distanceToCoords: 100}).root
Insert cell
class DivPlane {
constructor(opts) {
this.root = document.createElement('div');
this.root.style.position = 'absolute';
this.root.style.width = opts.width + 'px';
this.root.style.height = opts.height + 'px';
this.root.style.background = opts.color;
this.root.style.border = opts.border || '';
this.root.style.boxSizing = 'border-box';
this.root.style.transformStyle = 'preserve-3d';
this.root.style.backfaceVisibility = 'visible';
this.transform = [
`translate(${-opts.width / 2}px, ${-opts.height / 2}px)`,
opts.transform || '',
].join(' ');
this.position = opts.position;
}
}
Insert cell
class Composer {
constructor({height, perspective, scene, ...opts}) {
this.root = document.createElement('div');
this.width = opts.width || width;
this.height = height || (window.isExported ? window.innerHeight - 1 : screen.height - 300);
this.root.style.overflow = 'hidden';
this.root.style.position = 'relative';
this.root.style.width = this.width + 'px';
this.root.style.height = this.height + 'px';
this.root.style.userSelect = 'none';
this.root.style.webkitUserDrag = 'none';
this.perspective = perspective ?? 500;
this.root.style.perspective = `${this.perspective}px`;
this.root.style.perspectiveOrigin = 'center center';
this.root.style.transformStyle = 'preserve-3d';
this.sceneRoot = document.createElement('div');
this.sceneRoot.style.transformStyle = 'preserve-3d';
this.sceneRoot.style.transformOrigin = 'center';
this.sceneRoot.style.backfaceVisibility = 'visible';
this.root.appendChild(this.sceneRoot);
this.objects = [];
this.controls = new Controls({
domObject: this.root,
step: 50,
coords: opts.coords,
angleX: opts.angleX,
angleY: opts.angleY,
distanceToCoords: opts.distanceToCoords,
onUpdate: () => {
this.update();
},
});
this.controls.mount();
invalidation.then(() => {
this.controls.unmount();
});
this.add(scene);
this.update();
}
setAnimation(duration) {
this.sceneRoot.style.transition = `transform ${duration}s ease-in-out`;
}
update() {
const [cx, cy, cz] = this.controls.getPosition();
const [rx, ry, rz] = [this.width / 2, 0, -this.perspective];
this.sceneRoot.style.transform = [
`translate3d(${this.width / 2}px, ${this.height / 2}px, 0px)`,
`translate3d(${-rx}px, ${-ry}px, ${-rz}px)`,
`rotateX(${this.controls.angleY}deg)`,
`rotateY(${-this.controls.angleX}deg)`,
`translate3d(${rx}px, ${ry}px, ${rz}px)`,
`translate3d(${cx}px, ${cy}px, ${cz + this.perspective}px)`,
].join(' ');
this.objects.forEach(obj => {
if (obj.update) obj.update();
obj.root.style.transform = [
`translate3d(${-obj.position[0]}px, ${-obj.position[1]}px, ${-obj.position[2]}px)`,
obj.transform || '',
].join(' ');
});
}
add(obj) {
if (Array.isArray(obj)) {
obj.forEach(o => this.add(o));
return;
}
this.sceneRoot.appendChild(obj.root);
obj.root.style.position = 'absolute';
obj.composer = this;
obj.controls = this.controls;
this.objects.push(obj);
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class StaticImage {
constructor(opts) {
this.opts = opts;
this.url = opts.url;
this.ratio = opts.ratio || 1;
this.height = opts.height;
this.position = opts.position;
this.root = document.createElement('div');
this.root.innerHTML = 'Loading...';
this.root.style.transformOrigin = 'top left';
this.img = new Image();
this.img.src = this.url;
this.img.style.userDrag = 'none';
this.img.style.webkitUserDrag = 'none';
this.img.onload = () => {
this.ratio = this.img.width / this.img.height;
this.onSizeChange();
this.root.innerHTML = '';
this.root.appendChild(this.img);
this.composer.update();
}
}
onSizeChange() {
this.width = this.ratio * this.height;
this.transform = [
this.opts.transform || '',
`translate3d(${-this.width / 2}px, ${-this.height / 2}px, 0)`,
`scale(${this.height / this.img.height})`,
].join(' ');
}
}
Insert cell
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