I've been able to construct labeled donut chart just like in the following fiddle:
But now I'm trying to place the label in the middle of each arc and to span them along the arc (curve the label to follow each arc). To do that I've been thinking of putting the svg:text
along svg:textPath
using the d3.svg.line.radial
function.
Then I stumbled upon the following fiddle:
http://jsfiddle.net/Wexcode/CrDUy/
However I'm having difficulty to tie the var arcs
(the one having actual data) from the former fiddle with the var line
from the latter fiddle as the latter fiddle uses the d3.range
function as the data.
I've been doing trial-and-error for hours but nothing works. Does anyone know how the d3.svg.line.radial
works together with the d3.svg.arc
?
The
d3.svg.line.radial
function constructs a series of cubic Bezier curves (not arcs) between multiple points in an array based on input polar coordinates (radius and angle) for each point.(The example you linked to appears to draw a circle, but only because it breaks the circle down to many tightly spaced points -- try using 5 points instead of 50, and you'll see that the shape of the curve isn't a real circle.)
The
d3.svg.arc
function contstructs a shape consisting of two con-centric arcs and the straight lines connecting them, based on values for innerRadius, outerRadius, startAngle and endAngle.Both methods specify angles in radians starting from "12 o'clock" (vertical pointing up). However, there are a couple difficulties in getting the radial line function to work with the arc data objects.
The first problem is that the line generator expects to be passed an array of multiple points, not a single object. In order to ger around that, you'll have to set the datum of the path element to be an array of the arc group's object repeated twice, once for the start and once for the end of the arc, and then use a function in
i
to determine whether the startAngle or the endAngle should be used for the angle value of each point.Here's a variation of your fiddle creating those paths. I haven't bothered getting the text to run along the path, I'm just drawing the paths in black:
http://jsfiddle.net/MX7JC/688/
Now you see the second problem: if only given two points, the line generator will just create a straight line between them.
See simple curve example: http://jsfiddle.net/4VnHn/5/
In order to get any kind of curve with the default line generators, you need to add additional points to act as control points, and change the line interpolate method to an "open" option so that the end control points aren't drawn. I found that making the start and end control points 45 degrees beyond the start and end points of the curve (around the circle) created a curve that was acceptably similar to an arc in my simple example.
See better simple curve example: http://jsfiddle.net/4VnHn/6/
For your visualization, the curves generator now has to be passed the data object repeated four times in an array, and the angle accessor is now going to need a switch statement to figure out the different points: http://jsfiddle.net/MX7JC/689/
The results are acceptable for the small donut segments, but not for the ones that are wider than 45 degrees themselves -- in these cases, the control points end up so far around the around the circle that they throw off the curve completely. The curve generator doesn't know anything about the circle, it's just trying to smoothly connect the points to show a trend from one to the next.
A better solution is to actually draw an arc, using the arc notation for SVG paths. The arc generator uses arc notation, but it creates the full two-dimensional shape. To create arcs with a line generator, you're going to need a custom line interpolator function which you can then pass to the line generator's
interpolate
method.The line generator will execute the custom line interpolator function, passing in an array of points that have already been converted from polar coordinates to x,y coordinates. From there you need to define the arc equation. Because an arc function also need to know the radius for the arc, I use a nested function -- the outside function accepts the radius as a parameter and returns the function that will accept the points array as a parameter:
Live example: http://jsfiddle.net/4VnHn/8/
To get it to work with your donut graph, I started with the version above that was producing straight lines, and changed the interpolate parameter of the line generator to use my custom function. The only additional change I had to make was to add an extra check to make sure none of the angles on the graph ended up more than 360 degrees (which I'm sure was just a rounding issue on the last arc segment, but was causing my function to draw the final arc the entire way around the circle, backwards):
Live example: http://jsfiddle.net/MX7JC/690/
Finally, to use these curves as text paths:
(for your example, you could use the donut label plus the data label to come up with something like "textcurve-Agg-Intl");
<textPath>
element for each label;xlink:href
attribute to be#
plus the same unique id value for that dataI thought of a different approach. It's slightly roundabout way to do it, but requires a lot less custom code.
Instead of creating a custom line interpolator to draw the arc, use a d3 arc generator to create the curve definition for an entire pie segment, and then use regular expressions to extract the curve definition for just the outside curve of the pie.
Simplified example here: http://jsfiddle.net/4VnHn/10/
Example with the donut chart here: http://jsfiddle.net/MX7JC/691/
Key code:
The
r-45
is just halfway between the inner and outer radii of the donuts. The[\d\.\-e,\s]+
part of the regular expression matches digits, periods, negative signs, exponent indicators ('e'), commas or whitespace, but not any of the other letters which signify a different type of path command. I think the rest is pretty self-explanatory.