user2991908
user2991908

Reputation: 33

In D3 zoomable sunburst, how do I make the labeling dependent upon the zoom level?

I am attempting to visualize a large database with a zoomable sunburst diagram. I have too many children at the lower hierarchical levels of my json, hence the text labels are very cluttered and unreadable at the outer edges.

I am able to switch the labeling on or off based on their absolute depth level, but that means that those labels never get displayed, even when I zoom.

My question is, how do I compute the 'relative' depth in a particular zoom level and then display labels based on that?

As far as I can understand, d.depth only denotes absolute levels.

Upvotes: 3

Views: 3141

Answers (2)

ASheppardWork
ASheppardWork

Reputation: 95

I can't comment yet so, here is something you might find helpful if you go looking for Davies' code on his side and find the minified version that is not helpful with this answer at all - the full version moved here: https://code.google.com/p/testprogramming/source/browse/trunk/javascript/svg/d3/test/wheel.js?r=394&spec=svn394

The all important function isParentOf is missing from the above answer and must be gleaned from the full version.

-- additional information: If you want to have the label show based on the "zoom" level it is also important to note that the sunburst does not use the .zoom() function and you will have to find the path manually. In my case, I wanted to hide all labels at the top zoom level, but show them if any other level were chosen. To accomplish this, I hide the text at the beginning and then each click I use the following test

    var text = g.append("text")
    .attr("class","mylabel")
    .attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
    .attr("x", function(d) { return y(d.y); })
    .attr("dx", "4") // left - margin for text
    .attr("dy", ".25em") // vertical-align of text in cell
    .text(function(d) { return (d.name == 'root') ? ('') : d.name; })
    .attr("font-size", function(d) { return d.ci_type === 'type' ? 12 : 10}) //font-size of text
    //.attr("visibility",function(d) { return d.dx < 0.009? "hidden" : "visible"}) // hide text of labels of partitions less than 2%
    .attr("visibility", "hidden") // hide labels at root level - starting level


    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); })

      }
    });


    // if the vis is at the 'root' level hide text, otherwise show    <! here is the test!  
    var str = d.name;
    var patt = 'root';
    var atRoot = (str === patt) ? true : false ;

    //console.log(atRoot); 

    //console.log( d.name ) ;

    text.attr("visibility",function(d) { return atRoot ? "hidden" : "visible"})  


    // end of click      
     }

Upvotes: 1

AmeliaBR
AmeliaBR

Reputation: 27534

I'm assuming you're working from Jason Davies' example.

The relevant code from his script is

  function click(d) {
    path.transition()
      .duration(duration)
      .attrTween("d", arcTween(d));

    // Somewhat of a hack as we rely on arcTween updating the scales.
    text.style("visibility", function(e) {
          return isParentOf(d, e) ? null : d3.select(this).style("visibility");
        })
      .transition()
        .duration(duration)
        .attrTween("text-anchor", function(d) {
          return function() {
            return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
          };
        })
        .attrTween("transform", function(d) {
          var multiline = (d.name || "").split(" ").length > 1;
          return function() {
            var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
                rotate = angle + (multiline ? -.5 : 0);
            return "rotate(" + rotate + ")translate(" 
                    + (y(d.y) + padding) + ")rotate(" 
                    + (angle > 90 ? -180 : 0) + ")";
          };
        })
        .style("fill-opacity", function(e) { 
                 return isParentOf(d, e) ? 1 : 1e-6; 
        })
        .each("end", function(e) {
          d3.select(this).style("visibility", 
                    isParentOf(d, e) ? null : "hidden");
        });
  }

Notice how some of those functions reference two different data objects, d vs e. That's because, unless it is masked by an inner function, d inside the click function is the data object of the clicked element -- the one that becomes the centre of the circle.

If he gives the inner function a different name for the data object (function(e){}), then that is the data object associated with the individual element that is having attributes changed. So he is able to call functions that compare the two data objects to determine if a given element should be hidden or not at that level of zoom.

You want to do the same thing, except you're not only hiding text if it's a parent of the centre wheel, you're also hiding it if it is too deep a descendent. So you want something like:

          if (e.depth > d.depth + 3) return "hidden";

Where you add that code depends on style choices -- Jason Davies is actually changing text opacity or visibility at three points: visibility is set before and after the transition (during the "end" event), with opacity faded in between. Do you want your labels to pop in and out at a click, or do you want them to fade in and out?

Upvotes: 4

Related Questions