d3js How to gather nodes by cluster un force layout? - d3.js

I have network and each node has a property group obtained by a clustering method. I would like to know what is the best method to render a network with the force layout where nodes belonging to the same group are gathered in space?
One way, but i don't know how to implement it, would be to add a attractive force between nodes of the same group (little compared to the repulsive force applied to all nodes).

one possibility is to devide space in nb_group directions and push nodes in the direction allocated to their group:
var angle = 2*Math.PI/nb_group;
var intensity = 500;
var updateNode = function() {
this.attr("transform", function(d) {
var xm = d.x + intensity*Math.cos(angle*d.group);
var ym = d.y + intensity*Math.sin(angle*d.group);
return "translate(" + xm + "," + ym + ")";
});
}
my intensity is hugh because I have charge of 1000

Related

Avoiding Tangles in Long Networks, D3 Force Diagram

I am using the D3 Force Diagram and trying to find a way to make a network more "linear" and avoid "tangles" when the network generates, especially on long networks.
Example of Tangles
No Tangles, Better but not Ideal
Ideal result, network in a left to right line
Solutions that won't work:
increasing charge
reducing gravity
The example file I built on VizHub is simple, and although increasing charge may seem to work for small networks, 20-50 items will get tangled even with a very strong charge.
I have attempted to seed the location of the nodes starting locations based on the known "rough" rank order of the network. However, this gets disregarded and overwritten immediately by the on(tick) action. I may likely be doing something wrong and may need to do the seeding at a different point in the code but I was not able to find good examples or documentation on this.
I also attempted anchoring some points (less than ideal) but the links disregarded the seeded nodes locations. Again, quite possible that I didn't do it right and that may be my problem, but I was unable to find good examples of anchoring as well.
node.attr("transform", function (d) {
return "translate(" + d.x_seed + "," + d.y_seed + ")";})
Seeding when on("tick",...) disabled
I am constrained to using D3 version 3.5.17 at the latest so I cannot use some of the more modern force tools (like forceY) that would help "flatten" the network.
Any ideas on a good way to get the "ideal" result? Seeding, anchoring, something else?
I've partially answered my question. These have been integrated into the VizHub example now.
How to seed the locations on load.
for (var i = 0; i < nodesData.length; i++){
var t = nodesData[i];
var x_seed = t.rank*width/totalNodes
t.x_seed = x_seed;
t.x = x_seed;
var y_seed = var y_seed = height/2 + Math.random()*height/20 - height/40; // randomize y starting location a little to prevent nodes getting stuck in line
t.y_seed = y_seed;
t.y = y_seed;
}
Anchoring the first, last, and middle nodes:
force.on("tick", function () {
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;});
node
.attr("transform", function (d) {
if(d.rank == totalNodes || d.rank == 1 || d.rank == Math.floor(totalNodes/2)){
d.x = d.x_seed
d.y = d.y_seed
}
return "translate(" + d.x + "," + d.y + ")";
});
});

d3js display only path elements contained in bounding box

