D3: Draw part of interpolated path with dashed stroke - javascript

I'm drawing a simple interpolated line using D3.js (output as path). However, I want part of the path to have a dashed stroke if a boolean in a data point is set to true:
Link to image
An easy solution would be to just draw it with <line> segments instead - but that way I lose the interpolation.
Then I found an example from mbostock, showing how to draw a gradient along a interpolated path. I modified it to just draw transparent path fills whenever the boolean was set to true and white fills when false - while my old path is all dashed.
That works (queue the above screenshot) - but by adding around thousand path elements to the DOM contra having only a single path.
It's not desirable with that many DOM elements, especially since I'm going to make more curves and the site needs to be mobile optimized. Am I missing a much simpler solution?
Wouldn't mind a modified version of mbostock's example doing the heavy calculations in advance, as long as the DOM output is simple.
Thanks!

I prepared this example for another SO question. Screenshot is here:
I think you have enough material there to devise a solution that fits your needs.
Take a look also at this page:
SVG Path Styling

You could use stroke-dasharray to add dashes in the stroke of the generated path in the right places. That would entail finding the proper dash lengths. This can be done by calling pathElm.getPathLength() on the path up to the point where you want it to start being dashed, and to where you want it to end.
Let's say path A that is the part that is before the dashes should start. Set the d attribute with that part and call getPathLength() on it. Let's call this length a.
Append the the part of the path that should be dashed to the d attribute, then call getPathLength() again. Let's call this length b.
Create a new path element with the remaining part of the path, then call getPathLength() on that. Let's call this length c.
Construct a stroke-dasharray property string something like this:
var a = getPathLengthA();
var b = getPathLengthB();
var c = getPathLengthC();
var dasharray = a + " ";
for(var usedlen = 0; usedlen < (b-a); ) {
dasharray += "5 10 "; // just whatever dash pattern you need
usedlen += 15; // add the dash pattern length from the line above
}
dasharray += c;
pathElement.style.strokeDasharray = dasharray;
Here's a static example of that.

Related

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

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.

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/

How to curl paths into spiral on hover using Raphael JS

I have a set of Raphael paths that I want to animate when a separate path is hovered over.
The client sent an SVG illustration that I had converted into Raphael code. This is just a small section of the larger image.
What I'm trying to do is as follows:
starting canvas
Start with a set of path objects in a line. When you hover over the red path they animate in a spiral until they form
ending canvas
I've done some research, and I think that I'm going to have to animate each circle along a hidden path that depicts the arcs they have to move to get to the final spiral shape (Animate along a path), but I'm not sure if this is the most efficient way to create this animation.
On top of that, I'm not really sure how to calculate the angle and size of the hidden paths the circles would be following. What is going to be the best way to create this animation?
Thanks!
I think moving each circle along a hidden path is easiest way.
I would suggest to create a new SVG file in InkScape vector editor, draw circles in start and end positions and then connect them with arcs. Save the file. Then open the file in any text editor and copy all data to your JS code (copy path's "d" parameter and coordinates of circles).
It's been more than one year since it's asked but maybe it might help other people.
Using simple algebra thought in high school you can define two functions: linear and spiral.
function linear(i){
x = i*15;
y = x;
return {cx:x+offset,cy:y+offset};
}
function spiral(i){
r = 50 - i*5;
x = r*Math.cos(i);
y = r*Math.sin(i);
return {cx:x+offset,cy:y+offset};
}
This is just a 5 min fiddling, so it might seem cheap. Of course, you can define better algorithms using transform functions.
jsfiddle here

How do I get resulted pathString after transformation is applied?

Consider I have the following HTML5 path:
var myPath = paper.path([
'M', 0, 0
'L', 100, 100,
'L', 150, 50,
'Z']
]);
myPath.transform(['s', 0.5, 0.5, 0, 0]);
After tranformation (scaling) my path resizes accordingly in half, but inspecting the element is the same path string but with transformation matrix applied. Is there any way to retrieve the pathString resulted (lets say M,0,0,L,50,50,L,75,24,z).
I think you need transformPath method: http://raphaeljs.com/reference.html#Raphael.transformPath
The only solution would be using Raphael 1.x which used to modify paths instead of applying transformations. Otherwise you'd need to write your own routines to convert apply matrix transformations to paths (really difficult).
Please check my answer HERE and the testbed HERE.
There is a function flatten_transformations() which can bake (or apply) transforms to paths, so that transform attribute can be removed. It can handle all path segments (also arcs).
OLD ANSWER (not so complete implementation):
Of course there is a way (example in JSBIN) to get a resulted path data after transformations applied. And even very easy way.
Let's suppose we have a SVG path pathDOM and it's root element svgDOM. We can get a transformation matrix of path's coordinate space to root element's coordinate space using native getTransformToElement() -function. It is used this way:
var matrix = pathDOM.getTransformToElement(svgDOM);
When we apply this matrix to all points in path, we get a new path data, where all coordinates are relative to root element. It can be done this way:
var pt = svgDOM.createSVGPoint();
pt.x = some_x_coordinate_of_path_data;
pt.y = some_y_coordinate_of_path_data;
var new_point = pt.matrixTransform(matrix); // <- matrix object, which we created earlier
var new_x = new_point.x;
var new_y = new_point.y;
And that's it! After conversion the transform attribute can be emptied.
Of course all coordinates in path have to be converted to absolute and eg. Arc segments have to be converted to Line or Cubic Segments, which both can be achieved with Raphaël's path2curve() function.
I made a full functional example of using this "Flattening transformations" functionality in JSBIN. There is a ready made function flatten_transformations(), which is the only one needed (the rest is needed for UI purposes). The example has got a path that is nested inside two g elements. Path has own transformations applied, as well as both g elements. Purpose is to test that also nested transformations are flattened.
The code works in newest main browsers and even in IE9. My code that modifies transformations is rather interesting mix of jQuery, Raphael and native code that it may be cause of some problems in IE9 when clicking buttons, but fortunately those essential native functions getTransformToElement(), createSVGPoint() and matrixTransform() work as expected also in IE. I wanted to test simultaneously how those different coding bases plays together. Because it's the fact that Raphael itself is not perfect enough to handle all possible coding needs (lack of styles and groups and lack of possibility to append svg elements as textual xml-data are just ones to note).

