title: D3 circle pack - dynamic label update
body: <p>I'm pretty new to coding in D3.  I'm working on a near real-time circle pack chart that gets its underlying data from an ajax call and resizes the nodes based on changes in data values. The challenge I'm facing is likely to be dreadfully simple, but I've not yet found a similar-enough example online to leverage as a solution.</p>
  
  <p>When I run this code, I know that the text values are actually being passed properly as the data changes. However, what's happening is that the code keeps appending text tags to the svg "g" nodes (with the updated values) rather than changing the existing element to reflect the updated value. The result is a layered text mess in the middle of an otherwise attractive bubble.</p>
  
  <p>I have tried using <code>d3.exit().remove()</code> to no avail - it's possible that I misused it and that it's actually the appropriate technique to apply.</p>
  
  <p>Would someone be willing to provide some guidance on how I should accomplish 2 specific things:</p>
  
  <p>1) I'd like to re-use existing "text" elements rather than remove + append unless it's not practical.</p>
  
  <p>2) I'd like to update the values of an existing "text" element with new data without refreshing the page.</p>
  
  <p>The full code for the .js file is here below.  I'm aware that I can use "svg" instead of "svg:svg", etc. but I haven't gotten to the tidying-up stage on this file yet. </p>
  
  <pre><code>var Devices = {
    setup_devices : function() {
    var r = 500,
      format = d3.format(",d"),
      fill = d3.scale.category10();
  
    var bubble = d3.layout.pack()
      .sort(null)
      .size([r, r])
      .padding(1.5);
  
    var chart = d3.select("#device_info").append("svg:svg")
      .attr("width", r)
      .attr("height", r)
      .attr("class", "bubble")
      .append("svg:g")
      .attr("transform", "translate(2, 2)");
  
    var tip = d3.tip()
      .attr('class', 'd3-tip')
      .offset([-10, 0])
      .html(function(d) {
        return "&lt;strong&gt;Device:&lt;/strong&gt; &lt;span style='color:red'&gt;" + d.name + "&lt;/span&gt;";
      });
  
    chart.call(tip);
  
    setInterval(function() {
  
      console.log("Devices Refreshing");
  
      $.ajax({
        type: "GET",
        url: "/devices",
        dataType: "json",
        beforeSend: function() {
        },
        error: function( jqXHR, textStatus, thrownError ) {
          return true;
        },
        success: function(data) {
          update(data);
          return true;
        }
      });
  
      d3.timer.flush();
  
    }, 2000);
  
  
    function update(data) {
  
      var updated = chart.data([data]).selectAll("g.node")
        .data(bubble.nodes);
  
      updated.enter().append("svg:g")
        .attr("class", "node")
        .attr("transform", function(d) {
          return "translate(" + d.x + "," + d.y + ")";
        })
        .attr("data-name", function(d) {
          return d.name;
        })
        .attr("data-device", function(d) {
          return d.device_id;
        })
        .on('mouseover', tip.show)
        .on('mouseout', tip.hide)
        .append("svg:circle")
          .attr("r", function(d) { return d.r; })
          .style("fill", function(d) { return fill(d.name); })
          .attr("text-anchor", "middle")
          .attr("dy", ".3em")
          .text(function(d) { return d.value + "%" });
  
      updated.append("svg:text")
        .attr("text-anchor", "middle")
        .attr("dy", ".3em")
        .text(function(d) { return d.value + "%" });
  
      updated.transition()
        .duration(1000)
        .attr("class", "node")
        .attr("transform", function(d) {
          return "translate(" + d.x + "," + d.y + ")";
        });
  
      updated.select("circle").transition()
        .duration(1000)
        .attr("r", function(d) { return d.r; })
        .text(function(d) { return d.value + "%" });
      }
    }
  }
  </code></pre>