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