Skip to content
Snippets Groups Projects
Commit 06b1ba8e authored by margarr's avatar margarr
Browse files

Entrega js

parent c217d4b4
Branches
No related tags found
No related merge requests found
Pipeline #8735 passed
// Author: Mario Garrido Tapias
// Funcion que se ejecuta cuando el documento esta listo
document.addEventListener("DOMContentLoaded", function(e) {
d3.select("#cartograma").selectAll("svg").remove(); // Eliminamos todos los svg que esten en el div de los graficos, por si hubiera alguno
// Grafico principal
dibujaCartogramaD();
var radio = d3.selectAll("input[type=radio][name=opcion]");
// Reset del radioButtom
radio.attr("checked", false);
radio.each(function(d, i){
if(this.value == 4){
this["checked"] = true;
}
});
radio.on("change", function(d, i) { // Se le llama al cambiar cualquier elemento input de la seleccion
d3.select("#cartograma").selectAll("svg").remove();
// Eliminamos la leyenda por si acaso
d3.select(".legend").remove();
cambiarGrafico(i);
});
// Lo creamos fuera de los eventos mouseOVER y mouseOUT para poder acceder desde ambos
var div = d3.select("body").append("div")
.attr("class", "imagen")
.style("opacity", .85);
div
.style("left", 1053 + "px") //console.log(d3.event.pageX);
.style("top", 90 + "px") //console.log(d3.event.pageY);
.html("<img src = imagenes/regiones.png>");
// Cuando pasemos el raton por encima de los electores de alguna de las regiones mostramos una imagen de ayuda que apoyamos
// con el cambio del color de las etiquetas
radio.on("mouseover", function() {
if(this.value != 4){
d3.select("#region1").style("color","green");
d3.select("#region2").style("color","#d4ac0d");
d3.select("#region3").style("color","red");
d3.select("#region4").style("color","blue");
div.style("display", "block");
}
});
radio.on("mouseout", function() {
div.style("display", "none");
d3.select("#region1").style("color","black");
d3.select("#region2").style("color","black");
d3.select("#region3").style("color","black");
d3.select("#region4").style("color","black");
});
// Menu desplegable para diferentes ordenaciones
var desplegable = d3.select("#ordenar");
desplegable.on("change", function() {
d3.select("#cartograma").selectAll("svg").remove();
graficoBarrasH(tipoGrafico, this.value, 0);
graficoBarrasH(tipoGrafico, this.value, 1);
});
});
// Funciones PRINCIPALES
// Nos funcionara de variable global con la region que esta representada, para poder utilizarla en otras funciones
var tipoGrafico;
// Funcion principal que hace las llamadas a los creadores de los diferentes graficos en funcion de la opcion seleccionada
function cambiarGrafico(opcion){
tipoGrafico = +opcion; // Para forzar a que sea de tipo numerico
var titulo = "";
switch(tipoGrafico){
case 0: // Region 1
titulo = "en la región Noreste en 2018";
graficoBarrasH(0, 0, 0);
graficoBarrasH(0, 0, 1);
break;
case 1: // Region 2
titulo = "en la región Medio Oeste en 2018";
graficoBarrasH(1, 0, 0);
graficoBarrasH(1, 0, 1);
break;
case 2: // Region 3
titulo = "en la región Sur en 2018";
graficoBarrasH(2, 0, 0);
graficoBarrasH(2, 0, 1);
break;
case 3: // Region 4
titulo = "en la región Oeste en 2018";
graficoBarrasH(3, 0, 0);
graficoBarrasH(3, 0, 1);
break;
case 4: // Completo
titulo = "en EE.UU en 2018";
dibujaCartogramaD();
break;
}
// Cambiamos la segunda linea del titulo
d3.select("#titulo").selectAll("h1").filter(":nth-child(even)").text(titulo);
}
// Funcion que dibuja un grafico de barras horizontales
// Parámetros:
// > region: valor entre 0 y 3 que decide de que archivo leemos
// > ordenado: valor entre 0 y 2(desordenado, de > a <, de < a >), que decide como ordenamos los datos
// > tipo: indica que grafico representa siendo 0: Tasa Obesidad 1: Nº restaurantes
function graficoBarrasH(region, ordenado, tipo){
var archivo = "";
switch(region){
case 0:
archivo = "datos/region1.csv";
break;
case 1:
archivo = "datos/region2.csv";
break;
case 2:
archivo = "datos/region3.csv";
break;
case 3:
archivo = "datos/region4.csv";
break;
}
// Ajustamos las dimensiones(width y height) y los margenes del grafico
var margin = {top: 20, right: 30, bottom: 40, left: 90},
// Ancho
width = 460 - margin.left - margin.right,
// Alto
height = 430 - margin.top - margin.bottom;
// Creamos el desplegable que mostrara la informacion de la barra sobre la que esta el raton
var info = d3.select("body").append("div").attr("class", "toolTip");
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleBand().rangeRound([height, 0]).padding(.1);
// Color para rellenar las barras
var color = "";
if(tipo == 0){
// Necesitamos un formato al ser datos porcentajes
var formatPercent = d3.format(".0%");
var xAxis = d3.axisBottom(x).tickFormat(formatPercent);
color = "#f8c471";
}
else{
var xAxis = d3.axisBottom(x);
color = "#bb8fce";
}
var yAxis = d3.axisLeft(y);
// Añadimos el svg del grafico
var svg = d3.select("#cartograma").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Leemos los datos y creamos las variables dependientes de los datos como ejes, barras, ...
d3.csv(archivo, function(data) {
data.forEach(function (d){
if(tipo == 0){
// Nos aseguramos que coja como numerico la tasa de obesidad, lo dividimos entre 100 para que nos coja bien el formato del eje y
d.valor = +d.TasaObesidad/100;
}
else{
// Nos aseguramos que coja como numerico el numero de restaurantes
d.valor = +d.Nrestaurantes;
}
});
if(ordenado == 1){
// De mayor a menor
data = data.sort(function (a, b) {
return d3.ascending(a.valor, b.valor);
})
}
if(ordenado == 2){
// De menor a mayor
data = data.sort(function (a, b) {
return d3.descending(a.valor, b.valor);
})
}
// Dominio de los ejes
x.domain([0, d3.max(data, function(d) { return d.valor; })]);
y.domain(data.map(function(d) { return d.Estados; }));
// Añadimos ambos ejes
svg.append("g")
.transition()
.duration(1000)
.attr("class", "x axis")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis);
svg.append("g")
.transition()
.duration(1000)
.attr("class", "y axis")
.call(yAxis);
// Añadimos las etiquetas
var labels = svg.append("g")
.attr("class", "labels");
labels.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -89)
.attr("dy", ".73em")
.style("text-anchor", "end")
.style("font-size", "18px")
.style("font-family", 'Gill Sans MT')
.text("Estados de la región");
// Añadimos el titulo
var titulo = "";
if(tipo == 0){
titulo = "Tasa de obesidad por estados";
}
else{
titulo = "Nº de restaurantes por estado";
}
var title = svg.append("g")
.attr("class", "title");
title.append("g")
.attr("class", "title");
title.append("text")
.attr("x", (width/2))
.attr("y", -3)
.attr("text-anchor", "middle")
.style("font-size", "18px")
.style("font-family", 'Trebuchet MS')
.text(titulo);
// Añadimos las barras
// Hay que añadir la interaccion antes de las transiciones, ya que sino no funciona
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.on("mouseover", function(d, i) { // Interaccion cuando pasas el raton por encima
d3.select(this).attr("fill", "red");
info
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "block");
if(tipo == 0){
info.html((d.Estados) + "<br>" + (Math.round(d.valor*10000, -2)/100) + "%");
}
else{
info.html((d.Estados) + "<br>" + (d.Nrestaurantes));
}
})
.on("mouseout", function(d, i) { // Interaccion cuando dejas de pasar el raton por encima
d3.select(this).transition().duration(500).attr("fill", color);
info.style("display", "none");
})
.transition() // Añadimos la transicion para que simular una actualizacion, algo mayor que la de los ejes
.duration(2000)
.attr("x", x(0) )
.attr("y", function(d) { return y(d.Estados); })
.attr("width", function(d) { return x(d.valor); })
.attr("height", y.bandwidth() )
.attr("fill", color);
});
// Si el grafico es el de restaurantes mostramos el selector, ya que es el segundo que se representa
if(tipo == 1){
d3.select("body").select("#ordenar").style("display", "inline-block");
}
}
// Funcion que dibuja un cartograma de Dorling
function dibujaCartogramaD(){
// Ocultamos el selector para ordenar los graficos de barras
d3.select("body").select("#ordenar").style("display", "none");
// Dimensiones del svg
var WIDTH = "1060";
var HEIGHT = "650";
// Valores minimos del gradiente utilizado -> Nos serviran para la leyenda
var lowColor = "#fdebd0";
var highColor = "#f39c12";
var svg = d3.select("#cartograma").append("svg")
.attr("width", WIDTH)
.attr("height", HEIGHT)
.attr("y", "200");
d3.json("us.json", function(err, us) {
// Nos devuelve los estados colindantes para todos los estados
var neighbors = topojson.neighbors(us.objects.states.geometries),
// Cogemos cada arco individual del json ajustandolo como una ruta
nodes = topojson.feature(us, us.objects.states).features;
// Calculo de centros
nodes.forEach(function(node, i) {
var centroid = d3.geoPath().centroid(node);
node.x0 = centroid[0];
// Restamos cierta cantidad para que el cartograma no se desplace tanto hacia abajo y redimensione la pagina
node.y0 = centroid[1] - 130;
// Para limpiar arcos repetidos, ya que los vecinos comparten algunos arcos
cleanUpGeometry(node);
});
// Info desplegable con su clase definida
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
// Pintamos el mapa
var states = svg.selectAll("path")
.data(nodes)
.enter()
.append("path")
.attr("d", pathString)
.attr("fill", "#ccc");
// Lectura de los datos
d3.csv("datos/completo.csv", function(data) {
data.forEach(function (d){
// Nos aseguramos que coja como numerico la tasa de obesidad, lo dividimos entre 100 para que nos coja bien el formato del eje y
d.TasaObesidad = +d.TasaObesidad/100;
// Nos aseguramos que coja como numerico el numero de restaurantes
d.Nrestaurantes = +d.Nrestaurantes;
});
// Escogemos la posicion del dentro del circulo, su radio en funcion del nº de restaurantes, su color en funcion de la tasa de obesidad
nodes.forEach(function(node) {
node.x = node.x0;
node.y = node.y0;
// Lo dividimos, para que nos salga algo proporcionado que se pueda ver en la pantalla
node.r = obtenerRadio(data, node.id)/8;
node.col = obtenerGradiente(data, node.id);
node.info = getInfo(data, node.id);
});
var links = d3.merge(neighbors.map(function(neighborSet, i) {
return neighborSet.filter(j => nodes[j]).map(function(j) {
return {source: i, target: j, distance: nodes[i].r + nodes[j].r + 3};
});
}));
// Mantenemos la adyaciencia de los estados detectando las colisiones que pudiera haber, para evitar solapamientos
var simulation = d3.forceSimulation(nodes)
.force("cx", d3.forceX().x(d => WIDTH / 2).strength(0.02))
.force("cy", d3.forceY().y(d => HEIGHT / 2).strength(0.02))
.force("link", d3.forceLink(links).distance(d => d.distance))
.force("x", d3.forceX().x(d => d.x).strength(0.1))
.force("y", d3.forceY().y(d => d.y).strength(0.1))
.force("collide", d3.forceCollide().strength(0.8).radius(d => d.r + 3))
.stop();
while (simulation.alpha() > 0.1) {
simulation.tick();
}
// Por si fuera necesario mover alguno de los centros de los circulos debido a las colisiones
nodes.forEach(function(node){
var circle = pseudocircle(node),
closestPoints = node.rings.slice(1).map(function(ring){
var i = d3.scan(circle.map(point => distance(point, ring.centroid)));
return ring.map(() => circle[i]);
}),
interpolator = d3.interpolateArray(node.rings, [circle, ...closestPoints]);
node.interpolator = function(t){
var str = pathString(interpolator(t));
if (t > 0.99) {
return str.split("Z")[0] + "Z";
}
return str;
};
});
// Redibujamos los estados, esta vez, con sus respectivos circulos
states
.on('mousemove', function(d){ // Interaccion al pasar el raton por encima
d3.select(this).attr("fill", "red");
tooltip
.style("left", d3.event.pageX + 15 + "px")
.style("top", d3.event.pageY - 10 + "px")
.style("display", "inline-block");
tooltip.html(getInfo(data, d.id));
})
.on('mouseout', function() { // Interaccion al quitar el raton
d3.select(this).attr("fill", function(d) { return d.col});
tooltip.transition()
.duration(400)
.style("display", "none");
})
.sort((a, b) => b.r - a.r)
.transition()
.delay(1500)
.duration(2000)
.attrTween("d", node => node.interpolator)
.attr("fill", function(d) { return d.col}); // Rellenamos en funcion del color asignado
// Añadimos las etiquetas con las abreviaturas de los estados
svg.append("g")
.selectAll("labels")
.data(nodes)
.enter()
.append("text")
.transition()
.delay(1500)
.duration(2500)
.attr("x", function(d){ return d.x})
.attr("y", function(d){ return d.y})
.text(function(d){ return d.id})
.attr("class", "abreviaturas")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "central");
// Añadimos la leyenda
var w = 120, h = 250;
var key = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("class", "legend");
var legend = key.append("defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "100%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
legend.append("stop")
.attr("offset", "0%")
.attr("stop-color", highColor)
.attr("stop-opacity", 1);
legend.append("stop")
.attr("offset", "100%")
.attr("stop-color", lowColor)
.attr("stop-opacity", 1);
// Dibujamos el gradiente con cierto delay en la esquina derecha de abajo del cartograma
key.append("rect")
.transition()
.delay(3000)
.attr("width", w - 100)
.attr("height", h)
.style("fill", "url(#gradient)")
.attr("transform", "translate(10,10)");
var y = d3.scaleLinear()
.range([h, 0])
.domain([22.6, 38.1]);
var yAxis = d3.axisRight(y);
// Dibujamos la escala que va de 22.6 a 38.1
key.append("g")
.transition()
.delay(3000)
.attr("class", "y axis")
.attr("transform", "translate(41,10)")
.call(yAxis);
});
});
// Funciones AUXILIARES
// Funcion para crear los circulos en funcion del radio y la posicion del centro
function pseudocircle(node) {
return node.rings[0].map(function(point){
var angle = node.startingAngle - 2 * Math.PI * (point.along / node.perimeter);
return [
Math.cos(angle) * node.r + node.x,
Math.sin(angle) * node.r + node.y
];
});
}
// Devuelve el nº de restaurantes para un estado en funcion de su id (abreviatura)
function obtenerRadio(data, id){
var radio = 0.3;
for(i = 0; i < data.length; i++){
if(data[i].Abreviaturas == id){
radio = data[i].Nrestaurantes;
}
}
return radio;
}
// Devuele el color en funcion del gradiente de 5 colores especificado en esta y el valor de su tasa
function obtenerGradiente(data, id){
var col = "#f39c12"; // Max
for(i = 0; i < data.length; i++){
if(data[i].Abreviaturas == id){
v = data[i].TasaObesidad;
// Segun los intervalos obtenido en R
if (v > 0.277) col = "#fdebd0"; // Min
if (v > 0.3) col = "#fad7a0";
if (v > 0.321) col = "#f8c471";
if (v > 0.341) col = "#f5b041";
}
}
return col;
}
// Funcion para obtener la informacion del estado seleccionado con el raton
function getInfo(data, id){
var info = "";
for(i = 0; i < data.length; i++){
if(data[i].Abreviaturas == id){
e = data[i].Estados;
o = data[i].TasaObesidad;
r = data[i].Nrestaurantes;
info = e + " (" + id + ")" + "<br> Tasa de obesidad: " + (Math.round(o*10000, -2)/100) + "%" + "<br> Nº de restaurantes de comida rápida: " + r;
}
}
return info;
}
function cleanUpGeometry(node) {
node.rings = (node.geometry.type === "Polygon" ? [node.geometry.coordinates] : node.geometry.coordinates);
node.rings = node.rings.map(function(polygon){
polygon[0].area = d3.polygonArea(polygon[0]);
polygon[0].centroid = d3.polygonCentroid(polygon[0]);
return polygon[0];
});
node.rings.sort((a, b) => b.area - a.area);
node.perimeter = d3.polygonLength(node.rings[0]);
// Optional step, but makes for more circular circles
bisect(node.rings[0], node.perimeter / 72);
node.rings[0].reduce(function(prev, point){
point.along = prev ? prev.along + distance(point, prev) : 0;
node.perimeter = point.along;
return point;
}, null);
node.startingAngle = Math.atan2(node.rings[0][0][1] - node.y0, node.rings[0][0][0] - node.x0);
}
function bisect(ring, maxSegmentLength) {
for (var i = 0; i < ring.length; i++) {
var a = ring[i], b = i === ring.length - 1 ? ring[0] : ring[i + 1];
while (distance(a, b) > maxSegmentLength) {
b = midpoint(a, b);
ring.splice(i + 1, 0, b);
}
}
}
function distance(a, b) {
return Math.sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]));
}
function midpoint(a, b) {
return [a[0] + (b[0] - a[0]) * 0.5, a[1] + (b[1] - a[1]) * 0.5];
}
function pathString(d) {
return (d.rings || d).map(ring => "M" + ring.join("L") + "Z").join(" ");
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment