Published
Edited
Oct 3, 2020
6 forks
14 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Globe = require("globe.gl")
Insert cell
d3 = require('d3')
Insert cell
THREE = {
const THREE = window.THREE = await require("three@0.99.0/build/three.min.js");
await require("three@0.99.0/examples/js/controls/OrbitControls.js").catch(() => {});
return window.THREE;
}
Insert cell
indexBy = require('index-array-by')
Insert cell
Insert cell
{
const containerWidth = width;
const containerHeight = 500;
const container = html`<div style="height:500px"></div>`;
yield container;
// Gen random data
const N = 300;
const gData = [...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)]
}));
Globe()
.globeImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg')
.pointsData(gData)
.pointAltitude('size')
.pointColor('color')
.width(containerWidth)
.height(containerHeight)
(container)
yield container;
}
Insert cell
Insert cell
{
const containerWidth = width;
const containerHeight = 500;
const container = html`<div style="height:500px"></div>`;
yield container;
// Gen random data
const N = 20;
const arcsData = [...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)], ['red', 'white', 'blue', 'green'][Math.round(Math.random() * 3)]]
}));
Globe()
.globeImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg')
.arcsData(arcsData)
.arcColor('color')
.arcDashLength(() => Math.random())
.arcDashGap(() => Math.random())
.arcDashAnimateTime(() => Math.random() * 4000 + 500)
.width(containerWidth)
.height(containerHeight)
(container)
yield container;
}
Insert cell
Insert cell
{
const containerWidth = width;
const containerHeight = 500;
const container = html`<div style="height:500px"></div>`;
yield container;
const COUNTRY = 'Portugal';
const MAP_CENTER = { lat: 37.6, lng: -16.6, altitude: 0.4 };
const OPACITY = 0.1;
const myGlobe = Globe()
.width(containerWidth)
.height(containerHeight)
(container)
.globeImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg')
.arcLabel(d => `${d.airline}: ${d.srcIata} &#8594; ${d.dstIata}`)
.arcStartLat(d => d.srcAirport.lat)
.arcStartLng(d => d.srcAirport.lng)
.arcEndLat(d => d.dstAirport.lat)
.arcEndLng(d => d.dstAirport.lng)
.arcColor(d => [`rgba(0, 255, 0, ${OPACITY})`, `rgba(255, 0, 0, ${OPACITY})`])
.arcDashLength(0.4)
.arcDashGap(0.2)
.arcDashAnimateTime(1500)
.onArcHover(hoverArc => myGlobe
.arcColor(d => {
const op = !hoverArc ? OPACITY : d === hoverArc ? 0.9 : OPACITY / 4;
return [`rgba(0, 255, 0, ${op})`, `rgba(255, 0, 0, ${op})`];
})
)
.pointColor(() => 'orange')
.pointAltitude(0)
.pointRadius(0.04)
.pointsMerge(true);
// load data
const airportParse = ([airportId, name, city, country, iata, icao, lat, lng, alt, timezone, dst, tz, type, source]) => ({ airportId, name, city, country, iata, icao, lat, lng, alt, timezone, dst, tz, type, source });
const routeParse = ([airline, airlineId, srcIata, srcAirportId, dstIata, dstAirportId, codeshare, stops, equipment]) => ({ airline, airlineId, srcIata, srcAirportId, dstIata, dstAirportId, codeshare, stops, equipment});
Promise.all([
fetch('https://raw.githubusercontent.com/jpatokal/openflights/master/data/airports.dat').then(res => res.text())
.then(d => d3.csvParseRows(d, airportParse)),
fetch('https://raw.githubusercontent.com/jpatokal/openflights/master/data/routes.dat').then(res => res.text())
.then(d => d3.csvParseRows(d, routeParse))
]).then(([airports, routes]) => {
const filteredAirports = airports.filter(d => d.country === COUNTRY);
const byIata = indexBy(filteredAirports, 'iata', false);
const filteredRoutes = routes
.filter(d => byIata.hasOwnProperty(d.srcIata) && byIata.hasOwnProperty(d.dstIata)) // exclude unknown airports
.filter(d => d.stops === '0') // non-stop flights only
.map(d => Object.assign(d, {
srcAirport: byIata[d.srcIata],
dstAirport: byIata[d.dstIata]
}))
// .filter(d => d.srcAirport.country !== d.dstAirport.country); // international routes
// .filter(d => d.srcAirport.country === d.dstAirport.country); // domestic routes
.filter(d => d.srcAirport.country === COUNTRY && d.dstAirport.country === COUNTRY); // domestic routes within country
myGlobe
.pointsData(filteredAirports)
.arcsData(filteredRoutes)
.pointOfView(MAP_CENTER, 4000);

});
yield container;
}
Insert cell
Insert cell
{
const containerWidth = width;
const containerHeight = 500;
const container = html`<div style="height:500px"></div>`;
yield container;
const colorScale = d3.scaleSequentialSqrt(d3.interpolateYlOrRd);
// GDP per capita (avoiding countries with small pop)
const getVal = feat => feat.properties.GDP_MD_EST / Math.max(1e5, feat.properties.POP_EST);
fetch('https://raw.githubusercontent.com/vasturiano/globe.gl/master/example/datasets/ne_110m_admin_0_countries.geojson').then(res => res.json()).then(countries =>
{
const maxVal = Math.max(...countries.features.map(getVal));
colorScale.domain([0, maxVal]);
const world = Globe()
.width(containerWidth)
.height(containerHeight)
(container)
.globeImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg')
.polygonsData(countries.features)
.polygonAltitude(0.06)
.polygonCapColor(feat => colorScale(getVal(feat)))
.polygonSideColor(() => 'rgba(0, 100, 0, 0.15)')
.polygonStrokeColor(() => '#111');
world.polygonLabel(({ properties: d }) => `
<b>${d.ADMIN} (${d.ISO_A2}):</b> <br />
GDP: <i>${d.GDP_MD_EST}</i> M$<br/>
Population: <i>${d.POP_EST}</i>
`)
.onPolygonHover(hoverD => world
.polygonAltitude(d => d === hoverD ? 0.12 : 0.06)
.polygonCapColor(d => d === hoverD ? 'steelblue' : colorScale(getVal(d)))
)
.polygonsTransitionDuration(300)
(document.getElementById('globeViz'))
});
yield container;
}
Insert cell
Insert cell
{
const containerWidth = width;
const containerHeight = 500;
const container = html`<div style="height:500px"></div>`;
yield container;
const world = Globe()
.width(containerWidth)
.height(containerHeight)
(container)
.globeImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-dark.jpg')
.pointOfView({ altitude: 4 }, 5000)
.polygonCapColor(feat => 'rgba(200, 0, 0, 0.6)')
.polygonSideColor(() => 'rgba(0, 100, 0, 0.05)')
.polygonLabel(({ properties: d }) => `
<b>${d.ADMIN} (${d.ISO_A2})</b> <br />
Population: <i>${Math.round(+d.POP_EST / 1e4) / 1e2}M</i>
`);
// Auto-rotate
world.controls().autoRotate = true;
world.controls().autoRotateSpeed = 0.3;
fetch('https://raw.githubusercontent.com/vasturiano/globe.gl/master/example/datasets/ne_110m_admin_0_countries.geojson').then(res => res.json()).then(countries => {
world.polygonsData(countries.features);
setTimeout(() => world
.polygonsTransitionDuration(4000)
.polygonAltitude(feat => Math.max(0.1, Math.sqrt(+feat.properties.POP_EST) * 7e-5))
, 3000);
});

yield container;
}
Insert cell
Insert cell
{
const containerWidth = width;
const containerHeight = 500;
const container = html`<div style="height:500px"></div>`;
yield container;
fetch('https://raw.githubusercontent.com/vasturiano/globe.gl/master/example/datasets/ne_110m_populated_places_simple.geojson').then(res => res.json()).then(places => {
Globe()
.width(containerWidth)
.height(containerHeight)
(container)
.globeImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg')
.labelsData(places.features)
.labelLat(d => d.properties.latitude)
.labelLng(d => d.properties.longitude)
.labelText(d => d.properties.name)
.labelSize(d => Math.sqrt(d.properties.pop_max) * 4e-4)
.labelDotRadius(d => Math.sqrt(d.properties.pop_max) * 4e-4)
.labelColor(() => 'rgba(255, 165, 0, 0.75)')
.labelResolution(2)
(document.getElementById('globeViz'))
});
yield container;
}
Insert cell
Insert cell
{
const containerWidth = width;
const containerHeight = 500;
const container = html`<div style="height:500px"></div>`;
yield container;
// Gen random data
const N = 300;
const gData = [...Array(N).keys()].map(() => ({
lat: (Math.random() - 0.5) * 180,
lng: (Math.random() - 0.5) * 360,
alt: Math.random() * 0.8 + 0.1,
radius: Math.random() * 5,
color: ['red', 'white', 'blue', 'green'][Math.round(Math.random() * 3)]
}));
const world = Globe()
.width(containerWidth)
.height(containerHeight)
(container)
.globeImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-blue-marble.jpg')
.bumpImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-topology.png')
.pointOfView({ altitude: 3.5 })
.customLayerData(gData)
.customThreeObject(d => new THREE.Mesh(
new THREE.SphereBufferGeometry(d.radius),
new THREE.MeshLambertMaterial({ color: d.color })
))
.customThreeObjectUpdate((obj, d) => {
Object.assign(obj.position, world.getCoords(d.lat, d.lng, d.alt));
});
(function moveSpheres() {
gData.forEach(d => d.lat += 0.2);
world.customLayerData(world.customLayerData());
requestAnimationFrame(moveSpheres);
})();

yield container;
}
Insert cell
Insert cell
{
const containerWidth = width;
const containerHeight = 500;
const container = html`<div style="height:500px"></div>`;
yield container;
const weightColor = d3.scaleSequentialSqrt(d3.interpolateYlOrRd)
.domain([0, 1e7]);
const world = Globe()
.width(containerWidth)
.height(containerHeight)
(container)
.globeImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg')
.bumpImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-topology.png')
.hexBinPointWeight('pop')
.hexAltitude(d => d.sumWeight * 6e-8)
.hexBinResolution(4)
.hexTopColor(d => weightColor(d.sumWeight))
.hexSideColor(d => weightColor(d.sumWeight))
.hexBinMerge(true)
.enablePointerInteraction(false); // performance improvement
fetch('https://raw.githubusercontent.com/vasturiano/globe.gl/master/example/datasets/world_population.csv').then(res => res.text())
.then(csv => d3.csvParse(csv, ({ lat, lng, pop }) => ({ lat: +lat, lng: +lng, pop: +pop })))
.then(data => world.hexBinPointsData(data));
// Add auto-rotation
world.controls().autoRotate = true;
world.controls().autoRotateSpeed = 0.1;

yield container;
}
Insert cell
Insert cell
{
const containerWidth = width;
const containerHeight = 500;
const container = html`<div style="height:500px"></div>`;
yield container;
// Gen random data
const COUNTRY = 'United States';
const OPACITY = 0.1;
const myGlobe = Globe()
.width(containerWidth)
.height(containerHeight)
(container)
.globeImageUrl('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg')
.pointOfView({ lat: 39.6, lng: -98.5, altitude: 2 }) // aim at continental US centroid
.arcLabel(d => `${d.airline}: ${d.srcIata} &#8594; ${d.dstIata}`)
.arcStartLat(d => +d.srcAirport.lat)
.arcStartLng(d => +d.srcAirport.lng)
.arcEndLat(d => +d.dstAirport.lat)
.arcEndLng(d => +d.dstAirport.lng)
.arcDashLength(0.25)
.arcDashGap(1)
.arcDashInitialGap(() => Math.random())
.arcDashAnimateTime(4000)
.arcColor(d => [`rgba(0, 255, 0, ${OPACITY})`, `rgba(255, 0, 0, ${OPACITY})`])
.arcsTransitionDuration(0)
.pointColor(() => 'orange')
.pointAltitude(0)
.pointRadius(0.02)
.pointsMerge(true);
// load data
const airportParse = ([airportId, name, city, country, iata, icao, lat, lng, alt, timezone, dst, tz, type, source]) => ({ airportId, name, city, country, iata, icao, lat, lng, alt, timezone, dst, tz, type, source });
const routeParse = ([airline, airlineId, srcIata, srcAirportId, dstIata, dstAirportId, codeshare, stops, equipment]) => ({ airline, airlineId, srcIata, srcAirportId, dstIata, dstAirportId, codeshare, stops, equipment});
Promise.all([
fetch('https://raw.githubusercontent.com/jpatokal/openflights/master/data/airports.dat').then(res => res.text())
.then(d => d3.csvParseRows(d, airportParse)),
fetch('https://raw.githubusercontent.com/jpatokal/openflights/master/data/routes.dat').then(res => res.text())
.then(d => d3.csvParseRows(d, routeParse))
]).then(([airports, routes]) => {
const byIata = indexBy(airports, 'iata', false);
const filteredRoutes = routes
.filter(d => byIata.hasOwnProperty(d.srcIata) && byIata.hasOwnProperty(d.dstIata)) // exclude unknown airports
.filter(d => d.stops === '0') // non-stop flights only
.map(d => Object.assign(d, {
srcAirport: byIata[d.srcIata],
dstAirport: byIata[d.dstIata]
}))
.filter(d => d.srcAirport.country === COUNTRY && d.dstAirport.country !== COUNTRY); // international routes from country
myGlobe
.pointsData(airports)
.arcsData(filteredRoutes);
});

yield container;
}
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