D3js Force Layout On A Map
Solution 1:
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"){
returnprojection([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.
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
fixes nodes in v3, but d.fx
and d.fy
fix nodes in v4, see here
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 dataif(!d3.event.active) force.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
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;
})
;
})
functiondragInicia(d){
if (!d.lon || !d.lat) {
if (!d3.event.active) force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
}
functiondragging(d){
if (!d.lon || !d.lat) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
}
functiondragTermina(d){
if (!d.lon ||!d.lat) {
if(!d3.event.active) force.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
});
<scriptsrc="https://d3js.org/d3.v5.min.js"></script><scriptsrc="https://d3js.org/topojson.v0.min.js"></script>
Post a Comment for "D3js Force Layout On A Map"