I have implemented the following version of the force diagram to show inter-cluster movement of nodes.
https://jsfiddle.net/Edwig_Noronha/67ey5rz0/
The nodes are grouped into four clusters. After the first initialization of the force diagram ends I call a function to transition the nodes from source to destination clusters.
function moveNodes() {
Object.keys(inputdata).forEach(function(key, index) {
svg.selectAll("circle.viewernodes" + index)
.each(function(d) {
d.type = d.destination;
});
});
viewersTransitioned = true;
force.start();
}
However, The stabilization of the first initialization of the force diagram takes about 35 seconds. Hence the transition happens after that much time.
Q1) is it possible to achieve a quicker stabilization of the force diagram with collision detection?
The transition of the nodes from source to destination clusters happens along a linear path.
Q2) Is it possible to make the nodes move along projectile paths?
To achieve quicker stabilization you can do one of two things in my experience.
Initialize the nodes X and Y values to be near to their end/goal state
Such as nodes[i].x = 500 etc, then calling the simulation start.
This would somewhat defeat the purpose of what you're trying to show in your example, unless you don't want the nodes to be shown moving to the groups and just be in them to begin with...
Stronger force
Have the force moving/pulling the nodes be stronger. This would require an essentially fundamental change to your approach to this example. Instead of just transitioning their positions, create custom forces within your force-layout that affect the appropriate nodes only based on their attributes. Place these forces in the center of your 'sorting circles' and they would attract the nodes appropriately.
See here for what something like this would look like: https://bl.ocks.org/mbostock/1021841
Related
I'm looking for an algorithm that can find intersecting rectangles that overlap each other.
The catch is that my data structure is similar to a quadtree with bounding boxes instead of points.
I'm doing a basic rectangle intersection check but the problem is as I zoom into the tree the child nodes get detected and the parents, I would like to exclude the parent if its fully occluded by a child for the given camera rectangle.
zoom animation
As you can see from the image above as the camera rectangle (black box) sits inside the green node the purple node is still highlighted (filled), consequently as I zoom more and more the parents are always highlighted, even though the camera rectangle can be fully filled with child nodes only.
This makes sense since the camera rectangle is still inside the parent but I have searched and thought about the problem for a while and can't seem to figure out an elegant solution. There seems to be several ways of doing this for 3D spaces but I can't find anything simple for 2D AABB rectangles.
A few solutions that I thought of:
Subtract the child nodes from the parents resulting in concave polygons and then perform polygon intersection.
Use the fill color to check which rectangles are visible, therefore occluding the ones behind.
Perform raycasting or sub-division and check which is the smallest node for a given section.
Is there a better way to do this?
Thank you
Update 1
I have solved the problem by subdividing the camera into smaller sections and for each section the smallest intersecting node is found. This works but there must be a more efficient and cleaner way of doing this.
Update 2
Thank you Trentium for your answer. I can clearly see that an algorithm like that would be a lot more performant than what I'm currently doing.
Eventually I will implement it as splitting a rectangle into smaller rectangles and not polygons sounds like a fun challenge.
Also, I did some very non scientific benchmarks on the current approach and it takes 0.5ms-1ms to both filter and draw everything, so for now performance is still not a concern.
Suggest considering a variation of your first solution proposed "Subtract the child nodes from the parents resulting in concave polygons and then perform polygon intersection."
Specifically, if the immediate children are wholly contained within the parent, then suggest that for each rectangle, that an associated array of visible residual rectangles also be maintained. And then use this array of residual rectangles as the means of determining if the camera / viewport rectangle includes the parent or not.
For example, let's say the parent rectangle is (0,0) - (100,100) and there is an initial child rectangle added at (75,75)-(100,100). The data structure will appear as...
parent.rectangle = {0,0,100,100}
parent.visible = [ {0,0,100,75}, {0,75,75,100} ]
child1.rectangle = {75,75,100,100}
child1.visible = [ {75,75,100,100} ]
...and then if a second child comes along, say at {50,50,75,90}, then this new child is checked against each residual rectangle in the parent.visible array, subdividing the parent visible rectangles further as necessary...
parent.rectangle = {0,0,100,100}
parent.visible = [ {0,0,100,50}, {0,50,50,75}, {75,50,100,75}, {0,75,50,100}, {50,90,75,100} ]
child1.rectangle = {75,75,100,100}
child1.visible = [ {75,75,100,100} ]
child2.rectangle = {50,50,75,90}
child2.visible = [ {50,50,75,90} ]
This method will add a bit of work up front adjusting the immediate parent's visible rectangles as children are added, but should greatly reduce the amount of rectangle intersection tests relative to the current algorithm that involves subdivision of the camera / viewport. Plus this proposed algorithm only makes use of rectangle-to-rectangle intersection tests (both when adding a child to a parent, and when testing the camera / viewport intersections!) rather than the suggested rectangle-to-polygon tests...
I'm new to d3 and now trying to render a sunburst chart with a really big set of data. But I find out that with the number of paths goes up, the drilling of chart becomes dull, like stops for one sec and then drills.
Is there any way to improve this? I'm thinking to display a limited number of levels but how to make showing levels dynamically change when the center changes?
function click(d) {
var duration = config.animationDuration || config.animationDuration === 0 ? config.animationDuration : 1000;
path.transition()
.duration(duration)
.attrTween("d", arcTween(d))
path.attr("display", function(d){
if(d.depth>config.drillDownPath.length + data.levelLimit-1) return "none";
}); }
In the code above, I tracked the chart drilldown path to decide if a path is shown or not. But this didn't solve the question because the the path still exists in the DOM.
Can anyone help me with this? Thanks!
I know this is a bit late, but I might try this example for displaying a large hierarchical dataset allowing for the user to drill down to any level.
By only rendering and transitioning 2-3 levels of arcs at a time, your transitions will probably lag less and the chart would probably be more readable. However, as mentioned by Lars, it's really hard to give you a concrete demo without seeing more code...
There is an example where we can click on a circle and see inner circles.
Also there are different examples of the force layout.
Is it possible to have a force layout and each node of it will/can be a circle with inner force layout?
So it will work as infinite zoom (with additional data loading) for these circles.
Any ideas/examples are welcome.
I would approach the problem like this: Build a force-directed layout, starting with one of the tutorials (maybe this one since it build something that uses circle packing for initialization). Add D3's zoom behavior.
var force = d3.layout.force()
// force layout settings
var zoom = d3.behavior.zoom()
// etc.
So far, so good. Except that the force layout likes to hang out around [width/2, height/2], but it makes the zooming easier if you center around [0, 0]. Fight with geometric zooming for a little while until you realize that this problem really demands semantic zooming. Implement semantic zooming. Go get a coffee.
Figure out a relationship between the size of your circles and the zoom level that will let you tell when the next level needs to be uncovered. Something like this:
// expand when this percent of the screen is covered
var coverageThreshold = 0.6;
// the circles should be scaled to this size
var maxRadius = 20;
// the size of the visualization
var width = 960;
// which means this is the magic scale factor
var scaleThreshold = maxRadius / (coverageThreshold * width)
// note: the above is probably wrong
Now, implement a spatial data filter. As you're zooming down, you basically want to hide any data points that have zoomed out of view so that you don't waste gpu time computing their representation. Also, figure out an algorithm that will determine which node the user is zooming in on. This very well might use a Voronoi tessalation. Learn way more than you thought you needed to about geometry.
One more math thing we need to work out. We'll have the child nodes take the place of the parent node, so we need to scale their size based on the total size of the parent. This is going to be annoying and require some tweaking to get right, unless you know the right algorithm... I don't.
// size of the parent node
var parentRadius = someNumberPossiblyCalculated;
// area of the parent node
var parentArea = 2 * Math.PI * parentRadius;
// percent of the the parent's area that will be covered by children
// (here be dragons)
var childrenCoverageRatio = 0.8;
// total area covered by children
var childrenArea = parentArea * childrenCoverageArea;
// the total of the radiuses of the children
var childTotal = parent.children
.map(radiusFn)
.reduce(function(a, b) { return a + b; });
// the child radius function
// use this to generate the child elements with d3
// (optimize that divide in production!)
var childRadius = function(d) {
return maxRadius * radiusFn(d) / childTotal;
};
// note: the above is probably wrong
Ok, now we have the pieces in place to make the magic sauce. In the zoom handler, check d3.event.scale against your reference point. If the user has zoomed in past it, perform the following steps very quickly:
hide the parent elements that are off-screen
remove the parent node that is being zoomed into
add the child nodes of that parent to the layout at the x and y location of the parent
explicitly run force.tick() a handful of times so the children move apart a bit
potentially use the circle-packing layout to make this process cleaner
Ok, so now we have a nice little force layout with zoom. As you zoom in you'll hit some threshold, hopefully computed automatically by the visualization code. When you do, the node you're zooming in on "explodes" into all it's constituent nodes.
Now figure out how to structure your code so that you can "reset" things, to allow you to continue zooming in and have it happen again. That could be recursive, but it might be cleaner to just shrink the scales by a few orders of magnitude and simultaneously expand the SVG elements by the inverse factor.
Now zooming out. First of all, you'll want a distinct zoom threshold for the reverse process, a hysteresis effect in the control which will help prevent a jumpy visualization if someone's riding the mousewheel. You zoom in and it expands, then you have to zoom back just a little bit further before it contracts again.
Okay, when you hit the zoom out threshold you just drop the child elements and add the parent back at the centroid of the children's locations.
var parent.x = d3.mean(parent.children, function(d) { return d.x; });
var parent.y = d3.mean(parent.children, function(d) { return d.y; });
Also, as you're zooming out start showing those nodes that you hid while zooming in.
As #Lars mentioned, this would probably take a little while.
I am working on a d3 force layout which requires the nodes to be placed in such a way that there is a central node according to which all the other nodes are radially placed.The nodes are linked to each other like a normal force layout with appropriate source and target. The central node is dictating the position of all the other nodes, so essentially it is the source of all the nodes. Right now all I have been able to manage to do is to place them in a linear fashion using the linkDistance property with one node as the reference, but I need it in a radial manner. I could have shown an image but apparently my reputation is too low and I am not being allowed to post one.Can someone help me out with this?
Take a look at this example:
link to jsfiddle
Central (root) node has special treatment that makes it always remain in the center of the graph. On initialization, central node's property fixed is set to true, so that d3 force layout simulation doesn't move it. Also, it is placed in the center of rectangle containing layout:
root.fixed = true;
root.x = width / 2;
root.y = height / 2;
Hope this helps.
I want to make a D3 graph, which should be as follows:
When the html page is loaded, there will be a single node at a fixed location. Let us say top left. Let us call it template node and this node is non-movable.
When the user does mouse down on the template node, a new node is created at the same location as the template node and the user should be able to drag the new node to where he wants. The new node should remain exactly where the user moves it to.
At any time user should be able to move a node. Again the node should remain where the user leaves it.
User should be able to draw link between any two nodes. Let us assume that if he drags from one node to another without holding down ctrl key, then a link is drawn and if he drags while holding down the control key, then the node moves.
When a link is drawn between two nodes, then the nodes should not change positions.
When two nodes are linked and one of them is moved by dragging it, then the link should change in size and orientation as needed.
I am using force layout.
I am able to create a template node but it always goes to the center of the container - I think it is because the center of the container is the center of gravity. But not sure how to fix its position to the top left through code.
I can create links and new nodes. But the nodes move and links resize. May be it is because force layout tries to make link lengths equal to the link distance in the force layout. But I do not know how to use a function for link distance? I am even not sure if that will really help.
So what method should I use? Any idea?
For force layout, you can set the 'fixed' property of a node to true in order to prevent it from being affected by the simulation. After that, you should be able to set it's position manually. You might choose to do this in a function call:
function pinNode(node) {
node.fixed = true;
}
function unpinNode(node) {
node.fixed = false;
}
I believe you could get a node to the upper left corner with a call like this: pinNode(node, 0, 0). As long as the node has its fixed property set to true, it should remain unaffected by the sim. You might find this snippet from the documentation helpful; it describes how the fixed property is affected by force.drag:
Bind a behavior to nodes to allow interactive dragging, either using
the mouse or touch. Use this in conjunction with the call operator on
the nodes; for example, say node.call(force.drag) on initialization.
The drag event sets the fixed attribute of nodes on mouseover, such
that as soon as the mouse is over a node, it stops moving. Fixing on
mouseover, rather than on mousedown, makes it easier to catch moving
nodes. When a mousedown event is received, and on each subsequent
mousemove until mouseup, the node center is set to the current mouse
position. In addition, each mousemove triggers a resume of the force
layout, reheating the simulation. If you want dragged nodes to remain
fixed after dragging, set the fixed attribute to true on dragstart, as
in the sticky force layout example.
force.drag
Also see here: force layout nodes
If you want to use a function for link distance, include it when you create the force layout:
var force = d3.layout.force()
.size(width, height)
.linkStrength(0.5) // how much can link distance be overridedn by the simulation
.linkDistance(function() {return /* some evaluation */;});
// ...
// You might need to defer the calculation of linkDistance until later,
// such as in update(), since nodes might not have the properties
// that you need to check until that point:
function update() {
force
.nodes(nodes)
.links(links)
.linkDistance(function(link) {
// The function gets called for each link in the simulation.
// Each link will be connected to two nodes, source and target,
// which may be useful in determining link distance.
if (link.source.someProperty || link.target.somePropery) {
return /* something */;
} else {
return /* something else */;
}
});
}