d3 animated line graph - animation

I am trying to create an animated line graph where the line draws out its path from left to right, as in the first scene of this example. I have been closely following the code for mbostock's example, but still am having a couple issues.
When I try to run my code, I get an error saying "'d' is undefined", in the attr method inside the draw function shown below:
var line = d3.svg.line()
.interpolate('linear')
.x(function(d,i) {return x(i);})
.y(function(d,i) {return y(d['measures'][i]);});
chart.append('path')
.attr('class','line')
.data(array);
function draw(k) {
chart.select('.line')
.attr('d',function(d) { return line(d.measures.slice(0,k+1));});
}
var k=1, n = array.measures.length;
d3.timer( function() {
draw(k);
if((k+=2) >= n-1) {
draw(n-1);
//next transitions
return true;
}
This leads me to believe that my data has not been correctly bound to the path. However, I can't seem to understand why it would not be binding correctly
Any help would be much appreciated!

The example you picked as a starting point is binding the data for each ticker symbol to a <g> element, and then within each of those it's creating lines, etc. based on child data of that ticker. The .selectAll and .data methods are used when you're binding a list of data to a list of DOM nodes (e.g. <circle>, <rect>, <g>). But <path> is just a single DOM node with a long string of path data for rendering the line. You call the line function once to give you that string.
So for your purpose, if you just have one line that you're trying to animate, you just call your line function directly in order to create the path string to set on that node. There is no existing data bound to the node that you're trying to reuse. In your draw function:
.attr("d", line(array.measures.slice(0,k+1)))
Your line function itself needs slight adjustment too, assuming that array.measures contains a simple array of numbers:
var line = d3.svg.line()
.interpolate('linear')
.x(function(d,i) {return x(i);})
.y(function(d,i) {return y(d);});
BUT, having said all that, I think you would be better served by starting from a simpler example such as http://bl.ocks.org/duopixel/4063326 or one of the other approaches listed in this question or this question. It's hard to separate just the animated line portion of your original example from the rest of the cool stuff it's doing.

Related

in d3.geo MultiPoint how can I provide different shapes for different poins?

I have some geoJson data that I am charting using d3.geo.
When I write something like
d3.select("svg")
...
.attr("d", function(d) {
return path({
type:"MultiPoint",
coordinates: get_activity_coords_(d.activities)
});
})
I always get a circle for each coordinate. The coordinates represent locations of various stopping points of a journey. What I would prefer is a different shape for the first and the last coordinate.
Is it possible to do this using MultiPoint, is there an example that I can follow? I could draw the points one by one, but I recall reading that MultiPoint is far faster. Plus, the code would be much clearer to read.
Thanks a lot.
You can't do different shapes for MultiPoint geoJSON with d3.geo.path. You can change the radius based on a function, but it looks like you can only set it per feature and not per point, so you'd have to break your set of points into multiple features and lose any performance benefit from using the single element.
However, there are other ways to go about doing this.
One option, as you mentioned, is to create a nested selection with a separate <path> element for each point, and draw each path using a d3.svg.symbol() function. You can then customize the symbol function to be based on data or index.
var trips = d3.select("svg").selectAll("g.trips")
.data(/*The data you were currently using for each path,
now gets to a group of paths */)
.attr("class", "trips");
//also set any other properties for the each trip as a whole
var pointSymbol = d3.svg.symbol().type(function(d,i){
if (i === 0)
//this is the first point within its groups
return "cross";
if ( this === this.parentNode.querySelector("path:last-of-type") )
//this is the last point within its group
return "square";
//else:
return "circle";
});
var points = trips.selectAll("path")
.data(function(d) {
return get_activity_coords_(d.activities);
//return the array of point objects
})
.attr("transform", function(d){
/* calculate the position of the point using
your projection function directly */
})
.attr("d", pointSymbol);
Another option, which allows you to set custom shapes for the first and last point (but all intermediary points would be the same) is to connect the points as the vertices of a single, invisible <path> element and use line markers to draw the point symbols.
Your approach would be:
Create a <defs> element within your SVG (either hard-coded or dynamically with d3), and define the start, middle and end marker points within them. (You can use d3.svg.symbol() functions to draw the paths, or make your own, or use images, it's up to you.)
Use a d3.svg.line() function to create the path's "d" attribute based on your array of point coordinates; the x and y accessor functions for the line should use the projection function that you're using for the map to get the x/y position from the coordinates of that point. To avoid calculating the projection twice, you can save the projected coordinates in the data object:
var multipointLine = d3.svg.line()
.x(function(d,i) {
d.projectedCoords = projection(d);
return d.projectedCoords[0];
})
.y(function(d){ return d.projectedCoords[1];});
(You can't use your d3.geo.path() function to draw the lines as a map feature, because it will break the line into curves to match the curves of longitude and latitude lines in your map projection; to get the line markers to work, the path needs to be just a simple straight-line connection between points.)
Set the style on that path to be no stroke and no fill, so the line itself doesn't show up, but then set the marker-start, marker-mid and marker-end properties on the line to reference the id values of the correct marker element.
To get you started, here's an example using d3 to dynamically-generate line markers:
Is it possible to use d3.svg.symbol along with svg.marker

Using D3, how can I transition a line one point at time?

I'm working on a visualization project in which one component is a line chart overlayed on a bar graph. I delayed the bar transitions at a time. I would like the line to transition similarly so each point on the line remains "attached" to the bar.
Here's the code for the line:
var line = d3.svg.line()
.x(function(d, i) {
return xScale(i) + 20;
})
.y(function(d) {
return h - yScale(parseFloat(d.performance));
});
svg1.append("svg:path").attr("d", line(dataset[0].months));
And here's where I transition it:
svg1.select("path")
.transition()
.ease("linear")
.duration(1000)
.attr("d", line(dataset[count].months));
I've seen other questions addressing d3 line transitions but none that seem to address my issue, so I hope I'm not a repeater. Thanks in advance!
Note: I did try putting the delay() function in after transition which didn't work. I'm assuming this is because the line is a single <path> instead of multiple <rect> elements...
So this fell off my radar for a while, but I had some time the other day and figured out one approach for doing the delayed transition...
Here is the pen I wrote. I generated some random data and created a simple line chart to showing stock prices to play around with. The trick here is instead of iterating through a selection of elements using transition, we iterate through the dataset updating it point by point and transitioning the line as we go:
dataset.forEach(function(item, index) {
let set = dataset.slice();
// Update the current point in the copy
set[index].price = newPrice();
stock_line.transition()
.delay(index * 500)
.attr('d', line_generator(set));
});
Admittedly this is a bit hacky and possibly overkill, you could just update the whole line at once. Also #Lars mentioned the possibility of using the stroke-dashoffset trick to accomplish this as well. I have played around with that method to animate drawing the line but I'm not sure how I'd use it to accomplish the same delayed animation shown in the pen. If there is a less hacky implementation please let me know and I'll update this answer.

Using fields in inherited bound data

I am attempting to get my head around using bound data with d3.js. I'm following the documentation and am now a little confused.
I want to produce donut charts with radii that vary depending on the data. I am comfortable producing the arcs to make up a donut using an array, but am having a hard time working out how to pass along a size parameter with the data binding of the arc. For example, if the data bound to the parent of the arc is something like {size: 20, cont: [1, 7]}, how can I bind the first element of the array as well as the size element? I have a fiddle attempting to show what I am talking about. In that example, the two donuts should be different sizes. I have commented out the kind of thing I suspect should be going on on line 14.
I have tried variations on:
var arcs = donuts.selectAll(".arc")
.data(function(d) { var temp = [];
temp.push(d.cont);
temp.push(d.size);
return temp; })
.enter()
.append("g")
.attr("class", "arc");
But it is clearly not producing what I expect.
The problem here isn't really the data inheritance, but the fact that you're passing the original data to a layout and then only the result of that to your drawing functions. The pie layout does store the original datum in the .data member of the result, but you're only passing it part of the original data.
The "proper" thing to do would be to refactor your data structure such that you can pass it in as-is and use the pie layout's .value() function to tell it how to access the data. Then you can directly access the original data.
There's however a quicker solution -- you can simply use the indices that are passed to your function to index into the original array. The code for this would look like this.
.attr("d", function(d, i, j) { return arc.outerRadius(dataset[j].size)(d); })
Note that you need two indices here because you have nested data -- i would be the index within your array of values for a single pie chart, whereas j denotes the index of the element at the level above that. Updated jsfiddle here.

changing d3.JSON in Hierarchical Bars to JSON.parse

I'm using http://bl.ocks.org/mbostock/1283663
and I'm trying to change the following code
d3.json("readme.json", function(root) {
hierarchy.nodes(root);
x.domain([0, root.value]).nice();
down(root, 0);
});
to a JSON.parse (some data). I don't have a problem pulling the JSON data but I am totally confused about what is being setup in the rest of the d3.json process with the hierarchy.node(root), x.domain and down(root)
You're probably going to have to read a little more documentation before having a working understanding of what's going in this function. This is a tricky example to start with; I'm just going to walk you through what my process of trying to understand would look like. Going line by line:
d3.json("readme.json", function(root) {
This loads the referenced json file and calls function with it. 'root' starts out equal to the json file.
hierarchy.nodes(root);
Looking through the code, we find where hierarchy is declared:
var hierarchy = d3.layout.partition()
.value(function(d) { return d.size; });
So hierarchy is some kind of layout and hierarchy.nodes will add some useful attributes to root that will make it easier to graph.
x.domain([0, root.value]).nice();
searching for "x" in the example we find x = d3.scale.linear().range([0, w]). Tt appears that x is a linear scale. Basically, the x function will transform values in the domain - [0, root.value] to the range [0, w]. w is the width of the svg. root.value is a little trickier. The node page says
value - the node value, as returned by the value accessor
But what is the value accessor? The initial declaration of indicates it has something todo with root's 'size' attribute, but what? At this point, the documentation starts to get pretty confusing so you might want to pop open the debugger and see exactly what the value attribute of root and root's children looks like.
down(root, 0);
The down function is unique to the example and well commented. Try reading through it while referencing the documentation and see if you can figure it out.

Is it possible to create pie charts with object consistency?

the pie chart update example on the bl.ocks site doesn't update the elements 'in place':
http://bl.ocks.org/j0hnsmith/5591116
function change() {
clearTimeout(timeout);
path = path.data(pie(dataset[this.value])); // update the data
// set the start and end angles to Math.PI * 2 so we can transition
// anticlockwise to the actual values later
path.enter().append("path")
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", arc(enterAntiClockwise))
.each(function (d) {
this._current = {
data: d.data,
value: d.value,
startAngle: enterAntiClockwise.startAngle,
endAngle: enterAntiClockwise.endAngle
};
}); // store the initial values
path.exit()
.transition()
.duration(750)
.attrTween('d', arcTweenOut)
.remove() // now remove the exiting arcs
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
Instead, it just treats the new array of value as brand new data and resizes the chart accordingly.
I've created a fiddle demonstrating the issue very simply:
http://jsfiddle.net/u9GBq/23/
If you press 'add', it add a random int to the array: this works as intended.
If you press 'remove', the only element getting transitioned out is always the last element to have entered the pie. In short, it behaves like a LIFO stack.
The expected behaviour is for the relevant pie arc to get transitioned out instead.
Is it possible to apply object consistency to pies? I've also tried adding a key function (not demonstrated on the fiddle) but that just breaks (oddly enough it works fine with my stacked graphs).
Thank you.
The easiest solution to this problem is to set missing values to zero, rather than removing them entirely, as in Part III of the Pie Chart Update series of examples. Then you get object constancy for free: you have the same number of elements, in the same order, across updates.
Alternatively, if you want a data join as in Part IV, you have to tell D3 where the entering arcs should enter from, and where the exiting arcs should exit to. A reasonable strategy is to find the closest neighboring arc from the opposite data: for a given entering arc, find the closest neighboring arc in the old data (pre-transition); likewise for a given exiting arc, find the closest neighboring arc in the new data (post-transition).
To continue the example, say you’re showing sales of apples in different regions, and want to switch to show oranges. You could use the following key function to maintain object constancy:
function key(d) {
return d.data.region;
}
(This assumes you’re using d3.layout.pie, which wraps your original data and exposes it as d.data.)
Now say when you transition to oranges, you have the following old data and new data:
var data0 = path.data(), // retrieve the old data
data1 = pie(region.values); // compute the new data
For each entering arc at index i (where d is data1[i]), you can step sequentially through preceding data in data1, and see if you can find a match in data0:
var m = data0.length;
while (--i >= 0) {
var k = key(data1[i]);
for (var j = 0; j < m; ++j) {
if (key(data0[j]) === k) return data0[j]; // a match!
}
}
If you find a match, your entering arcs can start from the matching arc’s end angle. If you don’t find a preceding match, you can then look for a following matching arc instead. If there are no matches, then there’s no overlap between the two datasets, so you might enter the arcs from angle 0°, or do a crossfade. You can likewise apply this technique to exiting arcs.
Putting it all together, here’s Part V:
Ok, found the solution.
The trick was to pass the key this way:
path = path.data(pie(dataset), function (d) {return d.data}); // this is good
as opposed to not passing it, or passing it the wrong way:
path = path.data(pie(dataset, function (d) {return d.data})); // this is bad
And here's an updated fiddle with a working transition on the right arc! :)
http://jsfiddle.net/StephanTual/PA7WD/1/

Resources