Public
Edited
Oct 28, 2024
Insert cell
Insert cell
Insert cell
{
const renderer = new THREE.WebGLRenderer({antialias: true});
invalidation.then(() => renderer.dispose());
renderer.setSize(width, height);
renderer.setPixelRatio(devicePixelRatio);

const labelRender = new THREE.CSS2DRenderer()

renderer.render(scene, camera);
// set sphere position based on index

const labelRenderer = new THREE.CSS2DRenderer();

const html_render = html`<div style="position: relative; overflow: hidden;">
${renderer.domElement}
<div style="position: absolute; top: 0; left: 0; height: 100%; width: 100%;">
${labelRenderer.domElement}
</div>
</div>`
yield html_render;
}
Insert cell
scene = {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x214B5F);
scene.add(ground);
scene.add(curve);
scene.add(sphere);
scene.add(sphereOutline);
scene.add(heightLine);

// lighting
const ambientLight = new THREE.AmbientLight(0xffffff, .7); // Soft white ambient light
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1); // orange directional light
directionalLight.position.set(5, 10, 7.5); // Position it appropriately
directionalLight.castShadow = true; // Enable shadow casting
scene.add(directionalLight);


return scene;
}
Insert cell
sphere = {
const geometry = new THREE.SphereGeometry(.5, 32, 32);
const material = new THREE.MeshStandardMaterial({ color: 0xFFFFFF }); // Red color

const sphere = new THREE.Mesh(geometry, material);
sphere.position.copy(positions[time]);
return sphere
}
Insert cell
sphereOutline = {
const geometry = new THREE.SphereGeometry(3, 32, 32);
const material = new THREE.MeshStandardMaterial({ color: 0xFFFFFF, transparent: true, opacity: .2 });

const sphere = new THREE.Mesh(geometry, material);
sphere.position.copy(positions[time]);

// label
const moonDiv = document.createElement( 'div' );
moonDiv.className = 'threejs-label';
moonDiv.textContent = 'Moon';
moonDiv.style.backgroundColor = 'transparent';
moonDiv.style.color = '#FFF';

const birdLabel = new THREE.CSS2DObject( moonDiv );
birdLabel.position.set( 1, 0, 0 );
// birdLabel.center.set( 0, 1 );
sphere.add( birdLabel );
birdLabel.layers.set( 0 );
return sphere
}
Insert cell
heightLine = {
const material = new THREE.LineDashedMaterial({
color: 0xFFFFFF,
dashSize: .3,
gapSize: .3,
scale: 1,
});

const p2 = positions[time]
const p1 = new THREE.Vector3(positions[time].x, 0, positions[time].z)

const points = [p1,p2];

const geometry = new THREE.BufferGeometry().setFromPoints( points );

const line = new THREE.Line( geometry, material );
line.computeLineDistances();

return line;
}
Insert cell
ground = {
// Create a plane geometry for the ground
const planeGeometry = new THREE.PlaneGeometry(200, 100); // Width and height of the plane
const planeMaterial = new THREE.MeshPhongMaterial({
color: 0xFFFFFF,
side: THREE.DoubleSide, // Make the plane visible from both sides
transparent: true,
opacity: .1
});

// Create the plane mesh
const ground = new THREE.Mesh(planeGeometry, planeMaterial);

// Rotate the plane to be horizontal (the default is vertical)
ground.rotation.x = -Math.PI / 2; // Rotate 90 degrees around the x-axis

// Position the plane at y = 0
ground.position.set(0, 0, 0); // Center it in the scene

return ground
}
Insert cell
curve = {// Convert positions to THREE.Vector3 points for the curve
// Create the Catmull-Rom spline curve
const curve = new THREE.CatmullRomCurve3(positions);
curve.curveType = 'catmullrom';
curve.closed = false; // Keep the curve open

// Generate points along the curve and create a geometry
const curvePoints = curve.getPoints(500); // More points for a smoother curve

// Create a tube geometry along the curve
const tubeRadius = 0.1; // Adjust the radius for thickness
const tubularSegments = 250; // Number of segments around the tube
const radialSegments = 36; // Number of segments along the tube

const tubeGeometry = new THREE.TubeGeometry(curve, tubularSegments, tubeRadius, radialSegments, false);

// Define color array and assign colors per vertex
const colors = [];
const color1 = new THREE.Color(0x7ae7c7);
const color2 = new THREE.Color(0xEEB868);
const color3 = new THREE.Color(0xEF767A)

for (let i = 0; i < tubeGeometry.attributes.position.count; i++) {
const t = i / tubeGeometry.attributes.position.count; // Value from 0 to 1 along the tube length
// color1.clone().lerp(color2, t); // Interpolate between color1 and color2
const color = t <= .25 ? color1 : t <= .5 ? color2 : color3

// Push color values for each vertex
colors.push(color.r, color.g, color.b);
}

// Add color attribute to the geometry
tubeGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));

// Create a material that uses vertex colors
const tubeMaterial = new THREE.MeshStandardMaterial({
vertexColors: true,
side: THREE.DoubleSide,
transparent: true,
opacity: .5
});
return new THREE.Mesh(tubeGeometry, tubeMaterial);

}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
positions = {

// Generate spiral points
const pointsArray = [];

for (let i = 0; i < numPoints; i++) {
const [x,y,z] = positionFromIndex(i);

pointsArray.push(new THREE.Vector3(x, y, z));
}
return pointsArray
}
Insert cell
function positionFromIndex(i) {
const angle = i * (2 * Math.PI) / (numPoints / turns); // angle per point
const radius = spiralRadius * (i / numPoints); // radius grows with i
const x = radius * Math.cos(angle);
const y = heightPerTurn * i; // gradually increase y
const z = radius * Math.sin(angle);

return [x, y, z]
}
Insert cell
Insert cell
camera = {
const fov = 70;
const aspect = width / height;
const near = .1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 30, 65); // Move back and slightly above to see the full spiral
camera.lookAt(0, 25, 0); // Point towards the center of the spiral
return camera;
}
Insert cell
Insert cell
height = 700
Insert cell
Insert cell
THREE = require("three@0.130.0/build/three.min.js")
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more