Based on two D3 examples: Force layout (http://bl.ocks.org/mbostock/1095795) and clustered force layout (http://bl.ocks.org/mbostock/1748247), I managed to built a force layout with a few independent point of gravity to control nodes position on top of the links between nodes.
// Set up map
function map_init(){
force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.on("tick", tick);
svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
link = $map.selectAll(".link");
node = $map.selectAll(".node");
d3.json("graph.json", function(error, graph) {
// set up nodes
for( i = 0; i < graph.nodes.length; i++ ){
nodes.push( graph.nodes[i] );
}
// position nodes to three different gravity centres based on theme
for( i = 0; i < nodes.length; i++ ){
if ( nodes[i].theme == "theme1" ){
nodes[i].cx = 100;
nodes[i].cy = 100;
} else if ( nodes[i].theme == "theme2" ){
nodes[i].cx = 300;
nodes[i].cy = 300;
} else if ( nodes[i].theme == "theme3" ){
nodes[i].cx = 500;
nodes[i].cy = 500;
}
}
// link nodes of the same theme
theme1_nodes = nodes.filter(function(d){ return (d.theme == "theme1"); });
theme2_nodes = nodes.filter(function(d){ return (d.theme == "theme2"); });
theme3_nodes = nodes.filter(function(d){ return (d.theme == "theme3"); });
for (i = 0; i < theme1_nodes.length-1; i++){
links.push({ source: theme1_nodes[i], target: theme1_nodes[i+1] });
}
for (i = 0; i < theme2_nodes.length-1; i++){
links.push({ source: theme2_nodes[i], target: theme2_nodes[i+1] });
}
for (i = 0; i < theme3_nodes.length-1; i++){
links.push({ source: theme3_nodes[i], target: theme3_nodes[i+1] });
}
start();
});
}
// Start
function start() {
link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
link.enter()
.insert("svg:line")
.attr("class", "link");
link.exit()
.remove();
node = node.data(force.nodes(), function(d) { return d.id; });
var nodeEnter = node.enter()
.append("svg:g")
.attr("class", "node");
.on("click", map_nodeClick);
node.exit().remove();
// Enter node information
nodeEnter.each(function(d) {
theTitle = d3.select(this).append("svg:text")
.attr("font-family", "Helvetica")
.attr("class", "title")
.text( d.title );
});
// More content to go into each node
// .
// .
// .
force.start();
}
// Tick
function tick(e) {
node
.each(gravity(.2 * e.alpha))
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
link.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; });
}
// Gravity
function gravity(alpha) {
return function(d) {
d.y += (d.cy - d.y) * alpha;
d.x += (d.cx - d.x) * alpha;
};
}
// Set up when page first loads
map_init();
In order to reset/restart the force layout anytime without reloading the page, I bound the following function to a reset button:
// Remove force layout and data
function map_remove(){
node.remove();
link.remove();
svg.remove();
nodes = [];
links = [];
}
// Reset button
$('a#reset').click(function(e){
e.preventDefault();
map_remove();
map_init();
});
This webpage is displayed on a device accessible by group of people. Only loaded once in the morning and stayed running on iPad Safari for 12 hours. Link between nodes ideally changes dynamically based on users input (to be implemented). Apart from the force layout there are other info on the webpage. An option to relaunch/reset the force layout without reloading the page is required.
I'm guessing that this happens because you're not actually deleting the nodes/links from the force layout. At some point, you've given the variables nodes
and links
to the force layout. Changing what those names point to (i.e. []
) doesn't change the reference in the force layout. That is, the data objects are still there and referenced. There are two ways to remove them. You can either modify the nodes
and links
in place (e.g. with .slice()
), or reset them explicitly in the force layout.
nodes = [];
links = [];
force.nodes(nodes);
force.links(links);
Hard to say without a specific example, but the answer is most likely no. Javascript is garbage collected, so doing it manually shouldn't have an impact.
I did it with:
nodeCircles = {};
node.remove();
link.remove();
svg.clear();
nodes = [];
links = [];
Just put this into a method and then recreate your force and svg. That works great.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With