Published
Edited
Dec 24, 2021
Insert cell
Insert cell
Insert cell
chart = {
const scene = new T.Scene()
scene.background = new T.Color('#081f2b')

// let there be light
scene.add(new T.AmbientLight('#ffffff'))

// stars
for (const p of points.values())
scene.add(p)

// planets
for (const mesh of meshes.values())
scene.add(mesh)

while (true) {
controls.update()
renderer.render(scene, camera)
yield
}
}
Insert cell
update = {
for (const {name, dRA, dDec} of getAllPlanets(t)) {
meshes.get(name).position.setFromSphericalCoords(100, Math.PI / 2 - dDec, dRA)
// meshes.get(name).geometry = ... should update for new magnitude
}
svgContainer.replaceChildren(svgOverlay(t));
return t
}
Insert cell
ephemeris = require('https://bundle.run/ephemeris@2.0.0')
Insert cell
herravad = [56.0881, 13.2322, 0]
Insert cell
getAllPlanets = {
const cache = new Map()
return (t) => {
if (cache.has(t)) return cache.get(t)
const planets = Object.values(ephemeris.getAllPlanets(t, ...herravad).observed)
.filter(d => ['mercury','venus','mars','jupiter'].includes(d.name))
.map(({ name, raw }) => ({
name: name,
magnitude: raw.magnitude,
dRA: raw.position.apparent.dRA,
dDec: raw.position.apparent.dDec
}))
cache.set(t, planets)
return planets
}
}
Insert cell
times = d3.timeHour.every(12).range(new Date('1576-01-01'), new Date('1576-12-31'))
Insert cell
import { stars } from '@visnup/yale-bright-star-catalog'
Insert cell
radius = d3.scaleLinear([6, -2], [0, 2])
Insert cell
T = {
const THREE = window.THREE = await require('three@0.124')
await require('three/examples/js/controls/OrbitControls.js').catch(() => {})
return THREE
}
Insert cell
meshes = {
const meshes = new Map(getAllPlanets(times[0]).map(({name, magnitude}) => [
name, new T.Mesh(new T.SphereBufferGeometry(radius(magnitude)/3, 8, 8), colors[name] || white)
]))
invalidation.then(() => { for (const {geometry} of meshes.values()) geometry.dispose() })
return meshes
}
Insert cell
points = {
const points = new Map()
for (const {mag, sra0, sdec0} of stars) {
const r = Math.round(radius(mag) * 10) / 10
if (r > 0) {
const p = points.get(r) || new T.Points(new T.Geometry(), new T.PointsMaterial({ size: r }))
p.geometry.vertices.push(new T.Vector3().setFromSphericalCoords(100, Math.PI / 2 - sdec0, sra0))
points.set(r, p)
}
}
invalidation.then(() => {
for (const {geometry, material} of points.values()) {
geometry.dispose()
material.dispose()
}
})
return points
}
Insert cell
white = {
const white = new T.MeshBasicMaterial({ color: 0xffffff })
invalidation.then(() => white.dispose())
return white
}
Insert cell
colors = {
const colors = Object.fromEntries([
['mercury', 0x9C999A],
['venus', 0xE9DFD4],
['mars', 0xDD8665],
['jupiter', 0xC5BBAE],
['saturn', 0xB7A177],
['uranus', 0xD7F4F7],
['neptune', 0x7A92B7],
].map(([name, color]) => [name, new T.MeshBasicMaterial({ color })]))
invalidation.then(() => {
for (const material of Object.values(colors))
material.dispose()
})
return colors
}
Insert cell
controls = {
const controls = new T.OrbitControls(camera, renderer.domElement)
controls.enablePan = false
controls.enableZoom = true
controls.minDistance = 20
controls.maxDistance = 200
controls.rotateSpeed = -1
return controls
}
Insert cell
camera = {
const camera = new T.PerspectiveCamera(75, width / height, 1, 1000)
camera.position.set(75, 48, -44)
camera.updateProjectionMatrix()
return camera
}
Insert cell
renderer = {
const renderer = new T.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
return renderer
}
Insert cell
height = width <= 480 ? width * 1.5 : width / 1.5
Insert cell
svgContainer = html`<div style="position:absolute; pointer-events:none"></div>`
Insert cell
function svgOverlay(t) {
const svgnames = getAllPlanets(t).map(({name, dDec, dRA}) => {
const planetAttrs = {fill:'white','dominant-baseline':'hanging'};
const planetName = svgt`<text x=${0} y=${0} ${planetAttrs}>&nbsp;${name}</text>`
return translate(xyzcoords({dDec,dRA}), cull(planetName));
});
const pathAttrs= {stroke:'white',fill:'none','stroke-dasharray':'8 3'};
const svgline = svgt`<polyline points="${path('mars',t)}" ${pathAttrs}/>`
const view = camera.matrixWorldInverse.elements;
const project = camera.projectionMatrix.elements;
return svgtBox(transform(project,view,g(svgline,...svgnames)), width,height);
}
Insert cell
function xyzcoords({dDec,dRA}) {
const [z,x,y] = v.cartesian3([],[100,dDec,dRA]);
return [x,y,z];
}

Insert cell
function path(planetName,t) {
const ret = [];
for (const ix in times) {
if (times[ix]>t)
break;
if (ix%4)
continue;
const planet = getAllPlanets(times[ix]).find(({name})=>name===planetName);
ret.push(xyzcoords(planet));
}
return ret;
}

Insert cell
Insert cell
import { Scrubber } from '@mbostock/scrubber'
Insert cell
// import { nominative } from '@mbostock/star-map'
Insert cell
d3 = require('d3-scale@3', 'd3-time@2')
Insert cell
import { svgt, translate, g, transform, svgtBox, rotateY, cull } from '@sanderevers/svg-transformations'
Insert cell
m = require('https://bundle.run/@thi.ng/matrices@0.6.34')
Insert cell
v = require('https://bundle.run/@thi.ng/vectors@4.4.0')
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