I have two topojson files obtained from shapefiles which I append to the same g node of an svg element in a nested loop.
// Layer 1
d3.json("grid.topojson", function(error, grid) {
// Layer 2 (continents)
d3.json("continents.topojson", function(error, continent) {
...
It is possible to click on the continents to zoom in on a particular region (from https://bl.ocks.org/mbostock/4699541).
The grid layer is very dense so it slows down the zoom when clicking on the continents in the continent layer. To get around this, I would like to display the grid layer only after zooming in on a particular continent. I have an .on("click") event that triggers the zoom function:
function clicked(path, d, m_width, m_height, _this, grid) {
var this_class = get_classFromPath(_this);
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .2 / Math.max(dx / m_width, dy / m_height),
translate = [m_width / 2 - scale * x, m_height / 2 - scale * y];
g.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
// .selectAll("path.model-grid")
// .style("display", "inline");
}
In the clicked function, I want to make the grid visible after the zoom, but it seems unnecessary (and is very slow) to useselectAll since only a small part of the total grid is visible in the display. Unfortunately, the shapefile of the grid does not contain any ids I can use to correlate with the id of the continents, so I cannot select grid path elements by id.
Is there a way to find out which path elements of the grid layer are contained in the bounds box after the zoom?
Any help is much appreciated, thanks!

Smooth transitioning between tree, cluster, radial tree, and radial cluster layouts

For a project, I need to interactively change hierarchical data layout of a visualization - without any change of the underlying data whatsoever. The layouts capable of switching between themselves should be tree, cluster, radial tree, and radial cluster. And transitioning should be preferably an animation.
I thought that would be relatively easy task with D3. I started, but I got lost in translations and rotations, data bindings, and similar, so I am asking you for help. Also, probably I am doing something not in the spirit of D3, which is bad since I am seeking a clean solution.
I put together a jsfidle, but it is just a starting point, with added radio buttons, convenient small data set, and initial cluster layout - just to help anybody who wants to take a look at this. Thanks in advance!
UPDATE:
I wanted to focus on links only, so I temporary disabled other elements. Building on #AmeliaBR method, following animations are obtained:
Here is updated jsfiddle.
UPDATE 2:
Now with circles: (excuse my choice of colors)
{doom-duba-doom}
Here is one more updated jsfiddle.
I don't see why it would be that hard so long as all your layouts have the same overall structure of link-paths, circle nodes and text labels.
Just make sure all your objects, including your link paths, have a good data-key that is independent of the data attributes created by the layout functions. Then for each transition, update the data with the results of the appropriate layout function and draw that layout.
I've got the transition to radial tree implemented here: http://jsfiddle.net/YV2XX/5/
Key code:
//Radial Tree layout//
var diameter = 500;
var radialTree = d3.layout.tree()
.size([360, diameter / 2 ])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var radialDiagonal = d3.svg.diagonal.radial()
.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
function transitionToRadialTree() {
var nodes = radialTree.nodes(root), //recalculate layout
links = radialTree.links(nodes);
svg.transition().duration(1500)
.attr("transform", "translate(" + (diameter/2)
+ "," + (diameter/2) + ")");
//set appropriate translation (origin in middle of svg)
link.data(links, function(d){
return d.source.name + d.target.name;})
.transition().duration(1500)
.attr("d", radialDiagonal); //get the new radial path
node.data(nodes, function(d){
return d.name ;})
.transition().duration(1500)
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
node.select("circle")
.transition().duration(1500)
.attr("r", 4.5);
node.select("text")
.transition().duration(1500)
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; });
};
The layout code is all from http://bl.ocks.org/mbostock/4063550, I've just changed it to be an update instead of an initialization.
Also note that I have moved the variable declaration for root outside of the data-reading method, so it can be re-accessed by the transition functions.
Layout still needs some finessing, but you get the idea.
Now, if you wanted one of the transitions to be a partition, treemap or other layout that doesn't use the node-link structure, they it gets more complicated...
I don't have enough reputation to make a comment...so, I am just giving this tiny contribution as a pseudo-answer. After looking at this post, and based on #VividD's perfect comment on how simple the transitions turned out to be, I simply added the Tree Vertical option to the transformations in this fiddle.
The addition is simply this:
var diagonalVertical = d3.svg.diagonal()
.projection(function (d) {
return [d.x, d.y];
});
Anyways, I have bookmarked this highly instructional interaction.

Zoom on tree layout preserve root node location

I am quite new to d3 and am having trouble with zooming and dragging on a tree layout.
EDIT: Updated JSFiddle with information from comments
I have created a sample, JSFiddle here.
My issue is in the zoom function:
function zoom() {
var scale = d3.event.scale,
translation = d3.event.translate,
tbound = -height * scale * 100,
bbound = height * scale,
lbound = (-width + margin.right) * scale,
rbound = (width - margin.bottom) * scale;
console.log("pre min/max" + translation);
// limit translation to thresholds
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)];
console.log("scale" + scale);
console.log("translation" + translation);
d3.select("g")
.attr("transform", "translate(" + translation + ")" +
" scale(" + scale + ")");
}
If you expand and collapse nodes and then try to drag, the root node always goes back to the top left corner. I added some logging that shows that the value of translation in this case is -1,-1
Is there a way I can preserve the current root node position?
The problem is that the g element you're translating with the zoom behaviour is initialised with a non-zero translation. The zoom behaviour is not aware of this, resulting in the "jump" you see. To fix, initialise the zoom behaviour with that translation.
var zb = d3.behavior.zoom().scaleExtent([0.5, 5]).on("zoom", function () {
zoom();
});
zb.translate([margin.left, margin.top]);
Complete example here.

two graph of d3 in same page are getting effected when events happening in other graph

in a html page we have two divs.
one div consist of tree layout and second div consist of other graph .both are written in d3.
d3.select("g").transition().duration(duration).attr("transform",
"translate(" + x + "," + y + ")scale(" + scale + ")");
the above statement d3.select('g') is causing issue,it is trying to select the other div as well and it is effecting it.
tried adding id to each container but didnt worked.
thanks in advance
There are a few things you can do to differentiate between elements.
Give IDs to the divs and use them in the selector. d3.select("#divone > svg > g")
Assign different classes to the g elements. d3.select("g.classone")
Keep references to the SVGs when creating them and select from those.
Here's some example code for this way:
var svg1 = d3.select("#divone").append("svg"),
svg2 = d3.select("#divtwo").append("svg");
// more code
svg1.select("g");
Which way is the best depends entirely on your application, but in general the last solution is the safest one as you're keeping explicit references to your subselections.
Use something like this
function animateFirstStep() {
d3.select(this).transition().delay(0).duration(
100)
.attr("r", function(d) {
return d.r + 4;
});
or pass selector in place of this.
say the name if your function is generateChart(selector)
call the function like this generateChart("#NameofDiv")
it should work
Use something like this
.......
var g = d3.select(this)
redraw(g);
.....
function redraw(g) {
g.selectAll(".resize").attr("transform", function (d) {
"translate(" + x + "," + y + ")scale(" + scale + ")");
});
}

Resources