active_frontend 12.2.0 → 12.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/fonts/gotham/gotham-bold.woff +0 -0
  3. data/app/assets/fonts/gotham/gotham-book.woff +0 -0
  4. data/app/assets/fonts/gotham/gotham-light.woff +0 -0
  5. data/app/assets/fonts/gotham/gotham-medium.woff +0 -0
  6. data/app/assets/fonts/gotham/gotham-rounded-bold.woff +0 -0
  7. data/app/assets/fonts/gotham/gotham-rounded-book.woff +0 -0
  8. data/app/assets/fonts/gotham/gotham-rounded-light.woff +0 -0
  9. data/app/assets/fonts/gotham/gotham-rounded-medium.woff +0 -0
  10. data/lib/active_frontend/version.rb +1 -1
  11. data/vendor/assets/javascripts/_affix.js +4 -4
  12. data/vendor/assets/javascripts/_alert.js +1 -1
  13. data/vendor/assets/javascripts/_animation.js +14 -14
  14. data/vendor/assets/javascripts/_button.js +1 -1
  15. data/vendor/assets/javascripts/_carousel.js +5 -5
  16. data/vendor/assets/javascripts/_chart.js +713 -280
  17. data/vendor/assets/javascripts/_collapse.js +6 -6
  18. data/vendor/assets/javascripts/_dropdown.js +47 -43
  19. data/vendor/assets/javascripts/_inputmask.js +6 -4
  20. data/vendor/assets/javascripts/_map.js +136 -88
  21. data/vendor/assets/javascripts/_modal.js +136 -42
  22. data/vendor/assets/javascripts/_popover.js +2 -7
  23. data/vendor/assets/javascripts/_scrollspy.js +16 -19
  24. data/vendor/assets/javascripts/_slider.js +156 -85
  25. data/vendor/assets/javascripts/_tab.js +5 -3
  26. data/vendor/assets/javascripts/_tooltip.js +73 -31
  27. data/vendor/assets/stylesheets/_header.scss +3 -1
  28. data/vendor/assets/stylesheets/_typography.scss +8 -40
  29. metadata +10 -34
  30. data/app/assets/fonts/gotham/regular/gotham-bold.eot +0 -0
  31. data/app/assets/fonts/gotham/regular/gotham-bold.svg +0 -2066
  32. data/app/assets/fonts/gotham/regular/gotham-bold.ttf +0 -0
  33. data/app/assets/fonts/gotham/regular/gotham-bold.woff +0 -0
  34. data/app/assets/fonts/gotham/regular/gotham-book.eot +0 -0
  35. data/app/assets/fonts/gotham/regular/gotham-book.svg +0 -631
  36. data/app/assets/fonts/gotham/regular/gotham-book.ttf +0 -0
  37. data/app/assets/fonts/gotham/regular/gotham-book.woff +0 -0
  38. data/app/assets/fonts/gotham/regular/gotham-light.eot +0 -0
  39. data/app/assets/fonts/gotham/regular/gotham-light.svg +0 -635
  40. data/app/assets/fonts/gotham/regular/gotham-light.ttf +0 -0
  41. data/app/assets/fonts/gotham/regular/gotham-light.woff +0 -0
  42. data/app/assets/fonts/gotham/regular/gotham-medium.eot +0 -0
  43. data/app/assets/fonts/gotham/regular/gotham-medium.svg +0 -629
  44. data/app/assets/fonts/gotham/regular/gotham-medium.ttf +0 -0
  45. data/app/assets/fonts/gotham/regular/gotham-medium.woff +0 -0
  46. data/app/assets/fonts/gotham/round/gothamrnd-bold.eot +0 -0
  47. data/app/assets/fonts/gotham/round/gothamrnd-bold.svg +0 -3528
  48. data/app/assets/fonts/gotham/round/gothamrnd-bold.ttf +0 -0
  49. data/app/assets/fonts/gotham/round/gothamrnd-bold.woff +0 -0
  50. data/app/assets/fonts/gotham/round/gothamrnd-book.eot +0 -0
  51. data/app/assets/fonts/gotham/round/gothamrnd-book.svg +0 -3654
  52. data/app/assets/fonts/gotham/round/gothamrnd-book.ttf +0 -0
  53. data/app/assets/fonts/gotham/round/gothamrnd-book.woff +0 -0
  54. data/app/assets/fonts/gotham/round/gothamrnd-light.eot +0 -0
  55. data/app/assets/fonts/gotham/round/gothamrnd-light.svg +0 -3503
  56. data/app/assets/fonts/gotham/round/gothamrnd-light.ttf +0 -0
  57. data/app/assets/fonts/gotham/round/gothamrnd-light.woff +0 -0
  58. data/app/assets/fonts/gotham/round/gothamrnd-medium.eot +0 -0
  59. data/app/assets/fonts/gotham/round/gothamrnd-medium.svg +0 -3659
  60. data/app/assets/fonts/gotham/round/gothamrnd-medium.ttf +0 -0
  61. data/app/assets/fonts/gotham/round/gothamrnd-medium.woff +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8bf89ffaf09c97711f1ec992866a36da5875debe
4
- data.tar.gz: 508213e124646fbbb9010ff188493dbdd9479231
3
+ metadata.gz: 46f0d1ff12912c504835664509225a2d3ea926c9
4
+ data.tar.gz: 89a6501189dd5b9e91b1786c93b8e01c65213209
5
5
  SHA512:
6
- metadata.gz: edbae37d4faed2ed0981766ad4ac1a78b758d3eea244f53dba297cbb7297589e37d7cd39a695e03b7a30cafc2b8fbdb4bd604c931f357d827b499ae2cd70d6d1
7
- data.tar.gz: 306123a9b688d7b65860d79f6cb1d8dfde74461fb1b83be37e210f861a55480ed7042e8759ad895d88532d05bfa728ee17ebe9d644960813192bca0a123e2b8d
6
+ metadata.gz: 6599a6a5b57196ee8e068d59e782d4cc482420b689b40b6d83d0a7ba6af055b0da74c34f2b95f60bade355df248c4b0e462af153530f54470eaf1762dcd8f4bc
7
+ data.tar.gz: 7bef3c79841a5f081cf44432ac0b85c839c8a1dcea778a9f91d0edc96bcb74958a4d42b4290ea3faf02e9f3c3b78dabaab723c9e496d3ca7905a0ebf11d7471d
@@ -1,3 +1,3 @@
1
1
  module ActiveFrontend
2
- VERSION = "12.2.0"
2
+ VERSION = "12.3.0"
3
3
  end
@@ -12,14 +12,14 @@
12
12
  .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
13
13
 
14
14
  this.$element = $(element)
15
- this.affixed =
16
- this.unpin =
15
+ this.affixed = null
16
+ this.unpin = null
17
17
  this.pinnedOffset = null
18
18
 
19
19
  this.checkPosition()
20
20
  }
21
21
 
22
- Affix.VERSION = '3.3.2'
22
+ Affix.VERSION = '3.3.6'
23
23
 
24
24
  Affix.RESET = 'affix affix-top affix-bottom'
25
25
 
@@ -69,7 +69,7 @@
69
69
  var offset = this.options.offset
70
70
  var offsetTop = offset.top
71
71
  var offsetBottom = offset.bottom
72
- var scrollHeight = $('body').height()
72
+ var scrollHeight = Math.max($(document).height(), $(document.body).height())
73
73
 
74
74
  if (typeof offset != 'object') offsetBottom = offsetTop = offset
75
75
  if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
@@ -9,7 +9,7 @@
9
9
  $(el).on('click', dismiss, this.close)
10
10
  }
11
11
 
12
- Alert.VERSION = '3.3.2'
12
+ Alert.VERSION = '3.3.6'
13
13
 
14
14
  Alert.TRANSITION_DURATION = 150
15
15
 
@@ -9,7 +9,7 @@
9
9
  this.$element = $(element)
10
10
  };
11
11
 
12
- Animation.VERSION = '3.3.2'
12
+ Animation.VERSION = '3.3.6'
13
13
 
14
14
  Animation.TRANSITION_END = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend'
15
15
 
@@ -35,7 +35,7 @@
35
35
  animation = function(element) {
36
36
  if (settings.infinite === true) {
37
37
  settings.klass += ' infinite';
38
- }
38
+ };
39
39
 
40
40
  setDelay(element);
41
41
  setDuration(element);
@@ -51,10 +51,10 @@
51
51
  unhide = function(element) {
52
52
  if (element.css('visibility') === 'hidden') {
53
53
  element.css('visibility', 'visible');
54
- }
54
+ };
55
55
  if (element.is(':hidden')) {
56
56
  return element.show();
57
- }
57
+ };
58
58
  };
59
59
 
60
60
  removeClass = function(element) {
@@ -63,29 +63,29 @@
63
63
 
64
64
  setDelay = function(element) {
65
65
  return element.css({
66
- '-webkit-animation-delay': settings.delay,
67
- '-moz-animation-delay': settings.delay,
68
- '-o-animation-delay': settings.delay,
69
- 'animation-delay': settings.delay
66
+ '-webkit-animation-delay' : settings.delay,
67
+ '-moz-animation-delay' : settings.delay,
68
+ '-o-animation-delay' : settings.delay,
69
+ 'animation-delay' : settings.delay
70
70
  });
71
71
  };
72
72
 
73
73
  setDuration = function(element) {
74
74
  return element.css({
75
- '-webkit-animation-duration': settings.duration,
76
- '-moz-animation-duration': settings.duration,
77
- '-o-animation-duration': settings.duration,
78
- 'animation-duration': settings.duration
75
+ '-webkit-animation-duration' : settings.duration,
76
+ '-moz-animation-duration' : settings.duration,
77
+ '-o-animation-duration' : settings.duration,
78
+ 'animation-duration' : settings.duration
79
79
  });
80
80
  };
81
81
 
82
82
  callback = function(element) {
83
83
  if (settings.infinite === false) {
84
84
  removeClass(element);
85
- }
85
+ };
86
86
  if (typeof settings.callback === 'function') {
87
87
  return settings.callback.call(element);
88
- }
88
+ };
89
89
  };
90
90
 
