Skip to content

Instantly share code, notes, and snippets.

@musically-ut
Last active December 15, 2015 14:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save musically-ut/5278601 to your computer and use it in GitHub Desktop.
Save musically-ut/5278601 to your computer and use it in GitHub Desktop.
The circle-text plugin.

A demo for the circle-text plugin, which can place text aligned along the bottom of circles.

/*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>
<html>
<head>
<title>Circle text</title>
<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>
<style>
#container {
margin: auto;
width: 800px;
}
svg {
width: 100%;
min-height: 600px;
}
.arc-path {
visibility: hidden;
}
circle {
stroke: none;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var circles = [
{
cx: 200,
cy: 300,
r: 20,
text: 'A'
},
{
cx: 400,
cy: 300,
r: 50,
text: 'plugin'
},
{
cx: 600,
cy: 300,
r: 100,
text: 'for text inside circles!'
}
];
var svg = d3.select('#container')
.append('svg'),
colors = d3.scale.category10();
var circleText = d3.circleText()
.radius(function (d) { return d.r - 5; })
.value(function (d) { return d.text; })
.precision(0.1);
var gs = svg.selectAll('g')
.data(circles)
.enter()
.append('g')
.attr('transform', function (d) {
return 'translate(' + d.cx + ',' + d.cy + ')';
});
gs.append('circle')
.attr('cx', 0).attr('cy', 0)
.attr('r', function (d) { return d.r; })
.attr('fill', function (d, i) { return colors(i); })
.attr('fill-opacity', '0.3');
var texts = gs.call(circleText);
texts
.attr('stroke', 'none')
.attr('fill', function (d, i) { return colors(i); });
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment