Multi Level Pie Chart Using D3

multilevelPieChart

Using D3, the MultiLevel pie chart is constructed from a hierarchical data (shown below). This could be achieved by modifying the original example of pie chart in mike’s blog.

The implementation is a 2 step process. In the first step the hierarchical data is stored for each level and in the second step the corresponding pie charts are drawn (by tweaking inner and outer radius) for each level, using the stored data.

To store the data for each level, I have used BFS (Breadth First Search) to traverse the array of objects and then storing the each level’s data in an array, where the index of the array being the level of the in the nested object structure. I modified a little, the solution proposed in this blog to achieve the same.

var setMultiLevelData = function(data) {
    if (data == null)
        return;
    var level = data.length,
        counter = 0,
        index = 0,
        currentLevelData = [],
        queue = [];
    for (var i = 0; i < data.length; i++) {
        queue.push(data[i]);
    };
    while (!queue.length == 0) {
        var node = queue.shift();
        currentLevelData.push(node);
        level--;
        if (node.subData) {
            for (var i = 0; i < node.subData.length; i++) {
                queue.push(node.subData[i]);
                counter++;
            };
        }
        if (level == 0) {
            level = counter;
            counter = 0;            multiLevelData.push(currentLevelData);
            currentLevelData = [];
        }
    }
}

 

Once the data for each level is obtained, the all that remains is drawing multiple pie chart circles iteratively (for each level), one outside the other. I am using the function “drawPieChart” shown in below code (also explained in Mike’s blog) for each level by iterating over the stored multiLevel Data.

 

var drawPieChart = function(_data, index) {
    var pie = d3.layout.pie()
        .sort(null)
        .value(function(d) {
            return d.nodeData.population;
        });
    var arc = d3.svg.arc()
        .outerRadius((index + 1) * pieWidth - 1)
        .innerRadius(index * pieWidth);

    var g = svg.selectAll(".arc" + index).data(pie(_data)).enter().append("g")
        .attr("class", "arc" + index);

    g.append("path").attr("d", arc)
        .style("fill", function(d) {
            return color(d.data.nodeData.age);
        });

    g.append("text").attr("transform", function(d) {
            return "translate(" + arc.centroid(d) + ")";
        })
        .attr("dy", ".35em").style("text-anchor", "middle")
        .text(function(d) {
            return d.data.nodeData.age;
        });
}
//Calling drawPieChart for each level's data
for (var i = 0; i < multiLevelData.length; i++) {
    var _cData = multiLevelData[i];
    drawPieChart(_cData, i);
}

One point to notice in above code is, to have inner radius of the outer pie chart as outer radius of it’s immediate inner pie chart and so on, we used:

var arc = d3.svg.arc()
        .outerRadius((index + 1) * pieWidth - 1)
        .innerRadius(index * pieWidth);
//index is the index of corresponding level in the nested data object. The structure of the data object is shown at the bottom.

Refer following codepen snippet for complete working code:

The hierarchical data looks like:

{
    "nodeData": {
        "age": "5",
        "population": 60
    },
    "subData": [{
        "nodeData": {
            "age": "5",
            "population": 60
        },
        "subData": [{
            "nodeData": {
                "age": "5",
                "population": 60
            }
        }]
    }]
}, {
    "nodeData": {
        "age": "5-35",
        "population": 100
    },
    "subData": [{
        "nodeData": {
            "age": "5-15",
            "population": 60
        },
        "subData": [{
            "nodeData": {
                "age": "5-10",
                "population": 30
            }
        }, {
            "nodeData": {
                "age": "10-15",
                "population": 30
            }
        }]
    }, {
        "nodeData": {
            "age": "15-35",
            "population": 40
        },
        "subData": [{
            "nodeData": {
                "age": "15-25",
                "population": 25
            }
        }, {
            "nodeData": {
                "age": "25-35",
                "population": 15
            }
        }]
    }]
}, {
    "nodeData": {
        "age": "35-65",
        "population": 100
    },
    "subData": [{
        "nodeData": {
            "age": "35-50",
            "population": 75
        },
        "subData": [{
            "nodeData": {
                "age": "35-50",
                "population": 75
            }
        }]
    }, {
        "nodeData": {
            "age": "50-65",
            "population": 25
        },
        "subData": [{
            "nodeData": {
                "age": "50-65",
                "population": 25
            }
        }]
    }]
}, {
    "nodeData": {
        "age": "65",
        "population": 100
    },
    "subData": [{
        "nodeData": {
            "age": "65-75",
            "population": 60
        },
        "subData": [{
            "nodeData": {
                "age": "65-75",
                "population": 60
            }
        }]
    }, {
        "nodeData": {
            "age": "75",
            "population": 40
        },
        "subData": [{
            "nodeData": {
                "age": "75",
                "population": 40
            }
        }]
    }]
}