d3js强制在地图上进行布局 [英] d3js force layout on a map

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

问题描述

我正在尝试在地图上放置一个力布局节点系统.一些节点在我正在使用的json文件中具有lon和lat值.其他节点仅需要连接,而无需地理参考.我想将具有lon和lat值的节点放置在一起,而将其他节点简单地连接起来.

I'm trying to place a force layout node system on a map. SOME of the nodes have lon and lat values in the json file I'm using. Other nodes only need to be connected but not georeferenced. I would like to place in position the nodes which have lon and lat values and the others simply to be connected.

(我发现了下面的示例,但是没有lon和lat值的节点被放置在svg之外: https://bl.ocks.org/cmgiven/4cfa1a95f9b952622280a90138842b79 )我也尝试使用lon和lat值过滤节点,但仍然没有运气.

(I found this example which I followed but the nodes without lon and lat values are placed outside the svg: https://bl.ocks.org/cmgiven/4cfa1a95f9b952622280a90138842b79) I also tried to filter the nodes with lon and lat values but still no luck.

这是我目前得到的:

这是我的代码:

var w = 1340;
var h = 620;

//Zoom del mapa porque panamá es muy peque en la aproyección
var zoomOffset = 75000;
var wOffset = 103300;
var hOffset = 11500;
var escala = 0.50;

//Tipo de proyección del mapa escalado y transladado
//posicion del mapa
var projection = d3.geoMercator()
                   .translate([w + wOffset, h + hOffset])
                   .scale([zoomOffset])
;

//Los paths que toman el tipo de proyección
var path = d3.geoPath().projection(projection);

//El "centro" del pais
var center = projection([9.018, -79.500])
;

//Esquema de colores
var color = d3.scaleOrdinal(d3.schemeCategory20);

//Define la siulación de fuerza
var fuerza = d3.forceSimulation()
                .force("link", d3.forceLink()
                    .id(function(d){
                        return d.id;
                    })
                .distance(40))
                .force("charge", d3.forceManyBody().strength(-5))
                .force("center", d3.forceCenter(w/2, h/2))
;

//Leer datos de ambos json y llamar la funcion que dibuja todo
d3.queue()
    .defer(d3.json, 'proyectos_v5.json')
    .defer(d3.json, 'panama.json')
    .awaitAll(dibujar)
;

//Leer los datos y dibujar los assets y el mapa
function dibujar (error, data){
    if (error) {throw error}

    //Leer los datos de los json y ponerlos en arreglos distintos
    var graph = data[0];
    var features = data[1].features;

    //Printea los datos para verificar
    console.log(graph);
    console.log(features);

    //Le dice a la simulación cuales son los nodos y los links
    fuerza.nodes(graph.nodes);
    fuerza.force("link").links(graph.edges);

    //svg en donde dibujar
    var svg = d3.selectAll("body")
            .append("svg")
            .attr('width', w)
            .attr('height', h)
    ;

    //grupo en donde esten todos los objetos draggeables
    var mapa = svg.append("g")
        .attr('id', "mapa") //para luego dibujar los circulos y el mapa
        //dibuja el mapa, sin zoom porque no se necesita
        .selectAll("path")
        .data(features)
        .enter()
            .append("path")
            .attr("d", path)
            .style('fill', "#EDEDED")
    ;

        //crea las lineas con un svg y los datos de "edges"
    var lineas = svg.append('g')
        .selectAll("line")
        .data(graph.edges)
        .enter()
            .append("line")
            .style("stroke", "black")
            .style('stroke-width', 1)
    ;

    //crea los nodos de acuerdo a los nombres
    var nodos = svg.append('g')
        .selectAll("circle")
        .data(graph.nodes)
        .enter()
            .append("circle")
            .style('fill', function(d, i){
            return color(i);
            })
            .attr('r',5 )
            .call(d3.drag()
            .on("start", dragInicia)
            .on("drag", dragging)
            .on("end", dragTermina)) //llama la el metodo de nodos dragg y le dice que hacer en cada momento
    ;

    nodos.append("title")
        .text(function(d){
            return d.id;
    });

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

        nodos
        .attr('cx', function(d){
            if(d.fixed== true"){
                return projection([d.lon, d.lat])[0];
            } else {
                return d.x;
            }
        })
        .attr('cy', function(d){
            if(d.fixed== "true"){
                return projection([d.lon, d.lat])[1];
            } else {
                return d.y;
            }
        })
        ;       
    })

    //crea las funciones para saber qué hacer en cada momento del dragging
    function dragInicia(d){
        if (!d3.event.active) fuerza.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
    }

    function dragging(d){
        d.fx = d3.event.x;
        d.fy = d3.event.y;
    }

    function dragTermina(d){
        if(!d3.event.active) fuerza.alphaTarget(0);
        d.fx = null;
        d.fy = null;
    }
};

