const SAT_SIZE = 3; // deg
const Satellites = Kapsule({
props: {
satellites: { default: null }
},
stateInit: () => ({
groupColor: d3.scaleOrdinal(d3.schemeCategory10),
tip: d3.tip()
.attr('class', 'tooltip')
.direction('s')
.offset([30, 10])
.html(d => `
${d.name}
Altitude: ${Math.round(d.coords.height)}km
`)
}),
init(svg, state, projection) {
state.satG = svg.append('g');
state.geoPath = d3.geoPath()
.projection(projection)
.pointRadius(SAT_SIZE / 2);
// Tooltip
svg.call(state.tip);
let realSvg = svg.node();
while (realSvg && realSvg.tagName !== 'svg') { realSvg = realSvg.parentNode }
state.followCursor = d3.select(realSvg).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) {
// 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')
.on('mousemove', d => state.tip.show(d, state.followCursor.node()))
.on('mouseout', state.tip.hide);
sats.merge(newSats)
.attr('d', d => state.geoPath({
type: 'Point',
coordinates: [d.coords.longitude, d.coords.latitude].map(r2d),
properties: d
}));
}
});
// radians to degrees
function r2d(rad) {
return rad * 180 / Math.PI;
}