Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
E
Emisiones_CO2_antes_y_despues_Kyoto
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Deploy
Releases
Package Registry
Model registry
Operate
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
DESI_18-19
Emisiones_CO2_antes_y_despues_Kyoto
Commits
c241399e
Commit
c241399e
authored
6 years ago
by
viclope
Browse files
Options
Downloads
Patches
Plain Diff
Upload New File
parent
8328422c
No related branches found
Branches containing commit
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
visualizacion.js
+718
-0
718 additions, 0 deletions
visualizacion.js
with
718 additions
and
0 deletions
visualizacion.js
0 → 100644
+
718
−
0
View file @
c241399e
// 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
());
})
}
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment