3

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;
    }

EDIT: Fixed code after the great suggestions of Gerardo Furtado. I post it in case someone comes across a similar problem:

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



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=900,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();



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


function createElementGroup(){
  elementGroup = circlesGroup
  .selectAll('.element-group')
  .data(dataset, function(d){ return d.unique_followers});
 //doesn't work the exit transition 
   var elementExit = elementGroup.exit().transition().duration(1000).style("opacity", 0).remove();

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

    elementEnter.merge(elementGroup).attr("transform", function(d,i){

   //option 1 generation by mod   
   if(i%2===0){   
    return "translate(" + (i*80+45) + "," + h/1.55 + ")"; 
   }else{
    return "translate(" + (i*80+45) + "," + h/1.45 + ")"; 

   }

  /*   
  //option 2 random
  var random= (Math.random() * (1.6 - 1.45) + 1.45).toFixed(4);

         return "translate(" + (i*80+45) + "," + h/random + ")";*/ 


}).transition().duration(2000).style("opacity", 1.0);

    circle=elementEnter.append('circle');



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

  d3.selectAll('.element-group').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"));

  });

  //text to show
   var texts = elementEnter.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



}

function addBubbleLast(){

  var random=Math.floor(Math.random() * (40)) + 1;


    dataset.push({unique_followers: random, 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:1.0;
}

.element-group *{
  transition: all 0.5s ease;
}

circle + text{
}
John
  • 1,711
  • 2
  • 29
  • 42

2 Answers2

2

You need an "enter", "exit" and "update" selections.

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

Graham
  • 7,431
  • 18
  • 59
  • 84
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
  • two question: 1) do you know why my click event died?. It used to expand the bubbles twice its size and now after the modifications it's not working 2) why did you used the "key function" on the dataset? why did you bind the elements to that number? – John Nov 07 '16 at 02:34
  • The key function "takes a data point as input and returns a corresponding key: a string, such as a name, that uniquely identifies the data point." Regarding your click event: I didn't change the other parts of your code for you, that's your job now, I just showed you how to deal with the selections. You'll have to rewrite the whole code accordingly. – Gerardo Furtado Nov 07 '16 at 03:07
  • thanks, I'll have to see what's wrong, but I have one more question, are the elements being rerendered everytime here http://codepen.io/juanf03/pen/ZBYRNK?.....I just wan't to know the correct way....Cause I put the creation exit and update on a function to be reutilized...Does the click event have problems with the dynamically generated elements like it happened before with jquery?? that's why I'm asking – John Nov 07 '16 at 03:28
  • 1
    Yes, you can have some problems here. The best practice is putting the click event *after* the "enter+update" selection. – Gerardo Furtado Nov 07 '16 at 05:17
  • 1
    thanks....I already fixed it.....http://codepen.io/juanf03/pen/ZBYRNK?editors=0010 I changed it after the enter+update indeed and the selector instead of using elementGroup.on('click', I used d3.selectAll('.element-group').on('click') – John Nov 07 '16 at 14:19
  • Thank you! This is the only working example of update I found! – Tomáš Kafka Jan 15 '18 at 20:44
0

In my case I had svg circles and had to .remove() them and add completely new ones with the same class. But on d3.selectAll(".dynamic_child).on("click"... didn't register it. I found a workaround that doesn't dive deep into d3.js and instead uses jQuery.

My solution is the following:

$( ".static_parent" ).on("click", '.dynamic_child', function(){
    console.log("I'm working!")
    }

where the static_parent is just parent div that you keep and the dynamic_child is element(s) (html or svg) that you remove and add on the fly.

Original source post

pink_demon
  • 89
  • 4