how to control the simulation speed of d3 force layout - javascript

I am following a d3 force laytout example like this.
I want to control the speed of the dots flying to the cluster. In other words, I want some dots take more time to get to their final positions, while some dots take less time.
I tried to add a timer function to control the time of each tick, but it did not work.
this.force = d3.layout.force()
.on("tick", setTimeout(tick(d), 50));
I need help for this.

Don't set a timer to call the tick function, this is done automatically by the force layout.
There are however a number of parameters you can set to modify the behaviour of the force layout. The ones most relevant to what you're trying to do are the following.
.friction() corresponds to how quickly the velocity decays and therefore directly controls how fast nodes move. The default is 0.9, to make everything slower, set it to a lower value.
.charge() controls how strong the attraction/repulsion between nodes is. This doesn't control the velocity directly, but affects it.
Of these parameters, only the latter can be set on a node-by-node basis. This makes achieving what you want a bit tricky, as you would have to carefully balance the forces. As a start, setting the charge of the nodes that you want to move slower closer to 0 should help.
There are a few other parameters of the force layout that I think would not be useful in your particular case (I'm thinking of .linkStrength() and .linkDistance()), but you may want to have a look nevertheless.

I came to a possible solution. I can just manually call tick function for each node, say 100 times, and record the path. Then I use a timer function to redraw the node according to the path. In this way, I can control the time of drawing for each node. Does this make sense?

Change the setIntervel function , intervel timing
Try this code:
Fiddle:
setInterval(function(){
nodes.push({id: ~~(Math.random() * foci.length)});
force.start();
node = node.data(nodes);
node.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 8)
.style("fill", function(d) { return fill(d.id); })
.style("stroke", function(d) { return d3.rgb(fill(d.id)).darker(2); })
.call(force.drag);
}, 50); //change the intervel time

Related

d3 Force: Making sense of data binding

I can recreate the following 1000 times and have enough of an understanding to do so. But I'm trying to get my head around a few specific bits that I just 'do', rather than understand:
var w = 900,
h = 500;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.attr("style", "border: 1px solid grey;")
.on("mousemove", fn)
var force = d3.layout.force()
.size([w, h])
.on("tick", tick)
.gravity(0)
.charge(0)
.start()
function fn() {
var m = d3.mouse(this);
var point = {x: m[0], y: m[1]};
d3.select("#output").text(force.nodes().length)
var node = svg
.append("circle")
.data([point])
.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y})
.attr("r", 0.1)
.transition().ease(Math.sqrt)
.attr("r", 5)
.transition().delay(1000)
.each("end", function() {
force.nodes().shift()
})
.remove()
force.nodes().push(point)
force.start()
}
function tick() {
svg.selectAll("circle")
.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y})
}
In particular it's the data binding part I'm not sure about.
In function fn() (on mousemove of svg space) we define a new point and we need to do two things with it; push it into force.nodes() so that the x and y coordinates of the point can be manipulated by forces configured in the force layout, and we need to use the coordinates of the point to create and manipulate the visualisation.
So we create the point first off. We then build a circle to represent this point. We push the point into force.nodes() and after a short delay, we remove both the visualisation and the point from the force.nodes() array.
The bit I don't understand is how the visualisation and the point in the array stay "connected"?
Conjecture: The data point is an object which the force layout is constantly updating the x and y properties of. There is a "link" to this object bound to the circle element. The object is therefore easily accessed and used by the circle object, but not without us controlling that process. The circle is defined as having a cx and cy at point of its creation, but we need to keep accessing the underlying data to update its cx and cy?
If that's the case, how is the object "shared" by both force.nodes() and the circle element?
Or am I miles off the mark?
Also I have read a lot of documentation on this but I feel this is something more intrinsic to javascript rather than d3 necessarily, so it's not elaborated on in any literature I've so far read.
The link between the data structures that the force layout updates and the visualization (i.e. the DOM elements) is the tick event handler function. The tick event is generated by the force layout to signify that the force simulation has progressed another step (i.e. tick) and its internal state has changed. This signals that the visualization needs to be updated.
There are two parts to making this link happen. First, the data operated on by the force layout (i.e. the links and nodes) needs to be bound to DOM elements. This is done using the usual .selectAll().data().enter().append() pattern, usually in the initialisation code, sometimes in the tick event handler function. This establishes the link between data and DOM elements.
The second part to this is the code that updates the DOM elements when the force layout changes their positions. This is what happens in the tick event handler function. If you're not adding or removing elements, there's usually no need to rebind data and often you won't see the .selectAll().data() pattern, but only the code that actually updates the positions based on the data already bound to the elements (in your case this works even though you're changing the elements because the data binding happens in the function that updates the data for the force layout as well).
As an experiment, take an arbitrary force layout example and delete the tick event handler function -- you'll see that nothing happens at all even though the force layout is running.

