Dynamic Border Around Shapes

http://stackoverflow.com/questions/25086712/d3-create-dynamic-border-rectangle-around-svg-group

by Nivaldo

HTML

<h5>&rarr; check commented out code; author says is an improvement?</h5>
<div>Click the button to add a circle.</div>
<div>Click a circle to remove it.</div>

CSS

svg {
  border: 1px solid #ccc;
}

.bounding-rect {
  stroke: red;
  stroke-width: 2;
  fill: transparent;
}

.dot {
  fill: steelblue;
}

JavaScript

var w = 400,
    h = 300,
    margin = 50;

var addBtn = d3.select('body').append('div').append('button')
  .text('Add Circle')
  .on('click', function() {addCircles(1);});

// CREATE THE SVG
var svg = d3.select('body').append('svg')
  .attr('width', w + 2*margin)
  .attr('height', h + 2*margin)
  .append('g')
    .attr('transform', 'translate(' + margin + ',' + margin + ')');

// CREATE THE GROUP
var theGroup = svg.append('g')
  .attr('class', 'the-group');

// CREATE ITS BOUNDING RECT
var theRect = theGroup.append('rect')
  .attr('class', 'bounding-rect');

// INITIALIZE WITH A FEW CIRCLES
addCircles(4);
updateRect();


function updateRect() {
  // SELECT ALL CHILD NODES EXCEPT THE BOUNDING RECT
  var allChildNodes = theGroup.selectAll(':not(.bounding-rect)')[0]

  // `x` AND `y` ARE SIMPLY THE MIN VALUES OF ALL CHILD BBOXES
  var x = d3.min(allChildNodes, function(d) {return d.getBBox().x;}),
      y = d3.min(allChildNodes, function(d) {return d.getBBox().y;}),
      
      // WIDTH AND HEIGHT REQUIRE A BIT OF CALCULATION
      width = d3.max(allChildNodes, function(d) {
        var bb = d.getBBox();
        return (bb.x + bb.width) - x;
      }),
      
      height = d3.max(allChildNodes, function(d) {
        var bb = d.getBBox();
        return (bb.y + bb.height) - y;
      });
   
  // UPDATE THE ATTRS FOR THE RECT
  theRect.transition().duration(1000)
     .attr('x', x)
     .attr('y', y)
     .attr('width', width)
     .attr('height', height);
}

function addCircles(n) {
  for (var i = 0; i < n; i++) {
    theGroup.append('circle')
      .attr('class', 'dot')
      .attr('cx', Math.random() * w)
      .attr('cy', Math.random() * h)
      .attr('r', Math.random() * 20 + 4)
      .on('click', function() {
        d3.select(this).remove();
        updateRect();
      });
  }
  updateRect();
}

/*
var w = 400,
    h = 300,
    margin = 50;

var addBtn = d3.select('body').append('div').append('button')
  .text('Add Circle')
  .on('click', function()...