diff --git a/visualizacion.js b/visualizacion.js new file mode 100644 index 0000000000000000000000000000000000000000..12d79a994ecafd6f649062cc2ceac2c3f0b38533 --- /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()); + + }) + + } + + +} + + + +