In this example: http://bl.ocks.org/mbostock/1747543:
...Mike shows us how to avoid collision among nodes so that no two nodes overlap each other.
I wonder if it is possible to avoid collision between nodes and edges so that no node 'clips' or overlaps an edge unless it is connected by that edge.
The following example using D3 force-direct shows that node L overlaps with the edge connecting I and A, and similarly, node M overlaps with the edge connecting L and D. How do we prevent such cases?
If your graph doesn't have too many nodes, you can fake it. Just insert one or more nodes for each link, and set their position along the link in the tick handler. Check out http://bl.ocks.org/couchand/7190660 for an example, but the changes to Mike Bostock's version amount to basically just:
var linkNodes = [];
graph.links.forEach(function(link) {
linkNodes.push({
source: graph.nodes[link.source],
target: graph.nodes[link.target]
});
});
and
// force.on('tick', function() {
linkNodes.forEach(function(node) {
node.x = (node.source.x + node.target.x) * 0.5;
node.y = (node.source.y + node.target.y) * 0.5;
});
This will introduce a pretty serious performance overhead if you have very many nodes and edges, but if your graph doesn't get much larger than your example it would hardly be noticed.
You may also want to fiddle with the relative force of the real nodes versus the link nodes.
Take this one step further and you get the nice curved links of http://bl.ocks.org/mbostock/4600693.
Related
How to generate a nodemap in the following manner?
The idea here is to fix the central node at the centre of the viewport and then create coordinates in a circular manner without overlapping and arrange each nodes to corresponding position. This is level 1.
Then we proceed to level two where each child nodes with children becomes level two. There also we generate coordinates in circular manner without overlapping and arrange those child nodes in level two.
Likewise we proceed to subsequent levels.
NOTE note that overlapping should be prevented and also minimum possible radius should be used for circular arrangement. (i.e. without any collision)
Each node has it's own dimensions (length and breadth)
This is what I've tried out
function getCircularAngles(points) {
return [...Array(points).keys()].map(
(num) => num * ((Math.PI * 2) / points)
);
}
function getCoorByAngle(
origin,
angle,
radius
) {
return {
x: origin.x + Math.sin(angle) * radius,
y: origin.y + Math.cos(angle) * radius,
};
}
function getCircularCoords(
origin,
points,
radius
) {
return getCircularAngles(points).map((angle) =>
getCoorByAngle(origin, angle, radius)
);
}
But this is not being able to generate the circular coordinates recursively.
(i.e. by traversing through the child nodes / connections) & also this is not preventing collisions.
SO I need a better way to permute nodes without colliding in a circular manner recursively
I cant use any graphing libraries because the boxes I create are custom svg... I don't think 3rd party libraries are going to support custom svg elements. Need an algorithm in vanilla js.
For the best end result you may want to look into force-directed graph drawing techniques. Basically you run a physics simulation on your graph and it just sorts itself out. Many libraries already do this, including the very popular d3.js (which supports SVG, btw).
Otherwise, you can try doing exactly as you say: start laying out nodes level by level, pre-calculating the minimum radius required to avoid overlap. The problem comes when you've got a big graph. Let's say you have two adjacent nodes that both have a lot of children. You can push one of them out really far from the center so the children of each node don't touch, but a more ideal solution would have those two nodes at opposite sides of your graph. If you're wanting that you'll either need to use a force-directed graph, or else try many permutations and settle on the best one.
You might like to experiment with the GraphViz open-source language tool to see how it decides to plot your diagram.
GraphViz uses a language to describe relationships, then calculates a graph layout by various algorithms which they actually discuss at some length. (Could it be that you could use GraphViz to accomplish this task entirely?)
GraphViz web site
I read many codes about force layout with D3.js and I found that edges elements are only 'source' and 'target'. I want to generate a graph whose layout is like the 'Altas' in Gephi. In my data, edge has elements of 'weight', which describe the correlation of nodes it links, and this is supposed to be took into consideration when start the force layout. So that the similar nodes can gather together. Is there a way to implement so or the physics model used in force layout is irrelevant with the weight of edges?
Yes, that is possible in D3.js, however, I recommend the webcola library since it is more easy and faster and works very well with D3.js.
Each edge may contain other information besides source and target. So, it is easy to add a weight attribute, e.g.:
let edge = {
source: node1,
target: node2,
weight: 2
};
When using webcola (https://github.com/tgdwyer/WebCola/), you can add some constraints to use your weights, or you can use the linkDistance as a function, explained here: https://github.com/tgdwyer/WebCola/wiki/link-lengths, e.g.:
let weightFactor = 10;
let d3cola = cola.d3adaptor(d3)
.size([500, 400])
.linkDistance(function (l) { return l.weight * weightFactor; };
So, the steps are:
Build your graph with D3.js, however,
Simulate it with webcola
You can create an instance of a webcola simulation for d3.js like that:
d3cola.avoidOverlaps(true)
.handleDisconnected(false)
.start(30);
let simulation = this.d3cola
.nodes(graph.nodes) // graph is your graph
.links(graph.edges)
.start();
I have been working with the following code: http://bl.ocks.org/NPashaP/7683252. This is a graphical representation of a tree. I have stripped away much of the code (the graceful labels), only allowing two nodes per parent, and changed the data structure to an array.
The only problem left now is the repositioning. The original code does that perfectly. But since I want a binary tree, I have let the user choose to insert a left or right child. The original reposition code animates the first child straight down from the parent, but that would be wrong in a binary tree. I want it to either go to the left or right.
reposition = function (v) {
function repos(v) {
var lC = getLeafCount(v.v),
left = v.p.x; //parent's x-position
v.c.forEach(function (d) {
var vc = d; //saving reference of the child in parent object
d = tree.getVerticeById(d.v); //actually fetching the child object
var w = 0;
if(d.d == 'right') { w += 15 * lC }
if(d.d == 'left') { w -= 15 * lC }
d.p = {x: left + w, y: v.p.y + tree.h}; //setting the position
vc.p = d.p; //setting the child's pos in parent obj
repos(d);
});
}
repos(v[0]);
};
Some of the parts of my code are different from the original code, because I have changed the data structure as stated before. I have tried to comment the parts that may be confusing, but what is important is the math of the repositioning.
At first, this code seemed to work well (https://i.stack.imgur.com/gjzOq.png). But after some testing I discovered a huge problem with the repositioning: the nodes crash with each other (https://i.stack.imgur.com/pdQfy.png)!
Conclusion: I have tried to modify the original function to take the in mind the left and right positioning of nodes, but could not do it. I wrote this variant of the method which does, but it still has some problems as illustrated in the pictures. I would be thankful for some input in this matter.
I eventually did a quick fix for this problem.
After running the reposition function as posted in the question, i ran another function which fixed the problem. The new function goes through all levels of the tree and checks if the nodes are too close together. If they are, the algorithm finds closest common ancestor, and increases the distance between its left and right children. After this, there is no more collisions between nodes.
This is a continuation of my efforts to build a collapsible tree layout using d3.js.
Generate (multilevel) flare.json data format from flat json
The layout looks like: (http://bl.ocks.org/mbostock/raw/4339083/) with around 3k nodes and depth of some nodes around 25. The current size of the canvas I need to set is 8000px width and 8000px height in order that all nodes are visible which I know is not reasonable when the number of tree levels rendered is 2 or 3.
Furthermore, I intend to make this code reusable with other trees that maybe smaller/larger in size based on what data source(json file) is selected.
So I was wondering if it is possible to resize the canvas size relative to the positions of the nodes/ number of nodes shown on screen. This way, the code would be much more systematic and adaptable.
I saw this:
Dynamically resize the d3 tree layout based on number of childnodes
but this resizes the tree, which if you can imagine in a case of tree with around 3k nodes, makes it hard to read and comprehend.
I know this might not even be related to d3.js but I tagged it to explain my issue and bring in d3 experts too who might have faced a similar condition.
I am also attempting to filter out uninformative nodes based on my criteria so as to render less number of nodes than the actual data. (I know i will run into performance issues with larger trees). Any help would be much appreciated.
NOTE: When I say canvas, I mean the area on which the tree is drawn and not the "canvas". I am not familiar with the jargon so kindly read accordingly.
Hope this helps someone.
I faced similar problems also using the flare tree code as a base for what I was building and the suggested links did not seem to account for a lot of variance in node structure? I have many trees to display with a dynamic number of nodes and structuring. This solution worked for me:
Concerning height: After observing the translate offsets per node, I learned that d.x (vs d.y, as tree is flipped) is an offset from the root node, with the nodes above root going negative and those below going positive. So, I "calculated" the max offset in both directions each time a node is appended, then with that information, adjusted the canvas height and the view translation (starting point of root).
For width: max d.depth * by the (y length normalization the code uses) + margins
let aboveBount = 0
let belowBound = 0
let maxDepth = 0
nodeEnter.each( (d) => {
if( Math.sign(d.x) === -1 && d.x < boundAbove) {
boundAbove = d.x
}
if( Math.sign(d.x) === 1 && d.x > boundBelow) {
boundBelow = d.x
}
if( d.depth > maxDepth){
maxDepth = d.depth
}
})
const newHeight = boundBelow + Math.abs(boundAbove) + nodeHeight*2 + margin.top*2
svg.style('height', newHeight)
svg.style('width'. maxDepth*180 + margin.left*2)
//180 was the amount set to normailze path length
svg.attr('transform', `translate(${margin.left}, ${Math.abs(boundAbove) + margin.top})`)
Well, best wishes and happy coding!
I was facing the similar problem and now I have find out a solution. Check on this link. D3 collapsible tree, node merging issue
I’m generating multiple, random sized, circular elements using the Raphael JavaScript library but because it’s random a lot of the circular elements being generate overlap or cover each other. What I wanted to know, is there any way with JavaScript to tell if one element is in already in particular position so to avoid the overlapping? Essentially, I want to create random elements on a canvas, of a random size that don’t overlap or cover each other.
There's a couple of test files I created here to give you an idea of what I'm doing. The first one generates random objects and the second link sets them to a grid to stop the overlapping.
http://files.nicklowman.co.uk/movies/raphael_test_01/
http://files.nicklowman.co.uk/movies/raphael_test_03/
The easiest way is to create an object and give it a repulsive force that degrades towards zero at it's edge. As you drop these objects onto the canvas the objects will push away from each other until they reach a point of equilibrium.
Your examples aren't working for me, so I cannot visualize your exact scenario.
Before you "drop" an element on the canvas, you could query the positions of your other elements and do some calculations to check if the new element will overlap.
A very simple example of this concept using circle elements might look like this:
function overlap(circ1, circ2) {
var attrs = ["cx", "cy", "r"];
var c1 = circ1.attr(attrs);
var c2 = circ2.attr(attrs);
var dist = Math.sqrt(Math.pow(c1.cx - c2.cx ,2) + Math.pow(c1.cy - c2.cy, 2));
return (dist < (c1.r + c2.r));
}
var next_drop = paper.circle(x, y, r);
for (var i in circles) {
if (overlap(next_drop, circles[i])) {
// do something
}
}
Of course calculating just where you're going to place a circle after you've determined it overlaps with others is a little more complicated.