var width = 1300;
var height = 680;
//Width y height de las convenciones
var w = 200;
var h = 115;
//Setup del mapa leaflet en un centro específico y un "estilo" b/n
var map = L.map('map').setView([8.969970, -79.494529], 12);
mapLink = L.tileLayer('https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png').addTo(map);
// sin doble click para zoom
map.doubleClickZoom.disable();
//cuando ya está el mapa se agrega un svg en donde dibujar
var svgLayer = L.svg();
svgLayer.addTo(map);
//svg en donde dibujar
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', w)
.attr('height', h)
.style("top", svg.node().parentNode.offsetTop + 500 + "px")
.style("left", svg.node().parentNode.offsetLeft + 40 + "px")
;
svg2.append("rect")
.attr('x', 0)
.attr('y', 0)
.attr('width', w)
.attr('height', h)
.style('fill', "#fafafa")
.style('opacity', .9)
;
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0)
;
//Define la siulación de fuerza
var fuerza = d3.forceSimulation()
//fuerza atractora hacia el centro de los nodos que no están conectados
.force("x", d3.forceX(width / 2).strength(.3))
.force("y", d3.forceY(height / 2).strength(.3))
//link con el id del json
.force("link", d3.forceLink()
.id(function(d){
return d.id;
})
//distancia de los links
.distance(50))
//fuerza entre los nodos
.force("charge", d3.forceManyBody().strength(-150))
;
//Leer datos de ambos json y llamar la funcion que dibuja todo
d3.json('proyectos_v9.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();
//crea las lineas con un svg y los datos de "edges"
var lineas = g.selectAll("line")
.data(graph.edges)
.enter()
.append("line")
.attr('class', "link")
;
//crea los nodos de acuerdo a los nombres
var nodos = g.selectAll("circle")
.data(graph.nodes)
.enter()
.append("circle")
//dependiendo del tipo o el area le da una clase del CSS
.attr('class', function(d){
if (d.area == "urbano" || d.area == "ambiental" || d.area == "inclusion"){
return "nodos " + d.area;
}
if (d.tipo == "actor" || d.tipo == "colaborador" || d.tipo == "financiador"){
return "nodos " + d.tipo;
}
})
.style("stroke", function(d){
if (d.tipo == "proyecto" || d.tipo == "programa" || d.tipo == "plan"){
return "black";
}
})
.style('stroke-width', 2)
.attr('stroke-dasharray', function(d){
if(d.tipo == "programa"){
return ("1,1");
} else if(d.tipo == "plan"){
return ("2,4")
}
})
.attr('r',5 )
.attr("pointer-events","visible")
//si se hace click se muestran las conexiones
.on("click", connectedNodes)
.on("mouseover", function(d){
tooltip
.html(function(){
return "
" + d.id +
"
" + "
" +
d.descripcion
;
})
.style("top", svg.node().parentNode.offsetTop + height - 260 + "px")
.style("left", svg.node().parentNode.offsetLeft + width - 350 + "px")
.style("opacity", .9)
;
})
.on("mouseout", function(){
tooltip.style("opacity", 0);
})
;
//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('x1', function(d){
return d.source.x;
})
.attr('y1', function(d){
return d.source.y;
})
.attr('x2', function(d){
return d.target.x;
})
.attr('y2', function(d){
return d.target.y;
})
;
//pinta los nodos dentro del radio y el W y Y
nodos
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
;
});
//saber si las conexiones se ven o no
var toggle = 0;
//Create an array logging what is connected to what
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;
});
//This function looks up whether a pair are neighbours
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function connectedNodes() {
if (toggle == 0) {
//Reduce the opacity of all but the neighbouring nodes
d = d3.select(this).node().__data__;
nodos
.transition()
.style("opacity", function (o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0;
})
.attr('r', function(o){
return neighboring(d, o) | neighboring(o, d) ? 10 : 5;
})
.style('stroke-width', function(o){
return neighboring(d, o) | neighboring(o, d) ? 4 : 2;
})
;
lineas
.transition()
.style("opacity", function (o) {
return d.index==o.source.index | d.index==o.target.index ? 0.5 : 0;
})
;
//Reduce the opacity
toggle = 1;
}else {
//vuelve a poner la opacidad en 1 de los proyectos y 0.1 de los actores, etc
nodos
.filter(function(d){
return d.tipo == "proyecto" || d.tipo == "programa" || d.tipo == "plan";
})
.transition()
.style("opacity", 1)
.attr('r', 5)
.style('stroke-width', 2)
;
nodos
.filter(function(d){
return d.tipo == "actor" || d.tipo == "colaborador" || d.tipo == "financiador" ;
})
.transition()
.style("opacity", 1)
.attr('r', 5)
;
// y las lineas a 0
lineas
.transition()
.style("opacity", 0)
;
toggle = 0;
}
}
//Function which sets the transformation attribute to move the circles to the correct location on the map
function drawAndUpdateCircles() {
//si tiene lon y lat clavelos al punto en el mapa
//gracias a Andrew Reid (user:7106086 en stackoverflow)
graph.nodes.forEach(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;
} else {
d.fx = width - 100;
}
})
// reinicie la simulación para que los puntos puedan quedar en donde son si se hace zoom o drag
fuerza
.alpha(1)
.restart()
;
}
function convenciones(){
//CONVENCIONES
// crea el nido donde se miran las categorias de los nodos
var convencion_area = d3.nest()
.key(function(d) {
return d.area;
})
.rollup(function(d) {
return d.length;
})
.entries(graph.nodes)
;
var convencion_tipo = d3.nest()
.key(function(d) {
return d.tipo;
})
.rollup(function(d) {
return d.length;
})
.entries(graph.nodes)
;
// area
var areaX = 15;
var areaY = 20;
var radio = 5;
var offset_texto = 0.7;
var espaciado = 15;
var legend = svg2.append("g")
.append("svg")
.attr("class", "legend")
.selectAll("g")
.data(convencion_area)
.enter()
.append("g")
.attr("transform", function(d, i) {
return "translate(0," + i * espaciado + ")";
})
;
legend.append("circle")
.attr('cx', areaX + radio)
.attr('cy', areaY)
.attr('r', radio)
.attr('class', function (d) {
return "nodos" + (d.key ? " " + d.key: "");
})
.on("click", function(d){
// determine if current line is visible
var active = nodos.active ? false : true
var newOpacity = active ? 0 : 1;
if(d.key == "urbano"){
d3.selectAll(".nodos.urbano")
.style("opacity", newOpacity);
} else if (d.key == "ambiental"){
d3.selectAll(".nodos.ambiental")
.style("opacity", newOpacity);
}else if (d.key == "inclusion")
{
d3.selectAll(".nodos.inclusion")
.style("opacity", newOpacity);
}
nodos.active = active;
})
;
legend.append("text")
.attr("x", areaX + 15)
.attr('y', areaY - offset_texto)
.attr("dy", ".35em")
.text(function(d) {
return d.key;
})
;
// tipo
var tipoX = 100;
var tipoY = 20;
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(0," + i * espaciado + ")";
})
;
legend_2.append("circle")
.attr('cx', tipoX + radio)
.attr('cy', tipoY)
.attr('r', radio)
.attr('class', function (d) {
return "nodos" + (d.key ? " " + d.key: "");
})
.filter(function(d){
return d.key == "proyecto" || d.key == "programa" || d.key == "plan" ;
})
.style('fill-opacity', 0)
.style('stroke', "black")
.style("stroke-width", 2)
.attr('stroke-dasharray', function(d){
if(d.key == "programa"){
return ("1,1");
} else if(d.key == "plan"){
return ("2,4")
}
})
;
legend_2.append("text")
.attr("x", tipoX + 15)
.attr('y', tipoY - offset_texto)
.attr("dy", ".35em")
.text(function(d) {
return d.key;
})
;
}
//Dibuja los circulos actualizados en el mapa
drawAndUpdateCircles();
map.on("moveend", drawAndUpdateCircles);
});