//Tamaño de la viz
var width = screen.width;
var height = screen.height;
var ancho_convenciones = 760;
var alto_convenciones = (width / 100) * 2.5;
// ancho y alto cuadro actores
var anchoCuadroActores = (width / 100) * 16;
var altoCuadroActores = (height / 100) * 50;
//Setup del mapa leaflet en un centro específico y un "estilo" (layer) b/n
var map = L.map('map', { zoomControl:false }).setView([4.624565, -74.101109], 12);
//mapLink = L.tileLayer('https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png').addTo(map);
mapLink = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png').addTo(map);
//mapLink = L.tileLayer('http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png').addTo(map);
//mapLink = L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png').addTo(map);
map.doubleClickZoom.disable();
//map.dragging.disable();
//map.scrollWheelZoom.disable();
//cuando ya está el mapa se agrega un svg en donde dibujar
var svgLayer = L.svg();
svgLayer.addTo(map);
//seleccion el svg del mapa y crea un grupo
var svg = d3.select("#map").select("svg");
var g = svg.select('g');
//svg en donde dibujar las convenciones
var svg2 = d3.selectAll("#map")
.append("svg")
.attr('class', "convenciones")
.attr('width', ancho_convenciones)
.attr('height', alto_convenciones)
.style("top", svg.node().parentNode.offsetTop + (height / 100) * 80 + "px")
.style("left", svg.node().parentNode.offsetLeft + (width / 100) * 3 + "px")
;
// crea el tooltip y le da las características
var tooltip = d3.select("body")
.append("div")
.style("top", ((height / 100) * 15) + "px")
.style("left", ((width / 100) * 3) + "px")
.attr("class", "tooltip")
.style("opacity", 0)
;
// crea el tooltip y le da las características
var tooltip_2 = d3.select("body")
.append("div")
.style("top", ((height / 100) * 50) + "px")
.style("left", ((width / 100) * 3) + "px")
.attr("class", "tooltip")
.style("opacity", 0)
;
//Define la simulación de fuerza
var fuerza = d3.forceSimulation()
.force("link", d3.forceLink()
.id(function(d){
return d.id;
})
)
;
//Leer datos de ambos json y llamar la funcion que dibuja todo
d3.json('proyectos y actores_v4.json', function(data){
//pasar los datos a una variable
var graph = data;
//Printea los datos para verificar
console.log("Número de Actores y Proyectos: " + graph.nodes.length)
console.log(graph.nodes);
console.log("Número de links: " + graph.edges.length)
console.log(graph.edges);
//dibuja las convenciones
convenciones();
// cuadro debajo de los actores, la posicion se coloca en la funcion que actualiza todo
var cuadroActores = g.append("rect")
.attr('width', anchoCuadroActores)
.attr('height', altoCuadroActores)
.style('fill', "#fafafa")
.style('opacity', 1)
.attr("rx", 2)
.attr("ry", 2)
//.style("filter", "url(#drop-shadow)")
;
//crea los links entre nodos con un svg y los datos de "edges"
var lineas = g.append('g')
.selectAll("line")
.data(data.edges)
.enter()
.append("path")
.attr('class', "link")
;
// decide cuales nodos van en el proceso, cuales en el mapa y cuales de los actores
function isNodeOnLegend(d) {
return d.tipo === "actor";
}
function isNodeOnMap(d){
return d.tipo === "iniciativa" || d.tipo === "iniciativa_juvenil";
}
function isNodeOnBottom(d){
return d.tipo === "penal";
}
//funcion donde se pintan los nodos
function constructNodes(nodeList, className) {
//crea el grupo donde pintar los nodos
var nodes = g
.append("g")
.attr("class", className)
.selectAll(".nodos")
.data(nodeList)
.enter()
.append("g")
.attr("class", "gnode")
;
//pinta la nodos, características y tooltip
nodes.append("circle")
.attr('class', function(d){
if (isNodeOnBottom(d)) { return "nodos " + d.proceso; }
if (isNodeOnLegend(d)) { return "nodos " + d.bloque; }
if (isNodeOnMap(d)) { return "nodos " + d.tipo; }
})
.attr('r', 6)
.attr("pointer-events","visible")
.on("click", connectedNodes)
.on('mouseover', function(d){
tooltip
.html(function(){
if (d.tipo == "iniciativa" || d.tipo == "iniciativa_juvenil"){
return d.id + "
" + d.descripción;
} else {
return d.descripción;
}
})
.transition()
.style("opacity", 1)
;
tooltip_2
.html(function(){
return "VÍCTIMA" + "
" + d.víctima + "
" + "OFENSOR" + "
" + d.ofensor;
})
.transition()
.style("opacity", 1)
;
})
.on('mouseout', function() {
tooltip
.transition()
.style("opacity", 0)
;
tooltip_2
.transition()
.style("opacity", 0)
;
})
.attr('opacity', 1)
;
return nodes;
}
function dibujar_tooltip(){
tooltip
.html(function(){
return d.descripción;
})
.transition()
.style("opacity", 1)
;
tooltip_2
.html(function(){
return "VÍCTIMA" + "
" + d.víctima + "
" + "OFENSOR" + "
" + d.ofensor;
})
.transition()
.style("opacity", 1)
;
}
//guarda los nodos en una variable dependiendo de un filtro
var nodesOnMap = constructNodes(graph.nodes.filter(isNodeOnMap), "onmap");
var nodesOnLegend = constructNodes(graph.nodes.filter(isNodeOnLegend), "onlegend");
var nodesOnBottom = constructNodes(graph.nodes.filter(isNodeOnBottom), "onbottom");
var nodesAll = g.selectAll(".gnode");
// agrega los textos de los nodos de los actores
var textoActores = svg
.append("g")
.selectAll("text")
.data(graph.nodes.filter(isNodeOnLegend))
.enter()
.append("text")
.text(function(d) {
return d.id;
})
.attr('class', "text")
.attr('dx', "1em")
.attr('dy', "0.3em")
.attr('font-size', '11')
;
// agrega los textos de el proceso penal
var textoProceso = svg
.append("g")
.selectAll("text")
.data(graph.nodes.filter(isNodeOnBottom))
.enter()
.append("text")
.text(function(d) {
return d.id;
})
.attr('class', "text")
.attr('dy', "2.5em")
.attr("text-anchor", "middle")
.attr('font-size', '9')
;
//le dice a la simulacion cuales son los nodos y los links
fuerza.nodes(graph.nodes);
fuerza.force("link").links(graph.edges);
//simulación y actualizacion de la posicion de los nodos en cada "tick"
fuerza.on("tick", function (){
lineas.attr("d", function(d) {
return `M${d.source.x},${d.source.y}Q${width*0.7},${height*0.7} ${d.target.x},${d.target.y}`;
});
//funcion para actualizar la posicion de los nodos (rioV8)
function nodeUpdate(nodes) {
nodes.attr("transform", function (d) { return `translate(${d.x},${d.y})`; });
}
nodeUpdate(nodesOnMap);
nodeUpdate(nodesOnLegend);
nodeUpdate(nodesOnBottom);
});
//saber si las conexiones se ven o no
var toggle = 0;
//Crea un arreglo de las cosas que estan conectadas entre si
var linkedByIndex = {};
for (i = 0; i < graph.nodes.length; i++) {
linkedByIndex[i + "," + i] = 1;
};
graph.edges.forEach(function (d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
//Funcion para determinar si dos nodos estan conectados
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
// Función para saber que hacer cuando se haga click
function connectedNodes() {
if (toggle == 0) {
//Reduce la opacidad de todo menos de los que esten conectados
d = d3.select(this.parentNode).datum();
nodesAll
.transition()
.style("opacity", function (o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0;
})
;
lineas
.transition()
.style("opacity", function (o) {
return d.index==o.source.index | d.index==o.target.index ? 1 : 0;
})
;
textoActores
.transition()
.style('opacity', function(o){
return neighboring(d, o) | neighboring(o, d) ? 1 : 0;
})
textoProceso
.transition()
.style('opacity', function(o){
return neighboring(d, o) | neighboring(o, d) ? 1 : 0;
})
d3.selectAll('.lineasProceso')
.transition()
.style('opacity', 0)
;
d3.selectAll('.tooltip')
.style("opacity", 1)
;
tooltip
.style('opacity', 1)
;
toggle = 1;
} else {
// devuelve los nodos a la normalidadlos links invisibles
nodesAll
.transition()
.style("opacity", 1)
;
lineas
.transition()
.style("opacity", 0)
;
textoActores
.transition()
.style('opacity', 1)
;
textoProceso
.transition()
.style('opacity', 1)
;
d3.selectAll('.lineasProceso')
.transition()
.style('opacity', 1)
;
toggle = 0;
}
}
//Function which sets the transformation attribute to move the circles to the correct location on the map
function drawAndUpdateCircles(){
//Ubicación de los nodos en el mapa
var cuadroActoresLatLon = map.latLngToLayerPoint(map.getBounds().getNorthEast());
var cuadroProcesoLatLon = map.latLngToLayerPoint(map.getBounds().getNorthWest());
//Numero de nods de actores y del proceso
var numeroNodosActores = graph.nodes.filter(isNodeOnLegend);
// variable del espaciado entre Actores
var espaciadoActores = (altoCuadroActores) / numeroNodosActores.length;
//si tiene ubicacion, anclelos al mapa
nodesOnMap.each(function(d) {
if (d.lon && d.lat) {
p = new L.LatLng(d.lat, d.lon);
var layerPoint = map.latLngToLayerPoint(p);
d.fx = layerPoint.x;
d.fy = layerPoint.y;
}
});
// posicion arreglada del cuadro de actores
cuadroActores
.attr('x', cuadroActoresLatLon.x - (anchoCuadroActores * 1.1))
.attr('y', cuadroActoresLatLon.y + ((height / 100) * 20))
;
// nodos del cuadro de actores
nodesOnLegend.each( function (d, i) {
d.fx = cuadroActoresLatLon.x - (anchoCuadroActores * 1.025);
d.fy = cuadroActoresLatLon.y + ((height / 100) * 22) + i * espaciadoActores;
});
//textos de los actores
textoActores.each(function(d, i, nodesOnLegend){
d3.select(nodesOnLegend[i])
.attr('x', cuadroActoresLatLon.x - (anchoCuadroActores * 1) )
.attr('y', cuadroActoresLatLon.y + ((height / 100) * 22) + i * espaciadoActores)
});
fuerza
.alpha(1)
.restart()
;
}
// Función para saber cómo hacer lo de las convenciones
function convenciones(){
//cuadro de las convenciones
var cuadroConv = svg2.append("rect")
.attr('x', 0)
.attr('y', 0)
.attr('width', ancho_convenciones)
.attr('height', alto_convenciones)
.style('fill', "#fafafa")
.style('opacity', 1)
//.style("filter", "url(#drop-shadow)")
;
var convencion_tipo = d3.nest()
.key(function(d){
return d.tipo;
})
.rollup(function(d) {
return d.length; })
.entries(graph.nodes)
;
// crea el nido donde se miran las categorias de los nodos
var convenciones_bloque = d3.nest()
.key(function(d) {
return d.bloque;
})
.rollup(function(d) {
return d.length;
})
.entries(graph.nodes)
;
var radio = 5;
var offset_texto = 0.7;
var espaciado = 120 ;
// Circulos del Tipo
var tipoX = 20;
var tipoY = alto_convenciones / 2;
//Circulos de los bloques
var bloqueX = 500;
var bloqueY = alto_convenciones / 2;
var legend = svg2.append("g")
.append("svg")
.attr("class", "legend")
.selectAll("g")
.data(convenciones_bloque)
.enter()
.append("g")
.attr("transform", function(d, i) {
return "translate(" + i * espaciado + ",0)";
;
});
legend.append("circle")
.attr('cx', (bloqueX + radio) - 170)
.attr('cy', bloqueY)
.attr('r', radio)
.attr('class', function (d) {
return "nodos" + (d.key ? " " + d.key: "");
})
;
legend.append("text")
.attr("x", (bloqueX + 15) - 170)
.attr('y', bloqueY)
.attr("dy", ".35em")
.style('font-size', 10 + "px")
.text(function(d) {
return d.key;
})
;
var legend_2 = svg2.append("g")
.append("svg")
.attr("class", "legend")
.selectAll("g")
.data(convencion_tipo)
.enter()
.append("g")
.attr("transform", function(d, i) {
return "translate(" + i * espaciado + ",0)";
})
;
legend_2.append("circle")
.attr('cx', (tipoX + radio) - 120)
.attr('cy', tipoY)
.attr('r', radio)
.attr('class', function (d) {
return "nodos" + (d.key ? " " + d.key: "");
})
;
legend_2.append("text")
.attr("x", (tipoX + 15) - 120)
.attr('y', tipoY)
.attr("dy", ".35em")
.style('font-size', 10 + "px")
.text(function(d) {
if(d.key == "iniciativa_juvenil"){
return "SRPA";
} else if (d.key == "iniciativa"){
return "Sistema Penal"
}
})
.attr('class', 'text')
;
}
//Dibuja los circulos actualizados en el mapa
drawAndUpdateCircles();
map.on("moveend", drawAndUpdateCircles);
});