91
91
  complete = function(element) {
@@ -10,7 +10,7 @@
10
10
  this.isLoading = false
11
11
  }
12
12
 
13
- Button.VERSION = '3.3.2'
13
+ Button.VERSION = '3.3.6'
14
14
 
15
15
  Button.DEFAULTS = {
16
16
  loadingText: 'loading...'
@@ -8,10 +8,10 @@
8
8
  this.$element = $(element)
9
9
  this.$indicators = this.$element.find('.carousel-indicators')
10
10
  this.options = options
11
- this.paused =
12
- this.sliding =
13
- this.interval =
14
- this.$active =
11
+ this.paused = null
12
+ this.sliding = null
13
+ this.interval = null
14
+ this.$active = null
15
15
  this.$items = null
16
16
 
17
17
  this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
@@ -21,7 +21,7 @@
21
21
  .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
22
22
  }
23
23
 
24
- Carousel.VERSION = '3.3.2'
24
+ Carousel.VERSION = '3.3.6'
25
25
 
26
26
  Carousel.TRANSITION_DURATION = 600
27
27
 
@@ -14,8 +14,23 @@
14
14
  this.ctx = context;
15
15
 
16
16
  //Variables global to the chart
17
- var width = this.width = context.canvas.width;
18
- var height = this.height = context.canvas.height;
17
+ var computeDimension = function(element,dimension)
18
+ {
19
+ if (element['offset'+dimension])
20
+ {
21
+ return element['offset'+dimension];
22
+ }
23
+ else
24
+ {
25
+ return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
26
+ }
27
+ };
28
+
29
+ var width = this.width = computeDimension(context.canvas,'Width') || context.canvas.width;
30
+ var height = this.height = computeDimension(context.canvas,'Height') || context.canvas.height;
31
+
32
+ width = this.width = context.canvas.width;
33
+ height = this.height = context.canvas.height;
19
34
  this.aspectRatio = this.width / this.height;
20
35
  //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
21
36
  helpers.retinaScale(this);
@@ -67,7 +82,7 @@
67
82
  scaleBeginAtZero: false,
68
83
 
69
84
  // String - Scale label font declaration for the scale label
70
- scaleFontFamily: "'Gotham', 'Gotham Round', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
85
+ scaleFontFamily: "'Gotham Round', 'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
71
86
 
72
87
  // Number - Scale label font size in pixels
73
88
  scaleFontSize: 11,
@@ -87,6 +102,9 @@
87
102
  // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
88
103
  showTooltips: true,
89
104
 
105
+ // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
106
+ customTooltips: false,
107
+
90
108
  // Array - Array of string names to attach tooltip events
91
109
  tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
92
110
 
@@ -94,7 +112,7 @@
94
112
  tooltipFillColor: "rgba(16,18,25,1)",
95
113
 
96
114
  // String - Tooltip label font declaration for the scale label
97
- tooltipFontFamily: "'Gotham', 'Gotham Round', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
115
+ tooltipFontFamily: "'Gotham Round', 'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
98
116
 
99
117
  // Number - Tooltip label font size in pixels
100
118
  tooltipFontSize: 11,
@@ -106,7 +124,7 @@
106
124
  tooltipFontColor: "rgba(255,255,255,1)",
107
125
 
108
126
  // String - Tooltip title font declaration for the scale label
109
- tooltipTitleFontFamily: "'Gotham', 'Gotham Round', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
127
+ tooltipTitleFontFamily: "'Gotham Round', 'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
110
128
 
111
129
  // Number - Tooltip title font size in pixels
112
130
  tooltipTitleFontSize: 11,
@@ -117,6 +135,9 @@
117
135
  // String - Tooltip title font colour
118
136
  tooltipTitleFontColor: "rgba(255,255,255,1)",
119
137
 
138
+ // String - Tooltip title template
139
+ tooltipTitleTemplate: "<%= label %>",
140
+
120
141
  // Number - pixel width of padding around tooltip text
121
142
  tooltipYPadding: 8,
122
143
 
@@ -141,6 +162,38 @@
141
162
  // String - Colour behind the legend colour block
142
163
  multiTooltipKeyBackground: 'rgba(255,255,255,1)',
143
164
 
165
+ // Array - A list of colors to use as the defaults
166
+ segmentColorDefault: [
167
+ "rgba(151,212,19,1)",
168
+ "rgba(75,173,8,1)",
169
+ "rgba(59,187,178,1)",
170
+ "rgba(0,102,255,1)",
171
+ "rgba(86,21,237,1)",
172
+ "rgba(115,24,242,1)",
173
+ "rgba(255,0,102,1)",
174
+ "rgba(240,35,17,1)",
175
+ "rgba(255,102,0,1)",
176
+ "rgba(255,209,0,1)",
177
+ "rgba(35,40,55,1)",
178
+ "rgba(106,122,138,1)"
179
+ ],
180
+
181
+ // Array - A list of highlight colors to use as the defaults
182
+ segmentHighlightColorDefaults: [
183
+ "rgba(151,212,19,0.1)",
184
+ "rgba(75,173,8,0.1)",
185
+ "rgba(59,187,178,0.1)",
186
+ "rgba(0,102,255,0.1)",
187
+ "rgba(86,21,237,0.1)",
188
+ "rgba(115,24,242,0.1)",
189
+ "rgba(255,0,102,0.1)",
190
+ "rgba(240,35,17,0.1)",
191
+ "rgba(255,102,0,0.1)",
192
+ "rgba(255,209,0,0.1)",
193
+ "rgba(35,40,55,0.1)",
194
+ "rgba(106,122,138,0.1)"
195
+ ],
196
+
144
197
  // Function - Will fire on animation progression.
145
198
  onAnimationProgress: function(){},
146
199
 
@@ -177,14 +230,18 @@
177
230
  clone = helpers.clone = function(obj){
178
231
  var objClone = {};
179
232
  each(obj,function(value,key){
180
- if (obj.hasOwnProperty(key)) objClone[key] = value;
233
+ if (obj.hasOwnProperty(key)){
234
+ objClone[key] = value;
235
+ }
181
236
  });
182
237
  return objClone;
183
238
  },
184
239
  extend = helpers.extend = function(base){
185
240
  each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
186
241
  each(extensionObject,function(value,key){
187
- if (extensionObject.hasOwnProperty(key)) base[key] = value;
242
+ if (extensionObject.hasOwnProperty(key)){
243
+ base[key] = value;
244
+ }
188
245
  });
189
246
  });
190
247
  return base;
@@ -206,6 +263,41 @@
206
263
  return -1;
207
264
  }
208
265
  },
266
+ where = helpers.where = function(collection, filterCallback){
267
+ var filtered = [];
268
+
269
+ helpers.each(collection, function(item){
270
+ if (filterCallback(item)){
271
+ filtered.push(item);
272
+ }
273
+ });
274
+
275
+ return filtered;
276
+ },
277
+ findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
278
+ // Default to start of the array
279
+ if (!startIndex){
280
+ startIndex = -1;
281
+ }
282
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
283
+ var currentItem = arrayToSearch[i];
284
+ if (filterCallback(currentItem)){
285
+ return currentItem;
286
+ }
287
+ }
288
+ },
289
+ findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
290
+ // Default to end of the array
291
+ if (!startIndex){
292
+ startIndex = arrayToSearch.length;
293
+ }
294
+ for (var i = startIndex - 1; i >= 0; i--) {
295
+ var currentItem = arrayToSearch[i];
296
+ if (filterCallback(currentItem)){
297
+ return currentItem;
298
+ }
299
+ }
300
+ },
209
301
  inherits = helpers.inherits = function(extensions){
210
302
  //Basic javascript inheritance based on the model created in Backbone.js
211
303
  var parent = this;
@@ -232,9 +324,9 @@
232
324
  })(),
233
325
  warn = helpers.warn = function(str){
234
326
  //Method for warning of errors
235
- if (window.console && typeof window.console.warn == "function") console.warn(str);
327
+ if (window.console && typeof window.console.warn === "function") console.warn(str);
236
328
  },
237
- amd = helpers.amd = (typeof root.define == 'function' && root.define.amd),
329
+ amd = helpers.amd = (typeof define === 'function' && define.amd),
238
330
  //-- Math methods
239
331
  isNumber = helpers.isNumber = function(n){
240
332
  return !isNaN(parseFloat(n)) && isFinite(n);
@@ -260,7 +352,20 @@
260
352
  },
261
353
  getDecimalPlaces = helpers.getDecimalPlaces = function(num){
262
354
  if (num%1!==0 && isNumber(num)){
263
- return num.toString().split(".")[1].length;
355
+ var s = num.toString();
356
+ if(s.indexOf("e-") < 0){
357
+ // no exponent, e.g. 0.01
358
+ return s.split(".")[1].length;
359
+ }
360
+ else if(s.indexOf(".") < 0) {
361
+ // no decimal point, e.g. 1e-9
362
+ return parseInt(s.split("e-")[1]);
363
+ }
364
+ else {
365
+ // exponent and decimal point, e.g. 1.23e-9
366
+ var parts = s.split(".")[1].split("e-");
367
+ return parts[0].length + parseInt(parts[1]);
368
+ }
264
369
  }
265
370
  else {
266
371
  return 0;
@@ -319,10 +424,15 @@
319
424
  maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
320
425
  skipFitting = (minSteps >= maxSteps);
321
426
 
322
- var maxValue = max(valuesArray),
323
- minValue = min(valuesArray);
427
+ // Filter out null values since these would min() to zero
428
+ var values = [];
429
+ each(valuesArray, function( v ){
430
+ v == null || values.push( v );
431
+ });
432
+ var minValue = min(values),
433
+ maxValue = max(values);
324
434
 
325
- // We need some degree of seperation here to calculate the scales if all the values are the same
435
+ // We need some degree of separation here to calculate the scales if all the values are the same
326
436
  // Adding/minusing 0.5 will give us a range of 1.
327
437
  if (maxValue === minValue){
328
438
  maxValue += 0.5;
@@ -395,9 +505,10 @@
395
505
  //Templating methods
396
506
  //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
397
507
  template = helpers.template = function(templateString, valuesObject){
508
+
398
509
  // If templateString is function rather than string-template - call the function for valuesObject
399
- if(templateString instanceof Function)
400
- {
510
+
511
+ if(templateString instanceof Function){
401
512
  return templateString(valuesObject);
402
513
  }
403
514
 
@@ -436,7 +547,7 @@
436
547
  /* jshint ignore:end */
437
548
  generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
438
549
  var labelsArray = new Array(numberOfSteps);
439
- if (labelTemplateString){
550
+ if (templateString){
440
551
  each(labelsArray,function(val,index){
441
552
  labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
442
553
  });
@@ -457,7 +568,9 @@
457
568
  return -1 * t * (t - 2);
458
569
  },
459
570
  easeInOutQuad: function (t) {
460
- if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
571
+ if ((t /= 1 / 2) < 1){
572
+ return 1 / 2 * t * t;
573
+ }
461
574
  return -1 / 2 * ((--t) * (t - 2) - 1);
462
575
  },
463
576
  easeInCubic: function (t) {
@@ -467,7 +580,9 @@
467
580
  return 1 * ((t = t / 1 - 1) * t * t + 1);
468
581
  },
469
582
  easeInOutCubic: function (t) {
470
- if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
583
+ if ((t /= 1 / 2) < 1){
584
+ return 1 / 2 * t * t * t;
585
+ }
471
586
  return 1 / 2 * ((t -= 2) * t * t + 2);
472
587
  },
473
588
  easeInQuart: function (t) {
@@ -477,7 +592,9 @@
477
592
  return -1 * ((t = t / 1 - 1) * t * t * t - 1);
478
593
  },
479
594
  easeInOutQuart: function (t) {
480
- if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
595
+ if ((t /= 1 / 2) < 1){
596
+ return 1 / 2 * t * t * t * t;
597
+ }
481
598
  return -1 / 2 * ((t -= 2) * t * t * t - 2);
482
599
  },
483
600
  easeInQuint: function (t) {
@@ -487,7 +604,9 @@
487
604
  return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
488
605
  },
489
606
  easeInOutQuint: function (t) {
490
- if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
607
+ if ((t /= 1 / 2) < 1){
608
+ return 1 / 2 * t * t * t * t * t;
609
+ }
491
610
  return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
492
611
  },
493
612
  easeInSine: function (t) {
@@ -506,60 +625,95 @@
506
625
  return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
507
626
  },
508
627
  easeInOutExpo: function (t) {
509
- if (t === 0) return 0;
510
- if (t === 1) return 1;
511
- if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
628
+ if (t === 0){
629
+ return 0;
630
+ }
631
+ if (t === 1){
632
+ return 1;
633
+ }
634
+ if ((t /= 1 / 2) < 1){
635
+ return 1 / 2 * Math.pow(2, 10 * (t - 1));
636
+ }
512
637
  return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
513
638
  },
514
639
  easeInCirc: function (t) {
515
- if (t >= 1) return t;
640
+ if (t >= 1){
641
+ return t;
642
+ }
516
643
  return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
517
644
  },
518
645
  easeOutCirc: function (t) {
519
646
  return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
520
647
  },
521
648
  easeInOutCirc: function (t) {
522
- if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
649
+ if ((t /= 1 / 2) < 1){
650
+ return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
651
+ }
523
652
  return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
524
653
  },
525
654
  easeInElastic: function (t) {
526
655
  var s = 1.70158;
527
656
  var p = 0;
528
657
  var a = 1;
529
- if (t === 0) return 0;
530
- if ((t /= 1) == 1) return 1;
531
- if (!p) p = 1 * 0.3;
658
+ if (t === 0){
659
+ return 0;
660
+ }
661
+ if ((t /= 1) == 1){
662
+ return 1;
663
+ }
664
+ if (!p){
665
+ p = 1 * 0.3;
666
+ }
532
667
  if (a < Math.abs(1)) {
533
668
  a = 1;
534
669
  s = p / 4;
535
- } else s = p / (2 * Math.PI) * Math.asin(1 / a);
670
+ } else{
671
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
672
+ }
536
673
  return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
537
674
  },
538
675
  easeOutElastic: function (t) {
539
676
  var s = 1.70158;
540
677
  var p = 0;
541
678
  var a = 1;
542
- if (t === 0) return 0;
543
- if ((t /= 1) == 1) return 1;
544
- if (!p) p = 1 * 0.3;
679
+ if (t === 0){
680
+ return 0;
681
+ }
682
+ if ((t /= 1) == 1){
683
+ return 1;
684
+ }
685
+ if (!p){
686
+ p = 1 * 0.3;
687
+ }
545
688
  if (a < Math.abs(1)) {
546
689
  a = 1;
547
690
  s = p / 4;
548
- } else s = p / (2 * Math.PI) * Math.asin(1 / a);
691
+ } else{
692
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
693
+ }
549
694
  return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
550
695
  },
551
696
  easeInOutElastic: function (t) {
552
697
  var s = 1.70158;
553
698
  var p = 0;
554
699
  var a = 1;
555
- if (t === 0) return 0;
556
- if ((t /= 1 / 2) == 2) return 1;
557
- if (!p) p = 1 * (0.3 * 1.5);
700
+ if (t === 0){
701
+ return 0;
702
+ }
703
+ if ((t /= 1 / 2) == 2){
704
+ return 1;
705
+ }
706
+ if (!p){
707
+ p = 1 * (0.3 * 1.5);
708
+ }
558
709
  if (a < Math.abs(1)) {
559
710
  a = 1;
560
711
  s = p / 4;
561
- } else s = p / (2 * Math.PI) * Math.asin(1 / a);
562
- if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
712
+ } else {
713
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
714
+ }
715
+ if (t < 1){
716
+ return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));}
563
717
  return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
564
718
  },
565
719
  easeInBack: function (t) {
@@ -572,7 +726,9 @@
572
726
  },
573
727
  easeInOutBack: function (t) {
574
728
  var s = 1.70158;
575
- if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
729
+ if ((t /= 1 / 2) < 1){
730
+ return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
731
+ }
576
732
  return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
577
733
  },
578
734
  easeInBounce: function (t) {
@@ -590,7 +746,9 @@
590
746
  }
591
747
  },
592
748
  easeInOutBounce: function (t) {
593
- if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
749
+ if (t < 1 / 2){
750
+ return easingEffects.easeInBounce(t * 2) * 0.5;
751
+ }
594
752
  return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
595
753
  }
596
754
  },
@@ -693,21 +851,28 @@
693
851
  });
694
852
  },
695
853
  getMaximumWidth = helpers.getMaximumWidth = function(domNode){
696
- var container = domNode.parentNode;
854
+ var container = domNode.parentNode,
855
+ padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right'));
697
856
  // TODO = check cross browser stuff with this.
698
- return container.clientWidth;
857
+ return container.clientWidth - padding;
699
858
  },
700
859
  getMaximumHeight = helpers.getMaximumHeight = function(domNode){
701
- var container = domNode.parentNode;
860
+ var container = domNode.parentNode,
861
+ padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top'));
702
862
  // TODO = check cross browser stuff with this.
703
- return container.clientHeight;
863
+ return container.clientHeight - padding;
864
+ },
865
+ getStyle = helpers.getStyle = function (el, property) {
866
+ return el.currentStyle ?
867
+ el.currentStyle[property] :
868
+ document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
704
869
  },
705
870
  getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
706
871
  retinaScale = helpers.retinaScale = function(chart){
707
872
  var ctx = chart.ctx,
708
873
  width = chart.canvas.width,
709
874
  height = chart.canvas.height;
710
- //console.log(width + " x " + height);
875
+
711
876
  if (window.devicePixelRatio) {
712
877
  ctx.canvas.style.width = width + "px";
713
878
  ctx.canvas.style.height = height + "px";
@@ -775,7 +940,7 @@
775
940
  },
776
941
  stop : function(){
777
942
  // Stops any current animation loop occuring
778
- helpers.cancelAnimFrame.call(root, this.animationFrame);
943
+ Chart.animationService.cancelAnimation(this);
779
944
  return this;
780
945
  },
781
946
  resize : function(callback){
@@ -785,7 +950,7 @@
785
950
  newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
786
951
 
787
952
  canvas.width = this.chart.width = newWidth;
788
- canvas.height = this.chart.height = newHeight;
953
+ canvas.height = this.chart.height = newHeight;
789
954
 
790
955
  retinaScale(this.chart);
791
956
 
@@ -799,15 +964,26 @@
799
964
  if (reflow){
800
965
  this.reflow();
801
966
  }
967
+
802
968
  if (this.options.animation && !reflow){
803
- helpers.animationLoop(
804
- this.draw,
805
- this.options.animationSteps,
806
- this.options.animationEasing,
807
- this.options.onAnimationProgress,
808
- this.options.onAnimationComplete,
809
- this
810
- );
969
+ var animation = new Chart.Animation();
970
+ animation.numSteps = this.options.animationSteps;
971
+ animation.easing = this.options.animationEasing;
972
+
973
+ // render function
974
+ animation.render = function(chartInstance, animationObject) {
975
+ var easingFunction = helpers.easingEffects[animationObject.easing];
976
+ var stepDecimal = animationObject.currentStep / animationObject.numSteps;
977
+ var easeDecimal = easingFunction(stepDecimal);
978
+
979
+ chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
980
+ };
981
+
982
+ // user events
983
+ animation.onAnimationProgress = this.options.onAnimationProgress;
984
+ animation.onAnimationComplete = this.options.onAnimationComplete;
985
+
986
+ Chart.animationService.addAnimation(this, animation);
811
987
  }
812
988
  else{
813
989
  this.draw();
@@ -821,6 +997,21 @@
821
997
  destroy : function(){
822
998
  this.clear();
823
999
  unbindEvents(this, this.events);
1000
+ var canvas = this.chart.canvas;
1001
+
1002
+ // Reset canvas height/width attributes starts a fresh with the canvas context
1003
+ canvas.width = this.chart.width;
1004
+ canvas.height = this.chart.height;
1005
+
1006
+ // < IE9 doesn't support removeProperty
1007
+ if (canvas.style.removeProperty) {
1008
+ canvas.style.removeProperty('width');
1009
+ canvas.style.removeProperty('height');
1010
+ } else {
1011
+ canvas.style.removeAttribute('width');
1012
+ canvas.style.removeAttribute('height');
1013
+ }
1014
+
824
1015
  delete Chart.instances[this.id];
825
1016
  },
826
1017
  showTooltip : function(ChartElements, forceRedraw){
@@ -850,6 +1041,9 @@
850
1041
  this.activeElements = ChartElements;
851
1042
  }
852
1043
  this.draw();
1044
+ if(this.options.customTooltips){
1045
+ this.options.customTooltips(false);
1046
+ }
853
1047
  if (ChartElements.length > 0){
854
1048
  // If we have multiple datasets, show a MultiTooltip for all of the data points at that index
855
1049
  if (this.datasets && this.datasets.length > 1) {
@@ -878,7 +1072,7 @@
878
1072
  yMin;
879
1073
  helpers.each(this.datasets, function(dataset){
880
1074
  dataCollection = dataset.points || dataset.bars || dataset.segments;
881
- if (dataCollection[dataIndex]){
1075
+ if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
882
1076
  Elements.push(dataCollection[dataIndex]);
883
1077
  }
884
1078
  });
@@ -928,9 +1122,10 @@
928
1122
  labels: tooltipLabels,
929
1123
  legendColors: tooltipColors,
930
1124
  legendColorBackground : this.options.multiTooltipKeyBackground,
931
- title: ChartElements[0].label,
1125
+ title: template(this.options.tooltipTitleTemplate,ChartElements[0]),
932
1126
  chart: this.chart,
933
- ctx: this.chart.ctx
1127
+ ctx: this.chart.ctx,
1128
+ custom: this.options.customTooltips
934
1129
  }).draw();
935
1130
 
936
1131
  } else {
@@ -949,7 +1144,8 @@
949
1144
  caretHeight: this.options.tooltipCaretSize,
950
1145
  cornerRadius: this.options.tooltipCornerRadius,
951
1146
  text: template(this.options.tooltipTemplate, Element),
952
- chart: this.chart
1147
+ chart: this.chart,
1148
+ custom: this.options.customTooltips
953
1149
  }).draw();
954
1150
  }, this);
955
1151
  }
@@ -1042,6 +1238,9 @@
1042
1238
  x : this.x,
1043
1239
  y : this.y
1044
1240
  };
1241
+ },
1242
+ hasValue: function(){
1243
+ return isNumber(this.value);
1045
1244
  }
1046
1245
  });
1047
1246
 
@@ -1049,8 +1248,8 @@
1049
1248
 
1050
1249
 
1051
1250
  Chart.Point = Chart.Element.extend({
1052
- display : true,
1053
- inRange : function(chartX,chartY){
1251
+ display: true,
1252
+ inRange: function(chartX,chartY){
1054
1253
  var hitDetectionRange = this.hitDetectionRadius + this.radius;
1055
1254
  return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
1056
1255
  },
@@ -1072,7 +1271,6 @@
1072
1271
  }
1073
1272
 
1074
1273
 
1075
-
1076
1274
  //Quick debug for bezier curve splining
1077
1275
  //Highlights control points and the line between them.
1078
1276
  //Handy for dev - stripped in the min version.
@@ -1089,6 +1287,7 @@
1089
1287
  // ctx.fill();
1090
1288
 
1091
1289
  // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
1290
+ // ctx.lineTo(this.x, this.y);
1092
1291
  // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
1093
1292
  // ctx.stroke();
1094
1293
 
@@ -1107,9 +1306,18 @@
1107
1306
  y: chartY
1108
1307
  });
1109
1308
 
1309
+ // Normalize all angles to 0 - 2*PI (0 - 360°)
1310
+ var pointRelativeAngle = pointRelativePosition.angle % (Math.PI * 2),
1311
+ startAngle = (Math.PI * 2 + this.startAngle) % (Math.PI * 2),
1312
+ endAngle = (Math.PI * 2 + this.endAngle) % (Math.PI * 2) || 360;
1313
+
1314
+ // Calculate wether the pointRelativeAngle is between the start and the end angle
1315
+ var betweenAngles = (endAngle < startAngle) ?
1316
+ pointRelativeAngle <= endAngle || pointRelativeAngle >= startAngle:
1317
+ pointRelativeAngle >= startAngle && pointRelativeAngle <= endAngle;
1318
+
1110
1319
  //Check if within the range of the open/close angle
1111
- var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
1112
- withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
1320
+ var withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
1113
1321
 
1114
1322
  return (betweenAngles && withinRadius);
1115
1323
  //Ensure within the outside of the arc centre, but inside arc outer
@@ -1130,9 +1338,9 @@
1130
1338
 
1131
1339
  ctx.beginPath();
1132
1340
 
1133
- ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
1341
+ ctx.arc(this.x, this.y, this.outerRadius < 0 ? 0 : this.outerRadius, this.startAngle, this.endAngle);
1134
1342
 
1135
- ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
1343
+ ctx.arc(this.x, this.y, this.innerRadius < 0 ? 0 : this.innerRadius, this.endAngle, this.startAngle, true);
1136
1344
 
1137
1345
  ctx.closePath();
1138
1346
  ctx.strokeStyle = this.strokeColor;
@@ -1191,6 +1399,16 @@
1191
1399
  }
1192
1400
  });
1193
1401
 
1402
+ Chart.Animation = Chart.Element.extend({
1403
+ currentStep: null, // the current animation step
1404
+ numSteps: 60, // default number of steps
1405
+ easing: "", // the easing to use for this animation
1406
+ render: null, // render function used by the animation service
1407
+
1408
+ onAnimationProgress: null, // user specified callback to fire on each step of the animation
1409
+ onAnimationComplete: null, // user specified callback to fire when the animation finishes
1410
+ });
1411
+
1194
1412
  Chart.Tooltip = Chart.Element.extend({
1195
1413
  draw : function(){
1196
1414
 
@@ -1202,7 +1420,7 @@
1202
1420
  this.yAlign = "above";
1203
1421
 
1204
1422
  //Distance between the actual element.y position and the start of the tooltip caret
1205
- var caretPadding = 2;
1423
+ var caretPadding = this.caretPadding = 2;
1206
1424
 
1207
1425
  var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
1208
1426
  tooltipRectHeight = this.fontSize + 2*this.yPadding,
@@ -1224,47 +1442,53 @@
1224
1442
 
1225
1443
  ctx.fillStyle = this.fillColor;
1226
1444
 
1227
- switch(this.yAlign)
1228
- {
1229
- case "above":
1230
- //Draw a caret above the x/y
1231
- ctx.beginPath();
1232
- ctx.moveTo(this.x,this.y - caretPadding);
1233
- ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
1234
- ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
1235
- ctx.closePath();
1236
- ctx.fill();
1237
- break;
1238
- case "below":
1239
- tooltipY = this.y + caretPadding + this.caretHeight;
1240
- //Draw a caret below the x/y
1241
- ctx.beginPath();
1242
- ctx.moveTo(this.x, this.y + caretPadding);
1243
- ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
1244
- ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
1245
- ctx.closePath();
1246
- ctx.fill();
1247
- break;
1445
+ // Custom Tooltips
1446
+ if(this.custom){
1447
+ this.custom(this);
1248
1448
  }
1449
+ else{
1450
+ switch(this.yAlign)
1451
+ {
1452
+ case "above":
1453
+ //Draw a caret above the x/y
1454
+ ctx.beginPath();
1455
+ ctx.moveTo(this.x,this.y - caretPadding);
1456
+ ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
1457
+ ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
1458
+ ctx.closePath();
1459
+ ctx.fill();
1460
+ break;
1461
+ case "below":
1462
+ tooltipY = this.y + caretPadding + this.caretHeight;
1463
+ //Draw a caret below the x/y
1464
+ ctx.beginPath();
1465
+ ctx.moveTo(this.x, this.y + caretPadding);
1466
+ ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
1467
+ ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
1468
+ ctx.closePath();
1469
+ ctx.fill();
1470
+ break;
1471
+ }
1249
1472
 
1250
- switch(this.xAlign)
1251
- {
1252
- case "left":
1253
- tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
1254
- break;
1255
- case "right":
1256
- tooltipX = this.x - (this.cornerRadius + this.caretHeight);
1257
- break;
1258
- }
1473
+ switch(this.xAlign)
1474
+ {
1475
+ case "left":
1476
+ tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
1477
+ break;
1478
+ case "right":
1479
+ tooltipX = this.x - (this.cornerRadius + this.caretHeight);
1480
+ break;
1481
+ }
1259
1482
 
1260
- drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
1483
+ drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
1261
1484
 
1262
- ctx.fill();
1485
+ ctx.fill();
1263
1486
 
1264
- ctx.fillStyle = this.textColor;
1265
- ctx.textAlign = "center";
1266
- ctx.textBaseline = "middle";
1267
- ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
1487
+ ctx.fillStyle = this.textColor;
1488
+ ctx.textAlign = "center";
1489
+ ctx.textBaseline = "middle";
1490
+ ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
1491
+ }
1268
1492
  }
1269
1493
  });
1270
1494
 
@@ -1274,7 +1498,8 @@
1274
1498
 
1275
1499
  this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
1276
1500
 
1277
- this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
1501
+ this.titleHeight = this.title ? this.titleFontSize * 1.5 : 0;
1502
+ this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleHeight;
1278
1503
 
1279
1504
  this.ctx.font = this.titleFont;
1280
1505
 
@@ -1289,7 +1514,6 @@
1289
1514
  var halfHeight = this.height/2;
1290
1515
 
1291
1516
  //Check to ensure the height will fit on the canvas
1292
- //The three is to buffer form the very
1293
1517
  if (this.y - halfHeight < 0 ){
1294
1518
  this.y = halfHeight;
1295
1519
  } else if (this.y + halfHeight > this.chart.height){
@@ -1311,43 +1535,49 @@
1311
1535
 
1312
1536
  //If the index is zero, we're getting the title
1313
1537
  if (index === 0){
1314
- return baseLineHeight + this.titleFontSize/2;
1538
+ return baseLineHeight + this.titleHeight / 3;
1315
1539
  } else{
1316
- return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
1540
+ return baseLineHeight + ((this.fontSize * 1.5 * afterTitleIndex) + this.fontSize / 2) + this.titleHeight;
1317
1541
  }
1318
1542
 
1319
1543
  },
1320
1544
  draw : function(){
1321
- drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
1322
- var ctx = this.ctx;
1323
- ctx.fillStyle = this.fillColor;
1324
- ctx.fill();
1325
- ctx.closePath();
1545
+ // Custom Tooltips
1546
+ if(this.custom){
1547
+ this.custom(this);
1548
+ }
1549
+ else{
1550
+ drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
1551
+ var ctx = this.ctx;
1552
+ ctx.fillStyle = this.fillColor;
1553
+ ctx.fill();
1554
+ ctx.closePath();
1326
1555
 
1327
- ctx.textAlign = "left";
1328
- ctx.textBaseline = "middle";
1329
- ctx.fillStyle = this.titleTextColor;
1330
- ctx.font = this.titleFont;
1556
+ ctx.textAlign = "left";
1557
+ ctx.textBaseline = "middle";
1558
+ ctx.fillStyle = this.titleTextColor;
1559
+ ctx.font = this.titleFont;
1331
1560
 
1332
- ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
1561
+ ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
1333
1562
 
1334
- ctx.font = this.font;
1335
- helpers.each(this.labels,function(label,index){
1336
- ctx.fillStyle = this.textColor;
1337
- ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
1563
+ ctx.font = this.font;
1564
+ helpers.each(this.labels,function(label,index){
1565
+ ctx.fillStyle = this.textColor;
1566
+ ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
1338
1567
 
1339
- //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
1340
- //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1341
- //Instead we'll make a white filled block to put the legendColour palette over.
1568
+ //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
1569
+ //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1570
+ //Instead we'll make a white filled block to put the legendColour palette over.
1342
1571
 
1343
- ctx.fillStyle = this.legendColorBackground;
1344
- ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1572
+ ctx.fillStyle = this.legendColorBackground;
1573
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1345
1574
 
1346
- ctx.fillStyle = this.legendColors[index].fill;
1347
- ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1575
+ ctx.fillStyle = this.legendColors[index].fill;
1576
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1348
1577
 
1349
1578
 
1350
- },this);
1579
+ },this);
1580
+ }
1351
1581
  }
1352
1582
  });
1353
1583
 
@@ -1363,7 +1593,7 @@
1363
1593
  for (var i=0; i<=this.steps; i++){
1364
1594
  this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1365
1595
  }
1366
- this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
1596
+ this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) + 10 : 0;
1367
1597
  },
1368
1598
  addXLabel : function(label){
1369
1599
  this.xLabels.push(label);
@@ -1387,6 +1617,9 @@
1387
1617
  this.startPoint += this.padding;
1388
1618
  this.endPoint -= this.padding;
1389
1619
 
1620
+ // Cache the starting endpoint, excluding the space for x labels
1621
+ var cachedEndPoint = this.endPoint;
1622
+
1390
1623
  // Cache the starting height, so can determine if we need to recalculate the scale yAxis
1391
1624
  var cachedHeight = this.endPoint - this.startPoint,
1392
1625
  cachedYLabelWidth;
@@ -1418,6 +1651,7 @@
1418
1651
 
1419
1652
  // Only go through the xLabel loop again if the yLabel width has changed
1420
1653
  if (cachedYLabelWidth < this.yLabelWidth){
1654
+ this.endPoint = cachedEndPoint;
1421
1655
  this.calculateXLabelRotation();
1422
1656
  }
1423
1657
  }
@@ -1436,7 +1670,7 @@
1436
1670
 
1437
1671
 
1438
1672
  this.xScalePaddingRight = lastWidth/2 + 3;
1439
- this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
1673
+ this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth) ? firstWidth/2 : this.yLabelWidth;
1440
1674
 
1441
1675
  this.xLabelRotation = 0;
1442
1676
  if (this.display){
@@ -1455,7 +1689,7 @@
1455
1689
  lastRotated = cosRotation * lastWidth;
1456
1690
 
1457
1691
  // We're right aligning the text now.
1458
- if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
1692
+ if (firstRotated + this.fontSize / 2 > this.yLabelWidth){
1459
1693
  this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
1460
1694
  }
1461
1695
  this.xScalePaddingRight = this.fontSize/2;
@@ -1490,7 +1724,7 @@
1490
1724
  var isRotated = (this.xLabelRotation > 0),
1491
1725
  // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
1492
1726
  innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
1493
- valueWidth = innerWidth/(this.valuesCount - ((this.offsetGridLines) ? 0 : 1)),
1727
+ valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
1494
1728
  valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
1495
1729
 
1496
1730
  if (this.offsetGridLines){
@@ -1512,14 +1746,24 @@
1512
1746
  ctx.font = this.font;
1513
1747
  each(this.yLabels,function(labelString,index){
1514
1748
  var yLabelCenter = this.endPoint - (yLabelGap * index),
1515
- linePositionY = Math.round(yLabelCenter);
1749
+ linePositionY = Math.round(yLabelCenter),
1750
+ drawHorizontalLine = this.showHorizontalLines;
1516
1751
 
1517
1752
  ctx.textAlign = "right";
1518
1753
  ctx.textBaseline = "middle";
1519
1754
  if (this.showLabels){
1520
1755
  ctx.fillText(labelString,xStart - 10,yLabelCenter);
1521
1756
  }
1522
- ctx.beginPath();
1757
+
1758
+ // This is X axis, so draw it
1759
+ if (index === 0 && !drawHorizontalLine){
1760
+ drawHorizontalLine = true;
1761
+ }
1762
+
1763
+ if (drawHorizontalLine){
1764
+ ctx.beginPath();
1765
+ }
1766
+
1523
1767
  if (index > 0){
1524
1768
  // This is a grid line in the centre, so drop that
1525
1769
  ctx.lineWidth = this.gridLineWidth;
@@ -1532,10 +1776,12 @@
1532
1776
 
1533
1777
  linePositionY += helpers.aliasPixel(ctx.lineWidth);
1534
1778
 
1535
- ctx.moveTo(xStart, linePositionY);
1536
- ctx.lineTo(this.width, linePositionY);
1537
- ctx.stroke();
1538
- ctx.closePath();
1779
+ if(drawHorizontalLine){
1780
+ ctx.moveTo(xStart, linePositionY);
1781
+ ctx.lineTo(this.width, linePositionY);
1782
+ ctx.stroke();
1783
+ ctx.closePath();
1784
+ }
1539
1785
 
1540
1786
  ctx.lineWidth = this.lineWidth;
1541
1787
  ctx.strokeStyle = this.lineColor;
@@ -1551,9 +1797,17 @@
1551
1797
  var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
1552
1798
  // Check to see if line/bar here and decide where to place the line
1553
1799
  linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
1554
- isRotated = (this.xLabelRotation > 0);
1800
+ isRotated = (this.xLabelRotation > 0),
1801
+ drawVerticalLine = this.showVerticalLines;
1555
1802
 
1556
- ctx.beginPath();
1803
+ // This is Y axis, so draw it
1804
+ if (index === 0 && !drawVerticalLine){
1805
+ drawVerticalLine = true;
1806
+ }
1807
+
1808
+ if (drawVerticalLine){
1809
+ ctx.beginPath();
1810
+ }
1557
1811
 
1558
1812
  if (index > 0){
1559
1813
  // This is a grid line in the centre, so drop that
@@ -1564,10 +1818,13 @@
1564
1818
  ctx.lineWidth = this.lineWidth;
1565
1819
  ctx.strokeStyle = this.lineColor;
1566
1820
  }
1567
- ctx.moveTo(linePos,this.endPoint);
1568
- ctx.lineTo(linePos,this.startPoint - 3);
1569
- ctx.stroke();
1570
- ctx.closePath();
1821
+
1822
+ if (drawVerticalLine){
1823
+ ctx.moveTo(linePos,this.endPoint);
1824
+ ctx.lineTo(linePos,this.startPoint - 3);
1825
+ ctx.stroke();
1826
+ ctx.closePath();
1827
+ }
1571
1828
 
1572
1829
 
1573
1830
  ctx.lineWidth = this.lineWidth;
@@ -1814,14 +2071,40 @@
1814
2071
  ctx.lineWidth = this.angleLineWidth;
1815
2072
  ctx.strokeStyle = this.angleLineColor;
1816
2073
  for (var i = this.valuesCount - 1; i >= 0; i--) {
2074
+ var centerOffset = null, outerPosition = null;
2075
+
1817
2076
  if (this.angleLineWidth > 0){
1818
- var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
2077
+ centerOffset = this.calculateCenterOffset(this.max);
2078
+ outerPosition = this.getPointPosition(i, centerOffset);
1819
2079
  ctx.beginPath();
1820
2080
  ctx.moveTo(this.xCenter, this.yCenter);
1821
2081
  ctx.lineTo(outerPosition.x, outerPosition.y);
1822
2082
  ctx.stroke();
1823
2083
  ctx.closePath();
1824
2084
  }
2085
+
2086
+ if (this.backgroundColors && this.backgroundColors.length == this.valuesCount) {
2087
+ if (centerOffset == null)
2088
+ centerOffset = this.calculateCenterOffset(this.max);
2089
+
2090
+ if (outerPosition == null)
2091
+ outerPosition = this.getPointPosition(i, centerOffset);
2092
+
2093
+ var previousOuterPosition = this.getPointPosition(i === 0 ? this.valuesCount - 1 : i - 1, centerOffset);
2094
+ var nextOuterPosition = this.getPointPosition(i === this.valuesCount - 1 ? 0 : i + 1, centerOffset);
2095
+
2096
+ var previousOuterHalfway = { x: (previousOuterPosition.x + outerPosition.x) / 2, y: (previousOuterPosition.y + outerPosition.y) / 2 };
2097
+ var nextOuterHalfway = { x: (outerPosition.x + nextOuterPosition.x) / 2, y: (outerPosition.y + nextOuterPosition.y) / 2 };
2098
+
2099
+ ctx.beginPath();
2100
+ ctx.moveTo(this.xCenter, this.yCenter);
2101
+ ctx.lineTo(previousOuterHalfway.x, previousOuterHalfway.y);
2102
+ ctx.lineTo(outerPosition.x, outerPosition.y);
2103
+ ctx.lineTo(nextOuterHalfway.x, nextOuterHalfway.y);
2104
+ ctx.fillStyle = this.backgroundColors[i];
2105
+ ctx.fill();
2106
+ ctx.closePath();
2107
+ }
1825
2108
  // Extra 3px out for some label spacing
1826
2109
  var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
1827
2110
  ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
@@ -1858,6 +2141,93 @@
1858
2141
  }
1859
2142
  });
1860
2143
 
2144
+ Chart.animationService = {
2145
+ frameDuration: 17,
2146
+ animations: [],
2147
+ dropFrames: 0,
2148
+ addAnimation: function(chartInstance, animationObject) {
2149
+ for (var index = 0; index < this.animations.length; ++ index){
2150
+ if (this.animations[index].chartInstance === chartInstance){
2151
+ // replacing an in progress animation
2152
+ this.animations[index].animationObject = animationObject;
2153
+ return;
2154
+ }
2155
+ }
2156
+
2157
+ this.animations.push({
2158
+ chartInstance: chartInstance,
2159
+ animationObject: animationObject
2160
+ });
2161
+
2162
+ // If there are no animations queued, manually kickstart a digest, for lack of a better word
2163
+ if (this.animations.length == 1) {
2164
+ helpers.requestAnimFrame.call(window, this.digestWrapper);
2165
+ }
2166
+ },
2167
+ // Cancel the animation for a given chart instance
2168
+ cancelAnimation: function(chartInstance) {
2169
+ var index = helpers.findNextWhere(this.animations, function(animationWrapper) {
2170
+ return animationWrapper.chartInstance === chartInstance;
2171
+ });
2172
+
2173
+ if (index)
2174
+ {
2175
+ this.animations.splice(index, 1);
2176
+ }
2177
+ },
2178
+ // calls startDigest with the proper context
2179
+ digestWrapper: function() {
2180
+ Chart.animationService.startDigest.call(Chart.animationService);
2181
+ },
2182
+ startDigest: function() {
2183
+
2184
+ var startTime = Date.now();
2185
+ var framesToDrop = 0;
2186
+
2187
+ if(this.dropFrames > 1){
2188
+ framesToDrop = Math.floor(this.dropFrames);
2189
+ this.dropFrames -= framesToDrop;
2190
+ }
2191
+
2192
+ for (var i = 0; i < this.animations.length; i++) {
2193
+
2194
+ if (this.animations[i].animationObject.currentStep === null){
2195
+ this.animations[i].animationObject.currentStep = 0;
2196
+ }
2197
+
2198
+ this.animations[i].animationObject.currentStep += 1 + framesToDrop;
2199
+ if(this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps){
2200
+ this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
2201
+ }
2202
+
2203
+ this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
2204
+
2205
+ // Check if executed the last frame.
2206
+ if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps){
2207
+ // Call onAnimationComplete
2208
+ this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance);
2209
+ // Remove the animation.
2210
+ this.animations.splice(i, 1);
2211
+ // Keep the index in place to offset the splice
2212
+ i--;
2213
+ }
2214
+ }
2215
+
2216
+ var endTime = Date.now();
2217
+ var delay = endTime - startTime - this.frameDuration;
2218
+ var frameDelay = delay / this.frameDuration;
2219
+
2220
+ if(frameDelay > 1){
2221
+ this.dropFrames += frameDelay;
2222
+ }
2223
+
2224
+ // Do we have more stuff to animate?
2225
+ if (this.animations.length > 0){
2226
+ helpers.requestAnimFrame.call(window, this.digestWrapper);
2227
+ }
2228
+ }
2229
+ };
2230
+
1861
2231
  // Attach global event to resize each chart instance when the browser resizes
1862
2232
  helpers.addEvent(window, "resize", (function(){
1863
2233
  // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
@@ -1915,6 +2285,12 @@
1915
2285
  //Number - Width of the grid lines
1916
2286
  scaleGridLineWidth : 1,
1917
2287
 
2288
+ //Boolean - Whether to show horizontal lines (except X axis)
2289
+ scaleShowHorizontalLines: true,
2290
+
2291
+ //Boolean - Whether to show vertical lines (except Y axis)
2292
+ scaleShowVerticalLines: true,
2293
+
1918
2294
  //Boolean - If there is a stroke on each bar
1919
2295
  barShowStroke : true,
1920
2296
 
@@ -1928,7 +2304,7 @@
1928
2304
  barDatasetSpacing : 1,
1929
2305
 
1930
2306
  //String - A legend template
1931
- legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
2307
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></span></li><%}%></ul>"
1932
2308
 
1933
2309
  };
1934
2310
 
@@ -2000,18 +2376,16 @@
2000
2376
  this.datasets.push(datasetObject);
2001
2377
 
2002
2378
  helpers.each(dataset.data,function(dataPoint,index){
2003
- if (helpers.isNumber(dataPoint)){
2004
- //Add a new point for each piece of data, passing any required data to draw.
2005
- datasetObject.bars.push(new this.BarClass({
2006
- value : dataPoint,
2007
- label : data.labels[index],
2008
- datasetLabel: dataset.label,
2009
- strokeColor : dataset.strokeColor,
2010
- fillColor : dataset.fillColor,
2011
- highlightFill : dataset.highlightFill || dataset.fillColor,
2012
- highlightStroke : dataset.highlightStroke || dataset.strokeColor
2013
- }));
2014
- }
2379
+ //Add a new point for each piece of data, passing any required data to draw.
2380
+ datasetObject.bars.push(new this.BarClass({
2381
+ value : dataPoint,
2382
+ label : data.labels[index],
2383
+ datasetLabel: dataset.label,
2384
+ strokeColor : dataset.strokeColor,
2385
+ fillColor : dataset.fillColor,
2386
+ highlightFill : dataset.highlightFill || dataset.fillColor,
2387
+ highlightStroke : dataset.highlightStroke || dataset.strokeColor
2388
+ }));
2015
2389
  },this);
2016
2390
 
2017
2391
  },this);
@@ -2104,6 +2478,8 @@
2104
2478
  font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2105
2479
  lineWidth : this.options.scaleLineWidth,
2106
2480
  lineColor : this.options.scaleLineColor,
2481
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
2482
+ showVerticalLines : this.options.scaleShowVerticalLines,
2107
2483
  gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2108
2484
  gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2109
2485
  padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
@@ -2126,19 +2502,18 @@
2126
2502
  addData : function(valuesArray,label){
2127
2503
  //Map the values array for each of the datasets
2128
2504
  helpers.each(valuesArray,function(value,datasetIndex){
2129
- if (helpers.isNumber(value)){
2130
- //Add a new point for each piece of data, passing any required data to draw.
2131
- this.datasets[datasetIndex].bars.push(new this.BarClass({
2132
- value : value,
2133
- label : label,
2134
- x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
2135
- y: this.scale.endPoint,
2136
- width : this.scale.calculateBarWidth(this.datasets.length),
2137
- base : this.scale.endPoint,
2138
- strokeColor : this.datasets[datasetIndex].strokeColor,
2139
- fillColor : this.datasets[datasetIndex].fillColor
2140
- }));
2141
- }
2505
+ //Add a new point for each piece of data, passing any required data to draw.
2506
+ this.datasets[datasetIndex].bars.push(new this.BarClass({
2507
+ value : value,
2508
+ label : label,
2509
+ datasetLabel: this.datasets[datasetIndex].label,
2510
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
2511
+ y: this.scale.endPoint,
2512
+ width : this.scale.calculateBarWidth(this.datasets.length),
2513
+ base : this.scale.endPoint,
2514
+ strokeColor : this.datasets[datasetIndex].strokeColor,
2515
+ fillColor : this.datasets[datasetIndex].fillColor
2516
+ }));
2142
2517
  },this);
2143
2518
 
2144
2519
  this.scale.addXLabel(label);
@@ -2175,13 +2550,15 @@
2175
2550
  //Draw all the bars for each dataset
2176
2551
  helpers.each(this.datasets,function(dataset,datasetIndex){
2177
2552
  helpers.each(dataset.bars,function(bar,index){
2178
- bar.base = this.scale.endPoint;
2179
- //Transition then draw
2180
- bar.transition({
2181
- x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2182
- y : this.scale.calculateY(bar.value),
2183
- width : this.scale.calculateBarWidth(this.datasets.length)
2184
- }, easingDecimal).draw();
2553
+ if (bar.hasValue()){
2554
+ bar.base = this.scale.endPoint;
2555
+ //Transition then draw
2556
+ bar.transition({
2557
+ x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2558
+ y : this.scale.calculateY(bar.value),
2559
+ width : this.scale.calculateBarWidth(this.datasets.length)
2560
+ }, easingDecimal).draw();
2561
+ }
2185
2562
  },this);
2186
2563
 
2187
2564
  },this);
@@ -2190,6 +2567,7 @@
2190
2567
 
2191
2568
 
2192
2569
  }).call(this);
