const SAT_SIZE_REL = 35; // degrees per 1/height(sqkm)
const Satellites = Kapsule({
props: {
satellites: { default: null }
},
stateInit: () => ({
groupColor: d3.scaleOrdinal(d3.schemeCategory10),
geoCircle: d3.geoCircle()
.center(({ latitude, longitude }) => [longitude, latitude].map(r2d))
.radius(({ height }) => SAT_SIZE_REL / Math.sqrt(height))
.precision(30),
geoVoronoi: d3.geoVoronoi()
.x(d => r2d(d.coords.longitude))
.y(d => r2d(d.coords.latitude)),
tip: d3.tip()
.attr('class', 'tooltip')
.direction('s')
.offset([30, 10])
.html(({ properties: { site: d } }) => `
${d.name}
${d.group}
Altitude: ${Math.round(d.coords.height)}km
`)
}),
init(svg, state, projection) {
state.voronoiG = svg.append('g');
state.satG = svg.append('g');
state.geoPath = d3.geoPath()
.projection(projection)
.pointRadius(2);
// Tooltip
svg.call(state.tip);
state.followCursor = svg.append('circle').style('pointer-events', 'none'); // helper to make tip follow cursor
svg.on('mousemove', () => state.followCursor.attr('cx', d3.event.pageX).attr('cy', d3.event.pageY));
},
update(state) {
// voronoi polygons
try {
const voronoiPolygons = state.voronoiG.selectAll('.voronoi')
.data(
state.geoVoronoi(state.satellites).polygons().features,
d => d.properties.site.name
);
voronoiPolygons.exit().remove();
const newPolygons = voronoiPolygons
.enter().append('path')
.attr('class', 'voronoi')
.on('mousemove', d => state.tip.show(d, state.followCursor.node()))
.on('mouseout', state.tip.hide);
voronoiPolygons.merge(newPolygons)
.attr('d', state.geoPath)
} catch(e) {} // skip render cycle if voronoi computation errors (due to overlapping nodes)
// satellite markers
const sats = state.satG.selectAll('.satellite')
.data(state.satellites, d => d.name);
sats.exit().remove();
const newSats = sats.enter().append('path')
.attr('class', 'satellite')
.attr('fill', d => state.groupColor(d.group))
.attr('fill-opacity', 0.7);
sats.merge(newSats)
.attr('d', d => state.geoPath(state.geoCircle(d.coords)));
}
});
// radians to degrees
function r2d(rad) {
return rad * 180 / Math.PI;
}