UNPKG

r3f-globe

Version:

React Three Fiber component for Globe Data Visualization

312 lines (269 loc) 10.1 kB
import R3fGlobe from 'https://esm.sh/r3f-globe?external=three,react'; import { createElement, useMemo, useState, useEffect, useCallback } from 'react'; import * as THREE from 'three'; import { csvParse, scaleSequentialSqrt, interpolateYlOrRd } from 'https://esm.sh/d3'; import * as topojson from 'https://esm.sh/topojson-client'; export const Points = ptProps => { const N = 300; const gData = useMemo(() => [...Array(N).keys()].map(() => ({ lat: (Math.random() - 0.5) * 180, lng: (Math.random() - 0.5) * 360, size: Math.random() / 3, color: ['red', 'white', 'blue', 'green'][Math.round(Math.random() * 3)] })), [N]); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg', bumpImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-topology.png', pointsData: gData, pointAltitude: 'size', pointColor: 'color' }); } export const Arcs = ptProps => { const N = 20; const gData = useMemo(() => [...Array(N).keys()].map(() => ({ startLat: (Math.random() - 0.5) * 180, startLng: (Math.random() - 0.5) * 360, endLat: (Math.random() - 0.5) * 180, endLng: (Math.random() - 0.5) * 360, color: ['red', 'white', 'blue', 'green'][Math.round(Math.random() * 3)] })), [N]); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg', arcsData: gData, arcColor: 'color', arcDashLength: 0.4, arcDashGap: 4, arcDashInitialGap: () => Math.random() * 5, arcDashAnimateTime: 1000 }); } export const Paths = ptProps => { const N_PATHS = 10; const MAX_POINTS_PER_LINE = 10000; const MAX_STEP_DEG = 1; const MAX_STEP_ALT = 0.01; const gData = useMemo(() => [...Array(N_PATHS).keys()].map(() => { let lat = (Math.random() - 0.5) * 90; let lng = (Math.random() - 0.5) * 360; let alt = 0; return [[lat, lng, alt], ...[...Array(Math.round(Math.random() * MAX_POINTS_PER_LINE)).keys()].map(() => { lat += (Math.random() * 2 - 1) * MAX_STEP_DEG; lng += (Math.random() * 2 - 1) * MAX_STEP_DEG; alt += (Math.random() * 2 - 1) * MAX_STEP_ALT; alt = Math.max(0, alt); return [lat, lng, alt]; })]; }), []); return createElement(R3fGlobe, { ...ptProps, animateIn: false, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-dark.jpg', bumpImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-topology.png', pathsData: gData, pathPointAlt: pnt => pnt[2], pathColor: () => ['rgba(0,0,255,0.8)', 'rgba(255,0,0,0.8)'], pathDashLength: 0.01, pathDashGap: 0.004, pathDashAnimateTime: 100000 }); } export const CountryPolygons = ptProps => { const [polygons, setPolygons] = useState([]); useEffect(() => { fetch('./data/ne_110m_admin_0_countries.geojson') .then(r =>r.json()) .then(countries => setPolygons(countries.features.filter(d => d.properties.ISO_A2 !== 'AQ'))); }, []); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-dark.jpg', polygonsData: polygons, polygonCapColor: () => 'rgba(200, 0, 0, 0.7)', polygonSideColor: () => 'rgba(0, 200, 0, 0.1)', polygonStrokeColor: () => '#111' }); } export const Hexbin = ptProps => { const N = 6000; const gData = useMemo(() => [...Array(N).keys()].map(() => ({ lat: (Math.random() - 0.5) * 180 * 0.9, lng: (Math.random() - 0.5) * 360 / 1 })), [N]); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg', bumpImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-topology.png', hexBinPointsData: gData, hexBinPointWeight: 3, hexBinResolution: 2, hexMargin: 0.2, hexTopColor: () => 'red', hexSideColor: () => 'rgba(0,255,0,0.8)', hexBinMerge: true }); } export const HexedPolygons = ptProps => { const [polygons, setPolygons] = useState([]); useEffect(() => { fetch('./data/ne_110m_admin_0_countries.geojson') .then(r =>r.json()) .then(countries => setPolygons(countries.features)); }, []); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-dark.jpg', hexPolygonsData: polygons, hexPolygonResolution: 3, hexPolygonMargin: 0.3, hexPolygonUseDots: true, hexPolygonColor: () => `#${Math.round(Math.random() * Math.pow(2, 24)).toString(16).padStart(6, '0')}` }); } export const Heatmap = ptProps => { const N = 900; const gData = useMemo(() => [...Array(N).keys()].map(() => ({ lat: (Math.random() - 0.5) * 160, lng: (Math.random() - 0.5) * 360, weight: Math.random() })), [N]); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-dark.jpg', heatmapsData:[gData], heatmapPointLat: 'lat', heatmapPointLng: 'lng', heatmapPointWeight: 'weight', heatmapTopAltitude: 0.7 }); } export const Population = ptProps => { const [popData, setPopData] = useState([]); useEffect(() => { // load data fetch('./data/world_population.csv').then(res => res.text()) .then(csv => csvParse(csv, ({ lat, lng, pop }) => ({ lat: +lat, lng: +lng, pop: +pop }))) .then(setPopData); }, []); const weightColor = scaleSequentialSqrt(interpolateYlOrRd) .domain([0, 1e7]); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg', bumpImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-topology.png', backgroundImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/night-sky.png', hexBinPointsData: popData, hexBinPointWeight: 'pop', hexAltitude: d => d.sumWeight * 6e-8, hexBinResolution: 4, hexTopColor: d => weightColor(d.sumWeight), hexSideColor: d => weightColor(d.sumWeight), hexBinMerge: true }); } export const Hollow = ptProps => { const [landPolygons, setLandPolygons] = useState([]); useEffect(() => { // load data fetch('//unpkg.com/world-atlas/land-110m.json').then(res => res.json()) .then(landTopo => { setLandPolygons(topojson.feature(landTopo, landTopo.objects.land).features); }); }, []); const polygonsMaterial = useMemo(() => new THREE.MeshLambertMaterial({ color: 'darkslategrey', side: THREE.DoubleSide }), []); return createElement(R3fGlobe, { ...ptProps, backgroundColor: 'rgba(0,0,0,0)', showGlobe: false, showAtmosphere: false, polygonsData: landPolygons, polygonCapMaterial: polygonsMaterial, polygonSideColor: () => 'rgba(0, 0, 0, 0)' }); } export const Moon = ptProps => { const labelsTopOrientation = useMemo(() => new Set(['Apollo 12', 'Luna 2', 'Luna 20', 'Luna 21', 'Luna 24', 'LCROSS Probe'])); // avoid label collisions const [landingSites, setLandingSites] = useState([]); useEffect(() => { fetch('./data/moon_landings.json') .then(r =>r.json()) .then(setLandingSites); }, []); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: './imgs/lunar_surface.jpg', bumpImageUrl: './imgs/lunar_bumpmap.jpg', showGraticules: true, labelsData: landingSites, labelText: 'label', labelSize: 1.7, labelDotRadius: 0.4, labelDotOrientation: d => labelsTopOrientation.has(d.label) ? 'top' : 'bottom', labelColor: () => 'red' }); } export const Tiles = ptProps => { const TILE_MARGIN = 0.35; // degrees // Gen random data const GRID_SIZE = useMemo(() => [60, 20], []); const COLORS = useMemo(() => ['red', 'green', 'yellow', 'blue', 'orange', 'pink', 'brown', 'purple', 'magenta'], []); const materials = useMemo(() => COLORS.map(color => new THREE.MeshLambertMaterial({ color, opacity: 0.6, transparent: true })), [COLORS]); const tileWidth = 360 / GRID_SIZE[0]; const tileHeight = 180 / GRID_SIZE[1]; const tilesData = useMemo(() => { const tilesData = []; [...Array(GRID_SIZE[0]).keys()].forEach(lngIdx => [...Array(GRID_SIZE[1]).keys()].forEach(latIdx => tilesData.push({ lng: -180 + lngIdx * tileWidth, lat: -90 + (latIdx + 0.5) * tileHeight, material: materials[Math.floor(Math.random() * materials.length)] }) ) ); return tilesData; }, [GRID_SIZE, tileWidth, tileHeight, materials]); return createElement(R3fGlobe, { ...ptProps, tilesData: tilesData, tileWidth: tileWidth - TILE_MARGIN, tileHeight: tileHeight - TILE_MARGIN, tileMaterial: 'material' }); } export const Ripples = ptProps => { const N = 10; const gData = useMemo(() => [...Array(N).keys()].map(() => ({ lat: (Math.random() - 0.5) * 180, lng: (Math.random() - 0.5) * 360, maxR: Math.random() * 20 + 3, propagationSpeed: (Math.random() - 0.5) * 20 + 1, repeatPeriod: Math.random() * 2000 + 200 })), [N]); const colorInterpolator = useCallback(t => `rgba(255,100,50,${1-t})`, []); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-dark.jpg', ringsData: gData, ringColor: () => colorInterpolator, ringMaxRadius: 'maxR', ringPropagationSpeed: 'propagationSpeed', ringRepeatPeriod: 'repeatPeriod' }); } export const Shield = ptProps => { const shieldRing = useMemo(() => ({ lat: 90, lng: 0 }), []); return createElement(R3fGlobe, { ...ptProps, globeImageUrl: '//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg', ringsData: [shieldRing], ringAltitude: 0.25, ringColor: () => 'lightblue', ringMaxRadius: 180, ringPropagationSpeed: 20, ringRepeatPeriod: 200 }); } export default [HexedPolygons, Ripples, Paths, Points, Shield, CountryPolygons, Hollow, Hexbin, Tiles, Heatmap, Population, Arcs, Moon];