简体   繁体   中英

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

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM