active_frontend 12.2.0 → 12.3.0

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