Created
July 16, 2022 11:00
-
-
Save javilobo8/9abf05f6e4601b3f8d7e8aad5a94987a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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