shilpanb shilpanb - 1 month ago 7
Javascript Question

d3.js - Uncaught TypeError: Object has no method 'exit'

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?

Wex Wex
Answer

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/