Eamonn
Eamonn

Reputation: 617

D3 Data updates not working correctly

I thought I understood the D3 enter/update/exit, but I'm having issues implementing an example. The code in http://jsfiddle.net/eamonnmag/47TtN/ illustrates what I'm doing. As you'll see, at each 5 second interval, I increase the rating of an item, and update the display again. This works, in some way. The issue is in only updating what has changed - D3 is updating everything in this case. The enter and exit methods, displayed in the console output that nothing has changed, which makes my think that it's treating each array as a completely new instance.

My understanding of the selectAll() and data() calls was that it would 'bind' all data to a map called 'chocolates' somewhere behind the scenes, then do some logic to detect what was different.

var chocolate = svg.selectAll("chocolates").data(data);

In this case, that is not what's happening. This is the update code. Any pointers to what I've missed are most appreciated!

function update(data){
    var chocolate = svg.selectAll("chocolates").data(data);

    var chocolateEnter = chocolate.enter().append("g").attr("class", "node");

    chocolateEnter.append("circle")
    .attr("r", 5)
    .attr("class","dot")
    .attr("cx", function(d) {return x(d.price)})
    .attr("cy", function(d) { 
    //put the item off screen, to the bottom. The data item will slide up.
        return height+100;})
    .style("fill", function(d){ return colors(d.manufacturer); });

    chocolateEnter
        .append("text")
        .text(function(d) {
        return d.name;})
        .attr("x", function(d) {return x(d.price) -10})
        .attr("y", function(d){return y(d.rating+step)-10});

    chocolateEnter.on("mouseover", function(d) {
        d3.select(this).style("opacity", 1);
    }).on("mouseout", function(d) {
        d3.select(this).style("opacity", .7);
    })

    chocolate.selectAll('circle')
        .transition().duration(500)
        .attr('cy', function(d) {return y(d.rating+step)});


    var chocolateExit = chocolate.exit().remove();
    chocolateExit.selectAll('circle')
    .attr('r', 0);
}

setInterval(function() {    
    chocolates[3].rating = Math.min(chocolates[3].rating+1, 5);
    update(chocolates);

}, 5000);

Upvotes: 0

Views: 1465

Answers (2)

Lars Kotthoff
Lars Kotthoff

Reputation: 109232

There are a couple of issues in your code. First, the logic to detect what's different is, by default, to use the index of the item. That is, the first data item is matched to the first DOM element, and so on. This works in your case, but will break if you ever pass in partial data. I would suggest using the second argument to .data() to tell it how to match:

 var chocolate = svg.selectAll("g.node").data(data, function(d) { return d.name; });

Second, as the other poster has pointed out, selecting "chocolate" will select nothing, as there are no such DOM elements. Just select the actual elements instead.

Finally, since you're adding g elements for the data items, you might as well use them. What I mean is that currently, you're treating the circles and text separately and have to update both of them. You can however just put everything underneath g elements. Then you have to update only those, which simplifies your code.

I've made all the above changes in your modified fiddle here.

Upvotes: 1

Vikram Deshmukh
Vikram Deshmukh

Reputation: 15606

Easy as apple pie!

Why are you doing svg.selectAll("chocolates")? There is no HTML element in your DOM called chocolates. You need to change that to svg.selectAll(".node"). That will fix the problem.

Upvotes: 1

Related Questions