Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
386 views
in Technique[技术] by (71.8m points)

javascript - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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/


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...