chartjs-ror 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -9,8 +9,6 @@ Simplifies using [Chart.js][] in Rails views.
9
9
  * Optional alternative (better?) abscissa scale calculations.
10
10
  * Utility method for filling in gaps in integer series.
11
11
 
12
- NOTE: this is Rails 3.0 only at the moment, so pre-asset pipeline. I plan to upgrade soon.
13
-
14
12
 
15
13
  ## Installation
16
14
 
@@ -22,7 +20,14 @@ And then execute:
22
20
 
23
21
  $ bundle
24
22
 
25
- Add [Chart.js][] (and [Modernizr][] and [ExplorerCanvas][] if you need them) to your assets.
23
+ In your JavaScript manifest, add:
24
+
25
+ ```javascript
26
+ //= require Chart
27
+ //= require excanvas
28
+ ```
29
+
30
+ Add [Modernizr][] if you need it to your app's assets.
26
31
 
27
32
  Ensure your browsers will display `<figure/>` and `<figcaption/>` correctly.
28
33
 
@@ -17,6 +17,6 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.add_dependency 'activesupport', '>3'
20
+ gem.add_dependency 'rails', '~> 3.1'
21
21
  gem.add_development_dependency 'rake'
22
22
  end
@@ -1,4 +1,4 @@
1
- require 'chartjs/railtie' if defined?(Rails)
1
+ require 'chartjs/engine'
2
2
  require "chartjs/version"
3
3
 
4
4
  module Chartjs
@@ -2,7 +2,7 @@ require 'chartjs/chart_helpers'
2
2
  require 'chartjs/axis_helpers'
3
3
 
4
4
  module Chartjs
5
- class Railtie < Rails::Railtie
5
+ class Engine < Rails::Engine
6
6
  initializer 'chartjs.chart_helpers' do
7
7
  ActionView::Base.send :include, Chartjs::ChartHelpers
8
8
  ActionView::Base.send :include, Chartjs::AxisHelpers
