This is an example of how one might place the intermediate labels on circle-packing graphs using the circle-text plugin.
Transitions on position
and radius
of the path are now possible. Click on the circles to see them in action.
This is an example of how one might place the intermediate labels on circle-packing graphs using the circle-text plugin.
Transitions on position
and radius
of the path are now possible. Click on the circles to see them in action.
/*globals d3*/ | |
(function () { | |
d3.circleText = function () { | |
"use strict"; | |
var radius = function (d) { return d.r; }, | |
value = function (d) { return d.value; }, | |
fontSize = '100%', | |
method = "stretch", spacing = "auto", | |
position = "50%", precision = null; | |
function _draw(selection) { | |
selection.each(function (d, i) { | |
var g = d3.select(this); | |
// Reuse the old id, if present. Otherwise, generate a unique id | |
// for the path. | |
var oldPath = g.select('path.arc-path'); | |
var arcId = (oldPath.node() && oldPath.attr('id')) || | |
'id-' + guid(); | |
g.selectAll('path.arc-path') | |
.data([d]) | |
.enter() | |
.append('path') | |
.classed('arc-path', true) | |
.attr('id', arcId) | |
.attr('d', function (d) { return circle_d(radius(d)); }); | |
d3.transition(g.select('path.arc-path')) | |
.attr('d', function (d) { return circle_d(radius(d)); }); | |
var arcText = g.selectAll('text.arc-text').data([d]); | |
arcText.enter() | |
.append('text') | |
.classed('arc-text', true) | |
.attr('text-anchor', 'middle') | |
.append('textPath'); | |
// Not transitioning the `font-size` style since getComptedStyles | |
// for it may not return the actual value which was set in CSS, | |
// e.g., "font-size: 100%" may return "16px" when the font-size | |
// is requested via getComptedStyles. Hence, the font-size will | |
// be unnecessarily transitioned. | |
// Also see: http://stackoverflow.com/a/10145250/987185 | |
arcText.style('font-size', fontSize); | |
/* There is a bug in Chrome which makes it impossible to select | |
* camel case tags, like textPath. Hence, using the :first-child | |
* selector to select the embedded textPath element. | |
* | |
* https://github.com/mbostock/d3/issues/925 | |
* https://bugs.webkit.org/show_bug.cgi?id=46800 | |
* https://bugs.webkit.org/show_bug.cgi?id=83438 | |
* | |
* Keep the textPath hidden until the best position | |
* has been found. | |
*/ | |
d3.transition(arcText.select(':first-child')) | |
.attr('xlink:href', '#' + arcId) | |
.attr('method', method) | |
.attr('spacing', spacing) | |
.text(value) | |
.attr('startOffset', position); | |
}); | |
return selection.selectAll('text.arc-text'); | |
} | |
/*************************************** | |
* Private functions | |
*/ | |
/* Code for generating UUID version 4 from: | |
* http://note19.com/2007/05/27/javascript-guid-generator/ | |
*/ | |
function s4() { | |
return Math.floor((1 + Math.random()) * 0x10000) | |
.toString(16) | |
.substring(1); | |
} | |
function guid() { | |
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + | |
s4() + '-' + s4() + s4() + s4(); | |
} | |
function circle_d(r) { | |
return [ "M0,0", | |
"m", "0,", -r, | |
"a", r, ",", r, " 0 1,0 0,", 2*r, | |
"a", r, ",", r, " 0 1,0 0,", -2*r | |
].join(''); | |
} | |
/*************************************** | |
* Public properties | |
*/ | |
_draw.radius = function (_) { | |
if (arguments.length === 0) return radius; | |
radius = d3.functor(_); | |
return _draw; | |
}; | |
_draw.value = function (_) { | |
if (arguments.length === 0) return value; | |
value = d3.functor(_); | |
return _draw; | |
}; | |
_draw.precision = function (_) { | |
try { | |
console.warn('circleText.precision has been deprecated.'); | |
} catch (e) { | |
// Ignore if the warning could not be displayed. | |
} | |
if (arguments.length === 0) return precision; | |
precision = _; | |
return _draw; | |
}; | |
_draw.fontSize = function (_) { | |
if (arguments.length === 0) return fontSize; | |
fontSize = d3.functor(_); | |
return _draw; | |
}; | |
_draw.method = function (_) { | |
if (arguments.length === 0) return method; | |
method = _; | |
return _draw; | |
}; | |
_draw.spacing = function (_) { | |
if (arguments.length === 0) return spacing; | |
spacing = _; | |
return _draw; | |
}; | |
_draw.position = function (_) { | |
if (arguments.length === 0) return position; | |
position = _; | |
return _draw; | |
}; | |
return _draw; | |
}; | |
})(); |
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<script src="circle-text.js"></script> | |
<script> | |
var focussedNode = null, root = null; | |
var w = 800, h = 500, r = Math.min(w, h), | |
x = d3.scale.linear().range([(w - r)/2 + 0, (w - r)/2 + r]), | |
y = d3.scale.linear().range([(h - r)/2 + 0, (h - r)/2 + r]), | |
color = d3.scale.category10(), | |
offset = 50, swingDirection = 1; | |
var fontSize = function (d) { return (200 / (d.depth + 1)) + "%"; }; | |
var circleText = d3.circleText() | |
.radius(function (d) { return d.r - 5; }) | |
.value(function (d) { return d.name; }) | |
.precision(0.1) | |
.fontSize(fontSize); | |
function _nest(values, depth) { | |
var firstLevel = {}, | |
res = {}; | |
res.name = values[0] | |
.slice((depth >= 1) ? depth - 1 : 0, depth) | |
.reverse() | |
.join('.'); | |
values.forEach(function (v) { | |
if (v[depth]) { | |
if (!firstLevel.hasOwnProperty(v[depth])) { | |
firstLevel[v[depth]] = []; | |
} | |
firstLevel[v[depth]].push(v); | |
} | |
}); | |
var nextLevels = Object.keys(firstLevel); | |
if (nextLevels.length > 0) { | |
res.children = []; | |
nextLevels.forEach(function (key) { | |
res.children.push(_nest(firstLevel[key], depth + 1)); | |
}); | |
} | |
// Collapsing domains | |
while ( res.children && | |
res.children.length === 1) { | |
res.name = res.children[0].name+ '.' + res.name; | |
res.children = res.children[0].children; | |
} | |
return res; | |
} | |
function nest(domains) { | |
"use strict"; | |
var splitDomains = domains.map(function (d) { | |
return d.split('.').reverse(); | |
}); | |
return _nest(splitDomains, 1); | |
} | |
function darkenRGB(strColor) { | |
var rgb = d3.rgb(strColor); | |
return rgb.darker().toString(); | |
} | |
function _bubbleChart(vis, nestedDomains, opts) { | |
var packer = d3.layout.pack() | |
.value(function (d) { return 1; }) | |
.size([opts.w, opts.h]); | |
var nodes = packer.nodes(nestedDomains); | |
root = nodes.filter(function (n) { return n.parent == null; })[0]; | |
var gCircles = vis.selectAll('g.circle') | |
.data(nodes) | |
.enter() | |
.append('g') | |
.classed('circle', true) | |
.attr('transform', function (d) { | |
return 'translate(' + d.x + ',' + d.y + ')'; | |
}); | |
gCircles.append('circle') | |
.attr({ | |
'class': function (d) { return d.children ? "parent" : "child"; }, | |
cx: 0, | |
cy: 0, | |
r: function (d) { return d.r; } | |
}) | |
.style({ | |
stroke: function (d) { return darkenRGB("#777"); }, | |
'stroke-width': '1px', | |
fill: function (d) { return "#777"; }, | |
'fill-opacity': function (d) { return d.children ? 0.5 : 1; }, | |
}) | |
.on("click", function(d) { | |
zoom(focussedNode == d ? root : d); | |
d3.event.stopPropagation(); | |
}); | |
var gTexts = vis.selectAll('g.label') | |
.data(nodes) | |
.enter() | |
.append('g') | |
.classed('label', true) | |
.attr('transform', function (d) { | |
return 'translate(' + d.x + ',' + d.y + ')'; | |
}); | |
gTexts.filter(function (d) { return !!d.children; }) | |
.call(circleText) | |
.style('fill', 'white'); | |
gTexts.filter(function (d) { return !d.children; }) | |
.append('text') | |
.attr('dy', '0.35em') | |
.style({ | |
'text-anchor': "middle", | |
stroke: "none", | |
"font-size": fontSize, | |
fill: 'white', | |
}) | |
.text(function (d) { return d.name; }); | |
} | |
function draw(domains) { | |
"use strict"; | |
var opts = { | |
w: w, | |
h: h, | |
x: x, | |
y: y, | |
color: color | |
}; | |
var nestedDomains = nest(domains); | |
var vis = d3.select('#container') | |
.append('svg') | |
.attr({ | |
width: w, | |
height: h | |
}) | |
.append('g') | |
.attr('transform', | |
'translate(' + 0 + ',' + 0 + ')'); | |
_bubbleChart(vis, nestedDomains, opts); | |
} | |
function zoom(d, i) { | |
var k = r / d.r / 2; | |
x.domain([d.x - d.r, d.x + d.r]); | |
y.domain([d.y - d.r, d.y + d.r]); | |
var t = d3.select('svg').transition() | |
.duration(d3.event.altKey ? 7500 : 750); | |
t.selectAll("g.circle") | |
.attr("transform", function(d) { | |
return 'translate(' + x(d.x) + ',' + y(d.y) + ')'; | |
}) | |
.select('circle') | |
.attr("r", function(d) { | |
return k * d.r; | |
}); | |
// Swing the label from left to right. | |
swingDirection = swingDirection * ((offset >= 75 || offset <= 25) ? -1 : 1); | |
offset = offset + swingDirection * 25; | |
circleText | |
.radius(function (d) { return k * d.r - 5; }) | |
.position(offset + '%'); | |
t.selectAll('g.label') | |
.attr("transform", function(d) { | |
return 'translate(' + x(d.x) + ',' + y(d.y) + ')'; | |
}) | |
.filter(function (d) { return !!d.children; }) | |
.call(circleText); | |
focussedNode = d; | |
d3.event.stopPropagation(); | |
} | |
</script> | |
<style> | |
#container { width: 100%; } | |
svg { | |
display: block; | |
margin: auto; | |
} | |
text { | |
font-size: 11px; | |
pointer-events: none; | |
} | |
text.parent { | |
fill: #1f77b4; | |
} | |
circle { | |
pointer-events: all; | |
} | |
circle.parent { | |
stroke: steelblue; | |
} | |
circle.child { | |
pointer-events: none; | |
} | |
.arc-path { | |
visibility: hidden; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="container"></div> | |
<script> | |
var domains = [ | |
'00.names.playground.utkarshu.in', | |
'00.db.playground.utkarshu.in', | |
'00.server.playground.utkarshu.in', | |
'01.server.playground.utkarshu.in', | |
'new.stable.utkarshu.in', | |
'db.stable.utkarshu.in', | |
'mailbox.stable.utkarshu.in', | |
'data.stable.utkarshu.in', | |
'server.stable.utkarshu.in', | |
'login.utkarshu.in', | |
'00.redirects.fun.utkarshu.in', | |
'01.redirects.fun.utkarshu.in', | |
'private.experiments.utkarshu.in', | |
'public.experiments.utkarshu.in', | |
'00.serenity.utkarshu.in', | |
'01.serenity.utkarshu.in', | |
'integration.serenity.utkarshu.in', | |
'00.db.other.utkarshu.in', | |
'00.server.other.utkarshu.in', | |
'old.data.utkarshu.in', | |
'monitor.utkarshu.in', | |
'00.new.archive.utkarshu.in', | |
'00.db.archive.utkarshu.in', | |
'00.server.archive.utkarshu.in', | |
'00.redirects.unstable.utkarshu.in', | |
'01.redirects.unstable.utkarshu.in', | |
] | |
</script> | |
<script> | |
draw(domains); | |
</script> | |
</body> |