flashgrid-ext 1.1.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,1751 +1,1751 @@
1
1
  window.Chart = function(context, options){
2
- var chart = this;
3
-
4
- var animationOptions = {
5
- linear : function (t){
6
- return t;
7
- },
8
- easeInQuad: function (t) {
9
- return t*t;
10
- },
11
- easeOutQuad: function (t) {
12
- return -1 *t*(t-2);
13
- },
14
- easeInOutQuad: function (t) {
15
- if ((t/=1/2) < 1) return 1/2*t*t;
16
- return -1/2 * ((--t)*(t-2) - 1);
17
- },
18
- easeInCubic: function (t) {
19
- return t*t*t;
20
- },
21
- easeOutCubic: function (t) {
22
- return 1*((t=t/1-1)*t*t + 1);
23
- },
24
- easeInOutCubic: function (t) {
25
- if ((t/=1/2) < 1) return 1/2*t*t*t;
26
- return 1/2*((t-=2)*t*t + 2);
27
- },
28
- easeInQuart: function (t) {
29
- return t*t*t*t;
30
- },
31
- easeOutQuart: function (t) {
32
- return -1 * ((t=t/1-1)*t*t*t - 1);
33
- },
34
- easeInOutQuart: function (t) {
35
- if ((t/=1/2) < 1) return 1/2*t*t*t*t;
36
- return -1/2 * ((t-=2)*t*t*t - 2);
37
- },
38
- easeInQuint: function (t) {
39
- return 1*(t/=1)*t*t*t*t;
40
- },
41
- easeOutQuint: function (t) {
42
- return 1*((t=t/1-1)*t*t*t*t + 1);
43
- },
44
- easeInOutQuint: function (t) {
45
- if ((t/=1/2) < 1) return 1/2*t*t*t*t*t;
46
- return 1/2*((t-=2)*t*t*t*t + 2);
47
- },
48
- easeInSine: function (t) {
49
- return -1 * Math.cos(t/1 * (Math.PI/2)) + 1;
50
- },
51
- easeOutSine: function (t) {
52
- return 1 * Math.sin(t/1 * (Math.PI/2));
53
- },
54
- easeInOutSine: function (t) {
55
- return -1/2 * (Math.cos(Math.PI*t/1) - 1);
56
- },
57
- easeInExpo: function (t) {
58
- return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1));
59
- },
60
- easeOutExpo: function (t) {
61
- return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1);
62
- },
63
- easeInOutExpo: function (t) {
64
- if (t==0) return 0;
65
- if (t==1) return 1;
66
- if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1));
67
- return 1/2 * (-Math.pow(2, -10 * --t) + 2);
68
- },
69
- easeInCirc: function (t) {
70
- if (t>=1) return t;
71
- return -1 * (Math.sqrt(1 - (t/=1)*t) - 1);
72
- },
73
- easeOutCirc: function (t) {
74
- return 1 * Math.sqrt(1 - (t=t/1-1)*t);
75
- },
76
- easeInOutCirc: function (t) {
77
- if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1);
78
- return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1);
79
- },
80
- easeInElastic: function (t) {
81
- var s=1.70158;var p=0;var a=1;
82
- if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
83
- if (a < Math.abs(1)) { a=1; var s=p/4; }
84
- else var s = p/(2*Math.PI) * Math.asin (1/a);
85
- return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
86
- },
87
- easeOutElastic: function (t) {
88
- var s=1.70158;var p=0;var a=1;
89
- if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
90
- if (a < Math.abs(1)) { a=1; var s=p/4; }
91
- else var s = p/(2*Math.PI) * Math.asin (1/a);
92
- return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1;
93
- },
94
- easeInOutElastic: function (t) {
95
- var s=1.70158;var p=0;var a=1;
96
- if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5);
97
- if (a < Math.abs(1)) { a=1; var s=p/4; }
98
- else var s = p/(2*Math.PI) * Math.asin (1/a);
99
- if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
100
- return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1;
101
- },
102
- easeInBack: function (t) {
103
- var s = 1.70158;
104
- return 1*(t/=1)*t*((s+1)*t - s);
105
- },
106
- easeOutBack: function (t) {
107
- var s = 1.70158;
108
- return 1*((t=t/1-1)*t*((s+1)*t + s) + 1);
109
- },
110
- easeInOutBack: function (t) {
111
- var s = 1.70158;
112
- if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s));
113
- return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2);
114
- },
115
- easeInBounce: function (t) {
116
- return 1 - animationOptions.easeOutBounce (1-t);
117
- },
118
- easeOutBounce: function (t) {
119
- if ((t/=1) < (1/2.75)) {
120
- return 1*(7.5625*t*t);
121
- } else if (t < (2/2.75)) {
122
- return 1*(7.5625*(t-=(1.5/2.75))*t + .75);
123
- } else if (t < (2.5/2.75)) {
124
- return 1*(7.5625*(t-=(2.25/2.75))*t + .9375);
125
- } else {
126
- return 1*(7.5625*(t-=(2.625/2.75))*t + .984375);
127
- }
128
- },
129
- easeInOutBounce: function (t) {
130
- if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5;
131
- return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5;
132
- }
133
- };
134
-
135
- this.tooltips = [],
136
- defaults = {
137
- tooltips: {
138
- background: 'rgba(71,74,84,1)',
139
- fontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
140
- fontStyle : "bold",
141
- fontColor: 'rgba(255,255,255,1)',
142
- fontSize: '11px',
143
- labelTemplate: '<%=label%>: <%=value%>',
144
- padding: {
145
- top: 10,
146
- right: 5,
147
- bottom: 12,
148
- left: 5
149
- },
150
- offset: {
151
- left: 15,
152
- top: 0
153
- },
154
- border: {
155
- radius: 3
156
- },
157
- showHighlight: true,
158
- highlight: {
159
- stroke: {
160
- width: 1,
161
- color: 'rgba(255,255,255,0.25)'
162
- },
163
- fill: 'rgba(255,255,255,0.25)'
164
- }
165
- }
166
- },
167
- options = (options) ? mergeChartConfig(defaults, options) : defaults;
168
-
169
- function registerTooltip(ctx,areaObj,data,type) {
170
- chart.tooltips.push(new Tooltip(
171
- ctx,
172
- areaObj,
173
- data,
174
- type
175
- ));
176
- }
177
-
178
- var Tooltip = function(ctx, areaObj, data, type) {
179
- this.ctx = ctx;
180
- this.areaObj = areaObj;
181
- this.data = data;
182
- this.savedState = null;
183
- this.highlightState = null;
184
- this.x = null;
185
- this.y = null;
186
-
187
- this.inRange = function(x,y) {
188
- if(this.areaObj.type) {
189
- switch(this.areaObj.type) {
190
- case 'rect':
191
- return (x >= this.areaObj.x && x <= this.areaObj.x+this.areaObj.width) &&
192
- (y >= this.areaObj.y && y <= this.areaObj.y+this.areaObj.height);
193
- break;
194
- case 'circle':
195
- return ((Math.pow(x-this.areaObj.x, 2)+Math.pow(y-this.areaObj.y, 2)) < Math.pow(this.areaObj.r,2));
196
- break;
197
- case 'shape':
198
- var poly = this.areaObj.points;
199
- for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
200
- ((poly[i].y <= y && y < poly[j].y) || (poly[j].y <= y && y < poly[i].y))
201
- && (x < (poly[j].x - poly[i].x) * (y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
202
- && (c = !c);
203
- return c;
204
- break;
205
- }
206
- }
207
- }
208
-
209
- this.render = function(x,y) {
210
- if(this.savedState == null) {
211
- this.ctx.putImageData(chart.savedState,0,0);
212
- this.savedState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height);
213
- }
214
- this.ctx.putImageData(this.savedState,0,0);
215
- if(options.tooltips.showHighlight) {
216
- if(this.highlightState == null) {
217
- this.ctx.strokeStyle = options.tooltips.highlight.stroke.color;
218
- this.ctx.lineWidth = options.tooltips.highlight.stroke.width;
219
- this.ctx.fillStyle = options.tooltips.highlight.fill;
220
- switch(this.areaObj.type) {
221
- case 'rect':
222
- this.ctx.strokeRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height);
223
- this.ctx.fillStyle = options.tooltips.highlight.fill;
224
- this.ctx.fillRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height);
225
- break;
226
- case 'circle':
227
- this.ctx.beginPath();
228
- this.ctx.arc(this.areaObj.x, this.areaObj.y, this.areaObj.r, 0, 2*Math.PI, false);
229
- this.ctx.stroke();
230
- this.ctx.fill();
231
- break;
232
- case 'shape':
233
- this.ctx.beginPath();
234
- this.ctx.moveTo(this.areaObj.points[0].x, this.areaObj.points[0].y);
235
- for(var p in this.areaObj.points) {
236
- this.ctx.lineTo(this.areaObj.points[p].x, this.areaObj.points[p].y);
237
- }
238
- this.ctx.stroke();
239
- this.ctx.fill();
240
- break;
241
- }
242
- this.highlightState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height);
243
- } else {
244
- this.ctx.putImageData(this.highlightState,0,0);
245
- }
246
- }
247
- //if(this.x != x || this.y != y) {
248
- var posX = x+options.tooltips.offset.left,
249
- posY = y+options.tooltips.offset.top,
250
- tpl = tmpl(options.tooltips.labelTemplate, this.data),
251
- rectWidth = options.tooltips.padding.left+this.ctx.measureText(tpl).width+options.tooltips.padding.right;
252
- if(posX + rectWidth > this.ctx.canvas.width) {
253
- posX -= posX-rectWidth < 0 ? posX : rectWidth;
254
- }
255
- if(posY + 24 > this.ctx.canvas.height) {
256
- posY -= 24;
257
- }
258
- this.ctx.fillStyle = options.tooltips.background;
259
- this.ctx.fillRect(posX, posY, rectWidth, 24);
260
- if(options.tooltips.border.width > 0) {
261
- this.ctx.fillStyle = options.tooltips.order.color;
262
- this.ctx.lineWidth = options.tooltips.border.width;
263
- this.ctx.strokeRect(posX, posY, rectWidth, 24);
264
- }
265
- this.ctx.font = options.tooltips.fontStyle+ " "+options.tooltips.fontSize+" " + options.tooltips.fontFamily;
266
- this.ctx.fillStyle = options.tooltips.fontColor;
267
- this.ctx.textAlign = 'center';
268
- this.ctx.textBaseline = 'middle';
269
- this.ctx.fillText(tpl, posX+rectWidth/2, posY+12);
270
- this.x = x;
271
- this.y = y;
272
- //}
273
- }
274
- }
275
-
276
- //Variables global to the chart
277
- var width = context.canvas.width,
278
- height = context.canvas.height;
279
-
280
- this.savedState = null;
281
-
282
- function getPosition(e) {
283
- var xPosition = 0;
284
- var yPosition = 0;
285
-
286
- while(e) {
287
- xPosition += (e.offsetLeft - e.scrollLeft + e.clientLeft);
288
- yPosition += (e.offsetTop - e.scrollTop + e.clientTop);
289
- e = e.offsetParent;
290
- }
291
- return { x: xPosition, y: yPosition };
292
- }
293
-
294
- function tooltipEventHandler(e) {
295
- if(chart.tooltips.length > 0) {
296
- chart.savedState = chart.savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : chart.savedState;
297
- var rendered = 0;
298
- for(var i in chart.tooltips) {
299
- var position = getPosition(context.canvas),
300
- mx = (e.clientX)-position.x,
301
- my = (e.clientY)-position.y;
302
- if(chart.tooltips[i].inRange(mx,my)) {
303
- chart.tooltips[i].render(mx,my);
304
- rendered++;
305
- }
306
- }
307
- if(rendered == 0) {
308
- context.putImageData(chart.savedState,0,0);
309
- }
310
- }
311
- }
312
-
313
- if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
314
- context.canvas.ontouchstart = function(e) {
315
- e.clientX = e.targetTouches[0].clientX;
316
- e.clientY = e.targetTouches[0].clientY;
317
- tooltipEventHandler(e);
318
- }
319
- context.canvas.ontouchmove = function(e) {
320
- e.clientX = e.targetTouches[0].clientX;
321
- e.clientY = e.targetTouches[0].clientY;
322
- tooltipEventHandler(e);
323
- }
324
- } else {
325
- context.canvas.onmousemove = function(e) {
326
- tooltipEventHandler(e);
327
- }
328
- }
329
- context.canvas.onmouseout = function(e) {
330
- if(chart.savedState != null) {
331
- context.putImageData(chart.savedState,0,0);
332
- }
333
- }
334
-
335
-
336
- //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
337
- if (window.devicePixelRatio) {
338
- context.canvas.style.width = width + "px";
339
- context.canvas.style.height = height + "px";
340
- context.canvas.height = height * window.devicePixelRatio;
341
- context.canvas.width = width * window.devicePixelRatio;
342
- context.scale(window.devicePixelRatio, window.devicePixelRatio);
343
- }
344
-
345
- this.PolarArea = function(data,options){
346
-
347
- chart.PolarArea.defaults = {
348
- scaleOverlay : true,
349
- scaleOverride : false,
350
- scaleSteps : null,
351
- scaleStepWidth : null,
352
- scaleStartValue : null,
353
- scaleShowLine : true,
354
- scaleLineColor : "rgba(0,0,0,0.1)",
355
- scaleLineWidth : 1,
356
- scaleShowLabels : true,
357
- scaleLabel : "<%=value%>",
358
- scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
359
- scaleFontSize : 12,
360
- scaleFontStyle : "normal",
361
- scaleFontColor : "rgba(71,74,84,1)",
362
- scaleShowLabelBackdrop : true,
363
- scaleBackdropColor : "rgba(255,255,255,0.75)",
364
- scaleBackdropPaddingY : 2,
365
- scaleBackdropPaddingX : 2,
366
- segmentShowStroke : true,
367
- segmentStrokeColor : "#fff",
368
- segmentStrokeWidth : 1,
369
- animation : true,
370
- animationSteps : 100,
371
- animationEasing : "easeOutBounce",
372
- animateRotate : true,
373
- animateScale : false,
374
- onAnimationComplete : null,
375
- showTooltips : true
376
- };
377
-
378
- var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults;
379
-
380
- return new PolarArea(data,config,context);
381
- };
382
-
383
- this.Radar = function(data,options){
384
-
385
- chart.Radar.defaults = {
386
- scaleOverlay : false,
387
- scaleOverride : false,
388
- scaleSteps : null,
389
- scaleStepWidth : null,
390
- scaleStartValue : null,
391
- scaleShowLine : true,
392
- scaleLineColor : "rgba(0,0,0,0.1)",
393
- scaleLineWidth : 1,
394
- scaleShowLabels : false,
395
- scaleLabel : "<%=value%>",
396
- scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
397
- scaleFontSize : 12,
398
- scaleFontStyle : "normal",
399
- scaleFontColor : "rgba(71,74,84,1)",
400
- scaleShowLabelBackdrop : true,
401
- scaleBackdropColor : "rgba(255,255,255,0.75)",
402
- scaleBackdropPaddingY : 2,
403
- scaleBackdropPaddingX : 2,
404
- angleShowLineOut : true,
405
- angleLineColor : "rgba(0,0,0,0.1)",
406
- angleLineWidth : 1,
407
- pointLabelFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
408
- pointLabelFontStyle : "normal",
409
- pointLabelFontSize : 12,
410
- pointLabelFontColor : "rgba(71,74,84,1)",
411
- pointDot : true,
412
- pointDotRadius : 3,
413
- pointDotStrokeWidth : 1,
414
- datasetStroke : true,
415
- datasetStrokeWidth : 1,
416
- datasetFill : true,
417
- animation : true,
418
- animationSteps : 60,
419
- animationEasing : "easeOutQuart",
420
- onAnimationComplete : null,
421
- showTooltips : true
422
- };
423
-
424
- var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults;
425
-
426
- return new Radar(data,config,context);
427
- };
428
-
429
- this.Pie = function(data,options){
430
- chart.Pie.defaults = {
431
- segmentShowStroke : true,
432
- segmentStrokeColor : "#fff",
433
- segmentStrokeWidth : 1,
434
- animation : true,
435
- animationSteps : 100,
436
- animationEasing : "easeOutBounce",
437
- animateRotate : true,
438
- animateScale : false,
439
- onAnimationComplete : null,
440
- labelFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
441
- labelFontStyle : "normal",
442
- labelFontSize : 12,
443
- labelFontColor : "rgba(71,74,84,1)",
444
- labelAlign : 'right',
445
- showTooltips : true
446
- };
447
-
448
- var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults;
449
-
450
- return new Pie(data,config,context);
451
- };
452
-
453
- this.Doughnut = function(data,options){
454
-
455
- chart.Doughnut.defaults = {
456
- segmentShowStroke : true,
457
- segmentStrokeColor : "#fff",
458
- segmentStrokeWidth : 1,
459
- percentageInnerCutout : 50,
460
- animation : true,
461
- animationSteps : 100,
462
- animationEasing : "easeOutBounce",
463
- animateRotate : true,
464
- animateScale : false,
465
- onAnimationComplete : null,
466
- showTooltips : true
467
- };
468
-
469
- var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults;
470
-
471
- return new Doughnut(data,config,context);
472
-
473
- };
474
-
475
- this.Line = function(data,options){
476
-
477
- chart.Line.defaults = {
478
- scaleOverlay : false,
479
- scaleOverride : false,
480
- scaleSteps : null,
481
- scaleStepWidth : null,
482
- scaleStartValue : null,
483
- scaleLineColor : "rgba(0,0,0,0.1)",
484
- scaleLineWidth : 1,
485
- scaleShowLabels : true,
486
- scaleLabel : "<%=value%>",
487
- scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
488
- scaleFontSize : 12,
489
- scaleFontStyle : "normal",
490
- scaleFontColor : "rgba(71,74,84,1)",
491
- scaleShowGridLines : true,
492
- scaleGridLineColor : "rgba(0,0,0,0.05)",
493
- scaleGridLineWidth : 1,
494
- bezierCurve : true,
495
- pointDot : true,
496
- pointDotRadius : 4,
497
- pointDotStrokeWidth : 1,
498
- datasetStroke : true,
499
- datasetStrokeWidth : 1,
500
- datasetFill : true,
501
- animation : true,
502
- animationSteps : 60,
503
- animationEasing : "easeOutQuart",
504
- onAnimationComplete : null,
505
- showTooltips : true
506
- };
507
- var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults;
508
-
509
- return new Line(data,config,context);
510
- }
511
-
512
- this.Bar = function(data,options){
513
- chart.Bar.defaults = {
514
- scaleOverlay : false,
515
- scaleOverride : false,
516
- scaleSteps : null,
517
- scaleStepWidth : null,
518
- scaleStartValue : null,
519
- scaleLineColor : "rgba(0,0,0,0.1)",
520
- scaleLineWidth : 1,
521
- scaleShowLabels : true,
522
- scaleLabel : "<%=value%>",
523
- scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
524
- scaleFontSize : 12,
525
- scaleFontStyle : "normal",
526
- scaleFontColor : "rgba(71,74,84,1)",
527
- scaleShowGridLines : true,
528
- scaleGridLineColor : "rgba(0,0,0,0.05)",
529
- scaleGridLineWidth : 1,
530
- barShowStroke : true,
531
- barStrokeWidth : 1,
532
- barValueSpacing : 5,
533
- barDatasetSpacing : 2,
534
- animation : true,
535
- animationSteps : 60,
536
- animationEasing : "easeOutQuart",
537
- onAnimationComplete : null,
538
- showTooltips : true
539
- };
540
- var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults;
541
-
542
- return new Bar(data,config,context);
543
- }
544
-
545
- var clear = function(c){
546
- c.clearRect(0, 0, width, height);
547
- };
548
-
549
- var PolarArea = function(data,config,ctx){
550
- var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
551
-
552
-
553
- calculateDrawingSizes();
554
-
555
- valueBounds = getValueBounds();
556
-
557
- labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
558
-
559
- //Check and set the scale
560
- if (!config.scaleOverride){
561
-
562
- calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
563
- }
564
- else {
565
- calculatedScale = {
566
- steps : config.scaleSteps,
567
- stepValue : config.scaleStepWidth,
568
- graphMin : config.scaleStartValue,
569
- labels : []
570
- }
571
- populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
572
- }
573
-
574
- scaleHop = maxSize/(calculatedScale.steps);
575
-
576
- //Wrap in an animation loop wrapper
577
- animationLoop(config,drawScale,drawAllSegments,ctx);
578
-
579
- function calculateDrawingSizes(){
580
- maxSize = (Min([width,height])/2);
581
- //Remove whatever is larger - the font size or line width.
582
-
583
- maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]);
584
-
585
- labelHeight = config.scaleFontSize*2;
586
- //If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region.
587
- if (config.scaleShowLabelBackdrop){
588
- labelHeight += (2 * config.scaleBackdropPaddingY);
589
- maxSize -= config.scaleBackdropPaddingY*1.5;
590
- }
591
-
592
- scaleHeight = maxSize;
593
- //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
594
- labelHeight = Default(labelHeight,5);
595
- }
596
- function drawScale(){
597
- for (var i=0; i<calculatedScale.steps; i++){
598
- //If the line object is there
599
- if (config.scaleShowLine){
600
- ctx.beginPath();
601
- ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true);
602
- ctx.strokeStyle = config.scaleLineColor;
603
- ctx.lineWidth = config.scaleLineWidth;
604
- ctx.stroke();
605
- }
606
-
607
- if (config.scaleShowLabels){
608
- ctx.textAlign = "center";
609
- ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
610
- var label = calculatedScale.labels[i];
611
- //If the backdrop object is within the font object
612
- if (config.scaleShowLabelBackdrop){
613
- var textWidth = ctx.measureText(label).width;
614
- ctx.fillStyle = config.scaleBackdropColor;
615
- ctx.beginPath();
616
- ctx.rect(
617
- Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX), //X
618
- Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
619
- Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
620
- Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
621
- );
622
- ctx.fill();
623
- }
624
- ctx.textBaseline = "middle";
625
- ctx.fillStyle = config.scaleFontColor;
626
- ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1)));
627
- }
628
- }
629
- }
630
- function drawAllSegments(animationDecimal){
631
- var startAngle = -Math.PI/2,
632
- angleStep = (Math.PI*2)/data.length,
633
- scaleAnimation = 1,
634
- rotateAnimation = 1;
635
- if (config.animation) {
636
- if (config.animateScale) {
637
- scaleAnimation = animationDecimal;
638
- }
639
- if (config.animateRotate){
640
- rotateAnimation = animationDecimal;
641
- }
642
- }
643
-
644
- for (var i=0; i<data.length; i++){
645
-
646
- ctx.beginPath();
647
- ctx.arc(width/2,height/2,scaleAnimation * calculateOffset(data[i].value,calculatedScale,scaleHop),startAngle, startAngle + rotateAnimation*angleStep, false);
648
- ctx.lineTo(width/2,height/2);
649
- ctx.closePath();
650
- ctx.fillStyle = data[i].color;
651
- ctx.fill();
652
-
653
- if(animationDecimal >= 1 && config.showTooltips) {
654
- var points = [{x:width/2,y:height/2}],
655
- pAmount = 50,
656
- radius = calculateOffset(data[i].value,calculatedScale,scaleHop);
657
- points.push({x:width/2+radius*Math.cos(startAngle),y:height/2+radius*Math.sin(startAngle)});
658
- for(var p = 0; p <= pAmount; p++) {
659
- points.push({x:width/2+radius*Math.cos(startAngle+p/pAmount*rotateAnimation*angleStep),y:height/2+radius*Math.sin(startAngle+p/pAmount*rotateAnimation*angleStep)});
660
- }
661
- registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'PolarArea');
662
- }
663
-
664
- if(config.segmentShowStroke){
665
- ctx.strokeStyle = config.segmentStrokeColor;
666
- ctx.lineWidth = config.segmentStrokeWidth;
667
- ctx.stroke();
668
- }
669
- startAngle += rotateAnimation*angleStep;
670
- }
671
- }
672
- function getValueBounds() {
673
- var upperValue = Number.MIN_VALUE;
674
- var lowerValue = Number.MAX_VALUE;
675
- for (var i=0; i<data.length; i++){
676
- if (data[i].value > upperValue) {upperValue = data[i].value;}
677
- if (data[i].value < lowerValue) {lowerValue = data[i].value;}
678
- };
679
-
680
- var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
681
- var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
682
-
683
- return {
684
- maxValue : upperValue,
685
- minValue : lowerValue,
686
- maxSteps : maxSteps,
687
- minSteps : minSteps
688
- };
689
-
690
-
691
- }
692
- }
693
-
694
- var Radar = function (data,config,ctx) {
695
- var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
696
-
697
- //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up.
698
- if (!data.labels) data.labels = [];
699
-
700
- calculateDrawingSizes();
701
-
702
- var valueBounds = getValueBounds();
703
-
704
- labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
705
-
706
- //Check and set the scale
707
- if (!config.scaleOverride){
708
-
709
- calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
710
- }
711
- else {
712
- calculatedScale = {
713
- steps : config.scaleSteps,
714
- stepValue : config.scaleStepWidth,
715
- graphMin : config.scaleStartValue,
716
- labels : []
717
- }
718
- populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
719
- }
720
-
721
- scaleHop = maxSize/(calculatedScale.steps);
722
-
723
- animationLoop(config,drawScale,drawAllDataPoints,ctx);
724
-
725
- //Radar specific functions.
726
- function drawAllDataPoints(animationDecimal){
727
- var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
728
-
729
- ctx.save();
730
- //translate to the centre of the canvas.
731
- ctx.translate(width/2,height/2);
732
- //We accept multiple data sets for radar charts, so show loop through each set
733
- for (var i=0; i<data.datasets.length; i++){
734
- var offset = calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop);
735
- ctx.beginPath();
736
- ctx.moveTo(0,animationDecimal*(-1*offset));
737
- if(animationDecimal >= 1 && config.showTooltips) {
738
- var curX = width/2+offset*Math.cos(0-Math.PI/2),
739
- curY = height/2+offset*Math.sin(0-Math.PI/2),
740
- pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10,
741
- ttData = data.labels[0].trim() != "" ? data.labels[0]+": "+data.datasets[i].data[0] : data.datasets[i].data[0];
742
- registerTooltip(ctx,{type:'circle',x:curX,y:curY,r:pointRadius},{label:data.labels[0],value:data.datasets[i].data[0]},'Radar');
743
- }
744
- for (var j=1; j<data.datasets[i].data.length; j++){
745
- offset = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop);
746
- ctx.rotate(rotationDegree);
747
- ctx.lineTo(0,animationDecimal*(-1*offset));
748
- if(animationDecimal >= 1 && config.showTooltips) {
749
- var curX = width/2+offset*Math.cos(j*rotationDegree-Math.PI/2),
750
- curY = height/2+offset*Math.sin(j*rotationDegree-Math.PI/2),
751
- pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10,
752
- ttData = data.labels[j].trim() != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j];
753
- registerTooltip(ctx,{type:'circle',x:curX,y:curY,r:pointRadius},{label:data.labels[j],value:data.datasets[i].data[j]},'Radar');
754
- }
755
- }
756
- ctx.closePath();
757
-
758
-
759
- ctx.fillStyle = data.datasets[i].fillColor;
760
- ctx.strokeStyle = data.datasets[i].strokeColor;
761
- ctx.lineWidth = config.datasetStrokeWidth;
762
- ctx.fill();
763
- ctx.stroke();
764
-
765
-
766
- if (config.pointDot){
767
- ctx.fillStyle = data.datasets[i].pointColor;
768
- ctx.strokeStyle = data.datasets[i].pointStrokeColor;
769
- ctx.lineWidth = config.pointDotStrokeWidth;
770
- for (var k=0; k<data.datasets[i].data.length; k++){
771
- ctx.rotate(rotationDegree);
772
- ctx.beginPath();
773
- ctx.arc(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,2*Math.PI,false);
774
- ctx.fill();
775
- ctx.stroke();
776
- }
777
-
778
- }
779
- ctx.rotate(rotationDegree);
780
- }
781
- ctx.restore();
782
-
783
-
784
- }
785
- function drawScale(){
786
- var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
787
- ctx.save();
788
- ctx.translate(width / 2, height / 2);
789
-
790
- if (config.angleShowLineOut){
791
- ctx.strokeStyle = config.angleLineColor;
792
- ctx.lineWidth = config.angleLineWidth;
793
- for (var h=0; h<data.datasets[0].data.length; h++){
794
-
795
- ctx.rotate(rotationDegree);
796
- ctx.beginPath();
797
- ctx.moveTo(0,0);
798
- ctx.lineTo(0,-maxSize);
799
- ctx.stroke();
800
- }
801
- }
802
-
803
- for (var i=0; i<calculatedScale.steps; i++){
804
- ctx.beginPath();
805
-
806
- if(config.scaleShowLine){
807
- ctx.strokeStyle = config.scaleLineColor;
808
- ctx.lineWidth = config.scaleLineWidth;
809
- ctx.moveTo(0,-scaleHop * (i+1));
810
- for (var j=0; j<data.datasets[0].data.length; j++){
811
- ctx.rotate(rotationDegree);
812
- ctx.lineTo(0,-scaleHop * (i+1));
813
- }
814
- ctx.closePath();
815
- ctx.stroke();
816
-
817
- }
818
-
819
- if (config.scaleShowLabels){
820
- ctx.textAlign = 'center';
821
- ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
822
- ctx.textBaseline = "middle";
823
-
824
- if (config.scaleShowLabelBackdrop){
825
- var textWidth = ctx.measureText(calculatedScale.labels[i]).width;
826
- ctx.fillStyle = config.scaleBackdropColor;
827
- ctx.beginPath();
828
- ctx.rect(
829
- Math.round(- textWidth/2 - config.scaleBackdropPaddingX), //X
830
- Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
831
- Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
832
- Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
833
- );
834
- ctx.fill();
835
- }
836
- ctx.fillStyle = config.scaleFontColor;
837
- ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1));
838
- }
839
-
840
- }
841
- for (var k=0; k<data.labels.length; k++){
842
- ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
843
- ctx.fillStyle = config.pointLabelFontColor;
844
- var opposite = Math.sin(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
845
- var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
846
-
847
- if(rotationDegree*k == Math.PI || rotationDegree*k == 0){
848
- ctx.textAlign = "center";
849
- }
850
- else if(rotationDegree*k > Math.PI){
851
- ctx.textAlign = "right";
852
- }
853
- else{
854
- ctx.textAlign = "left";
855
- }
856
-
857
- ctx.textBaseline = "middle";
858
-
859
- ctx.fillText(data.labels[k],opposite,-adjacent);
860
-
861
- }
862
- ctx.restore();
863
- };
864
- function calculateDrawingSizes(){
865
- maxSize = (Min([width,height])/2);
866
-
867
- labelHeight = config.scaleFontSize*2;
868
-
869
- var labelLength = 0;
870
- for (var i=0; i<data.labels.length; i++){
871
- ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
872
- var textMeasurement = ctx.measureText(data.labels[i]).width;
873
- if(textMeasurement>labelLength) labelLength = textMeasurement;
874
- }
875
-
876
- //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size.
877
- maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]);
878
-
879
- maxSize -= config.pointLabelFontSize;
880
- maxSize = CapValue(maxSize, null, 0);
881
- scaleHeight = maxSize;
882
- //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
883
- labelHeight = Default(labelHeight,5);
884
- };
885
- function getValueBounds() {
886
- var upperValue = Number.MIN_VALUE;
887
- var lowerValue = Number.MAX_VALUE;
888
-
889
- for (var i=0; i<data.datasets.length; i++){
890
- for (var j=0; j<data.datasets[i].data.length; j++){
891
- if (data.datasets[i].data[j] > upperValue){upperValue = data.datasets[i].data[j]}
892
- if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]}
893
- }
894
- }
895
-
896
- var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
897
- var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
898
-
899
- return {
900
- maxValue : upperValue,
901
- minValue : lowerValue,
902
- maxSteps : maxSteps,
903
- minSteps : minSteps
904
- };
905
-
906
-
907
- }
908
- }
909
-
910
- var Pie = function(data,config,ctx){
911
- var segmentTotal = 0;
912
-
913
- //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
914
- var pieRadius = Min([height/2,width/2]) - 5;
915
-
916
- for (var i=0; i<data.length; i++){
917
- segmentTotal += data[i].value;
918
- }
919
- ctx.fillStyle = 'black';
920
- ctx.textBaseline = 'base';
921
-
922
- animationLoop(config,null,drawPieSegments,ctx);
923
-
924
- function drawPieSegments (animationDecimal){
925
- var cumulativeAngle = -Math.PI/2,
926
- scaleAnimation = 1,
927
- rotateAnimation = 1;
928
- if (config.animation) {
929
- if (config.animateScale) {
930
- scaleAnimation = animationDecimal;
931
- }
932
- if (config.animateRotate){
933
- rotateAnimation = animationDecimal;
934
- }
935
- }
936
-
937
- for (var i=0; i<data.length; i++){
938
- var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
939
- ctx.beginPath();
940
- ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle);
941
- ctx.lineTo(width/2,height/2);
942
- ctx.closePath();
943
- ctx.fillStyle = data[i].color;
944
- ctx.fill();
945
-
946
- if(data[i].label && scaleAnimation*pieRadius*2*segmentAngle/(2*Math.PI) > config.labelFontSize) {
947
- function getPieLabelX(align, r) {
948
- switch(align) {
949
- case 'left':
950
- return -r+20;
951
- break;
952
- case 'center':
953
- return -r/2;
954
- break;
955
- }
956
- return -10;
957
- }
958
-
959
- function reversePieLabelAlign(align) {
960
- switch(align) {
961
- case 'left': return 'right'; break;
962
- case 'right': return 'left'; break;
963
- case 'center': return align; break;
964
- }
965
- }
966
-
967
- var fontSize = data[i].labelFontSize || config.labelFontSize+'px';
968
-
969
- if(fontSize.match(/^[0-9]+$/g) != null) {
970
- fontSize = fontSize+'px';
971
- }
972
- ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily;
973
- ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color);
974
- ctx.textBaseline = 'middle';
975
- // rotate text, so it perfectly fits in segments
976
- var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2,
977
- tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation),
978
- tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation);
979
- ctx.textAlign = data[i].labelAlign || config.labelAlign;
980
- textX = getPieLabelX(ctx.textAlign, scaleAnimation*pieRadius);
981
- if(textRotation < -Math.PI/2) {
982
- textRotation -= Math.PI;
983
- ctx.textAlign = reversePieLabelAlign(ctx.textAlign);
984
- textX = -textX;
985
- }
986
- ctx.translate(tX, tY);
987
- ctx.rotate(-textRotation);
988
- ctx.fillText(data[i].label, textX, 0);
989
- ctx.rotate(textRotation);
990
- ctx.translate(-tX, -tY);
991
- }
992
-
993
- if(animationDecimal >= 1 && config.showTooltips) {
994
- var points = [{x:width/2,y:height/2}],
995
- pAmount = 50;
996
- points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle)});
997
- for(var p = 0; p <= pAmount; p++) {
998
- points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)});
999
- }
1000
- registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'Pie');
1001
- }
1002
-
1003
- if(config.segmentShowStroke){
1004
- ctx.lineWidth = config.segmentStrokeWidth;
1005
- ctx.strokeStyle = config.segmentStrokeColor;
1006
- ctx.stroke();
1007
- }
1008
- cumulativeAngle += segmentAngle;
1009
- }
1010
- }
1011
- }
1012
-
1013
- var Doughnut = function(data,config,ctx){
1014
- var segmentTotal = 0;
1015
-
1016
- //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
1017
- var doughnutRadius = Min([height/2,width/2]) - 5;
1018
-
1019
- var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100);
1020
-
1021
- for (var i=0; i<data.length; i++){
1022
- segmentTotal += data[i].value;
1023
- }
1024
-
1025
-
1026
- animationLoop(config,null,drawPieSegments,ctx);
1027
-
1028
-
1029
- function drawPieSegments (animationDecimal){
1030
- var cumulativeAngle = -Math.PI/2,
1031
- scaleAnimation = 1,
1032
- rotateAnimation = 1;
1033
- if (config.animation) {
1034
- if (config.animateScale) {
1035
- scaleAnimation = animationDecimal;
1036
- }
1037
- if (config.animateRotate){
1038
- rotateAnimation = animationDecimal;
1039
- }
1040
- }
1041
- for (var i=0; i<data.length; i++){
1042
- var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
1043
- ctx.beginPath();
1044
- ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false);
1045
- ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true);
1046
- ctx.closePath();
1047
- ctx.fillStyle = data[i].color;
1048
- ctx.fill();
1049
-
1050
- if(animationDecimal >= 1 && config.showTooltips) {
1051
- var points = [],
1052
- pAmount = 50;
1053
- points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle)});
1054
- for(var p = 0; p <= pAmount; p++) {
1055
- points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)});
1056
- }
1057
- points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+segmentAngle)});
1058
- for(var p = pAmount; p >= 0; p--) {
1059
- points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)});
1060
- }
1061
- registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'Doughnut');
1062
- }
1063
-
1064
- if(config.segmentShowStroke){
1065
- ctx.lineWidth = config.segmentStrokeWidth;
1066
- ctx.strokeStyle = config.segmentStrokeColor;
1067
- ctx.stroke();
1068
- }
1069
- cumulativeAngle += segmentAngle;
1070
- }
1071
- }
1072
-
1073
-
1074
-
1075
- }
1076
-
1077
- var Line = function(data,config,ctx){
1078
- var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0;
1079
-
1080
- calculateDrawingSizes();
1081
-
1082
- valueBounds = getValueBounds();
1083
- //Check and set the scale
1084
- labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
1085
- if (!config.scaleOverride){
1086
-
1087
- calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
1088
- }
1089
- else {
1090
- calculatedScale = {
1091
- steps : config.scaleSteps,
1092
- stepValue : config.scaleStepWidth,
1093
- graphMin : config.scaleStartValue,
1094
- labels : []
1095
- }
1096
- populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
1097
- }
1098
-
1099
- scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
1100
- calculateXAxisSize();
1101
- animationLoop(config,drawScale,drawLines,ctx);
1102
-
1103
- function drawLines(animPc){
1104
- for (var i=0; i<data.datasets.length; i++){
1105
- ctx.strokeStyle = data.datasets[i].strokeColor;
1106
- ctx.lineWidth = config.datasetStrokeWidth;
1107
- ctx.beginPath();
1108
- ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)))
1109
-
1110
- for (var j=1; j<data.datasets[i].data.length; j++){
1111
- if (config.bezierCurve){
1112
- ctx.bezierCurveTo(xPos(j-0.5),yPos(i,j-1),xPos(j-0.5),yPos(i,j),xPos(j),yPos(i,j));
1113
- }
1114
- else{
1115
- ctx.lineTo(xPos(j),yPos(i,j));
1116
- }
1117
- }
1118
- var pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10;
1119
- for(var j = 0; j < data.datasets[i].data.length; j++) {
1120
- if(animPc >= 1 && config.showTooltips) {
1121
- // register tooltips
1122
- registerTooltip(ctx,{type:'circle',x:xPos(j),y:yPos(i,j),r:pointRadius},{label:data.labels[j],value:data.datasets[i].data[j]},'Line');
1123
- }
1124
- }
1125
- ctx.stroke();
1126
- if (config.datasetFill){
1127
- ctx.lineTo(yAxisPosX + (valueHop*(data.datasets[i].data.length-1)),xAxisPosY);
1128
- ctx.lineTo(yAxisPosX,xAxisPosY);
1129
- ctx.closePath();
1130
- ctx.fillStyle = data.datasets[i].fillColor;
1131
- ctx.fill();
1132
- }
1133
- else{
1134
- ctx.closePath();
1135
- }
1136
- if(config.pointDot){
1137
- ctx.fillStyle = data.datasets[i].pointColor;
1138
- ctx.strokeStyle = data.datasets[i].pointStrokeColor;
1139
- ctx.lineWidth = config.pointDotStrokeWidth;
1140
- for (var k=0; k<data.datasets[i].data.length; k++){
1141
- ctx.beginPath();
1142
- ctx.arc(yAxisPosX + (valueHop *k),xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,0,Math.PI*2,true);
1143
- ctx.fill();
1144
- ctx.stroke();
1145
- }
1146
- }
1147
- }
1148
-
1149
- function yPos(dataSet,iteration){
1150
- return xAxisPosY - animPc*(calculateOffset(data.datasets[dataSet].data[iteration],calculatedScale,scaleHop));
1151
- }
1152
- function xPos(iteration){
1153
- return yAxisPosX + (valueHop * iteration);
1154
- }
1155
- }
1156
- function drawScale(){
1157
- //X axis line
1158
- ctx.lineWidth = config.scaleLineWidth;
1159
- ctx.strokeStyle = config.scaleLineColor;
1160
- ctx.beginPath();
1161
- ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
1162
- ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
1163
- ctx.stroke();
1164
-
1165
-
1166
- if (rotateLabels > 0){
1167
- ctx.save();
1168
- ctx.textAlign = "right";
1169
- }
1170
- else{
1171
- ctx.textAlign = "center";
1172
- }
1173
- ctx.fillStyle = config.scaleFontColor;
1174
- for (var i=0; i<data.labels.length; i++){
1175
- ctx.save();
1176
- if (rotateLabels > 0){
1177
- ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
1178
- ctx.rotate(-(rotateLabels * (Math.PI/180)));
1179
- ctx.fillText(data.labels[i], 0,0);
1180
- ctx.restore();
1181
- }
1182
-
1183
- else{
1184
- ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3);
1185
- }
1186
-
1187
- ctx.beginPath();
1188
- ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3);
1189
-
1190
- //Check i isnt 0, so we dont go over the Y axis twice.
1191
- if(config.scaleShowGridLines && i>0){
1192
- ctx.lineWidth = config.scaleGridLineWidth;
1193
- ctx.strokeStyle = config.scaleGridLineColor;
1194
- ctx.lineTo(yAxisPosX + i * valueHop, 5);
1195
- }
1196
- else{
1197
- ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3);
1198
- }
1199
- ctx.stroke();
1200
- }
1201
-
1202
- //Y axis
1203
- ctx.lineWidth = config.scaleLineWidth;
1204
- ctx.strokeStyle = config.scaleLineColor;
1205
- ctx.beginPath();
1206
- ctx.moveTo(yAxisPosX,xAxisPosY+5);
1207
- ctx.lineTo(yAxisPosX,5);
1208
- ctx.stroke();
1209
-
1210
- ctx.textAlign = "right";
1211
- ctx.textBaseline = "middle";
1212
- for (var j=0; j<calculatedScale.steps; j++){
1213
- ctx.beginPath();
1214
- ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
1215
- if (config.scaleShowGridLines){
1216
- ctx.lineWidth = config.scaleGridLineWidth;
1217
- ctx.strokeStyle = config.scaleGridLineColor;
1218
- ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));
1219
- }
1220
- else{
1221
- ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
1222
- }
1223
-
1224
- ctx.stroke();
1225
-
1226
- if (config.scaleShowLabels){
1227
- ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
1228
- }
1229
- }
1230
-
1231
-
1232
- }
1233
- function calculateXAxisSize(){
1234
- var longestText = 1;
1235
- //if we are showing the labels
1236
- if (config.scaleShowLabels){
1237
- ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1238
- for (var i=0; i<calculatedScale.labels.length; i++){
1239
- var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
1240
- longestText = (measuredText > longestText)? measuredText : longestText;
1241
- }
1242
- //Add a little extra padding from the y axis
1243
- longestText +=10;
1244
- }
1245
- xAxisLength = width - longestText - widestXLabel;
1246
- valueHop = Math.floor(xAxisLength/(data.labels.length-1));
1247
-
1248
- yAxisPosX = width-widestXLabel/2-xAxisLength;
1249
- xAxisPosY = scaleHeight + config.scaleFontSize/2;
1250
- }
1251
- function calculateDrawingSizes(){
1252
- maxSize = height;
1253
-
1254
- //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
1255
- ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1256
- widestXLabel = 1;
1257
- for (var i=0; i<data.labels.length; i++){
1258
- var textLength = ctx.measureText(data.labels[i]).width;
1259
- //If the text length is longer - make that equal to longest text!
1260
- widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
1261
- }
1262
- if (width/data.labels.length < widestXLabel){
1263
- rotateLabels = 45;
1264
- if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
1265
- rotateLabels = 90;
1266
- maxSize -= widestXLabel;
1267
- }
1268
- else{
1269
- maxSize -= Math.sin(rotateLabels) * widestXLabel;
1270
- }
1271
- }
1272
- else{
1273
- maxSize -= config.scaleFontSize;
1274
- }
1275
-
1276
- //Add a little padding between the x line and the text
1277
- maxSize -= 5;
1278
-
1279
-
1280
- labelHeight = config.scaleFontSize;
1281
-
1282
- maxSize -= labelHeight;
1283
- //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
1284
-
1285
- scaleHeight = maxSize;
1286
-
1287
- //Then get the area above we can safely draw on.
1288
-
1289
- }
1290
- function getValueBounds() {
1291
- var upperValue = Number.MIN_VALUE;
1292
- var lowerValue = Number.MAX_VALUE;
1293
- for (var i=0; i<data.datasets.length; i++){
1294
- for (var j=0; j<data.datasets[i].data.length; j++){
1295
- if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1296
- if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1297
- }
1298
- };
1299
-
1300
- var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1301
- var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1302
-
1303
- return {
1304
- maxValue : upperValue,
1305
- minValue : lowerValue,
1306
- maxSteps : maxSteps,
1307
- minSteps : minSteps
1308
- };
1309
-
1310
-
1311
- }
1312
-
1313
-
1314
- }
1315
-
1316
- var Bar = function(data,config,ctx){
1317
- var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0;
1318
-
1319
- calculateDrawingSizes();
1320
-
1321
- valueBounds = getValueBounds();
1322
- //Check and set the scale
1323
- labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
1324
- if (!config.scaleOverride){
1325
-
1326
- calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
1327
- }
1328
- else {
1329
- calculatedScale = {
1330
- steps : config.scaleSteps,
1331
- stepValue : config.scaleStepWidth,
1332
- graphMin : config.scaleStartValue,
1333
- labels : []
1334
- }
1335
- populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
1336
- }
1337
-
1338
- scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
1339
- calculateXAxisSize();
1340
- animationLoop(config,drawScale,drawBars,ctx);
1341
-
1342
- function drawBars(animPc){
1343
- ctx.lineWidth = config.barStrokeWidth;
1344
- for (var i=0; i<data.datasets.length; i++){
1345
- for (var j=0; j<data.datasets[i].data.length; j++){
1346
- var barOffset = yAxisPosX + config.barValueSpacing + valueHop*j + barWidth*i + config.barDatasetSpacing*i + config.barStrokeWidth*i;
1347
- ctx.fillStyle = cycleColor(data.datasets[i].fillColor, j);
1348
- ctx.strokeStyle = cycleColor(data.datasets[i].strokeColor, j);
1349
- ctx.beginPath();
1350
- ctx.moveTo(barOffset, xAxisPosY);
1351
- ctx.lineTo(barOffset, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1352
- ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1353
- ctx.lineTo(barOffset + barWidth, xAxisPosY);
1354
- if(config.barShowStroke){
1355
- ctx.stroke();
1356
- }
1357
- ctx.closePath();
1358
- ctx.fill();
1359
-
1360
- if(animPc >= 1 && config.showTooltips) {
1361
- // register tooltips
1362
- var x = barOffset,
1363
- height = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop),
1364
- y = xAxisPosY-height,
1365
- width = barWidth;
1366
- registerTooltip(ctx,{type:'rect',x:x,y:y,width:width,height:height},{label:data.labels[j],value:data.datasets[i].data[j]},'Bar');
1367
- }
1368
- }
1369
- }
1370
-
1371
- }
1372
- function drawScale(){
1373
- //X axis line
1374
- ctx.lineWidth = config.scaleLineWidth;
1375
- ctx.strokeStyle = config.scaleLineColor;
1376
- ctx.beginPath();
1377
- ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
1378
- ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
1379
- ctx.stroke();
1380
-
1381
-
1382
- if (rotateLabels > 0){
1383
- ctx.save();
1384
- ctx.textAlign = "right";
1385
- }
1386
- else{
1387
- ctx.textAlign = "center";
1388
- }
1389
- ctx.fillStyle = config.scaleFontColor;
1390
- for (var i=0; i<data.labels.length; i++){
1391
- ctx.save();
1392
- if (rotateLabels > 0){
1393
- ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
1394
- ctx.rotate(-(rotateLabels * (Math.PI/180)));
1395
- ctx.fillText(data.labels[i], 0,0);
1396
- ctx.restore();
1397
- }
1398
-
1399
- else{
1400
- ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3);
1401
- }
1402
-
1403
- ctx.beginPath();
1404
- ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3);
1405
-
1406
- //Check i isnt 0, so we dont go over the Y axis twice.
1407
- ctx.lineWidth = config.scaleGridLineWidth;
1408
- ctx.strokeStyle = config.scaleGridLineColor;
1409
- ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5);
1410
- ctx.stroke();
1411
- }
1412
-
1413
- //Y axis
1414
- ctx.lineWidth = config.scaleLineWidth;
1415
- ctx.strokeStyle = config.scaleLineColor;
1416
- ctx.beginPath();
1417
- ctx.moveTo(yAxisPosX,xAxisPosY+5);
1418
- ctx.lineTo(yAxisPosX,5);
1419
- ctx.stroke();
1420
-
1421
- ctx.textAlign = "right";
1422
- ctx.textBaseline = "middle";
1423
- for (var j=0; j<calculatedScale.steps; j++){
1424
- ctx.beginPath();
1425
- ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
1426
- if (config.scaleShowGridLines){
1427
- ctx.lineWidth = config.scaleGridLineWidth;
1428
- ctx.strokeStyle = config.scaleGridLineColor;
1429
- ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));
1430
- }
1431
- else{
1432
- ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
1433
- }
1434
-
1435
- ctx.stroke();
1436
- if (config.scaleShowLabels){
1437
- ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
1438
- }
1439
- }
1440
-
1441
-
1442
- }
1443
- function calculateXAxisSize(){
1444
- var longestText = 1;
1445
- //if we are showing the labels
1446
- if (config.scaleShowLabels){
1447
- ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1448
- for (var i=0; i<calculatedScale.labels.length; i++){
1449
- var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
1450
- longestText = (measuredText > longestText)? measuredText : longestText;
1451
- }
1452
- //Add a little extra padding from the y axis
1453
- longestText +=10;
1454
- }
1455
- xAxisLength = width - longestText - widestXLabel;
1456
- valueHop = Math.floor(xAxisLength/(data.labels.length));
1457
-
1458
- barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length;
1459
-
1460
- yAxisPosX = width-widestXLabel/2-xAxisLength;
1461
- xAxisPosY = scaleHeight + config.scaleFontSize/2;
1462
- }
1463
- function calculateDrawingSizes(){
1464
- maxSize = height;
1465
-
1466
- //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
1467
- ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1468
- widestXLabel = 1;
1469
- for (var i=0; i<data.labels.length; i++){
1470
- var textLength = ctx.measureText(data.labels[i]).width;
1471
- //If the text length is longer - make that equal to longest text!
1472
- widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
1473
- }
1474
- if (width/data.labels.length < widestXLabel){
1475
- rotateLabels = 45;
1476
- if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
1477
- rotateLabels = 90;
1478
- maxSize -= widestXLabel;
1479
- }
1480
- else{
1481
- maxSize -= Math.sin(rotateLabels) * widestXLabel;
1482
- }
1483
- }
1484
- else{
1485
- maxSize -= config.scaleFontSize;
1486
- }
1487
-
1488
- //Add a little padding between the x line and the text
1489
- maxSize -= 5;
1490
-
1491
-
1492
- labelHeight = config.scaleFontSize;
1493
-
1494
- maxSize -= labelHeight;
1495
- //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
1496
-
1497
- scaleHeight = maxSize;
1498
-
1499
- //Then get the area above we can safely draw on.
1500
-
1501
- }
1502
- function getValueBounds() {
1503
- var upperValue = Number.MIN_VALUE;
1504
- var lowerValue = Number.MAX_VALUE;
1505
- for (var i=0; i<data.datasets.length; i++){
1506
- for (var j=0; j<data.datasets[i].data.length; j++){
1507
- if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1508
- if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1509
- }
1510
- };
1511
-
1512
- var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1513
- var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1514
-
1515
- return {
1516
- maxValue : upperValue,
1517
- minValue : lowerValue,
1518
- maxSteps : maxSteps,
1519
- minSteps : minSteps
1520
- };
1521
-
1522
-
1523
- }
1524
- }
1525
-
1526
- function calculateOffset(val,calculatedScale,scaleHop){
1527
- var outerValue = calculatedScale.steps * calculatedScale.stepValue;
1528
- var adjustedValue = val - calculatedScale.graphMin;
1529
- var scalingFactor = CapValue(adjustedValue/outerValue,1,0);
1530
- return (scaleHop*calculatedScale.steps) * scalingFactor;
1531
- }
1532
-
1533
- function animationLoop(config,drawScale,drawData,ctx){
1534
- var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1,
1535
- easingFunction = animationOptions[config.animationEasing],
1536
- percentAnimComplete =(config.animation)? 0 : 1;
1537
-
1538
-
1539
-
1540
- if (typeof drawScale !== "function") drawScale = function(){};
1541
-
1542
- requestAnimFrame(animLoop);
1543
-
1544
- function animateFrame(){
1545
- var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1;
1546
- clear(ctx);
1547
- if(config.scaleOverlay){
1548
- drawData(easeAdjustedAnimationPercent);
1549
- drawScale();
1550
- } else {
1551
- drawScale();
1552
- drawData(easeAdjustedAnimationPercent);
1553
- }
1554
- }
1555
- function animLoop(){
1556
- //We need to check if the animation is incomplete (less than 1), or complete (1).
1557
- percentAnimComplete += animFrameAmount;
1558
- animateFrame();
1559
- //Stop the loop continuing forever
1560
- if (percentAnimComplete <= 1){
1561
- requestAnimFrame(animLoop);
1562
- }
1563
- else{
1564
- if (typeof config.onAnimationComplete == "function") config.onAnimationComplete();
1565
- }
1566
-
1567
- }
1568
-
1569
- }
1570
-
1571
- //Declare global functions to be called within this namespace here.
1572
-
1573
-
1574
- // shim layer with setTimeout fallback
1575
- var requestAnimFrame = (function(){
1576
- return window.requestAnimationFrame ||
1577
- window.webkitRequestAnimationFrame ||
1578
- window.mozRequestAnimationFrame ||
1579
- window.oRequestAnimationFrame ||
1580
- window.msRequestAnimationFrame ||
1581
- function(callback) {
1582
- window.setTimeout(callback, 1000 / 60);
1583
- };
1584
- })();
1585
-
1586
- function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){
1587
- var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum;
1588
- valueRange = maxValue - minValue;
1589
- rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);
1590
- graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1591
- graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1592
- graphRange = graphMax - graphMin;
1593
- stepValue = Math.pow(10, rangeOrderOfMagnitude);
1594
- numberOfSteps = Math.round(graphRange / stepValue);
1595
-
1596
- //Compare number of steps to the max and min for that size graph, and add in half steps if need be.
1597
- while(numberOfSteps < minSteps || numberOfSteps > maxSteps) {
1598
- if (numberOfSteps < minSteps){
1599
- stepValue /= 2;
1600
- numberOfSteps = Math.round(graphRange/stepValue);
1601
- }
1602
- else{
1603
- stepValue *=2;
1604
- numberOfSteps = Math.round(graphRange/stepValue);
1605
- }
1606
- }
1607
-
1608
- var labels = [];
1609
- populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue);
1610
-
1611
- return {
1612
- steps : numberOfSteps,
1613
- stepValue : stepValue,
1614
- graphMin : graphMin,
1615
- labels : labels
1616
- }
1617
-
1618
- function calculateOrderOfMagnitude(val){
1619
- return Math.floor(Math.log(val) / Math.LN10);
1620
- }
1621
- }
1622
-
1623
- //Populate an array of all the labels by interpolating the string.
1624
- function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) {
1625
- if (labelTemplateString) {
1626
- //Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
1627
- for (var i = 1; i < numberOfSteps + 1; i++) {
1628
- labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))}));
1629
- }
1630
- }
1631
- }
1632
- // Cycle a given array of colours (for multi coloured bars in bargraphs)
1633
- function cycleColor(colors, i) {
2
+ var chart = this;
3
+
4
+ var animationOptions = {
5
+ linear : function (t){
6
+ return t;
7
+ },
8
+ easeInQuad: function (t) {
9
+ return t*t;
10
+ },
11
+ easeOutQuad: function (t) {
12
+ return -1 *t*(t-2);
13
+ },
14
+ easeInOutQuad: function (t) {
15
+ if ((t/=1/2) < 1) return 1/2*t*t;
16
+ return -1/2 * ((--t)*(t-2) - 1);
17
+ },
18
+ easeInCubic: function (t) {
19
+ return t*t*t;
20
+ },
21
+ easeOutCubic: function (t) {
22
+ return 1*((t=t/1-1)*t*t + 1);
23
+ },
24
+ easeInOutCubic: function (t) {
25
+ if ((t/=1/2) < 1) return 1/2*t*t*t;
26
+ return 1/2*((t-=2)*t*t + 2);
27
+ },
28
+ easeInQuart: function (t) {
29
+ return t*t*t*t;
30
+ },
31
+ easeOutQuart: function (t) {
32
+ return -1 * ((t=t/1-1)*t*t*t - 1);
33
+ },
34
+ easeInOutQuart: function (t) {
35
+ if ((t/=1/2) < 1) return 1/2*t*t*t*t;
36
+ return -1/2 * ((t-=2)*t*t*t - 2);
37
+ },
38
+ easeInQuint: function (t) {
39
+ return 1*(t/=1)*t*t*t*t;
40
+ },
41
+ easeOutQuint: function (t) {
42
+ return 1*((t=t/1-1)*t*t*t*t + 1);
43
+ },
44
+ easeInOutQuint: function (t) {
45
+ if ((t/=1/2) < 1) return 1/2*t*t*t*t*t;
46
+ return 1/2*((t-=2)*t*t*t*t + 2);
47
+ },
48
+ easeInSine: function (t) {
49
+ return -1 * Math.cos(t/1 * (Math.PI/2)) + 1;
50
+ },
51
+ easeOutSine: function (t) {
52
+ return 1 * Math.sin(t/1 * (Math.PI/2));
53
+ },
54
+ easeInOutSine: function (t) {
55
+ return -1/2 * (Math.cos(Math.PI*t/1) - 1);
56
+ },
57
+ easeInExpo: function (t) {
58
+ return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1));
59
+ },
60
+ easeOutExpo: function (t) {
61
+ return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1);
62
+ },
63
+ easeInOutExpo: function (t) {
64
+ if (t==0) return 0;
65
+ if (t==1) return 1;
66
+ if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1));
67
+ return 1/2 * (-Math.pow(2, -10 * --t) + 2);
68
+ },
69
+ easeInCirc: function (t) {
70
+ if (t>=1) return t;
71
+ return -1 * (Math.sqrt(1 - (t/=1)*t) - 1);
72
+ },
73
+ easeOutCirc: function (t) {
74
+ return 1 * Math.sqrt(1 - (t=t/1-1)*t);
75
+ },
76
+ easeInOutCirc: function (t) {
77
+ if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1);
78
+ return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1);
79
+ },
80
+ easeInElastic: function (t) {
81
+ var s=1.70158;var p=0;var a=1;
82
+ if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
83
+ if (a < Math.abs(1)) { a=1; var s=p/4; }
84
+ else var s = p/(2*Math.PI) * Math.asin (1/a);
85
+ return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
86
+ },
87
+ easeOutElastic: function (t) {
88
+ var s=1.70158;var p=0;var a=1;
89
+ if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
90
+ if (a < Math.abs(1)) { a=1; var s=p/4; }
91
+ else var s = p/(2*Math.PI) * Math.asin (1/a);
92
+ return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1;
93
+ },
94
+ easeInOutElastic: function (t) {
95
+ var s=1.70158;var p=0;var a=1;
96
+ if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5);
97
+ if (a < Math.abs(1)) { a=1; var s=p/4; }
98
+ else var s = p/(2*Math.PI) * Math.asin (1/a);
99
+ if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
100
+ return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1;
101
+ },
102
+ easeInBack: function (t) {
103
+ var s = 1.70158;
104
+ return 1*(t/=1)*t*((s+1)*t - s);
105
+ },
106
+ easeOutBack: function (t) {
107
+ var s = 1.70158;
108
+ return 1*((t=t/1-1)*t*((s+1)*t + s) + 1);
109
+ },
110
+ easeInOutBack: function (t) {
111
+ var s = 1.70158;
112
+ if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s));
113
+ return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2);
114
+ },
115
+ easeInBounce: function (t) {
116
+ return 1 - animationOptions.easeOutBounce (1-t);
117
+ },
118
+ easeOutBounce: function (t) {
119
+ if ((t/=1) < (1/2.75)) {
120
+ return 1*(7.5625*t*t);
121
+ } else if (t < (2/2.75)) {
122
+ return 1*(7.5625*(t-=(1.5/2.75))*t + .75);
123
+ } else if (t < (2.5/2.75)) {
124
+ return 1*(7.5625*(t-=(2.25/2.75))*t + .9375);
125
+ } else {
126
+ return 1*(7.5625*(t-=(2.625/2.75))*t + .984375);
127
+ }
128
+ },
129
+ easeInOutBounce: function (t) {
130
+ if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5;
131
+ return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5;
132
+ }
133
+ };
134
+
135
+ this.tooltips = [],
136
+ defaults = {
137
+ tooltips: {
138
+ background: 'rgba(71,74,84,1)',
139
+ fontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
140
+ fontStyle : "bold",
141
+ fontColor: 'rgba(255,255,255,1)',
142
+ fontSize: '11px',
143
+ labelTemplate: '<%=label%>: <%=value%>',
144
+ padding: {
145
+ top: 10,
146
+ right: 5,
147
+ bottom: 12,
148
+ left: 5
149
+ },
150
+ offset: {
151
+ left: 15,
152
+ top: 0
153
+ },
154
+ border: {
155
+ radius: 3
156
+ },
157
+ showHighlight: true,
158
+ highlight: {
159
+ stroke: {
160
+ width: 1,
161
+ color: 'rgba(255,255,255,0.25)'
162
+ },
163
+ fill: 'rgba(255,255,255,0.25)'
164
+ }
165
+ }
166
+ },
167
+ options = (options) ? mergeChartConfig(defaults, options) : defaults;
168
+
169
+ function registerTooltip(ctx,areaObj,data,type) {
170
+ chart.tooltips.push(new Tooltip(
171
+ ctx,
172
+ areaObj,
173
+ data,
174
+ type
175
+ ));
176
+ }
177
+
178
+ var Tooltip = function(ctx, areaObj, data, type) {
179
+ this.ctx = ctx;
180
+ this.areaObj = areaObj;
181
+ this.data = data;
182
+ this.savedState = null;
183
+ this.highlightState = null;
184
+ this.x = null;
185
+ this.y = null;
186
+
187
+ this.inRange = function(x,y) {
188
+ if(this.areaObj.type) {
189
+ switch(this.areaObj.type) {
190
+ case 'rect':
191
+ return (x >= this.areaObj.x && x <= this.areaObj.x+this.areaObj.width) &&
192
+ (y >= this.areaObj.y && y <= this.areaObj.y+this.areaObj.height);
193
+ break;
194
+ case 'circle':
195
+ return ((Math.pow(x-this.areaObj.x, 2)+Math.pow(y-this.areaObj.y, 2)) < Math.pow(this.areaObj.r,2));
196
+ break;
197
+ case 'shape':
198
+ var poly = this.areaObj.points;
199
+ for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
200
+ ((poly[i].y <= y && y < poly[j].y) || (poly[j].y <= y && y < poly[i].y))
201
+ && (x < (poly[j].x - poly[i].x) * (y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
202
+ && (c = !c);
203
+ return c;
204
+ break;
205
+ }
206
+ }
207
+ }
208
+
209
+ this.render = function(x,y) {
210
+ if(this.savedState == null) {
211
+ this.ctx.putImageData(chart.savedState,0,0);
212
+ this.savedState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height);
213
+ }
214
+ this.ctx.putImageData(this.savedState,0,0);
215
+ if(options.tooltips.showHighlight) {
216
+ if(this.highlightState == null) {
217
+ this.ctx.strokeStyle = options.tooltips.highlight.stroke.color;
218
+ this.ctx.lineWidth = options.tooltips.highlight.stroke.width;
219
+ this.ctx.fillStyle = options.tooltips.highlight.fill;
220
+ switch(this.areaObj.type) {
221
+ case 'rect':
222
+ this.ctx.strokeRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height);
223
+ this.ctx.fillStyle = options.tooltips.highlight.fill;
224
+ this.ctx.fillRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height);
225
+ break;
226
+ case 'circle':
227
+ this.ctx.beginPath();
228
+ this.ctx.arc(this.areaObj.x, this.areaObj.y, this.areaObj.r, 0, 2*Math.PI, false);
229
+ this.ctx.stroke();
230
+ this.ctx.fill();
231
+ break;
232
+ case 'shape':
233
+ this.ctx.beginPath();
234
+ this.ctx.moveTo(this.areaObj.points[0].x, this.areaObj.points[0].y);
235
+ for(var p in this.areaObj.points) {
236
+ this.ctx.lineTo(this.areaObj.points[p].x, this.areaObj.points[p].y);
237
+ }
238
+ this.ctx.stroke();
239
+ this.ctx.fill();
240
+ break;
241
+ }
242
+ this.highlightState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height);
243
+ } else {
244
+ this.ctx.putImageData(this.highlightState,0,0);
245
+ }
246
+ }
247
+ //if(this.x != x || this.y != y) {
248
+ var posX = x+options.tooltips.offset.left,
249
+ posY = y+options.tooltips.offset.top,
250
+ tpl = tmpl(options.tooltips.labelTemplate, this.data),
251
+ rectWidth = options.tooltips.padding.left+this.ctx.measureText(tpl).width+options.tooltips.padding.right;
252
+ if(posX + rectWidth > this.ctx.canvas.width) {
253
+ posX -= posX-rectWidth < 0 ? posX : rectWidth;
254
+ }
255
+ if(posY + 24 > this.ctx.canvas.height) {
256
+ posY -= 24;
257
+ }
258
+ this.ctx.fillStyle = options.tooltips.background;
259
+ this.ctx.fillRect(posX, posY, rectWidth, 24);
260
+ if(options.tooltips.border.width > 0) {
261
+ this.ctx.fillStyle = options.tooltips.order.color;
262
+ this.ctx.lineWidth = options.tooltips.border.width;
263
+ this.ctx.strokeRect(posX, posY, rectWidth, 24);
264
+ }
265
+ this.ctx.font = options.tooltips.fontStyle+ " "+options.tooltips.fontSize+" " + options.tooltips.fontFamily;
266
+ this.ctx.fillStyle = options.tooltips.fontColor;
267
+ this.ctx.textAlign = 'center';
268
+ this.ctx.textBaseline = 'middle';
269
+ this.ctx.fillText(tpl, posX+rectWidth/2, posY+12);
270
+ this.x = x;
271
+ this.y = y;
272
+ //}
273
+ }
274
+ }
275
+
276
+ //Variables global to the chart
277
+ var width = context.canvas.width,
278
+ height = context.canvas.height;
279
+
280
+ this.savedState = null;
281
+
282
+ function getPosition(e) {
283
+ var xPosition = 0;
284
+ var yPosition = 0;
285
+
286
+ while(e) {
287
+ xPosition += (e.offsetLeft - e.scrollLeft + e.clientLeft);
288
+ yPosition += (e.offsetTop - e.scrollTop + e.clientTop);
289
+ e = e.offsetParent;
290
+ }
291
+ return { x: xPosition, y: yPosition };
292
+ }
293
+
294
+ function tooltipEventHandler(e) {
295
+ if(chart.tooltips.length > 0) {
296
+ chart.savedState = chart.savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : chart.savedState;
297
+ var rendered = 0;
298
+ for(var i in chart.tooltips) {
299
+ var position = getPosition(context.canvas),
300
+ mx = (e.clientX)-position.x,
301
+ my = (e.clientY)-position.y;
302
+ if(chart.tooltips[i].inRange(mx,my)) {
303
+ chart.tooltips[i].render(mx,my);
304
+ rendered++;
305
+ }
306
+ }
307
+ if(rendered == 0) {
308
+ context.putImageData(chart.savedState,0,0);
309
+ }
310
+ }
311
+ }
312
+
313
+ if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
314
+ context.canvas.ontouchstart = function(e) {
315
+ e.clientX = e.targetTouches[0].clientX;
316
+ e.clientY = e.targetTouches[0].clientY;
317
+ tooltipEventHandler(e);
318
+ }
319
+ context.canvas.ontouchmove = function(e) {
320
+ e.clientX = e.targetTouches[0].clientX;
321
+ e.clientY = e.targetTouches[0].clientY;
322
+ tooltipEventHandler(e);
323
+ }
324
+ } else {
325
+ context.canvas.onmousemove = function(e) {
326
+ tooltipEventHandler(e);
327
+ }
328
+ }
329
+ context.canvas.onmouseout = function(e) {
330
+ if(chart.savedState != null) {
331
+ context.putImageData(chart.savedState,0,0);
332
+ }
333
+ }
334
+
335
+
336
+ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
337
+ if (window.devicePixelRatio) {
338
+ context.canvas.style.width = width + "px";
339
+ context.canvas.style.height = height + "px";
340
+ context.canvas.height = height * window.devicePixelRatio;
341
+ context.canvas.width = width * window.devicePixelRatio;
342
+ context.scale(window.devicePixelRatio, window.devicePixelRatio);
343
+ }
344
+
345
+ this.PolarArea = function(data,options){
346
+
347
+ chart.PolarArea.defaults = {
348
+ scaleOverlay : true,
349
+ scaleOverride : false,
350
+ scaleSteps : null,
351
+ scaleStepWidth : null,
352
+ scaleStartValue : null,
353
+ scaleShowLine : true,
354
+ scaleLineColor : "rgba(0,0,0,0.1)",
355
+ scaleLineWidth : 1,
356
+ scaleShowLabels : true,
357
+ scaleLabel : "<%=value%>",
358
+ scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
359
+ scaleFontSize : 12,
360
+ scaleFontStyle : "normal",
361
+ scaleFontColor : "rgba(71,74,84,1)",
362
+ scaleShowLabelBackdrop : true,
363
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
364
+ scaleBackdropPaddingY : 2,
365
+ scaleBackdropPaddingX : 2,
366
+ segmentShowStroke : true,
367
+ segmentStrokeColor : "#fff",
368
+ segmentStrokeWidth : 1,
369
+ animation : true,
370
+ animationSteps : 100,
371
+ animationEasing : "easeOutBounce",
372
+ animateRotate : true,
373
+ animateScale : false,
374
+ onAnimationComplete : null,
375
+ showTooltips : true
376
+ };
377
+
378
+ var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults;
379
+
380
+ return new PolarArea(data,config,context);
381
+ };
382
+
383
+ this.Radar = function(data,options){
384
+
385
+ chart.Radar.defaults = {
386
+ scaleOverlay : false,
387
+ scaleOverride : false,
388
+ scaleSteps : null,
389
+ scaleStepWidth : null,
390
+ scaleStartValue : null,
391
+ scaleShowLine : true,
392
+ scaleLineColor : "rgba(0,0,0,0.1)",
393
+ scaleLineWidth : 1,
394
+ scaleShowLabels : false,
395
+ scaleLabel : "<%=value%>",
396
+ scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
397
+ scaleFontSize : 12,
398
+ scaleFontStyle : "normal",
399
+ scaleFontColor : "rgba(71,74,84,1)",
400
+ scaleShowLabelBackdrop : true,
401
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
402
+ scaleBackdropPaddingY : 2,
403
+ scaleBackdropPaddingX : 2,
404
+ angleShowLineOut : true,
405
+ angleLineColor : "rgba(0,0,0,0.1)",
406
+ angleLineWidth : 1,
407
+ pointLabelFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
408
+ pointLabelFontStyle : "normal",
409
+ pointLabelFontSize : 12,
410
+ pointLabelFontColor : "rgba(71,74,84,1)",
411
+ pointDot : true,
412
+ pointDotRadius : 3,
413
+ pointDotStrokeWidth : 1,
414
+ datasetStroke : true,
415
+ datasetStrokeWidth : 1,
416
+ datasetFill : true,
417
+ animation : true,
418
+ animationSteps : 60,
419
+ animationEasing : "easeOutQuart",
420
+ onAnimationComplete : null,
421
+ showTooltips : true
422
+ };
423
+
424
+ var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults;
425
+
426
+ return new Radar(data,config,context);
427
+ };
428
+
429
+ this.Pie = function(data,options){
430
+ chart.Pie.defaults = {
431
+ segmentShowStroke : true,
432
+ segmentStrokeColor : "#fff",
433
+ segmentStrokeWidth : 1,
434
+ animation : true,
435
+ animationSteps : 100,
436
+ animationEasing : "easeOutBounce",
437
+ animateRotate : true,
438
+ animateScale : false,
439
+ onAnimationComplete : null,
440
+ labelFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
441
+ labelFontStyle : "normal",
442
+ labelFontSize : 12,
443
+ labelFontColor : "rgba(71,74,84,1)",
444
+ labelAlign : 'right',
445
+ showTooltips : true
446
+ };
447
+
448
+ var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults;
449
+
450
+ return new Pie(data,config,context);
451
+ };
452
+
453
+ this.Doughnut = function(data,options){
454
+
455
+ chart.Doughnut.defaults = {
456
+ segmentShowStroke : true,
457
+ segmentStrokeColor : "#fff",
458
+ segmentStrokeWidth : 1,
459
+ percentageInnerCutout : 50,
460
+ animation : true,
461
+ animationSteps : 100,
462
+ animationEasing : "easeOutBounce",
463
+ animateRotate : true,
464
+ animateScale : false,
465
+ onAnimationComplete : null,
466
+ showTooltips : true
467
+ };
468
+
469
+ var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults;
470
+
471
+ return new Doughnut(data,config,context);
472
+
473
+ };
474
+
475
+ this.Line = function(data,options){
476
+
477
+ chart.Line.defaults = {
478
+ scaleOverlay : false,
479
+ scaleOverride : false,
480
+ scaleSteps : null,
481
+ scaleStepWidth : null,
482
+ scaleStartValue : null,
483
+ scaleLineColor : "rgba(0,0,0,0.1)",
484
+ scaleLineWidth : 1,
485
+ scaleShowLabels : true,
486
+ scaleLabel : "<%=value%>",
487
+ scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
488
+ scaleFontSize : 12,
489
+ scaleFontStyle : "normal",
490
+ scaleFontColor : "rgba(71,74,84,1)",
491
+ scaleShowGridLines : true,
492
+ scaleGridLineColor : "rgba(0,0,0,0.05)",
493
+ scaleGridLineWidth : 1,
494
+ bezierCurve : true,
495
+ pointDot : true,
496
+ pointDotRadius : 4,
497
+ pointDotStrokeWidth : 1,
498
+ datasetStroke : true,
499
+ datasetStrokeWidth : 1,
500
+ datasetFill : true,
501
+ animation : true,
502
+ animationSteps : 60,
503
+ animationEasing : "easeOutQuart",
504
+ onAnimationComplete : null,
505
+ showTooltips : true
506
+ };
507
+ var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults;
508
+
509
+ return new Line(data,config,context);
510
+ }
511
+
512
+ this.Bar = function(data,options){
513
+ chart.Bar.defaults = {
514
+ scaleOverlay : false,
515
+ scaleOverride : false,
516
+ scaleSteps : null,
517
+ scaleStepWidth : null,
518
+ scaleStartValue : null,
519
+ scaleLineColor : "rgba(0,0,0,0.1)",
520
+ scaleLineWidth : 1,
521
+ scaleShowLabels : true,
522
+ scaleLabel : "<%=value%>",
523
+ scaleFontFamily : "'Gotham', 'Helvetica', Helvetica, Arial, sans-serif",
524
+ scaleFontSize : 12,
525
+ scaleFontStyle : "normal",
526
+ scaleFontColor : "rgba(71,74,84,1)",
527
+ scaleShowGridLines : true,
528
+ scaleGridLineColor : "rgba(0,0,0,0.05)",
529
+ scaleGridLineWidth : 1,
530
+ barShowStroke : true,
531
+ barStrokeWidth : 1,
532
+ barValueSpacing : 5,
533
+ barDatasetSpacing : 2,
534
+ animation : true,
535
+ animationSteps : 60,
536
+ animationEasing : "easeOutQuart",
537
+ onAnimationComplete : null,
538
+ showTooltips : true
539
+ };
540
+ var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults;
541
+
542
+ return new Bar(data,config,context);
543
+ }
544
+
545
+ var clear = function(c){
546
+ c.clearRect(0, 0, width, height);
547
+ };
548
+
549
+ var PolarArea = function(data,config,ctx){
550
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
551
+
552
+
553
+ calculateDrawingSizes();
554
+
555
+ valueBounds = getValueBounds();
556
+
557
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
558
+
559
+ //Check and set the scale
560
+ if (!config.scaleOverride){
561
+
562
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
563
+ }
564
+ else {
565
+ calculatedScale = {
566
+ steps : config.scaleSteps,
567
+ stepValue : config.scaleStepWidth,
568
+ graphMin : config.scaleStartValue,
569
+ labels : []
570
+ }
571
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
572
+ }
573
+
574
+ scaleHop = maxSize/(calculatedScale.steps);
575
+
576
+ //Wrap in an animation loop wrapper
577
+ animationLoop(config,drawScale,drawAllSegments,ctx);
578
+
579
+ function calculateDrawingSizes(){
580
+ maxSize = (Min([width,height])/2);
581
+ //Remove whatever is larger - the font size or line width.
582
+
583
+ maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]);
584
+
585
+ labelHeight = config.scaleFontSize*2;
586
+ //If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region.
587
+ if (config.scaleShowLabelBackdrop){
588
+ labelHeight += (2 * config.scaleBackdropPaddingY);
589
+ maxSize -= config.scaleBackdropPaddingY*1.5;
590
+ }
591
+
592
+ scaleHeight = maxSize;
593
+ //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
594
+ labelHeight = Default(labelHeight,5);
595
+ }
596
+ function drawScale(){
597
+ for (var i=0; i<calculatedScale.steps; i++){
598
+ //If the line object is there
599
+ if (config.scaleShowLine){
600
+ ctx.beginPath();
601
+ ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true);
602
+ ctx.strokeStyle = config.scaleLineColor;
603
+ ctx.lineWidth = config.scaleLineWidth;
604
+ ctx.stroke();
605
+ }
606
+
607
+ if (config.scaleShowLabels){
608
+ ctx.textAlign = "center";
609
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
610
+ var label = calculatedScale.labels[i];
611
+ //If the backdrop object is within the font object
612
+ if (config.scaleShowLabelBackdrop){
613
+ var textWidth = ctx.measureText(label).width;
614
+ ctx.fillStyle = config.scaleBackdropColor;
615
+ ctx.beginPath();
616
+ ctx.rect(
617
+ Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX), //X
618
+ Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
619
+ Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
620
+ Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
621
+ );
622
+ ctx.fill();
623
+ }
624
+ ctx.textBaseline = "middle";
625
+ ctx.fillStyle = config.scaleFontColor;
626
+ ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1)));
627
+ }
628
+ }
629
+ }
630
+ function drawAllSegments(animationDecimal){
631
+ var startAngle = -Math.PI/2,
632
+ angleStep = (Math.PI*2)/data.length,
633
+ scaleAnimation = 1,
634
+ rotateAnimation = 1;
635
+ if (config.animation) {
636
+ if (config.animateScale) {
637
+ scaleAnimation = animationDecimal;
638
+ }
639
+ if (config.animateRotate){
640
+ rotateAnimation = animationDecimal;
641
+ }
642
+ }
643
+
644
+ for (var i=0; i<data.length; i++){
645
+
646
+ ctx.beginPath();
647
+ ctx.arc(width/2,height/2,scaleAnimation * calculateOffset(data[i].value,calculatedScale,scaleHop),startAngle, startAngle + rotateAnimation*angleStep, false);
648
+ ctx.lineTo(width/2,height/2);
649
+ ctx.closePath();
650
+ ctx.fillStyle = data[i].color;
651
+ ctx.fill();
652
+
653
+ if(animationDecimal >= 1 && config.showTooltips) {
654
+ var points = [{x:width/2,y:height/2}],
655
+ pAmount = 50,
656
+ radius = calculateOffset(data[i].value,calculatedScale,scaleHop);
657
+ points.push({x:width/2+radius*Math.cos(startAngle),y:height/2+radius*Math.sin(startAngle)});
658
+ for(var p = 0; p <= pAmount; p++) {
659
+ points.push({x:width/2+radius*Math.cos(startAngle+p/pAmount*rotateAnimation*angleStep),y:height/2+radius*Math.sin(startAngle+p/pAmount*rotateAnimation*angleStep)});
660
+ }
661
+ registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'PolarArea');
662
+ }
663
+
664
+ if(config.segmentShowStroke){
665
+ ctx.strokeStyle = config.segmentStrokeColor;
666
+ ctx.lineWidth = config.segmentStrokeWidth;
667
+ ctx.stroke();
668
+ }
669
+ startAngle += rotateAnimation*angleStep;
670
+ }
671
+ }
672
+ function getValueBounds() {
673
+ var upperValue = Number.MIN_VALUE;
674
+ var lowerValue = Number.MAX_VALUE;
675
+ for (var i=0; i<data.length; i++){
676
+ if (data[i].value > upperValue) {upperValue = data[i].value;}
677
+ if (data[i].value < lowerValue) {lowerValue = data[i].value;}
678
+ };
679
+
680
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
681
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
682
+
683
+ return {
684
+ maxValue : upperValue,
685
+ minValue : lowerValue,
686
+ maxSteps : maxSteps,
687
+ minSteps : minSteps
688
+ };
689
+
690
+
691
+ }
692
+ }
693
+
694
+ var Radar = function (data,config,ctx) {
695
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
696
+
697
+ //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up.
698
+ if (!data.labels) data.labels = [];
699
+
700
+ calculateDrawingSizes();
701
+
702
+ var valueBounds = getValueBounds();
703
+
704
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
705
+
706
+ //Check and set the scale
707
+ if (!config.scaleOverride){
708
+
709
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
710
+ }
711
+ else {
712
+ calculatedScale = {
713
+ steps : config.scaleSteps,
714
+ stepValue : config.scaleStepWidth,
715
+ graphMin : config.scaleStartValue,
716
+ labels : []
717
+ }
718
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
719
+ }
720
+
721
+ scaleHop = maxSize/(calculatedScale.steps);
722
+
723
+ animationLoop(config,drawScale,drawAllDataPoints,ctx);
724
+
725
+ //Radar specific functions.
726
+ function drawAllDataPoints(animationDecimal){
727
+ var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
728
+
729
+ ctx.save();
730
+ //translate to the centre of the canvas.
731
+ ctx.translate(width/2,height/2);
732
+ //We accept multiple data sets for radar charts, so show loop through each set
733
+ for (var i=0; i<data.datasets.length; i++){
734
+ var offset = calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop);
735
+ ctx.beginPath();
736
+ ctx.moveTo(0,animationDecimal*(-1*offset));
737
+ if(animationDecimal >= 1 && config.showTooltips) {
738
+ var curX = width/2+offset*Math.cos(0-Math.PI/2),
739
+ curY = height/2+offset*Math.sin(0-Math.PI/2),
740
+ pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10,
741
+ ttData = data.labels[0].trim() != "" ? data.labels[0]+": "+data.datasets[i].data[0] : data.datasets[i].data[0];
742
+ registerTooltip(ctx,{type:'circle',x:curX,y:curY,r:pointRadius},{label:data.labels[0],value:data.datasets[i].data[0]},'Radar');
743
+ }
744
+ for (var j=1; j<data.datasets[i].data.length; j++){
745
+ offset = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop);
746
+ ctx.rotate(rotationDegree);
747
+ ctx.lineTo(0,animationDecimal*(-1*offset));
748
+ if(animationDecimal >= 1 && config.showTooltips) {
749
+ var curX = width/2+offset*Math.cos(j*rotationDegree-Math.PI/2),
750
+ curY = height/2+offset*Math.sin(j*rotationDegree-Math.PI/2),
751
+ pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10,
752
+ ttData = data.labels[j].trim() != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j];
753
+ registerTooltip(ctx,{type:'circle',x:curX,y:curY,r:pointRadius},{label:data.labels[j],value:data.datasets[i].data[j]},'Radar');
754
+ }
755
+ }
756
+ ctx.closePath();
757
+
758
+
759
+ ctx.fillStyle = data.datasets[i].fillColor;
760
+ ctx.strokeStyle = data.datasets[i].strokeColor;
761
+ ctx.lineWidth = config.datasetStrokeWidth;
762
+ ctx.fill();
763
+ ctx.stroke();
764
+
765
+
766
+ if (config.pointDot){
767
+ ctx.fillStyle = data.datasets[i].pointColor;
768
+ ctx.strokeStyle = data.datasets[i].pointStrokeColor;
769
+ ctx.lineWidth = config.pointDotStrokeWidth;
770
+ for (var k=0; k<data.datasets[i].data.length; k++){
771
+ ctx.rotate(rotationDegree);
772
+ ctx.beginPath();
773
+ ctx.arc(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,2*Math.PI,false);
774
+ ctx.fill();
775
+ ctx.stroke();
776
+ }
777
+
778
+ }
779
+ ctx.rotate(rotationDegree);
780
+ }
781
+ ctx.restore();
782
+
783
+
784
+ }
785
+ function drawScale(){
786
+ var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
787
+ ctx.save();
788
+ ctx.translate(width / 2, height / 2);
789
+
790
+ if (config.angleShowLineOut){
791
+ ctx.strokeStyle = config.angleLineColor;
792
+ ctx.lineWidth = config.angleLineWidth;
793
+ for (var h=0; h<data.datasets[0].data.length; h++){
794
+
795
+ ctx.rotate(rotationDegree);
796
+ ctx.beginPath();
797
+ ctx.moveTo(0,0);
798
+ ctx.lineTo(0,-maxSize);
799
+ ctx.stroke();
800
+ }
801
+ }
802
+
803
+ for (var i=0; i<calculatedScale.steps; i++){
804
+ ctx.beginPath();
805
+
806
+ if(config.scaleShowLine){
807
+ ctx.strokeStyle = config.scaleLineColor;
808
+ ctx.lineWidth = config.scaleLineWidth;
809
+ ctx.moveTo(0,-scaleHop * (i+1));
810
+ for (var j=0; j<data.datasets[0].data.length; j++){
811
+ ctx.rotate(rotationDegree);
812
+ ctx.lineTo(0,-scaleHop * (i+1));
813
+ }
814
+ ctx.closePath();
815
+ ctx.stroke();
816
+
817
+ }
818
+
819
+ if (config.scaleShowLabels){
820
+ ctx.textAlign = 'center';
821
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
822
+ ctx.textBaseline = "middle";
823
+
824
+ if (config.scaleShowLabelBackdrop){
825
+ var textWidth = ctx.measureText(calculatedScale.labels[i]).width;
826
+ ctx.fillStyle = config.scaleBackdropColor;
827
+ ctx.beginPath();
828
+ ctx.rect(
829
+ Math.round(- textWidth/2 - config.scaleBackdropPaddingX), //X
830
+ Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
831
+ Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
832
+ Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
833
+ );
834
+ ctx.fill();
835
+ }
836
+ ctx.fillStyle = config.scaleFontColor;
837
+ ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1));
838
+ }
839
+
840
+ }
841
+ for (var k=0; k<data.labels.length; k++){
842
+ ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
843
+ ctx.fillStyle = config.pointLabelFontColor;
844
+ var opposite = Math.sin(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
845
+ var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
846
+
847
+ if(rotationDegree*k == Math.PI || rotationDegree*k == 0){
848
+ ctx.textAlign = "center";
849
+ }
850
+ else if(rotationDegree*k > Math.PI){
851
+ ctx.textAlign = "right";
852
+ }
853
+ else{
854
+ ctx.textAlign = "left";
855
+ }
856
+
857
+ ctx.textBaseline = "middle";
858
+
859
+ ctx.fillText(data.labels[k],opposite,-adjacent);
860
+
861
+ }
862
+ ctx.restore();
863
+ };
864
+ function calculateDrawingSizes(){
865
+ maxSize = (Min([width,height])/2);
866
+
867
+ labelHeight = config.scaleFontSize*2;
868
+
869
+ var labelLength = 0;
870
+ for (var i=0; i<data.labels.length; i++){
871
+ ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
872
+ var textMeasurement = ctx.measureText(data.labels[i]).width;
873
+ if(textMeasurement>labelLength) labelLength = textMeasurement;
874
+ }
875
+
876
+ //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size.
877
+ maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]);
878
+
879
+ maxSize -= config.pointLabelFontSize;
880
+ maxSize = CapValue(maxSize, null, 0);
881
+ scaleHeight = maxSize;
882
+ //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
883
+ labelHeight = Default(labelHeight,5);
884
+ };
885
+ function getValueBounds() {
886
+ var upperValue = Number.MIN_VALUE;
887
+ var lowerValue = Number.MAX_VALUE;
888
+
889
+ for (var i=0; i<data.datasets.length; i++){
890
+ for (var j=0; j<data.datasets[i].data.length; j++){
891
+ if (data.datasets[i].data[j] > upperValue){upperValue = data.datasets[i].data[j]}
892
+ if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]}
893
+ }
894
+ }
895
+
896
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
897
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
898
+
899
+ return {
900
+ maxValue : upperValue,
901
+ minValue : lowerValue,
902
+ maxSteps : maxSteps,
903
+ minSteps : minSteps
904
+ };
905
+
906
+
907
+ }
908
+ }
909
+
910
+ var Pie = function(data,config,ctx){
911
+ var segmentTotal = 0;
912
+
913
+ //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
914
+ var pieRadius = Min([height/2,width/2]) - 5;
915
+
916
+ for (var i=0; i<data.length; i++){
917
+ segmentTotal += data[i].value;
918
+ }
919
+ ctx.fillStyle = 'black';
920
+ ctx.textBaseline = 'base';
921
+
922
+ animationLoop(config,null,drawPieSegments,ctx);
923
+
924
+ function drawPieSegments (animationDecimal){
925
+ var cumulativeAngle = -Math.PI/2,
926
+ scaleAnimation = 1,
927
+ rotateAnimation = 1;
928
+ if (config.animation) {
929
+ if (config.animateScale) {
930
+ scaleAnimation = animationDecimal;
931
+ }
932
+ if (config.animateRotate){
933
+ rotateAnimation = animationDecimal;
934
+ }
935
+ }
936
+
937
+ for (var i=0; i<data.length; i++){
938
+ var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
939
+ ctx.beginPath();
940
+ ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle);
941
+ ctx.lineTo(width/2,height/2);
942
+ ctx.closePath();
943
+ ctx.fillStyle = data[i].color;
944
+ ctx.fill();
945
+
946
+ if(data[i].label && scaleAnimation*pieRadius*2*segmentAngle/(2*Math.PI) > config.labelFontSize) {
947
+ function getPieLabelX(align, r) {
948
+ switch(align) {
949
+ case 'left':
950
+ return -r+20;
951
+ break;
952
+ case 'center':
953
+ return -r/2;
954
+ break;
955
+ }
956
+ return -10;
957
+ }
958
+
959
+ function reversePieLabelAlign(align) {
960
+ switch(align) {
961
+ case 'left': return 'right'; break;
962
+ case 'right': return 'left'; break;
963
+ case 'center': return align; break;
964
+ }
965
+ }
966
+
967
+ var fontSize = data[i].labelFontSize || config.labelFontSize+'px';
968
+
969
+ if(fontSize.match(/^[0-9]+$/g) != null) {
970
+ fontSize = fontSize+'px';
971
+ }
972
+ ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily;
973
+ ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color);
974
+ ctx.textBaseline = 'middle';
975
+ // rotate text, so it perfectly fits in segments
976
+ var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2,
977
+ tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation),
978
+ tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation);
979
+ ctx.textAlign = data[i].labelAlign || config.labelAlign;
980
+ textX = getPieLabelX(ctx.textAlign, scaleAnimation*pieRadius);
981
+ if(textRotation < -Math.PI/2) {
982
+ textRotation -= Math.PI;
983
+ ctx.textAlign = reversePieLabelAlign(ctx.textAlign);
984
+ textX = -textX;
985
+ }
986
+ ctx.translate(tX, tY);
987
+ ctx.rotate(-textRotation);
988
+ ctx.fillText(data[i].label, textX, 0);
989
+ ctx.rotate(textRotation);
990
+ ctx.translate(-tX, -tY);
991
+ }
992
+
993
+ if(animationDecimal >= 1 && config.showTooltips) {
994
+ var points = [{x:width/2,y:height/2}],
995
+ pAmount = 50;
996
+ points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle)});
997
+ for(var p = 0; p <= pAmount; p++) {
998
+ points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)});
999
+ }
1000
+ registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'Pie');
1001
+ }
1002
+
1003
+ if(config.segmentShowStroke){
1004
+ ctx.lineWidth = config.segmentStrokeWidth;
1005
+ ctx.strokeStyle = config.segmentStrokeColor;
1006
+ ctx.stroke();
1007
+ }
1008
+ cumulativeAngle += segmentAngle;
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ var Doughnut = function(data,config,ctx){
1014
+ var segmentTotal = 0;
1015
+
1016
+ //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
1017
+ var doughnutRadius = Min([height/2,width/2]) - 5;
1018
+
1019
+ var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100);
1020
+
1021
+ for (var i=0; i<data.length; i++){
1022
+ segmentTotal += data[i].value;
1023
+ }
1024
+
1025
+
1026
+ animationLoop(config,null,drawPieSegments,ctx);
1027
+
1028
+
1029
+ function drawPieSegments (animationDecimal){
1030
+ var cumulativeAngle = -Math.PI/2,
1031
+ scaleAnimation = 1,
1032
+ rotateAnimation = 1;
1033
+ if (config.animation) {
1034
+ if (config.animateScale) {
1035
+ scaleAnimation = animationDecimal;
1036
+ }
1037
+ if (config.animateRotate){
1038
+ rotateAnimation = animationDecimal;
1039
+ }
1040
+ }
1041
+ for (var i=0; i<data.length; i++){
1042
+ var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
1043
+ ctx.beginPath();
1044
+ ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false);
1045
+ ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true);
1046
+ ctx.closePath();
1047
+ ctx.fillStyle = data[i].color;
1048
+ ctx.fill();
1049
+
1050
+ if(animationDecimal >= 1 && config.showTooltips) {
1051
+ var points = [],
1052
+ pAmount = 50;
1053
+ points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle)});
1054
+ for(var p = 0; p <= pAmount; p++) {
1055
+ points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)});
1056
+ }
1057
+ points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+segmentAngle)});
1058
+ for(var p = pAmount; p >= 0; p--) {
1059
+ points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)});
1060
+ }
1061
+ registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'Doughnut');
1062
+ }
1063
+
1064
+ if(config.segmentShowStroke){
1065
+ ctx.lineWidth = config.segmentStrokeWidth;
1066
+ ctx.strokeStyle = config.segmentStrokeColor;
1067
+ ctx.stroke();
1068
+ }
1069
+ cumulativeAngle += segmentAngle;
1070
+ }
1071
+ }
1072
+
1073
+
1074
+
1075
+ }
1076
+
1077
+ var Line = function(data,config,ctx){
1078
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0;
1079
+
1080
+ calculateDrawingSizes();
1081
+
1082
+ valueBounds = getValueBounds();
1083
+ //Check and set the scale
1084
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
1085
+ if (!config.scaleOverride){
1086
+
1087
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
1088
+ }
1089
+ else {
1090
+ calculatedScale = {
1091
+ steps : config.scaleSteps,
1092
+ stepValue : config.scaleStepWidth,
1093
+ graphMin : config.scaleStartValue,
1094
+ labels : []
1095
+ }
1096
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
1097
+ }
1098
+
1099
+ scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
1100
+ calculateXAxisSize();
1101
+ animationLoop(config,drawScale,drawLines,ctx);
1102
+
1103
+ function drawLines(animPc){
1104
+ for (var i=0; i<data.datasets.length; i++){
1105
+ ctx.strokeStyle = data.datasets[i].strokeColor;
1106
+ ctx.lineWidth = config.datasetStrokeWidth;
1107
+ ctx.beginPath();
1108
+ ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)))
1109
+
1110
+ for (var j=1; j<data.datasets[i].data.length; j++){
1111
+ if (config.bezierCurve){
1112
+ ctx.bezierCurveTo(xPos(j-0.5),yPos(i,j-1),xPos(j-0.5),yPos(i,j),xPos(j),yPos(i,j));
1113
+ }
1114
+ else{
1115
+ ctx.lineTo(xPos(j),yPos(i,j));
1116
+ }
1117
+ }
1118
+ var pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10;
1119
+ for(var j = 0; j < data.datasets[i].data.length; j++) {
1120
+ if(animPc >= 1 && config.showTooltips) {
1121
+ // register tooltips
1122
+ registerTooltip(ctx,{type:'circle',x:xPos(j),y:yPos(i,j),r:pointRadius},{label:data.labels[j],value:data.datasets[i].data[j]},'Line');
1123
+ }
1124
+ }
1125
+ ctx.stroke();
1126
+ if (config.datasetFill){
1127
+ ctx.lineTo(yAxisPosX + (valueHop*(data.datasets[i].data.length-1)),xAxisPosY);
1128
+ ctx.lineTo(yAxisPosX,xAxisPosY);
1129
+ ctx.closePath();
1130
+ ctx.fillStyle = data.datasets[i].fillColor;
1131
+ ctx.fill();
1132
+ }
1133
+ else{
1134
+ ctx.closePath();
1135
+ }
1136
+ if(config.pointDot){
1137
+ ctx.fillStyle = data.datasets[i].pointColor;
1138
+ ctx.strokeStyle = data.datasets[i].pointStrokeColor;
1139
+ ctx.lineWidth = config.pointDotStrokeWidth;
1140
+ for (var k=0; k<data.datasets[i].data.length; k++){
1141
+ ctx.beginPath();
1142
+ ctx.arc(yAxisPosX + (valueHop *k),xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,0,Math.PI*2,true);
1143
+ ctx.fill();
1144
+ ctx.stroke();
1145
+ }
1146
+ }
1147
+ }
1148
+
1149
+ function yPos(dataSet,iteration){
1150
+ return xAxisPosY - animPc*(calculateOffset(data.datasets[dataSet].data[iteration],calculatedScale,scaleHop));
1151
+ }
1152
+ function xPos(iteration){
1153
+ return yAxisPosX + (valueHop * iteration);
1154
+ }
1155
+ }
1156
+ function drawScale(){
1157
+ //X axis line
1158
+ ctx.lineWidth = config.scaleLineWidth;
1159
+ ctx.strokeStyle = config.scaleLineColor;
1160
+ ctx.beginPath();
1161
+ ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
1162
+ ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
1163
+ ctx.stroke();
1164
+
1165
+
1166
+ if (rotateLabels > 0){
1167
+ ctx.save();
1168
+ ctx.textAlign = "right";
1169
+ }
1170
+ else{
1171
+ ctx.textAlign = "center";
1172
+ }
1173
+ ctx.fillStyle = config.scaleFontColor;
1174
+ for (var i=0; i<data.labels.length; i++){
1175
+ ctx.save();
1176
+ if (rotateLabels > 0){
1177
+ ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
1178
+ ctx.rotate(-(rotateLabels * (Math.PI/180)));
1179
+ ctx.fillText(data.labels[i], 0,0);
1180
+ ctx.restore();
1181
+ }
1182
+
1183
+ else{
1184
+ ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3);
1185
+ }
1186
+
1187
+ ctx.beginPath();
1188
+ ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3);
1189
+
1190
+ //Check i isnt 0, so we dont go over the Y axis twice.
1191
+ if(config.scaleShowGridLines && i>0){
1192
+ ctx.lineWidth = config.scaleGridLineWidth;
1193
+ ctx.strokeStyle = config.scaleGridLineColor;
1194
+ ctx.lineTo(yAxisPosX + i * valueHop, 5);
1195
+ }
1196
+ else{
1197
+ ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3);
1198
+ }
1199
+ ctx.stroke();
1200
+ }
1201
+
1202
+ //Y axis
1203
+ ctx.lineWidth = config.scaleLineWidth;
1204
+ ctx.strokeStyle = config.scaleLineColor;
1205
+ ctx.beginPath();
1206
+ ctx.moveTo(yAxisPosX,xAxisPosY+5);
1207
+ ctx.lineTo(yAxisPosX,5);
1208
+ ctx.stroke();
1209
+
1210
+ ctx.textAlign = "right";
1211
+ ctx.textBaseline = "middle";
1212
+ for (var j=0; j<calculatedScale.steps; j++){
1213
+ ctx.beginPath();
1214
+ ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
1215
+ if (config.scaleShowGridLines){
1216
+ ctx.lineWidth = config.scaleGridLineWidth;
1217
+ ctx.strokeStyle = config.scaleGridLineColor;
1218
+ ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));
1219
+ }
1220
+ else{
1221
+ ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
1222
+ }
1223
+
1224
+ ctx.stroke();
1225
+
1226
+ if (config.scaleShowLabels){
1227
+ ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
1228
+ }
1229
+ }
1230
+
1231
+
1232
+ }
1233
+ function calculateXAxisSize(){
1234
+ var longestText = 1;
1235
+ //if we are showing the labels
1236
+ if (config.scaleShowLabels){
1237
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1238
+ for (var i=0; i<calculatedScale.labels.length; i++){
1239
+ var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
1240
+ longestText = (measuredText > longestText)? measuredText : longestText;
1241
+ }
1242
+ //Add a little extra padding from the y axis
1243
+ longestText +=10;
1244
+ }
1245
+ xAxisLength = width - longestText - widestXLabel;
1246
+ valueHop = Math.floor(xAxisLength/(data.labels.length-1));
1247
+
1248
+ yAxisPosX = width-widestXLabel/2-xAxisLength;
1249
+ xAxisPosY = scaleHeight + config.scaleFontSize/2;
1250
+ }
1251
+ function calculateDrawingSizes(){
1252
+ maxSize = height;
1253
+
1254
+ //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
1255
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1256
+ widestXLabel = 1;
1257
+ for (var i=0; i<data.labels.length; i++){
1258
+ var textLength = ctx.measureText(data.labels[i]).width;
1259
+ //If the text length is longer - make that equal to longest text!
1260
+ widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
1261
+ }
1262
+ if (width/data.labels.length < widestXLabel){
1263
+ rotateLabels = 45;
1264
+ if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
1265
+ rotateLabels = 90;
1266
+ maxSize -= widestXLabel;
1267
+ }
1268
+ else{
1269
+ maxSize -= Math.sin(rotateLabels) * widestXLabel;
1270
+ }
1271
+ }
1272
+ else{
1273
+ maxSize -= config.scaleFontSize;
1274
+ }
1275
+
1276
+ //Add a little padding between the x line and the text
1277
+ maxSize -= 5;
1278
+
1279
+
1280
+ labelHeight = config.scaleFontSize;
1281
+
1282
+ maxSize -= labelHeight;
1283
+ //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
1284
+
1285
+ scaleHeight = maxSize;
1286
+
1287
+ //Then get the area above we can safely draw on.
1288
+
1289
+ }
1290
+ function getValueBounds() {
1291
+ var upperValue = Number.MIN_VALUE;
1292
+ var lowerValue = Number.MAX_VALUE;
1293
+ for (var i=0; i<data.datasets.length; i++){
1294
+ for (var j=0; j<data.datasets[i].data.length; j++){
1295
+ if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1296
+ if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1297
+ }
1298
+ };
1299
+
1300
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1301
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1302
+
1303
+ return {
1304
+ maxValue : upperValue,
1305
+ minValue : lowerValue,
1306
+ maxSteps : maxSteps,
1307
+ minSteps : minSteps
1308
+ };
1309
+
1310
+
1311
+ }
1312
+
1313
+
1314
+ }
1315
+
1316
+ var Bar = function(data,config,ctx){
1317
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0;
1318
+
1319
+ calculateDrawingSizes();
1320
+
1321
+ valueBounds = getValueBounds();
1322
+ //Check and set the scale
1323
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
1324
+ if (!config.scaleOverride){
1325
+
1326
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
1327
+ }
1328
+ else {
1329
+ calculatedScale = {
1330
+ steps : config.scaleSteps,
1331
+ stepValue : config.scaleStepWidth,
1332
+ graphMin : config.scaleStartValue,
1333
+ labels : []
1334
+ }
1335
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
1336
+ }
1337
+
1338
+ scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
1339
+ calculateXAxisSize();
1340
+ animationLoop(config,drawScale,drawBars,ctx);
1341
+
1342
+ function drawBars(animPc){
1343
+ ctx.lineWidth = config.barStrokeWidth;
1344
+ for (var i=0; i<data.datasets.length; i++){
1345
+ for (var j=0; j<data.datasets[i].data.length; j++){
1346
+ var barOffset = yAxisPosX + config.barValueSpacing + valueHop*j + barWidth*i + config.barDatasetSpacing*i + config.barStrokeWidth*i;
1347
+ ctx.fillStyle = cycleColor(data.datasets[i].fillColor, j);
1348
+ ctx.strokeStyle = cycleColor(data.datasets[i].strokeColor, j);
1349
+ ctx.beginPath();
1350
+ ctx.moveTo(barOffset, xAxisPosY);
1351
+ ctx.lineTo(barOffset, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1352
+ ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1353
+ ctx.lineTo(barOffset + barWidth, xAxisPosY);
1354
+ if(config.barShowStroke){
1355
+ ctx.stroke();
1356
+ }
1357
+ ctx.closePath();
1358
+ ctx.fill();
1359
+
1360
+ if(animPc >= 1 && config.showTooltips) {
1361
+ // register tooltips
1362
+ var x = barOffset,
1363
+ height = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop),
1364
+ y = xAxisPosY-height,
1365
+ width = barWidth;
1366
+ registerTooltip(ctx,{type:'rect',x:x,y:y,width:width,height:height},{label:data.labels[j],value:data.datasets[i].data[j]},'Bar');
1367
+ }
1368
+ }
1369
+ }
1370
+
1371
+ }
1372
+ function drawScale(){
1373
+ //X axis line
1374
+ ctx.lineWidth = config.scaleLineWidth;
1375
+ ctx.strokeStyle = config.scaleLineColor;
1376
+ ctx.beginPath();
1377
+ ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
1378
+ ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
1379
+ ctx.stroke();
1380
+
1381
+
1382
+ if (rotateLabels > 0){
1383
+ ctx.save();
1384
+ ctx.textAlign = "right";
1385
+ }
1386
+ else{
1387
+ ctx.textAlign = "center";
1388
+ }
1389
+ ctx.fillStyle = config.scaleFontColor;
1390
+ for (var i=0; i<data.labels.length; i++){
1391
+ ctx.save();
1392
+ if (rotateLabels > 0){
1393
+ ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
1394
+ ctx.rotate(-(rotateLabels * (Math.PI/180)));
1395
+ ctx.fillText(data.labels[i], 0,0);
1396
+ ctx.restore();
1397
+ }
1398
+
1399
+ else{
1400
+ ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3);
1401
+ }
1402
+
1403
+ ctx.beginPath();
1404
+ ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3);
1405
+
1406
+ //Check i isnt 0, so we dont go over the Y axis twice.
1407
+ ctx.lineWidth = config.scaleGridLineWidth;
1408
+ ctx.strokeStyle = config.scaleGridLineColor;
1409
+ ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5);
1410
+ ctx.stroke();
1411
+ }
1412
+
1413
+ //Y axis
1414
+ ctx.lineWidth = config.scaleLineWidth;
1415
+ ctx.strokeStyle = config.scaleLineColor;
1416
+ ctx.beginPath();
1417
+ ctx.moveTo(yAxisPosX,xAxisPosY+5);
1418
+ ctx.lineTo(yAxisPosX,5);
1419
+ ctx.stroke();
1420
+
1421
+ ctx.textAlign = "right";
1422
+ ctx.textBaseline = "middle";
1423
+ for (var j=0; j<calculatedScale.steps; j++){
1424
+ ctx.beginPath();
1425
+ ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
1426
+ if (config.scaleShowGridLines){
1427
+ ctx.lineWidth = config.scaleGridLineWidth;
1428
+ ctx.strokeStyle = config.scaleGridLineColor;
1429
+ ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));
1430
+ }
1431
+ else{
1432
+ ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
1433
+ }
1434
+
1435
+ ctx.stroke();
1436
+ if (config.scaleShowLabels){
1437
+ ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
1438
+ }
1439
+ }
1440
+
1441
+
1442
+ }
1443
+ function calculateXAxisSize(){
1444
+ var longestText = 1;
1445
+ //if we are showing the labels
1446
+ if (config.scaleShowLabels){
1447
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1448
+ for (var i=0; i<calculatedScale.labels.length; i++){
1449
+ var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
1450
+ longestText = (measuredText > longestText)? measuredText : longestText;
1451
+ }
1452
+ //Add a little extra padding from the y axis
1453
+ longestText +=10;
1454
+ }
1455
+ xAxisLength = width - longestText - widestXLabel;
1456
+ valueHop = Math.floor(xAxisLength/(data.labels.length));
1457
+
1458
+ barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length;
1459
+
1460
+ yAxisPosX = width-widestXLabel/2-xAxisLength;
1461
+ xAxisPosY = scaleHeight + config.scaleFontSize/2;
1462
+ }
1463
+ function calculateDrawingSizes(){
1464
+ maxSize = height;
1465
+
1466
+ //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
1467
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1468
+ widestXLabel = 1;
1469
+ for (var i=0; i<data.labels.length; i++){
1470
+ var textLength = ctx.measureText(data.labels[i]).width;
1471
+ //If the text length is longer - make that equal to longest text!
1472
+ widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
1473
+ }
1474
+ if (width/data.labels.length < widestXLabel){
1475
+ rotateLabels = 45;
1476
+ if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
1477
+ rotateLabels = 90;
1478
+ maxSize -= widestXLabel;
1479
+ }
1480
+ else{
1481
+ maxSize -= Math.sin(rotateLabels) * widestXLabel;
1482
+ }
1483
+ }
1484
+ else{
1485
+ maxSize -= config.scaleFontSize;
1486
+ }
1487
+
1488
+ //Add a little padding between the x line and the text
1489
+ maxSize -= 5;
1490
+
1491
+
1492
+ labelHeight = config.scaleFontSize;
1493
+
1494
+ maxSize -= labelHeight;
1495
+ //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
1496
+
1497
+ scaleHeight = maxSize;
1498
+
1499
+ //Then get the area above we can safely draw on.
1500
+
1501
+ }
1502
+ function getValueBounds() {
1503
+ var upperValue = Number.MIN_VALUE;
1504
+ var lowerValue = Number.MAX_VALUE;
1505
+ for (var i=0; i<data.datasets.length; i++){
1506
+ for (var j=0; j<data.datasets[i].data.length; j++){
1507
+ if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1508
+ if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1509
+ }
1510
+ };
1511
+
1512
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1513
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1514
+
1515
+ return {
1516
+ maxValue : upperValue,
1517
+ minValue : lowerValue,
1518
+ maxSteps : maxSteps,
1519
+ minSteps : minSteps
1520
+ };
1521
+
1522
+
1523
+ }
1524
+ }
1525
+
1526
+ function calculateOffset(val,calculatedScale,scaleHop){
1527
+ var outerValue = calculatedScale.steps * calculatedScale.stepValue;
1528
+ var adjustedValue = val - calculatedScale.graphMin;
1529
+ var scalingFactor = CapValue(adjustedValue/outerValue,1,0);
1530
+ return (scaleHop*calculatedScale.steps) * scalingFactor;
1531
+ }
1532
+
1533
+ function animationLoop(config,drawScale,drawData,ctx){
1534
+ var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1,
1535
+ easingFunction = animationOptions[config.animationEasing],
1536
+ percentAnimComplete =(config.animation)? 0 : 1;
1537
+
1538
+
1539
+
1540
+ if (typeof drawScale !== "function") drawScale = function(){};
1541
+
1542
+ requestAnimFrame(animLoop);
1543
+
1544
+ function animateFrame(){
1545
+ var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1;
1546
+ clear(ctx);
1547
+ if(config.scaleOverlay){
1548
+ drawData(easeAdjustedAnimationPercent);
1549
+ drawScale();
1550
+ } else {
1551
+ drawScale();
1552
+ drawData(easeAdjustedAnimationPercent);
1553
+ }
1554
+ }
1555
+ function animLoop(){
1556
+ //We need to check if the animation is incomplete (less than 1), or complete (1).
1557
+ percentAnimComplete += animFrameAmount;
1558
+ animateFrame();
1559
+ //Stop the loop continuing forever
1560
+ if (percentAnimComplete <= 1){
1561
+ requestAnimFrame(animLoop);
1562
+ }
1563
+ else{
1564
+ if (typeof config.onAnimationComplete == "function") config.onAnimationComplete();
1565
+ }
1566
+
1567
+ }
1568
+
1569
+ }
1570
+
1571
+ //Declare global functions to be called within this namespace here.
1572
+
1573
+
1574
+ // shim layer with setTimeout fallback
1575
+ var requestAnimFrame = (function(){
1576
+ return window.requestAnimationFrame ||
1577
+ window.webkitRequestAnimationFrame ||
1578
+ window.mozRequestAnimationFrame ||
1579
+ window.oRequestAnimationFrame ||
1580
+ window.msRequestAnimationFrame ||
1581
+ function(callback) {
1582
+ window.setTimeout(callback, 1000 / 60);
1583
+ };
1584
+ })();
1585
+
1586
+ function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){
1587
+ var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum;
1588
+ valueRange = maxValue - minValue;
1589
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);
1590
+ graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1591
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1592
+ graphRange = graphMax - graphMin;
1593
+ stepValue = Math.pow(10, rangeOrderOfMagnitude);
1594
+ numberOfSteps = Math.round(graphRange / stepValue);
1595
+
1596
+ //Compare number of steps to the max and min for that size graph, and add in half steps if need be.
1597
+ while(numberOfSteps < minSteps || numberOfSteps > maxSteps) {
1598
+ if (numberOfSteps < minSteps){
1599
+ stepValue /= 2;
1600
+ numberOfSteps = Math.round(graphRange/stepValue);
1601
+ }
1602
+ else{
1603
+ stepValue *=2;
1604
+ numberOfSteps = Math.round(graphRange/stepValue);
1605
+ }
1606
+ }
1607
+
1608
+ var labels = [];
1609
+ populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue);
1610
+
1611
+ return {
1612
+ steps : numberOfSteps,
1613
+ stepValue : stepValue,
1614
+ graphMin : graphMin,
1615
+ labels : labels
1616
+ }
1617
+
1618
+ function calculateOrderOfMagnitude(val){
1619
+ return Math.floor(Math.log(val) / Math.LN10);
1620
+ }
1621
+ }
1622
+
1623
+ //Populate an array of all the labels by interpolating the string.
1624
+ function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) {
1625
+ if (labelTemplateString) {
1626
+ //Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
1627
+ for (var i = 1; i < numberOfSteps + 1; i++) {
1628
+ labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))}));
1629
+ }
1630
+ }
1631
+ }
1632
+ // Cycle a given array of colours (for multi coloured bars in bargraphs)
1633
+ function cycleColor(colors, i) {
1634
1634
  return (colors && colors.constructor.name == "Array") ? colors[i % colors.length] : colors;
1635
1635
  }