2570
+
2193
2571
  (function(){
2194
2572
  "use strict";
2195
2573
 
@@ -2224,11 +2602,10 @@
2224
2602
  animateScale : false,
2225
2603
 
2226
2604
  //String - A legend template
2227
- legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
2605
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"><%if(segments[i].label){%><%=segments[i].label%><%}%></span></li><%}%></ul>"
2228
2606
 
2229
2607
  };
2230
2608
 
2231
-
2232
2609
  Chart.Type.extend({
2233
2610
  //Passing in a name registers this chart in the Chart namespace
2234
2611
  name: "Doughnut",
@@ -2265,6 +2642,9 @@
2265
2642
  this.calculateTotal(data);
2266
2643
 
2267
2644
  helpers.each(data,function(datapoint, index){
2645
+ if (!datapoint.color) {
2646
+ datapoint.color = 'hsl(' + (360 * index / data.length) + ', 100%, 50%)';
2647
+ }
2268
2648
  this.addData(datapoint, index, true);
2269
2649
  },this);
2270
2650
 
@@ -2281,7 +2661,11 @@
2281
2661
  return segmentsArray;
2282
2662
  },
2283
2663
  addData : function(segment, atIndex, silent){
2284
- var index = atIndex || this.segments.length;
2664
+ var index = atIndex !== undefined ? atIndex : this.segments.length;
2665
+ if ( typeof(segment.color) === "undefined" ) {
2666
+ segment.color = Chart.defaults.global.segmentColorDefault[index % Chart.defaults.global.segmentColorDefault.length];
2667
+ segment.highlight = Chart.defaults.global.segmentHighlightColorDefaults[index % Chart.defaults.global.segmentHighlightColorDefaults.length];
2668
+ }
2285
2669
  this.segments.splice(index, 0, new this.SegmentArc({
2286
2670
  value : segment.value,
2287
2671
  outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
@@ -2300,13 +2684,17 @@
2300
2684
  this.update();
2301
2685
  }
2302
2686
  },
2303
- calculateCircumference : function(value){
2304
- return (Math.PI*2)*(value / this.total);
2687
+ calculateCircumference : function(value) {
2688
+ if ( this.total > 0 ) {
2689
+ return (Math.PI*2)*(value / this.total);
2690
+ } else {
2691
+ return 0;
2692
+ }
2305
2693
  },
2306
2694
  calculateTotal : function(data){
2307
2695
  this.total = 0;
2308
2696
  helpers.each(data,function(segment){
2309
- this.total += segment.value;
2697
+ this.total += Math.abs(segment.value);
2310
2698
  },this);
2311
2699
  },
2312
2700
  update : function(){
@@ -2374,6 +2762,7 @@
2374
2762
  });
2375
2763
 
2376
2764
  }).call(this);
2765
+
2377
2766
  (function(){
2378
2767
  "use strict";
2379
2768
 
@@ -2392,6 +2781,12 @@
2392
2781
  //Number - Width of the grid lines
2393
2782
  scaleGridLineWidth : 1,
2394
2783
 
2784
+ //Boolean - Whether to show horizontal lines (except X axis)
2785
+ scaleShowHorizontalLines: true,
2786
+
2787
+ //Boolean - Whether to show vertical lines (except Y axis)
2788
+ scaleShowVerticalLines: true,
2789
+
2395
2790
  //Boolean - Whether the line is curved between points
2396
2791
  bezierCurve : false,
2397
2792
 
@@ -2420,7 +2815,10 @@
2420
2815
  datasetFill : true,
2421
2816
 
2422
2817
  //String - A legend template
2423
- legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
2818
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></span></li><%}%></ul>",
2819
+
2820
+ //Boolean - Whether to horizontally center the label and point dot inside the grid
2821
+ offsetGridLines : false
2424
2822
 
2425
2823
  };
2426
2824
 
@@ -2431,9 +2829,10 @@
2431
2829
  initialize: function(data){
2432
2830
  //Declare the extension of the default point, to cater for the options passed in to the constructor
2433
2831
  this.PointClass = Chart.Point.extend({
2832
+ offsetGridLines : this.options.offsetGridLines,
2434
2833
  strokeWidth : this.options.pointDotStrokeWidth,
2435
2834
  radius : this.options.pointDotRadius,
2436
- display : this.options.pointDot,
2835
+ display: this.options.pointDot,
2437
2836
  hitDetectionRadius : this.options.pointHitDetectionRadius,
2438
2837
  ctx : this.chart.ctx,
2439
2838
  inRange : function(mouseX){
@@ -2474,21 +2873,16 @@
2474
2873
 
2475
2874
 
2476
2875
  helpers.each(dataset.data,function(dataPoint,index){
2477
- //Best way to do this? or in draw sequence...?
2478
- if (helpers.isNumber(dataPoint)){
2479
2876
  //Add a new point for each piece of data, passing any required data to draw.
2480
- datasetObject.points.push(new this.PointClass({
2481
- value : dataPoint,
2482
- label : data.labels[index],
2483
- // x: this.scale.calculateX(index),
2484
- // y: this.scale.endPoint,
2485
- datasetLabel: dataset.label,
2486
- strokeColor : dataset.pointStrokeColor,
2487
- fillColor : dataset.pointColor,
2488
- highlightFill : dataset.pointHighlightFill || dataset.pointColor,
2489
- highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
2490
- }));
2491
- }
2877
+ datasetObject.points.push(new this.PointClass({
2878
+ value : dataPoint,
2879
+ label : data.labels[index],
2880
+ datasetLabel: dataset.label,
2881
+ strokeColor : dataset.pointStrokeColor,
2882
+ fillColor : dataset.pointColor,
2883
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
2884
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
2885
+ }));
2492
2886
  },this);
2493
2887
 
2494
2888
  this.buildScale(data.labels);
@@ -2551,6 +2945,7 @@
2551
2945
  width : this.chart.width,
2552
2946
  ctx : this.chart.ctx,
2553
2947
  textColor : this.options.scaleFontColor,
2948
+ offsetGridLines : this.options.offsetGridLines,
2554
2949
  fontSize : this.options.scaleFontSize,
2555
2950
  fontStyle : this.options.scaleFontStyle,
2556
2951
  fontFamily : this.options.scaleFontFamily,
@@ -2571,6 +2966,8 @@
2571
2966
  font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2572
2967
  lineWidth : this.options.scaleLineWidth,
2573
2968
  lineColor : this.options.scaleLineColor,
2969
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
2970
+ showVerticalLines : this.options.scaleShowVerticalLines,
2574
2971
  gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2575
2972
  gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2576
2973
  padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
@@ -2595,17 +2992,16 @@
2595
2992
  //Map the values array for each of the datasets
2596
2993
 
2597
2994
  helpers.each(valuesArray,function(value,datasetIndex){
2598
- if (helpers.isNumber(value)){
2599
- //Add a new point for each piece of data, passing any required data to draw.
2600
- this.datasets[datasetIndex].points.push(new this.PointClass({
2601
- value : value,
2602
- label : label,
2603
- x: this.scale.calculateX(this.scale.valuesCount+1),
2604
- y: this.scale.endPoint,
2605
- strokeColor : this.datasets[datasetIndex].pointStrokeColor,
2606
- fillColor : this.datasets[datasetIndex].pointColor
2607
- }));
2608
- }
2995
+ //Add a new point for each piece of data, passing any required data to draw.
2996
+ this.datasets[datasetIndex].points.push(new this.PointClass({
2997
+ value : value,
2998
+ label : label,
2999
+ datasetLabel: this.datasets[datasetIndex].label,
3000
+ x: this.scale.calculateX(this.scale.valuesCount+1),
3001
+ y: this.scale.endPoint,
3002
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
3003
+ fillColor : this.datasets[datasetIndex].pointColor
3004
+ }));
2609
3005
  },this);
2610
3006
 
2611
3007
  this.scale.addXLabel(label);
@@ -2633,37 +3029,65 @@
2633
3029
 
2634
3030
  var ctx = this.chart.ctx;
2635
3031
 
3032
+ // Some helper methods for getting the next/prev points
3033
+ var hasValue = function(item){
3034
+ return item.value !== null;
3035
+ },
3036
+ nextPoint = function(point, collection, index){
3037
+ return helpers.findNextWhere(collection, hasValue, index) || point;
3038
+ },
3039
+ previousPoint = function(point, collection, index){
3040
+ return helpers.findPreviousWhere(collection, hasValue, index) || point;
3041
+ };
3042
+
3043
+ if (!this.scale) return;
2636
3044
  this.scale.draw(easingDecimal);
2637
3045
 
2638
3046
 
2639
3047
  helpers.each(this.datasets,function(dataset){
3048
+ var pointsWithValues = helpers.where(dataset.points, hasValue);
2640
3049
 
2641
3050
  //Transition each point first so that the line and point drawing isn't out of sync
2642
3051
  //We can use this extra loop to calculate the control points of this dataset also in this loop
2643
3052
 
2644
- helpers.each(dataset.points,function(point,index){
2645
- point.transition({
2646
- y : this.scale.calculateY(point.value),
2647
- x : this.scale.calculateX(index)
2648
- }, easingDecimal);
2649
-
3053
+ helpers.each(dataset.points, function(point, index){
3054
+ if (point.hasValue()){
3055
+ point.transition({
3056
+ y : this.scale.calculateY(point.value),
3057
+ x : this.scale.calculateX(index)
3058
+ }, easingDecimal);
3059
+ }
2650
3060
  },this);
2651
3061
 
2652
3062
 
2653
- // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
3063
+ // Control points need to be calculated in a separate loop, because we need to know the current x/y of the point
2654
3064
  // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
2655
3065
  if (this.options.bezierCurve){
2656
- helpers.each(dataset.points,function(point,index){
2657
- //If we're at the start or end, we don't have a previous/next point
2658
- //By setting the tension to 0 here, the curve will transition to straight at the end
2659
- if (index === 0){
2660
- point.controlPoints = helpers.splineCurve(point,point,dataset.points[index+1],0);
3066
+ helpers.each(pointsWithValues, function(point, index){
3067
+ var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
3068
+ point.controlPoints = helpers.splineCurve(
3069
+ previousPoint(point, pointsWithValues, index),
3070
+ point,
3071
+ nextPoint(point, pointsWithValues, index),
3072
+ tension
3073
+ );
3074
+
3075
+ // Prevent the bezier going outside of the bounds of the graph
3076
+
3077
+ // Cap puter bezier handles to the upper/lower scale bounds
3078
+ if (point.controlPoints.outer.y > this.scale.endPoint){
3079
+ point.controlPoints.outer.y = this.scale.endPoint;
2661
3080
  }
2662
- else if (index >= dataset.points.length-1){
2663
- point.controlPoints = helpers.splineCurve(dataset.points[index-1],point,point,0);
3081
+ else if (point.controlPoints.outer.y < this.scale.startPoint){
3082
+ point.controlPoints.outer.y = this.scale.startPoint;
2664
3083
  }
2665
- else{
2666
- point.controlPoints = helpers.splineCurve(dataset.points[index-1],point,dataset.points[index+1],this.options.bezierCurveTension);
3084
+
3085
+ // Cap inner bezier handles to the upper/lower scale bounds
3086
+ if (point.controlPoints.inner.y > this.scale.endPoint){
3087
+ point.controlPoints.inner.y = this.scale.endPoint;
3088
+ }
3089
+ else if (point.controlPoints.inner.y < this.scale.startPoint){
3090
+ point.controlPoints.inner.y = this.scale.startPoint;
2667
3091
  }
2668
3092
  },this);
2669
3093
  }
@@ -2673,12 +3097,18 @@
2673
3097
  ctx.lineWidth = this.options.datasetStrokeWidth;
2674
3098
  ctx.strokeStyle = dataset.strokeColor;
2675
3099
  ctx.beginPath();
2676
- helpers.each(dataset.points,function(point,index){
2677
- if (index>0){
3100
+
3101
+ helpers.each(pointsWithValues, function(point, index){
3102
+ if (index === 0){
3103
+ ctx.moveTo(point.x, point.y);
3104
+ }
3105
+ else{
2678
3106
  if(this.options.bezierCurve){
3107
+ var previous = previousPoint(point, pointsWithValues, index);
3108
+
2679
3109
  ctx.bezierCurveTo(
2680
- dataset.points[index-1].controlPoints.outer.x,
2681
- dataset.points[index-1].controlPoints.outer.y,
3110
+ previous.controlPoints.outer.x,
3111
+ previous.controlPoints.outer.y,
2682
3112
  point.controlPoints.inner.x,
2683
3113
  point.controlPoints.inner.y,
2684
3114
  point.x,
@@ -2688,19 +3118,17 @@
2688
3118
  else{
2689
3119
  ctx.lineTo(point.x,point.y);
2690
3120
  }
2691
-
2692
3121
  }
2693
- else{
2694
- ctx.moveTo(point.x,point.y);
2695
- }
2696
- },this);
2697
- ctx.stroke();
3122
+ }, this);
2698
3123
 
3124
+ if (this.options.datasetStroke) {
3125
+ ctx.stroke();
3126
+ }
2699
3127
 
2700
- if (this.options.datasetFill){
3128
+ if (this.options.datasetFill && pointsWithValues.length > 0){
2701
3129
  //Round off the line by going to the base of the chart, back to the start, then fill.
2702
- ctx.lineTo(dataset.points[dataset.points.length-1].x, this.scale.endPoint);
2703
- ctx.lineTo(this.scale.calculateX(0), this.scale.endPoint);
3130
+ ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
3131
+ ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
2704
3132
  ctx.fillStyle = dataset.fillColor;
2705
3133
  ctx.closePath();
2706
3134
  ctx.fill();
@@ -2709,16 +3137,16 @@
2709
3137
  //Now draw the points over the line
2710
3138
  //A little inefficient double looping, but better than the line
2711
3139
  //lagging behind the point positions
2712
- helpers.each(dataset.points,function(point){
3140
+ helpers.each(pointsWithValues,function(point){
2713
3141
  point.draw();
2714
3142
  });
2715
-
2716
3143
  },this);
2717
3144
  }
2718
3145
  });
2719
3146
 
2720
3147
 
2721
3148
  }).call(this);
3149
+
2722
3150
  (function(){
2723
3151
  "use strict";
2724
3152
 
@@ -2749,7 +3177,7 @@
2749
3177
  //Boolean - Stroke a line around each segment in the chart
2750
3178
  segmentShowStroke : true,
2751
3179
 
2752
- //String - The colour of the stroke on each segement.
3180
+ //String - The colour of the stroke on each segment.
2753
3181
  segmentStrokeColor : "rgba(255,255,255,1)",
2754
3182
 
2755
3183
  //Number - The width of the stroke value in pixels
@@ -2768,7 +3196,7 @@
2768
3196
  animateScale : false,
2769
3197
 
2770
3198
  //String - A legend template
2771
- legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
3199
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"><%if(segments[i].label){%><%=segments[i].label%><%}%></span></li><%}%></ul>"
2772
3200
  };
2773
3201
 
2774
3202
 
@@ -2916,6 +3344,8 @@
2916
3344
  helpers.each(this.segments,function(segment){
2917
3345
  segment.save();
2918
3346
  });
3347
+
3348
+ this.reflow();
2919
3349
  this.render();
2920
3350
  },
2921
3351
  reflow : function(){
@@ -2967,6 +3397,7 @@
2967
3397
  });
2968
3398
 
2969
3399
  }).call(this);
3400
+
2970
3401
  (function(){
2971
3402
  "use strict";
2972
3403
 
@@ -2998,7 +3429,7 @@
2998
3429
  angleLineWidth : 1,
2999
3430
 
3000
3431
  //String - Point label font declaration
3001
- pointLabelFontFamily : "'Gotham', 'Gotham Round', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
3432
+ pointLabelFontFamily : "'Gotham Round', 'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
3002
3433
 
3003
3434
  //String - Point label font weight
3004
3435
  pointLabelFontStyle : "bold",
@@ -3031,7 +3462,7 @@
3031
3462
  datasetFill : true,
3032
3463
 
3033
3464
  //String - A legend template
3034
- legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
3465
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></span></li><%}%></ul>"
3035
3466
 
3036
3467
  },
3037
3468
 
@@ -3039,7 +3470,7 @@
3039
3470
  this.PointClass = Chart.Point.extend({
3040
3471
  strokeWidth : this.options.pointDotStrokeWidth,
3041
3472
  radius : this.options.pointDotRadius,
3042
- display : this.options.pointDot,
3473
+ display: this.options.pointDot,
3043
3474
  hitDetectionRadius : this.options.pointHitDetectionRadius,
3044
3475
  ctx : this.chart.ctx
3045
3476
  });
@@ -3071,7 +3502,6 @@
3071
3502
  var datasetObject = {
3072
3503
  label: dataset.label || null,
3073
3504
  fillColor : dataset.fillColor,
3074
- datasetLabel: dataset.label,
3075
3505
  strokeColor : dataset.strokeColor,
3076
3506
  pointColor : dataset.pointColor,
3077
3507
  pointStrokeColor : dataset.pointStrokeColor,
@@ -3081,24 +3511,22 @@
3081
3511
  this.datasets.push(datasetObject);
3082
3512
 
3083
3513
  helpers.each(dataset.data,function(dataPoint,index){
3084
- //Best way to do this? or in draw sequence...?
3085
- if (helpers.isNumber(dataPoint)){
3086
3514
  //Add a new point for each piece of data, passing any required data to draw.
3087
- var pointPosition;
3088
- if (!this.scale.animation){
3089
- pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
3090
- }
3091
- datasetObject.points.push(new this.PointClass({
3092
- value : dataPoint,
3093
- label : data.labels[index],
3094
- x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
3095
- y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
3096
- strokeColor : dataset.pointStrokeColor,
3097
- fillColor : dataset.pointColor,
3098
- highlightFill : dataset.pointHighlightFill || dataset.pointColor,
3099
- highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
3100
- }));
3515
+ var pointPosition;
3516
+ if (!this.scale.animation){
3517
+ pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
3101
3518
  }
3519
+ datasetObject.points.push(new this.PointClass({
3520
+ value : dataPoint,
3521
+ label : data.labels[index],
3522
+ datasetLabel: dataset.label,
3523
+ x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
3524
+ y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
3525
+ strokeColor : dataset.pointStrokeColor,
3526
+ fillColor : dataset.pointColor,
3527
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
3528
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
3529
+ }));
3102
3530
  },this);
3103
3531
 
3104
3532
  },this);
@@ -3146,6 +3574,7 @@
3146
3574
  showLabels: this.options.scaleShowLabels,
3147
3575
  showLabelBackdrop: this.options.scaleShowLabelBackdrop,
3148
3576
  backdropColor: this.options.scaleBackdropColor,
3577
+ backgroundColors: this.options.scaleBackgroundColors,
3149
3578
  backdropPaddingY : this.options.scaleBackdropPaddingY,
3150
3579
  backdropPaddingX: this.options.scaleBackdropPaddingX,
3151
3580
  lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
@@ -3213,17 +3642,16 @@
3213
3642
  //Map the values array for each of the datasets
3214
3643
  this.scale.valuesCount++;
3215
3644
  helpers.each(valuesArray,function(value,datasetIndex){
3216
- if (helpers.isNumber(value)){
3217
- var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
3218
- this.datasets[datasetIndex].points.push(new this.PointClass({
3219
- value : value,
3220
- label : label,
3221
- x: pointPosition.x,
3222
- y: pointPosition.y,
3223
- strokeColor : this.datasets[datasetIndex].pointStrokeColor,
3224
- fillColor : this.datasets[datasetIndex].pointColor
3225
- }));
3226
- }
3645
+ var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
3646
+ this.datasets[datasetIndex].points.push(new this.PointClass({
3647
+ value : value,
3648
+ label : label,
3649
+ datasetLabel: this.datasets[datasetIndex].label,
3650
+ x: pointPosition.x,
3651
+ y: pointPosition.y,
3652
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
3653
+ fillColor : this.datasets[datasetIndex].pointColor
3654
+ }));
3227
3655
  },this);
3228
3656
 
3229
3657
  this.scale.labels.push(label);
@@ -3270,7 +3698,9 @@
3270
3698
 
3271
3699
  //Transition each point first so that the line and point drawing isn't out of sync
3272
3700
  helpers.each(dataset.points,function(point,index){
3273
- point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
3701
+ if (point.hasValue()){
3702
+ point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
3703
+ }
3274
3704
  },this);
3275
3705
 
3276
3706
 
@@ -3291,13 +3721,16 @@
3291
3721
  ctx.stroke();
3292
3722
 
3293
3723
  ctx.fillStyle = dataset.fillColor;
3294
- ctx.fill();
3295
-
3724
+ if(this.options.datasetFill){
3725
+ ctx.fill();
3726
+ }
3296
3727
  //Now draw the points over the line
3297
3728
  //A little inefficient double looping, but better than the line
3298
3729
  //lagging behind the point positions
3299
3730
  helpers.each(dataset.points,function(point){
3300
- point.draw();
3731
+ if (point.hasValue()){
3732
+ point.draw();
3733
+ }
3301
3734
  });
3302
3735
 
3303
3736
  },this);