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.
Related
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
Basically the title. The client is complaining that when he zooms in, the text labels for the nodes are quite large. Is there a way to keep the node labels at a fixed font size even when zooming in or out?
From the nodes documentation (http://visjs.org/docs/network/nodes.html), there's a scaling.label option, but it doesn't seem to work. I think this is only relevant if I'm using values to scale the nodes.
Here is my implementation:
network.on( "zoom", function(properties){
var options = {
nodes: {
// 1/scale to make text larger as scale is smaller
// 16 is my default font size
font: {
size: ( 1 / network.getScale() ) * 16
}
}
};
network.setOptions(options);
});
As far as I know, there's no such option. The scaling.label option, if I understand correctly what you mean, is used to set a scaling factor, not disable zooming.
However, you can implement this yourself, namely change scaling of labels on zoom. Fortunately, there's zoom event: set a handler like
network.on('zoom',rescaleLabels);
and implement rescaleLabels by setting the corresponding scale factor to their labels. In there, you can use network.getScale() to get new scale and then set scaling of nodes.
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 am using force layout. New d3 nodes are created by clicking inside a div element. The node is created at the point of click. The nodes are rectangles of size 50 pixels x 50 pixels. Immediately after creating a node, I set its fixed property to true so that it does not move on its own. I am not calling force.drag. The nodes can be moved by holding down ctrl key and dragging the node. An edge can be created by dragging mouse (without holding ctrl key) from one node to the other.
Now, I want to add the following feature.
The closest distance between any two nodes should be more than a certain minimum. You can assume any positive value for the minimum distance. Let us assume 100 pixels. When any new node is created too close to an existing node, then the nodes should move so that the distance between any two nodes becomes more than 100 pixels. Similarly, when one node is moved and brought too close to another, then also the nodes should move to maintain minimum 100 pixels distance.
There is no condition on which nodes to move and in which direction. One way is to check coordinates and distances and then calculate which nodes to move, how much, in what direction and execute code accordingly. But, is there a simpler way in d3?
Consider just using force.linkDistance() and force.linkStrength() to achieve this. linkDistance represents your minimum distance constraint, and linkStrength (in the range of [0, 1]) determines how 'rigid' the link distance is, or how much linkDistance can be overridden by the simulation.
force.linkDistance
force.linkStrength
Is there an equivalent to jQuery's slideDown method for SVG elements? Right now the elements just show up rather suddenly. I would like to soften that a little by sliding down.
Edit:
I'm basing my code on the dynamic stacked bar graph here: http://benjchristensen.com/2011/12/16/dynamic-stacked-bar-chart-using-d3-js/
Instead of one graph with many bars, I have many graphs with one bar. I want to make it so that when the graphs appear, it will slowly slide down.
I don't believe that SVG is easily supported by jQuery. Keith Wood has a SVG specific jQuery library that allows you to do some animation though.
A different option would be to use a graphics library that works with SVG. The first that comes to mind is D3.js, which provides some very easy to use methods for manipulating SVG elements. Additionally, the selector style is similar to what you may be used to for jQuery.
If you're using D3, I'd check out the transition methods. There's quite a bit you can do and D3 is incredibly powerful. An example would be:
var rect = d3.select('#rectangle');
// this selects a rectangle. Let's say that it starts at
// the origin, or even off screen in your case
rect.transition()
.duration(250)
.attr('x', 10)
.attr('y', 20);
// this changes the x and y attributes
// of the rectangle to 10 and 20 respectively
// using a transition over a 250ms duration.
For the particular example that you gave, I would just change the y attribute so that it starts off screen (say y = -1 * height) and then transition it down to y = height or y = 200 or something like that.