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
}));