和一些json:

    {
    "id": "Urbanicación La Marina",
    "lat": 9.0463,
    "lon": -79.4204,
    "año": 2019,
    "tipo": "proyecto",
    "area": "urbano",
    "extension": "",
    "estado": "",
    "publico": "",
    "fixed": "true"
  },
  {
    "id": "Zona Logística del aeropuerto de Tocumen",
    "lat": 9.0567,
    "lon": -79.4191,
    "año": 2019,
    "tipo": "proyecto",
    "area": "urbano",
    "extension": "",
    "estado": "",
    "publico": "",
    "fixed": "true"
  },
  {
    "id": "100 ciudades resilentes",
    "lat": "",
    "lon": "",
    "año": "",
    "tipo": "actor",
    "area": "",
    "extension": "",
    "estado": "",
    "publico": "",
    "fixed": "false"
  },
  {
    "id": "ACOBIR",
    "lat": "",
    "lon": "",
    "año": "",
    "tipo": "actor",
    "area": "",
    "extension": "",
    "estado": "",
    "publico": "",
    "fixed": "false"
  }

推荐答案

这应该不是问题.但是,到目前为止,您使用的方法会引起一些问题.例如:

This shouldn't be a problem. However, the approach you have so far will cause some problems. For example:

.attr('cy', function(d){
  if(d.fixed== "true"){
    return projection([d.lon, d.lat])[1];
  } else {
    return d.y;
  }
})

此方法可能会冻结代表该节点的圆圈,但该节点将继续在模拟范围内移动.在更新链接时,这肯定会导致视觉问题-它们引用给定节点的仿真位置,而不是其视觉位置.这说明了上图中未连接到节点一端的某些奇数链接.

This approach might freeze the circle representing the node, but the node continues to move within the simulation. This will certainly cause visual problems when updating the links - they reference the simulation's position for a given node, not its visual position. This explains some of the odd links that aren't connected to nodes at one end in your image above.

相反,让我们为每个具有纬度和经度的节点设置一个fxfy属性,以使模拟永远不会改变其位置,例如:

Instead, lets set an fx and fy property for each node that has a latitude and longitude so that the simulation never changes its position, something like:

graph.nodes.forEach(function(d) {
    if(d.lon && d.lat) { 
        var p = projection([d.lon,d.lat]);
        d.fx = p[0];
        d.fy = p[1];
    }
})

d.fixed = true修复了v3中的节点,但是d.fxd.fy修复了v4中的节点,请参见此处

d.fixed = true fixes nodes in v3, but d.fx and d.fy fix nodes in v4, see here

现在,我们可以在勾号中跳过if fixed == true检查:

Now we can skip the if fixed == true check in the tick:

  .attr('cy', function(d){
      return d.y;  // d.y == d.fy if d.fy is set
   })

现在我们有固定的节点,但是我们应该确保任何取消节点固定的拖动或其他功能都不会取消或移动这些投影的节点.例如,具有拖动功能:

Now we have nodes that are fixed, but we should make sure that any dragging or other function which unfixes nodes doesn't unfix or move these projected nodes. For example with the drag functions:

