Extending the D3 Zoomable Sunburst with Labels

At luzid, the d3.js library is our tool of choice for creating sophisticated interactive data visualizations in the browser that immediately catch the user’s attention. Even more so, D3 is widely accepted amongst data visualization practitioners around the world. In this article, we will extend Mike Bostock’s Zoomable Sunburst example with text labels.

Sunburst

ZoomableSunburst
A Sunburst visualization is a visualization technique suitable for presenting hierarchical data structures in a radial layout. In this layout the root node is located at the center and nodes farther away from the root are laid out in concentric rings located farther away from the center. A ring contains all nodes at the same distance from the root within the data structure and each node is represented by an arc (and an associated angle span) and a color – visual properties which relate to metrics of the node such as ‘cost’ and ‘type’. A node and its child nodes are always laid out adjacently in the same angle span. Essentially, Sunbursts can be described as nested pie charts.

A Sunburst can quickly become cluttered if nodes with a small angle span have lots of child nodes. Since the child nodes are laid out within their parent’s angle span, their respective arcs can become unnoticeably small. The ability to zoom into a particular node and bring it close to the center elegantly leverages this shortcoming. But still, without labels, even a simple pie chart is only of limited value to the reader. The challenge in this tutorial lies in extending the Zoomable Sunburst in such a way that labels only remain visible if their associated nodes are visible. Enough said, let’s get started!

Labels

ZoomableSunburstWithLabels
In Bostock’s example, arcs are rendered as filled svg:path elements. In order to associate an arc with a label, we introduce a parenting svg:g element first:

var g = svg.selectAll("g")
  .data(partition.nodes(root))
    .enter().append("g");

var path = g.append("path")
  .attr("d", arc)
  .style("fill", function(d) { return color((d.children ? d : d.parent).name); })
  .on("click", click);

Now we can add an svg:text element to each group element:

var text = g.append("text")
  .attr("x", function(d) { return y(d.y); })
  .attr("dx", "6") // margin
  .attr("dy", ".35em") // vertical-align
  .text(function(d) { return d.name; });

At that point labels ared rendered, but they do not yet align with an arc’s angle span. Let’s see how this could be accomplished: the start and end positions of an arc are located in a range of 0 to 2π radians, x(d.x) and x(d.x + d.dx) of a particular data point, respectively, where x(d.dx) identifies an arc’s angle width. Therefore, the angle in the middle of the start and the end position equals to x(d.x + d.dx / 2) radians. Since angles are offset by 2π in D3 compared to SVG (the angle 0 is at a circle’s topmost position in D3, whereas it is located at a circle’s rightmost position in SVG), we must subtract the offset before we can rotate the text using an SVG rotate transform. Lastly, radians must be converted to degrees by applying the transform angle / 2 * Math.PI * 360, or simply angle / Math.PI * 180:

function computeTextRotation(d) {
  var angle = x(d.x + d.dx / 2) - Math.PI / 2;
  return angle / Math.PI * 180;
}

text.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; });

If you click on an arc now you will see that the arcs align correctly, but all texts remain in their old positions. What is therefore still left to do is to show a label only if its associated arc is visible (which is exactly the case if an arc lies in the angle span of the zoomed arc):

function click(d) {
  // fade out all text elements
  text.transition().attr("opacity", 0);

  path.transition()
    .duration(750)
    .attrTween("d", arcTween(d))
    .each("end", function(e, i) {
        // check if the animated element's data e lies within the visible angle span given in d
        if (e.x >= d.x && e.x < (d.x + d.dx)) {
          // get a selection of the associated text element
          var arcText = d3.select(this.parentNode).select("text");
          // fade in the text element and recalculate positions
          arcText.transition().duration(750)
            .attr("opacity", 1)
            .attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
            .attr("x", function(d) { return y(d.y); });
        }
    });
}

In this tutorial I have shown how Mike Bostock's Zoomable Sunburst example can be extended with text labels to improve the comprehensibility of the chart. Check out the full example code at bl.ocks.org and stay tuned for an upcoming article in which we will explore other algorithms to further improve label rendering to support readability of Sunburst visualizations. If you have been new to D3 you should definitely check out the many other great examples in the D3.js Gallery.

3 Responses to “Extending the D3 Zoomable Sunburst with Labels”

  1. mnv

    Nice example, thanks!
    Text can be clickable too:

    var text = g.append("text")
    .attr("dy", ".35em") // vertical-align
    .attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
    .attr("x", function(d) { return y(d.y); })
    .attr("dx", "6") // margin
    .attr("display", 'block')
    .text(function(d) {
    return d.name;
    }).on("click", click);

    Reply
  2. StevenWill

    Wow, this is really cool! Thanks for sharing.

    Reply

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>