From c241399edf137636ea7a8395659e202f0d343961 Mon Sep 17 00:00:00 2001
From: viclope <victor.lopez@alumnos.uva.es>
Date: Thu, 13 Dec 2018 16:34:51 +0100
Subject: [PATCH] Upload New File

---
 visualizacion.js | 718 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 718 insertions(+)
 create mode 100644 visualizacion.js

diff --git a/visualizacion.js b/visualizacion.js
new file mode 100644
index 0000000..12d79a9
--- /dev/null
+++ b/visualizacion.js
@@ -0,0 +1,718 @@
+// Constantes de la visualización
+
+// svgHeight: altura  total del svg
+
+const colors = { // mapeo de colores asociados a cada país
+    China: '#EE2E2E',
+    'United States': '#9B54C3',
+    India: '#FF9933',
+    Russia: '#3574EE',
+    Japan: '#FFDD00',
+    Germany: '#000',
+    Iran: '#C38865',
+    'Saudi Arabia': '#006C35'
+    }, 
+    svgWidth = 1000, // anchura total del svg
+    svgHeight = 620, // altura total del svg
+    hPadding = 40, // espaciado interior lateral del svg con la visualización
+    vPadding = 30, // espaciado interior vertical del svg con la visualización
+    svg = d3.select('svg'),  // elemento del DOM del svg 
+    lineCorrectionFactor = .3, // corrige la longitud de las líneas para evitar huecos entre ellas
+    pointThickness = 6, // grosor de los puntos (per cápita)
+    lineWidth = 2.5, // grosor de las líneas (per cápita)
+    minX = 1960, // mínimo valor del eje x
+    maxX = 2016, // máximo valor del eje x
+    numOfCountries = 8, // número de países de la visualización
+    lineAnimationDuration = 10,
+    scaleX = d3.scaleLinear() // escala del eje x (es siempre la misma)
+               .domain([minX, maxX])
+               .range([hPadding, svgWidth - hPadding]),
+    ylabel = svg.append("text")
+        .attr("transform", "rotate(-90)")
+        .attr("y", 0)
+        .attr("x", 0 - (svgHeight / 2))
+        .attr('class', 'ylabel')
+        .attr("dy", "1em")
+        .style("text-anchor", "middle")
+        .style('font-size', '18px'); 
+
+    
+    let perCapita = true, // guarda el modo de visualización actual
+        first = true, // se utiliza para trazar una sola vez el eje x y la línea del P. Kyoto
+        scaleY = undefined, // mantiene la escala del eje Y para utilizarla 'fuera'
+        pointsPerCountry = undefined; // guarda la estructura de datos que mantiene los puntos de los polígonos
+                                      // de los distintos países
+
+    const countriesHidden = Object.assign({}, colors); // guarda qué países están ocultos en la visualización
+    for (country in countriesHidden) countriesHidden[country] = false; // En un principio todos visibles (hidden = false)      
+
+// Constantes de la interfaz
+
+// Elementos del DOM que se modifican dinámicamente: overlay(datos de los puntos), slider, leyenda ...
+
+const overlayInfo = document.querySelector('#overlay-info'),
+      countryDataOverlay = document.querySelector('#country-data'),
+      perCapitaDataOverlay = document.querySelector('#per-cap-data'),
+      yearDataOverlay = document.querySelector('#year-data'),
+      currentDate = document.querySelector('#current-date'),
+      dateLimiter = document.querySelector('#date-limiter'),
+      legend = document.querySelector('#legend'),
+      slider = document.querySelector('#slider'),
+      arranger = slider.children[0],
+      progress = slider.children[1];
+
+// Inicializar leyenda
+
+// Crea la leyenda dinámicamente con el mapeo de colores, el estilo se ha creado en el css
+
+let hijoN = 0;
+
+for (country in colors) {
+
+    const newItem =
+        `<div class="legend-item">
+            <div class="item-square">
+            </div>
+            <div class="item-text" countryAssociated="${country}">
+            ${country} 
+            </div>
+        </div>`;
+
+    legend.innerHTML += newItem;
+
+    const currentChild = legend.children[hijoN++]
+    const childSquare = currentChild.children[0]; 
+    childSquare.style.background = colors[country];
+
+}
+
+// Cambio entre visualizaciones
+
+perCapitaVisualization(); // Empezamos con la per cápita
+
+//Manejar cambios a la visualización de emisiones total
+
+document.querySelector('#total').addEventListener('change', function(){
+    
+    // Los radio buttons son personalizados por lo que se utiliza esto para darles estilo 
+    // distinto cuando están seleccionados
+    
+    document.querySelector('[for="capita"]').classList.remove('marked');
+    document.querySelector('[for="total"]').classList.add('marked');
+
+    // Animación de líneas para el cambio
+    
+    d3.selectAll('.perCapitaLine')
+      .transition()
+      .duration(lineAnimationDuration)
+      .delay(delayLines)
+      .attr('x1', function(){ return d3.select(this).attr('x2') })
+      .attr('y1', function(){ return d3.select(this).attr('y2') })
+
+    // Cuando termina la animación (timeout para evitar cortarla) se realizan ajustes
+    // para el cambio y se cambia de visualización
+
+    setTimeout(function(){
+
+        // Evita errores con el overlay
+        overlayInfo.style.display = 'none';
+        
+        // Ocultar el slider y recolocarlo cuando está oculto para el próximo cambio
+        dateLimiter.style.opacity = 0;
+        
+        moveArranger(document.body.offsetWidth); 
+        currentDate.innerHTML = maxX;
+
+        // Elimina las líneas para ahorrar espacio del árbol DOM para ganar eficiencia
+        d3.selectAll('.perCapitaLine,circle').remove();
+        
+        perCapita = !perCapita
+        totalVisualization();
+
+    }, lineAnimationDuration * (maxX - minX) + 200);
+
+});
+
+//Manejar cambios a la visualización de emisiones per cápita
+
+document.querySelector('#capita').addEventListener('change', function(){
+
+    // Estilo de los radio buttons (se le quita a uno y se le pone al otro por eso se 'repite') 
+
+    document.querySelector('[for="total"]').classList.remove('marked');
+    document.querySelector('[for="capita"]').classList.add('marked');
+    
+    // Animación de cierre de visualización total
+    d3.selectAll('polygon')
+        .transition()
+        .duration(50)
+        .delay((d,i) => 50*i)
+        .attr('points', '')
+    
+    d3.selectAll('.worldLine')
+      .transition()
+      .duration(500)
+      .attr('opacity', 0)
+
+    // De nuevo timeout para evitar cortar la animación
+
+    setTimeout(function () {
+        
+        // Recuperamos el slider
+        dateLimiter.style.opacity = 1;
+        // Eliminamos polígonos por cuestión de eficiencia 
+        // (visualmente han desaparecido al igual que las líneas)
+        d3.selectAll('.worldLine').remove();
+        d3.selectAll('polygon').remove();
+        
+        perCapita = !perCapita
+        perCapitaVisualization();
+
+    }, 100*numOfCountries);
+
+});
+
+// Visualización emisiones per cápita
+
+// Función que representa la parte de las emisiones per cápita
+
+function perCapitaVisualization(){
+
+    d3.csv('per-capita-emissions-filtered.csv')
+    .then(function (data) {
+        
+        const minY = d3.min(data.map(d => Number(d.pcapEmissions)));
+        const maxY = d3.max(data.map(d => Number(d.pcapEmissions)));
+
+        // Se genera la escala del eje y (cambia entre las dos partes)
+
+        scaleY = d3.scaleLinear()
+                    .domain([minY, maxY])
+                    .range([svgHeight-vPadding, vPadding]);
+
+        // Se crea el eje y se llama (modificando parámetros, ticks que aparecen ...)
+
+        const yAxis = d3.axisLeft().scale(scaleY)
+                                .ticks(0)
+                                .tickSize(0);
+
+        svg.append('g')
+           .attr('transform', `translate(${hPadding}, 0)`)
+           .call(yAxis);
+
+        // Etiqueta del eje y (texto)
+        
+        ylabel.text("CO2 (Toneladas)");
+
+        // Crea el eje X y la línea del protocolo de Kyoto, son comunes a ambas partes por lo que
+        // se genera una única vez        
+
+        if(first){  
+
+            const xAxis = d3.axisBottom().scale(scaleX)
+                .ticks(5)
+                .tickFormat(d3.format('.0f'))
+                .tickSizeOuter(0);
+    
+            svg.append('g')
+                .attr('transform', `translate(0,${svgHeight - vPadding})`)
+                .call(xAxis);
+
+            d3.selectAll('.tick text') // Desplaza hacia abajo el texto de los ticks
+                .attr('y', 16)
+    
+            const kyotoX = scaleX(1997),
+                kyotoColor = '#B3A6A6';
+    
+            svg.append('line') 
+                .attr('x1', kyotoX)
+                .attr('x2', kyotoX)
+                .attr('y1', scaleY(minY))
+                .attr('y2', scaleY(maxY + 1))
+                .style('stroke', kyotoColor)
+                .style('stroke-width', lineWidth)
+                .style('stroke-dasharray', '10');
+    
+            svg.append('text')
+                .attr('x', kyotoX + 15)
+                .attr('y', scaleY(maxY) - 10)
+                .text('Protocolo de Kyoto')
+                .style('font-size', '18px')
+                .style('fill', kyotoColor)
+            
+            first = !first
+
+        }
+
+        // Se crean
+
+        const initialX = d => scaleX(Number(d.Year));
+        const initialY = d => scaleY(Number(d.pcapEmissions));
+
+        // Se generan las líneas de la visualización per cápita
+        // con su animación de aparición, si el país no se mostraba en la parte anterior,
+        // se sigue sin mostrar
+
+        svg.selectAll('.perCapitaLine')
+            .data(data)
+            .enter()
+            .append('line')
+            .attr('x1', initialX)
+            .attr('x2', initialX)
+            .attr('y1', initialY)
+            .attr('y2', initialY)
+            .attr('country', d => d.Entity)
+            .attr('year', d => Number(d.Year) + 1)
+            .attr('class', 'perCapitaLine')
+            .attr('visibility', d => countriesHidden[d.Entity] ? 'hidden' : 'visible')
+            .style('stroke', d => colors[d.Entity])
+            .style('stroke-width', lineWidth)
+            .transition()
+            .duration(lineAnimationDuration)
+            .delay(delayLines)
+            .attr('x2', (d, i) => {
+                const finalX2 = d.Year !== '2016' ? Number(data[i + 1].Year) : Number(d.Year);
+                return scaleX(finalX2) + lineCorrectionFactor;
+            })
+            .attr('y2', (d, i) => {
+                const finalY2 = d.Year !== '2016' ? Number(data[i + 1].pcapEmissions) : Number(d.pcapEmissions);
+                return scaleY(finalY2);
+            })
+        
+        // Se generan los puntos de la visualización per cápita
+        // que se utilizan como indicador visual de sobre qué punto se está mostrando información (overlay),
+        // además se asocia también el evento de hovering en su creación
+
+        svg.selectAll('circle')
+            .data(data)
+            .enter()
+            .append('circle')
+            .attr("r", pointThickness)
+            .attr("cx", d => scaleX(Number(d.Year)))
+            .attr("cy", d => scaleY(Number(d.pcapEmissions)))
+            .attr('country', d => d.Entity)
+            .attr('year', d => d.Year)
+            .attr('per-cap', d => d.pcapEmissions)
+            .attr('visibility', d => countriesHidden[d.Entity] ? 'hidden' : 'visible')
+            .style('fill', 'transparent')
+            .style('cursor', 'pointer')
+            .on('mouseover', function(){ // Mostrar el overlay y el punto (actualizando información)
+
+                // Se utiliza un solo overlay que se oculta y reposiciona por cuestiones de eficiencia
+                // del árbol DOM
+
+                const thisCircle = d3.select(this);
+                const overlayYCorrection = 40;
+                const overlayXCorrection = 280;
+
+                thisCircle.style('fill', d => colors[d.Entity]);
+                
+                overlayInfo.style.display = 'block';
+                overlayInfo.style.top = Number(thisCircle.attr('cy')) + overlayYCorrection + 'px';
+                overlayInfo.style.left = Number(thisCircle.attr('cx')) + overlayXCorrection + 'px';
+
+                countryDataOverlay.innerHTML = thisCircle.attr('country');
+                perCapitaDataOverlay.innerHTML = thisCircle.attr('per-cap');
+                yearDataOverlay.innerHTML = thisCircle.attr('year');
+
+            })
+            .on('mouseout', function () { // Ocultar el overlay y el punto
+                d3.select(this).style('fill', 'transparent')
+                overlayInfo.style.display = 'none';
+            });
+
+    });
+    
+}
+
+// Visualización emisiones totales
+
+function totalVisualization(){
+
+    d3.csv('total-emissions-filtered.csv')
+    .then(function (data) {
+    
+        const minY = d3.min(data.map(d => Number(d.totalEmissions)));
+        const maxY = d3.max(data.map(d => Number(d.totalEmissions)));
+        
+        // Se cambia la escala del eje Y (distinta en las dos partes)
+
+        scaleY = d3.scaleLinear()
+            .domain([minY, maxY])
+            .range([svgHeight - vPadding, vPadding]);
+        
+        // Se crea y llama el nuevo eje y
+
+        const yAxis = d3.axisLeft().scale(scaleY)
+            .ticks(0)
+            .tickSize(0);
+    
+        svg.append('g')
+            .attr('transform', `translate(${hPadding}, 0)`)
+            .call(yAxis);
+        
+        // Etiqueta del eje y (texto)
+
+        ylabel.text("CO2 (Mill. Toneladas)");
+    
+        const dataPerCountry = Object.assign({}, colors); // Agrupa los datos por países(comodidad)
+                                                          // Inicialmente cada coloumna se encuentra en un objeto separado
+
+        // Crea un array con 57 ceros (numero de datos por país) que acumula las emisiones
+        let previousCountryValues = [...Array(57).keys()].map(() => 0); 
+        pointsPerCountry = []; // Estructura de datos útil para manejar el polígono
+
+        for(country in dataPerCountry) {
+            
+            dataPerCountry[country] = data.filter(d => d.Entity === country) // Va agrupando los datos por países
+            
+            pointsPerCountry.push(
+                {
+                    totalEmissions: dataPerCountry[country].map(d => Number(d.totalEmissions)), // Emisiones de cada año
+                    base: previousCountryValues.map(d => d), // base del polígono, final del 'techo' del anterio 
+                    years: dataPerCountry[country].map(d => Number(d.Year)), // Año de cada punto
+                    country
+                }
+            );
+
+            // Acumula los valores de emisiones (los áreas son acumulados) para asignarlos a la base de los polígonos
+            // Si un páis está oculto no se suma al acumulado
+            previousCountryValues = previousCountryValues.map((val, indx) => {
+                
+                const totalEmissionsThatYear = Number(dataPerCountry[country][indx].totalEmissions);
+                return val + (countriesHidden[country] ? 0 : totalEmissionsThatYear)
+            
+            });
+    
+    
+        }
+
+        // Se crean los poligonos de la visualización (si no se han eliminado de la misma)
+        // con su correspondiente animación de aparición las bases se invierten porque svg
+        // utiliza el convexo de hull de los vértices para generar polígonos (orden de reloj de los vértices)
+
+        svg.selectAll('polygon')
+            .data(reverseArray(pointsPerCountry))
+            .enter()
+            .append('polygon')
+            .attr('points', d => {
+                const basePoints = d.years.map(year => [year, 0]);
+                return pointsFormatting(basePoints,reverseArray(basePoints));
+            })
+            .attr('fill', d => colors[d.country])
+            .attr('country', d => d.country)
+            .attr("stroke", "black")
+            .attr("stroke-width", .8)
+            .attr('visibility', d => countriesHidden[d.country] ? 'hidden' : 'visible')
+            .transition()
+            .duration(1000)
+            .delay((d, i) => (numOfCountries-i) * 100)
+            .attr('points', d => {
+                const basePoints = d.base.map((baseY, i) => [d.years[i], baseY]);
+                const topPoints = d.base.map((baseY, i) => [d.years[i], baseY + d.totalEmissions[i]]);
+
+                if(!countriesHidden[d.country]) return pointsFormatting(topPoints, basePoints.reverse()); 
+                else  return pointsFormatting(basePoints,reverseArray(basePoints));
+            })
+
+        // filtrado de los datos (cogiendo los correspondientes a datos mundiales)
+        const worldData = data.filter(d => d.Entity == 'World');
+        
+        // Trazado de la línea de emisiones totales mundiales
+        svg.selectAll('.worldLine')
+           .data(worldData)
+           .enter()
+           .append('line')
+           .attr('x1', d => scaleX(Number(d.Year)))
+           .attr('x2', (d,i) => d.Year !== '2016' ? scaleX(Number(worldData[i + 1].Year)) + lineCorrectionFactor : scaleX(Number(d.Year)))
+           .attr('y1', d => scaleY(Number(d.totalEmissions)))
+           .attr('y2', (d,i) => d.Year !== '2016' ? scaleY(Number(worldData[i + 1].totalEmissions)) : scaleY(Number(d.totalEmissions)))
+           .attr('class', 'worldLine')
+           .attr('stroke-width', 3)
+           .attr('stroke', 'gray')
+           .attr('opacity', 0)
+        
+        // Texto de la línea de emisiones totales mundiales
+        svg.append('text')
+           .attr('x', scaleX(Number(worldData[worldData.length - 1].Year)) - 70)
+           .attr('y', scaleY(Number(worldData[worldData.length - 1].totalEmissions))-10)
+           .text('Mundial')
+           .attr('class', 'worldLine')
+           .attr('fill', 'gray')
+           .attr('opacity', 0)
+        
+        // Animación de la línea y el texto de emisiones totales mundiales (aparición progresiva)
+
+        d3.selectAll('.worldLine')
+            .transition()
+            .duration(2000)
+            .attr('opacity', 1)
+                
+    })
+}
+
+// Interacción
+
+document.querySelectorAll('.item-text').forEach(function (item) { // Se añaden eventos a todos los items de la leyenda
+
+    // Maneja el evento de interacción de que al hacer click sobre un elemento de la leyenda
+    // el país se elimina de la visualización o se añade si estaba eliminado
+    item.addEventListener('click', function(e) { 
+
+        const legendItem = e.target; // elemento de la leyenda asociado (el que ha generado el evento)
+        const countryAssociated = legendItem.getAttribute('countryAssociated'); // País del elemento
+        const lineSelected = d3.selectAll(`[country="${countryAssociated}"]:not(polygon)`); // Elementos de la visualización del país
+
+        // Lo copio ya que necesito cambiar el estado antes de llamar a la estructura de control
+        // ya que necesito la estructura de datos actualizada para cambiar las áreas
+        // y evito repetir código (podría meterlo dos veces antes de llamar a hideShowCountryArea)
+        const thisCountryIsHidden = countriesHidden[countryAssociated];
+
+        //Si el país estaba oculto en la estructura de datos ahora no lo está y viceversa.
+        countriesHidden[countryAssociated] = !countriesHidden[countryAssociated]; 
+
+        // Si el país estaba eliminado/oculto se devuelve a la visualización y se actualiza el elemento de la leyenda (no tachado)
+        // Si no se tacha y se elimina de la visualización
+        if (thisCountryIsHidden) {
+
+            lineSelected.attr('visibility', function () {
+                const thisLine = d3.select(this),
+                      thisLineYear = Number(thisLine.attr('year')),
+                      maxYear = Number(currentDate.innerHTML);
+
+                // Mantiene la coherencia si la fecha está limitada
+                if (thisLineYear > maxYear) {
+                    return 'hidden';
+                }
+                else {
+                    return 'visible';
+                }
+            });
+
+            legendItem.style.textDecoration = 'none';
+            if(!perCapita) hideShowCountryArea(countryAssociated, show=true) //Los polígonos se eliminan de distinta manera
+
+        }
+        else {
+
+            if(!perCapita) hideShowCountryArea(countryAssociated) //Los polígonos se eliminan de distinta manera
+            lineSelected.attr('visibility', 'hidden');
+            legendItem.style.textDecoration = 'line-through';
+            resetOpacity();
+
+        }        
+
+    });
+
+    // Maneja el evento de que al entrar con el ratón en el elemento de la leyenda
+    // asociado a un país, el resto países tienen un alfa menor en la visualización (destaca más el deseado)
+    item.addEventListener('mouseenter', function (e) {
+
+
+        const legendItem = e.target; // Elemento del DOM del elemento de la leyenda
+        const countryAssociated = legendItem.getAttribute('countryAssociated'); // Obtiene el país asociado a ese elemento de la leyenda
+
+        // Bucle que disminuye el alfa del resto de países, si el país se ha eliminado de la visualización
+        // no se efectúa esta operación
+
+        if(countriesHidden[countryAssociated] !== true){
+
+            for(country in colors){
+    
+                const countryElements = d3.selectAll(`[country="${country}"]`);
+                if(country !== countryAssociated && perCapita) countryElements.style('opacity',.2)
+                else if(country !== countryAssociated) countryElements.style('opacity',.2)
+    
+            }
+
+        }
+    });
+
+    // Los elementos de la visualización de todos los países vuelven a su alfa normal al dejar de hacer hover
+    item.addEventListener('mouseout', function() { 
+                                                  
+        resetOpacity();
+
+    });
+
+})
+
+// Función que devuelve el alfa normal a todos los elementos de la visualización
+function resetOpacity() {
+
+    for (country in colors) d3.selectAll(`[country="${country}"]`)
+                              .style('opacity', 1)
+                              .style('opacity', 1);
+
+}
+
+// 
+
+let mouseHold = false;
+// Se pulsa el ratón en el slider
+slider.addEventListener('mousedown', () => mouseHold = true); 
+//Se suelta el ratón, no importa el lugar dónde se suelte dado que se permite desplazamiento no lineal,
+//es decir, se suelta dentro de la página (el documento)
+document.addEventListener('mouseup', () => mouseHold = false); 
+
+// Se mueve el ratón por el documento, el manejador del evento comprobará si se ha pulsado el ratón
+// sobre el slider y sigue mantenido
+document.addEventListener('mousemove', handleArrangerMovement); 
+// Se hace click sobre un punto del slider (evita la necesidad de arrastrar)
+slider.addEventListener('mousedown', handleArrangerMovement);
+
+// Manejador del arrastrado del slider
+function handleArrangerMovement(event) {
+
+    // Obtiene el tamaño del slider, su posición horizontal(x), la mitad de la anchura del 'agarrador' (ya que se
+    // utiliza el punto medio del mismo como valor del limitador de fechas) y la nueva posición requerida por el mouse
+    // de manera dinámica
+    const bounds = slider.getBoundingClientRect(),
+        x = bounds.left,
+        halfArrangerWidth = arranger.offsetWidth / 2,
+        newPos = event.clientX - x - halfArrangerWidth;
+
+    // Si el ratón ha sido pulsado en el slider y mantenido está, nueva posición se calcula y se limita el valor de la 
+    // fecha en el gráfico per cápita, dejando de  visualizar las líneas posteriores a esa feha
+    if(mouseHold) {
+
+        const barPercentage = moveArranger(newPos),
+              newCurrentDate = Math.round(barPercentage * (maxX - minX) + minX);
+
+        currentDate.innerHTML = newCurrentDate;
+        
+        d3.selectAll('line, circle')
+          .attr('visibility', function(){
+
+              const thisLine = d3.select(this),
+                    thisLineYear = Number(thisLine.attr('year')),
+                    thisLineCountry = thisLine.attr('country');
+              
+              if(thisLineYear <= newCurrentDate && !countriesHidden[thisLineCountry]) return 'visible';
+              return 'hidden';
+              
+          })
+
+    }
+
+}
+
+// Funciones auxiliares
+
+// Función que dada una nueva posición del agarrador (la cual puede no ser válida), es decir, mayor o menor que los límites del slider,
+// la  transforma (si es mayor el máximo del slider, si es menor el mínimo del slider y si es válida pues la requerida).
+// Finalmente efectúa el movimiento de los componentes de la interfaz (agarrador y progreso), y calcula y devuelve el porcentaje de la 
+// barra en el que se encuetra el agarrador, que se puede utilizar en el resto de funciones para calcular el año limitado p.ej
+
+function moveArranger(newPosition) {
+
+    const width = slider.offsetWidth,
+        halfArrangerWidth = arranger.offsetWidth / 2,
+        newPositionMedium = newPosition + halfArrangerWidth;
+
+    let finalPosition;
+
+    if (newPositionMedium > width) {
+        finalPosition = width - halfArrangerWidth;
+    }
+    else if (newPositionMedium < 0) finalPosition = - halfArrangerWidth;
+    else finalPosition = newPosition;
+    
+    console.log(finalPosition);
+
+    arranger.style.left = finalPosition + 'px'
+    progress.style.width = (finalPosition >= 0 ? finalPosition : 0) + 'px';
+
+    const barPercentage = (finalPosition + halfArrangerWidth) / width;
+
+    return barPercentage;
+
+}
+
+// Función que toma dos arrays de puntos en formato [[x1,y1],[x2,y2],[x3,y3], ...] y [[x7,y7],[x8,y8],[x9,y9], ...] y los concatena
+// y transforma en puntos con el formato "x1,y1 x2,y2 x3,y3 x7,y7 x8,y8 x9,y9" que es el que utiliza d3 para polígonos (realmente svg)
+function pointsFormatting(pointsArray1, pointsArray2) {
+
+    const pointArr1Formatted = pointsArray1.map(point => [scaleX(point[0]), scaleY(point[1])]);
+    const pointArr2Formatted = pointsArray2.map(point => [scaleX(point[0]), scaleY(point[1])]);
+
+    return pointArr1Formatted.concat(pointArr2Formatted);
+
+}
+
+// Invierte un array sin modificar el original (arr.reverse() modifica arr)
+const reverseArray = arr => arr.slice().reverse();
+
+// Función que calcula el delay de cada una de las líneas (tanto en la animación de aparición como en la de desaparición),
+// cuánto más nuevas más delay.
+function delayLines() {
+    return (Number(d3.select(this).attr('year')) - (minX + 1)) * lineAnimationDuration
+}
+
+// Función que se encarga de ocultar y mostrar países en el Stacked Area Chart en consecuencia a las acciones del usuario
+// en la leyenda. Si se oculta un país, resta sus emisiones al resto de áreas (a las bases del polígono) 
+// que se encuentran por encima de la de éste país (disminuye el acumulado). Si se muestra un país se suman a las bases
+// de los polígonos superiores. Finalmente actualiza estos cambios en la visualización.
+
+function hideShowCountryArea(countryModified, show = false) {
+
+    let i = 0;
+
+    for(; pointsPerCountry[i].country !== countryModified; i++);
+
+    const indexOfModified = i;
+
+    for(; i < pointsPerCountry.length; i++){
+
+        const countryRepositionated = pointsPerCountry[i].country
+
+        if(countryRepositionated !== countryModified){
+            pointsPerCountry[i].base = pointsPerCountry[i].base.map((b, j) => {
+
+                if (show) return b + pointsPerCountry[indexOfModified].totalEmissions[j];
+                else return b - pointsPerCountry[indexOfModified].totalEmissions[j];
+
+            })
+        }
+
+        d3.select(`polygon[country="${countryRepositionated}"]`)
+            .transition()
+            .duration(1000)
+            .attr('points', function() {
+
+                if (countryRepositionated === countryModified && !countriesHidden[countryModified]) {
+
+                    d3.select(this).attr('visibility', 'visible');
+
+                }
+
+                const basePoints = pointsPerCountry[i].base.map((baseY, j) => {
+
+                    const currentYear = pointsPerCountry[i].years[j];
+                    return [currentYear, baseY];
+
+                });
+
+                const topPoints = pointsPerCountry[i].base.map((baseY, j) => {
+
+                    const currentYear = pointsPerCountry[i].years[j];
+                    let currentYearEmissions = pointsPerCountry[i].totalEmissions[j];
+
+                    currentYearEmissions = countriesHidden[countryRepositionated] ? 0 : currentYearEmissions;
+
+                    return [currentYear, baseY + currentYearEmissions];
+
+                });
+
+                return pointsFormatting(topPoints, basePoints.reverse());
+
+            })
+
+    }
+
+    
+}
+
+
+
+
-- 
GitLab