Cool-or Particles in JS with D3

(Wait for it)

<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
/*
  *
  *** Based on http://bl.ocks.org/mbostock/1893974
  *** Modifyed by http://www.entropicparticles.com/particles/
  *
*/
var maxRadius = 40, // maximum radius of circle
    minRadius = 1, // minimum radius of a circle
    padding = 1, // padding between circles; also minimum radius
    margin = {top: -minRadius, right: -minRadius, bottom: -minRadius, left: -minRadius},
    width = 500,// - margin.left - margin.right,
    height = 500,// - margin.top - margin.bottom;
    Sun = {sx: 250, sy: 250, sr: 200};
    
var k = 1, // initial number of candidates to consider per circle
    m = 10, // initial number of circles to add per frame
    n = 2500, // remaining number of circles to add
    newCircle = SunCircleGenerator(maxRadius, padding);

var svg = d3.select("div").append("svg")
    .attr("width", width)
    .attr("height", height)
    .style("background","white")
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
d3.timer(function() {
  for (var i = 0; i < m && --n >= 0; ++i) {
    var circle = newCircle(k);
    svg.append("circle")
        .attr("cx", circle[0]).attr("cy", circle[1]).attr("r", 0)
        .style("fill", writeTheSunColor(circle[0],circle[1]))
        .style("stroke","black")
        .transition().attr("r", circle[2]);

    // As we add more circles, generate more candidates per circle.
    // Since this takes more effort, gradually reduce circles per frame.
    if (k < 500) k *= 1.01, m *= .998;
  }
  return !n;
});

function SunCircleGenerator(maxRadius, padding) {
  var quadtree = d3.geom.quadtree().extent([[0, 0], [width, height]])([]),
      searchRadius = maxRadius * 2,
      maxRadius2 = maxRadius * maxRadius;

  return function(k) {
    var bestX, bestY, bestDistance = 0;

    for (var i = 0; i < k || bestDistance < padding; ++i) {
      var x = Math.random() * (width-margin.left-margin.right) + margin.left,
          y = Math.random() * (height-margin.top-margin.bottom) + margin.bottom,
          rx1 = x - searchRadius,
          rx2 = x + searchRadius,
          ry1 = y - searchRadius,
          ry2 = y + searchRadius,
          minDistance = Math.min(maxRadius,x,width-x,y,height-y); // minimum distance for this candidate
          rrxy= Math.sqrt( (x-Sun.sx)*(x-Sun.sx) + (y-Sun.sy)*(y-Sun.sy) ); // Distance to Sun 
          if ( rrxy < Sun.sr) {
              minDistance = Math.min(minDistance,maxRadius - rrxy*(0.95*maxRadius)/Sun.sr); 
          } //Maximum radius is smaller close to the Sun's border
      
          minDistance = Math.min(minDistance,Math.abs(Sun.sr - rrxy)); // minimum distance for this candidate
          
      quadtree.visit(function(quad, x1, y1, x2, y2) {
        if (p = quad.point) {
          var p,
              dx = x - p[0],
              dy = y - p[1],
              d2 = dx * dx + dy * dy,
              r2 = p[2]*p[2];
          if (d2 < r2 ) return minDistance = 0, true; // within a circle or margins
          var d = Math.sqrt(d2) - p[2];
          if (d < minDistance) minDistance = d;
        }
        return !minDistance || x1 > rx2 || x2 < rx1 || y1 > ry2 || y2 < ry1; // or outside search radius
      });

      if (minDistance > bestDistance) bestX = x, bestY = y, bestDistance = minDistance;
    }

    var best = [bestX, bestY, bestDistance - padding];
    quadtree.add(best);
    return best;
  };
}

// Didn't know how to select a color palette so I defined my own functions from RGB or HSV
function writeColor(d) { // d is an array with RGB from 0 to 255. It returns color in Hex
    var a = (Math.round(d[0])).toString(16);
    var b = (Math.round(d[1])).toString(16);
    var c = (Math.round(d[2])).toString(16);
    if (a.length == 1) {a = "0"+a;}
    if (b.length == 1) {b = "0"+b;}
    if (c.length == 1) {c = "0"+c;}
    return "#"+a+b+c
}
function writeMyColor(d) { // d is a number from 0 to 1 for Hue, S & V fluctuate randomly
    return writeColor(hsvToRgb(d%1, Math.random()*0.1+0.9, Math.random()*0.1+0.9)); 
}
function writeTheSunColor(a,b) { // a & b ar x & y positions, color palette depends on distance to Sun
    var culor;
    var rr = Math.sqrt(Math.pow((a-Sun.sx),2)+Math.pow((b-Sun.sy),2))/Sun.sr;
    if( rr < 1 ) {
      culor = writeMyColor( 0.05*Math.random()+0.1*rr ); 
      } else {
      culor = writeMyColor( 0.05*Math.random()+1.4-0.5*rr ); 
      }
    return  culor
}

/**
 *** SOURCE: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
 *
 * Converts an HSV color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
 * Assumes h, s, and v are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 255].
 *
 * @param   Number  h       The hue
 * @param   Number  s       The saturation
 * @param   Number  v       The value
 * @return  Array           The RGB representation
 */
function hsvToRgb(h, s, v){
    var r, g, b;

    var i = Math.floor(h * 6);
    var f = h * 6 - i;
    var p = v * (1 - s);
    var q = v * (1 - f * s);
    var t = v * (1 - (1 - f) * s);

    switch(i % 6){
        case 0: r = v, g = t, b = p; break;
        case 1: r = q, g = v, b = p; break;
        case 2: r = p, g = v, b = t; break;
        case 3: r = p, g = q, b = v; break;
        case 4: r = t, g = p, b = v; break;
        case 5: r = v, g = p, b = q; break;
    }

    return [r * 255, g * 255, b * 255];
}

</script>