Public
Edited
Dec 14, 2021
2 stars
Insert cell
Insert cell
Insert cell
canvas = {
// SET UP ------------------------------------
// scene
const aspect = 0.58125;
//const width = 500
const height = width * aspect;
const scene = new THREE.Scene();

// camera
const camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000); // field of view, aspect ratio, near, far plane
camera.position.set(0, 0, 40); // camera is +40 in z direction
camera.lookAt(0, 0, 0); // camera is looking at origin

// renderer
const renderer = new THREE.WebGLRenderer({ antialias: true }); // most powerful all purpose renderer, with smooth edges
renderer.setClearColor("#f6f6f6"); // background color
renderer.setSize(width, height); // set window size
yield renderer.domElement;

// camera controls
const controls = new THREE.OrbitControls(camera, renderer.domElement); // allows mouse to move camera
controls.enableZoom = false; // disables zoom
controls.enablePan = false; // disables pan
controls.enableRotate = true; // determines if camera can rotate

// MATERIALS ---------------------------------
const material = new THREE.MeshLambertMaterial({
color: 0xcccccc,
side: THREE.DoubleSide,
transparent: true,
flatShading: true
});

// SHAPE ------------------------------------
const geometry = new THREE.TorusKnotGeometry(5, 2, 60, 6);
geometry.computeFlatVertexNormals(); // Needed to us material parameter {flatShading: true}
const shape = new THREE.Mesh(geometry, material.clone());
shape.material.color.setHex(0x9022dd);

let n = 50;
let goldenRatio = 1.61803398875;
let pi = Math.PI;
let radius = 13;
for (let i = 0; i < n; i++) {
let theta = (2 * pi * i) / goldenRatio;
let phi = Math.acos(1 - (2 * (i + 0.5)) / n);
const dodec = new THREE.Mesh(
new THREE.DodecahedronGeometry(1, 0),
material.clone()
);
dodec.material.color.setHex(0x66ee88);
dodec.position.set(
radius * (Math.cos(theta) * Math.sin(phi)),
radius * (Math.sin(theta) * Math.sin(phi)),
radius * Math.cos(phi)
);
scene.add(dodec);
}

// Light ---------------------------------------
const hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
hemiLight.color.setHSL(0.6, 0.6, 0.6);
hemiLight.groundColor.setHSL(0.095, 0.5, 0.75);
hemiLight.position.set(0, 50, 0);

const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.color.setHSL(0.1, 1, 0.95);
dirLight.position.set(-1, 1.75, 1);
dirLight.position.multiplyScalar(30);
scene.add(dirLight);

// SCENE --------------------------------------
scene.add(shape);
scene.add(hemiLight);
scene.add(dirLight);

// RAYCASTER-----------------------------------
const mouse = new THREE.Vector2(-0.5, 0.25); // starts mouse position off screen so no objects are highlighted at start
let intersected;
const raycaster = new THREE.Raycaster();

let x;
let y;
renderer.domElement.onmousemove = (event) => {
event.preventDefault();
console.log(event);
x = event.layerX;
y = event.layerY;
(mouse.x = (x / width) * 2 - 1), (mouse.y = -(y / height) * 2 + 1);
};

//////////////////////////////////////////////////////////////////////////////////////////////
// RENDER LOOP ///////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
let intersects;
let INTERSECTED;
let t = 0;

const render = function () {
requestAnimationFrame(render); // adjust scale on resize and stops animation when window is closed to save memory

// mouse actions
raycaster.setFromCamera(mouse, camera); //raycaster runs each animation frame
intersects = raycaster.intersectObjects(scene.children); // checks intersection

// highlight object
if (intersects.length > 0) {
if (INTERSECTED != intersects[0].object) {
if (INTERSECTED)
INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = intersects[0].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex(0x444400);
}
} else {
if (INTERSECTED)
INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = null;
}

// animation
t += 0.005;
shape.rotation.set(0, 0, t);

renderer.render(scene, camera); // renders scene
};

// Input -------------------------
window.addEventListener("click", () => {
INTERSECTED.material.color.setHex(Math.random() * 0xffffff);
});

render();
}
Insert cell
Insert cell
canvas2 = {
// SET UP ------------------------------------
// scene
const aspect = 0.58125;
//const width = 500
const height = width * aspect;
const scene = new THREE.Scene();

// camera
const camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000); // field of view, aspect ratio, near, far plane
camera.position.set(0, 0, 40); // camera is +40 in z direction
camera.lookAt(0, 0, 0); // camera is looking at origin

// renderer
const renderer = new THREE.WebGLRenderer({ antialias: true }); // most powerful all purpose renderer, with smooth edges
renderer.setClearColor("#f6f6f6"); // background color
renderer.setSize(width, height); // set window size
yield renderer.domElement;

// camera controls
const controls = new THREE.OrbitControls(camera, renderer.domElement); // allows mouse to move camera
controls.enableZoom = true;
false; // disables zoom
controls.enablePan = false; // disables pan
controls.enableRotate = true; // determines if camera can rotate
controls.autoRotate = true;
controls.autoRotateSpeed = 1;

// MATERIALS ---------------------------------
const material = new THREE.MeshLambertMaterial({
color: 0xcccccc,
side: THREE.DoubleSide,
transparent: true,
flatShading: true
});

// SHAPE ------------------------------------
const geometry = new THREE.TorusKnotGeometry(5, 2, 60, 6);
geometry.computeFlatVertexNormals(); // Needed to us material parameter {flatShading: true}
const shape = new THREE.Mesh(geometry, material.clone());
shape.material.color.setHex(0x9022dd);

//sprite
function makeLabelCanvas(baseWidth, size, name) {
const borderSize = 10;
const ctx = document.createElement("canvas").getContext("2d");
const font = `bold ${size}px calibri`;
ctx.font = font;
// measure how long the name will be
const textWidth = ctx.measureText(name).width;

const spriteWidth = baseWidth + borderSize * 2;
const spriteHeight = size + borderSize * 2;
ctx.canvas.width = spriteWidth;
ctx.canvas.height = spriteHeight;

// need to set font again after resizing canvas
ctx.font = font;
ctx.textBaseline = "middle";
ctx.textAlign = "center";

ctx.fillStyle = "#66ee88";
ctx.fillRect(0, 0, spriteWidth, spriteHeight);

// scale to fit but don't stretch
const scaleFactor = Math.min(1, baseWidth / textWidth);
ctx.translate(spriteWidth / 2, spriteHeight / 1.85);
ctx.scale(scaleFactor, 1);
ctx.fillStyle = "white";
ctx.fillText(name, 0, 0);

return ctx.canvas;
}

let n = 150;
let goldenRatio = 1.61803398875;
let pi = Math.PI;
let radius = 13;
for (let i = 0; i < n; i++) {
let theta = (2 * pi * i) / goldenRatio;
let phi = Math.acos(1 - (2 * (i + 0.5)) / n);

const canvas = makeLabelCanvas(
Math.max(1, Math.ceil(Math.log10(i) + 0.01)) * 50 + 50,
100,
i
);
const texture = new THREE.CanvasTexture(canvas);
texture.minFilter = THREE.LinearFilter;
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
const labelMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: true
});
const labelBaseScale = 0.01;
const label = new THREE.Sprite(labelMaterial);
label.position.set(
radius * (Math.cos(theta) * Math.sin(phi)),
radius * (Math.sin(theta) * Math.sin(phi)),
radius * Math.cos(phi) * 2
);
label.scale.x = canvas.width * labelBaseScale;
label.scale.y = canvas.height * labelBaseScale;
scene.add(label);

/*const sprite = new THREE.Sprite( new THREE.SpriteMaterial( { color: '#69f' } ) );
sprite.scale.set(2, 1, 1);
sprite.material.color.setHex(0x66ee88)
sprite.position.set(
radius*(Math.cos(theta) * Math.sin(phi)),
radius*(Math.sin(theta) * Math.sin(phi)),
radius*(Math.cos(phi))
)
scene.add(sprite)*/
}

// Light ---------------------------------------
const hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
hemiLight.color.setHSL(0.6, 0.6, 0.6);
hemiLight.groundColor.setHSL(0.095, 0.5, 0.75);
hemiLight.position.set(0, 50, 0);

const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.color.setHSL(0.1, 1, 0.95);
dirLight.position.set(-1, 1.75, 1);
dirLight.position.multiplyScalar(30);
scene.add(dirLight);

// SCENE --------------------------------------
const pickable = new THREE.Group();
scene.add(pickable.add(shape));
scene.add(hemiLight);
scene.add(dirLight);

// RAYCASTER-----------------------------------
const mouse = new THREE.Vector2(-0.5, 0.25); // starts mouse position off screen so no objects are highlighted at start
let intersected;
const raycaster = new THREE.Raycaster();

let x;
let y;
renderer.domElement.onmousemove = (event) => {
event.preventDefault();
console.log(event);
x = event.layerX;
y = event.layerY;
(mouse.x = (x / width) * 2 - 1), (mouse.y = -(y / height) * 2 + 1);
};

//////////////////////////////////////////////////////////////////////////////////////////////
// RENDER LOOP ///////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
let intersects;
let INTERSECTED;
let t = 0;

const render = function () {
requestAnimationFrame(render); // adjust scale on resize and stops animation when window is closed to save memory

// mouse actions
raycaster.setFromCamera(mouse, camera); //raycaster runs each animation frame
intersects = raycaster.intersectObjects(pickable.children); // checks intersection
// intersects = raycaster.intersectObjects(scene.children); // checks intersection

// highlight object
if (intersects.length > 0) {
if (INTERSECTED != intersects[0].object) {
if (INTERSECTED)
INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = intersects[0].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex(0x444400);
}
} else {
if (INTERSECTED)
INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = null;
}

// animation
t += 0.005;
shape.quaternion.copy(camera.quaternion);
controls.update();

renderer.render(scene, camera); // renders scene
};

// Input -------------------------
window.addEventListener("click", () => {
INTERSECTED.material.color.setHex(Math.random() * 0xffffff);
});

render();
}
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