Zoomable Circle Packing with Automatic Text Sizing in D3.js
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
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
(ie 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
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
链接地址: http://www.djcxy.com/p/85568.html