Make two instances of d3.forceCollide() play nice together - javascript

I want two instances of d3.forceCollide(). In one, every node is pushed away from one another to prevent overlap. In the second, only a subset of nodes are pushed away from one another, with a much bigger radius.
To accomplish the second force, I tweak the initialize method to filter the incoming nodes, like so:
function selective(force,filter){
var init = force.initialize;
force.initialize = function(_){return init(_.filter(filter));};
return force;
}
var dpi = 90; // approximate pixels per inch in SVG
var size = dpi * (1/4); // quarter-inch unit size
var universally_applied =
d3.forceCollide()
.radius(size)
.strength(1);
var selectively_applied =
selective(
d3.forceCollide(),
function(d){return d.id === color;}
)
.radius(size*5)
.strength(1);
}
Now, this ALMOST works. I created a fiddle to see it in action: https://jsfiddle.net/jarrowwx/0dax43ue/38/ - every colored circle is supposed to repel every other circle of the same color, from a distance. Every other color, it just bumps into and pushes it out of the way.
If I do not change the order in which things are defined, then the selectively applied force is ONLY applied to the first color (red). If I shuffle the data array before applying forces, it is difficult to define exactly what happens, but the force is applied to some circles and not most of the others, even among the same color.
Any ideas what is going on here, or how to fix it?

