D3 bubble chart / pack layout - How to make bubbles radiate out from the largest bubbles to the smallest?

SoldierOfFortran picture SoldierOfFortran · Jun 21, 2014 · Viewed 12.3k times · Source


So I am using the same code as this D3 gallery example (with my own data):

http://bl.ocks.org/mbostock/4063269


I'd like to get a bubble chart where the circles are arranged with the biggest in the center and then radiating out to the smallest.


Here is a mock up I created in Photoshop:

What I actually want



Here is what I get when I use the example (the default circle packing algorithm with default sort):

Default packing



I tried tweaking the sort (including trying d3.ascending and d3.descending). The best I could come up with just basically subverts the sort with a constant (ha!) but still is far from what I'd like:

//...
.sort( function(a, b) { return -1;} )
//...

Best I could get by tweaking sort



Ok, so any chance this can be done without having to alter the actual D3 pack layout algorithm? If not, perhaps someone has extended or modified the pack layout and could tell me the 5 lines I could change in the D3 source to hack this.



Thanks in advance!


Edit:

As requested, here is the code I am using. Basically the same as the linked sample above, with a few superficial changes as indicated by the commented lines:

var diameter = 960,
format = d3.format(",d"),
color = d3.scale.category20c();

var bubble = d3.layout.pack()
//  .sort(null)
//  .sort(d3.ascending)
//  .sort(d3.descending)
    .sort( function(a, b) { return  -1;} ) // basically a < b always
    .size([diameter, diameter])
    .padding(1.5);

var svg = d3.select("body").append("svg")
    .attr("width", diameter)
    .attr("height", diameter)
    .attr("class", "bubble");

d3.json("data.json", function(error, root) 
{
  var node = svg.selectAll(".node")
        .data(bubble.nodes(classes(root))
        .filter(function(d) { return !d.children; }))
        .enter().append("g")
        .attr("class", "node")
        .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

  node.append("title")
      .text(function(d) { return d.className + ": " + format(d.value); });

  node.append("circle")
      .attr("r", function(d) { return d.r; })
      .style("fill", function(d) 
            { 
                // return color(d.packageName); 
                return color(d.value); // this gives a different color for every leaf node
            });

  node.append("text")
      .attr("dy", ".3em")
      .style("text-anchor", "middle")
     // .text(function(d) { return d.className.substring(0, d.r / 3); });
});

// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) 
{
  var classes = [];

  function recurse(name, node) {
    if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
    else classes.push({packageName: name, className: node.name, value: node.size});
  }

  recurse(null, root);
  return {children: classes};
}

d3.select(self.frameElement).style("height", diameter + "px");

And my data.json file:

{
    "name": "Root",
    "children": [
        {
            "name": "Leaf",
            "children": null,
            "size": 2098629
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 104720
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 5430
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 102096
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 986974
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 59735
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 1902
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 120
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 870751
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 36672
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 274338
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 517693
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 145807
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 476178
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 11771
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 153
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 2138
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 8436
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 3572
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 120235
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 210945
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 56033
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 358704
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 295736
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 26087
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 33110
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 3828
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 1105544
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 98740
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 80723
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 5766
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 1453
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 10443176
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 14055
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 1890127
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 404575
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 272777
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 1269763
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 5081
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 3168510
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 717031
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 88418
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 762084
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 255055
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 535
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 81238
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 17075
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 5331
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 74834
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 110359
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 27333
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 143
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 12721
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 529
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 115684
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 3990850
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 6045060
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 2445766
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 479865
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 105743
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 183750
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 661
        },
        {
            "name": "Leaf",
            "children": null,
            "size": 11181
        }
    ],
    "size": 41103329
}

Answer

VividD picture VividD · Jun 24, 2014

All you need to do is to specify:

.sort(function(a, b) {
    return -(a.value - b.value);
})

This is different than specifying .sort(d3.ascending) or .sort(d3.descending), since d3.ascending and d3.descending are defined as

function(a, b) {
  return a < b ? -1 : a > b ? 1 : 0;
}

and

function(a, b) {
  return b < a ? -1 : b > a ? 1 : 0;
}

respecitevly, and the pack layout is affected by their "insensitivity" to the difference of data points.

This is my test example: (with your data) jsfiddle

enter image description here


Experimentally, I applied also following sort function: (it is a kind of hybrid)

.sort( function(a, b) {
    var threshold = 10000000;
    if ((a.value > threshold) && (b.value > threshold)) {
        return -(a.value - b.value);
    } else {
        return -1;
    }
})

... and for values for threshold of 10000000, 3000000, 1000000, 300000, 100000, 30000 respectively I got: jsfiddle

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here