3

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.

ThatOneGuy
  • 176
  • 7
user2991908
  • 33
  • 1
  • 3
  • What do you mean by "relative depth"? – Lars Kotthoff Jan 25 '14 at 20:32
  • Suppose there are 5 levels. In the basic view, all 5 levels are displayed. In this view, I want only levels 1,2,3 to show full text, and levels 4,5 to show abbreviated or no text. Then, on zooming in by one level, we have levels 2,3,4,5 displayed. This time, I want levels 2,3,4 to be fully labeled, while level 5 is abbreviated. So at any zoom, I want the lowest 3 displayed levels to have full text and the remaining ones abbreviated. – user2991908 Jan 26 '14 at 05:02
  • You may be better off figuring out if there's enough space for the label -- see http://stackoverflow.com/questions/19792552/d3-put-arc-labels-in-a-pie-chart-if-there-is-enough-space – Lars Kotthoff Jan 26 '14 at 10:45

2 Answers2

4

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?

AmeliaBR
  • 27,344
  • 6
  • 86
  • 119
  • Glad to hear it. Please mark it as "accepted" so this doesn't show up in the "unanswered questions" list anymore. – AmeliaBR Jan 30 '14 at 16:08
  • I have a follow up question - If, based on the same conditions, I wanted to change the text itself, instead of just the visibility, can I still do that ? For example, if I want to display only one word at the outer spoke initially, but once we zoom in, I want to display more.. – user2991908 Feb 04 '14 at 19:40
  • Yes, you should be able to use a similar comparison between the two depth levels in any of the functions. For changing the text (which isn't transition-able) you'd probably want to also do it in the "end" function, just add a `.text((/*test condition*/? /*value*/:/*other value*/ )` call, after the visibility style function. – AmeliaBR Feb 04 '14 at 20:14
1

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      
     }
ASheppardWork
  • 95
  • 1
  • 2
  • 12