var width = 1890;
var height = 940;
//Width y height de las convenciones
var w = 200;
var h = 160;
var cuadroX = 400;
var cuadroY = 10;
//Setup del mapa leaflet en un centro específico y un "estilo" b/n
var map = L.map('map' , { zoomControl:false }).setView([8.905678, -79.403385], 11);
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');
var cuadroLatLon = map.latLngToLayerPoint(map.getBounds().getNorthEast());
var cuadro = g.append("rect")
.attr('x', cuadroLatLon.x - cuadroX)
.attr('y', cuadroLatLon.y )
.attr('width', 400)
.attr('height', height - 20)
.style('fill', "#fafafa")
.style('opacity', 0.9)
;
//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 + 750 + "px")
//.style("left", svg.node().parentNode.offsetLeft + 40 + "px")
;
svg2.append("rect")
.attr('x', 10)
.attr('y', 10)
.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;}).distance(50)) //distancia de los links
//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_v11.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")
;
function isNodeOnLegend(d) {
return d.tipo === "academia" || d.tipo === "financiacion" || d.tipo === "gobierno" || d.tipo === "ong" || d.tipo === "privado" || d.tipo === "gremios" ;
}
function isNodeOnMap(d){
return d.tipo === "proyecto" || d.tipo === "programa" || d.tipo === "plan";
}
function isNodeByArea(d){
return d.area === "urbano" || d.area === "ambiental" || d.area === "inclusion";
}
function constructNodes(nodeList, className) {
//crea los nodos de acuerdo a los nombres
var nodes = g.append("g")
.attr("class", className)
.selectAll(".nodos")
.data(nodeList)
.enter()
.append("g")
.attr("class", "gnode")
;
nodes.append("circle")
//dependiendo del tipo o el area le da una clase del CSS
.attr('class', function(d){
if (isNodeByArea(d)) { return "nodos " + d.area; }
if (isNodeOnLegend(d)) { return "nodos " + d.tipo; }
})
.style("stroke", function(d) { if (isNodeOnMap(d)) { return "black"; } })
.style('stroke-width', 2)
.attr('stroke-dasharray', function(d){
if (d.tipo == "programa") { return ("1,1"); }
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 + 430 + "px")
.style("left", svg.node().parentNode.offsetLeft + 20 + "px")
.style("opacity", .9)
;
})
.on("mouseout", function() { tooltip.style("opacity", 0); })
;
return nodes;
}
var nodesOnMap = constructNodes(graph.nodes.filter(isNodeOnMap), "onmap");
var nodesOnLegend = constructNodes(graph.nodes.filter(isNodeOnLegend), "onlegend");
var nodesAll = g.selectAll(".gnode");
//filtra los nodos por actor para colocarles una opacidad inicial de 0...
nodesOnLegend
.append("text")
.text(function(d) {return d.id; })
.attr('dx', "1em")
.attr('dy', "0.5em")
.attr('opacity', 1)
;
//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
function nodeUpdate(nodes) {
nodes.attr("transform", function (d) { return `translate(${d.x},${d.y})`; });
}
nodeUpdate(nodesOnMap);
nodeUpdate(nodesOnLegend);
});
//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.parentNode).datum();
nodesAll
.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 {
nodesAll
.transition()
.style("opacity", 1)
;
// 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)
nodesOnMap.each(function(d) {
var cuadroLatLon = map.latLngToLayerPoint(map.getBounds().getNorthEast());
cuadro
.attr('x', cuadroLatLon.x - cuadroX)
.attr('y', cuadroLatLon.y + cuadroY )
;
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;
}
});
var neLatLon = map.latLngToLayerPoint(map.getBounds().getNorthEast());
nodesOnLegend.each( function (d,i) {
d.fx = neLatLon.x - cuadroX + 20;
d.fy = neLatLon.y + 20 + i * 13.7;
});
// 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 = 20;
var areaY = 25;
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 = 25;
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);
});