Missing Initial Step from Step Plot D3.js

This is an issue that I have not discovered a clean fix for.
Within my step plot below:
http://jsfiddle.net/q47r3pyk/7/
You can see that I have a vertical blue line at the start of the chart.
This is because I added an additional x-value and y-value so that the first step will show up in the step plot.
If you look at
http://jsfiddle.net/q47r3pyk/8/
where I remove the first dummy entry with
x-value of "12-Jul-14"
y-value of 0
The first step of my step plot will have a width of zero.
Is there a recommended approach within d3.js for having the first step show without losing the last step by using step-after? or a fix for removing the vertical blue line that shows up with my hack of adding a dummy value?
Step is specified in
var line = d3.svg.line()
.x(function (d) {
return x(d.x);
})
.y(function (d) {
return y(+d.y);
})
.interpolate("step-after");
You could clip the path using SVG clip-path:
svg.append("clipPath").attr("id", "canvasClip")
.append("rect")
.attr("height", height)
.attr("width", width)
Then reference the clip-path on your line:
svg.append("path")
.datum(formatted_data)
.attr("class", "line")
.attr("d", line)
.attr("clip-path", "url(#canvasClip)")
Here's the fiddle, it seems to work fine:
http://jsfiddle.net/q47r3pyk/11/
But, ultimately, I think this is a case where the interpolator is acting as it should, and you really shouldn't be thinking about how to subvert it but rather whether it's the right interpolator for your data. In this case, I'd say no, and suggest you use "step" as your interpolator instead of "step-before" or "step-after". I think that would give you the result you're looking for and lines up well with your interactivity.
Is this help?
I have tried several times, i think it is the best result i have got, hope it helps!
x.domain(d3.extent(x_axis, function (d) {
return parseDate(d);
})).nice(x_axis.length);
fiddle

d3.js - How can I expand force directed graph horizontally?

I'm working on a node graph in d3 (seen here: http://jsfiddle.net/u56Tr/1) The graph itself is quite circular (links/nodes spread out relatively equally in all directions) - how would I make this fit better in a 16:9 window? I assume I'd have to expand the x,y positions of the elements but I can't quite get it right.
Relevant D3 code:
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_bg.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
node.attr("x", function(d) {
return d.x - (d.group > 0 ? 24 : 32);
})
.attr("y", function(d) {
return d.y - (d.group > 0 ? 24 : 32);
});
});
You can easily squeeze diagram to fit 16:9 better.
9/16 is .5625. In this fiddle you just look for lines that contain .5625, thats all you need to get your diagram squeezed.
And in this another fiddle you can see your diagram completely smashed, with smaller constant. :) .
Since diagram has a little chaotic look, maybe the good constant for your case would be around 0.4. that way it will fit 16:9 with more certainty.
The point of the force layout is to automatically lay out a graph like this so that you don't have to specify the positions of the nodes yourself. How it does that is largely affected by the connections between the nodes. The graph you have doesn't lend itself very well to being laid out in a wide screen format because of its high connectivity -- that is, many nodes are connected to many other nodes. If you were to stretch the graph, some links would become much longer than others and some nodes would be much closer together than others -- this is exactly what the force layout prevents.
You can of course manually "hack" the layout to achieve what you want -- you could, for example, specify different link distances/strengths for each link so that the ones you want to be larger are weaker. Similarly, you could specify a charge function that assigns a weaker repulsion charge to the nodes you want to be closer together.
However, for all of this you would need a pretty good idea of what you want the graph to look like when finished. It would probably be easier to ditch the force layout altogether and simply specify the positions of the nodes yourself.

Map does not render completely in d3.js

