chartjs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +32 -0
  4. data/lib/assets/javascripts/chart.js +1426 -0
  5. data/lib/chartjs.rb +8 -0
  6. data/lib/chartjs/version.rb +3 -0
  7. data/lib/tasks/chartjs_tasks.rake +4 -0
  8. data/test/chartjs_test.rb +7 -0
  9. data/test/dummy/Gemfile +1 -0
  10. data/test/dummy/Gemfile.lock +85 -0
  11. data/test/dummy/README.rdoc +28 -0
  12. data/test/dummy/Rakefile +6 -0
  13. data/test/dummy/app/assets/javascripts/application.js +14 -0
  14. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  15. data/test/dummy/app/controllers/application_controller.rb +5 -0
  16. data/test/dummy/app/helpers/application_helper.rb +2 -0
  17. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  18. data/test/dummy/bin/bundle +3 -0
  19. data/test/dummy/bin/rails +4 -0
  20. data/test/dummy/bin/rake +4 -0
  21. data/test/dummy/config.ru +4 -0
  22. data/test/dummy/config/application.rb +23 -0
  23. data/test/dummy/config/boot.rb +5 -0
  24. data/test/dummy/config/database.yml +25 -0
  25. data/test/dummy/config/environment.rb +5 -0
  26. data/test/dummy/config/environments/development.rb +29 -0
  27. data/test/dummy/config/environments/production.rb +80 -0
  28. data/test/dummy/config/environments/test.rb +36 -0
  29. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  30. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  31. data/test/dummy/config/initializers/inflections.rb +16 -0
  32. data/test/dummy/config/initializers/mime_types.rb +5 -0
  33. data/test/dummy/config/initializers/secret_token.rb +12 -0
  34. data/test/dummy/config/initializers/session_store.rb +3 -0
  35. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  36. data/test/dummy/config/locales/en.yml +23 -0
  37. data/test/dummy/config/routes.rb +56 -0
  38. data/test/dummy/db/development.sqlite3 +0 -0
  39. data/test/dummy/log/development.log +60 -0
  40. data/test/dummy/public/404.html +58 -0
  41. data/test/dummy/public/422.html +58 -0
  42. data/test/dummy/public/500.html +57 -0
  43. data/test/dummy/public/favicon.ico +0 -0
  44. data/test/test_helper.rb +15 -0
  45. metadata +151 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3d379df9306b388d6d1ee8dcfcc3fbbdcb02e022
4
+ data.tar.gz: 04dc786eca8e548b0b169e87e0dc39f532ef3c7f
5
+ SHA512:
6
+ metadata.gz: 54839b80e800cd4ada1224de6328a9934139586d1b293660ca6240f6f98c5daf65250f30e2a026140b7ed7ef458ba6bb399c72922396d7dd7cdfbf868673e813
7
+ data.tar.gz: 70b42de6747b9f4fe8ce0fe2565981115d47bdece34d0f3132d4459592fa512d8340ab6a3d522e114cb986a9aa2cafadcae43c124b513d39fa8631ca5ad2bcee
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Victor Velazquez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Chartjs'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,1426 @@
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
+ for (var j=0; j<calculatedScale.steps; j++){
1121
+ ctx.beginPath();
1122
+ ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
1123
+ if (config.scaleShowGridLines){
1124
+ ctx.lineWidth = config.scaleGridLineWidth;
1125
+ ctx.strokeStyle = config.scaleGridLineColor;
1126
+ ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));
1127
+ }
1128
+ else{
1129
+ ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
1130
+ }
1131
+
1132
+ ctx.stroke();
1133
+ if (config.scaleShowLabels){
1134
+ ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
1135
+ }
1136
+ }
1137
+
1138
+
1139
+ }
1140
+ function calculateXAxisSize(){
1141
+ var longestText = 1;
1142
+ //if we are showing the labels
1143
+ if (config.scaleShowLabels){
1144
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1145
+ for (var i=0; i<calculatedScale.labels.length; i++){
1146
+ var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
1147
+ longestText = (measuredText > longestText)? measuredText : longestText;
1148
+ }
1149
+ //Add a little extra padding from the y axis
1150
+ longestText +=10;
1151
+ }
1152
+ xAxisLength = width - longestText - widestXLabel;
1153
+ valueHop = Math.floor(xAxisLength/(data.labels.length));
1154
+
1155
+ barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length;
1156
+
1157
+ yAxisPosX = width-widestXLabel/2-xAxisLength;
1158
+ xAxisPosY = scaleHeight + config.scaleFontSize/2;
1159
+ }
1160
+ function calculateDrawingSizes(){
1161
+ maxSize = height;
1162
+
1163
+ //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.
1164
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1165
+ widestXLabel = 1;
1166
+ for (var i=0; i<data.labels.length; i++){
1167
+ var textLength = ctx.measureText(data.labels[i]).width;
1168
+ //If the text length is longer - make that equal to longest text!
1169
+ widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
1170
+ }
1171
+ if (width/data.labels.length < widestXLabel){
1172
+ rotateLabels = 45;
1173
+ if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
1174
+ rotateLabels = 90;
1175
+ maxSize -= widestXLabel;
1176
+ }
1177
+ else{
1178
+ maxSize -= Math.sin(rotateLabels) * widestXLabel;
1179
+ }
1180
+ }
1181
+ else{
1182
+ maxSize -= config.scaleFontSize;
1183
+ }
1184
+
1185
+ //Add a little padding between the x line and the text
1186
+ maxSize -= 5;
1187
+
1188
+
1189
+ labelHeight = config.scaleFontSize;
1190
+
1191
+ maxSize -= labelHeight;
1192
+ //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
1193
+
1194
+ scaleHeight = maxSize;
1195
+
1196
+ //Then get the area above we can safely draw on.
1197
+
1198
+ }
1199
+ function getValueBounds() {
1200
+ var upperValue = Number.MIN_VALUE;
1201
+ var lowerValue = Number.MAX_VALUE;
1202
+ for (var i=0; i<data.datasets.length; i++){
1203
+ for (var j=0; j<data.datasets[i].data.length; j++){
1204
+ if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1205
+ if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1206
+ }
1207
+ };
1208
+
1209
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1210
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1211
+
1212
+ return {
1213
+ maxValue : upperValue,
1214
+ minValue : lowerValue,
1215
+ maxSteps : maxSteps,
1216
+ minSteps : minSteps
1217
+ };
1218
+
1219
+
1220
+ }
1221
+ }
1222
+
1223
+ function calculateOffset(val,calculatedScale,scaleHop){
1224
+ var outerValue = calculatedScale.steps * calculatedScale.stepValue;
1225
+ var adjustedValue = val - calculatedScale.graphMin;
1226
+ var scalingFactor = CapValue(adjustedValue/outerValue,1,0);
1227
+ return (scaleHop*calculatedScale.steps) * scalingFactor;
1228
+ }
1229
+
1230
+ function animationLoop(config,drawScale,drawData,ctx){
1231
+ var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1,
1232
+ easingFunction = animationOptions[config.animationEasing],
1233
+ percentAnimComplete =(config.animation)? 0 : 1;
1234
+
1235
+
1236
+
1237
+ if (typeof drawScale !== "function") drawScale = function(){};
1238
+
1239
+ requestAnimFrame(animLoop);
1240
+
1241
+ function animateFrame(){
1242
+ var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1;
1243
+ clear(ctx);
1244
+ if(config.scaleOverlay){
1245
+ drawData(easeAdjustedAnimationPercent);
1246
+ drawScale();
1247
+ } else {
1248
+ drawScale();
1249
+ drawData(easeAdjustedAnimationPercent);
1250
+ }
1251
+ }
1252
+ function animLoop(){
1253
+ //We need to check if the animation is incomplete (less than 1), or complete (1).
1254
+ percentAnimComplete += animFrameAmount;
1255
+ animateFrame();
1256
+ //Stop the loop continuing forever
1257
+ if (percentAnimComplete <= 1){
1258
+ requestAnimFrame(animLoop);
1259
+ }
1260
+ else{
1261
+ if (typeof config.onAnimationComplete == "function") config.onAnimationComplete();
1262
+ }
1263
+
1264
+ }
1265
+
1266
+ }
1267
+
1268
+ //Declare global functions to be called within this namespace here.
1269
+
1270
+
1271
+ // shim layer with setTimeout fallback
1272
+ var requestAnimFrame = (function(){
1273
+ return window.requestAnimationFrame ||
1274
+ window.webkitRequestAnimationFrame ||
1275
+ window.mozRequestAnimationFrame ||
1276
+ window.oRequestAnimationFrame ||
1277
+ window.msRequestAnimationFrame ||
1278
+ function(callback) {
1279
+ window.setTimeout(callback, 1000 / 60);
1280
+ };
1281
+ })();
1282
+
1283
+ function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){
1284
+ var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum;
1285
+
1286
+ valueRange = maxValue - minValue;
1287
+
1288
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);
1289
+
1290
+ graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1291
+
1292
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1293
+
1294
+ graphRange = graphMax - graphMin;
1295
+
1296
+ stepValue = Math.pow(10, rangeOrderOfMagnitude);
1297
+
1298
+ numberOfSteps = Math.round(graphRange / stepValue);
1299
+
1300
+ //Compare number of steps to the max and min for that size graph, and add in half steps if need be.
1301
+ while(numberOfSteps < minSteps || numberOfSteps > maxSteps) {
1302
+ if (numberOfSteps < minSteps){
1303
+ stepValue /= 2;
1304
+ numberOfSteps = Math.round(graphRange/stepValue);
1305
+ }
1306
+ else{
1307
+ stepValue *=2;
1308
+ numberOfSteps = Math.round(graphRange/stepValue);
1309
+ }
1310
+ };
1311
+
1312
+ var labels = [];
1313
+ populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue);
1314
+
1315
+ return {
1316
+ steps : numberOfSteps,
1317
+ stepValue : stepValue,
1318
+ graphMin : graphMin,
1319
+ labels : labels
1320
+
1321
+ }
1322
+
1323
+ function calculateOrderOfMagnitude(val){
1324
+ return Math.floor(Math.log(val) / Math.LN10);
1325
+ }
1326
+
1327
+
1328
+ }
1329
+
1330
+ //Populate an array of all the labels by interpolating the string.
1331
+ function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) {
1332
+ if (labelTemplateString) {
1333
+ //Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
1334
+ for (var i = 1; i < numberOfSteps + 1; i++) {
1335
+ labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))}));
1336
+ }
1337
+ }
1338
+ }
1339
+
1340
+ //Max value from array
1341
+ function Max( array ){
1342
+ return Math.max.apply( Math, array );
1343
+ };
1344
+ //Min value from array
1345
+ function Min( array ){
1346
+ return Math.min.apply( Math, array );
1347
+ };
1348
+ //Default if undefined
1349
+ function Default(userDeclared,valueIfFalse){
1350
+ if(!userDeclared){
1351
+ return valueIfFalse;
1352
+ } else {
1353
+ return userDeclared;
1354
+ }
1355
+ };
1356
+ //Is a number function
1357
+ function isNumber(n) {
1358
+ return !isNaN(parseFloat(n)) && isFinite(n);
1359
+ }
1360
+ //Apply cap a value at a high or low number
1361
+ function CapValue(valueToCap, maxValue, minValue){
1362
+ if(isNumber(maxValue)) {
1363
+ if( valueToCap > maxValue ) {
1364
+ return maxValue;
1365
+ }
1366
+ }
1367
+ if(isNumber(minValue)){
1368
+ if ( valueToCap < minValue ){
1369
+ return minValue;
1370
+ }
1371
+ }
1372
+ return valueToCap;
1373
+ }
1374
+ function getDecimalPlaces (num){
1375
+ var numberOfDecimalPlaces;
1376
+ if (num%1!=0){
1377
+ return num.toString().split(".")[1].length
1378
+ }
1379
+ else{
1380
+ return 0;
1381
+ }
1382
+
1383
+ }
1384
+
1385
+ function mergeChartConfig(defaults,userDefined){
1386
+ var returnObj = {};
1387
+ for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; }
1388
+ for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; }
1389
+ return returnObj;
1390
+ }
1391
+
1392
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
1393
+ var cache = {};
1394
+
1395
+ function tmpl(str, data){
1396
+ // Figure out if we're getting a template, or if we need to
1397
+ // load the template - and be sure to cache the result.
1398
+ var fn = !/\W/.test(str) ?
1399
+ cache[str] = cache[str] ||
1400
+ tmpl(document.getElementById(str).innerHTML) :
1401
+
1402
+ // Generate a reusable function that will serve as a template
1403
+ // generator (and which will be cached).
1404
+ new Function("obj",
1405
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
1406
+
1407
+ // Introduce the data as local variables using with(){}
1408
+ "with(obj){p.push('" +
1409
+
1410
+ // Convert the template into pure JavaScript
1411
+ str
1412
+ .replace(/[\r\t\n]/g, " ")
1413
+ .split("<%").join("\t")
1414
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
1415
+ .replace(/\t=(.*?)%>/g, "',$1,'")
1416
+ .split("\t").join("');")
1417
+ .split("%>").join("p.push('")
1418
+ .split("\r").join("\\'")
1419
+ + "');}return p.join('');");
1420
+
1421
+ // Provide some basic currying to the user
1422
+ return data ? fn( data ) : fn;
1423
+ };
1424
+ }
1425
+
1426
+