@@ -1,3 +1,3 @@
1
1
  module Chartjs
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -0,0 +1,1431 @@
1
+ /*!
2
+ * Chart.js
3
+ * http://chartjs.org/
4
+ *
5
+ * Copyright 2013 Nick Downie
6
+ * Released under the MIT license
7
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
8
+ */
9
+
10
+ //Define the global Chart Variable as a class.
11
+ window.Chart = function(context){
12
+
13
+ var chart = this;
14
+
15
+
16
+ //Easing functions adapted from Robert Penner's easing equations
17
+ //http://www.robertpenner.com/easing/
18
+
19
+ var animationOptions = {
20
+ linear : function (t){
21
+ return t;
22
+ },
23
+ easeInQuad: function (t) {
24
+ return t*t;
25
+ },
26
+ easeOutQuad: function (t) {
27
+ return -1 *t*(t-2);
28
+ },
29
+ easeInOutQuad: function (t) {
30
+ if ((t/=1/2) < 1) return 1/2*t*t;
31
+ return -1/2 * ((--t)*(t-2) - 1);
32
+ },
33
+ easeInCubic: function (t) {
34
+ return t*t*t;
35
+ },
36
+ easeOutCubic: function (t) {
37
+ return 1*((t=t/1-1)*t*t + 1);
38
+ },
39
+ easeInOutCubic: function (t) {
40
+ if ((t/=1/2) < 1) return 1/2*t*t*t;
41
+ return 1/2*((t-=2)*t*t + 2);
42
+ },
43
+ easeInQuart: function (t) {
44
+ return t*t*t*t;
45
+ },
46
+ easeOutQuart: function (t) {
47
+ return -1 * ((t=t/1-1)*t*t*t - 1);
48
+ },
49
+ easeInOutQuart: function (t) {
50
+ if ((t/=1/2) < 1) return 1/2*t*t*t*t;
51
+ return -1/2 * ((t-=2)*t*t*t - 2);
52
+ },
53
+ easeInQuint: function (t) {
54
+ return 1*(t/=1)*t*t*t*t;
55
+ },
56
+ easeOutQuint: function (t) {
57
+ return 1*((t=t/1-1)*t*t*t*t + 1);
58
+ },
59
+ easeInOutQuint: function (t) {
60
+ if ((t/=1/2) < 1) return 1/2*t*t*t*t*t;
61
+ return 1/2*((t-=2)*t*t*t*t + 2);
62
+ },
63
+ easeInSine: function (t) {
64
+ return -1 * Math.cos(t/1 * (Math.PI/2)) + 1;
65
+ },
66
+ easeOutSine: function (t) {
67
+ return 1 * Math.sin(t/1 * (Math.PI/2));
68
+ },
69
+ easeInOutSine: function (t) {
70
+ return -1/2 * (Math.cos(Math.PI*t/1) - 1);
71
+ },
72
+ easeInExpo: function (t) {
73
+ return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1));
74
+ },
75
+ easeOutExpo: function (t) {
76
+ return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1);
77
+ },
78
+ easeInOutExpo: function (t) {
79
+ if (t==0) return 0;
80
+ if (t==1) return 1;
81
+ if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1));
82
+ return 1/2 * (-Math.pow(2, -10 * --t) + 2);
83
+ },
84
+ easeInCirc: function (t) {
85
+ if (t>=1) return t;
86
+ return -1 * (Math.sqrt(1 - (t/=1)*t) - 1);
87
+ },
88
+ easeOutCirc: function (t) {
89
+ return 1 * Math.sqrt(1 - (t=t/1-1)*t);
90
+ },
91
+ easeInOutCirc: function (t) {
92
+ if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1);
93
+ return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1);
94
+ },
95
+ easeInElastic: function (t) {
96
+ var s=1.70158;var p=0;var a=1;
97
+ if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
98
+ if (a < Math.abs(1)) { a=1; var s=p/4; }
99
+ else var s = p/(2*Math.PI) * Math.asin (1/a);
100
+ return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
101
+ },
102
+ easeOutElastic: function (t) {
103
+ var s=1.70158;var p=0;var a=1;
104
+ if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
105
+ if (a < Math.abs(1)) { a=1; var s=p/4; }
106
+ else var s = p/(2*Math.PI) * Math.asin (1/a);
107
+ return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1;
108
+ },
109
+ easeInOutElastic: function (t) {
110
+ var s=1.70158;var p=0;var a=1;
111
+ if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5);
112
+ if (a < Math.abs(1)) { a=1; var s=p/4; }
113
+ else var s = p/(2*Math.PI) * Math.asin (1/a);
114
+ if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
115
+ return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1;
116
+ },
117
+ easeInBack: function (t) {
118
+ var s = 1.70158;
119
+ return 1*(t/=1)*t*((s+1)*t - s);
120
+ },
121
+ easeOutBack: function (t) {
122
+ var s = 1.70158;
123
+ return 1*((t=t/1-1)*t*((s+1)*t + s) + 1);
124
+ },
125
+ easeInOutBack: function (t) {
126
+ var s = 1.70158;
127
+ if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s));
128
+ return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2);
129
+ },
130
+ easeInBounce: function (t) {
131
+ return 1 - animationOptions.easeOutBounce (1-t);
132
+ },
133
+ easeOutBounce: function (t) {
134
+ if ((t/=1) < (1/2.75)) {
135
+ return 1*(7.5625*t*t);
136
+ } else if (t < (2/2.75)) {
137
+ return 1*(7.5625*(t-=(1.5/2.75))*t + .75);
138
+ } else if (t < (2.5/2.75)) {
139
+ return 1*(7.5625*(t-=(2.25/2.75))*t + .9375);
140
+ } else {
141
+ return 1*(7.5625*(t-=(2.625/2.75))*t + .984375);
142
+ }
143
+ },
144
+ easeInOutBounce: function (t) {
145
+ if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5;
146
+ return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5;
147
+ }
148
+ };
149
+
150
+ //Variables global to the chart
151
+ var width = context.canvas.width;
152
+ var height = context.canvas.height;
153
+
154
+
155
+ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
156
+ if (window.devicePixelRatio) {
157
+ context.canvas.style.width = width + "px";
158
+ context.canvas.style.height = height + "px";
159
+ context.canvas.height = height * window.devicePixelRatio;
160
+ context.canvas.width = width * window.devicePixelRatio;
161
+ context.scale(window.devicePixelRatio, window.devicePixelRatio);
162
+ }
163
+
164
+ this.PolarArea = function(data,options){
165
+
166
+ chart.PolarArea.defaults = {
167
+ scaleOverlay : true,
168
+ scaleOverride : false,
169
+ scaleSteps : null,
170
+ scaleStepWidth : null,
171
+ scaleStartValue : null,
172
+ scaleShowLine : true,
173
+ scaleLineColor : "rgba(0,0,0,.1)",
174
+ scaleLineWidth : 1,
175
+ scaleShowLabels : true,
176
+ scaleLabel : "<%=value%>",
177
+ scaleFontFamily : "'Arial'",
178
+ scaleFontSize : 12,
179
+ scaleFontStyle : "normal",
180
+ scaleFontColor : "#666",
181
+ scaleShowLabelBackdrop : true,
182
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
183
+ scaleBackdropPaddingY : 2,
184
+ scaleBackdropPaddingX : 2,
185
+ segmentShowStroke : true,
186
+ segmentStrokeColor : "#fff",
187
+ segmentStrokeWidth : 2,
188
+ animation : true,
189
+ animationSteps : 100,
190
+ animationEasing : "easeOutBounce",
191
+ animateRotate : true,
192
+ animateScale : false,
193
+ onAnimationComplete : null
194
+ };
195
+
196
+ var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults;
197
+
198
+ return new PolarArea(data,config,context);
199
+ };
200
+
201
+ this.Radar = function(data,options){
202
+
203
+ chart.Radar.defaults = {
204
+ scaleOverlay : false,
205
+ scaleOverride : false,
206
+ scaleSteps : null,
207
+ scaleStepWidth : null,
208
+ scaleStartValue : null,
209
+ scaleShowLine : true,
210
+ scaleLineColor : "rgba(0,0,0,.1)",
211
+ scaleLineWidth : 1,
212
+ scaleShowLabels : false,
213
+ scaleLabel : "<%=value%>",
214
+ scaleFontFamily : "'Arial'",
215
+ scaleFontSize : 12,
216
+ scaleFontStyle : "normal",
217
+ scaleFontColor : "#666",
218
+ scaleShowLabelBackdrop : true,
219
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
220
+ scaleBackdropPaddingY : 2,
221
+ scaleBackdropPaddingX : 2,
222
+ angleShowLineOut : true,
223
+ angleLineColor : "rgba(0,0,0,.1)",
224
+ angleLineWidth : 1,
225
+ pointLabelFontFamily : "'Arial'",
226
+ pointLabelFontStyle : "normal",
227
+ pointLabelFontSize : 12,
228
+ pointLabelFontColor : "#666",
229
+ pointDot : true,
230
+ pointDotRadius : 3,
231
+ pointDotStrokeWidth : 1,
232
+ datasetStroke : true,
233
+ datasetStrokeWidth : 2,
234
+ datasetFill : true,
235
+ animation : true,
236
+ animationSteps : 60,
237
+ animationEasing : "easeOutQuart",
238
+ onAnimationComplete : null
239
+ };
240
+
241
+ var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults;
242
+
243
+ return new Radar(data,config,context);
244
+ };
245
+
246
+ this.Pie = function(data,options){
247
+ chart.Pie.defaults = {
248
+ segmentShowStroke : true,
249
+ segmentStrokeColor : "#fff",
250
+ segmentStrokeWidth : 2,
251
+ animation : true,
252
+ animationSteps : 100,
253
+ animationEasing : "easeOutBounce",
254
+ animateRotate : true,
255
+ animateScale : false,
256
+ onAnimationComplete : null
257
+ };
258
+
259
+ var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults;
260
+
261
+ return new Pie(data,config,context);
262
+ };
263
+
264
+ this.Doughnut = function(data,options){
265
+
266
+ chart.Doughnut.defaults = {
267
+ segmentShowStroke : true,
268
+ segmentStrokeColor : "#fff",
269
+ segmentStrokeWidth : 2,
270
+ percentageInnerCutout : 50,
271
+ animation : true,
272
+ animationSteps : 100,
273
+ animationEasing : "easeOutBounce",
274
+ animateRotate : true,
275
+ animateScale : false,
276
+ onAnimationComplete : null
277
+ };
278
+
279
+ var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults;
280
+
281
+ return new Doughnut(data,config,context);
282
+
283
+ };
284
+
285
+ this.Line = function(data,options){
286
+
287
+ chart.Line.defaults = {
288
+ scaleOverlay : false,
289
+ scaleOverride : false,
290
+ scaleSteps : null,
291
+ scaleStepWidth : null,
292
+ scaleStartValue : null,
293
+ scaleLineColor : "rgba(0,0,0,.1)",
294
+ scaleLineWidth : 1,
295
+ scaleShowLabels : true,
296
+ scaleLabel : "<%=value%>",
297
+ scaleFontFamily : "'Arial'",
298
+ scaleFontSize : 12,
299
+ scaleFontStyle : "normal",
300
+ scaleFontColor : "#666",
301
+ scaleShowGridLines : true,
302
+ scaleGridLineColor : "rgba(0,0,0,.05)",
303
+ scaleGridLineWidth : 1,
304
+ bezierCurve : true,
305
+ pointDot : true,
306
+ pointDotRadius : 4,
307
+ pointDotStrokeWidth : 2,
308
+ datasetStroke : true,
309
+ datasetStrokeWidth : 2,
310
+ datasetFill : true,
311
+ animation : true,
312
+ animationSteps : 60,
313
+ animationEasing : "easeOutQuart",
314
+ onAnimationComplete : null
315
+ };
316
+ var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults;
317
+
318
+ return new Line(data,config,context);
319
+ }
320
+
321
+ this.Bar = function(data,options){
322
+ chart.Bar.defaults = {
323
+ scaleOverlay : false,
324
+ scaleOverride : false,
325
+ scaleSteps : null,
326
+ scaleStepWidth : null,
327
+ scaleStartValue : null,
328
+ scaleLineColor : "rgba(0,0,0,.1)",
329
+ scaleLineWidth : 1,
330
+ scaleShowLabels : true,
331
+ scaleLabel : "<%=value%>",
332
+ scaleFontFamily : "'Arial'",
333
+ scaleFontSize : 12,
334
+ scaleFontStyle : "normal",
335
+ scaleFontColor : "#666",
336
+ scaleShowGridLines : true,
337
+ scaleGridLineColor : "rgba(0,0,0,.05)",
338
+ scaleGridLineWidth : 1,
339
+ barShowStroke : true,
340
+ barStrokeWidth : 2,
341
+ barValueSpacing : 5,
342
+ barDatasetSpacing : 1,
343
+ animation : true,
344
+ animationSteps : 60,
345
+ animationEasing : "easeOutQuart",
346
+ onAnimationComplete : null
347
+ };
348
+ var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults;
349
+
350
+ return new Bar(data,config,context);
351
+ }
352
+
353
+ var clear = function(c){
354
+ c.clearRect(0, 0, width, height);
355
+ };
356
+
357
+ var PolarArea = function(data,config,ctx){
358
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
359
+
360
+
361
+ calculateDrawingSizes();
362
+
363
+ valueBounds = getValueBounds();
364
+
365
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
366
+
367
+ //Check and set the scale
368
+ if (!config.scaleOverride){
369
+
370
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
371
+ }
372
+ else {
373
+ calculatedScale = {
374
+ steps : config.scaleSteps,
375
+ stepValue : config.scaleStepWidth,
376
+ graphMin : config.scaleStartValue,
377
+ labels : []
378
+ }
379
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
380
+ }
381
+
382
+ scaleHop = maxSize/(calculatedScale.steps);
383
+
384
+ //Wrap in an animation loop wrapper
385
+ animationLoop(config,drawScale,drawAllSegments,ctx);
386
+
387
+ function calculateDrawingSizes(){
388
+ maxSize = (Min([width,height])/2);
389
+ //Remove whatever is larger - the font size or line width.
390
+
391
+ maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]);
392
+
393
+ labelHeight = config.scaleFontSize*2;
394
+ //If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region.
395
+ if (config.scaleShowLabelBackdrop){
396
+ labelHeight += (2 * config.scaleBackdropPaddingY);
397
+ maxSize -= config.scaleBackdropPaddingY*1.5;
398
+ }
399
+
400
+ scaleHeight = maxSize;
401
+ //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
402
+ labelHeight = Default(labelHeight,5);
403
+ }
404
+ function drawScale(){
405
+ for (var i=0; i<calculatedScale.steps; i++){
406
+ //If the line object is there
407
+ if (config.scaleShowLine){
408
+ ctx.beginPath();
409
+ ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true);
410
+ ctx.strokeStyle = config.scaleLineColor;
411
+ ctx.lineWidth = config.scaleLineWidth;
412
+ ctx.stroke();
413
+ }
414
+
415
+ if (config.scaleShowLabels){
416
+ ctx.textAlign = "center";
417
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
418
+ var label = calculatedScale.labels[i];
419
+ //If the backdrop object is within the font object
420
+ if (config.scaleShowLabelBackdrop){
421
+ var textWidth = ctx.measureText(label).width;
422
+ ctx.fillStyle = config.scaleBackdropColor;
423
+ ctx.beginPath();
424
+ ctx.rect(
425
+ Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX), //X
426
+ Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
427
+ Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
428
+ Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
429
+ );
430
+ ctx.fill();
431
+ }
432
+ ctx.textBaseline = "middle";
433
+ ctx.fillStyle = config.scaleFontColor;
434
+ ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1)));
435
+ }
436
+ }
437
+ }
438
+ function drawAllSegments(animationDecimal){
439
+ var startAngle = -Math.PI/2,
440
+ angleStep = (Math.PI*2)/data.length,
441
+ scaleAnimation = 1,
442
+ rotateAnimation = 1;
443
+ if (config.animation) {
444
+ if (config.animateScale) {
445
+ scaleAnimation = animationDecimal;
446
+ }
447
+ if (config.animateRotate){
448
+ rotateAnimation = animationDecimal;
449
+ }
450
+ }
451
+
452
+ for (var i=0; i<data.length; i++){
453
+
454
+ ctx.beginPath();
455
+ ctx.arc(width/2,height/2,scaleAnimation * calculateOffset(data[i].value,calculatedScale,scaleHop),startAngle, startAngle + rotateAnimation*angleStep, false);
456
+ ctx.lineTo(width/2,height/2);
457
+ ctx.closePath();
458
+ ctx.fillStyle = data[i].color;
459
+ ctx.fill();
460
+
461
+ if(config.segmentShowStroke){
462
+ ctx.strokeStyle = config.segmentStrokeColor;
463
+ ctx.lineWidth = config.segmentStrokeWidth;
464
+ ctx.stroke();
465
+ }
466
+ startAngle += rotateAnimation*angleStep;
467
+ }
468
+ }
469
+ function getValueBounds() {
470
+ var upperValue = Number.MIN_VALUE;
471
+ var lowerValue = Number.MAX_VALUE;
472
+ for (var i=0; i<data.length; i++){
473
+ if (data[i].value > upperValue) {upperValue = data[i].value;}
474
+ if (data[i].value < lowerValue) {lowerValue = data[i].value;}
475
+ };
476
+
477
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
478
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
479
+
480
+ return {
481
+ maxValue : upperValue,
482
+ minValue : lowerValue,
483
+ maxSteps : maxSteps,
484
+ minSteps : minSteps
485
+ };
486
+
487
+
488
+ }
489
+ }
490
+
491
+ var Radar = function (data,config,ctx) {
492
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
493
+
494
+ //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up.
495
+ if (!data.labels) data.labels = [];
496
+
497
+ calculateDrawingSizes();
498
+
499
+ var valueBounds = getValueBounds();
500
+
501
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
502
+
503
+ //Check and set the scale
504
+ if (!config.scaleOverride){
505
+
506
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
507
+ }
508
+ else {
509
+ calculatedScale = {
510
+ steps : config.scaleSteps,
511
+ stepValue : config.scaleStepWidth,
512
+ graphMin : config.scaleStartValue,
513
+ labels : []
514
+ }
515
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
516
+ }
517
+
518
+ scaleHop = maxSize/(calculatedScale.steps);
519
+
520
+ animationLoop(config,drawScale,drawAllDataPoints,ctx);
521
+
522
+ //Radar specific functions.
523
+ function drawAllDataPoints(animationDecimal){
524
+ var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
525
+
526
+ ctx.save();
527
+ //translate to the centre of the canvas.
528
+ ctx.translate(width/2,height/2);
529
+
530
+ //We accept multiple data sets for radar charts, so show loop through each set
531
+ for (var i=0; i<data.datasets.length; i++){
532
+ ctx.beginPath();
533
+
534
+ ctx.moveTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)));
535
+ for (var j=1; j<data.datasets[i].data.length; j++){
536
+ ctx.rotate(rotationDegree);
537
+ ctx.lineTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)));
538
+
539
+ }
540
+ ctx.closePath();
541
+
542
+
543
+ ctx.fillStyle = data.datasets[i].fillColor;
544
+ ctx.strokeStyle = data.datasets[i].strokeColor;
545
+ ctx.lineWidth = config.datasetStrokeWidth;
546
+ ctx.fill();
547
+ ctx.stroke();
548
+
549
+
550
+ if (config.pointDot){
551
+ ctx.fillStyle = data.datasets[i].pointColor;
552
+ ctx.strokeStyle = data.datasets[i].pointStrokeColor;
553
+ ctx.lineWidth = config.pointDotStrokeWidth;
554
+ for (var k=0; k<data.datasets[i].data.length; k++){
555
+ ctx.rotate(rotationDegree);
556
+ ctx.beginPath();
557
+ ctx.arc(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,2*Math.PI,false);
558
+ ctx.fill();
559
+ ctx.stroke();
560
+ }
561
+
562
+ }
563
+ ctx.rotate(rotationDegree);
564
+
565
+ }
566
+ ctx.restore();
567
+
568
+
569
+ }
570
+ function drawScale(){
571
+ var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
572
+ ctx.save();
573
+ ctx.translate(width / 2, height / 2);
574
+
575
+ if (config.angleShowLineOut){
576
+ ctx.strokeStyle = config.angleLineColor;
577
+ ctx.lineWidth = config.angleLineWidth;
578
+ for (var h=0; h<data.datasets[0].data.length; h++){
579
+
580
+ ctx.rotate(rotationDegree);
581
+ ctx.beginPath();
582
+ ctx.moveTo(0,0);
583
+ ctx.lineTo(0,-maxSize);
584
+ ctx.stroke();
585
+ }
586
+ }
587
+
588
+ for (var i=0; i<calculatedScale.steps; i++){
589
+ ctx.beginPath();
590
+
591
+ if(config.scaleShowLine){
592
+ ctx.strokeStyle = config.scaleLineColor;
593
+ ctx.lineWidth = config.scaleLineWidth;
594
+ ctx.moveTo(0,-scaleHop * (i+1));
595
+ for (var j=0; j<data.datasets[0].data.length; j++){
596
+ ctx.rotate(rotationDegree);
597
+ ctx.lineTo(0,-scaleHop * (i+1));
598
+ }
599
+ ctx.closePath();
600
+ ctx.stroke();
601
+
602
+ }
603
+
604
+ if (config.scaleShowLabels){
605
+ ctx.textAlign = 'center';
606
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
607
+ ctx.textBaseline = "middle";
608
+
609
+ if (config.scaleShowLabelBackdrop){
610
+ var textWidth = ctx.measureText(calculatedScale.labels[i]).width;
611
+ ctx.fillStyle = config.scaleBackdropColor;
612
+ ctx.beginPath();
613
+ ctx.rect(
614
+ Math.round(- textWidth/2 - config.scaleBackdropPaddingX), //X
615
+ Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
616
+ Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
617
+ Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
618
+ );
619
+ ctx.fill();
620
+ }
621
+ ctx.fillStyle = config.scaleFontColor;
622
+ ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1));
623
+ }
624
+
625
+ }
626
+ for (var k=0; k<data.labels.length; k++){
627
+ ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
628
+ ctx.fillStyle = config.pointLabelFontColor;
629
+ var opposite = Math.sin(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
630
+ var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
631
+
632
+ if(rotationDegree*k == Math.PI || rotationDegree*k == 0){
633
+ ctx.textAlign = "center";
634
+ }
635
+ else if(rotationDegree*k > Math.PI){
636
+ ctx.textAlign = "right";
637
+ }
638
+ else{
639
+ ctx.textAlign = "left";
640
+ }
641
+
642
+ ctx.textBaseline = "middle";
643
+
644
+ ctx.fillText(data.labels[k],opposite,-adjacent);
645
+
646
+ }
647
+ ctx.restore();
648
+ };
649
+ function calculateDrawingSizes(){
650
+ maxSize = (Min([width,height])/2);
651
+
652
+ labelHeight = config.scaleFontSize*2;
653
+
654
+ var labelLength = 0;
655
+ for (var i=0; i<data.labels.length; i++){
656
+ ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
657
+ var textMeasurement = ctx.measureText(data.labels[i]).width;
658
+ if(textMeasurement>labelLength) labelLength = textMeasurement;
659
+ }
660
+
661
+ //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.
662
+ maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]);
663
+
664
+ maxSize -= config.pointLabelFontSize;
665
+ maxSize = CapValue(maxSize, null, 0);
666
+ scaleHeight = maxSize;
667
+ //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
668
+ labelHeight = Default(labelHeight,5);
669
+ };
670
+ function getValueBounds() {
671
+ var upperValue = Number.MIN_VALUE;
672
+ var lowerValue = Number.MAX_VALUE;
673
+
674
+ for (var i=0; i<data.datasets.length; i++){
675
+ for (var j=0; j<data.datasets[i].data.length; j++){
676
+ if (data.datasets[i].data[j] > upperValue){upperValue = data.datasets[i].data[j]}
677
+ if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]}
678
+ }
679
+ }
680
+
681
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
682
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
683
+
684
+ return {
685
+ maxValue : upperValue,
686
+ minValue : lowerValue,
687
+ maxSteps : maxSteps,
688
+ minSteps : minSteps
689
+ };
690
+
691
+
692
+ }
693
+ }
694
+
695
+ var Pie = function(data,config,ctx){
696
+ var segmentTotal = 0;
697
+
698
+ //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
699
+ var pieRadius = Min([height/2,width/2]) - 5;
700
+
701
+ for (var i=0; i<data.length; i++){
702
+ segmentTotal += data[i].value;
703
+ }
704
+
705
+
706
+ animationLoop(config,null,drawPieSegments,ctx);
707
+
708
+ function drawPieSegments (animationDecimal){
709
+ var cumulativeAngle = -Math.PI/2,
710
+ scaleAnimation = 1,
711
+ rotateAnimation = 1;
712
+ if (config.animation) {
713
+ if (config.animateScale) {
714
+ scaleAnimation = animationDecimal;
715
+ }
716
+ if (config.animateRotate){
717
+ rotateAnimation = animationDecimal;
718
+ }
719
+ }
720
+ for (var i=0; i<data.length; i++){
721
+ var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
722
+ ctx.beginPath();
723
+ ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle);
724
+ ctx.lineTo(width/2,height/2);
725
+ ctx.closePath();
726
+ ctx.fillStyle = data[i].color;
727
+ ctx.fill();
728
+
729
+ if(config.segmentShowStroke){
730
+ ctx.lineWidth = config.segmentStrokeWidth;
731
+ ctx.strokeStyle = config.segmentStrokeColor;
732
+ ctx.stroke();
733
+ }
734
+ cumulativeAngle += segmentAngle;
735
+ }
736
+ }
737
+ }
738
+
739
+ var Doughnut = function(data,config,ctx){
740
+ var segmentTotal = 0;
741
+
742
+ //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
743
+ var doughnutRadius = Min([height/2,width/2]) - 5;
744
+
745
+ var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100);
746
+
747
+ for (var i=0; i<data.length; i++){
748
+ segmentTotal += data[i].value;
749
+ }
750
+
751
+
752
+ animationLoop(config,null,drawPieSegments,ctx);
753
+
754
+
755
+ function drawPieSegments (animationDecimal){
756
+ var cumulativeAngle = -Math.PI/2,
757
+ scaleAnimation = 1,
758
+ rotateAnimation = 1;
759
+ if (config.animation) {
760
+ if (config.animateScale) {
761
+ scaleAnimation = animationDecimal;
762
+ }
763
+ if (config.animateRotate){
764
+ rotateAnimation = animationDecimal;
765
+ }
766
+ }
767
+ for (var i=0; i<data.length; i++){
768
+ var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
769
+ ctx.beginPath();
770
+ ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false);
771
+ ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true);
772
+ ctx.closePath();
773
+ ctx.fillStyle = data[i].color;
774
+ ctx.fill();
775
+
776
+ if(config.segmentShowStroke){
777
+ ctx.lineWidth = config.segmentStrokeWidth;
778
+ ctx.strokeStyle = config.segmentStrokeColor;
779
+ ctx.stroke();
780
+ }
781
+ cumulativeAngle += segmentAngle;
782
+ }
783
+ }
784
+
785
+
786
+
787
+ }
788
+
789
+ var Line = function(data,config,ctx){
790
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0;
791
+
792
+ calculateDrawingSizes();
793
+
794
+ valueBounds = getValueBounds();
795
+ //Check and set the scale
796
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
797
+ if (!config.scaleOverride){
798
+
799
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
800
+ }
801
+ else {
802
+ calculatedScale = {
803
+ steps : config.scaleSteps,
804
+ stepValue : config.scaleStepWidth,
805
+ graphMin : config.scaleStartValue,
806
+ labels : []
807
+ }
808
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
809
+ }
810
+
811
+ scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
812
+ calculateXAxisSize();
813
+ animationLoop(config,drawScale,drawLines,ctx);
814
+
815
+ function drawLines(animPc){
816
+ for (var i=0; i<data.datasets.length; i++){
817
+ ctx.strokeStyle = data.datasets[i].strokeColor;
818
+ ctx.lineWidth = config.datasetStrokeWidth;
819
+ ctx.beginPath();
820
+ ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)))
821
+
822
+ for (var j=1; j<data.datasets[i].data.length; j++){
823
+ if (config.bezierCurve){
824
+ ctx.bezierCurveTo(xPos(j-0.5),yPos(i,j-1),xPos(j-0.5),yPos(i,j),xPos(j),yPos(i,j));
825
+ }
826
+ else{
827
+ ctx.lineTo(xPos(j),yPos(i,j));
828
+ }
829
+ }
830
+ ctx.stroke();
831
+ if (config.datasetFill){
832
+ ctx.lineTo(yAxisPosX + (valueHop*(data.datasets[i].data.length-1)),xAxisPosY);
833
+ ctx.lineTo(yAxisPosX,xAxisPosY);
834
+ ctx.closePath();
835
+ ctx.fillStyle = data.datasets[i].fillColor;
836
+ ctx.fill();
837
+ }
838
+ else{
839
+ ctx.closePath();
840
+ }
841
+ if(config.pointDot){
842
+ ctx.fillStyle = data.datasets[i].pointColor;
843
+ ctx.strokeStyle = data.datasets[i].pointStrokeColor;
844
+ ctx.lineWidth = config.pointDotStrokeWidth;
845
+ for (var k=0; k<data.datasets[i].data.length; k++){
846
+ ctx.beginPath();
847
+ ctx.arc(yAxisPosX + (valueHop *k),xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,0,Math.PI*2,true);
848
+ ctx.fill();
849
+ ctx.stroke();
850
+ }
851
+ }
852
+ }
853
+
854
+ function yPos(dataSet,iteration){
855
+ return xAxisPosY - animPc*(calculateOffset(data.datasets[dataSet].data[iteration],calculatedScale,scaleHop));
856
+ }
857
+ function xPos(iteration){
858
+ return yAxisPosX + (valueHop * iteration);
859
+ }
860
+ }
861
+ function drawScale(){
862
+ //X axis line
863
+ ctx.lineWidth = config.scaleLineWidth;
864
+ ctx.strokeStyle = config.scaleLineColor;
865
+ ctx.beginPath();
866
+ ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
867
+ ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
868
+ ctx.stroke();
869
+
870
+
871
+ if (rotateLabels > 0){
872
+ ctx.save();
873
+ ctx.textAlign = "right";
874
+ }
875
+ else{
876
+ ctx.textAlign = "center";
877
+ }
878
+ ctx.fillStyle = config.scaleFontColor;
879
+ for (var i=0; i<data.labels.length; i++){
880
+ ctx.save();
881
+ if (rotateLabels > 0){
882
+ ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
883
+ ctx.rotate(-(rotateLabels * (Math.PI/180)));
884
+ ctx.fillText(data.labels[i], 0,0);
885
+ ctx.restore();
886
+ }
887
+
888
+ else{
889
+ ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3);
890
+ }
891
+
892
+ ctx.beginPath();
893
+ ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3);
894
+
895
+ //Check i isnt 0, so we dont go over the Y axis twice.
896
+ if(config.scaleShowGridLines && i>0){
897
+ ctx.lineWidth = config.scaleGridLineWidth;
898
+ ctx.strokeStyle = config.scaleGridLineColor;
899
+ ctx.lineTo(yAxisPosX + i * valueHop, 5);
900
+ }
901
+ else{
902
+ ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3);
903
+ }
904
+ ctx.stroke();
905
+ }
906
+
907
+ //Y axis
908
+ ctx.lineWidth = config.scaleLineWidth;
909
+ ctx.strokeStyle = config.scaleLineColor;
910
+ ctx.beginPath();
911
+ ctx.moveTo(yAxisPosX,xAxisPosY+5);
912
+ ctx.lineTo(yAxisPosX,5);
913
+ ctx.stroke();
914
+
915
+ ctx.textAlign = "right";
916
+ ctx.textBaseline = "middle";
917
+ for (var j=0; j<calculatedScale.steps; j++){
918
+ ctx.beginPath();
919
+ ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
920
+ if (config.scaleShowGridLines){
921
+ ctx.lineWidth = config.scaleGridLineWidth;
922
+ ctx.strokeStyle = config.scaleGridLineColor;
923
+ ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));
924
+ }
925
+ else{
926
+ ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
927
+ }
928
+
929
+ ctx.stroke();
930
+
931
+ if (config.scaleShowLabels){
932
+ ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
933
+ }
934
+ }
935
+
936
+
937
+ }
938
+ function calculateXAxisSize(){
939
+ var longestText = 1;
940
+ //if we are showing the labels
941
+ if (config.scaleShowLabels){
942
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
943
+ for (var i=0; i<calculatedScale.labels.length; i++){
944
+ var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
945
+ longestText = (measuredText > longestText)? measuredText : longestText;
946
+ }
947
+ //Add a little extra padding from the y axis
948
+ longestText +=10;
949
+ }
950
+ xAxisLength = width - longestText - widestXLabel;
951
+ valueHop = Math.floor(xAxisLength/(data.labels.length-1));
952
+
953
+ yAxisPosX = width-widestXLabel/2-xAxisLength;
954
+ xAxisPosY = scaleHeight + config.scaleFontSize/2;
955
+ }
956
+ function calculateDrawingSizes(){
957
+ maxSize = height;
958
+
959
+ //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.
960
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
961
+ widestXLabel = 1;
962
+ for (var i=0; i<data.labels.length; i++){
963
+ var textLength = ctx.measureText(data.labels[i]).width;
964
+ //If the text length is longer - make that equal to longest text!
965
+ widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
966
+ }
967
+ if (width/data.labels.length < widestXLabel){
968
+ rotateLabels = 45;
969
+ if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
970
+ rotateLabels = 90;
971
+ maxSize -= widestXLabel;
972
+ }
973
+ else{
974
+ maxSize -= Math.sin(rotateLabels) * widestXLabel;
975
+ }
976
+ }
977
+ else{
978
+ maxSize -= config.scaleFontSize;
979
+ }
980
+
981
+ //Add a little padding between the x line and the text
982
+ maxSize -= 5;
983
+
984
+
985
+ labelHeight = config.scaleFontSize;
986
+
987
+ maxSize -= labelHeight;
988
+ //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
989
+
990
+ scaleHeight = maxSize;
991
+
992
+ //Then get the area above we can safely draw on.
993
+
994
+ }
995
+ function getValueBounds() {
996
+ var upperValue = Number.MIN_VALUE;
997
+ var lowerValue = Number.MAX_VALUE;
998
+ for (var i=0; i<data.datasets.length; i++){
999
+ for (var j=0; j<data.datasets[i].data.length; j++){
1000
+ if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1001
+ if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1002
+ }
1003
+ };
1004
+
1005
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1006
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1007
+
1008
+ return {
1009
+ maxValue : upperValue,
1010
+ minValue : lowerValue,
1011
+ maxSteps : maxSteps,
1012
+ minSteps : minSteps
1013
+ };
1014
+
1015
+
1016
+ }
1017
+
1018
+
1019
+ }
1020
+
1021
+ var Bar = function(data,config,ctx){
1022
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0;
1023
+
1024
+ calculateDrawingSizes();
1025
+
1026
+ valueBounds = getValueBounds();
1027
+ //Check and set the scale
1028
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
1029
+ if (!config.scaleOverride){
1030
+
1031
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
1032
+ }
1033
+ else {
1034
+ calculatedScale = {
1035
+ steps : config.scaleSteps,
1036
+ stepValue : config.scaleStepWidth,
1037
+ graphMin : config.scaleStartValue,
1038
+ labels : []
1039
+ }
1040
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
1041
+ }
1042
+
1043
+ scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
1044
+ calculateXAxisSize();
1045
+ animationLoop(config,drawScale,drawBars,ctx);
1046
+
1047
+ function drawBars(animPc){
1048
+ ctx.lineWidth = config.barStrokeWidth;
1049
+ for (var i=0; i<data.datasets.length; i++){
1050
+ ctx.fillStyle = data.datasets[i].fillColor;
1051
+ ctx.strokeStyle = data.datasets[i].strokeColor;
1052
+ for (var j=0; j<data.datasets[i].data.length; j++){
1053
+ var barOffset = yAxisPosX + config.barValueSpacing + valueHop*j + barWidth*i + config.barDatasetSpacing*i + config.barStrokeWidth*i;
1054
+
1055
+ ctx.beginPath();
1056
+ ctx.moveTo(barOffset, xAxisPosY);
1057
+ ctx.lineTo(barOffset, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1058
+ ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1059
+ ctx.lineTo(barOffset + barWidth, xAxisPosY);
1060
+ if(config.barShowStroke){
1061
+ ctx.stroke();
1062
+ }
1063
+ ctx.closePath();
1064
+ ctx.fill();
1065
+ }
1066
+ }
1067
+
1068
+ }
1069
+ function drawScale(){
1070
+ //X axis line
1071
+ ctx.lineWidth = config.scaleLineWidth;
1072
+ ctx.strokeStyle = config.scaleLineColor;
1073
+ ctx.beginPath();
1074
+ ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
1075
+ ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
1076
+ ctx.stroke();
1077
+
1078
+
1079
+ if (rotateLabels > 0){
1080
+ ctx.save();
1081
+ ctx.textAlign = "right";
1082
+ }
1083
+ else{
1084
+ ctx.textAlign = "center";
1085
+ }
1086
+ ctx.fillStyle = config.scaleFontColor;
1087
+ for (var i=0; i<data.labels.length; i++){
1088
+ ctx.save();
1089
+ if (rotateLabels > 0){
1090
+ ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
1091
+ ctx.rotate(-(rotateLabels * (Math.PI/180)));
1092
+ ctx.fillText(data.labels[i], 0,0);
1093
+ ctx.restore();
1094
+ }
1095
+
1096
+ else{
1097
+ ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3);
1098
+ }
1099
+
1100
+ ctx.beginPath();
1101
+ ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3);
1102
+
1103
+ //Check i isnt 0, so we dont go over the Y axis twice.
1104
+ ctx.lineWidth = config.scaleGridLineWidth;
1105
+ ctx.strokeStyle = config.scaleGridLineColor;
1106
+ ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5);
1107
+ ctx.stroke();
1108
+ }
1109
+
1110
+ //Y axis
1111
+ ctx.lineWidth = config.scaleLineWidth;
1112
+ ctx.strokeStyle = config.scaleLineColor;
1113
+ ctx.beginPath();
1114
+ ctx.moveTo(yAxisPosX,xAxisPosY+5);
1115
+ ctx.lineTo(yAxisPosX,5);
1116
+ ctx.stroke();
1117
+
1118
+ ctx.textAlign = "right";
1119
+ ctx.textBaseline = "middle";
1120
+
1121
+ // Always draw 0 on y-axis.
1122
+ // Note this bypasses the templating process.
1123
+ ctx.fillText('0',yAxisPosX-8,xAxisPosY);
1124
+
1125
+ for (var j=0; j<calculatedScale.steps; j++){
1126
+ ctx.beginPath();
1127
+ ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
1128
+ if (config.scaleShowGridLines){
1129
+ ctx.lineWidth = config.scaleGridLineWidth;
1130
+ ctx.strokeStyle = config.scaleGridLineColor;
1131
+ ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));
1132
+ }
1133
+ else{
1134
+ ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
1135
+ }
1136
+
1137
+ ctx.stroke();
1138
+ if (config.scaleShowLabels){
1139
+ ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
1140
+ }
1141
+ }
1142
+
1143
+
1144
+ }
1145
+ function calculateXAxisSize(){
1146
+ var longestText = 1;
1147
+ //if we are showing the labels
1148
+ if (config.scaleShowLabels){
1149
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1150
+ for (var i=0; i<calculatedScale.labels.length; i++){
1151
+ var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
1152
+ longestText = (measuredText > longestText)? measuredText : longestText;
1153
+ }
1154
+ //Add a little extra padding from the y axis
1155
+ longestText +=10;
1156
+ }
1157
+ xAxisLength = width - longestText - widestXLabel;
1158
+ valueHop = Math.floor(xAxisLength/(data.labels.length));
1159
+
1160
+ barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length;
1161
+
1162
+ yAxisPosX = width-widestXLabel/2-xAxisLength;
1163
+ xAxisPosY = scaleHeight + config.scaleFontSize/2;
1164
+ }
1165
+ function calculateDrawingSizes(){
1166
+ maxSize = height;
1167
+
1168
+ //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.
1169
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1170
+ widestXLabel = 1;
1171
+ for (var i=0; i<data.labels.length; i++){
1172
+ var textLength = ctx.measureText(data.labels[i]).width;
1173
+ //If the text length is longer - make that equal to longest text!
1174
+ widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
1175
+ }
1176
+ if (width/data.labels.length < widestXLabel){
1177
+ rotateLabels = 45;
1178
+ if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
1179
+ rotateLabels = 90;
1180
+ maxSize -= widestXLabel;
1181
+ }
1182
+ else{
1183
+ maxSize -= Math.sin(rotateLabels) * widestXLabel;
1184
+ }
1185
+ }
1186
+ else{
1187
+ maxSize -= config.scaleFontSize;
1188
+ }
1189
+
1190
+ //Add a little padding between the x line and the text
1191
+ maxSize -= 5;
1192
+
1193
+
1194
+ labelHeight = config.scaleFontSize;
1195
+
1196
+ maxSize -= labelHeight;
1197
+ //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
1198
+
1199
+ scaleHeight = maxSize;
1200
+
1201
+ //Then get the area above we can safely draw on.
1202
+
1203
+ }
1204
+ function getValueBounds() {
1205
+ var upperValue = Number.MIN_VALUE;
1206
+ var lowerValue = Number.MAX_VALUE;
1207
+ for (var i=0; i<data.datasets.length; i++){
1208
+ for (var j=0; j<data.datasets[i].data.length; j++){
1209
+ if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1210
+ if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1211
+ }
1212
+ };
1213
+
1214
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1215
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1216
+
1217
+ return {
1218
+ maxValue : upperValue,
1219
+ minValue : lowerValue,
1220
+ maxSteps : maxSteps,
1221
+ minSteps : minSteps
1222
+ };
1223
+
1224
+
1225
+ }
1226
+ }
1227
+
1228
+ function calculateOffset(val,calculatedScale,scaleHop){
1229
+ var outerValue = calculatedScale.steps * calculatedScale.stepValue;
1230
+ var adjustedValue = val - calculatedScale.graphMin;
1231
+ var scalingFactor = CapValue(adjustedValue/outerValue,1,0);
1232
+ return (scaleHop*calculatedScale.steps) * scalingFactor;
1233
+ }
1234
+
1235
+ function animationLoop(config,drawScale,drawData,ctx){
1236
+ var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1,
1237
+ easingFunction = animationOptions[config.animationEasing],
1238
+ percentAnimComplete =(config.animation)? 0 : 1;
1239
+
1240
+
1241
+
1242
+ if (typeof drawScale !== "function") drawScale = function(){};
1243
+
1244
+ requestAnimFrame(animLoop);
1245
+
1246
+ function animateFrame(){
1247
+ var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1;
1248
+ clear(ctx);
1249
+ if(config.scaleOverlay){
1250
+ drawData(easeAdjustedAnimationPercent);
1251
+ drawScale();
1252
+ } else {
1253
+ drawScale();
1254
+ drawData(easeAdjustedAnimationPercent);
1255
+ }
1256
+ }
1257
+ function animLoop(){
1258
+ //We need to check if the animation is incomplete (less than 1), or complete (1).
1259
+ percentAnimComplete += animFrameAmount;
1260
+ animateFrame();
1261
+ //Stop the loop continuing forever
1262
+ if (percentAnimComplete <= 1){
1263
+ requestAnimFrame(animLoop);
1264
+ }
1265
+ else{
1266
+ if (typeof config.onAnimationComplete == "function") config.onAnimationComplete();
1267
+ }
1268
+
1269
+ }
1270
+
1271
+ }
1272
+
1273
+ //Declare global functions to be called within this namespace here.
1274
+
1275
+
1276
+ // shim layer with setTimeout fallback
1277
+ var requestAnimFrame = (function(){
1278
+ return window.requestAnimationFrame ||
1279
+ window.webkitRequestAnimationFrame ||
1280
+ window.mozRequestAnimationFrame ||
1281
+ window.oRequestAnimationFrame ||
1282
+ window.msRequestAnimationFrame ||
1283
+ function(callback) {
1284
+ window.setTimeout(callback, 1000 / 60);
1285
+ };
1286
+ })();
1287
+
1288
+ function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){
1289
+ var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum;
1290
+
1291
+ valueRange = maxValue - minValue;
1292
+
1293
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);
1294
+
1295
+ graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1296
+
1297
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1298
+
1299
+ graphRange = graphMax - graphMin;
1300
+
1301
+ stepValue = Math.pow(10, rangeOrderOfMagnitude);
1302
+
1303
+ numberOfSteps = Math.round(graphRange / stepValue);
1304
+
1305
+ //Compare number of steps to the max and min for that size graph, and add in half steps if need be.
1306
+ while(numberOfSteps < minSteps || numberOfSteps > maxSteps) {
1307
+ if (numberOfSteps < minSteps){
1308
+ stepValue /= 2;
1309
+ numberOfSteps = Math.round(graphRange/stepValue);
1310
+ }
1311
+ else{
1312
+ stepValue *=2;
1313
+ numberOfSteps = Math.round(graphRange/stepValue);
1314
+ }
1315
+ };
1316
+
1317
+ var labels = [];
1318
+ populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue);
1319
+
1320
+ return {
1321
+ steps : numberOfSteps,
1322
+ stepValue : stepValue,
1323
+ graphMin : graphMin,
1324
+ labels : labels
1325
+
1326
+ }
1327
+
1328
+ function calculateOrderOfMagnitude(val){
1329
+ return Math.floor(Math.log(val) / Math.LN10);
1330
+ }
1331
+
1332
+
1333
+ }
1334
+
1335
+ //Populate an array of all the labels by interpolating the string.
1336
+ function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) {
1337
+ if (labelTemplateString) {
1338
+ //Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
1339
+ for (var i = 1; i < numberOfSteps + 1; i++) {
1340
+ labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))}));
1341
+ }
1342
+ }
1343
+ }
1344
+
1345
+ //Max value from array
1346
+ function Max( array ){
1347
+ return Math.max.apply( Math, array );
1348
+ };
1349
+ //Min value from array
1350
+ function Min( array ){
1351
+ return Math.min.apply( Math, array );
1352
+ };
1353
+ //Default if undefined
1354
+ function Default(userDeclared,valueIfFalse){
1355
+ if(!userDeclared){
1356
+ return valueIfFalse;
1357
+ } else {
1358
+ return userDeclared;
1359
+ }
1360
+ };
1361
+ //Is a number function
1362
+ function isNumber(n) {
1363
+ return !isNaN(parseFloat(n)) && isFinite(n);
1364
+ }
1365
+ //Apply cap a value at a high or low number
1366
+ function CapValue(valueToCap, maxValue, minValue){
1367
+ if(isNumber(maxValue)) {
1368
+ if( valueToCap > maxValue ) {
1369
+ return maxValue;
1370
+ }
1371
+ }
1372
+ if(isNumber(minValue)){
1373
+ if ( valueToCap < minValue ){
1374
+ return minValue;
1375
+ }
1376
+ }
1377
+ return valueToCap;
1378
+ }
1379
+ function getDecimalPlaces (num){
1380
+ var numberOfDecimalPlaces;
1381
+ if (num%1!=0){
1382
+ return num.toString().split(".")[1].length
1383
+ }
1384
+ else{
1385
+ return 0;
1386
+ }
1387
+
1388
+ }
1389
+
1390
+ function mergeChartConfig(defaults,userDefined){
1391
+ var returnObj = {};
1392
+ for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; }
1393
+ for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; }
1394
+ return returnObj;
1395
+ }
1396
+
1397
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
1398
+ var cache = {};
1399
+
1400
+ function tmpl(str, data){
1401
+ // Figure out if we're getting a template, or if we need to
1402
+ // load the template - and be sure to cache the result.
1403
+ var fn = !/\W/.test(str) ?
1404
+ cache[str] = cache[str] ||
1405
+ tmpl(document.getElementById(str).innerHTML) :
1406
+
1407
+ // Generate a reusable function that will serve as a template
1408
+ // generator (and which will be cached).
1409
+ new Function("obj",
1410
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
1411
+
1412
+ // Introduce the data as local variables using with(){}
1413
+ "with(obj){p.push('" +
1414
+
1415
+ // Convert the template into pure JavaScript
1416
+ str
1417
+ .replace(/[\r\t\n]/g, " ")
1418
+ .split("<%").join("\t")
1419
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
1420
+ .replace(/\t=(.*?)%>/g, "',$1,'")
1421
+ .split("\t").join("');")
1422
+ .split("%>").join("p.push('")
1423
+ .split("\r").join("\\'")
1424
+ + "');}return p.join('');");
1425
+
1426
+ // Provide some basic currying to the user
1427
+ return data ? fn( data ) : fn;
1428
+ };
1429
+ }
1430
+
1431
+