Published
Edited
May 16, 2021
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
fieldOfViewDeg = 4
Insert cell
imageSize = viewSize
Insert cell
imageURL = {
const RA = 180 * catalog["RA0"] / Math.PI;
const DEC = 180 * catalog["DEC0"] / Math.PI;
const pixscale = 3600 * fieldOfViewDeg / imageSize;
const url = new URL("https://www.legacysurvey.org/viewer/jpeg-cutout");
url.searchParams.set("ra", Number(RA).toFixed(3));
url.searchParams.set("dec", Number(DEC).toFixed(3));
url.searchParams.set("layer", "unwise-neo6");
url.searchParams.set("pixscale", Number(pixscale).toFixed(5));
url.searchParams.set("size", imageSize);
return url;
}
Insert cell
Insert cell
// blocked by CORS policy
//imageData = await fetch(imageURL)
Insert cell
md`Use the version hosted on github instead of a FileAttachment`
Insert cell
bgImageURL = "https://dkirkby.github.io/desi3d/img/rosette15-unwise-neo6.jpg"
//bgImageURL = await FileAttachment("rosette15-unwise-neo6.jpg").url()
Insert cell
Insert cell
viewSize = Math.min(width, 800)
Insert cell
Insert cell
maxOrbitAngleDeg = 15;
Insert cell
camera = {
const fov = 25;
const aspect = 1;
const near = 25;
const far = 8000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0,0,0);
camera.lookAt(new THREE.Vector3(0,0,10000));
return camera;
}
Insert cell
sourceColor = ({
BGS: "0xffff00",
LRG: "0xff0000",
ELG: "0x00ff00",
QSO: "0x00ffff",
LYA: "0xddffff"})
Insert cell
visibleTypes = Object.keys(sourceColor)
Insert cell
Insert cell
textureURLs = {
const numTextures = 4;
const base = "https://dkirkby.github.io/desi3d/img/";
return Object.fromEntries(Types.map(ttype => [
ttype,
Array.from({length:numTextures}, (_,i) => base + ttype + i + ".png")
]));
}
Insert cell
textures = {
const loader = new THREE.TextureLoader();
const load = url => new Promise(async resolve => loader.load(url, resolve));
const entries = Object.entries(textureURLs).map(async ([key, urls]) => {
const textures = await Promise.all(urls.map(load));
return [key, textures];
});
return Object.fromEntries(await Promise.all(entries));
}
Insert cell
class SceneObj {
constructor(name, coords, minRedshift, maxRedshift) {
// Find the indices passing the redshift cuts, assuming that the input coords are already sorted.
const last = coords["z"].filter(z => z <= maxRedshift).length;
const first = coords["z"].slice(0,last).filter(z => z <= minRedshift).length;
this.unitX = Float32Array.from(coords["phi"].slice(first,last), phi => Math.cos(phi));
this.unitY = Float32Array.from(coords["phi"].slice(first,last), phi => Math.sin(phi));
this.theta = Float32Array.from(coords["theta"].slice(first,last), mrad => 1e-3*mrad);
this.redshift = Float32Array.from(coords["z"].slice(first,last));
this.count = this.redshift.length;
this.obj = new THREE.Group();
this.obj.name = name;
}
needsUpdate() {
for(const child of this.obj.children) {
child.geometry.attributes.position.needsUpdate = true;
}
}
}
Insert cell
RNG = new RandomState('desi3d');
Insert cell
class SceneGalaxy extends SceneObj {
constructor(name, coords, minRedshift, maxRedshift, size) {
super(name, coords, minRedshift, maxRedshift);
const nChunks = textures[name].length;
this.chunks = arange(nChunks + 1, {dtype:Int32Array}).map(n => Math.round(n*this.count/nChunks));
this.shuffle = RNG.shuffle(arange(this.count, {dtype:Int32Array}));
this.xyzs = [];
for(let j=0; j<nChunks; j++) {
const [lo,hi] = this.chunks.slice(j,j+2);
const material = new THREE.PointsMaterial({
size: size, map: textures[name][j], color: Number.parseInt(sourceColor[name]),
alphaTest:0.5, transparent: true});
const xyz = zeros(3*(hi-lo), {dtype:Float32Array});
this.xyzs.push(xyz);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(xyz, 3));
this.obj.add(new THREE.Points(geometry, material));
}
}
updateComoving(distanceGrid, transverseGrid) {
const z = interp(this.redshift, redshiftGrid, distanceGrid);
const zt = interp(this.redshift, redshiftGrid, transverseGrid);
for(let j=0; j<this.chunks.length-1; j++) {
const [lo,hi] = this.chunks.slice(j,j+2);
for(let i=lo; i<hi; i++) {
const k = this.shuffle[i];
const rperp = zt[k] * this.theta[k];
const xyz = this.xyzs[j];
const base=3*(i-lo);
xyz[base + 0] = rperp * this.unitX[k];
xyz[base + 1] = rperp * this.unitY[k];
xyz[base + 2] = z[k];
}
}
this.needsUpdate();
}
}
Insert cell
class SceneLineOfSight extends SceneObj {
constructor(name, coords, minRedshift, maxRedshift) {
super(name, coords, minRedshift, maxRedshift);
this.minRedshift = minRedshift;
const material = new THREE.LineBasicMaterial({color: Number.parseInt(sourceColor[name]), linewidth: 1});
this.xyz = zeros(6 * this.count, {dtype:Float32Array});
this.geometry = new THREE.BufferGeometry();
this.geometry.setAttribute('position', new THREE.BufferAttribute(this.xyz, 3));
this.obj.add(new THREE.LineSegments(this.geometry, material));
}
updateComoving(distanceGrid, transverseGrid) {
const z1 = interp(this.minRedshift, redshiftGrid, distanceGrid);
const z2 = interp(this.redshift, redshiftGrid, distanceGrid);
const zt1 = interp(this.minRedshift, redshiftGrid, transverseGrid);
const zt2 = interp(this.redshift, redshiftGrid, transverseGrid);
for(let i=0; i < this.count; i++) {
const rperp1 = zt1 * this.theta[i];
const rperp2 = zt2[i] * this.theta[i];
this.xyz[6*i + 0] = rperp1 * this.unitX[i];
this.xyz[6*i + 1] = rperp1 * this.unitY[i];
this.xyz[6*i + 2] = z1;
this.xyz[6*i + 3] = rperp2 * this.unitX[i];
this.xyz[6*i + 4] = rperp2 * this.unitY[i];
this.xyz[6*i + 5] = z2[i];
}
this.needsUpdate();
}
}
Insert cell
SceneObjects = ({
BGS: new SceneGalaxy("BGS", catalog["BGS"], 0.1, 0.5, 2),
LRG: new SceneGalaxy("LRG", catalog["LRG"], 0.3, 1.4, 6),
ELG: new SceneGalaxy("ELG", catalog["ELG"], 0.4, 1.9, 6),
QSO: new SceneGalaxy("QSO", catalog["QSO"], 0.9, 5.0, 6),
LYA: new SceneLineOfSight("LYA", catalog["QSO"], 2.1, 5.0)
})
Insert cell
SceneObjects["BGS"].count + SceneObjects["LRG"].count + SceneObjects["ELG"].count + SceneObjects["QSO"].count
Insert cell
createRings = function(distances, radius, nSegments=48, color=0xffffff) {
const nRings = distances.length;
const xyz = zeros(6 * nSegments * nRings, {dtype:Float32Array});
const dPhi = 2 * Math.PI / nSegments;
let [x1, y1, x2, y2] = [0, 0, radius, 0];
for(let i=0; i<nSegments; i++) {
[x1, y1] = [x2, y2];
const phi = dPhi * (i+1);
x2 = radius * Math.cos(phi);
y2 = radius * Math.sin(phi);
for(let j=0; j<nRings; j++) {
const z = distances[j];
const base = 6 * (nSegments * j + i);
xyz[base + 0] = x1;
xyz[base + 1] = y1;
xyz[base + 2] = z;
xyz[base + 3] = x2;
xyz[base + 4] = y2;
xyz[base + 5] = z;
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(xyz, 3));
const material = new THREE.LineBasicMaterial({color: color, linewidth: 1});
const rings = new THREE.LineSegments(geometry, material);
rings.name = "rings";
return rings;
}
Insert cell
scene = {
const scene = new THREE.Scene();
//scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.FogExp2(0x000000, 0.001);
for(const T of visibleTypes) scene.add(SceneObjects[T].obj);
scene.add(createRings(linspace(600, 6000, 19), 150));
return scene;
}
Insert cell
toggled = {
universe.updateVisibility(showTypes);
return true;
}
Insert cell
animated = {
universe.updateView(cameraDistance);
return true;
}
Insert cell
dynamic = {
universe.updateCosmology(cosmologyModel);
return true;
}
Insert cell
Insert cell
Insert cell
H0 = Planck15.H0.value
Insert cell
Orad0 = Planck15.Ogamma0 + Planck15.Onu0
Insert cell
cosmologyModel = new FluidsModel(Orad0, Om0, Ode0, H0)
Insert cell
cameraDistance = cosmologyModel.comoving_distance(0, cameraRedshift)
Insert cell
universeAge = cosmologyModel.age
Insert cell
universeSize = cosmologyModel.horizon
Insert cell
Insert cell
initialModel = Planck15
Insert cell
Om0Default=Planck15.Om0
Insert cell
Ode0Default=Planck15.Ode0
Insert cell
md`Build (redshift,distance) lookup tables for interpolating comoving coordinates`
Insert cell
Insert cell
redshiftGrid = linspace(0, redshiftMax+0.1, nGrid)
Insert cell
applyCosmology = function(cosmo) {
const distanceGrid = cosmo.gridz(redshiftGrid, "comoving_distance");
const transverseGrid = distanceGrid.map(d => cosmo.Sk(d));
for(const T of visibleTypes) SceneObjects[T].updateComoving(distanceGrid, transverseGrid);
}
Insert cell
Insert cell
Insert cell
catalog = JSON.parse(
await blob2text(
await png2blob("https://dkirkby.github.io/desi3d/img/aleatoire.png")))
Insert cell
Types = catalog["ttypes"]
Insert cell
counts = Types.map(T => catalog[T]["z"].length).reduce((a, b) => a + b, 0)
Insert cell
Insert cell
redshiftMax = Math.max(...Types.map(T => Math.max(...catalog[T]["z"])))
Insert cell
// Copied from @mootari/data-images to avoid a separate js fetch at page load
async function png2blob(src, {channels = 3} = {}) {
const img = DOM.element('img', {crossorigin: 'anonymous', src});
await img.decode();
const {naturalWidth: width, naturalHeight: height} = img;
const ctx = DOM.element('canvas', {width, height}).getContext('2d');
ctx.drawImage(img, 0, 0);
return decode(ctx.getImageData(0, 0, width, height), {channels});
}
Insert cell
// Copied from @mootari/data-images to avoid a separate js fetch at page load
function decode(imgData, {channels = 4, eofMarker = EOF} = {}) {
const d = imgData.data;
const arr = new Uint8Array(d.length / 4 * channels);
for(let i = 0, j = 0; i < d.length; (i += 4), (j += 4 - channels)) {
arr.set(d.slice(i, i + channels), i - j);
}

let len = arr.length;
while(len > 0 && arr[len-1] !== eofMarker) len--;
return new Blob([arr.slice(0, len-1)]);
}
Insert cell
// Copied from @mootari/data-images to avoid a separate js fetch at page load
async function blob2text(blob) {
return new Response(blob).text();
}
Insert cell
EOF = 123
Insert cell
Insert cell
Insert cell
Insert cell
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