Skip to content

Instantly share code, notes, and snippets.

@javilobo8
Created July 16, 2022 11:00
Show Gist options
  • Save javilobo8/9abf05f6e4601b3f8d7e8aad5a94987a to your computer and use it in GitHub Desktop.
Save javilobo8/9abf05f6e4601b3f8d7e8aad5a94987a to your computer and use it in GitHub Desktop.
import * as d3 from 'd3';
const bgRGB = d3.rgb('#040404');
const CANVAS_WIDTH = 1600;
const CANVAS_HEIGHT = 800;
const MIN_STAR_MAG = 6;
function getRetinaRatio(ctx) {
const devicePixelRatio = window.devicePixelRatio || 1;
const backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1;
return devicePixelRatio / backingStoreRatio;
}
function makeRadialGradient(ctx, x, y, r, color) {
const radialgradient = ctx.createRadialGradient(x, y, 0, x, y, r);
radialgradient.addColorStop(0.2, color);
radialgradient.addColorStop(0.5, `rgba(${bgRGB.r},${bgRGB.g},${bgRGB.b},0)`);
radialgradient.addColorStop(0.5, `rgba(${bgRGB.r},${bgRGB.g},${bgRGB.b},1)`);
radialgradient.addColorStop(1, `rgba(${bgRGB.r},${bgRGB.g},${bgRGB.b},0)`);
ctx.fillStyle = radialgradient;
}
const d3func = (canvas) => {
const INITIAL_PROJECTION_SCALE = 600;
const MAX_PROJECTION_SCALE = 2000;
const MIN_PROJECTION_SCALE = 100;
const projection = d3.geoStereographic()
.scale(INITIAL_PROJECTION_SCALE)
.translate([CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2]);
const fixedProjection = d3.geoStereographic()
.scale(INITIAL_PROJECTION_SCALE)
.rotate([0, 0])
.translate([CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2]);
const ctx = canvas.node().getContext('2d');
const ratio = getRetinaRatio(ctx);
const scaledWidth = CANVAS_WIDTH * ratio;
const scaledHeight = CANVAS_HEIGHT * ratio;
canvas.node().width = scaledWidth;
canvas.node().height = scaledHeight;
canvas
.style('width', CANVAS_WIDTH + 'px')
.style('height', CANVAS_HEIGHT + 'px');
ctx.scale(ratio, ratio);
const path = d3.geoPath()
.projection(projection)
.context(ctx);
const graticule = d3.geoGraticule()
.step([15, 15]);
d3.json('/skymap/star-data.json')
.then((data) => {
const geoConstellations = [];
const starsMag = [];
data = data.map((constellation) => {
constellation.stars = constellation.stars.filter((star) => {
if (star.mag < MIN_STAR_MAG) {
starsMag.push(star.mag);
}
return star.mag < MIN_STAR_MAG;
});
return constellation;
});
const minMaxMag = d3.extent(starsMag);
const opacityScale = d3.scaleLinear()
.domain(minMaxMag)
.range([1, 0.4]);
const magScale = d3.scaleLinear()
.domain(minMaxMag)
.range([2.7, 1.7]);
data.forEach((constellation) => {
let geometries = [];
constellation.stars.map((star) => {
var rgb = d3.rgb(star.color);
var rgba = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + opacityScale(star.mag) + ')';
geometries.push({
type: 'Point',
coordinates: [-star.ra, star.dec],
properties: {
color: rgba,
mag: magScale(star.mag)
}
});
});
const lines = constellation.lines.map((line) => ([
[-line.ra1, line.dec1],
[-line.ra2, line.dec2],
]));
geometries.push({
type: 'MultiLineString',
coordinates: lines,
});
// ??
if (constellation.name == 'Serpens') {
const bound1 = constellation.boundary[0].map((coords) => [-coords[0], coords[1]]);
const bound2 = constellation.boundary[1].map((coords) => [-coords[0], coords[1]]);
geometries.push({ type: 'LineString', coordinates: bound1 });
geometries.push({ type: 'LineString', coordinates: bound2 });
} else {
const boundLines = constellation.boundary.map((coords) => [-coords[0], coords[1]]);
geometries.push({ type: 'LineString', coordinates: boundLines });
}
geometries = {
type: 'GeometryCollection',
geometries: geometries,
};
geoConstellations.push({
type: 'Feature',
geometry: geometries,
properties: {
name: constellation.name,
zodiac: constellation.zodiac,
center: d3.geoCentroid(geometries),
}
});
});
draw(ctx, geoConstellations, [30, -70]);
let raStart;
let decStart;
const drag = d3.drag();
drag.on('start', (event) => {
raStart = projection.invert(d3.pointer(event))[0];
decStart = fixedProjection.invert(d3.pointer(event))[1];
});
drag.on('drag', (event) => {
const raFinish = projection.invert(d3.pointer(event))[0];
const decFinish = fixedProjection.invert(d3.pointer(event))[1];
const raRotate = raFinish - raStart;
const decRotate = decFinish - decStart;
const rotate = projection.rotate();
const newCenter = [
rotate[0] + raRotate,
rotate[1] + decRotate,
];
draw(ctx, geoConstellations, newCenter);
raStart = projection.invert(d3.pointer(event))[0];
decStart = fixedProjection.invert(d3.pointer(event))[1];
});
canvas.call(drag);
const zoom = d3.zoom();
zoom.on('zoom', (event) => {
const newScale = INITIAL_PROJECTION_SCALE * event.transform.k;
if (newScale >= MIN_PROJECTION_SCALE && newScale <= MAX_PROJECTION_SCALE) {
projection.scale(newScale);
draw(ctx, geoConstellations);
}
});
canvas.call(zoom);
});
function distance(p) {
const center = [CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2];
const xRotate = center[0] - p[0];
const yRotate = center[1] - p[1];
return Math.sqrt(Math.pow(xRotate, 2) + Math.pow(yRotate, 2));
}
function draw(ctx, constellations, center) {
let min = 0;
let minDistance = distance(projection(constellations[0].properties.center));
if (center) projection.rotate(center);
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// BG
ctx.fillStyle = bgRGB;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// graticule
ctx.strokeStyle = '#fff';
ctx.lineWidth = .1;
ctx.beginPath();
path(graticule());
ctx.stroke();
// Lines
ctx.lineWidth = .4;
ctx.beginPath();
path({ type: 'LineString', coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]] });
ctx.stroke();
ctx.strokeStyle = '#f2f237';
ctx.beginPath();
path({ type: 'LineString', coordinates: [[-180, 0], [-90, 23.26], [0, 0], [90, -23.26], [180, 0]] });
ctx.stroke();
constellations.forEach(function (constellation, i) {
var currentDistance = distance(projection(constellations[i].properties.center));
if (currentDistance < minDistance) {
min = i;
minDistance = currentDistance;
}
constellation.geometry.geometries.forEach(function (geo) {
switch (geo.type) {
case 'Point': { // Stars
makeRadialGradient(
ctx,
projection(geo.coordinates)[0],
projection(geo.coordinates)[1],
geo.properties.mag,
geo.properties.color
);
path.pointRadius([geo.properties.mag]);
ctx.beginPath();
path(geo);
ctx.fill();
break;
}
case 'LineString-disabled': {
ctx.strokeStyle = '#000';
ctx.beginPath();
path(geo);
ctx.stroke();
break;
}
case 'MultiLineString': {
// ctx.strokeStyle = (constellation.properties.zodiac) ? '#f2f237' : '#999';
ctx.strokeStyle = '#999';
ctx.beginPath();
path(geo);
ctx.stroke();
}
default:
}
});
});
ctx.strokeStyle = '#f00';
ctx.lineWidth = 1.2;
constellations[min].geometry.geometries.forEach(function (geo) {
if (geo.type == 'LineString') {
ctx.beginPath();
path(geo);
ctx.stroke();
}
});
ctx.fillStyle = '#fff';
ctx.textAlign = 'left';
ctx.font = '18px sans-serif';
ctx.fillText(constellations[min].properties.name, 5, 18);
}
};
export default d3func;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment