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/
Related
I am trying to create a 3d "chart" using d3 data-binding and x3dom.
The data-binding seems to work correctly and I am able to generate HTML which looks correct, but x3dom does not show the expected scale (as in the size of the shape is not scaled at all).
The code is fairly simple. First, I bind data points to transforms:
var t = scene.selectAll('transform').data(data);
var transform = t.enter();
var shape = transform.append('transform')
.attr('translation', function (d, i) {
...
}).append('shape');
Within each transform element created for a data-point, I use this code to create a box:
shape.append('box').attr('size', '1, 1, 1');
Then, I expect to scale the boxes later using the data, using a transition:
t.transition().attr('scale', function (d) {
return "1.0 1.0 " + d.size;
});
This only works if I run the whole function that does the above twice, the second time in a setInterval() function!
I am a newbie, so I am certainly missing something here, but the code does generate what I expect and does work if run twice, for whatever reason. So, why doesn't it just work first time?
Note: I could of course just set the size directly instead of using an animation to do that later by scaling the shapes, but that also didn't work. It seems that the shapes never get the correct size using either technique, unless I run them twice.
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.
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.
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.
I'm working on a project that uses SVG with Raphael.js. One component is a group of circles, each of which "wiggles" around randomly - that is, slowly moves along the x and y axes a small amount, and in random directions. Think of it like putting a marble on your palm and shaking your palm around slowly.
Is anyone aware of a Raphael.js plugin or code example that already accomplishes something like this? I'm not terribly particular about the effect - it just needs to be subtle/smooth and continuous.
If I need to create something on my own, do you have any suggestions for how I might go about it? My initial idea is along these lines:
Draw a circle on the canvas.
Start a loop that:
Randomly finds x and y coordinates within some circular boundary anchored on the circle's center point.
Animates the circle from its current location to those coordinates over a random time interval, using in/out easing to smooth the effect.
My concern is that this might look too mechanical - i.e., I assume it will look more like the circle is tracing a star pattern, or having a a seizure, or something like that. Ideally it would curve smoothly through the random points that it generates, but that seems far more complex.
If you can recommend any other code (preferably JavaScript) that I could adapt, that would be great too - e.g., a jQuery plugin or the like. I found one named jquery-wiggle, but that seems to only work along one axis.
Thanks in advance for any advice!
Something like the following could do it:
var paper = Raphael('canvas', 300, 300);
var circle_count = 40;
var wbound = 10; // how far an element can wiggle.
var circleholder = paper.set();
function rdm(from, to){
return Math.floor(Math.random() * (to - from + 1) + from);
}
// add a wiggle method to elements
Raphael.el.wiggle = function() {
var newcx = this.attrs.origCx + rdm(-wbound, wbound);
var newcy = this.attrs.origCy + rdm(-wbound, wbound);
this.animate({cx: newcx, cy: newcy}, 500, '<');
}
// draw our circles
// hackish: setting circle.attrs.origCx
for (var i=0;i<circle_count;i++) {
var cx = rdm(0, 280);
var cy = rdm(0, 280);
var rad = rdm(0, 15);
var circle = paper.circle(cx, cy, rad);
circle.attrs.origCx = cx;
circle.attrs.origCy = cy;
circleholder.push(circle);
}
// loop over all circles and wiggle
function wiggleall() {
for (var i=0;i<circleholder.length;i++) {
circleholder[i].wiggle();
}
}
// call wiggleAll every second
setInterval(function() {wiggleall()}, 1000);
http://jsfiddle.net/UDWW6/1/
Changing the easing, and delays between certain things happening should at least help in making things look a little more natural. Hope that helps.
You can accomplish a similar effect by extending Raphael's default easing formulas:
Raphael.easing_formulas["wiggle"] = function(n) { return Math.random() * 5 };
[shape].animate({transform:"T1,1"}, 500, "wiggle", function(e) {
this.transform("T0,0");
});
Easing functions take a ratio of time elapsed to total time and manipulate it. The returned value is applied to the properties being animated.
This easing function ignores n and returns a random value. You can create any wiggle you like by playing with the return formula.
A callback function is necessary if you want the shape to end up back where it began, since applying a transformation that does not move the shape does not produce an animation. You'll probably have to alter the transformation values.
Hope this is useful!
There is a very good set of easing effects available in Raphael.
Here's a random set of circles that are "given" bounce easing.
Dynamically add animation to objects
The full range of easing effects can be found here. You can play around with them and reference the latest documentation at the same time.
Putting calls in a loop is not the thing to do, though. Use callbacks, which are readily available.