Reputation: 155
I am trying to draw a series of line after every 5 seconds by changing the lineData array which I feed to the d3 line function:
Here are the excerpts of the code relevant:
var svg = d3.select("body").append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "svgMain")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(drag);
var lines = svg.append("g")
.attr("id", "lines");
function changingLines() {
lines.selectAll("line")
.data(lineData)
.enter().append("line")
.attr("x1", function(d, i) { console.log(d[0][0]); return d[0][0];})
.attr("y1", function(d, i) { console.log(d[0][1]); return d[0][1];})
.attr("x2", function(d, i) { return d[1][0];})
.attr("y2", function(d, i) { return d[1][1];})
.style("stroke", "#000")
.style("stroke-width", "0.5")
.attr("class", "lines");
lines.selectAll("line").exit().remove();
setTimeout(changingLines, 2000);
}
I call the function changingLines() once every 2 seconds with different values for lineData array.
I get the error: Uncaught TypeError: Object has no method 'exit
What am I doing wrong?
Upvotes: 3
Views: 6167
Reputation: 15695
You've got a couple related problems with this code actually. When you call changingLines
, the only elements which will have their attributes updated are the elements in the enter
selection (calling .enter()
returns the enter
selection`).
Remember that by default, .data()
will only add elements to the enter
selection if there are new elements in the array you pass into it, e.g.
// Old data array:
var data = [1, 2, 3, 4];
// New data array:
var newData = [5, 6, 7, 8, 9];
/*
The first 4 elements in newData will replace the first 4 elements of oldData.
The number of new elements (which will be in the enter() selection) is only 1.
*/
What you should do is save the join computed by your data
call, and use it to individually access enter
, exit
, and update
selections.
var linesContainer = svg.append("g").attr("id", "lines");
function changingLines() {
/* Compute the data join */
var lines = linesContainer.selectAll("line").data(lineData);
/* Enter */
lines.enter().append("line");
/* Exit */
lines.exit().remove();
/* Update */
lines
.attr("x1", function(d, i) { console.log(d[0][0]); return d[0][0];})
.attr("y1", function(d, i) { console.log(d[0][1]); return d[0][1];})
.attr("x2", function(d, i) { return d[1][0];})
.attr("y2", function(d, i) { return d[1][1];})
.style("stroke", "#000")
.style("stroke-width", "0.5")
.attr("class", "lines");
setTimeout(changingLines, 2000);
}
This will remove old line
elements and add new line
elements to before it updates the attributes and styles.
https://github.com/mbostock/d3/wiki/Selections#wiki-enter
The enter selection merges into the update selection when you append or insert. This approach reduces code duplication between enter and update. Rather than applying operators to both the enter and update selection separately, you can now apply them to the update selection after entering the nodes. In the rare case that you want to run operators only on the updating nodes, you can run them on the update selection before entering new nodes.
This should also fix your problem with not being able to call exit()
. When you were calling lines.selectAll("line")
the second time, you were creating a new selection, and therefore you wouldn't have access to the selections computed when you did your previous join.
Read, and reread this article: http://bost.ocks.org/mike/join/
Upvotes: 18