How to draw a vector path progressively? (Raphael.js)

How to animate a vector path like it's being drawn, progressively? In other words, slowly show the path pixel by pixel.
I'm using Raphaël.js, but if your answer is not library specific—like maybe there's some general programming pattern for doing that kind of thing (I'm fairly new to vector animation)—it's welcome!
It's easy to do with straight paths, as easy as an example on that page::
path("M114 253").animate({path: "M114 253 L 234 253"});
But try to change code on that page, say, this way::
path("M114 26").animate({path: "M114 26 C 24 23 234 253 234 253"});
And you'll see what I mean. Path is certainly animated from it initial state (point "M114 26") to the end state (curve "C 24 23 234 253 234 253" starting on point "M114 26"), but not in a way specified in question, not like it's being drawn.
I don't see how animateAlong can do that. It can animate an object along a path, but how can I make this path to gradually show itself while object is being animated along it?
The solution?
(Via peteorpeter's answer.)
Seems like currently the best way to do it is via 'fake' dashes using raw SVG. For the explanation see this demo or this document, page 4.
How produce progressive drawing?
We have to use stroke-dasharray and stroke-dashoffset and know length of curve to draw.
This code draw nothing on screen for circle, ellipse, polyline, polygone or path:
<[element] style="stroke-dasharray:[curve_length],[curve_length]; stroke-dashoffset:[curve_length]"/>
If in animate element stroke-dashoffset decrease to 0, we get progressive drawing of curve.
<circle cx="200" cy="200" r="115"
style="fill:none; stroke:blue; stroke-dasharray:723,723; stroke-dashoffset:723">
<animate begin="0" attributeName="stroke-dashoffset"
from="723" to="0" dur="5s" fill="freeze"/>
</circle>
If you know a better way, please leave an answer.
Update (26 Apr. 2012): Found an example that illustrates the idea well, see Animated Bézier Curves.
Maybe someone is searching for an answer, like me for two days now:
// Draw a path and hide it:
var root = paper.path('M0 50L30 50Q100 100 50 50').hide();
var length = root.getTotalLength();
// Setup your animation (in my case jQuery):
element.animate({ 'to': 1 }, {
duration: 500,
step: function(pos, fx) {
var offset = length * fx.pos;
var subpath = root.getSubpath(0, offset);
paper.clear();
paper.path(subpath);
}
});
That did the trick for me, only by using RaphaelJS methods.
Here is a jsFiddle example as requested in the comments, http://jsfiddle.net/eA8bj/
Eureka! (Maybe - assuming you're comfortable stepping outside the friendly realm of Raphael into pure SVG land...)
You can use SVG keyTimes and keySplines.
Here's a working example:
http://www.carto.net/svg/samples/animated_bustrack.shtml
...and here's some potentially useful explanation:
http://msdn.microsoft.com/en-us/library/ms533119(v=vs.85).aspx
I'd like to offer an alternative, Raphael+JS-only solution that I have made substantial use of in my own work. It has several advantages over davidenke's solution:
Doesn't clear the paper with each cycle, allowing the animated path to coexist nicely with other elements;
Reuses a single path with Raphael's own progressive animation, making for smoother animations;
Substantially less resource intensive.
Here's the method (which could quite easily be retooled into an extension):
function drawpath( canvas, pathstr, duration, attr, callback )
{
var guide_path = canvas.path( pathstr ).attr( { stroke: "none", fill: "none" } );
var path = canvas.path( guide_path.getSubpath( 0, 1 ) ).attr( attr );
var total_length = guide_path.getTotalLength( guide_path );
var last_point = guide_path.getPointAtLength( 0 );
var start_time = new Date().getTime();
var interval_length = 50;
var result = path;
var interval_id = setInterval( function()
{
var elapsed_time = new Date().getTime() - start_time;
var this_length = elapsed_time / duration * total_length;
var subpathstr = guide_path.getSubpath( 0, this_length );
attr.path = subpathstr;
path.animate( attr, interval_length );
if ( elapsed_time >= duration )
{
clearInterval( interval_id );
if ( callback != undefined ) callback();
guide_path.remove();
}
}, interval_length );
return result;
}
And here are two samples of its usage on my site: one for Path Transformation, and the other for Progressive Lettering.
I've created a script for this: Scribble.js, based on this great dasharray/dashoffset technique.
Just instantiate it overs a bunch of SVG <path>s:
var scribble = new Scribble(paths, {duration: 3000});
scribble.erase();
scribble.draw(function () {
// done
});
--
NB: Full USAGE code here: https://gist.github.com/abernier/e082a201b0865de1a41f#file-index-html-L31
Enjoy ;)
Using "pathLength" attribute we can set virtual length to the path. From then we can use this virtual length in "stroke-dasharray".
So if we set "pathLength" to 100 units we then can set "stroke-dasharray" to "50,50" wich wuld be exactly 50%, 50% of the path!
There is one problem with this approach: the only browser that supports this attribute is Opera 11.
Here is example of smooth curve drawind animation without javascript or hardcoded length.(Works properly only in Opera 11)
Anton & Peteorpeter's solution sadly breaks down in Chrome when paths get complicated. It's fine for the bus map in that linked demo. Check out this animated "flower petals" jsfiddle I created, which draws correctly in FF10 and Safari5, but flickers uncontrollably in Chrome:
http://jsfiddle.net/VjMvz/
(This is all HTML and inline SVG, no javascript.)
I'm still looking for a non-Flash solution for this. AnimateAlong obviously won't cut it for what I'm doing. Raphael.js could work, though it threatens to turn into callback spaghetti really fast.
Davidenke, can you post a working jsfiddle with your solution? I just can't get it to work. I'm getting an error in Chrome 18 that nodes that are set to "display: none" with your ".hide" have no method 'getTotalLength'.
Unfortunately, as you seem to agree, you probably can't do this elegantly in Raphael.
However, if, by some stroke of %deity% you don't need to support IE for this particular feature, you could forgo the Raphael API and manipulate the SVG directly. Then, perhaps, you could rig a mask to ride along the path and reveal the line at a natural pace.
You could degrade gracefully in IE to simply show the path using Raphael, without animation.
i was just doing exactly this. The first thing i tried was Anton's solution but the performance sucks.
In the end the easiest way to get the result i wanted was to use the alternative "keyframe" syntax for the animate function.
draw the final path invisibly, then generate a whole bunch of key frames by using getSubpath in a loop.
create a new path that is visible and equals the first keyframe.
then do something like:
path.anmimate({ keyFrameObject, timeframe });
you shouldn't need a keyframe for every pixel that you want to draw. After playing around with the parameters, i found that a value of 100px per keyframe worked for the complexity/size of what i was trying to "draw"
Just an update to this, you could try Lazy Line Painter
Have you tried Raphael's animateAlong? You can see it in action on a demo page.
Alright, here's my thoughts on this… The solution's too far from ideal.
To gradually show the path mean we should show it, like, dot by dot. And vector paths consist not of dots, but of curves, so it appears to me there's no ‘natural’ way to gradually ‘draw’ the path in vector graphics. (Though I'm fairly new to this and may be mistaken.)
The only way would be to somehow convert a path to a number of dots and show them one by one.
Currently my workaround is to draw a path, make it invisible, break it into a number of subpaths, and show that subpaths one by one.
This isn't hard to do with Raphael, but it's not elegant either, and quite slow on a large paths. Not accepting my answer, hoping there's a better way…

Categories