I have text objects labeling points that are evenly spaced around a circle. Thanks to this article, I am able to correctly position both the points and text objects but the labels on the left hemisphere of the circle need to be rotated 180 degrees (flipped vertically) to be more legible.
I thought I could rotate the text object about its own origin before rotating it to the appropriate position around the circle but was unable to determine how to locate the center position of each text object.
How can I rotate text objects about their center for those on the left hemisphere of the circle (angle>= PI/2 && angle<=PI*1.5)? Or is there a better technique to use?
<style type="text/css">
* {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 13px;
}
circle {
fill: steelblue;
fill-opacity: .8;
}
circle:hover {
fill: orange;
fill-opacity: .8;
}
</style>
<div id="canvas"></div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.min.js"></script>
<script type="text/javascript">
(function () {
var paddding = 250;
var createNodes = function () {
var nodeData = [
{ id: 0, label: 'AAA' },
{ id: 1, label: 'BBB' },
{ id: 2, label: 'CCC' },
{ id: 3, label: 'DDD' },
{ id: 4, label: 'EEE' },
{ id: 5, label: 'FFF' },
{ id: 6, label: 'GGG' },
{ id: 7, label: 'HHH' }
];
var radius = 100;
var nodes = [],
width = (radius * 2) + paddding,
height = (radius * 2) + paddding,
angle,
x,
y,
i;
var numNodes = nodeData.length;
for (i = 0; i < numNodes; i++) {
angle = (i / (numNodes / 2)) * Math.PI;
x = (radius * Math.cos(angle)) + (width / 2);
y = (radius * Math.sin(angle)) + (width / 2);
nodes.push({ 'id': i, 'x': x, 'y': y, 'label': nodeData[i].label, 'angle': angle });
}
return nodes;
}
var createSvg = function (radius, callback) {
d3.selectAll('svg').remove();
var svg = d3.select('#canvas').append('svg:svg')
.attr('width', (radius * 2) + paddding)
.attr('height', (radius * 2) + paddding);
callback(svg);
}
var createElements = function (svg, nodes, elementRadius) {
element = svg.selectAll('circle')
.data(nodes)
.enter().append('svg:circle')
.attr('r', elementRadius)
.attr('cx', function (d, i) { return d.x; })
.attr('cy', function (d, i) { return d.y; });
element = svg.selectAll('text')
.data(nodes)
.enter().append('svg:text')
.text(function (d, i) { return d.label + " - " + d.angle.toFixed(2) + ", " + (d.angle*180/Math.PI); })
.attr('x', function (d, i) { return nodes[0].x + 15; }) // add 15 for spacing off point
.attr('y', function (d, i) { return nodes[0].y; })
.attr("dy", ".35em")
.style("alignment-baseline","middle")
.style("text-anchor", "start")
.attr("transform", function(d,i) {
return "rotate(" + (d.angle * 180) / Math.PI + ", 225, 225)";})
;
}
var draw = function () {
var radius = 100;
var nodes = createNodes();
createSvg(radius, function (svg) {
createElements(svg, nodes, 10);
});
}
$(document).ready(function () {
draw();
});
})();
</script>
If you want to reverse the labels for those on the left side of the circle. You can achieve different ways. One way is by modifying three attributes of the text as you append it:
If you modify only some of these, you might not get the results you are looking for.
Modification of
text-end
This is needed as your text will start away from the point you are defining, and as the text may have variable length, defining a start point will be more complex than necessary. For points you need to flip, you'll need to use:
Modification of the
transform
andx
The text needs to be rotated 180 degrees so that it is right way up; however, if you modify this function to add 180 degrees to any text, then the text will appear on the wrong side of the display. So, you'll need to set x to a new value too, so that it appears on the correct side of the display:
All together, that looks like:
However, now the labels on the right are upside down. All that is left is to determine is whether a label falls on the right half or the left half and assign the appropriate attributes based on this.
Zero degrees points to the right, it is not the top of the diagram. Therefore, you need to ascertain if
d.angle
is less than 90 degrees (bottom right) or more than 270 degrees (top right), if so, your original code can be applied. If not, then you need to flip the label using the above code: