Zoomable Circle Packing with Automatic Text Sizing in D3.js
Asked Answered
S

2

7

I'm trying to merge two of Mike's examples: Zoomable Circle Packing + Automatic Text Sizing.

It works when initially displayed at the top-level. However, if you zoom in to the next level, the fonts are not sized correctly.

I'm not sure if I need to modify the transform, or modify the part which calculates the font size.

Here's my codepen: http://codepen.io/anon/pen/GJWqrL

var circleFill = function(d) {
    if (d['color']) {
        return d.color;
    } else {
        return d.children ? color(d.depth) : '#FFF';
    }
}

var calculateTextFontSize = function(d) {
    return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 11) + "px";
}

var margin = 20,
    diameter = 960;

var color = d3.scale.linear()
    .domain([-1, 18])
    .range(["hsl(0,0%,100%)", "hsl(228,30%,40%)"])
    .interpolate(d3.interpolateHcl);

var pack = d3.layout.pack()
    .padding(2)
    .size([diameter - margin, diameter - margin])
    .value(function(d) {
        return d.size;
    })

var svg = d3.select("body").append("svg")
    .attr("width", window.innerWidth)
    .attr("height", window.innerHeight)
    .append("g")
    .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");

var focus = root,
    nodes = pack.nodes(root),
    view;

var circle = svg.selectAll("circle")
    .data(nodes)
    .enter().append("circle")
    .attr("class", function(d) {
        return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root";
    })
    .style("fill", circleFill)
    .on("click", function(d) {
        if (focus !== d) zoom(d), d3.event.stopPropagation();
    });

circle.append("svg:title")
    .text(function(d) {
        return d.name;
    })

var text = svg.selectAll("text")
    .data(nodes)
    .enter().append("text")
    .attr("class", "label")
    .style("fill-opacity", function(d) {
        return d.parent === root ? 1 : 0;
    })
    .style("display", function(d) {
        return d.parent === root ? null : "none";
    })
    .text(function(d) {
        return d.name;
    })
    .style("font-size", calculateTextFontSize)
    .attr("dy", ".35em");

var node = svg.selectAll("circle,text");

d3.select("body")
    .style("background", color(-1))
    .on("click", function() {
        zoom(root);
    });

zoomTo([root.x, root.y, root.r * 2 + margin]);

function zoom(d) {
    var focus0 = focus;
    focus = d;

    var transition = d3.transition()
        .duration(d3.event.altKey ? 7500 : 750)
        .tween("zoom", function(d) {
            var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
            return function(t) {
                zoomTo(i(t));
            };
        });

    transition.selectAll("text")
        .filter(function(d) {
            return d.parent === focus || this.style.display === "inline";
        })
        .style("fill-opacity", function(d) {
            return d.parent === focus ? 1 : 0;
        })
        .each("start", function(d) {
            if (d.parent === focus) this.style.display = "inline";
        })
        .each("end", function(d) {
            if (d.parent !== focus) this.style.display = "none";
        });
}

function zoomTo(v) {
    var k = diameter / v[2];
    view = v;
    node.attr("transform", function(d) {
        return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
    });
    circle.attr("r", function(d) {
        return d.r * k;
    });
}

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

Clicking the largest sub-circle in the "vis" circle illustrates the problem.

https://dl.dropboxusercontent.com/u/3040414/vis-circle.png

Stirps answered 2/6, 2015 at 17:2 Comment(4)
It looks to me like it's working fine for all of the sections except vis at the top level. Even children of vis are ok. Is there something different about vis?Hogg
Some of the sub-circles appear fine, but click on some of the sub-circles in the "Vis" circle to see the problem. dl.dropboxusercontent.com/u/3040414/vis-circle.pngStirps
Even in your data there is no "Vis" circle... if I search your JS there is no "Vis". What are you talking about?Forbear
It's "vis" actually. I updated the screen-shot on Dropbox: dl.dropboxusercontent.com/u/3040414/vis-circle.pngStirps
D
5

First give an id to the circle, here I am giving text name as the circle ID so that i can link the text and its circle via text name.

var circle = svg.selectAll("circle")
  .data(nodes)
  .enter().append("circle")
  .attr("class", function(d) {
    return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root";
  })
  .style("fill", circleFill)
  .attr("r", function(d) {
    return d.r;
  })
  .attr("id", function(d) {
    return d.name;//setting text name as the ID
  })
  .on("click", function(d) {
    if (focus !== d) zoom(d), d3.event.stopPropagation();
  });

On transition complete of zoom(d) function(i.e when you click on a circle and it zooms) add a timeout function which will recalculate the text font size based on the zoom.

setTimeout(function() {
  d3.selectAll("text").filter(function(d) {
    return d.parent === focus || this.style.display === "inline";
  }).style("font-size", calculateTextFontSize);//calculate the font
}, 500)

Your calculateTextFontSize function will look like this(I am using the real DOM radius to calculate the font size):

var calculateTextFontSize = function(d) {
  var id = d3.select(this).text();
  var radius = 0;
  if (d.fontsize){
    //if fontsize is already calculated use that.
    return d.fontsize;
  }
  if (!d.computed ) {
    //if computed not present get & store the getComputedTextLength() of the text field
    d.computed = this.getComputedTextLength();
    if(d.computed != 0){
      //if computed is not 0 then get the visual radius of DOM
      var r = d3.selectAll("#" + id).attr("r");
      //if radius present in DOM use that
      if (r) {
        radius = r;
      }
      //calculate the font size and store it in object for future
      d.fontsize = (2 * radius - 8) / d.computed * 24 + "px";
      return d.fontsize;  
    }
  }
}

Working code here

Deena answered 29/2, 2016 at 11:38 Comment(4)
Thanks for the answer. Interesting use of timeout. I marked this answer as correct since it works with the flare sample data I provided. Unfortunately it's not working 100% for my actual data which is larger. I suspect it has to do with the timing.Stirps
if you don't have any issues in sharing your dataset can you make it available on gist i ll have a look. integrating it with the current example.Deena
@Cyril Thank you so much, that was a big help.. There is one more thing I want to do, I wanted to show pictures in the leaf nodes, I am passing that from the root data but how can I display that in the circle?Diplosis
@Cyril buddy i need your help :(Diplosis
T
1

I also had same problem as you and I tried this one and it works for me.

D3.js Auto font-sizing based on nodes individual radius/diameter

Troytroyer answered 3/6, 2015 at 12:37 Comment(1)
I'm trying to apply this, however, the bounding box for my top-level nodes is 0,0 which makes scale = Infinity. Do you have working code somewhere online (github, jsfiddle, codepen)?Stirps

© 2022 - 2024 — McMap. All rights reserved.