强制布局中的列表中的节点(在地图上) [英] Nodes as list in force layout (over a map)

查看:90
本文介绍了强制布局中的列表中的节点(在地图上)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在单张地图的顶部构建部队布局可视化.我能够在特定的纬度和经度中找到一些节点,但是现在我在处理其他节点时遇到了麻烦.现在,这就是我所拥有的: https://bl.ocks.org/pierre73ee 9f48af9dba15cb3e2b0c4053f5c47d87cb79e4ba

I'm building a force layout visualization on top of a leaflet map. I was able to locate some of the nodes in a specific lon and lat but now I'm having trouble with the other nodes. Right now this is what I have: https://bl.ocks.org/pierreee1/701c9cea921feec39c256af725277a12/9f48af9dba15cb3e2b0c4053f5c47d87cb79e4ba

我想要的是将这些节点定位为列表.像这样:

What I would like is to position those nodes like a list. Something like this:

因此,如果您想查看链接,可以单击一个节点:

So you can click on a node if you'd like to see the links:

此外,它们与地图一起移动.我应该创建一个新的svg层,以便可以将节点正确地固定在一个固定位置吗?

Also, they move along with the map. Should I create a new svg layer so I can position the nodes correctly with a fixed position?

推荐答案

我如何解决它:

  • 仅使用链接强制更新链接源和目标字段
  • 就像地图上的节点一样,传奇"节点也位于固定位置.位置是根据地图的右上latlon位置确定的.节点分为两组,以使用回调的i参数.
  • 每个节点圆都是g的子级,它被转换为正确的位置.此g具有节点基准对象.
  • 图例节点g也有一个文本节点(id)
  • 由于节点图例的条目过多,因此不得不将地图放大到900px.
  • 重新定位工具提示,因为它与某些图例节点重叠
  • 由于图例节点没有足够的空间增长,因此节点仅对点击的不透明度进行动画处理.
  • only use the link force to update the link source and target fields
  • like the nodes on the map position the "legend" nodes on a fixed position. Position is determined based on the top-right latlon position of the map. Nodes are split in two groups to use the i argument of the callback.
  • every node-circle is a child of a g, that is translated to the correct position. This g has the node datum object.
  • the legend node gs also have a text node (the id)
  • had to enlarge the map to 900px because the node legend had too many entries.
  • reposition the tooltip because it overlaps some of the legend nodes
  • the nodes only animate the opacity on click because the legend nodes do not have enough room to grow big.

我无法使其成为正在运行的示例,因为如果使用示例bl.ock中的proyectos_v9.json,我不知道如何设置d4的交叉原点.

I could not make it a running example because I don't know how to set the cross origin allow for d4 if I use the proyectos_v9.json from your example bl.ock.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
<title>Mapa de Actores y Proyectos en Territorio</title>
<script src="https://d3js.org/d3.v4.js"></script>
<link href="https://fonts.googleapis.com/css?family=Varela+Round" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.3/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.3.3/dist/leaflet.js"></script>
<style type="text/css">
.link {
    stroke: black;
    stroke-opacity: .5;
    stroke-width: 2;
    opacity: 0;
}
.nodos:hover{ cursor: pointer; }
.nodos.urbano { fill: #1C49C4; }
.nodos.ambiental { fill: #57C759; }
.nodos.inclusion{ fill: #E5D064; }
.nodos.actor{ fill: #75C3F0; }
.nodos.colaborador{ fill: #AAF075; }
.nodos.financiador{ fill: #E7E6A6; }
.leaflet-pane { z-index: 98; }
.tooltip {
    position: absolute;
    background-color: #fafafa;
    font-family: 'Varela Round', sans-serif;
    font-size: 10px;
    width: 300px;
    height: 200px;
    padding-left: 10px;
    z-index: 99;
}
br {
    display: block;
    margin: 5px 0;
}
.convenciones{
  position: absolute;
  z-index: 99;
}
</style>
</head>
<body>
<div id="map" style="width: 1300px; height: 900px"></div>
<script>
var width = 1300;
var height = 900;

//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 + 750 + "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;}).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_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")
  ;

  function isNodeOnLegend(d) { return d.tipo === "actor" || d.tipo === "colaborador" || d.tipo === "financiador" ; }
  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 "<br/>" + d.id +
                  "<br/>" + "<br/>" +
                  d.descripcion
              ;
            })
            .style("top", svg.node().parentNode.offsetTop + 90 + "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)
    ;

//     var label = nodos.append("svg:text")
//     .text(function (d) { return d.id; })
//     .style("text-anchor", "middle")
//     .style("fill", "#555")
//     .style("font-family", "Arial")
//     .style("font-size", 12);

  //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);

          //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)
    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;
      }
    });
    var neLatLon = map.latLngToLayerPoint(map.getBounds().getNorthEast());
    nodesOnLegend.each( function (d,i) {
        d.fx = neLatLon.x - 150;
        d.fy = neLatLon.y + 10 + i * 13;
    });

    // 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);

});

</script>
</body>
</html>

这篇关于强制布局中的列表中的节点(在地图上)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