@cryptotaxi247 / netdata-1 / commits / 8b7cd623a

even more d3pie optimizations

Costa Tsaousis (ktsaou) committed Feb 4, 2018 at 04:15 UTC 8b7cd623aa59ae5f2fc9057cf8986fcef1d8a55f
2 files changed +1871 -1870
web/dashboard.js
+1 -1
@@ -243,7 +243,7 @@ var NETDATA = window.NETDATA || {};
243 NETDATA.raphael_js = NETDATA.serverStatic + 'lib/raphael-2.2.4-min.js';
244 NETDATA.c3_js = NETDATA.serverStatic + 'lib/c3-0.4.18.min.js';
245 NETDATA.c3_css = NETDATA.serverStatic + 'css/c3-0.4.18.min.css';
246 - NETDATA.d3pie_js = NETDATA.serverStatic + 'lib/d3pie-0.2.1-netdata-2.js?v7';
246 + NETDATA.d3pie_js = NETDATA.serverStatic + 'lib/d3pie-0.2.1-netdata-2.js?v26';
247 NETDATA.d3_js = NETDATA.serverStatic + 'lib/d3-4.12.2.min.js';
248 NETDATA.morris_js = NETDATA.serverStatic + 'lib/morris-0.5.1.min.js';
249 NETDATA.morris_css = NETDATA.serverStatic + 'css/morris-0.5.1.css';
web/lib/d3pie-0.2.1-netdata-2.js
+1870 -1869
@@ -21,1207 +21,1206 @@
21 }
22 }(this, function() {
23
24 - var _scriptName = "d3pie";
25 - var _version = "0.2.1";
24 + var _scriptName = "d3pie";
25 + var _version = "0.2.1";
26
27 - // used to uniquely generate IDs and classes, ensuring no conflict between multiple pies on the same page
28 - var _uniqueIDCounter = 0;
27 + // used to uniquely generate IDs and classes, ensuring no conflict between multiple pies on the same page
28 + var _uniqueIDCounter = 0;
29
30
31 - // this section includes all helper libs on the d3pie object. They're populated via grunt-template. Note: to keep
32 - // the syntax highlighting from getting all messed up, I commented out each line. That REQUIRES each of the files
33 - // to have an empty first line. Crumby, yes, but acceptable.
34 - //// --------- _default-settings.js -----------/**
31 + // this section includes all helper libs on the d3pie object. They're populated via grunt-template. Note: to keep
32 + // the syntax highlighting from getting all messed up, I commented out each line. That REQUIRES each of the files
33 + // to have an empty first line. Crumby, yes, but acceptable.
34 + //// --------- _default-settings.js -----------/**
35 /**
36 * Contains the out-the-box settings for the script. Any of these settings that aren't explicitly overridden for the
37 * d3pie instance will inherit from these. This is also included on the main website for use in the generation script.
38 */
39 var defaultSettings = {
40 - header: {
41 - title: {
42 - text: "",
43 - color: "#333333",
44 - fontSize: 18,
45 - fontWeight: "bold",
46 - font: "arial"
47 - },
48 - subtitle: {
49 - text: "",
50 - color: "#666666",
51 - fontSize: 14,
52 - fontWeight: "bold",
53 - font: "arial"
54 - },
55 - location: "top-center",
56 - titleSubtitlePadding: 8
57 - },
58 - footer: {
59 - text: "",
60 - color: "#666666",
61 - fontSize: 14,
62 - fontWeight: "bold",
63 - font: "arial",
64 - location: "left"
65 - },
66 - size: {
67 - canvasHeight: 500,
68 - canvasWidth: 500,
69 - pieInnerRadius: "0%",
70 - pieOuterRadius: null
71 - },
72 - data: {
73 - sortOrder: "none",
74 - ignoreSmallSegments: {
75 - enabled: false,
76 - valueType: "percentage",
77 - value: null
78 - },
79 - smallSegmentGrouping: {
80 - enabled: false,
81 - value: 1,
82 - valueType: "percentage",
83 - label: "Other",
84 - color: "#cccccc"
85 - },
86 - content: []
87 - },
88 - labels: {
89 - outer: {
90 - format: "label",
91 - hideWhenLessThanPercentage: null,
92 - pieDistance: 30
93 - },
94 - inner: {
95 - format: "percentage",
96 - hideWhenLessThanPercentage: null
97 - },
98 - mainLabel: {
99 - color: "#333333",
100 - font: "arial",
101 - fontWeight: "normal",
102 - fontSize: 10
103 - },
104 - percentage: {
105 - color: "#dddddd",
106 - font: "arial",
107 - fontWeight: "bold",
108 - fontSize: 10,
109 - decimalPlaces: 0
110 - },
111 - value: {
112 - color: "#cccc44",
113 - fontWeight: "bold",
114 - font: "arial",
115 - fontSize: 10
116 - },
117 - lines: {
118 - enabled: true,
119 - style: "curved",
120 - color: "segment"
121 - },
122 - truncation: {
123 - enabled: false,
124 - truncateLength: 30
125 - },
40 + header: {
41 + title: {
42 + text: "",
43 + color: "#333333",
44 + fontSize: 18,
45 + fontWeight: "bold",
46 + font: "arial"
47 + },
48 + subtitle: {
49 + text: "",
50 + color: "#666666",
51 + fontSize: 14,
52 + fontWeight: "bold",
53 + font: "arial"
54 + },
55 + location: "top-center",
56 + titleSubtitlePadding: 8
57 + },
58 + footer: {
59 + text: "",
60 + color: "#666666",
61 + fontSize: 14,
62 + fontWeight: "bold",
63 + font: "arial",
64 + location: "left"
65 + },
66 + size: {
67 + canvasHeight: 500,
68 + canvasWidth: 500,
69 + pieInnerRadius: "0%",
70 + pieOuterRadius: null
71 + },
72 + data: {
73 + sortOrder: "none",
74 + ignoreSmallSegments: {
75 + enabled: false,
76 + valueType: "percentage",
77 + value: null
78 + },
79 + smallSegmentGrouping: {
80 + enabled: false,
81 + value: 1,
82 + valueType: "percentage",
83 + label: "Other",
84 + color: "#cccccc"
85 + },
86 + content: []
87 + },
88 + labels: {
89 + outer: {
90 + format: "label",
91 + hideWhenLessThanPercentage: null,
92 + pieDistance: 30
93 + },
94 + inner: {
95 + format: "percentage",
96 + hideWhenLessThanPercentage: null
97 + },
98 + mainLabel: {
99 + color: "#333333",
100 + font: "arial",
101 + fontWeight: "normal",
102 + fontSize: 10
103 + },
104 + percentage: {
105 + color: "#dddddd",
106 + font: "arial",
107 + fontWeight: "bold",
108 + fontSize: 10,
109 + decimalPlaces: 0
110 + },
111 + value: {
112 + color: "#cccc44",
113 + fontWeight: "bold",
114 + font: "arial",
115 + fontSize: 10
116 + },
117 + lines: {
118 + enabled: true,
119 + style: "curved",
120 + color: "segment"
121 + },
122 + truncation: {
123 + enabled: false,
124 + truncateLength: 30
125 + },
126 formatter: null
127 - },
128 - effects: {
129 - load: {
130 - effect: "none", // "default", commented in the code
131 - speed: 1000
132 - },
133 - pullOutSegmentOnClick: {
134 - effect: "none", // "bounce", commented in the code
135 - speed: 300,
136 - size: 10
137 - },
138 - highlightSegmentOnMouseover: false,
139 - highlightLuminosity: -0.2
140 - },
141 - tooltips: {
142 - enabled: false,
143 - type: "placeholder", // caption|placeholder
144 - string: "",
145 - placeholderParser: null,
146 - styles: {
147 - fadeInSpeed: 250,
148 - backgroundColor: "#000000",
149 - backgroundOpacity: 0.5,
150 - color: "#efefef",
151 - borderRadius: 2,
152 - font: "arial",
153 - fontWeight: "bold",
154 - fontSize: 10,
155 - padding: 4
156 - }
157 - },
158 - misc: {
159 - colors: {
160 - background: null,
161 - segments: [
162 - "#2484c1", "#65a620", "#7b6888", "#a05d56", "#961a1a", "#d8d23a", "#e98125", "#d0743c", "#635222", "#6ada6a",
163 - "#0c6197", "#7d9058", "#207f33", "#44b9b0", "#bca44a", "#e4a14b", "#a3acb2", "#8cc3e9", "#69a6f9", "#5b388f",
164 - "#546e91", "#8bde95", "#d2ab58", "#273c71", "#98bf6e", "#4daa4b", "#98abc5", "#cc1010", "#31383b", "#006391",
165 - "#c2643f", "#b0a474", "#a5a39c", "#a9c2bc", "#22af8c", "#7fcecf", "#987ac6", "#3d3b87", "#b77b1c", "#c9c2b6",
166 - "#807ece", "#8db27c", "#be66a2", "#9ed3c6", "#00644b", "#005064", "#77979f", "#77e079", "#9c73ab", "#1f79a7"
167 - ],
168 - segmentStroke: "#ffffff"
169 - },
170 - gradient: {
171 - enabled: false,
172 - percentage: 95,
173 - color: "#000000"
174 - },
175 - canvasPadding: {
176 - top: 5,
177 - right: 5,
178 - bottom: 5,
179 - left: 5
180 - },
181 - pieCenterOffset: {
182 - x: 0,
183 - y: 0
184 - },
185 - cssPrefix: null
186 - },
187 - callbacks: {
188 - onload: null,
189 - onMouseoverSegment: null,
190 - onMouseoutSegment: null,
191 - onClickSegment: null
192 - }
127 + },
128 + effects: {
129 + load: {
130 + effect: "none", // "default", commented in the code
131 + speed: 1000
132 + },
133 + pullOutSegmentOnClick: {
134 + effect: "none", // "bounce", commented in the code
135 + speed: 300,
136 + size: 10
137 + },
138 + highlightSegmentOnMouseover: false,
139 + highlightLuminosity: -0.2
140 + },
141 + tooltips: {
142 + enabled: false,
143 + type: "placeholder", // caption|placeholder
144 + string: "",
145 + placeholderParser: null,
146 + styles: {
147 + fadeInSpeed: 250,
148 + backgroundColor: "#000000",
149 + backgroundOpacity: 0.5,
150 + color: "#efefef",
151 + borderRadius: 2,
152 + font: "arial",
153 + fontWeight: "bold",
154 + fontSize: 10,
155 + padding: 4
156 + }
157 + },
158 + misc: {
159 + colors: {
160 + background: null,
161 + segments: [
162 + "#2484c1", "#65a620", "#7b6888", "#a05d56", "#961a1a", "#d8d23a", "#e98125", "#d0743c", "#635222", "#6ada6a",
163 + "#0c6197", "#7d9058", "#207f33", "#44b9b0", "#bca44a", "#e4a14b", "#a3acb2", "#8cc3e9", "#69a6f9", "#5b388f",
164 + "#546e91", "#8bde95", "#d2ab58", "#273c71", "#98bf6e", "#4daa4b", "#98abc5", "#cc1010", "#31383b", "#006391",
165 + "#c2643f", "#b0a474", "#a5a39c", "#a9c2bc", "#22af8c", "#7fcecf", "#987ac6", "#3d3b87", "#b77b1c", "#c9c2b6",
166 + "#807ece", "#8db27c", "#be66a2", "#9ed3c6", "#00644b", "#005064", "#77979f", "#77e079", "#9c73ab", "#1f79a7"
167 + ],
168 + segmentStroke: "#ffffff"
169 + },
170 + gradient: {
171 + enabled: false,
172 + percentage: 95,
173 + color: "#000000"
174 + },
175 + canvasPadding: {
176 + top: 5,
177 + right: 5,
178 + bottom: 5,
179 + left: 5
180 + },
181 + pieCenterOffset: {
182 + x: 0,
183 + y: 0
184 + },
185 + cssPrefix: null
186 + },
187 + callbacks: {
188 + onload: null,
189 + onMouseoverSegment: null,
190 + onMouseoutSegment: null,
191 + onClickSegment: null
192 + }
193 };
194
195 - //// --------- validate.js -----------
195 + //// --------- validate.js -----------
196 var validate = {
197
198 - // called whenever a new pie chart is created
199 - initialCheck: function(pie) {
200 - var cssPrefix = pie.cssPrefix;
201 - var element = pie.element;
202 - var options = pie.options;
203 -
204 - // confirm d3 is available [check minimum version]
205 - if (!window.d3 || !window.d3.hasOwnProperty("version")) {
206 - console.error("d3pie error: d3 is not available");
207 - return false;
208 - }
209 -
210 - // confirm element is either a DOM element or a valid string for a DOM element
211 - if (!(element instanceof HTMLElement || element instanceof SVGElement)) {
212 - console.error("d3pie error: the first d3pie() param must be a valid DOM element (not jQuery) or a ID string.");
213 - return false;
214 - }
215 -
216 - // confirm the CSS prefix is valid. It has to start with a-Z and contain nothing but a-Z0-9_-
217 - if (!(/[a-zA-Z][a-zA-Z0-9_-]*$/.test(cssPrefix))) {
218 - console.error("d3pie error: invalid options.misc.cssPrefix");
219 - return false;
220 - }
221 -
222 - // confirm some data has been supplied
223 - if (!helpers.isArray(options.data.content)) {
224 - console.error("d3pie error: invalid config structure: missing data.content property.");
225 - return false;
226 - }
227 - if (options.data.content.length === 0) {
228 - console.error("d3pie error: no data supplied.");
229 - return false;
230 - }
231 -
232 - // clear out any invalid data. Each data row needs a valid positive number and a label
233 - var data = [];
234 - for (var i=0; i<options.data.content.length; i++) {
235 - if (typeof options.data.content[i].value !== "number" || isNaN(options.data.content[i].value)) {
236 - console.log("not valid: ", options.data.content[i]);
237 - continue;
238 - }
239 - if (options.data.content[i].value <= 0) {
240 - console.log("not valid - should have positive value: ", options.data.content[i]);
241 - continue;
242 - }
243 - data.push(options.data.content[i]);
244 - }
245 - pie.options.data.content = data;
246 -
247 - // labels.outer.hideWhenLessThanPercentage - 1-100
248 - // labels.inner.hideWhenLessThanPercentage - 1-100
249 -
250 - return true;
251 - }
198 + // called whenever a new pie chart is created
199 + initialCheck: function(pie) {
200 + var cssPrefix = pie.cssPrefix;
201 + var element = pie.element;
202 + var options = pie.options;
203 +
204 + // confirm d3 is available [check minimum version]
205 + if (!window.d3 || !window.d3.hasOwnProperty("version")) {
206 + console.error("d3pie error: d3 is not available");
207 + return false;
208 + }
209 +
210 + // confirm element is either a DOM element or a valid string for a DOM element
211 + if (!(element instanceof HTMLElement || element instanceof SVGElement)) {
212 + console.error("d3pie error: the first d3pie() param must be a valid DOM element (not jQuery) or a ID string.");
213 + return false;
214 + }
215 +
216 + // confirm the CSS prefix is valid. It has to start with a-Z and contain nothing but a-Z0-9_-
217 + if (!(/[a-zA-Z][a-zA-Z0-9_-]*$/.test(cssPrefix))) {
218 + console.error("d3pie error: invalid options.misc.cssPrefix");
219 + return false;
220 + }
221 +
222 + // confirm some data has been supplied
223 + if (!helpers.isArray(options.data.content)) {
224 + console.error("d3pie error: invalid config structure: missing data.content property.");
225 + return false;
226 + }
227 + if (options.data.content.length === 0) {
228 + console.error("d3pie error: no data supplied.");
229 + return false;
230 + }
231 +
232 + // clear out any invalid data. Each data row needs a valid positive number and a label
233 + var data = [];
234 + for (var i=0; i<options.data.content.length; i++) {
235 + if (typeof options.data.content[i].value !== "number" || isNaN(options.data.content[i].value)) {
236 + console.log("not valid: ", options.data.content[i]);
237 + continue;
238 + }
239 + if (options.data.content[i].value <= 0) {
240 + console.log("not valid - should have positive value: ", options.data.content[i]);
241 + continue;
242 + }
243 + data.push(options.data.content[i]);
244 + }
245 + pie.options.data.content = data;
246 +
247 + // labels.outer.hideWhenLessThanPercentage - 1-100
248 + // labels.inner.hideWhenLessThanPercentage - 1-100
249 +
250 + return true;
251 + }
252 };
253
254 - //// --------- helpers.js -----------
254 + //// --------- helpers.js -----------
255 var helpers = {
256
257 - // creates the SVG element
258 - addSVGSpace: function(pie) {
259 - var element = pie.element;
260 - var canvasWidth = pie.options.size.canvasWidth;
261 - var canvasHeight = pie.options.size.canvasHeight;
262 - var backgroundColor = pie.options.misc.colors.background;
263 -
264 - var svg = d3.select(element).append("svg:svg")
265 - .attr("width", canvasWidth)
266 - .attr("height", canvasHeight);
267 -
268 - if (backgroundColor !== "transparent") {
269 - svg.style("background-color", function() { return backgroundColor; });
270 - }
271 -
272 - return svg;
273 - },
274 -
275 - shuffleArray: function(array) {
276 - var currentIndex = array.length, tmpVal, randomIndex;
277 -
278 - while (0 !== currentIndex) {
279 - randomIndex = Math.floor(Math.random() * currentIndex);
280 - currentIndex -= 1;
281 -
282 - // and swap it with the current element
283 - tmpVal = array[currentIndex];
284 - array[currentIndex] = array[randomIndex];
285 - array[randomIndex] = tmpVal;
286 - }
287 - return array;
288 - },
289 -
290 - processObj: function(obj, is, value) {
291 - if (typeof is === 'string') {
292 - return helpers.processObj(obj, is.split('.'), value);
293 - } else if (is.length === 1 && value !== undefined) {
257 + // creates the SVG element
258 + addSVGSpace: function(pie) {
259 + var element = pie.element;
260 + var canvasWidth = pie.options.size.canvasWidth;
261 + var canvasHeight = pie.options.size.canvasHeight;
262 + var backgroundColor = pie.options.misc.colors.background;
263 +
264 + var svg = d3.select(element).append("svg:svg")
265 + .attr("width", canvasWidth)
266 + .attr("height", canvasHeight);
267 +
268 + if (backgroundColor !== "transparent") {
269 + svg.style("background-color", function() { return backgroundColor; });
270 + }
271 +
272 + return svg;
273 + },
274 +
275 + shuffleArray: function(array) {
276 + var currentIndex = array.length, tmpVal, randomIndex;
277 +
278 + while (0 !== currentIndex) {
279 + randomIndex = Math.floor(Math.random() * currentIndex);
280 + currentIndex -= 1;
281 +
282 + // and swap it with the current element
283 + tmpVal = array[currentIndex];
284 + array[currentIndex] = array[randomIndex];
285 + array[randomIndex] = tmpVal;
286 + }
287 + return array;
288 + },
289 +
290 + processObj: function(obj, is, value) {
291 + if (typeof is === 'string') {
292 + return helpers.processObj(obj, is.split('.'), value);
293 + } else if (is.length === 1 && value !== undefined) {
294 obj[is[0]] = value;
295 - return obj[is[0]];
296 - } else if (is.length === 0) {
297 - return obj;
298 - } else {
299 - return helpers.processObj(obj[is[0]], is.slice(1), value);
300 - }
301 - },
302 -
303 - getDimensions: function(el) {
304 - if(typeof el === 'string')
305 - el = document.getElementById(el);
306 -
307 - var w = 0, h = 0;
308 - if (el) {
309 - var dimensions = el.getBBox();
310 - w = dimensions.width;
311 - h = dimensions.height;
312 - }
313 - else {
314 - console.log("error: getDimensions() " + id + " not found.");
315 - }
316 -
317 - return { w: w, h: h };
318 - },
319 -
320 - /**
321 - * This is based on the SVG coordinate system, where top-left is 0,0 and bottom right is n-n.
322 - * @param r1
323 - * @param r2
324 - * @returns {boolean}
325 - */
326 - rectIntersect: function(r1, r2) {
327 - var returnVal = (
328 - // r2.left > r1.right
329 - (r2.x > (r1.x + r1.w)) ||
330 -
331 - // r2.right < r1.left
332 - ((r2.x + r2.w) < r1.x) ||
333 -
334 - // r2.top < r1.bottom
335 - ((r2.y + r2.h) < r1.y) ||
336 -
337 - // r2.bottom > r1.top
338 - (r2.y > (r1.y + r1.h))
339 - );
340 -
341 - return !returnVal;
342 - },
343 -
344 - /**
345 - * Returns a lighter/darker shade of a hex value, based on a luminance value passed.
346 - * @param hex a hex color value such as “#abc” or “#123456″ (the hash is optional)
347 - * @param lum the luminosity factor: -0.1 is 10% darker, 0.2 is 20% lighter, etc.
348 - * @returns {string}
349 - */
350 - getColorShade: function(hex, lum) {
351 -
352 - // validate hex string
353 - hex = String(hex).replace(/[^0-9a-f]/gi, '');
354 - if (hex.length < 6) {
355 - hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
356 - }
357 - lum = lum || 0;
358 -
359 - // convert to decimal and change luminosity
360 - var newHex = "#";
361 - for (var i=0; i<3; i++) {
362 - var c = parseInt(hex.substr(i * 2, 2), 16);
363 - c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
364 - newHex += ("00" + c).substr(c.length);
365 - }
366 -
367 - return newHex;
368 - },
369 -
370 - /**
371 - * Users can choose to specify segment colors in three ways (in order of precedence):
372 - * 1. include a "color" attribute for each row in data.content
373 - * 2. include a misc.colors.segments property which contains an array of hex codes
374 - * 3. specify nothing at all and rely on this lib provide some reasonable defaults
375 - *
376 - * This function sees what's included and populates this.options.colors with whatever's required
377 - * for this pie chart.
378 - * @param data
379 - */
380 - initSegmentColors: function(pie) {
381 - var data = pie.options.data.content;
382 - var colors = pie.options.misc.colors.segments;
383 -
384 - // TODO this needs a ton of error handling
385 -
386 - var finalColors = [];
387 - for (var i=0; i<data.length; i++) {
388 - if (data[i].hasOwnProperty("color")) {
389 - finalColors.push(data[i].color);
390 - } else {
391 - finalColors.push(colors[i]);
392 - }
393 - }
394 -
395 - return finalColors;
396 - },
397 -
398 - applySmallSegmentGrouping: function(data, smallSegmentGrouping) {
399 - var totalSize;
400 - if (smallSegmentGrouping.valueType === "percentage") {
401 - totalSize = math.getTotalPieSize(data);
402 - }
403 -
404 - // loop through each data item
405 - var newData = [];
406 - var groupedData = [];
407 - var totalGroupedData = 0;
408 - for (var i=0; i<data.length; i++) {
409 - if (smallSegmentGrouping.valueType === "percentage") {
410 - var dataPercent = (data[i].value / totalSize) * 100;
411 - if (dataPercent <= smallSegmentGrouping.value) {
412 - groupedData.push(data[i]);
413 - totalGroupedData += data[i].value;
414 - continue;
415 - }
416 - data[i].isGrouped = false;
417 - newData.push(data[i]);
418 - } else {
419 - if (data[i].value <= smallSegmentGrouping.value) {
420 - groupedData.push(data[i]);
421 - totalGroupedData += data[i].value;
422 - continue;
423 - }
424 - data[i].isGrouped = false;
425 - newData.push(data[i]);
426 - }
427 - }
428 -
429 - // we're done! See if there's any small segment groups to add
430 - if (groupedData.length) {
431 - newData.push({
432 - color: smallSegmentGrouping.color,
433 - label: smallSegmentGrouping.label,
434 - value: totalGroupedData,
435 - isGrouped: true,
436 - groupedData: groupedData
437 - });
438 - }
439 -
440 - return newData;
441 - },
442 -
443 - // for debugging
444 - showPoint: function(svg, x, y) {
445 - svg.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).style("fill", "black");
446 - },
447 -
448 - isFunction: function(functionToCheck) {
449 - var getType = {};
450 - return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
451 - },
452 -
453 - isArray: function(o) {
454 - return Object.prototype.toString.call(o) === '[object Array]';
455 - }
295 + return obj[is[0]];
296 + } else if (is.length === 0) {
297 + return obj;
298 + } else {
299 + return helpers.processObj(obj[is[0]], is.slice(1), value);
300 + }
301 + },
302 +
303 + getDimensions: function(el) {
304 + if(typeof el === 'string')
305 + el = document.getElementById(el);
306 +
307 + var w = 0, h = 0;
308 + if (el) {
309 + var dimensions = el.getBBox();
310 + w = dimensions.width;
311 + h = dimensions.height;
312 + }
313 + else {
314 + console.log("error: getDimensions() " + id + " not found.");
315 + }
316 +
317 + return { w: w, h: h };
318 + },
319 +
320 + /**
321 + * This is based on the SVG coordinate system, where top-left is 0,0 and bottom right is n-n.
322 + * @param r1
323 + * @param r2
324 + * @returns {boolean}
325 + */
326 + rectIntersect: function(r1, r2) {
327 + var returnVal = (
328 + // r2.left > r1.right
329 + (r2.x > (r1.x + r1.w)) ||
330 +
331 + // r2.right < r1.left
332 + ((r2.x + r2.w) < r1.x) ||
333 +
334 + // r2.top < r1.bottom
335 + ((r2.y + r2.h) < r1.y) ||
336 +
337 + // r2.bottom > r1.top
338 + (r2.y > (r1.y + r1.h))
339 + );
340 +
341 + return !returnVal;
342 + },
343 +
344 + /**
345 + * Returns a lighter/darker shade of a hex value, based on a luminance value passed.
346 + * @param hex a hex color value such as “#abc” or “#123456″ (the hash is optional)
347 + * @param lum the luminosity factor: -0.1 is 10% darker, 0.2 is 20% lighter, etc.
348 + * @returns {string}
349 + */
350 + getColorShade: function(hex, lum) {
351 +
352 + // validate hex string
353 + hex = String(hex).replace(/[^0-9a-f]/gi, '');
354 + if (hex.length < 6) {
355 + hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
356 + }
357 + lum = lum || 0;
358 +
359 + // convert to decimal and change luminosity
360 + var newHex = "#";
361 + for (var i=0; i<3; i++) {
362 + var c = parseInt(hex.substr(i * 2, 2), 16);
363 + c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
364 + newHex += ("00" + c).substr(c.length);
365 + }
366 +
367 + return newHex;
368 + },
369 +
370 + /**
371 + * Users can choose to specify segment colors in three ways (in order of precedence):
372 + * 1. include a "color" attribute for each row in data.content
373 + * 2. include a misc.colors.segments property which contains an array of hex codes
374 + * 3. specify nothing at all and rely on this lib provide some reasonable defaults
375 + *
376 + * This function sees what's included and populates this.options.colors with whatever's required
377 + * for this pie chart.
378 + * @param data
379 + */
380 + initSegmentColors: function(pie) {
381 + var data = pie.options.data.content;
382 + var colors = pie.options.misc.colors.segments;
383 +
384 + // TODO this needs a ton of error handling
385 +
386 + var finalColors = [];
387 + for (var i=0; i<data.length; i++) {
388 + if (data[i].hasOwnProperty("color")) {
389 + finalColors.push(data[i].color);
390 + } else {
391 + finalColors.push(colors[i]);
392 + }
393 + }
394 +
395 + return finalColors;
396 + },
397 +
398 + applySmallSegmentGrouping: function(data, smallSegmentGrouping) {
399 + var totalSize;
400 + if (smallSegmentGrouping.valueType === "percentage") {
401 + totalSize = math.getTotalPieSize(data);
402 + }
403 +
404 + // loop through each data item
405 + var newData = [];
406 + var groupedData = [];
407 + var totalGroupedData = 0;
408 + for (var i=0; i<data.length; i++) {
409 + if (smallSegmentGrouping.valueType === "percentage") {
410 + var dataPercent = (data[i].value / totalSize) * 100;
411 + if (dataPercent <= smallSegmentGrouping.value) {
412 + groupedData.push(data[i]);
413 + totalGroupedData += data[i].value;
414 + continue;
415 + }
416 + data[i].isGrouped = false;
417 + newData.push(data[i]);
418 + } else {
419 + if (data[i].value <= smallSegmentGrouping.value) {
420 + groupedData.push(data[i]);
421 + totalGroupedData += data[i].value;
422 + continue;
423 + }
424 + data[i].isGrouped = false;
425 + newData.push(data[i]);
426 + }
427 + }
428 +
429 + // we're done! See if there's any small segment groups to add
430 + if (groupedData.length) {
431 + newData.push({
432 + color: smallSegmentGrouping.color,
433 + label: smallSegmentGrouping.label,
434 + value: totalGroupedData,
435 + isGrouped: true,
436 + groupedData: groupedData
437 + });
438 + }
439 +
440 + return newData;
441 + },
442 +
443 + // for debugging
444 + showPoint: function(svg, x, y) {
445 + svg.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).style("fill", "black");
446 + },
447 +
448 + isFunction: function(functionToCheck) {
449 + var getType = {};
450 + return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
451 + },
452 +
453 + isArray: function(o) {
454 + return Object.prototype.toString.call(o) === '[object Array]';
455 + }
456 };
457
458
459 // taken from jQuery
460 var extend = function() {
461 - var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
462 - i = 1,
463 - length = arguments.length,
464 - deep = false,
465 - toString = Object.prototype.toString,
466 - hasOwn = Object.prototype.hasOwnProperty,
467 - class2type = {
468 - "[object Boolean]": "boolean",
469 - "[object Number]": "number",
470 - "[object String]": "string",
471 - "[object Function]": "function",
472 - "[object Array]": "array",
473 - "[object Date]": "date",
474 - "[object RegExp]": "regexp",
475 - "[object Object]": "object"
476 - },
477 -
478 - jQuery = {
479 - isFunction: function (obj) {
480 - return jQuery.type(obj) === "function";
481 - },
482 - isArray: Array.isArray ||
483 - function (obj) {
484 - return jQuery.type(obj) === "array";
485 - },
486 - isWindow: function (obj) {
487 - return obj !== null && obj === obj.window;
488 - },
489 - isNumeric: function (obj) {
490 - return !isNaN(parseFloat(obj)) && isFinite(obj);
491 - },
492 - type: function (obj) {
493 - return obj === null ? String(obj) : class2type[toString.call(obj)] || "object";
494 - },
495 - isPlainObject: function (obj) {
496 - if (!obj || jQuery.type(obj) !== "object" || obj.nodeType) {
497 - return false;
498 - }
499 - try {
500 - if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
501 - return false;
502 - }
503 - } catch (e) {
504 - return false;
505 - }
506 - var key;
507 - for (key in obj) {}
508 - return key === undefined || hasOwn.call(obj, key);
509 - }
510 - };
511 - if (typeof target === "boolean") {
512 - deep = target;
513 - target = arguments[1] || {};
514 - i = 2;
515 - }
516 - if (typeof target !== "object" && !jQuery.isFunction(target)) {
517 - target = {};
518 - }
519 - if (length === i) {
520 - target = this;
521 - --i;
522 - }
523 - for (i; i < length; i++) {
524 - if ((options = arguments[i]) !== null) {
525 - for (name in options) {
526 - src = target[name];
527 - copy = options[name];
528 - if (target === copy) {
529 - continue;
530 - }
531 - if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
532 - if (copyIsArray) {
533 - copyIsArray = false;
534 - clone = src && jQuery.isArray(src) ? src : [];
535 - } else {
536 - clone = src && jQuery.isPlainObject(src) ? src : {};
537 - }
538 - // WARNING: RECURSION
539 - target[name] = extend(deep, clone, copy);
540 - } else if (copy !== undefined) {
541 - target[name] = copy;
542 - }
543 - }
544 - }
545 - }
546 - return target;
461 + var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
462 + i = 1,
463 + length = arguments.length,
464 + deep = false,
465 + toString = Object.prototype.toString,
466 + hasOwn = Object.prototype.hasOwnProperty,
467 + class2type = {
468 + "[object Boolean]": "boolean",
469 + "[object Number]": "number",
470 + "[object String]": "string",
471 + "[object Function]": "function",
472 + "[object Array]": "array",
473 + "[object Date]": "date",
474 + "[object RegExp]": "regexp",
475 + "[object Object]": "object"
476 + },
477 +
478 + jQuery = {
479 + isFunction: function (obj) {
480 + return jQuery.type(obj) === "function";
481 + },
482 + isArray: Array.isArray ||
483 + function (obj) {
484 + return jQuery.type(obj) === "array";
485 + },
486 + isWindow: function (obj) {
487 + return obj !== null && obj === obj.window;
488 + },
489 + isNumeric: function (obj) {
490 + return !isNaN(parseFloat(obj)) && isFinite(obj);
491 + },
492 + type: function (obj) {
493 + return obj === null ? String(obj) : class2type[toString.call(obj)] || "object";
494 + },
495 + isPlainObject: function (obj) {
496 + if (!obj || jQuery.type(obj) !== "object" || obj.nodeType) {
497 + return false;
498 + }
499 + try {
500 + if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
501 + return false;
502 + }
503 + } catch (e) {
504 + return false;
505 + }
506 + var key;
507 + for (key in obj) {}
508 + return key === undefined || hasOwn.call(obj, key);
509 + }
510 + };
511 + if (typeof target === "boolean") {
512 + deep = target;
513 + target = arguments[1] || {};
514 + i = 2;
515 + }
516 + if (typeof target !== "object" && !jQuery.isFunction(target)) {
517 + target = {};
518 + }
519 + if (length === i) {
520 + target = this;
521 + --i;
522 + }
523 + for (i; i < length; i++) {
524 + if ((options = arguments[i]) !== null) {
525 + for (name in options) {
526 + src = target[name];
527 + copy = options[name];
528 + if (target === copy) {
529 + continue;
530 + }
531 + if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
532 + if (copyIsArray) {
533 + copyIsArray = false;
534 + clone = src && jQuery.isArray(src) ? src : [];
535 + } else {
536 + clone = src && jQuery.isPlainObject(src) ? src : {};
537 + }
538 + // WARNING: RECURSION
539 + target[name] = extend(deep, clone, copy);
540 + } else if (copy !== undefined) {
541 + target[name] = copy;
542 + }
543 + }
544 + }
545 + }
546 + return target;
547 };
548 - //// --------- math.js -----------
548 + //// --------- math.js -----------
549 var math = {
550
551 - toRadians: function(degrees) {
552 - return degrees * (Math.PI / 180);
553 - },
554 -
555 - toDegrees: function(radians) {
556 - return radians * (180 / Math.PI);
557 - },
558 -
559 - computePieRadius: function(pie) {
560 - var size = pie.options.size;
561 - var canvasPadding = pie.options.misc.canvasPadding;
562 -
563 - // outer radius is either specified (e.g. through the generator), or omitted altogether
564 - // and calculated based on the canvas dimensions. Right now the estimated version isn't great - it should
565 - // be possible to calculate it to precisely generate the maximum sized pie, but it's fussy as heck. Something
566 - // for the next release.
567 -
568 - // first, calculate the default _outerRadius
569 - var w = size.canvasWidth - canvasPadding.left - canvasPadding.right;
570 - var h = size.canvasHeight - canvasPadding.top - canvasPadding.bottom;
571 -
572 - // now factor in the footer, title & subtitle
573 - if (pie.options.header.location !== "pie-center") {
574 - h -= pie.textComponents.headerHeight;
575 - }
576 -
577 - if (pie.textComponents.footer.exists) {
578 - h -= pie.textComponents.footer.h;
579 - }
580 -
581 - // for really teeny pies, h may be < 0. Adjust it back
582 - h = (h < 0) ? 0 : h;
583 -
584 - var outerRadius = ((w < h) ? w : h) / 3;
585 - var innerRadius, percent;
586 -
587 - // if the user specified something, use that instead
588 - if (size.pieOuterRadius !== null) {
589 - if (/%/.test(size.pieOuterRadius)) {
590 - percent = parseInt(size.pieOuterRadius.replace(/[\D]/, ""), 10);
591 - percent = (percent > 99) ? 99 : percent;
592 - percent = (percent < 0) ? 0 : percent;
593 -
594 - var smallestDimension = (w < h) ? w : h;
595 -
596 - // now factor in the label line size
597 - if (pie.options.labels.outer.format !== "none") {
598 - var pieDistanceSpace = parseInt(pie.options.labels.outer.pieDistance, 10) * 2;
599 - if (smallestDimension - pieDistanceSpace > 0) {
600 - smallestDimension -= pieDistanceSpace;
601 - }
602 - }
603 -
604 - outerRadius = Math.floor((smallestDimension / 100) * percent) / 2;
605 - } else {
606 - outerRadius = parseInt(size.pieOuterRadius, 10);
607 - }
608 - }
609 -
610 - // inner radius
611 - if (/%/.test(size.pieInnerRadius)) {
612 - percent = parseInt(size.pieInnerRadius.replace(/[\D]/, ""), 10);
613 - percent = (percent > 99) ? 99 : percent;
614 - percent = (percent < 0) ? 0 : percent;
615 - innerRadius = Math.floor((outerRadius / 100) * percent);
616 - } else {
617 - innerRadius = parseInt(size.pieInnerRadius, 10);
618 - }
619 -
620 - pie.innerRadius = innerRadius;
621 - pie.outerRadius = outerRadius;
622 - },
623 -
624 - getTotalPieSize: function(data) {
625 - var totalSize = 0;
626 - for (var i=0; i<data.length; i++) {
627 - totalSize += data[i].value;
628 - }
629 - return totalSize;
630 - },
631 -
632 - sortPieData: function(pie) {
633 - var data = pie.options.data.content;
634 - var sortOrder = pie.options.data.sortOrder;
635 -
636 - switch (sortOrder) {
637 - case "none":
638 - // do nothing
639 - break;
640 - case "random":
641 - data = helpers.shuffleArray(data);
642 - break;
643 - case "value-asc":
644 - data.sort(function(a, b) { return (a.value < b.value) ? -1 : 1; });
645 - break;
646 - case "value-desc":
647 - data.sort(function(a, b) { return (a.value < b.value) ? 1 : -1; });
648 - break;
649 - case "label-asc":
650 - data.sort(function(a, b) { return (a.label.toLowerCase() > b.label.toLowerCase()) ? 1 : -1; });
651 - break;
652 - case "label-desc":
653 - data.sort(function(a, b) { return (a.label.toLowerCase() < b.label.toLowerCase()) ? 1 : -1; });
654 - break;
655 - }
656 -
657 - return data;
658 - },
659 -
660 - // var pieCenter = math.getPieCenter();
661 - getPieTranslateCenter: function(pieCenter) {
662 - return "translate(" + pieCenter.x + "," + pieCenter.y + ")";
663 - },
664 -
665 - /**
666 - * Used to determine where on the canvas the center of the pie chart should be. It takes into account the
667 - * height and position of the title, subtitle and footer, and the various paddings.
668 - * @private
669 - */
670 - calculatePieCenter: function(pie) {
671 - var pieCenterOffset = pie.options.misc.pieCenterOffset;
672 - var hasTopTitle = (pie.textComponents.title.exists && pie.options.header.location !== "pie-center");
673 - var hasTopSubtitle = (pie.textComponents.subtitle.exists && pie.options.header.location !== "pie-center");
674 -
675 - var headerOffset = pie.options.misc.canvasPadding.top;
676 - if (hasTopTitle && hasTopSubtitle) {
677 - headerOffset += pie.textComponents.title.h + pie.options.header.titleSubtitlePadding + pie.textComponents.subtitle.h;
678 - } else if (hasTopTitle) {
679 - headerOffset += pie.textComponents.title.h;
680 - } else if (hasTopSubtitle) {
681 - headerOffset += pie.textComponents.subtitle.h;
682 - }
683 -
684 - var footerOffset = 0;
685 - if (pie.textComponents.footer.exists) {
686 - footerOffset = pie.textComponents.footer.h + pie.options.misc.canvasPadding.bottom;
687 - }
688 -
689 - var x = ((pie.options.size.canvasWidth - pie.options.misc.canvasPadding.left - pie.options.misc.canvasPadding.right) / 2) + pie.options.misc.canvasPadding.left;
690 - var y = ((pie.options.size.canvasHeight - footerOffset - headerOffset) / 2) + headerOffset;
691 -
692 - x += pieCenterOffset.x;
693 - y += pieCenterOffset.y;
694 -
695 - pie.pieCenter = { x: x, y: y };
696 - },
697 -
698 -
699 - /**
700 - * Rotates a point (x, y) around an axis (xm, ym) by degrees (a).
701 - * @param x
702 - * @param y
703 - * @param xm
704 - * @param ym
705 - * @param a angle in degrees
706 - * @returns {Array}
707 - */
708 - rotate: function(x, y, xm, ym, a) {
551 + toRadians: function(degrees) {
552 + return degrees * (Math.PI / 180);
553 + },
554 +
555 + toDegrees: function(radians) {
556 + return radians * (180 / Math.PI);
557 + },
558 +
559 + computePieRadius: function(pie) {
560 + var size = pie.options.size;
561 + var canvasPadding = pie.options.misc.canvasPadding;
562 +
563 + // outer radius is either specified (e.g. through the generator), or omitted altogether
564 + // and calculated based on the canvas dimensions. Right now the estimated version isn't great - it should
565 + // be possible to calculate it to precisely generate the maximum sized pie, but it's fussy as heck. Something
566 + // for the next release.
567 +
568 + // first, calculate the default _outerRadius
569 + var w = size.canvasWidth - canvasPadding.left - canvasPadding.right;
570 + var h = size.canvasHeight - canvasPadding.top - canvasPadding.bottom;
571 +
572 + // now factor in the footer, title & subtitle
573 + if (pie.options.header.location !== "pie-center") {
574 + h -= pie.textComponents.headerHeight;
575 + }
576 +
577 + if (pie.textComponents.footer.exists) {
578 + h -= pie.textComponents.footer.h;
579 + }
580 +
581 + // for really teeny pies, h may be < 0. Adjust it back
582 + h = (h < 0) ? 0 : h;
583 +
584 + var outerRadius = ((w < h) ? w : h) / 3;
585 + var innerRadius, percent;
586 +
587 + // if the user specified something, use that instead
588 + if (size.pieOuterRadius !== null) {
589 + if (/%/.test(size.pieOuterRadius)) {
590 + percent = parseInt(size.pieOuterRadius.replace(/[\D]/, ""), 10);
591 + percent = (percent > 99) ? 99 : percent;
592 + percent = (percent < 0) ? 0 : percent;
593 +
594 + var smallestDimension = (w < h) ? w : h;
595 +
596 + // now factor in the label line size
597 + if (pie.options.labels.outer.format !== "none") {
598 + var pieDistanceSpace = parseInt(pie.options.labels.outer.pieDistance, 10) * 2;
599 + if (smallestDimension - pieDistanceSpace > 0) {
600 + smallestDimension -= pieDistanceSpace;
601 + }
602 + }
603 +
604 + outerRadius = Math.floor((smallestDimension / 100) * percent) / 2;
605 + } else {
606 + outerRadius = parseInt(size.pieOuterRadius, 10);
607 + }
608 + }
609 +
610 + // inner radius
611 + if (/%/.test(size.pieInnerRadius)) {
612 + percent = parseInt(size.pieInnerRadius.replace(/[\D]/, ""), 10);
613 + percent = (percent > 99) ? 99 : percent;
614 + percent = (percent < 0) ? 0 : percent;
615 + innerRadius = Math.floor((outerRadius / 100) * percent);
616 + } else {
617 + innerRadius = parseInt(size.pieInnerRadius, 10);
618 + }
619 +
620 + pie.innerRadius = innerRadius;
621 + pie.outerRadius = outerRadius;
622 + },
623 +
624 + getTotalPieSize: function(data) {
625 + var totalSize = 0;
626 + for (var i=0; i<data.length; i++) {
627 + totalSize += data[i].value;
628 + }
629 + return totalSize;
630 + },
631 +
632 + sortPieData: function(pie) {
633 + var data = pie.options.data.content;
634 + var sortOrder = pie.options.data.sortOrder;
635 +
636 + switch (sortOrder) {
637 + case "none":
638 + // do nothing
639 + break;
640 + case "random":
641 + data = helpers.shuffleArray(data);
642 + break;
643 + case "value-asc":
644 + data.sort(function(a, b) { return (a.value < b.value) ? -1 : 1; });
645 + break;
646 + case "value-desc":
647 + data.sort(function(a, b) { return (a.value < b.value) ? 1 : -1; });
648 + break;
649 + case "label-asc":
650 + data.sort(function(a, b) { return (a.label.toLowerCase() > b.label.toLowerCase()) ? 1 : -1; });
651 + break;
652 + case "label-desc":
653 + data.sort(function(a, b) { return (a.label.toLowerCase() < b.label.toLowerCase()) ? 1 : -1; });
654 + break;
655 + }
656 +
657 + return data;
658 + },
659 +
660 + // var pieCenter = math.getPieCenter();
661 + getPieTranslateCenter: function(pieCenter) {
662 + return "translate(" + pieCenter.x + "," + pieCenter.y + ")";
663 + },
664 +
665 + /**
666 + * Used to determine where on the canvas the center of the pie chart should be. It takes into account the
667 + * height and position of the title, subtitle and footer, and the various paddings.
668 + * @private
669 + */
670 + calculatePieCenter: function(pie) {
671 + var pieCenterOffset = pie.options.misc.pieCenterOffset;
672 + var hasTopTitle = (pie.textComponents.title.exists && pie.options.header.location !== "pie-center");
673 + var hasTopSubtitle = (pie.textComponents.subtitle.exists && pie.options.header.location !== "pie-center");
674 +
675 + var headerOffset = pie.options.misc.canvasPadding.top;
676 + if (hasTopTitle && hasTopSubtitle) {
677 + headerOffset += pie.textComponents.title.h + pie.options.header.titleSubtitlePadding + pie.textComponents.subtitle.h;
678 + } else if (hasTopTitle) {
679 + headerOffset += pie.textComponents.title.h;
680 + } else if (hasTopSubtitle) {
681 + headerOffset += pie.textComponents.subtitle.h;
682 + }
683 +
684 + var footerOffset = 0;
685 + if (pie.textComponents.footer.exists) {
686 + footerOffset = pie.textComponents.footer.h + pie.options.misc.canvasPadding.bottom;
687 + }
688 +
689 + var x = ((pie.options.size.canvasWidth - pie.options.misc.canvasPadding.left - pie.options.misc.canvasPadding.right) / 2) + pie.options.misc.canvasPadding.left;
690 + var y = ((pie.options.size.canvasHeight - footerOffset - headerOffset) / 2) + headerOffset;
691 +
692 + x += pieCenterOffset.x;
693 + y += pieCenterOffset.y;
694 +
695 + pie.pieCenter = { x: x, y: y };
696 + },
697 +
698 +
699 + /**
700 + * Rotates a point (x, y) around an axis (xm, ym) by degrees (a).
701 + * @param x
702 + * @param y
703 + * @param xm
704 + * @param ym
705 + * @param a angle in degrees
706 + * @returns {Array}
707 + */
708 + rotate: function(x, y, xm, ym, a) {
709
710 a = a * Math.PI / 180; // convert to radians
711
712 var cos = Math.cos,
713 - sin = Math.sin,
714 - // subtract midpoints, so that midpoint is translated to origin and add it in the end again
715 - xr = (x - xm) * cos(a) - (y - ym) * sin(a) + xm,
716 - yr = (x - xm) * sin(a) + (y - ym) * cos(a) + ym;
717 -
718 - return { x: xr, y: yr };
719 - },
720 -
721 - /**
722 - * Translates a point x, y by distance d, and by angle a.
723 - * @param x
724 - * @param y
725 - * @param dist
726 - * @param a angle in degrees
727 - */
728 - translate: function(x, y, d, a) {
729 - var rads = math.toRadians(a);
730 - return {
731 - x: x + d * Math.sin(rads),
732 - y: y - d * Math.cos(rads)
733 - };
734 - },
735 -
736 - // from: http://stackoverflow.com/questions/19792552/d3-put-arc-labels-in-a-pie-chart-if-there-is-enough-space
737 - pointIsInArc: function(pt, ptData, d3Arc) {
738 - // Center of the arc is assumed to be 0,0
739 - // (pt.x, pt.y) are assumed to be relative to the center
740 - var r1 = d3Arc.innerRadius()(ptData), // Note: Using the innerRadius
741 - r2 = d3Arc.outerRadius()(ptData),
742 - theta1 = d3Arc.startAngle()(ptData),
743 - theta2 = d3Arc.endAngle()(ptData);
744 -
745 - var dist = pt.x * pt.x + pt.y * pt.y,
746 - angle = Math.atan2(pt.x, -pt.y); // Note: different coordinate system
747 -
748 - angle = (angle < 0) ? (angle + Math.PI * 2) : angle;
749 -
750 - return (r1 * r1 <= dist) && (dist <= r2 * r2) &&
751 - (theta1 <= angle) && (angle <= theta2);
752 - }
713 + sin = Math.sin,
714 + // subtract midpoints, so that midpoint is translated to origin and add it in the end again
715 + xr = (x - xm) * cos(a) - (y - ym) * sin(a) + xm,
716 + yr = (x - xm) * sin(a) + (y - ym) * cos(a) + ym;
717 +
718 + return { x: xr, y: yr };
719 + },
720 +
721 + /**
722 + * Translates a point x, y by distance d, and by angle a.
723 + * @param x
724 + * @param y
725 + * @param dist
726 + * @param a angle in degrees
727 + */
728 + translate: function(x, y, d, a) {
729 + var rads = math.toRadians(a);
730 + return {
731 + x: x + d * Math.sin(rads),
732 + y: y - d * Math.cos(rads)
733 + };
734 + },
735 +
736 + // from: http://stackoverflow.com/questions/19792552/d3-put-arc-labels-in-a-pie-chart-if-there-is-enough-space
737 + pointIsInArc: function(pt, ptData, d3Arc) {
738 + // Center of the arc is assumed to be 0,0
739 + // (pt.x, pt.y) are assumed to be relative to the center
740 + var r1 = d3Arc.innerRadius()(ptData), // Note: Using the innerRadius
741 + r2 = d3Arc.outerRadius()(ptData),
742 + theta1 = d3Arc.startAngle()(ptData),
743 + theta2 = d3Arc.endAngle()(ptData);
744 +
745 + var dist = pt.x * pt.x + pt.y * pt.y,
746 + angle = Math.atan2(pt.x, -pt.y); // Note: different coordinate system
747 +
748 + angle = (angle < 0) ? (angle + Math.PI * 2) : angle;
749 +
750 + return (r1 * r1 <= dist) && (dist <= r2 * r2) &&
751 + (theta1 <= angle) && (angle <= theta2);
752 + }
753 };
754
755 - //// --------- labels.js -----------
755 + //// --------- labels.js -----------
756 var labels = {
757
758 - /**
759 - * Adds the labels to the pie chart, but doesn't position them. There are two locations for the
760 - * labels: inside (center) of the segments, or outside the segments on the edge.
761 - * @param section "inner" or "outer"
762 - * @param sectionDisplayType "percentage", "value", "label", "label-value1", etc.
763 - * @param pie
764 - */
765 - add: function(pie, section, sectionDisplayType) {
766 - var include = labels.getIncludes(sectionDisplayType);
767 - var settings = pie.options.labels;
768 -
769 - // group the label groups (label, percentage, value) into a single element for simpler positioning
770 - var outerLabel = pie.svg.insert("g", "." + pie.cssPrefix + "labels-" + section)
771 - .attr("class", pie.cssPrefix + "labels-" + section);
758 + /**
759 + * Adds the labels to the pie chart, but doesn't position them. There are two locations for the
760 + * labels: inside (center) of the segments, or outside the segments on the edge.
761 + * @param section "inner" or "outer"
762 + * @param sectionDisplayType "percentage", "value", "label", "label-value1", etc.
763 + * @param pie
764 + */
765 + add: function(pie, section, sectionDisplayType) {
766 + var include = labels.getIncludes(sectionDisplayType);
767 + var settings = pie.options.labels;
768 +
769 + // group the label groups (label, percentage, value) into a single element for simpler positioning
770 + var outerLabel = pie.svg.insert("g", "." + pie.cssPrefix + "labels-" + section)
771 + .attr("class", pie.cssPrefix + "labels-" + section);
772
773 var labelGroup = pie.__labels[section] = outerLabel.selectAll("." + pie.cssPrefix + "labelGroup-" + section)
774 - .data(pie.options.data.content)
775 - .enter()
776 - .append("g")
777 - .attr("id", function(d, i) { return pie.cssPrefix + "labelGroup" + i + "-" + section; })
778 - .attr("data-index", function(d, i) { return i; })
779 - .attr("class", pie.cssPrefix + "labelGroup-" + section)
780 - .style("opacity", 0);
781 -
782 - var formatterContext = { section: section, sectionDisplayType: sectionDisplayType };
783 -
784 - // 1. Add the main label
785 - if (include.mainLabel) {
786 - labelGroup.append("text")
787 - .attr("id", function(d, i) { return pie.cssPrefix + "segmentMainLabel" + i + "-" + section; })
788 - .attr("class", pie.cssPrefix + "segmentMainLabel-" + section)
789 - .text(function(d, i) {
790 - var str = d.label;
791 -
792 - // if a custom formatter has been defined, pass it the raw label string - it can do whatever it wants with it.
793 - // we only apply truncation if it's not defined
794 - if (settings.formatter) {
795 - formatterContext.index = i;
796 - formatterContext.part = 'mainLabel';
797 - formatterContext.value = d.value;
798 - formatterContext.label = str;
799 - str = settings.formatter(formatterContext);
800 - } else if (settings.truncation.enabled && d.label.length > settings.truncation.truncateLength) {
801 - str = d.label.substring(0, settings.truncation.truncateLength) + "...";
802 - }
803 - return str;
804 - })
805 - .style("font-size", settings.mainLabel.fontSize + "px")
806 - .style("font-family", settings.mainLabel.font)
807 - .style("font-weight", settings.mainLabel.fontWeight)
808 - .style("fill", function(d, i) {
809 - return (settings.mainLabel.color === "segment") ? pie.options.colors[i] : settings.mainLabel.color;
810 - });
811 - }
812 -
813 - // 2. Add the percentage label
814 - if (include.percentage) {
815 - labelGroup.append("text")
816 - .attr("id", function(d, i) { return pie.cssPrefix + "segmentPercentage" + i + "-" + section; })
817 - .attr("class", pie.cssPrefix + "segmentPercentage-" + section)
818 - .text(function(d, i) {
819 - var percentage = d.percentage;
820 - if (settings.formatter) {
821 - formatterContext.index = i;
822 - formatterContext.part = "percentage";
823 - formatterContext.value = d.value;
824 - formatterContext.label = d.percentage;
825 - percentage = settings.formatter(formatterContext);
826 - } else {
827 - percentage += "%";
828 - }
829 - return percentage;
830 - })
831 - .style("font-size", settings.percentage.fontSize + "px")
832 - .style("font-family", settings.percentage.font)
833 - .style("font-weight", settings.percentage.fontWeight)
834 - .style("fill", settings.percentage.color);
835 - }
836 -
837 - // 3. Add the value label
838 - if (include.value) {
839 - labelGroup.append("text")
840 - .attr("id", function(d, i) { return pie.cssPrefix + "segmentValue" + i + "-" + section; })
841 - .attr("class", pie.cssPrefix + "segmentValue-" + section)
842 - .text(function(d, i) {
843 - formatterContext.index = i;
844 - formatterContext.part = "value";
845 - formatterContext.value = d.value;
846 - formatterContext.label = d.value;
847 - return settings.formatter ? settings.formatter(formatterContext, d.value) : d.value;
848 - })
849 - .style("font-size", settings.value.fontSize + "px")
850 - .style("font-family", settings.value.font)
851 - .style("font-weight", settings.value.fontWeight)
852 - .style("fill", settings.value.color);
853 - }
854 - },
855 -
856 - /**
857 - * @param section "inner" / "outer"
858 - */
859 - positionLabelElements: function(pie, section, sectionDisplayType) {
860 - labels["dimensions-" + section] = [];
861 -
862 - // get the latest widths, heights
863 - var labelGroups = pie.__labels[section];
864 - labelGroups.each(function(d, i) {
865 - var mainLabel = d3.select(this).selectAll("." + pie.cssPrefix + "segmentMainLabel-" + section);
866 - var percentage = d3.select(this).selectAll("." + pie.cssPrefix + "segmentPercentage-" + section);
867 - var value = d3.select(this).selectAll("." + pie.cssPrefix + "segmentValue-" + section);
868 -
869 - labels["dimensions-" + section].push({
870 - mainLabel: (mainLabel.node() !== null) ? mainLabel.node().getBBox() : null,
871 - percentage: (percentage.node() !== null) ? percentage.node().getBBox() : null,
872 - value: (value.node() !== null) ? value.node().getBBox() : null
873 - });
874 - });
875 -
876 - var singleLinePad = 5;
877 - var dims = labels["dimensions-" + section];
878 - switch (sectionDisplayType) {
879 - case "label-value1":
880 - d3.selectAll("." + pie.cssPrefix + "segmentValue-" + section)
881 - .attr("dx", function(d, i) { return dims[i].mainLabel.width + singleLinePad; });
882 - break;
883 - case "label-value2":
884 - d3.selectAll("." + pie.cssPrefix + "segmentValue-" + section)
885 - .attr("dy", function(d, i) { return dims[i].mainLabel.height; });
886 - break;
887 - case "label-percentage1":
888 - d3.selectAll("." + pie.cssPrefix + "segmentPercentage-" + section)
889 - .attr("dx", function(d, i) { return dims[i].mainLabel.width + singleLinePad; });
890 - break;
891 - case "label-percentage2":
892 - d3.selectAll("." + pie.cssPrefix + "segmentPercentage-" + section)
893 - .attr("dx", function(d, i) { return (dims[i].mainLabel.width / 2) - (dims[i].percentage.width / 2); })
894 - .attr("dy", function(d, i) { return dims[i].mainLabel.height; });
895 - break;
896 - }
897 - },
898 -
899 - computeLabelLinePositions: function(pie) {
900 - pie.lineCoordGroups = [];
774 + .data(pie.options.data.content)
775 + .enter()
776 + .append("g")
777 + .attr("id", function(d, i) { return pie.cssPrefix + "labelGroup" + i + "-" + section; })
778 + .attr("data-index", function(d, i) { return i; })
779 + .attr("class", pie.cssPrefix + "labelGroup-" + section)
780 + .style("opacity", 0);
781 +
782 + var formatterContext = { section: section, sectionDisplayType: sectionDisplayType };
783 +
784 + // 1. Add the main label
785 + if (include.mainLabel) {
786 + labelGroup.append("text")
787 + .attr("id", function(d, i) { return pie.cssPrefix + "segmentMainLabel" + i + "-" + section; })
788 + .attr("class", pie.cssPrefix + "segmentMainLabel-" + section)
789 + .text(function(d, i) {
790 + var str = d.label;
791 +
792 + // if a custom formatter has been defined, pass it the raw label string - it can do whatever it wants with it.
793 + // we only apply truncation if it's not defined
794 + if (settings.formatter) {
795 + formatterContext.index = i;
796 + formatterContext.part = 'mainLabel';
797 + formatterContext.value = d.value;
798 + formatterContext.label = str;
799 + str = settings.formatter(formatterContext);
800 + } else if (settings.truncation.enabled && d.label.length > settings.truncation.truncateLength) {
801 + str = d.label.substring(0, settings.truncation.truncateLength) + "...";
802 + }
803 + return str;
804 + })
805 + .style("font-size", settings.mainLabel.fontSize + "px")
806 + .style("font-family", settings.mainLabel.font)
807 + .style("font-weight", settings.mainLabel.fontWeight)
808 + .style("fill", function(d, i) {
809 + return (settings.mainLabel.color === "segment") ? pie.options.colors[i] : settings.mainLabel.color;
810 + });
811 + }
812 +
813 + // 2. Add the percentage label
814 + if (include.percentage) {
815 + labelGroup.append("text")
816 + .attr("id", function(d, i) { return pie.cssPrefix + "segmentPercentage" + i + "-" + section; })
817 + .attr("class", pie.cssPrefix + "segmentPercentage-" + section)
818 + .text(function(d, i) {
819 + var percentage = d.percentage;
820 + if (settings.formatter) {
821 + formatterContext.index = i;
822 + formatterContext.part = "percentage";
823 + formatterContext.value = d.value;
824 + formatterContext.label = d.percentage;
825 + percentage = settings.formatter(formatterContext);
826 + } else {
827 + percentage += "%";
828 + }
829 + return percentage;
830 + })
831 + .style("font-size", settings.percentage.fontSize + "px")
832 + .style("font-family", settings.percentage.font)
833 + .style("font-weight", settings.percentage.fontWeight)
834 + .style("fill", settings.percentage.color);
835 + }
836 +
837 + // 3. Add the value label
838 + if (include.value) {
839 + labelGroup.append("text")
840 + .attr("id", function(d, i) { return pie.cssPrefix + "segmentValue" + i + "-" + section; })
841 + .attr("class", pie.cssPrefix + "segmentValue-" + section)
842 + .text(function(d, i) {
843 + formatterContext.index = i;
844 + formatterContext.part = "value";
845 + formatterContext.value = d.value;
846 + formatterContext.label = d.value;
847 + return settings.formatter ? settings.formatter(formatterContext, d.value) : d.value;
848 + })
849 + .style("font-size", settings.value.fontSize + "px")
850 + .style("font-family", settings.value.font)
851 + .style("font-weight", settings.value.fontWeight)
852 + .style("fill", settings.value.color);
853 + }
854 + },
855 +
856 + /**
857 + * @param section "inner" / "outer"
858 + */
859 + positionLabelElements: function(pie, section, sectionDisplayType) {
860 + labels["dimensions-" + section] = [];
861 +
862 + // get the latest widths, heights
863 + var labelGroups = pie.__labels[section];
864 + labelGroups.each(function(d, i) {
865 + var mainLabel = d3.select(this).selectAll("." + pie.cssPrefix + "segmentMainLabel-" + section);
866 + var percentage = d3.select(this).selectAll("." + pie.cssPrefix + "segmentPercentage-" + section);
867 + var value = d3.select(this).selectAll("." + pie.cssPrefix + "segmentValue-" + section);
868 +
869 + labels["dimensions-" + section].push({
870 + mainLabel: (mainLabel.node() !== null) ? mainLabel.node().getBBox() : null,
871 + percentage: (percentage.node() !== null) ? percentage.node().getBBox() : null,
872 + value: (value.node() !== null) ? value.node().getBBox() : null
873 + });
874 + });
875 +
876 + var singleLinePad = 5;
877 + var dims = labels["dimensions-" + section];
878 + switch (sectionDisplayType) {
879 + case "label-value1":
880 + pie.svg.selectAll("." + pie.cssPrefix + "segmentValue-" + section)
881 + .attr("dx", function(d, i) { return dims[i].mainLabel.width + singleLinePad; });
882 + break;
883 + case "label-value2":
884 + pie.svg.selectAll("." + pie.cssPrefix + "segmentValue-" + section)
885 + .attr("dy", function(d, i) { return dims[i].mainLabel.height; });
886 + break;
887 + case "label-percentage1":
888 + pie.svg.selectAll("." + pie.cssPrefix + "segmentPercentage-" + section)
889 + .attr("dx", function(d, i) { return dims[i].mainLabel.width + singleLinePad; });
890 + break;
891 + case "label-percentage2":
892 + pie.svg.selectAll("." + pie.cssPrefix + "segmentPercentage-" + section)
893 + .attr("dx", function(d, i) { return (dims[i].mainLabel.width / 2) - (dims[i].percentage.width / 2); })
894 + .attr("dy", function(d, i) { return dims[i].mainLabel.height; });
895 + break;
896 + }
897 + },
898 +
899 + computeLabelLinePositions: function(pie) {
900 + pie.lineCoordGroups = [];
901 pie.__labels.outer
902 - .each(function(d, i) { return labels.computeLinePosition(pie, i); });
903 - },
904 -
905 - computeLinePosition: function(pie, i) {
906 - var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
907 - var originCoords = math.rotate(pie.pieCenter.x, pie.pieCenter.y - pie.outerRadius, pie.pieCenter.x, pie.pieCenter.y, angle);
908 - var heightOffset = pie.outerLabelGroupData[i].h / 5; // TODO check
909 - var labelXMargin = 6; // the x-distance of the label from the end of the line [TODO configurable]
910 -
911 - var quarter = Math.floor(angle / 90);
912 - var midPoint = 4;
913 - var x2, y2, x3, y3;
914 -
915 - // this resolves an issue when the
916 - if (quarter === 2 && angle === 180) {
917 - quarter = 1;
918 - }
919 -
920 - switch (quarter) {
921 - case 0:
922 - x2 = pie.outerLabelGroupData[i].x - labelXMargin - ((pie.outerLabelGroupData[i].x - labelXMargin - originCoords.x) / 2);
923 - y2 = pie.outerLabelGroupData[i].y + ((originCoords.y - pie.outerLabelGroupData[i].y) / midPoint);
924 - x3 = pie.outerLabelGroupData[i].x - labelXMargin;
925 - y3 = pie.outerLabelGroupData[i].y - heightOffset;
926 - break;
927 - case 1:
928 - x2 = originCoords.x + (pie.outerLabelGroupData[i].x - originCoords.x) / midPoint;
929 - y2 = originCoords.y + (pie.outerLabelGroupData[i].y - originCoords.y) / midPoint;
930 - x3 = pie.outerLabelGroupData[i].x - labelXMargin;
931 - y3 = pie.outerLabelGroupData[i].y - heightOffset;
932 - break;
933 - case 2:
934 - var startOfLabelX = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
935 - x2 = originCoords.x - (originCoords.x - startOfLabelX) / midPoint;
936 - y2 = originCoords.y + (pie.outerLabelGroupData[i].y - originCoords.y) / midPoint;
937 - x3 = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
938 - y3 = pie.outerLabelGroupData[i].y - heightOffset;
939 - break;
940 - case 3:
941 - var startOfLabel = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
942 - x2 = startOfLabel + ((originCoords.x - startOfLabel) / midPoint);
943 - y2 = pie.outerLabelGroupData[i].y + (originCoords.y - pie.outerLabelGroupData[i].y) / midPoint;
944 - x3 = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
945 - y3 = pie.outerLabelGroupData[i].y - heightOffset;
946 - break;
947 - }
948 -
949 - /*
950 - * x1 / y1: the x/y coords of the start of the line, at the mid point of the segments arc on the pie circumference
951 - * x2 / y2: if "curved" line style is being used, this is the midpoint of the line. Other
952 - * x3 / y3: the end of the line; closest point to the label
953 - */
954 - if (pie.options.labels.lines.style === "straight") {
955 - pie.lineCoordGroups[i] = [
956 - { x: originCoords.x, y: originCoords.y },
957 - { x: x3, y: y3 }
958 - ];
959 - } else {
960 - pie.lineCoordGroups[i] = [
961 - { x: originCoords.x, y: originCoords.y },
962 - { x: x2, y: y2 },
963 - { x: x3, y: y3 }
964 - ];
965 - }
966 - },
967 -
968 - addLabelLines: function(pie) {
969 - var lineGroups = pie.svg.insert("g", "." + pie.cssPrefix + "pieChart") // meaning, BEFORE .pieChart
970 - .attr("class", pie.cssPrefix + "lineGroups")
902 + .each(function(d, i) { return labels.computeLinePosition(pie, i); });
903 + },
904 +
905 + computeLinePosition: function(pie, i) {
906 + var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
907 + var originCoords = math.rotate(pie.pieCenter.x, pie.pieCenter.y - pie.outerRadius, pie.pieCenter.x, pie.pieCenter.y, angle);
908 + var heightOffset = pie.outerLabelGroupData[i].h / 5; // TODO check
909 + var labelXMargin = 6; // the x-distance of the label from the end of the line [TODO configurable]
910 +
911 + var quarter = Math.floor(angle / 90);
912 + var midPoint = 4;
913 + var x2, y2, x3, y3;
914 +
915 + // this resolves an issue when the
916 + if (quarter === 2 && angle === 180) {
917 + quarter = 1;
918 + }
919 +
920 + switch (quarter) {
921 + case 0:
922 + x2 = pie.outerLabelGroupData[i].x - labelXMargin - ((pie.outerLabelGroupData[i].x - labelXMargin - originCoords.x) / 2);
923 + y2 = pie.outerLabelGroupData[i].y + ((originCoords.y - pie.outerLabelGroupData[i].y) / midPoint);
924 + x3 = pie.outerLabelGroupData[i].x - labelXMargin;
925 + y3 = pie.outerLabelGroupData[i].y - heightOffset;
926 + break;
927 + case 1:
928 + x2 = originCoords.x + (pie.outerLabelGroupData[i].x - originCoords.x) / midPoint;
929 + y2 = originCoords.y + (pie.outerLabelGroupData[i].y - originCoords.y) / midPoint;
930 + x3 = pie.outerLabelGroupData[i].x - labelXMargin;
931 + y3 = pie.outerLabelGroupData[i].y - heightOffset;
932 + break;
933 + case 2:
934 + var startOfLabelX = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
935 + x2 = originCoords.x - (originCoords.x - startOfLabelX) / midPoint;
936 + y2 = originCoords.y + (pie.outerLabelGroupData[i].y - originCoords.y) / midPoint;
937 + x3 = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
938 + y3 = pie.outerLabelGroupData[i].y - heightOffset;
939 + break;
940 + case 3:
941 + var startOfLabel = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
942 + x2 = startOfLabel + ((originCoords.x - startOfLabel) / midPoint);
943 + y2 = pie.outerLabelGroupData[i].y + (originCoords.y - pie.outerLabelGroupData[i].y) / midPoint;
944 + x3 = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
945 + y3 = pie.outerLabelGroupData[i].y - heightOffset;
946 + break;
947 + }
948 +
949 + /*
950 + * x1 / y1: the x/y coords of the start of the line, at the mid point of the segments arc on the pie circumference
951 + * x2 / y2: if "curved" line style is being used, this is the midpoint of the line. Other
952 + * x3 / y3: the end of the line; closest point to the label
953 + */
954 + if (pie.options.labels.lines.style === "straight") {
955 + pie.lineCoordGroups[i] = [
956 + { x: originCoords.x, y: originCoords.y },
957 + { x: x3, y: y3 }
958 + ];
959 + } else {
960 + pie.lineCoordGroups[i] = [
961 + { x: originCoords.x, y: originCoords.y },
962 + { x: x2, y: y2 },
963 + { x: x3, y: y3 }
964 + ];
965 + }
966 + },
967 +
968 + addLabelLines: function(pie) {
969 + var lineGroups = pie.svg.insert("g", "." + pie.cssPrefix + "pieChart") // meaning, BEFORE .pieChart
970 + .attr("class", pie.cssPrefix + "lineGroups")
971 .style("opacity", 1);
972
973 - var lineGroup = lineGroups.selectAll("." + pie.cssPrefix + "lineGroup")
974 - .data(pie.lineCoordGroups)
975 - .enter()
976 - .append("g")
977 - .attr("class", pie.cssPrefix + "lineGroup");
973 + var lineGroup = lineGroups.selectAll("." + pie.cssPrefix + "lineGroup")
974 + .data(pie.lineCoordGroups)
975 + .enter()
976 + .append("g")
977 + .attr("class", pie.cssPrefix + "lineGroup");
978
979 var lineFunction = d3.line()
980 - .curve(d3.curveBasis)
981 - .x(function(d) { return d.x; })
982 - .y(function(d) { return d.y; });
983 -
984 - lineGroup.append("path")
985 - .attr("d", lineFunction)
986 - .attr("stroke", function(d, i) {
987 - return (pie.options.labels.lines.color === "segment") ? pie.options.colors[i] : pie.options.labels.lines.color;
988 - })
989 - .attr("stroke-width", 1)
990 - .attr("fill", "none")
991 - .style("opacity", function(d, i) {
992 - var percentage = pie.options.labels.outer.hideWhenLessThanPercentage;
993 - var isHidden = (percentage !== null && d.percentage < percentage) || pie.options.data.content[i].label === "";
994 - return isHidden ? 0 : 1;
995 - });
996 - },
997 -
998 - positionLabelGroups: function(pie, section) {
999 - if (pie.options.labels[section].format === "none")
1000 - return;
980 + .curve(d3.curveBasis)
981 + .x(function(d) { return d.x; })
982 + .y(function(d) { return d.y; });
983 +
984 + lineGroup.append("path")
985 + .attr("d", lineFunction)
986 + .attr("stroke", function(d, i) {
987 + return (pie.options.labels.lines.color === "segment") ? pie.options.colors[i] : pie.options.labels.lines.color;
988 + })
989 + .attr("stroke-width", 1)
990 + .attr("fill", "none")
991 + .style("opacity", function(d, i) {
992 + var percentage = pie.options.labels.outer.hideWhenLessThanPercentage;
993 + var isHidden = (percentage !== null && d.percentage < percentage) || pie.options.data.content[i].label === "";
994 + return isHidden ? 0 : 1;
995 + });
996 + },
997 +
998 + positionLabelGroups: function(pie, section) {
999 + if (pie.options.labels[section].format === "none")
1000 + return;
1001
1002 pie.__labels[section]
1003 .style("opacity", function(d, i) {
1004 var percentage = pie.options.labels[section].hideWhenLessThanPercentage;
1005 return (percentage !== null && d.percentage < percentage) ? 0 : 1;
1006 })
1007 - .attr("transform", function(d, i) {
1008 - var x, y;
1009 - if (section === "outer") {
1010 - x = pie.outerLabelGroupData[i].x;
1011 - y = pie.outerLabelGroupData[i].y;
1012 - } else {
1013 - var pieCenterCopy = extend(true, {}, pie.pieCenter);
1014 -
1015 - // now recompute the "center" based on the current _innerRadius
1016 - if (pie.innerRadius > 0) {
1017 - var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
1018 - var newCoords = math.translate(pie.pieCenter.x, pie.pieCenter.y, pie.innerRadius, angle);
1019 - pieCenterCopy.x = newCoords.x;
1020 - pieCenterCopy.y = newCoords.y;
1021 - }
1022 -
1023 - var dims = helpers.getDimensions(pie.cssPrefix + "labelGroup" + i + "-inner");
1024 - var xOffset = dims.w / 2;
1025 - var yOffset = dims.h / 4; // confusing! Why 4? should be 2, but it doesn't look right
1026 -
1027 - x = pieCenterCopy.x + (pie.lineCoordGroups[i][0].x - pieCenterCopy.x) / 1.8;
1028 - y = pieCenterCopy.y + (pie.lineCoordGroups[i][0].y - pieCenterCopy.y) / 1.8;
1029 -
1030 - x = x - xOffset;
1031 - y = y + yOffset;
1032 - }
1033 -
1034 - return "translate(" + x + "," + y + ")";
1035 - });
1036 - },
1037 -
1038 -
1039 - getIncludes: function(val) {
1040 - var addMainLabel = false;
1041 - var addValue = false;
1042 - var addPercentage = false;
1043 -
1044 - switch (val) {
1045 - case "label":
1046 - addMainLabel = true;
1047 - break;
1048 - case "value":
1049 - addValue = true;
1050 - break;
1051 - case "percentage":
1052 - addPercentage = true;
1053 - break;
1054 - case "label-value1":
1055 - case "label-value2":
1056 - addMainLabel = true;
1057 - addValue = true;
1058 - break;
1059 - case "label-percentage1":
1060 - case "label-percentage2":
1061 - addMainLabel = true;
1062 - addPercentage = true;
1063 - break;
1064 - }
1065 - return {
1066 - mainLabel: addMainLabel,
1067 - value: addValue,
1068 - percentage: addPercentage
1069 - };
1070 - },
1071 -
1072 -
1073 - /**
1074 - * This does the heavy-lifting to compute the actual coordinates for the outer label groups. It does two things:
1075 - * 1. Make a first pass and position them in the ideal positions, based on the pie sizes
1076 - * 2. Do some basic collision avoidance.
1077 - */
1078 - computeOuterLabelCoords: function(pie) {
1079 -
1080 - // 1. figure out the ideal positions for the outer labels
1007 + .attr("transform", function(d, i) {
1008 + var x, y;
1009 + if (section === "outer") {
1010 + x = pie.outerLabelGroupData[i].x;
1011 + y = pie.outerLabelGroupData[i].y;
1012 + } else {
1013 + var pieCenterCopy = extend(true, {}, pie.pieCenter);
1014 +
1015 + // now recompute the "center" based on the current _innerRadius
1016 + if (pie.innerRadius > 0) {
1017 + var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
1018 + var newCoords = math.translate(pie.pieCenter.x, pie.pieCenter.y, pie.innerRadius, angle);
1019 + pieCenterCopy.x = newCoords.x;
1020 + pieCenterCopy.y = newCoords.y;
1021 + }
1022 +
1023 + var dims = helpers.getDimensions(pie.cssPrefix + "labelGroup" + i + "-inner");
1024 + var xOffset = dims.w / 2;
1025 + var yOffset = dims.h / 4; // confusing! Why 4? should be 2, but it doesn't look right
1026 +
1027 + x = pieCenterCopy.x + (pie.lineCoordGroups[i][0].x - pieCenterCopy.x) / 1.8;
1028 + y = pieCenterCopy.y + (pie.lineCoordGroups[i][0].y - pieCenterCopy.y) / 1.8;
1029 +
1030 + x = x - xOffset;
1031 + y = y + yOffset;
1032 + }
1033 +
1034 + return "translate(" + x + "," + y + ")";
1035 + });
1036 + },
1037 +
1038 +
1039 + getIncludes: function(val) {
1040 + var addMainLabel = false;
1041 + var addValue = false;
1042 + var addPercentage = false;
1043 +
1044 + switch (val) {
1045 + case "label":
1046 + addMainLabel = true;
1047 + break;
1048 + case "value":
1049 + addValue = true;
1050 + break;
1051 + case "percentage":
1052 + addPercentage = true;
1053 + break;
1054 + case "label-value1":
1055 + case "label-value2":
1056 + addMainLabel = true;
1057 + addValue = true;
1058 + break;
1059 + case "label-percentage1":
1060 + case "label-percentage2":
1061 + addMainLabel = true;
1062 + addPercentage = true;
1063 + break;
1064 + }
1065 + return {
1066 + mainLabel: addMainLabel,
1067 + value: addValue,
1068 + percentage: addPercentage
1069 + };
1070 + },
1071 +
1072 +
1073 + /**
1074 + * This does the heavy-lifting to compute the actual coordinates for the outer label groups. It does two things:
1075 + * 1. Make a first pass and position them in the ideal positions, based on the pie sizes
1076 + * 2. Do some basic collision avoidance.
1077 + */
1078 + computeOuterLabelCoords: function(pie) {
1079 +
1080 + // 1. figure out the ideal positions for the outer labels
1081 pie.__labels.outer
1082 - .each(function(d, i) {
1083 - return labels.getIdealOuterLabelPositions(pie, i);
1084 - });
1085 -
1086 - // 2. now adjust those positions to try to accommodate conflicts
1087 - labels.resolveOuterLabelCollisions(pie);
1088 - },
1089 -
1090 - /**
1091 - * This attempts to resolve label positioning collisions.
1092 - */
1093 - resolveOuterLabelCollisions: function(pie) {
1082 + .each(function(d, i) {
1083 + return labels.getIdealOuterLabelPositions(pie, i);
1084 + });
1085 +
1086 + // 2. now adjust those positions to try to accommodate conflicts
1087 + labels.resolveOuterLabelCollisions(pie);
1088 + },
1089 +
1090 + /**
1091 + * This attempts to resolve label positioning collisions.
1092 + */
1093 + resolveOuterLabelCollisions: function(pie) {
1094 if (pie.options.labels.outer.format === "none") {
1095 return;
1096 }
1097
1098 - var size = pie.options.data.content.length;
1099 - labels.checkConflict(pie, 0, "clockwise", size);
1100 - labels.checkConflict(pie, size-1, "anticlockwise", size);
1101 - },
1098 + var size = pie.options.data.content.length;
1099 + labels.checkConflict(pie, 0, "clockwise", size);
1100 + labels.checkConflict(pie, size-1, "anticlockwise", size);
1101 + },
1102
1103 - checkConflict: function(pie, currIndex, direction, size) {
1103 + checkConflict: function(pie, currIndex, direction, size) {
1104 var i, curr;
1105
1106 - if (size <= 1) {
1107 - return;
1108 - }
1109 -
1110 - var currIndexHemisphere = pie.outerLabelGroupData[currIndex].hs;
1111 - if (direction === "clockwise" && currIndexHemisphere !== "right") {
1112 - return;
1113 - }
1114 - if (direction === "anticlockwise" && currIndexHemisphere !== "left") {
1115 - return;
1116 - }
1117 - var nextIndex = (direction === "clockwise") ? currIndex+1 : currIndex-1;
1118 -
1119 - // this is the current label group being looked at. We KNOW it's positioned properly (the first item
1120 - // is always correct)
1121 - var currLabelGroup = pie.outerLabelGroupData[currIndex];
1122 -
1123 - // this one we don't know about. That's the one we're going to look at and move if necessary
1124 - var examinedLabelGroup = pie.outerLabelGroupData[nextIndex];
1125 -
1126 - var info = {
1127 - labelHeights: pie.outerLabelGroupData[0].h,
1128 - center: pie.pieCenter,
1129 - lineLength: (pie.outerRadius + pie.options.labels.outer.pieDistance),
1130 - heightChange: pie.outerLabelGroupData[0].h + 1 // 1 = padding
1131 - };
1132 -
1133 - // loop through *ALL* label groups examined so far to check for conflicts. This is because when they're
1134 - // very tightly fitted, a later label group may still appear high up on the page
1135 - if (direction === "clockwise") {
1106 + if (size <= 1) {
1107 + return;
1108 + }
1109 +
1110 + var currIndexHemisphere = pie.outerLabelGroupData[currIndex].hs;
1111 + if (direction === "clockwise" && currIndexHemisphere !== "right") {
1112 + return;
1113 + }
1114 + if (direction === "anticlockwise" && currIndexHemisphere !== "left") {
1115 + return;
1116 + }
1117 + var nextIndex = (direction === "clockwise") ? currIndex+1 : currIndex-1;
1118 +
1119 + // this is the current label group being looked at. We KNOW it's positioned properly (the first item
1120 + // is always correct)
1121 + var currLabelGroup = pie.outerLabelGroupData[currIndex];
1122 +
1123 + // this one we don't know about. That's the one we're going to look at and move if necessary
1124 + var examinedLabelGroup = pie.outerLabelGroupData[nextIndex];
1125 +
1126 + var info = {
1127 + labelHeights: pie.outerLabelGroupData[0].h,
1128 + center: pie.pieCenter,
1129 + lineLength: (pie.outerRadius + pie.options.labels.outer.pieDistance),
1130 + heightChange: pie.outerLabelGroupData[0].h + 1 // 1 = padding
1131 + };
1132 +
1133 + // loop through *ALL* label groups examined so far to check for conflicts. This is because when they're
1134 + // very tightly fitted, a later label group may still appear high up on the page
1135 + if (direction === "clockwise") {
1136 i = 0;
1137 - for (; i<=currIndex; i++) {
1138 - curr = pie.outerLabelGroupData[i];
1139 -
1140 - // if there's a conflict with this label group, shift the label to be AFTER the last known
1141 - // one that's been properly placed
1142 - if (!labels.isLabelHidden(pie, i) && helpers.rectIntersect(curr, examinedLabelGroup)) {
1143 - labels.adjustLabelPos(pie, nextIndex, currLabelGroup, info);
1144 - break;
1145 - }
1146 - }
1147 - } else {
1137 + for (; i<=currIndex; i++) {
1138 + curr = pie.outerLabelGroupData[i];
1139 +
1140 + // if there's a conflict with this label group, shift the label to be AFTER the last known
1141 + // one that's been properly placed
1142 + if (!labels.isLabelHidden(pie, i) && helpers.rectIntersect(curr, examinedLabelGroup)) {
1143 + labels.adjustLabelPos(pie, nextIndex, currLabelGroup, info);
1144 + break;
1145 + }
1146 + }
1147 + } else {
1148 i = size - 1;
1149 - for (; i >= currIndex; i--) {
1150 - curr = pie.outerLabelGroupData[i];
1151 -
1152 - // if there's a conflict with this label group, shift the label to be AFTER the last known
1153 - // one that's been properly placed
1154 - if (!labels.isLabelHidden(pie, i) && helpers.rectIntersect(curr, examinedLabelGroup)) {
1155 - labels.adjustLabelPos(pie, nextIndex, currLabelGroup, info);
1156 - break;
1157 - }
1158 - }
1159 - }
1160 - labels.checkConflict(pie, nextIndex, direction, size);
1161 - },
1162 -
1163 - isLabelHidden: function(pie, index) {
1164 - var percentage = pie.options.labels.outer.hideWhenLessThanPercentage;
1165 - return (percentage !== null && d.percentage < percentage) || pie.options.data.content[index].label === "";
1166 - },
1167 -
1168 - // does a little math to shift a label into a new position based on the last properly placed one
1169 - adjustLabelPos: function(pie, nextIndex, lastCorrectlyPositionedLabel, info) {
1170 - var xDiff, yDiff, newXPos, newYPos;
1171 - newYPos = lastCorrectlyPositionedLabel.y + info.heightChange;
1172 - yDiff = info.center.y - newYPos;
1173 -
1174 - if (Math.abs(info.lineLength) > Math.abs(yDiff)) {
1175 - xDiff = Math.sqrt((info.lineLength * info.lineLength) - (yDiff * yDiff));
1176 - } else {
1177 - xDiff = Math.sqrt((yDiff * yDiff) - (info.lineLength * info.lineLength));
1178 - }
1179 -
1180 - if (lastCorrectlyPositionedLabel.hs === "right") {
1181 - newXPos = info.center.x + xDiff;
1182 - } else {
1183 - newXPos = info.center.x - xDiff - pie.outerLabelGroupData[nextIndex].w;
1184 - }
1185 -
1186 - pie.outerLabelGroupData[nextIndex].x = newXPos;
1187 - pie.outerLabelGroupData[nextIndex].y = newYPos;
1188 - },
1189 -
1190 - /**
1191 - * @param i 0-N where N is the dataset size - 1.
1192 - */
1193 - getIdealOuterLabelPositions: function(pie, i) {
1194 - var labelGroupNode = d3.select("#" + pie.cssPrefix + "labelGroup" + i + "-outer").node();
1195 - if (!labelGroupNode) {
1196 - return;
1149 + for (; i >= currIndex; i--) {
1150 + curr = pie.outerLabelGroupData[i];
1151 +
1152 + // if there's a conflict with this label group, shift the label to be AFTER the last known
1153 + // one that's been properly placed
1154 + if (!labels.isLabelHidden(pie, i) && helpers.rectIntersect(curr, examinedLabelGroup)) {
1155 + labels.adjustLabelPos(pie, nextIndex, currLabelGroup, info);
1156 + break;
1157 + }
1158 + }
1159 + }
1160 + labels.checkConflict(pie, nextIndex, direction, size);
1161 + },
1162 +
1163 + isLabelHidden: function(pie, index) {
1164 + var percentage = pie.options.labels.outer.hideWhenLessThanPercentage;
1165 + return (percentage !== null && d.percentage < percentage) || pie.options.data.content[index].label === "";
1166 + },
1167 +
1168 + // does a little math to shift a label into a new position based on the last properly placed one
1169 + adjustLabelPos: function(pie, nextIndex, lastCorrectlyPositionedLabel, info) {
1170 + var xDiff, yDiff, newXPos, newYPos;
1171 + newYPos = lastCorrectlyPositionedLabel.y + info.heightChange;
1172 + yDiff = info.center.y - newYPos;
1173 +
1174 + if (Math.abs(info.lineLength) > Math.abs(yDiff)) {
1175 + xDiff = Math.sqrt((info.lineLength * info.lineLength) - (yDiff * yDiff));
1176 + } else {
1177 + xDiff = Math.sqrt((yDiff * yDiff) - (info.lineLength * info.lineLength));
1178 + }
1179 +
1180 + if (lastCorrectlyPositionedLabel.hs === "right") {
1181 + newXPos = info.center.x + xDiff;
1182 + } else {
1183 + newXPos = info.center.x - xDiff - pie.outerLabelGroupData[nextIndex].w;
1184 + }
1185 +
1186 + pie.outerLabelGroupData[nextIndex].x = newXPos;
1187 + pie.outerLabelGroupData[nextIndex].y = newYPos;
1188 + },
1189 +
1190 + /**
1191 + * @param i 0-N where N is the dataset size - 1.
1192 + */
1193 + getIdealOuterLabelPositions: function(pie, i) {
1194 + var labelGroupNode = pie.svg.select("#" + pie.cssPrefix + "labelGroup" + i + "-outer").node();
1195 + if (!labelGroupNode) return;
1196 +
1197 + var labelGroupDims = labelGroupNode.getBBox();
1198 + var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
1199 +
1200 + var originalX = pie.pieCenter.x;
1201 + var originalY = pie.pieCenter.y - (pie.outerRadius + pie.options.labels.outer.pieDistance);
1202 + var newCoords = math.rotate(originalX, originalY, pie.pieCenter.x, pie.pieCenter.y, angle);
1203 +
1204 + // if the label is on the left half of the pie, adjust the values
1205 + var hemisphere = "right"; // hemisphere
1206 + if (angle > 180) {
1207 + newCoords.x -= (labelGroupDims.width + 8);
1208 + hemisphere = "left";
1209 + } else {
1210 + newCoords.x += 8;
1211 + }
1212 +
1213 + pie.outerLabelGroupData[i] = {
1214 + x: newCoords.x,
1215 + y: newCoords.y,
1216 + w: labelGroupDims.width,
1217 + h: labelGroupDims.height,
1218 + hs: hemisphere
1219 + };
1220 }
1198 - var labelGroupDims = labelGroupNode.getBBox();
1199 - var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
1200 -
1201 - var originalX = pie.pieCenter.x;
1202 - var originalY = pie.pieCenter.y - (pie.outerRadius + pie.options.labels.outer.pieDistance);
1203 - var newCoords = math.rotate(originalX, originalY, pie.pieCenter.x, pie.pieCenter.y, angle);
1204 -
1205 - // if the label is on the left half of the pie, adjust the values
1206 - var hemisphere = "right"; // hemisphere
1207 - if (angle > 180) {
1208 - newCoords.x -= (labelGroupDims.width + 8);
1209 - hemisphere = "left";
1210 - } else {
1211 - newCoords.x += 8;
1212 - }
1213 -
1214 - pie.outerLabelGroupData[i] = {
1215 - x: newCoords.x,
1216 - y: newCoords.y,
1217 - w: labelGroupDims.width,
1218 - h: labelGroupDims.height,
1219 - hs: hemisphere
1220 - };
1221 - }
1221 };
1222
1224 - //// --------- segments.js -----------
1223 + //// --------- segments.js -----------
1224 var segments = {
1225
1226 effectMap: {
@@ -1236,475 +1235,475 @@ var segments = {
1235 "exp": d3.easeExp
1236 },
1237
1239 - /**
1240 - * Creates the pie chart segments and displays them according to the desired load effect.
1241 - * @private
1242 - */
1243 - create: function(pie) {
1244 - var pieCenter = pie.pieCenter;
1245 - var colors = pie.options.colors;
1246 - var loadEffects = pie.options.effects.load;
1247 - var segmentStroke = pie.options.misc.colors.segmentStroke;
1248 -
1249 - // we insert the pie chart BEFORE the title, to ensure the title overlaps the pie
1250 - var pieChartElement = pie.svg.insert("g", "#" + pie.cssPrefix + "title")
1251 - .attr("transform", function() { return math.getPieTranslateCenter(pieCenter); })
1252 - .attr("class", pie.cssPrefix + "pieChart");
1253 -
1254 - var arc = d3.arc()
1255 - .innerRadius(pie.innerRadius)
1256 - .outerRadius(pie.outerRadius)
1257 - .startAngle(0)
1258 - .endAngle(function(d) {
1259 - return (d.value / pie.totalSize) * 2 * Math.PI;
1260 - });
1261 -
1262 - var g = pieChartElement.selectAll("." + pie.cssPrefix + "arc")
1263 - .data(pie.options.data.content)
1264 - .enter()
1265 - .append("g")
1266 - .attr("class", pie.cssPrefix + "arc");
1267 -
1268 - // if we're not fading in the pie, just set the load speed to 0
1269 - //var loadSpeed = loadEffects.speed;
1270 - //if (loadEffects.effect === "none") {
1271 - // loadSpeed = 0;
1272 - //}
1273 -
1274 - g.append("path")
1275 - .attr("id", function(d, i) { return pie.cssPrefix + "segment" + i; })
1276 - .attr("fill", function(d, i) {
1277 - var color = colors[i];
1278 - if (pie.options.misc.gradient.enabled) {
1279 - color = "url(#" + pie.cssPrefix + "grad" + i + ")";
1280 - }
1281 - return color;
1282 - })
1283 - .style("stroke", segmentStroke)
1284 - .style("stroke-width", 1)
1285 - //.transition()
1286 - //.ease(d3.easeCubicInOut)
1287 - //.duration(loadSpeed)
1288 - .attr("data-index", function(d, i) { return i; })
1289 - .attr("d", arc);
1238 + /**
1239 + * Creates the pie chart segments and displays them according to the desired load effect.
1240 + * @private
1241 + */
1242 + create: function(pie) {
1243 + var pieCenter = pie.pieCenter;
1244 + var colors = pie.options.colors;
1245 + var loadEffects = pie.options.effects.load;
1246 + var segmentStroke = pie.options.misc.colors.segmentStroke;
1247 +
1248 + // we insert the pie chart BEFORE the title, to ensure the title overlaps the pie
1249 + var pieChartElement = pie.svg.insert("g", "#" + pie.cssPrefix + "title")
1250 + .attr("transform", function() { return math.getPieTranslateCenter(pieCenter); })
1251 + .attr("class", pie.cssPrefix + "pieChart");
1252 +
1253 + var arc = d3.arc()
1254 + .innerRadius(pie.innerRadius)
1255 + .outerRadius(pie.outerRadius)
1256 + .startAngle(0)
1257 + .endAngle(function(d) {
1258 + return (d.value / pie.totalSize) * 2 * Math.PI;
1259 + });
1260 +
1261 + var g = pieChartElement.selectAll("." + pie.cssPrefix + "arc")
1262 + .data(pie.options.data.content)
1263 + .enter()
1264 + .append("g")
1265 + .attr("class", pie.cssPrefix + "arc");
1266 +
1267 + // if we're not fading in the pie, just set the load speed to 0
1268 + //var loadSpeed = loadEffects.speed;
1269 + //if (loadEffects.effect === "none") {
1270 + // loadSpeed = 0;
1271 + //}
1272 +
1273 + g.append("path")
1274 + .attr("id", function(d, i) { return pie.cssPrefix + "segment" + i; })
1275 + .attr("fill", function(d, i) {
1276 + var color = colors[i];
1277 + if (pie.options.misc.gradient.enabled) {
1278 + color = "url(#" + pie.cssPrefix + "grad" + i + ")";
1279 + }
1280 + return color;
1281 + })
1282 + .style("stroke", segmentStroke)
1283 + .style("stroke-width", 1)
1284 + //.transition()
1285 + //.ease(d3.easeCubicInOut)
1286 + //.duration(loadSpeed)
1287 + .attr("data-index", function(d, i) { return i; })
1288 + .attr("d", arc);
1289 /*
1291 - .attrTween("d", function(b) {
1292 - var i = d3.interpolate({ value: 0 }, b);
1293 - return function(t) {
1294 - var ret = pie.arc(i(t));
1295 - console.log(ret);
1296 - return ret;
1297 - };
1298 - });
1290 + .attrTween("d", function(b) {
1291 + var i = d3.interpolate({ value: 0 }, b);
1292 + return function(t) {
1293 + var ret = pie.arc(i(t));
1294 + console.log(ret);
1295 + return ret;
1296 + };
1297 + });
1298 */
1300 - pie.svg.selectAll("g." + pie.cssPrefix + "arc")
1301 - .attr("transform",
1302 - function(d, i) {
1303 - var angle = 0;
1304 - if (i > 0) {
1305 - angle = segments.getSegmentAngle(i-1, pie.options.data.content, pie.totalSize);
1306 - }
1307 - return "rotate(" + angle + ")";
1308 - }
1309 - );
1310 - pie.arc = arc;
1311 - },
1312 -
1313 - addGradients: function(pie) {
1314 - var grads = pie.svg.append("defs")
1315 - .selectAll("radialGradient")
1316 - .data(pie.options.data.content)
1317 - .enter().append("radialGradient")
1318 - .attr("gradientUnits", "userSpaceOnUse")
1319 - .attr("cx", 0)
1320 - .attr("cy", 0)
1321 - .attr("r", "120%")
1322 - .attr("id", function(d, i) { return pie.cssPrefix + "grad" + i; });
1323 -
1324 - grads.append("stop").attr("offset", "0%").style("stop-color", function(d, i) { return pie.options.colors[i]; });
1325 - grads.append("stop").attr("offset", pie.options.misc.gradient.percentage + "%").style("stop-color", pie.options.misc.gradient.color);
1326 - },
1327 -
1328 - addSegmentEventHandlers: function(pie) {
1329 - var arc = d3.selectAll("." + pie.cssPrefix + "arc");
1330 - arc = arc.merge(pie.__labels.inner.merge(pie.__labels.outer));
1331 -
1332 - arc.on("click", function() {
1333 - var currentEl = d3.select(this);
1334 - var segment;
1335 -
1336 - // mouseover works on both the segments AND the segment labels, hence the following
1337 - if (currentEl.attr("class") === pie.cssPrefix + "arc") {
1338 - segment = currentEl.select("path");
1339 - } else {
1340 - var index = currentEl.attr("data-index");
1341 - segment = d3.select("#" + pie.cssPrefix + "segment" + index);
1342 - }
1343 -
1344 - var isExpanded = segment.attr("class") === pie.cssPrefix + "expanded";
1345 - segments.onSegmentEvent(pie, pie.options.callbacks.onClickSegment, segment, isExpanded);
1346 - if (pie.options.effects.pullOutSegmentOnClick.effect !== "none") {
1347 - if (isExpanded) {
1348 - segments.closeSegment(pie, segment.node());
1349 - } else {
1350 - segments.openSegment(pie, segment.node());
1351 - }
1352 - }
1353 - });
1354 -
1355 - arc.on("mouseover", function() {
1356 - var currentEl = d3.select(this);
1357 - var segment, index;
1358 -
1359 - if (currentEl.attr("class") === pie.cssPrefix + "arc") {
1360 - segment = currentEl.select("path");
1361 - } else {
1362 - index = currentEl.attr("data-index");
1363 - segment = d3.select("#" + pie.cssPrefix + "segment" + index);
1364 - }
1365 -
1366 - if (pie.options.effects.highlightSegmentOnMouseover) {
1367 - index = segment.attr("data-index");
1368 - var segColor = pie.options.colors[index];
1369 - segment.style("fill", helpers.getColorShade(segColor, pie.options.effects.highlightLuminosity));
1370 - }
1371 -
1372 - if (pie.options.tooltips.enabled) {
1373 - index = segment.attr("data-index");
1374 - tt.showTooltip(pie, index);
1375 - }
1376 -
1377 - var isExpanded = segment.attr("class") === pie.cssPrefix + "expanded";
1378 - segments.onSegmentEvent(pie, pie.options.callbacks.onMouseoverSegment, segment, isExpanded);
1379 - });
1380 -
1381 - arc.on("mousemove", function() {
1382 - tt.moveTooltip(pie);
1383 - });
1384 -
1385 - arc.on("mouseout", function() {
1386 - var currentEl = d3.select(this);
1387 - var segment, index;
1388 -
1389 - if (currentEl.attr("class") === pie.cssPrefix + "arc") {
1390 - segment = currentEl.select("path");
1391 - } else {
1392 - index = currentEl.attr("data-index");
1393 - segment = d3.select("#" + pie.cssPrefix + "segment" + index);
1394 - }
1395 -
1396 - if (pie.options.effects.highlightSegmentOnMouseover) {
1397 - index = segment.attr("data-index");
1398 - var color = pie.options.colors[index];
1399 - if (pie.options.misc.gradient.enabled) {
1400 - color = "url(#" + pie.cssPrefix + "grad" + index + ")";
1401 - }
1402 - segment.style("fill", color);
1403 - }
1404 -
1405 - if (pie.options.tooltips.enabled) {
1406 - index = segment.attr("data-index");
1407 - tt.hideTooltip(pie, index);
1408 - }
1409 -
1410 - var isExpanded = segment.attr("class") === pie.cssPrefix + "expanded";
1411 - segments.onSegmentEvent(pie, pie.options.callbacks.onMouseoutSegment, segment, isExpanded);
1412 - });
1413 - },
1414 -
1415 - // helper function used to call the click, mouseover, mouseout segment callback functions
1416 - onSegmentEvent: function(pie, func, segment, isExpanded) {
1417 - if (!helpers.isFunction(func)) {
1418 - return;
1419 - }
1420 - var index = parseInt(segment.attr("data-index"), 10);
1421 - func({
1422 - segment: segment.node(),
1423 - index: index,
1424 - expanded: isExpanded,
1425 - data: pie.options.data.content[index]
1426 - });
1427 - },
1428 -
1429 - openSegment: function(pie, segment) {
1430 - if (pie.isOpeningSegment) {
1431 - return;
1432 - }
1433 - pie.isOpeningSegment = true;
1434 -
1435 - segments.maybeCloseOpenSegment();
1436 -
1437 - d3.select(segment)
1438 - .transition()
1439 - .ease(segments.effectMap[pie.options.effects.pullOutSegmentOnClick.effect])
1440 - .duration(pie.options.effects.pullOutSegmentOnClick.speed)
1441 - .attr("transform", function(d, i) {
1442 - var c = pie.arc.centroid(d),
1443 - x = c[0],
1444 - y = c[1],
1445 - h = Math.sqrt(x*x + y*y),
1446 - pullOutSize = parseInt(pie.options.effects.pullOutSegmentOnClick.size, 10);
1447 -
1448 - return "translate(" + ((x/h) * pullOutSize) + ',' + ((y/h) * pullOutSize) + ")";
1449 - })
1450 - .on("end", function(d, i) {
1451 - pie.currentlyOpenSegment = segment;
1452 - pie.isOpeningSegment = false;
1453 - d3.select(segment).attr("class", pie.cssPrefix + "expanded");
1454 - });
1455 - },
1456 -
1457 - maybeCloseOpenSegment: function() {
1458 - if (typeof pie !== 'undefined' && d3.selectAll("." + pie.cssPrefix + "expanded").size() > 0) {
1459 - segments.closeSegment(pie, d3.select("." + pie.cssPrefix + "expanded").node());
1460 - }
1461 - },
1462 -
1463 - closeSegment: function(pie, segment) {
1464 - d3.select(segment)
1465 - .transition()
1466 - .duration(400)
1467 - .attr("transform", "translate(0,0)")
1468 - .on("end", function(d, i) {
1469 - d3.select(segment).attr("class", "");
1470 - pie.currentlyOpenSegment = null;
1471 - });
1472 - },
1473 -
1474 - getCentroid: function(el) {
1475 - var bbox = el.getBBox();
1476 - return {
1477 - x: bbox.x + bbox.width / 2,
1478 - y: bbox.y + bbox.height / 2
1479 - };
1480 - },
1481 -
1482 - /**
1483 - * General helper function to return a segment's angle, in various different ways.
1484 - * @param index
1485 - * @param opts optional object for fine-tuning exactly what you want.
1486 - */
1487 - getSegmentAngle: function(index, data, totalSize, opts) {
1488 - var options = extend({
1489 - // if true, this returns the full angle from the origin. Otherwise it returns the single segment angle
1490 - compounded: true,
1491 -
1492 - // optionally returns the midpoint of the angle instead of the full angle
1493 - midpoint: false
1494 - }, opts);
1495 -
1496 - var currValue = data[index].value;
1497 - var fullValue;
1498 - if (options.compounded) {
1499 - fullValue = 0;
1500 -
1501 - // get all values up to and including the specified index
1502 - for (var i=0; i<=index; i++) {
1503 - fullValue += data[i].value;
1504 - }
1505 - }
1506 -
1507 - if (typeof fullValue === 'undefined') {
1508 - fullValue = currValue;
1509 - }
1510 -
1511 - // now convert the full value to an angle
1512 - var angle = (fullValue / totalSize) * 360;
1513 -
1514 - // lastly, if we want the midpoint, factor that sucker in
1515 - if (options.midpoint) {
1516 - var currAngle = (currValue / totalSize) * 360;
1517 - angle -= (currAngle / 2);
1518 - }
1519 -
1520 - return angle;
1521 - }
1299 + pie.svg.selectAll("g." + pie.cssPrefix + "arc")
1300 + .attr("transform",
1301 + function(d, i) {
1302 + var angle = 0;
1303 + if (i > 0) {
1304 + angle = segments.getSegmentAngle(i-1, pie.options.data.content, pie.totalSize);
1305 + }
1306 + return "rotate(" + angle + ")";
1307 + }
1308 + );
1309 + pie.arc = arc;
1310 + },
1311 +
1312 + addGradients: function(pie) {
1313 + var grads = pie.svg.append("defs")
1314 + .selectAll("radialGradient")
1315 + .data(pie.options.data.content)
1316 + .enter().append("radialGradient")
1317 + .attr("gradientUnits", "userSpaceOnUse")
1318 + .attr("cx", 0)
1319 + .attr("cy", 0)
1320 + .attr("r", "120%")
1321 + .attr("id", function(d, i) { return pie.cssPrefix + "grad" + i; });
1322 +
1323 + grads.append("stop").attr("offset", "0%").style("stop-color", function(d, i) { return pie.options.colors[i]; });
1324 + grads.append("stop").attr("offset", pie.options.misc.gradient.percentage + "%").style("stop-color", pie.options.misc.gradient.color);
1325 + },
1326 +
1327 + addSegmentEventHandlers: function(pie) {
1328 + var arc = pie.svg.selectAll("." + pie.cssPrefix + "arc");
1329 + arc = arc.merge(pie.__labels.inner.merge(pie.__labels.outer));
1330 +
1331 + arc.on("click", function() {
1332 + var currentEl = d3.select(this);
1333 + var segment;
1334 +
1335 + // mouseover works on both the segments AND the segment labels, hence the following
1336 + if (currentEl.attr("class") === pie.cssPrefix + "arc") {
1337 + segment = currentEl.select("path");
1338 + } else {
1339 + var index = currentEl.attr("data-index");
1340 + segment = d3.select("#" + pie.cssPrefix + "segment" + index);
1341 + }
1342 +
1343 + var isExpanded = segment.attr("class") === pie.cssPrefix + "expanded";
1344 + segments.onSegmentEvent(pie, pie.options.callbacks.onClickSegment, segment, isExpanded);
1345 + if (pie.options.effects.pullOutSegmentOnClick.effect !== "none") {
1346 + if (isExpanded) {
1347 + segments.closeSegment(pie, segment.node());
1348 + } else {
1349 + segments.openSegment(pie, segment.node());
1350 + }
1351 + }
1352 + });
1353 +
1354 + arc.on("mouseover", function() {
1355 + var currentEl = d3.select(this);
1356 + var segment, index;
1357 +
1358 + if (currentEl.attr("class") === pie.cssPrefix + "arc") {
1359 + segment = currentEl.select("path");
1360 + } else {
1361 + index = currentEl.attr("data-index");
1362 + segment = d3.select("#" + pie.cssPrefix + "segment" + index);
1363 + }
1364 +
1365 + if (pie.options.effects.highlightSegmentOnMouseover) {
1366 + index = segment.attr("data-index");
1367 + var segColor = pie.options.colors[index];
1368 + segment.style("fill", helpers.getColorShade(segColor, pie.options.effects.highlightLuminosity));
1369 + }
1370 +
1371 + if (pie.options.tooltips.enabled) {
1372 + index = segment.attr("data-index");
1373 + tt.showTooltip(pie, index);
1374 + }
1375 +
1376 + var isExpanded = segment.attr("class") === pie.cssPrefix + "expanded";
1377 + segments.onSegmentEvent(pie, pie.options.callbacks.onMouseoverSegment, segment, isExpanded);
1378 + });
1379 +
1380 + arc.on("mousemove", function() {
1381 + tt.moveTooltip(pie);
1382 + });
1383 +
1384 + arc.on("mouseout", function() {
1385 + var currentEl = d3.select(this);
1386 + var segment, index;
1387 +
1388 + if (currentEl.attr("class") === pie.cssPrefix + "arc") {
1389 + segment = currentEl.select("path");
1390 + } else {
1391 + index = currentEl.attr("data-index");
1392 + segment = d3.select("#" + pie.cssPrefix + "segment" + index);
1393 + }
1394 +
1395 + if (pie.options.effects.highlightSegmentOnMouseover) {
1396 + index = segment.attr("data-index");
1397 + var color = pie.options.colors[index];
1398 + if (pie.options.misc.gradient.enabled) {
1399 + color = "url(#" + pie.cssPrefix + "grad" + index + ")";
1400 + }
1401 + segment.style("fill", color);
1402 + }
1403 +
1404 + if (pie.options.tooltips.enabled) {
1405 + index = segment.attr("data-index");
1406 + tt.hideTooltip(pie, index);
1407 + }
1408 +
1409 + var isExpanded = segment.attr("class") === pie.cssPrefix + "expanded";
1410 + segments.onSegmentEvent(pie, pie.options.callbacks.onMouseoutSegment, segment, isExpanded);
1411 + });
1412 + },
1413 +
1414 + // helper function used to call the click, mouseover, mouseout segment callback functions
1415 + onSegmentEvent: function(pie, func, segment, isExpanded) {
1416 + if (!helpers.isFunction(func)) {
1417 + return;
1418 + }
1419 + var index = parseInt(segment.attr("data-index"), 10);
1420 + func({
1421 + segment: segment.node(),
1422 + index: index,
1423 + expanded: isExpanded,
1424 + data: pie.options.data.content[index]
1425 + });
1426 + },
1427 +
1428 + openSegment: function(pie, segment) {
1429 + if (pie.isOpeningSegment) {
1430 + return;
1431 + }
1432 + pie.isOpeningSegment = true;
1433 +
1434 + segments.maybeCloseOpenSegment(pie);
1435 +
1436 + d3.select(segment)
1437 + .transition()
1438 + .ease(segments.effectMap[pie.options.effects.pullOutSegmentOnClick.effect])
1439 + .duration(pie.options.effects.pullOutSegmentOnClick.speed)
1440 + .attr("transform", function(d, i) {
1441 + var c = pie.arc.centroid(d),
1442 + x = c[0],
1443 + y = c[1],
1444 + h = Math.sqrt(x*x + y*y),
1445 + pullOutSize = parseInt(pie.options.effects.pullOutSegmentOnClick.size, 10);
1446 +
1447 + return "translate(" + ((x/h) * pullOutSize) + ',' + ((y/h) * pullOutSize) + ")";
1448 + })
1449 + .on("end", function(d, i) {
1450 + pie.currentlyOpenSegment = segment;
1451 + pie.isOpeningSegment = false;
1452 + d3.select(segment).attr("class", pie.cssPrefix + "expanded");
1453 + });
1454 + },
1455 +
1456 + maybeCloseOpenSegment: function(pie) {
1457 + if (typeof pie !== 'undefined' && pie.svg.selectAll("." + pie.cssPrefix + "expanded").size() > 0) {
1458 + segments.closeSegment(pie, pie.svg.select("." + pie.cssPrefix + "expanded").node());
1459 + }
1460 + },
1461 +
1462 + closeSegment: function(pie, segment) {
1463 + d3.select(segment)
1464 + .transition()
1465 + .duration(400)
1466 + .attr("transform", "translate(0,0)")
1467 + .on("end", function(d, i) {
1468 + d3.select(segment).attr("class", "");
1469 + pie.currentlyOpenSegment = null;
1470 + });
1471 + },
1472 +
1473 + getCentroid: function(el) {
1474 + var bbox = el.getBBox();
1475 + return {
1476 + x: bbox.x + bbox.width / 2,
1477 + y: bbox.y + bbox.height / 2
1478 + };
1479 + },
1480 +
1481 + /**
1482 + * General helper function to return a segment's angle, in various different ways.
1483 + * @param index
1484 + * @param opts optional object for fine-tuning exactly what you want.
1485 + */
1486 + getSegmentAngle: function(index, data, totalSize, opts) {
1487 + var options = extend({
1488 + // if true, this returns the full angle from the origin. Otherwise it returns the single segment angle
1489 + compounded: true,
1490 +
1491 + // optionally returns the midpoint of the angle instead of the full angle
1492 + midpoint: false
1493 + }, opts);
1494 +
1495 + var currValue = data[index].value;
1496 + var fullValue;
1497 + if (options.compounded) {
1498 + fullValue = 0;
1499 +
1500 + // get all values up to and including the specified index
1501 + for (var i=0; i<=index; i++) {
1502 + fullValue += data[i].value;
1503 + }
1504 + }
1505 +
1506 + if (typeof fullValue === 'undefined') {
1507 + fullValue = currValue;
1508 + }
1509 +
1510 + // now convert the full value to an angle
1511 + var angle = (fullValue / totalSize) * 360;
1512 +
1513 + // lastly, if we want the midpoint, factor that sucker in
1514 + if (options.midpoint) {
1515 + var currAngle = (currValue / totalSize) * 360;
1516 + angle -= (currAngle / 2);
1517 + }
1518 +
1519 + return angle;
1520 + }
1521
1522 };
1523
1525 - //// --------- text.js -----------
1524 + //// --------- text.js -----------
1525 var text = {
1527 - offscreenCoord: -10000,
1528 -
1529 - addTitle: function(pie) {
1530 - pie.__title = pie.svg.selectAll("." + pie.cssPrefix + "title")
1531 - .data([pie.options.header.title])
1532 - .enter()
1533 - .append("text")
1534 - .text(function(d) { return d.text; })
1535 - .attr("id", pie.cssPrefix + "title")
1536 - .attr("class", pie.cssPrefix + "title")
1537 - .attr("x", text.offscreenCoord)
1538 - .attr("y", text.offscreenCoord)
1539 - .attr("text-anchor", function() {
1540 - var location;
1541 - if (pie.options.header.location === "top-center" || pie.options.header.location === "pie-center") {
1542 - location = "middle";
1543 - } else {
1544 - location = "left";
1545 - }
1546 - return location;
1547 - })
1548 - .attr("fill", function(d) { return d.color; })
1549 - .style("font-size", function(d) { return d.fontSize + "px"; })
1550 - .style("font-weight", function(d) { return d.fontWeight; })
1551 - .style("font-family", function(d) { return d.font; });
1552 - },
1553 -
1554 - positionTitle: function(pie) {
1555 - var textComponents = pie.textComponents;
1556 - var headerLocation = pie.options.header.location;
1557 - var canvasPadding = pie.options.misc.canvasPadding;
1558 - var canvasWidth = pie.options.size.canvasWidth;
1559 - var titleSubtitlePadding = pie.options.header.titleSubtitlePadding;
1560 -
1561 - var x;
1562 - if (headerLocation === "top-left") {
1563 - x = canvasPadding.left;
1564 - } else {
1565 - x = ((canvasWidth - canvasPadding.right) / 2) + canvasPadding.left;
1566 - }
1567 -
1568 - // add whatever offset has been added by user
1569 - x += pie.options.misc.pieCenterOffset.x;
1570 -
1571 - var y = canvasPadding.top + textComponents.title.h;
1572 -
1573 - if (headerLocation === "pie-center") {
1574 - y = pie.pieCenter.y;
1575 -
1576 - // still not fully correct
1577 - if (textComponents.subtitle.exists) {
1578 - var totalTitleHeight = textComponents.title.h + titleSubtitlePadding + textComponents.subtitle.h;
1579 - y = y - (totalTitleHeight / 2) + textComponents.title.h;
1580 - } else {
1581 - y += (textComponents.title.h / 4);
1582 - }
1583 - }
1584 -
1585 - pie.__title
1586 - .attr("x", x)
1587 - .attr("y", y);
1588 - },
1589 -
1590 - addSubtitle: function(pie) {
1591 - var headerLocation = pie.options.header.location;
1592 -
1593 - pie.__subtitle = pie.svg.selectAll("." + pie.cssPrefix + "subtitle")
1594 - .data([pie.options.header.subtitle])
1595 - .enter()
1596 - .append("text")
1597 - .text(function(d) { return d.text; })
1598 - .attr("x", text.offscreenCoord)
1599 - .attr("y", text.offscreenCoord)
1600 - .attr("id", pie.cssPrefix + "subtitle")
1601 - .attr("class", pie.cssPrefix + "subtitle")
1602 - .attr("text-anchor", function() {
1603 - var location;
1604 - if (headerLocation === "top-center" || headerLocation === "pie-center") {
1605 - location = "middle";
1606 - } else {
1607 - location = "left";
1608 - }
1609 - return location;
1610 - })
1611 - .attr("fill", function(d) { return d.color; })
1612 - .style("font-size", function(d) { return d.fontSize + "px"; })
1613 - .style("font-weight", function(d) { return d.fontWeight; })
1614 - .style("font-family", function(d) { return d.font; });
1615 - },
1616 -
1617 - positionSubtitle: function(pie) {
1618 - var canvasPadding = pie.options.misc.canvasPadding;
1619 - var canvasWidth = pie.options.size.canvasWidth;
1620 -
1621 - var x;
1622 - if (pie.options.header.location === "top-left") {
1623 - x = canvasPadding.left;
1624 - } else {
1625 - x = ((canvasWidth - canvasPadding.right) / 2) + canvasPadding.left;
1626 - }
1627 -
1628 - // add whatever offset has been added by user
1629 - x += pie.options.misc.pieCenterOffset.x;
1630 -
1631 - var y = text.getHeaderHeight(pie);
1632 -
1633 - pie.__subtitle
1634 - .attr("x", x)
1635 - .attr("y", y);
1636 - },
1637 -
1638 - addFooter: function(pie) {
1639 - pie.__footer = pie.svg.selectAll("." + pie.cssPrefix + "footer")
1640 - .data([pie.options.footer])
1641 - .enter()
1642 - .append("text")
1643 - .text(function(d) { return d.text; })
1644 - .attr("x", text.offscreenCoord)
1645 - .attr("y", text.offscreenCoord)
1646 - .attr("id", pie.cssPrefix + "footer")
1647 - .attr("class", pie.cssPrefix + "footer")
1648 - .attr("text-anchor", function() {
1649 - var location = "left";
1650 - if (pie.options.footer.location === "bottom-center") {
1651 - location = "middle";
1652 - } else if (pie.options.footer.location === "bottom-right") {
1653 - location = "left"; // on purpose. We have to change the x-coord to make it properly right-aligned
1654 - }
1655 - return location;
1656 - })
1657 - .attr("fill", function(d) { return d.color; })
1658 - .style("font-size", function(d) { return d.fontSize + "px"; })
1659 - .style("font-weight", function(d) { return d.fontWeight; })
1660 - .style("font-family", function(d) { return d.font; });
1661 - },
1662 -
1663 - positionFooter: function(pie) {
1664 - var footerLocation = pie.options.footer.location;
1665 - var footerWidth = pie.textComponents.footer.w;
1666 - var canvasWidth = pie.options.size.canvasWidth;
1667 - var canvasHeight = pie.options.size.canvasHeight;
1668 - var canvasPadding = pie.options.misc.canvasPadding;
1669 -
1670 - var x;
1671 - if (footerLocation === "bottom-left") {
1672 - x = canvasPadding.left;
1673 - } else if (footerLocation === "bottom-right") {
1674 - x = canvasWidth - footerWidth - canvasPadding.right;
1675 - } else {
1676 - x = canvasWidth / 2; // TODO - shouldn't this also take into account padding?
1677 - }
1678 -
1679 - pie.__footer
1680 - .attr("x", x)
1681 - .attr("y", canvasHeight - canvasPadding.bottom);
1682 - },
1683 -
1684 - getHeaderHeight: function(pie) {
1685 - var h;
1686 - if (pie.textComponents.title.exists) {
1687 -
1688 - // if the subtitle isn't defined, it'll be set to 0
1689 - var totalTitleHeight = pie.textComponents.title.h + pie.options.header.titleSubtitlePadding + pie.textComponents.subtitle.h;
1690 - if (pie.options.header.location === "pie-center") {
1691 - h = pie.pieCenter.y - (totalTitleHeight / 2) + totalTitleHeight;
1692 - } else {
1693 - h = totalTitleHeight + pie.options.misc.canvasPadding.top;
1694 - }
1695 - } else {
1696 - if (pie.options.header.location === "pie-center") {
1697 - var footerPlusPadding = pie.options.misc.canvasPadding.bottom + pie.textComponents.footer.h;
1698 - h = ((pie.options.size.canvasHeight - footerPlusPadding) / 2) + pie.options.misc.canvasPadding.top + (pie.textComponents.subtitle.h / 2);
1699 - } else {
1700 - h = pie.options.misc.canvasPadding.top + pie.textComponents.subtitle.h;
1701 - }
1702 - }
1703 - return h;
1704 - }
1526 + offscreenCoord: -10000,
1527 +
1528 + addTitle: function(pie) {
1529 + pie.__title = pie.svg.selectAll("." + pie.cssPrefix + "title")
1530 + .data([pie.options.header.title])
1531 + .enter()
1532 + .append("text")
1533 + .text(function(d) { return d.text; })
1534 + .attr("id", pie.cssPrefix + "title")
1535 + .attr("class", pie.cssPrefix + "title")
1536 + .attr("x", text.offscreenCoord)
1537 + .attr("y", text.offscreenCoord)
1538 + .attr("text-anchor", function() {
1539 + var location;
1540 + if (pie.options.header.location === "top-center" || pie.options.header.location === "pie-center") {
1541 + location = "middle";
1542 + } else {
1543 + location = "left";
1544 + }
1545 + return location;
1546 + })
1547 + .attr("fill", function(d) { return d.color; })
1548 + .style("font-size", function(d) { return d.fontSize + "px"; })
1549 + .style("font-weight", function(d) { return d.fontWeight; })
1550 + .style("font-family", function(d) { return d.font; });
1551 + },
1552 +
1553 + positionTitle: function(pie) {
1554 + var textComponents = pie.textComponents;
1555 + var headerLocation = pie.options.header.location;
1556 + var canvasPadding = pie.options.misc.canvasPadding;
1557 + var canvasWidth = pie.options.size.canvasWidth;
1558 + var titleSubtitlePadding = pie.options.header.titleSubtitlePadding;
1559 +
1560 + var x;
1561 + if (headerLocation === "top-left") {
1562 + x = canvasPadding.left;
1563 + } else {
1564 + x = ((canvasWidth - canvasPadding.right) / 2) + canvasPadding.left;
1565 + }
1566 +
1567 + // add whatever offset has been added by user
1568 + x += pie.options.misc.pieCenterOffset.x;
1569 +
1570 + var y = canvasPadding.top + textComponents.title.h;
1571 +
1572 + if (headerLocation === "pie-center") {
1573 + y = pie.pieCenter.y;
1574 +
1575 + // still not fully correct
1576 + if (textComponents.subtitle.exists) {
1577 + var totalTitleHeight = textComponents.title.h + titleSubtitlePadding + textComponents.subtitle.h;
1578 + y = y - (totalTitleHeight / 2) + textComponents.title.h;
1579 + } else {
1580 + y += (textComponents.title.h / 4);
1581 + }
1582 + }
1583 +
1584 + pie.__title
1585 + .attr("x", x)
1586 + .attr("y", y);
1587 + },
1588 +
1589 + addSubtitle: function(pie) {
1590 + var headerLocation = pie.options.header.location;
1591 +
1592 + pie.__subtitle = pie.svg.selectAll("." + pie.cssPrefix + "subtitle")
1593 + .data([pie.options.header.subtitle])
1594 + .enter()
1595 + .append("text")
1596 + .text(function(d) { return d.text; })
1597 + .attr("x", text.offscreenCoord)
1598 + .attr("y", text.offscreenCoord)
1599 + .attr("id", pie.cssPrefix + "subtitle")
1600 + .attr("class", pie.cssPrefix + "subtitle")
1601 + .attr("text-anchor", function() {
1602 + var location;
1603 + if (headerLocation === "top-center" || headerLocation === "pie-center") {
1604 + location = "middle";
1605 + } else {
1606 + location = "left";
1607 + }
1608 + return location;
1609 + })
1610 + .attr("fill", function(d) { return d.color; })
1611 + .style("font-size", function(d) { return d.fontSize + "px"; })
1612 + .style("font-weight", function(d) { return d.fontWeight; })
1613 + .style("font-family", function(d) { return d.font; });
1614 + },
1615 +
1616 + positionSubtitle: function(pie) {
1617 + var canvasPadding = pie.options.misc.canvasPadding;
1618 + var canvasWidth = pie.options.size.canvasWidth;
1619 +
1620 + var x;
1621 + if (pie.options.header.location === "top-left") {
1622 + x = canvasPadding.left;
1623 + } else {
1624 + x = ((canvasWidth - canvasPadding.right) / 2) + canvasPadding.left;
1625 + }
1626 +
1627 + // add whatever offset has been added by user
1628 + x += pie.options.misc.pieCenterOffset.x;
1629 +
1630 + var y = text.getHeaderHeight(pie);
1631 +
1632 + pie.__subtitle
1633 + .attr("x", x)
1634 + .attr("y", y);
1635 + },
1636 +
1637 + addFooter: function(pie) {
1638 + pie.__footer = pie.svg.selectAll("." + pie.cssPrefix + "footer")
1639 + .data([pie.options.footer])
1640 + .enter()
1641 + .append("text")
1642 + .text(function(d) { return d.text; })
1643 + .attr("x", text.offscreenCoord)
1644 + .attr("y", text.offscreenCoord)
1645 + .attr("id", pie.cssPrefix + "footer")
1646 + .attr("class", pie.cssPrefix + "footer")
1647 + .attr("text-anchor", function() {
1648 + var location = "left";
1649 + if (pie.options.footer.location === "bottom-center") {
1650 + location = "middle";
1651 + } else if (pie.options.footer.location === "bottom-right") {
1652 + location = "left"; // on purpose. We have to change the x-coord to make it properly right-aligned
1653 + }
1654 + return location;
1655 + })
1656 + .attr("fill", function(d) { return d.color; })
1657 + .style("font-size", function(d) { return d.fontSize + "px"; })
1658 + .style("font-weight", function(d) { return d.fontWeight; })
1659 + .style("font-family", function(d) { return d.font; });
1660 + },
1661 +
1662 + positionFooter: function(pie) {
1663 + var footerLocation = pie.options.footer.location;
1664 + var footerWidth = pie.textComponents.footer.w;
1665 + var canvasWidth = pie.options.size.canvasWidth;
1666 + var canvasHeight = pie.options.size.canvasHeight;
1667 + var canvasPadding = pie.options.misc.canvasPadding;
1668 +
1669 + var x;
1670 + if (footerLocation === "bottom-left") {
1671 + x = canvasPadding.left;
1672 + } else if (footerLocation === "bottom-right") {
1673 + x = canvasWidth - footerWidth - canvasPadding.right;
1674 + } else {
1675 + x = canvasWidth / 2; // TODO - shouldn't this also take into account padding?
1676 + }
1677 +
1678 + pie.__footer
1679 + .attr("x", x)
1680 + .attr("y", canvasHeight - canvasPadding.bottom);
1681 + },
1682 +
1683 + getHeaderHeight: function(pie) {
1684 + var h;
1685 + if (pie.textComponents.title.exists) {
1686 +
1687 + // if the subtitle isn't defined, it'll be set to 0
1688 + var totalTitleHeight = pie.textComponents.title.h + pie.options.header.titleSubtitlePadding + pie.textComponents.subtitle.h;
1689 + if (pie.options.header.location === "pie-center") {
1690 + h = pie.pieCenter.y - (totalTitleHeight / 2) + totalTitleHeight;
1691 + } else {
1692 + h = totalTitleHeight + pie.options.misc.canvasPadding.top;
1693 + }
1694 + } else {
1695 + if (pie.options.header.location === "pie-center") {
1696 + var footerPlusPadding = pie.options.misc.canvasPadding.bottom + pie.textComponents.footer.h;
1697 + h = ((pie.options.size.canvasHeight - footerPlusPadding) / 2) + pie.options.misc.canvasPadding.top + (pie.textComponents.subtitle.h / 2);
1698 + } else {
1699 + h = pie.options.misc.canvasPadding.top + pie.textComponents.subtitle.h;
1700 + }
1701 + }
1702 + return h;
1703 + }
1704 };
1705
1707 - //// --------- validate.js -----------
1706 + //// --------- validate.js -----------
1707 var tt = {
1708 addTooltips: function(pie) {
1709
@@ -1745,8 +1744,8 @@ var tt = {
1744 });
1745 });
1746
1748 - tooltips.selectAll("." + pie.cssPrefix + "tooltip rect")
1749 - .attr("width", function (d, i) {
1747 + tooltips.selectAll("." + pie.cssPrefix + "tooltip rect")
1748 + .attr("width", function (d, i) {
1749 var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + i);
1750 return dims.w + (2 * pie.options.tooltips.styles.padding);
1751 })
@@ -1758,7 +1757,7 @@ var tt = {
1757 var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + i);
1758 return -(dims.h / 2) + 1;
1759 });
1761 - },
1760 + },
1761
1762 showTooltip: function(pie, index) {
1763 var fadeInSpeed = pie.options.tooltips.styles.fadeInSpeed;
@@ -1822,148 +1821,148 @@ var tt = {
1821 };
1822
1823
1825 - // --------------------------------------------------------------------------------------------
1824 + // --------------------------------------------------------------------------------------------
1825
1827 - // our constructor
1828 - var d3pie = function(element, options) {
1826 + // our constructor
1827 + var d3pie = function(element, options) {
1828
1830 - // element can be an ID or DOM element
1831 - this.element = element;
1832 - if (typeof element === "string") {
1833 - var el = element.replace(/^#/, ""); // replace any jQuery-like ID hash char
1834 - this.element = document.getElementById(el);
1835 - }
1829 + // element can be an ID or DOM element
1830 + this.element = element;
1831 + if (typeof element === "string") {
1832 + var el = element.replace(/^#/, ""); // replace any jQuery-like ID hash char
1833 + this.element = document.getElementById(el);
1834 + }
1835
1837 - var opts = {};
1838 - extend(true, opts, defaultSettings, options);
1839 - this.options = opts;
1836 + var opts = {};
1837 + extend(true, opts, defaultSettings, options);
1838 + this.options = opts;
1839
1841 - // if the user specified a custom CSS element prefix (ID, class), use it
1842 - if (this.options.misc.cssPrefix !== null) {
1843 - this.cssPrefix = this.options.misc.cssPrefix;
1844 - } else {
1845 - this.cssPrefix = "p" + _uniqueIDCounter + "_";
1846 - _uniqueIDCounter++;
1847 - }
1840 + // if the user specified a custom CSS element prefix (ID, class), use it
1841 + if (this.options.misc.cssPrefix !== null) {
1842 + this.cssPrefix = this.options.misc.cssPrefix;
1843 + } else {
1844 + this.cssPrefix = "p" + _uniqueIDCounter + "_";
1845 + _uniqueIDCounter++;
1846 + }
1847
1848
1850 - // now run some validation on the user-defined info
1851 - if (!validate.initialCheck(this)) {
1852 - return;
1853 - }
1849 + // now run some validation on the user-defined info
1850 + if (!validate.initialCheck(this)) {
1851 + return;
1852 + }
1853
1855 - // add a data-role to the DOM node to let anyone know that it contains a d3pie instance, and the d3pie version
1856 - d3.select(this.element).attr(_scriptName, _version);
1854 + // add a data-role to the DOM node to let anyone know that it contains a d3pie instance, and the d3pie version
1855 + d3.select(this.element).attr(_scriptName, _version);
1856
1858 - // things that are done once
1859 - _setupData.call(this);
1860 - _init.call(this);
1861 - };
1857 + // things that are done once
1858 + _setupData.call(this);
1859 + _init.call(this);
1860 + };
1861
1863 - d3pie.prototype.recreate = function() {
1864 - // now run some validation on the user-defined info
1865 - if (!validate.initialCheck(this)) {
1862 + d3pie.prototype.recreate = function() {
1863 + // now run some validation on the user-defined info
1864 + if (!validate.initialCheck(this)) {
1865 return;
1866 }
1867
1869 - _setupData.call(this);
1870 - _init.call(this);
1871 - };
1872 -
1873 - d3pie.prototype.redraw = function() {
1874 - this.element.innerHTML = "";
1875 - _init.call(this);
1876 - };
1877 -
1878 - d3pie.prototype.destroy = function() {
1879 - this.element.innerHTML = ""; // clear out the SVG
1880 - d3.select(this.element).attr(_scriptName, null); // remove the data attr
1881 - };
1882 -
1883 - /**
1884 - * Returns all pertinent info about the current open info. Returns null if nothing's open, or if one is, an object of
1885 - * the following form:
1886 - * {
1887 - * element: DOM NODE,
1888 - * index: N,
1889 - * data: {}
1890 - * }
1891 - */
1892 - d3pie.prototype.getOpenSegment = function() {
1893 - var segment = this.currentlyOpenSegment;
1894 - if (segment !== null && typeof segment !== "undefined") {
1895 - var index = parseInt(d3.select(segment).attr("data-index"), 10);
1896 - return {
1897 - element: segment,
1898 - index: index,
1899 - data: this.options.data.content[index]
1900 - };
1901 - } else {
1902 - return null;
1903 - }
1904 - };
1905 -
1906 - d3pie.prototype.openSegment = function(index) {
1907 - index = parseInt(index, 10);
1908 - if (index < 0 || index > this.options.data.content.length-1) {
1909 - return;
1910 - }
1911 - segments.openSegment(this, d3.select("#" + this.cssPrefix + "segment" + index).node());
1912 - };
1913 -
1914 - d3pie.prototype.closeSegment = function() {
1915 - segments.maybeCloseOpenSegment();
1916 - };
1917 -
1918 - // this let's the user dynamically update aspects of the pie chart without causing a complete redraw. It
1919 - // intelligently re-renders only the part of the pie that the user specifies. Some things cause a repaint, others
1920 - // just redraw the single element
1921 - d3pie.prototype.updateProp = function(propKey, value) {
1922 - switch (propKey) {
1923 - case "header.title.text":
1924 - var oldVal = helpers.processObj(this.options, propKey);
1925 - helpers.processObj(this.options, propKey, value);
1926 - d3.select("#" + this.cssPrefix + "title").html(value);
1927 - if ((oldVal === "" && value !== "") || (oldVal !== "" && value === "")) {
1928 - this.redraw();
1929 - }
1930 - break;
1931 -
1932 - case "header.subtitle.text":
1933 - var oldValue = helpers.processObj(this.options, propKey);
1934 - helpers.processObj(this.options, propKey, value);
1935 - d3.select("#" + this.cssPrefix + "subtitle").html(value);
1936 - if ((oldValue === "" && value !== "") || (oldValue !== "" && value === "")) {
1937 - this.redraw();
1938 - }
1939 - break;
1940 -
1941 - case "callbacks.onload":
1942 - case "callbacks.onMouseoverSegment":
1943 - case "callbacks.onMouseoutSegment":
1944 - case "callbacks.onClickSegment":
1945 - case "effects.pullOutSegmentOnClick.effect":
1946 - case "effects.pullOutSegmentOnClick.speed":
1947 - case "effects.pullOutSegmentOnClick.size":
1948 - case "effects.highlightSegmentOnMouseover":
1949 - case "effects.highlightLuminosity":
1950 - helpers.processObj(this.options, propKey, value);
1951 - break;
1952 -
1953 - // everything else, attempt to update it & do a repaint
1954 - default:
1955 - helpers.processObj(this.options, propKey, value);
1956 -
1957 - this.destroy();
1958 - this.recreate();
1959 - break;
1960 - }
1961 - };
1962 -
1963 -
1964 - // ------------------------------------------------------------------------------------------------
1965 -
1966 - var _setupData = function () {
1868 + _setupData.call(this);
1869 + _init.call(this);
1870 + };
1871 +
1872 + d3pie.prototype.redraw = function() {
1873 + this.element.innerHTML = "";
1874 + _init.call(this);
1875 + };
1876 +
1877 + d3pie.prototype.destroy = function() {
1878 + this.element.innerHTML = ""; // clear out the SVG
1879 + d3.select(this.element).attr(_scriptName, null); // remove the data attr
1880 + };
1881 +
1882 + /**
1883 + * Returns all pertinent info about the current open info. Returns null if nothing's open, or if one is, an object of
1884 + * the following form:
1885 + * {
1886 + * element: DOM NODE,
1887 + * index: N,
1888 + * data: {}
1889 + * }
1890 + */
1891 + d3pie.prototype.getOpenSegment = function() {
1892 + var segment = this.currentlyOpenSegment;
1893 + if (segment !== null && typeof segment !== "undefined") {
1894 + var index = parseInt(d3.select(segment).attr("data-index"), 10);
1895 + return {
1896 + element: segment,
1897 + index: index,
1898 + data: this.options.data.content[index]
1899 + };
1900 + } else {
1901 + return null;
1902 + }
1903 + };
1904 +
1905 + d3pie.prototype.openSegment = function(index) {
1906 + index = parseInt(index, 10);
1907 + if (index < 0 || index > this.options.data.content.length-1) {
1908 + return;
1909 + }
1910 + segments.openSegment(this, d3.select("#" + this.cssPrefix + "segment" + index).node());
1911 + };
1912 +
1913 + d3pie.prototype.closeSegment = function() {
1914 + segments.maybeCloseOpenSegment(this);
1915 + };
1916 +
1917 + // this let's the user dynamically update aspects of the pie chart without causing a complete redraw. It
1918 + // intelligently re-renders only the part of the pie that the user specifies. Some things cause a repaint, others
1919 + // just redraw the single element
1920 + d3pie.prototype.updateProp = function(propKey, value) {
1921 + switch (propKey) {
1922 + case "header.title.text":
1923 + var oldVal = helpers.processObj(this.options, propKey);
1924 + helpers.processObj(this.options, propKey, value);
1925 + d3.select("#" + this.cssPrefix + "title").html(value);
1926 + if ((oldVal === "" && value !== "") || (oldVal !== "" && value === "")) {
1927 + this.redraw();
1928 + }
1929 + break;
1930 +
1931 + case "header.subtitle.text":
1932 + var oldValue = helpers.processObj(this.options, propKey);
1933 + helpers.processObj(this.options, propKey, value);
1934 + d3.select("#" + this.cssPrefix + "subtitle").html(value);
1935 + if ((oldValue === "" && value !== "") || (oldValue !== "" && value === "")) {
1936 + this.redraw();
1937 + }
1938 + break;
1939 +
1940 + case "callbacks.onload":
1941 + case "callbacks.onMouseoverSegment":
1942 + case "callbacks.onMouseoutSegment":
1943 + case "callbacks.onClickSegment":
1944 + case "effects.pullOutSegmentOnClick.effect":
1945 + case "effects.pullOutSegmentOnClick.speed":
1946 + case "effects.pullOutSegmentOnClick.size":
1947 + case "effects.highlightSegmentOnMouseover":
1948 + case "effects.highlightLuminosity":
1949 + helpers.processObj(this.options, propKey, value);
1950 + break;
1951 +
1952 + // everything else, attempt to update it & do a repaint
1953 + default:
1954 + helpers.processObj(this.options, propKey, value);
1955 +
1956 + this.destroy();
1957 + this.recreate();
1958 + break;
1959 + }
1960 + };
1961 +
1962 +
1963 + // ------------------------------------------------------------------------------------------------
1964 +
1965 + var _setupData = function () {
1966 this.options.data.content = math.sortPieData(this);
1967 if (this.options.data.smallSegmentGrouping.enabled) {
1968 this.options.data.content = helpers.applySmallSegmentGrouping(this.options.data.content, this.options.data.smallSegmentGrouping);
@@ -1981,142 +1980,144 @@ var tt = {
1980 }
1981
1982 // adjust the final item to ensure the percentage always adds up to precisely 100%. This is necessary
1984 - var totalPercentage = 0;
1983 + var totalPercentage = 0;
1984 for (var j=0; j<this.options.data.content.length; j++) {
1986 - if (j === this.options.data.content.length - 1) {
1985 + if (j === this.options.data.content.length - 1) {
1986 this.options.data.content[j].percentage = (100 - totalPercentage).toFixed(dp);
1988 - }
1989 - totalPercentage += parseFloat(this.options.data.content[j].percentage);
1990 - }
1991 - };
1992 -
1993 - var _init = function() {
1994 -
1995 - // prep-work
1996 - this.svg = helpers.addSVGSpace(this);
1997 -
1998 - // store info about the main text components as part of the d3pie object instance
1999 - this.textComponents = {
2000 - headerHeight: 0,
2001 - title: {
2002 - exists: this.options.header.title.text !== "",
2003 - h: 0,
2004 - w: 0
2005 - },
2006 - subtitle: {
2007 - exists: this.options.header.subtitle.text !== "",
2008 - h: 0,
2009 - w: 0
2010 - },
2011 - footer: {
2012 - exists: this.options.footer.text !== "",
2013 - h: 0,
2014 - w: 0
2015 - }
2016 - };
2017 -
2018 - this.outerLabelGroupData = [];
2019 -
2020 - // add the key text components offscreen (title, subtitle, footer). We need to know their widths/heights for later computation
2021 - if (this.textComponents.title.exists) text.addTitle(this);
2022 - if (this.textComponents.subtitle.exists) text.addSubtitle(this);
2023 - text.addFooter(this);
2024 -
2025 - // the footer never moves. Put it in place now
2026 - var self = this;
2027 - text.positionFooter(self);
2028 - var d3 = helpers.getDimensions(self.cssPrefix + "footer");
2029 - self.textComponents.footer.h = d3.h;
2030 - self.textComponents.footer.w = d3.w;
2031 -
2032 - if (self.textComponents.title.exists) {
2033 - var d1 = helpers.getDimensions(self.cssPrefix + "title");
2034 - self.textComponents.title.h = d1.h;
2035 - self.textComponents.title.w = d1.w;
2036 - }
2037 -
2038 - if (self.textComponents.subtitle.exists) {
2039 - var d2 = helpers.getDimensions(self.cssPrefix + "subtitle");
2040 - self.textComponents.subtitle.h = d2.h;
2041 - self.textComponents.subtitle.w = d2.w;
2042 - }
2043 -
2044 - // now compute the full header height
2045 - if (self.textComponents.title.exists || self.textComponents.subtitle.exists) {
2046 - var headerHeight = 0;
2047 - if (self.textComponents.title.exists) {
2048 - headerHeight += self.textComponents.title.h;
2049 - if (self.textComponents.subtitle.exists) {
2050 - headerHeight += self.options.header.titleSubtitlePadding;
2051 - }
2052 - }
2053 - if (self.textComponents.subtitle.exists) {
2054 - headerHeight += self.textComponents.subtitle.h;
2055 - }
2056 - self.textComponents.headerHeight = headerHeight;
2057 - }
2058 -
2059 - // at this point, all main text component dimensions have been calculated
2060 - math.computePieRadius(self);
2061 -
2062 - // this value is used all over the place for placing things and calculating locations. We figure it out ONCE
2063 - // and store it as part of the object
2064 - math.calculatePieCenter(self);
2065 -
2066 - // position the title and subtitle
2067 - text.positionTitle(self);
2068 - text.positionSubtitle(self);
2069 -
2070 - // now create the pie chart segments, and gradients if the user desired
2071 - if (self.options.misc.gradient.enabled) {
2072 - segments.addGradients(self);
2073 - }
2074 - segments.create(self); // also creates this.arc
2075 -
2076 - self.__labels = {};
2077 - labels.add(self, "inner", self.options.labels.inner.format);
2078 - labels.add(self, "outer", self.options.labels.outer.format);
2079 -
2080 - // position the label elements relatively within their individual group (label, percentage, value)
2081 - labels.positionLabelElements(self, "inner", self.options.labels.inner.format);
2082 - labels.positionLabelElements(self, "outer", self.options.labels.outer.format);
2083 - labels.computeOuterLabelCoords(self);
2084 -
2085 - // this is (and should be) dumb. It just places the outer groups at their calculated, collision-free positions
2086 - labels.positionLabelGroups(self, "outer");
2087 -
2088 - // we use the label line positions for many other calculations, so ALWAYS compute them
2089 - labels.computeLabelLinePositions(self);
2090 -
2091 - // only add them if they're actually enabled
2092 - if (self.options.labels.lines.enabled && self.options.labels.outer.format !== "none") {
2093 - labels.addLabelLines(self);
2094 - }
2095 -
2096 - labels.positionLabelGroups(self, "inner");
2097 -
2098 - if (helpers.isFunction(self.options.callbacks.onload)) {
2099 - try {
2100 - self.options.callbacks.onload();
2101 - } catch (e) { }
2102 - }
2103 -
2104 - // add and position the tooltips
2105 - if (self.options.tooltips.enabled) {
2106 - tt.addTooltips(self);
2107 - }
2108 -
2109 - segments.addSegmentEventHandlers(self);
2110 - };
2111 -
2112 - var _getPercentage = function(value, total, decimalPlaces) {
2113 - var relativeAmount = value / total;
2114 - if (decimalPlaces <= 0) {
2115 - return Math.round(relativeAmount * 100);
2116 - } else {
2117 - return (relativeAmount * 100).toFixed(decimalPlaces);
2118 - }
2119 - };
1987 + }
1988 + totalPercentage += parseFloat(this.options.data.content[j].percentage);
1989 + }
1990 + };
1991 +
1992 + var _init = function() {
1993 +
1994 + // prep-work
1995 + this.svg = helpers.addSVGSpace(this);
1996 +
1997 + // store info about the main text components as part of the d3pie object instance
1998 + this.textComponents = {
1999 + headerHeight: 0,
2000 + title: {
2001 + exists: this.options.header.title.text !== "",
2002 + h: 0,
2003 + w: 0
2004 + },
2005 + subtitle: {
2006 + exists: this.options.header.subtitle.text !== "",
2007 + h: 0,
2008 + w: 0
2009 + },
2010 + footer: {
2011 + exists: this.options.footer.text !== "",
2012 + h: 0,
2013 + w: 0
2014 + }
2015 + };
2016 +
2017 + this.outerLabelGroupData = [];
2018 +
2019 + // add the key text components offscreen (title, subtitle, footer). We need to know their widths/heights for later computation
2020 + if (this.textComponents.title.exists) text.addTitle(this);
2021 + if (this.textComponents.subtitle.exists) text.addSubtitle(this);
2022 + text.addFooter(this);
2023 +
2024 + // console.log(this);
2025 +
2026 + // the footer never moves. Put it in place now
2027 + var self = this;
2028 + text.positionFooter(self);
2029 + var d3 = helpers.getDimensions(self.__footer.node());
2030 + self.textComponents.footer.h = d3.h;
2031 + self.textComponents.footer.w = d3.w;
2032 +
2033 + if (self.textComponents.title.exists) {
2034 + var d1 = helpers.getDimensions(self.__title.node());
2035 + self.textComponents.title.h = d1.h;
2036 + self.textComponents.title.w = d1.w;
2037 + }
2038 +
2039 + if (self.textComponents.subtitle.exists) {
2040 + var d2 = helpers.getDimensions(self.__subtitle.node());
2041 + self.textComponents.subtitle.h = d2.h;
2042 + self.textComponents.subtitle.w = d2.w;
2043 + }
2044 +
2045 + // now compute the full header height
2046 + if (self.textComponents.title.exists || self.textComponents.subtitle.exists) {
2047 + var headerHeight = 0;
2048 + if (self.textComponents.title.exists) {
2049 + headerHeight += self.textComponents.title.h;
2050 + if (self.textComponents.subtitle.exists) {
2051 + headerHeight += self.options.header.titleSubtitlePadding;
2052 + }
2053 + }
2054 + if (self.textComponents.subtitle.exists) {
2055 + headerHeight += self.textComponents.subtitle.h;
2056 + }
2057 + self.textComponents.headerHeight = headerHeight;
2058 + }
2059 +
2060 + // at this point, all main text component dimensions have been calculated
2061 + math.computePieRadius(self);
2062 +
2063 + // this value is used all over the place for placing things and calculating locations. We figure it out ONCE
2064 + // and store it as part of the object
2065 + math.calculatePieCenter(self);
2066 +
2067 + // position the title and subtitle
2068 + text.positionTitle(self);
2069 + text.positionSubtitle(self);
2070 +
2071 + // now create the pie chart segments, and gradients if the user desired
2072 + if (self.options.misc.gradient.enabled) {
2073 + segments.addGradients(self);
2074 + }
2075 + segments.create(self); // also creates this.arc
2076 +
2077 + self.__labels = {};
2078 + labels.add(self, "inner", self.options.labels.inner.format);
2079 + labels.add(self, "outer", self.options.labels.outer.format);
2080 +
2081 + // position the label elements relatively within their individual group (label, percentage, value)
2082 + labels.positionLabelElements(self, "inner", self.options.labels.inner.format);
2083 + labels.positionLabelElements(self, "outer", self.options.labels.outer.format);
2084 + labels.computeOuterLabelCoords(self);
2085 +
2086 + // this is (and should be) dumb. It just places the outer groups at their calculated, collision-free positions
2087 + labels.positionLabelGroups(self, "outer");
2088 +
2089 + // we use the label line positions for many other calculations, so ALWAYS compute them
2090 + labels.computeLabelLinePositions(self);
2091 +
2092 + // only add them if they're actually enabled
2093 + if (self.options.labels.lines.enabled && self.options.labels.outer.format !== "none") {
2094 + labels.addLabelLines(self);
2095 + }
2096 +
2097 + labels.positionLabelGroups(self, "inner");
2098 +
2099 + if (helpers.isFunction(self.options.callbacks.onload)) {
2100 + try {
2101 + self.options.callbacks.onload();
2102 + } catch (e) { }
2103 + }
2104 +
2105 + // add and position the tooltips
2106 + if (self.options.tooltips.enabled) {
2107 + tt.addTooltips(self);
2108 + }
2109 +
2110 + segments.addSegmentEventHandlers(self);
2111 + };
2112 +
2113 + var _getPercentage = function(value, total, decimalPlaces) {
2114 + var relativeAmount = value / total;
2115 + if (decimalPlaces <= 0) {
2116 + return Math.round(relativeAmount * 100);
2117 + } else {
2118 + return (relativeAmount * 100).toFixed(decimalPlaces);
2119 + }
2120 + };
2121
2122 return d3pie;
2123 }));