{
const svg = d3.create("svg").attr('width', width).attr('height', height).attr('class', 'canvas-solar-system');
let mouseX = 0, mouseY = 0, mousePageX = 0, mousePageY = 0;
let isMouseIn = false;
const circleSize =
d3.max(planetsJson, p => p.distanceFromSun) / d3.max(planetsData, p => p.distanceFromSun);
const container = svg.append('g');
tooltipPlanet.style('display', 'none');
container.append('circle')
.attr('r', circleSize)
.attr('cx', width/2)
.attr('cy', height/2)
.attr('class', 'sun');
container.selectAll('g.planet').data(planetsData).enter()
.append('g').each(function(d, i) {
const thisD3 = d3.select(this);
thisD3.append('circle').attr('class', 'orbit')
.attr('r', d.distanceFromSunScaled)
.attr('cx', width/2).attr('cy', height/2);
thisD3.append('circle').attr('class', 'planet')
//.attr('r', d.radioScaled)
.attr('r', circleSize)
.attr('cx', d.distanceFromSunScaled).attr('cy', 0);
});
// Trasladamos las coordenadas de los planetas considerando el sol como el centro,
// y también seteamos la posición inicial.
container.selectAll('.planet').attr('transform', d => getTransformStringForPlanet(d));
svg.call(
d3.zoom()
.extent([[0, 0], [width, height]]) // El área zoomeable
.scaleExtent([0.1, 10]) // Minimo y máximo factor de escala posible
.on('zoom', function() {
container.attr('transform', d3.event.transform);
}));
svg.on('mousemove', function () {
isMouseIn = true;
tooltipPlanet.style('display', 'block');
// Capturamos los valores de la posición del mouse en el canvas y en la página, para usarlos después,
// en el while(true) de la animación.
mouseX = d3.event.layerX;
mouseY = d3.event.layerY;
mousePageX = d3.event.pageX;
mousePageY = d3.event.pageY;
}).on('mouseleave', function (){
// Este if es un truco para evitar ignorar el evento cuando no se dispara saliendo efectivamente del svg.
if(d3.event.relatedTarget === null || d3.event.relatedTarget.nodeName !== 'HTML') return;
isMouseIn = false;
tooltipPlanet.style('display', 'none');
});
function selectClosesNodeToMouse() {
let closestNodeToMouse = null;
let minDistance = Number.MAX_SAFE_INTEGER;
let closestCoords = null;
container.selectAll('.planet').nodes().forEach(function (node) {
let coords = getElementCoords(node);
let currentDistance = getDistance(coords.x, coords.y, mouseX, mouseY);
if (currentDistance < minDistance) {
closestCoords = coords;
minDistance = currentDistance;
closestNodeToMouse = node;
}
});
// Despintamos todos los planetas.
container.selectAll('.planet').classed('highlight', false);
// Borramos la línea anterior.
svg.selectAll('.mouse-planet-line').remove();
if(!isMouseIn) return;
// Pintamos solo el que está más cerca.
d3.select(closestNodeToMouse).classed('highlight', true);
// Dibujamos una línea entre el planeta más cercano y el mouse.
svg.append('line').attr('class', 'mouse-planet-line')
.attr('x1', closestCoords.x).attr('y1', closestCoords.y)
.attr('x2', mouseX).attr('y2', mouseY);
let planet = d3.select(closestNodeToMouse).data()[0];
tooltipPlanet
.style('top', mousePageY + 'px')
.style('left', mousePageX + 'px')
.html(`
<b>${planet.name}</b><br/>
${planet.distanceFromSun}M km<br/>
`);;
}
function getDistance(x1, y1, x2, y2) {
return Math.sqrt((x1 - x2)**2 + (y1 - y2)**2);
}
// Códingo para obtener las coordenadas de un HtmlElement(SVG) teniendo en cuenta las transformaciones.
// https://stackoverflow.com/questions/18554224/getting-screen-positions-of-d3-nodes-after-transform/18561829#answer-18561829
function getElementCoords(element) {
let ctm = element.getCTM();
let coords = {
x: element.getAttribute('cx'),
y: element.getAttribute('cy')
};
return {
x: ctm.e + coords.x*ctm.a + coords.y*ctm.c,
y: ctm.f + coords.x*ctm.b + coords.y*ctm.d
};
};
// Función para trasladar las coordenadas de cada planeta considerando el sol como el centro,
// y también para determinar la posición en la órbita, según la velocidad orbital de cada uno.
function getTransformStringForPlanet (planet) {
const delta = (Date.now() - initTime);
let transformString = `translate(${width/2}, ${height/2}) ` +
`rotate(${delta * planet.orbitalVelocity / (1100 - rotationSpeed * 100)})`;
return transformString;
}
//return svg.node();
// Animación para la rotación de los planetas.
//*
while(true) {
yield svg.node();
container.selectAll('.planet').attr('transform', d => getTransformStringForPlanet(d));
selectClosesNodeToMouse();
}
//*/
// Otra forma de animar, pero que deja corriendo un setInterval huérfano cada vez que se ejecuta la celda.
/*setInterval(function(){
container.selectAll('.planet').attr('transform', d => getTransformStringForPlanet(d));
selectClosesNodeToMouse();
}, 0);*/
}