我正在尝试在地图上放置一个力布局节点系统。有些节点在我正在使用的json文件中具有lon和lat值。其他节点仅需要连接,而无需地理参考。我想将具有lon和lat值的节点放置在一起,而将其他节点简单地连接起来。
(我发现了下面的示例,但是没有lon和lat值的节点被放置在svg之外:https://bl.ocks.org/cmgiven/4cfa1a95f9b952622280a90138842b79)我也尝试过滤具有lon和lat值但仍然没有运气的节点。
这是我目前得到的:
这是我的代码:
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"
}
答案 0 :(得分:1)
这应该不是问题。但是,到目前为止,您使用的方法会引起一些问题。例如:
.attr('cy', function(d){
if(d.fixed== "true"){
return projection([d.lon, d.lat])[1];
} else {
return d.y;
}
})
此方法可能会冻结表示该节点的圆圈,但该节点将继续在模拟范围内移动。在更新链接时,这肯定会导致视觉问题-它们引用给定节点的仿真位置,而不是其视觉位置。这说明了上图中未连接到节点一端的一些奇数链接。
相反,让我们为每个具有经度和纬度的节点设置fx
和fy
属性,以使模拟永远不会改变其位置,例如:
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.fx
和d.fy
修复了v4中的节点,请参见here
现在,我们可以跳过勾号中的if fixed == true
检查:
.attr('cy', function(d){
return d.y; // d.y == d.fy if d.fy is set
})
现在我们有固定的节点,但是我们应该确保任何取消节点固定的拖动或其他功能都不会取消或移动这些投影的节点。例如,具有拖动功能:
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))
。
将其与一些组成的数据放在一起,我得到:
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>