1636
- //Max value from array
1637
- function Max( array ){
1638
- return Math.max.apply( Math, array );
1639
- };
1640
- //Min value from array
1641
- function Min( array ){
1642
- return Math.min.apply( Math, array );
1643
- };
1644
- //Default if undefined
1645
- function Default(userDeclared,valueIfFalse){
1646
- if(!userDeclared){
1647
- return valueIfFalse;
1648
- } else {
1649
- return userDeclared;
1650
- }
1651
- };
1652
- //Is a number function
1653
- function isNumber(n) {
1654
- return !isNaN(parseFloat(n)) && isFinite(n);
1655
- }
1656
- //Apply cap a value at a high or low number
1657
- function CapValue(valueToCap, maxValue, minValue){
1658
- if(isNumber(maxValue)) {
1659
- if( valueToCap > maxValue ) {
1660
- return maxValue;
1661
- }
1662
- }
1663
- if(isNumber(minValue)){
1664
- if ( valueToCap < minValue ){
1665
- return minValue;
1666
- }
1667
- }
1668
- return valueToCap;
1669
- }
1670
- function getDecimalPlaces (num){
1671
- var numberOfDecimalPlaces;
1672
- if (num%1!=0){
1673
- return num.toString().split(".")[1].length
1674
- }
1675
- else{
1676
- return 0;
1677
- }
1678
-
1679
- }
1680
-
1681
- function mergeChartConfig(defaults,userDefined){
1682
- var returnObj = {};
1683
- for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; }
1684
- for (var attrname in userDefined) {
1685
- if(typeof(userDefined[attrname]) === "object" && defaults[attrname]) {
1686
- returnObj[attrname] = mergeChartConfig(defaults[attrname], userDefined[attrname]);
1687
- } else {
1688
- returnObj[attrname] = userDefined[attrname];
1689
- }
1690
- }
1691
- return returnObj;
1692
- }
1693
-
1694
- //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
1695
- var cache = {};
1696
-
1697
- function tmpl(str, data){
1698
- // Figure out if we're getting a template, or if we need to
1699
- // load the template - and be sure to cache the result.
1700
- var fn = !/\W/.test(str) ?
1701
- cache[str] = cache[str] ||
1702
- tmpl(document.getElementById(str).innerHTML) :
1703
-
1704
- // Generate a reusable function that will serve as a template
1705
- // generator (and which will be cached).
1706
- new Function("obj",
1707
- "var p=[],print=function(){p.push.apply(p,arguments);};" +
1708
-
1709
- // Introduce the data as local variables using with(){}
1710
- "with(obj){p.push('" +
1711
-
1712
- // Convert the template into pure JavaScript
1713
- str
1714
- .replace(/[\r\t\n]/g, " ")
1715
- .split("<%").join("\t")
1716
- .replace(/((^|%>)[^\t]*)'/g, "$1\r")
1717
- .replace(/\t=(.*?)%>/g, "',$1,'")
1718
- .split("\t").join("');")
1719
- .split("%>").join("p.push('")
1720
- .split("\r").join("\\'")
1721
- + "');}return p.join('');");
1722
-
1723
- // Provide some basic currying to the user
1724
- return data ? fn( data ) : fn;
1725
- };
1726
-
1727
- function getFadeColor(percent, primColor, secColor) {
1728
- var pseudoEl = document.createElement('div'),
1729
- rgbPrim,
1730
- rgbSec;
1731
- pseudoEl.style.color = primColor;
1732
- document.body.appendChild(pseudoEl);
1733
- rgbPrim = window.getComputedStyle(pseudoEl).color;
1734
- pseudoEl.style.color = secColor;
1735
- rgbSec = window.getComputedStyle(pseudoEl).color;
1736
- var regex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/,
1737
- valuesP = regex.exec(rgbPrim),
1738
- valuesS = regex.exec(rgbSec),
1739
- rP = Math.round(parseFloat(valuesP[1])),
1740
- gP = Math.round(parseFloat(valuesP[2])),
1741
- bP = Math.round(parseFloat(valuesP[3])),
1742
- rS = Math.round(parseFloat(valuesS[1])),
1743
- gS = Math.round(parseFloat(valuesS[2])),
1744
- bS = Math.round(parseFloat(valuesS[3])),
1745
- rCur = parseInt((rP-rS)*percent+rS),
1746
- gCur = parseInt((gP-gS)*percent+gS),
1747
- bCur = parseInt((bP-bS)*percent+bS);
1748
- pseudoEl.parentNode.removeChild(pseudoEl);
1749
- return "rgb("+rCur+','+gCur+','+bCur+')';
1750
- }
1636
+ //Max value from array
1637
+ function Max( array ){
1638
+ return Math.max.apply( Math, array );
1639
+ };
1640
+ //Min value from array
1641
+ function Min( array ){
1642
+ return Math.min.apply( Math, array );
1643
+ };
1644
+ //Default if undefined
1645
+ function Default(userDeclared,valueIfFalse){
1646
+ if(!userDeclared){
1647
+ return valueIfFalse;
1648
+ } else {
1649
+ return userDeclared;
1650
+ }
1651
+ };
1652
+ //Is a number function
1653
+ function isNumber(n) {
1654
+ return !isNaN(parseFloat(n)) && isFinite(n);
1655
+ }
1656
+ //Apply cap a value at a high or low number
1657
+ function CapValue(valueToCap, maxValue, minValue){
1658
+ if(isNumber(maxValue)) {
1659
+ if( valueToCap > maxValue ) {
1660
+ return maxValue;
1661
+ }
1662
+ }
1663
+ if(isNumber(minValue)){
1664
+ if ( valueToCap < minValue ){
1665
+ return minValue;
1666
+ }
1667
+ }
1668
+ return valueToCap;
1669
+ }
1670
+ function getDecimalPlaces (num){
1671
+ var numberOfDecimalPlaces;
1672
+ if (num%1!=0){
1673
+ return num.toString().split(".")[1].length
1674
+ }
1675
+ else{
1676
+ return 0;
1677
+ }
1678
+
1679
+ }
1680
+
1681
+ function mergeChartConfig(defaults,userDefined){
1682
+ var returnObj = {};
1683
+ for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; }
1684
+ for (var attrname in userDefined) {
1685
+ if(typeof(userDefined[attrname]) === "object" && defaults[attrname]) {
1686
+ returnObj[attrname] = mergeChartConfig(defaults[attrname], userDefined[attrname]);
1687
+ } else {
1688
+ returnObj[attrname] = userDefined[attrname];
1689
+ }
1690
+ }
1691
+ return returnObj;
1692
+ }
1693
+
1694
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
1695
+ var cache = {};
1696
+
1697
+ function tmpl(str, data){
1698
+ // Figure out if we're getting a template, or if we need to
1699
+ // load the template - and be sure to cache the result.
1700
+ var fn = !/\W/.test(str) ?
1701
+ cache[str] = cache[str] ||
1702
+ tmpl(document.getElementById(str).innerHTML) :
1703
+
1704
+ // Generate a reusable function that will serve as a template
1705
+ // generator (and which will be cached).
1706
+ new Function("obj",
1707
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
1708
+
1709
+ // Introduce the data as local variables using with(){}
1710
+ "with(obj){p.push('" +
1711
+
1712
+ // Convert the template into pure JavaScript
1713
+ str
1714
+ .replace(/[\r\t\n]/g, " ")
1715
+ .split("<%").join("\t")
1716
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
1717
+ .replace(/\t=(.*?)%>/g, "',$1,'")
1718
+ .split("\t").join("');")
1719
+ .split("%>").join("p.push('")
1720
+ .split("\r").join("\\'")
1721
+ + "');}return p.join('');");
1722
+
1723
+ // Provide some basic currying to the user
1724
+ return data ? fn( data ) : fn;
1725
+ };
1726
+
1727
+ function getFadeColor(percent, primColor, secColor) {
1728
+ var pseudoEl = document.createElement('div'),
1729
+ rgbPrim,
1730
+ rgbSec;
1731
+ pseudoEl.style.color = primColor;
1732
+ document.body.appendChild(pseudoEl);
1733
+ rgbPrim = window.getComputedStyle(pseudoEl).color;
1734
+ pseudoEl.style.color = secColor;
1735
+ rgbSec = window.getComputedStyle(pseudoEl).color;
1736
+ var regex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/,
1737
+ valuesP = regex.exec(rgbPrim),
1738
+ valuesS = regex.exec(rgbSec),
1739
+ rP = Math.round(parseFloat(valuesP[1])),
1740
+ gP = Math.round(parseFloat(valuesP[2])),
1741
+ bP = Math.round(parseFloat(valuesP[3])),
1742
+ rS = Math.round(parseFloat(valuesS[1])),
1743
+ gS = Math.round(parseFloat(valuesS[2])),
1744
+ bS = Math.round(parseFloat(valuesS[3])),
1745
+ rCur = parseInt((rP-rS)*percent+rS),
1746
+ gCur = parseInt((gP-gS)*percent+gS),
1747
+ bCur = parseInt((bP-bS)*percent+bS);
1748
+ pseudoEl.parentNode.removeChild(pseudoEl);
1749
+ return "rgb("+rCur+','+gCur+','+bCur+')';
1750
+ }
1751
1751
  }