I am creating a globe on which I plot a number of locations. Each location is identified with a small circle and provides information via a tooltip when the cursor hovers over the circle.
My problem is the global map renders incompletely most of the time. That is various countries do not show up and the behavior of the code changes completely at this point. I say most of the time because about every 5th time i refresh the browser it does render completely. I feel like I either have a hole in my code or the JSON file has a syntax problem that confuses the browser.
btw: I have the same problem is FF, Safari, and Chrome. I am using v3 of D3.js
Here is the rendering code:
d3.json("d/world-countries.json", function (error, collection) {
map.selectAll("path")
.data(collection.features)
.enter()
.append("svg:path")
.attr("class", "country")
.attr("d", path)
.append("svg:title")
.text( function(d) {
return d.properties.name; });
});
track = "countries";
d3.json("d/quakes.json", function (error, collection) {
map.selectAll("quakes")
.data(collection.features)
.enter()
.append("svg:path")
.attr("r", function (d) {
return impactSize(d.properties.mag);
})
.attr("cx", function (d) {
return projection(d.geometry.coordinates)[0];
})
.attr("cy", function (d) {
return projection(d.geometry.coordinates)[1];
})
.attr("class", "quake")
.on("mouseover", nodehi)
.on("mouseout", nodelo)
.attr("d", path)
.append("svg:title")
.text( function(d) {
var tip = d.properties.description + " long "+ (d.geometry.coordinates)[0] + " lat " + (d.geometry.coordinates)[1];
return tip
});
});
Any thoughts would be appreciated...
The reason you're seeing this behaviour is that you're doing two asynchronous calls (to d3.json) that are not independent of each other because of the way you're selecting elements and binding data to them. By the nature of asynchronous calls, you can't tell which one will finish first, and depending on which one does, you see either the correct or incorrect behaviour.
In both handler functions, you're appending path elements. In the first one (for the world file), you're also selecting path elements to bind data to them. If the other call finished first, there will be path elements on the page. These will be matched to the data that you pass to .data(), and hence the .enter() selection won't contain all the elements you're expecting. This is not a problem if the calls finish the other way because you're selecting quake elements in the other handler.
There are several ways to fix this. You could either assign identifying classes to all your paths (which you're doing already) and change the selectors accordingly -- in the first handler, do .selectAll("path.country") and in the second .selectAll("path.quake").
Alternatively, you could nest the two calls to d3.json such that the second one is only made once the first one is finished. I would do both of those to be sure when elements are drawn, but for performance reasons you may still want to make the two calls at the same time.

d3: confusion about selectAll() when creating new elements

I am new to d3 and am using 'Interactive Data Visualization for the Web' by Scott Murray (which is great btw) to get me started. Now everything I saw so far works as described but something got me confused when looking at the procedure to create a new element. Simple example (from Scott Murray):
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle");
The name "circle" is used for the selectAll which returns an empty selection (which is ok as I learned). Then circles are appended by putting the same name into the .append. Great!
Now what got me confused was what happens when you want to do the same thing again. So you have a second dataset and want to generate new circles in the same way. Using the same code just replacing the dataset will obviously not work as the selectAll("circle") will not return an empty selection anymore. So I played around and found out that I can use any name in the selectAll and even leave it empty like this: selectAll()
Scott Murrays examples always just use one type (circle, text, etc.) per dataset. Finally I found in the official examples something like
svg.selectAll("line.left")
.data(dataset)
.enter()
.append("line")
.attr ...
svg.selectAll("line.right")
.data(dataset)
.enter()
.append("line");
.attr ...
Now my question: How is this entry in selectAll("ENTRY") really used? Can it be utilized later to again reference those elements in any way or is it really just a dummy name which can be chosen in any way and just needs to return an empty selection? I could not find this entry anywhere in the resulting DOM or object structure anymore.
Thank you for de-confusing me.
What you put in the selectAll() call before the call to .data() really only matters if you're changing/updating what's displayed. Imagine that you have a number of circles already and you want to change their positions. The coordinates are determined by the data, so initially you would do something like
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return d; })
.attr("cy", function(d) { return d; });
Now your new data has the same number of elements, but different coordinates. To update the circle positions, all you need to do is
svg.selectAll("circle")
.data(newData)
.attr("cx", function(d) { return d; })
.attr("cy", function(d) { return d; });
What happens is that D3 matches the elements in newData to the existing circles (what you selected in selectAll). This way you don't need to append the circles again (they are there already after all), but only update their coordinates.
Note that in the first call, you didn't technically need to select circles. It is good practice to do so however just to make clear what you're trying to do and to avoid issues with accidentally selecting other elements.
You can find more on this update pattern here for example.

Categories

Resources