The D3 team decided that this behavior was a bug (Isolating forces to a subset of nodes? #72), and fixed it. The fix was included in version 1.0.4 of d3-force, which is available as part of the full D3 build as of version 4.4.0.
The problem is resolved using the solution suggested by "Partial forces on nodes in D3.js", and the code works as intended now.

Related

How to create merged shapes based upon blurred originals

I'm using easeljs and attempting to generate a simple water simulation based on this physics liquid demo. The issue I'm struggling with is the final step where the author states they "get hard edges". It is this step that merges the particles into an amorphous blob that gives the effect of cohesion and flow.
In case the link is no longer available, in summary, I've followed the simulation "steps" and created a prototype for particle liquid with the following :
Created a particle physics simulation
Added a blur filter
Apply a threshold to get "hard edges"
So I wrote some code that is using a threshold check to color red (0xFF0000) any shapes/particles that meet the criteria. In this case the criteria is any that have a color greater than RGB (0,0,200). If not, they are colored blue (0x0000FF).
var blurFilter = new createjs.BlurFilter(emitter.size, emitter.size*3, 1);
var thresholdFilter = new createjs.ThresholdFilter(0, 0, 200, 0xFF0000, 0x0000FF);
Note that only blue and red appear because of the previously mentioned threshold filter. For reference, the colors generated are RGB (0,0,0-255). The method r() simply generates a random number up to the passed in value.
graphics.beginFill(createjs.Graphics.getRGB(0,0,r(255)));
I'm using this idea of applying a threshold criteria so that I can later set some boundaries around the particle adhesion. My thought is that larger shapes would have greater "gravity".
You can see from the fountain of particles running below in the attached animated gif that I've completed Steps #1-2 above, but it is this Step #3 that I'm not certain how to apply. I haven't been able to identify a single filter that I could apply from easeljs that would transform the shapes or merge them in any way.
I was considering that I might be able to do a getBounds() and draw a new shape but they wouldn't truly be merged at that time. Nor would they exhibit the properties of liquid despite being larger and appearing to be combined.
bounds = blurFilter.getBounds(); // emitter.size+bounds.x, etc.
The issue really becomes how to define the shapes that are blurred in the image. Apart from squinting my eyes and using my imagination I haven't been able to come to a solution.
I also looked around for a solution to apply gravity between shapes so they could, perhaps, draw together and combine but maybe it's simply that easeljs is not the right tool for this.
Thanks for any thoughts on how I might approach this.

d3.js linkStrength influence on linkDistance in a force graph

I'm working on a graph to show relations between different nodes. The closer related the nodes are (according to business logic), the closer together the nodes should be.
I noticed that some links with the linkStrength of .1 are shorter (that is what I wanted to achieve) and some others with the same strength are longer than the ones with the linkStength of 1. From a documentation about the force layout parameters I now found this quote:
The default linkStrength value is 1.0, which maintains the full effect of linkDistance. By setting the value of linkStrength less than 1, though, the distance constraint can be relaxed.
Does that mean that if I was to set linkDistance to 150, the links with linkStrength(1.0) will be closer to 150 than the ones with linkStrength(.1)? And if yes, do they need to be shorter, longer or doesn't that matter at all? Because I was kind of surprised about the layout.
To cut a long story short: when using D3's force layout there is no built-in way to enforce a fixed length of the links. The force layout is inherently dynamic and setting values for force.linkDistance() and force.linkStrength() introduces just another force to the set of calculations carried out on each iteration, i.e. each tick, while the force layout is running.
There are three forces calculated on each tick:
1. Link length. The first force to be calculated is the adjustment for link lengths set via the above mentioned methods. This is done in a loop for each link and, looking at the source, this comes down to essentially one line of code:
l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
With l calculated to be the Euclidean distance between the link's source node and target node, the desired link distance distances[i] and the link's strengths[i] this line determines how to pull both nodes together or push them apart to approximate the link distance set via force.linkDistance(). It's easy to see, that the link strength has a linear effect on the resulting force. Contrary to the API documentation, though, which defines the valid range for the strength to be in the interval [0,1], the source code does not impose any restrictions on the value set by force.linkStrength().
2. Gravity. The second force to be calculated will take into account gravitational forces on each node.
3. Charge. Lastly, the mutual forces of the nodes' charges are calculated.
Because the effects of all the calculated forces are superimposed and will add up to the resulting movement of each node for a given tick, it becomes clear, that the link length is only one part of the entire computation. The other two forces may weaken or even reverse its effect.
As for your question
Does that mean that if I was to set linkDistance to 150, the links with linkStrength(1.0) will be closer to 150 than the ones with linkStrength(.1)?
The outcome depends largely on the setup of the force layout's parameters and the distribution of the nodes, and still no guarantee is made regarding the final lengths of the links.
The link strength sets the rigidity of the links, not the distance between the nodes (force layout doc). The distance of the nodes from each other is controlled by their charge. You can make the charge of a node dynamic like so:
var force = d3.layout.force()
.nodes(nodes)
.theta(0.1)
.charge(function (d) {
return -(d.radius * d.radius * 0.125);
})
.gravity(0.1)
.size([width, height])
.on("tick", tick)
.start();
A working example that uses this technique: vizz.ly

What's the idiomatic way to sync transitions so that adjacent shapes move together?

Say I have an arbitrary path like this:
[##########]
I also have a circle like this: o
I want to keep o at the tip of the arbitrary path, so it looks like this:
[##########]o
(Assume o is centered vertically between the top and bottom of the path object) And when the path grows or shrinks, the o should always stay at the tip.
[###############]o
Most importantly, when a transform is applied to the path, the transform should also be applied accordingly to the circle -- they should be in sync when in motion.
I've tried making the circle a path marker, but run into trouble
(a) getting it to only move through the center of the path
(b) getting it to "stick" in the final position
(all examples have it infinitely rotating around the path, like this and this and this)
Calling two transition functions (one for each set of shapes) one after another is usually sufficient, because the time it takes the browser to run through the code is much less than the delay between animation frames.
However, if your animation is sufficiently complex that there is a noticeable lag between the two, or if you are doing a lot of complex calculations that will affect both elements, you could use a custom tween function on one selection, and within it select the other shape and update it (you'll want to select it in your "outer" function, so that your inner function which gets called at every update can just quickly reposition it to match the new value).
Regarding transformations, the easiest way to keep things coordinated is to put both elements in a <g> and transform the group instead of the individual elements.
Putting the ideas together, you could get a transition process something like this:
d3.selectAll("g.groups").transition().delay(time)
.attr("transform", function(d,i){ /* Calculate new transform */ })
.tween("stretch", function(d,i){
/* Select the sub-elements, do all the calculations
then create interpolators for both objects */
var g = d3.select(this);
var path = g.select("path");
var dot = g.select("circle");
var newEndPoint = /*** Calculate final position ***/;
var offset = /*** distance from end point to center of circle ***/;
var pathInterpolator = d3.interpolateString(
path.attr(d),
/*** new path including new end point ***/
);
var dotInterpolator = d3.interpolateObject(
{cx=dot.attr("cx"), cy=dot.attr("cy")},
{cx=newEndPoint.x + offset, cy=newEndPoint.y}
);
return function(t){
/* the function that updates both objects at each tick */
path.attr("d", pathInterpolator(t) );
dot.attr( dotInterpolator(t) );
};
});
How complex your real calculations are will depend on how arbitrary your "arbitrary path" is, of course. Maybe you'll need to calculate both x and y offsets to keep the circle positioned correctly. But that becomes an issue of geometry, not of synchronization. Regardless of what else your path shape does as it transitions, if the end point is an actual point in the path data, it will transition in a direct line, the same as the transition of the circle's coordinates.

Trace path with DOM object

I'm new to javascript and d3js. I would like a DOM object to trace out a path specified by a parametrized curve (x(t),y(t)). Here is an example of such a parametrization:
var theta = [];
for(var i = 0; i <= N; i++){
theta.push(2*Math.PI*i/N);
}
var points = [];
for(var i = 0; i <= N; i++){
points.push([Math.cos(theta[i]),Math.sin(theta[i])]);
}
The above is the parametrization of a curve -- in this case, also a circle -- and I would like my DOM object to follow the trajectory of this curve. [Aside: is there any better way to define points? It seems ridiculous to run a for loop.]
A crude way to achieve the sort of effect I'm looking for is to run a for loop in the update() part of d3. First, I simply append a circle to the svg variable, so that it need not be linked to any data. It is then selected and updated without required enter/exit.
for (var i = 0; i <= N; i++){
svg.selectAll("circle")
.transition()
.attr("cx",points[i][0]+w/2) // w: width
.attr("cy",points[i][1]+h/2) // h: height
.duration(dt) //
.delay(dt*i);
}
[Aside: I've heard queue() would be better, as opposed to calculating the total delay. Comments?] However, the easing property of the transition makes it run in a choppy fashion. I imagine I could specify no easing, but I'm sure there must be a better way to achieve what I want, which is simply for the initial DOM object (the circle) to move smoothly along a specific trajectory.
In the end, I would want to do this for multiple DOM objects which will eventually be linked to data, each with a specific curve to follow. Any tips on how I would do this?
Thanks in advance for any help, and I will gladly take any advice, including references.
Interesting but not terribly practical approach
The SVG spec actually has a number of animation options, including the ability to move an object along a path. The path is defined in the same form as for a <path> element, so you could use the d3.svg.arc functions to create the path.
Once you have a path defined, it is easy to use d3 to add in the animation:
http://fiddle.jshell.net/RnNsE/1/
although you'll want to read up on SVG animation elements and attributes.
However, there is a limitation to this wonderful animation: poor browser support. So if this is for a website, you're going to need to do the animation with d3 and Javascript.
Production-ready approach
The key to getting d3 to create smooth animations for you is to use a custom "tween" function on a transition.
When you do a transition, d3 initializes a tween function for each change on each element, and starts up timer functions to trigger the updates. At each "tick" of the timer, d3 calls the appropriate "tween" function with the information about how far along the transition it is. So if the tick occurs 500ms into a 2000ms transition, the tween function will given the value 0.25 (assuming a linear easing function, other easing functions complicate the relationship between time elapsed and the expected "distance" along the transition).
Now, for most changes the tween function is fairly straightforward, and d3 will figure one out for you automatically. If you change a "cx" value from 100 to 200, then the tween function is going to return 125 when the transition value 25%, 150 when the transition is 50%, and so on. If you change a "fill" value from red to yellow, it will calculate the numerical values of those colours and convert between them.
The value returned by the tween function at each tick is then used to update the attribute or style of the element. Since the updates happen many times a second, it usually results in a smooth animation. For the simple example of changing the "cx" value of a circle, the circle moves in a straight line from the starting point to the end point.
But you don't want it to move in a straight line. You want it to move in a circle (or along any path you choose). So you're going to need to create a custom function that tells the circle where it should be 25% of the way through the transition, and where it should be 50% through the transition, and so on.
And if you're worried you have to figure that out on your own, never fear. Like so, so much with D3, Mike Bostock has done the hard work for you. But even he didn't have to do the hard hard work. His approach uses two built-in Javascript functions for SVG paths, getTotalLength() and getPointAtLength(). The first tells you the total length of the path, the second gives you the coordinates of the point a certain distance from the start of the path.
With those two values, it is straightforward to figure out the coordinates you should be at if you want to be a certain percent of the way along the path: at 25%, you want to be at path.getPointAtLength(0.25*path.getTotalLength() ).
Here's Mike's function that makes that happen:
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
A little confusing, no? A function that returns a function that returns a function.
That's because when you specify a "tween" for a transition, what you actually have to specify is a "tween factory" -- the function that will return an appropriate tween function for each element in your selection.
Now, in his example he only has one path and one object moving along it, so those extra layers don't get used. But in the general case, your tween factory function would take the arguments d (the data object for that element in the selection), i (the index of that element) and a (the initial value of the attribute or style that you're changing). With those values, you have to return the custom tween function which take a transition state value t (a number between 0 or 1, or possibly a bit beyond 1 for certain easing functions) and computes the attribute value at that state in the transition.
You'll note that this function returns a translation instruction. That's generally going to be an easier way to move an object around, compared to using cx and cy, since you can specify both horizontal and vertical movement in one transform attribute call, so you only need the one tween function to do both.
Here's my example from above, updated to use a d3 tween to move the circles along the path:
http://fiddle.jshell.net/RnNsE/2/
Key code:
circles.transition().ease("linear")
.duration(5000)
.delay(function(d,i){return i*5000;})
.attrTween("transform", createPathTween);
//creates a tween function to translate an element
//along the path that is a sibling to the element
function createPathTween(d, i, a) {
var path = this.parentNode.getElementsByTagName("path")[0];
//i.e., go from this <circle> -> parent <g> -> array of child <path> elements
-> first (and only) element in that array
var l = path.getTotalLength();
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
}
My version strips out the outermost layer of nested functions from Mike's version, but it adds in a bit of Javascript to find the correct <path> element for each circle element.
Note that you need an SVG path element in order to use the getTotalLength() and getPointAtLength() functions; however, this path can be invisible (fill:none; stroke:none; in CSS) if you don't want it to show on the screen. And again, while my path definitions are hard-coded, you could use one of d3's arc or line generators to construct it for you.
And just for fun, here's my example with a different easing function:
http://fiddle.jshell.net/RnNsE/3/
Note that I didn't change anything about the tweening function -- all that's changed is the t values that d3 passes in to that function as the transition progresses.
P.S. Here's another good resource on d3 custom tween functions:
http://blog.safaribooksonline.com/2013/07/11/reusable-d3-js-using-attrtween-transitions-and-mv/

D3.js patterns for managing multiple elements bound to same data

I'm learning D3 and my toy application is a visualizer for a 2-dimensional gravity simulation using Symplectic Velocity Verlet Integration.
I have had quite some success animating and drawing a path using bound structured data, but it seems to me that D3 isn't really designed for attaching data to more than one element. It sort of assumes that for any given data that there is a clear and simple SVG element that should own it, and this is evident in the direct storage of data within the __data__ property inside the DOM.
It's not clear, though, the proper way to represent a datum with more than one SVG element. For instance, I'd really prefer to draw a path and a circle for each planet, the path traces its past position (and can have a bunch of clever line-length and color interpolation applied), and the circle plots its current position.
I can even come up with a few more elements I might want to draw: A vector-arrow for velocity... A vector-arrow for acceleration...
In my case, my master data structure is constructed like this, and is dynamically maintained in this structure:
var data = [];
function makeParticle(x, y, vx, vy) {
// state vector plus goodies
return [
x, y,
vx, vy,
0, 0,
[] // path
];
}
data.push(makeParticle(400, 100, -0.5, 1));
data.push(makeParticle(300, -100, 0.5, 2)); // and so on
Each element of data is a planet, which contains its current state vector (position, velocity, and cached acceleration (needed for the integrator)) as well as its path history which is an array of positions which is kept truncated to some reasonably large length.
Currently I am updating the path history like this:
var paths = d3.select('svg').selectAll("path").data(data);
paths.enter().append('path'); // put cool transitions here
paths.exit().remove();
paths.attr("stroke", "red")
.attr("d", function(d){
return lineDrawer(d[6]);
})
This works fine, each path tracks its own copy of its own planet's path.
It's not immediately clear to me how to extend this elegantly to include my circle at the head of each path. I certainly do not want to duplicate the entire datum into the circle's DOM element (as the path data is simply not necessary for it).
Edit in light of my self-answer:
I am hoping that someone can help to elucidate a way to use "groups" to make data-driven the set of things to draw for each datum. e.g. [position, velocity, force, path] as data where they are visualized using, respectively, a circle, an arrow closed path, an arrow closed path, and an open path. It is also possible that this is completely overthinking it because these properties are sort of fixed.
I guess in the process of thinking the problem through, it's become clear to me that all I have to do is filter out the data, so that I only attach the position state data to selectAll('circle.planet') rather than the full datum value which includes the massive path history array. This way it gets exactly the data it is responsible for displaying.
It seems like I have to do some more reading about subselections (I'm mostly puzzled by why (or if) the subselections are limited to two dimensions of hierarchy), but this seems completely reasonable. It seems logical, if I want to draw 4 items for each datum, I just have to somehow "assign" the correct subsets of my datum's structure to each SVG visualizer element.

Categories