Juan Juan - 1 month ago 14
Javascript Question

D3.js: "On the fly" added elements to array are not refreshing the svg graphic

I have like a bubble row in which I have 6 bubbles at any given time. The array has 6 json objects. The code is displaying only the circles that were first added on loading. But when I modify the array I want to remove the first bubble and add one bubble to the right end of the row. I'm using a setInterval to insert an element into the array to test it. The array is changing properly since i'm logging the state of the array, but the svg graphic is not refreshed. I just don't know if the problem is with reutilizing the createElementGroup() or how to remove nodes in this case(I saw that the common case is using the exit() d3 method but I'm not sure where to implement it in this particular case).

Additionally, Where should I put the transition to make it smooth when I remove and add an element?. The live demo is here:

http://codepen.io/juanf03/pen/BQyYBq (you can click on the bubbles to see it expand and show the data, in that way I check that is the correct node)

The code:

//listener that will be executed on setIntervalCheck to update the graphic
setInterval(function(){
moveForwardOnBubbleList();
createElementGroup();
}, 100000);



var profile_pic_url="https://scontent.fsst1-2.fna.fbcdn.net/v/t1.0-9/13680856_103268503450198_1479797031996897052_n.jpg?oh=f43bced91822fb210c8be8a410825da9&oe=58D46460";

var dataset = [{unique_followers: 5, profile_pic:profile_pic_url}, {unique_followers: 10, profile_pic:profile_pic_url},{ unique_followers: 15, profile_pic:profile_pic_url}, { unique_followers: 20, profile_pic:profile_pic_url}, { unique_followers: 25, profile_pic:profile_pic_url}, {unique_followers: 40, profile_pic:profile_pic_url} ];

var w=600,h=600;

var svg=d3.select("body").append("svg")
.attr("width",w)
.attr("height",h);

//1st level:All circles group
var circlesGroup = svg.append("g").classed("general-group",true);
//2nd level: Group of circle and text
var elementGroup;

var circle;

var circleAttributes;
//create g's of existing data
createElementGroup();

elementGroup.on('click', function(d,i){
var that=this;

d3.selectAll('.element-group').each(function(d,i) {
if(this.id!==that.id){
d3.select(this).classed("selected",false);
}
});

d3.select(this).classed("selected", !d3.select(this).classed("selected"));

});

//adding circular background image to the circles
//var circlesSelection=svg.selectAll('circle');


function createElementGroup(){
elementGroup = circlesGroup
.selectAll('circle')
.data(dataset)
.enter()
.append("g").classed("element-group",true);

circle=elementGroup.append('circle');

circleAttributes = circle
.attr("r", 20)
.attr("stroke","black")
.attr("fill", "white")
.classed("circle",true);

//text to show
elementGroup.append("text")
.attr("text-anchor", "middle")
.text(function(d) {
return parseInt(d.unique_followers);
})
.style("pointer-events","none")
.classed('tweet-number', true);

//image to show as background

//element group positioning for the text to be inside circle
elementGroup.attr("transform", function(d,i){
return "translate(" + (i*80+45) + "," + h/2 + ")";
});

elementGroup.attr( "fill-opacity", 0 ).transition().duration(500).attr( "fill-opacity", 1 );
elementGroup.attr("id", function(d, i) { return "c"+i; });


}

function addBubbleLast(){
dataset.push({unique_followers: 40, profile_pic:profile_pic_url});
}

function removeFirstBubble(){
dataset.shift();

}

function moveForwardOnBubbleList(){
addBubbleLast();
removeFirstBubble();
}



/*CSS*/
body
{
/*padding-top: 50px;*/
padding-left: 100px;
}

.tweet-number{
opacity:0.25;
}

.circle{

}


.selected *{
transform: scale(2);
transition: all 0.5s ease, opacity 0.5s ease;
opacity:1.0;
}

Answer

You need an "enter", "exit" and "update" selections (see here).

First, we bind the data (with a key function):

elementGroup = circlesGroup
    .selectAll('.element-group')
    .data(dataset, function(d){ return d.unique_followers});

Then, we set the enter selection:

var elementEnter = elementGroup.enter()
    .append("g").classed("element-group",true);

Now an important note: as this is D3 v4.x, you need to merge the selections to have a working update selection:

elementEnter.merge(elementGroup).attr("transform", function(d,i){
    return "translate(" + (i*80+45) + "," + h/2 + ")"; 
});

Finally, the exit selection:

var elementExit = elementGroup.exit().remove();

Here is your CodePen: http://codepen.io/anon/pen/Wobyem