function dragTermina(d){
    if (!d.lon ||!d.lat) {  // don't move nodes with geographic data
        if(!d3.event.active) force.alphaTarget(0);
        d.fx = null;
        d.fy = null;
    }
}

此外,由于您的可视化对象已通过地理坐标固定在地面上,因此我们无需将节点居中:.force("center", d3.forceCenter(w/2, h/2)).

Also, since your visualization is anchored to the ground with geographic coordinates, we don't need to center the nodes with: .force("center", d3.forceCenter(w/2, h/2)).

将其与一些组成的数据放在一起,我得到:

Putting that together, with some made up data, I get:

	var width = 960;
	var height = 500;
	
	
	var graph = { nodes : [
		{id: "New York", lat: 40.706109,lon:-74.01194 },
		{id: "London", lat: 51.508070, lon: -0.126432 },
		{id: "Montevideo", lat: -34.901776, lon: -56.163983 },
		{id: "London-NewYork1" },
		{id: "London-NewYork2" },
		{id: "London-NewYork3" },
		{id: "Montevideo-London"}
	  ],
      links : [
		{ source: "New York", target: "London-NewYork1" },
		{ source: "New York", target: "London-NewYork2" },
		{ source: "New York", target: "London-NewYork3" },
		{ source: "London-NewYork1", target: "London" },
		{ source: "London-NewYork2", target: "London" },
		{ source: "London-NewYork3", target: "London" }	,	
		{ source: "London", target: "Montevideo-London" },
		{ source: "Montevideo-London", target: "Montevideo" }
	  ]
	}
	
	
    var force = d3.forceSimulation()
        .force("link", d3.forceLink()
            .id(function(d){
                return d.id;
        })
        .distance(10))
        .force("charge", d3.forceManyBody().strength(-200));

		
	var svg = d3.select("body")
	  .append("svg")
	  .attr("width",width)
	  .attr("height",height);
	  
	var projection = d3.geoMercator()
	  .center([0,10])
	  .translate([width/2,height/2]);
	  
	var path = d3.geoPath().projection(projection);
	
	var g = svg.append("g");
	
    d3.json("https://unpkg.com/world-atlas@1/world/110m.json").then(function(data) {
        g.selectAll("path")
          .data(topojson.object(data, data.objects.countries).geometries)
          .enter()
          .append("path")
          .attr("d", path)
		  .attr("fill","lightgreen");
	
		var links = svg.append('g')
		  .selectAll("line")
		  .data(graph.links)
		  .enter()
		     .append("line")
			 .attr("stroke-width", 2)
			 .attr("stroke", "black");


    	var nodes = svg.append('g')
          .selectAll("circle")
          .data(graph.nodes)
          .enter()
            .append("circle")
            .attr('r',5 )          
			.call(d3.drag()
            .on("start", dragInicia)
            .on("drag", dragging)
            .on("end", dragTermina));
			

	
    force.nodes(graph.nodes);
    force.force("link").links(graph.links);
	
	graph.nodes.forEach(function(d) {
		if(d.lon && d.lat) { 
			var p = projection([d.lon,d.lat]);
			d.fx = p[0];
			d.fy = p[1];
		}
	})
	
	//simulación y actualizacion de la posicion de los nodos en cada "tick"
    force.on("tick", function (){
        links
            .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;
            })
        ;

        nodes
        .attr('cx', function(d){
                return d.x;
        })
        .attr('cy', function(d){
                 return d.y;
        })
        ;       
    })
	
	
    function dragInicia(d){
        if (!d.lon || !d.lat) {
			if (!d3.event.active) force.alphaTarget(0.3).restart();
			d.fx = d.x;
			d.fy = d.y;
		}
    }

    function dragging(d){
		if (!d.lon || !d.lat) {
			d.fx = d3.event.x;
			d.fy = d3.event.y;
		}
    }

    function dragTermina(d){
        if (!d.lon ||!d.lat) {
			if(!d3.event.active) force.alphaTarget(0);
			d.fx = null;
			d.fy = null;
		}
    }
	
				
    });
		

<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/topojson.v0.min.js"></script>

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

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