active_frontend 13.3.0 → 14.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.fasterer.yml +19 -0
  4. data/.reek +27 -0
  5. data/.rubocop.yml +38 -0
  6. data/.scss-lint.yml +27 -0
  7. data/Rakefile +1 -1
  8. data/active_frontend.gemspec +21 -18
  9. data/app/.DS_Store +0 -0
  10. data/app/assets/.DS_Store +0 -0
  11. data/app/assets/fonts/.DS_Store +0 -0
  12. data/app/assets/fonts/dripicons/.DS_Store +0 -0
  13. data/app/assets/fonts/dripicons/dripicons.woff +0 -0
  14. data/app/assets/fonts/fakt-pro/.DS_Store +0 -0
  15. data/app/assets/fonts/fakt-pro/fakt-pro-bold.woff +0 -0
  16. data/app/assets/fonts/fakt-pro/fakt-pro-normal.woff +0 -0
  17. data/app/assets/fonts/fakt-pro/fakt-pro-semibold.woff +0 -0
  18. data/app/assets/fonts/fakt-pro/fakt-pro-semilight.woff +0 -0
  19. data/app/assets/fonts/fakt-soft-pro/.DS_Store +0 -0
  20. data/app/assets/fonts/fakt-soft-pro/fakt-soft-pro-bold.woff +0 -0
  21. data/app/assets/fonts/fakt-soft-pro/fakt-soft-pro-normal.woff +0 -0
  22. data/app/assets/fonts/fakt-soft-pro/fakt-soft-pro-semibold.woff +0 -0
  23. data/app/assets/fonts/fakt-soft-pro/fakt-soft-pro-semilight.woff +0 -0
  24. data/app/assets/images/.DS_Store +0 -0
  25. data/app/assets/images/.keep +0 -0
  26. data/app/assets/images/placeholders/.DS_Store +0 -0
  27. data/app/assets/images/placeholders/camera-large.png +0 -0
  28. data/app/assets/images/placeholders/camera-small.png +0 -0
  29. data/app/assets/images/placeholders/camera.png +0 -0
  30. data/app/assets/images/placeholders/document-large.png +0 -0
  31. data/app/assets/images/placeholders/document-small.png +0 -0
  32. data/app/assets/images/placeholders/document.png +0 -0
  33. data/app/assets/images/placeholders/photo-large.png +0 -0
  34. data/app/assets/images/placeholders/{picture-small.png → photo-small.png} +0 -0
  35. data/app/assets/images/placeholders/photo.png +0 -0
  36. data/app/assets/images/placeholders/store-large.png +0 -0
  37. data/app/assets/images/placeholders/store-small.png +0 -0
  38. data/app/assets/images/placeholders/store.png +0 -0
  39. data/app/assets/images/placeholders/user-large.png +0 -0
  40. data/app/assets/images/placeholders/user-small.png +0 -0
  41. data/app/assets/images/placeholders/user.png +0 -0
  42. data/app/helpers/active_frontend_helper.rb +22 -37
  43. data/lib/.DS_Store +0 -0
  44. data/lib/active_frontend.rb +3 -4
  45. data/lib/active_frontend/.DS_Store +0 -0
  46. data/lib/active_frontend/version.rb +1 -1
  47. data/lib/generators/active_frontend/install_generator.rb +3 -3
  48. data/lib/generators/active_frontend/templates/install.js +35 -30
  49. data/lib/generators/active_frontend/templates/install.scss +55 -58
  50. data/vendor/.DS_Store +0 -0
  51. data/vendor/assets/.DS_Store +0 -0
  52. data/vendor/assets/javascripts/.DS_Store +0 -0
  53. data/vendor/assets/javascripts/active_frontend.js +35 -30
  54. data/vendor/assets/javascripts/base/_affix.js +170 -0
  55. data/vendor/assets/javascripts/base/_alert.js +80 -0
  56. data/vendor/assets/javascripts/base/_animation.js +106 -0
  57. data/vendor/assets/javascripts/base/_button.js +123 -0
  58. data/vendor/assets/javascripts/base/_carousel.js +237 -0
  59. data/vendor/assets/javascripts/base/_collapse.js +200 -0
  60. data/vendor/assets/javascripts/base/_colorpicker.js +147 -0
  61. data/vendor/assets/javascripts/base/_datepicker.js +1411 -0
  62. data/vendor/assets/javascripts/base/_dropdown.js +154 -0
  63. data/vendor/assets/javascripts/base/_filepicker.js +235 -0
  64. data/vendor/assets/javascripts/base/_hoverdown.js +116 -0
  65. data/vendor/assets/javascripts/base/_layout.js +126 -0
  66. data/vendor/assets/javascripts/base/_list.js +103 -0
  67. data/vendor/assets/javascripts/{_modal.js → base/_modal.js} +170 -167
  68. data/vendor/assets/javascripts/base/_popover.js +101 -0
  69. data/vendor/assets/javascripts/base/_scrollspy.js +161 -0
  70. data/vendor/assets/javascripts/base/_switch.js +160 -0
  71. data/vendor/assets/javascripts/base/_tab.js +139 -0
  72. data/vendor/assets/javascripts/base/_table.js +224 -0
  73. data/vendor/assets/javascripts/base/_timeago.js +270 -0
  74. data/vendor/assets/javascripts/base/_timepicker.js +541 -0
  75. data/vendor/assets/javascripts/base/_tooltip.js +525 -0
  76. data/vendor/assets/javascripts/base/_tour.js +268 -0
  77. data/vendor/assets/javascripts/base/_transition.js +52 -0
  78. data/vendor/assets/javascripts/base/_typeahead.js +362 -0
  79. data/vendor/assets/javascripts/extensions/_calendar.js +4709 -0
  80. data/vendor/assets/javascripts/extensions/_chart.js +9371 -0
  81. data/vendor/assets/javascripts/extensions/_map.js +2153 -0
  82. data/vendor/assets/stylesheets/.DS_Store +0 -0
  83. data/vendor/assets/stylesheets/{_mixin.scss → _utility.scss} +96 -10
  84. data/vendor/assets/stylesheets/_variable.scss +201 -19
  85. data/vendor/assets/stylesheets/active_frontend.scss +55 -58
  86. data/vendor/assets/stylesheets/blocks/_anchor.scss +15 -0
  87. data/vendor/assets/stylesheets/blocks/_button.scss +278 -0
  88. data/vendor/assets/stylesheets/blocks/_code.scss +144 -0
  89. data/vendor/assets/stylesheets/blocks/_common.scss +127 -0
  90. data/vendor/assets/stylesheets/blocks/_form.scss +508 -0
  91. data/vendor/assets/stylesheets/blocks/_icon.scss +359 -0
  92. data/vendor/assets/stylesheets/blocks/_list.scss +76 -0
  93. data/vendor/assets/stylesheets/blocks/_multimedia.scss +62 -0
  94. data/vendor/assets/stylesheets/blocks/_reset.scss +179 -0
  95. data/vendor/assets/stylesheets/blocks/_table.scss +211 -0
  96. data/vendor/assets/stylesheets/blocks/_typography.scss +204 -0
  97. data/vendor/assets/stylesheets/components/_ad.scss +78 -0
  98. data/vendor/assets/stylesheets/components/_affix.scss +14 -0
  99. data/vendor/assets/stylesheets/components/_alert.scss +50 -0
  100. data/vendor/assets/stylesheets/components/_animation.scss +1670 -0
  101. data/vendor/assets/stylesheets/components/_breadcrumb.scss +17 -0
  102. data/vendor/assets/stylesheets/components/_calendar.scss +213 -0
  103. data/vendor/assets/stylesheets/components/_card.scss +30 -0
  104. data/vendor/assets/stylesheets/components/_carousel.scss +135 -0
  105. data/vendor/assets/stylesheets/components/_chart.scss +10 -0
  106. data/vendor/assets/stylesheets/components/_collapse.scss +17 -0
  107. data/vendor/assets/stylesheets/components/_colorpicker.scss +38 -0
  108. data/vendor/assets/stylesheets/components/_datepicker.scss +80 -0
  109. data/vendor/assets/stylesheets/components/_dropmenu.scss +151 -0
  110. data/vendor/assets/stylesheets/components/_footer.scss +11 -0
  111. data/vendor/assets/stylesheets/components/_grid.scss +144 -0
  112. data/vendor/assets/stylesheets/components/_header.scss +99 -0
  113. data/vendor/assets/stylesheets/components/_label_and_badge.scss +57 -0
  114. data/vendor/assets/stylesheets/components/_layout.scss +63 -0
  115. data/vendor/assets/stylesheets/components/_map.scss +14 -0
  116. data/vendor/assets/stylesheets/components/_milestone.scss +49 -0
  117. data/vendor/assets/stylesheets/components/_missive.scss +40 -0
  118. data/vendor/assets/stylesheets/components/_modal.scss +126 -0
  119. data/vendor/assets/stylesheets/components/_nav_and_tab.scss +202 -0
  120. data/vendor/assets/stylesheets/components/_navbar.scss +66 -0
  121. data/vendor/assets/stylesheets/components/_pagination.scss +79 -0
  122. data/vendor/assets/stylesheets/components/_placeholder.scss +23 -0
  123. data/vendor/assets/stylesheets/components/_popover.scss +167 -0
  124. data/vendor/assets/stylesheets/components/_progress.scss +62 -0
  125. data/vendor/assets/stylesheets/components/_sidebar.scss +74 -0
  126. data/vendor/assets/stylesheets/components/_spinner.scss +83 -0
  127. data/vendor/assets/stylesheets/components/_switch.scss +150 -0
  128. data/vendor/assets/stylesheets/components/_timepicker.scss +30 -0
  129. data/vendor/assets/stylesheets/components/_tooltip.scss +93 -0
  130. data/vendor/assets/stylesheets/components/_transition.scss +12 -0
  131. data/vendor/assets/stylesheets/components/_typeahead.scss +18 -0
  132. metadata +150 -94
  133. data/app/assets/fonts/gotham/gotham-bold.woff +0 -0
  134. data/app/assets/fonts/gotham/gotham-book.woff +0 -0
  135. data/app/assets/fonts/gotham/gotham-light.woff +0 -0
  136. data/app/assets/fonts/gotham/gotham-medium.woff +0 -0
  137. data/app/assets/fonts/gotham/gotham-rounded-bold.woff +0 -0
  138. data/app/assets/fonts/gotham/gotham-rounded-book.woff +0 -0
  139. data/app/assets/fonts/gotham/gotham-rounded-light.woff +0 -0
  140. data/app/assets/fonts/gotham/gotham-rounded-medium.woff +0 -0
  141. data/app/assets/images/placeholders/archive-large.png +0 -0
  142. data/app/assets/images/placeholders/archive-small.png +0 -0
  143. data/app/assets/images/placeholders/archive.png +0 -0
  144. data/app/assets/images/placeholders/picture-large.png +0 -0
  145. data/app/assets/images/placeholders/picture.png +0 -0
  146. data/vendor/assets/javascripts/_affix.js +0 -153
  147. data/vendor/assets/javascripts/_alert.js +0 -85
  148. data/vendor/assets/javascripts/_animation.js +0 -103
  149. data/vendor/assets/javascripts/_button.js +0 -107
  150. data/vendor/assets/javascripts/_carousel.js +0 -228
  151. data/vendor/assets/javascripts/_chart.js +0 -3742
  152. data/vendor/assets/javascripts/_collapse.js +0 -202
  153. data/vendor/assets/javascripts/_color_picker.js +0 -108
  154. data/vendor/assets/javascripts/_date_picker.js +0 -1650
  155. data/vendor/assets/javascripts/_dropdown.js +0 -156
  156. data/vendor/assets/javascripts/_file_input.js +0 -71
  157. data/vendor/assets/javascripts/_hoverdown.js +0 -109
  158. data/vendor/assets/javascripts/_inputmask.js +0 -341
  159. data/vendor/assets/javascripts/_loader.js +0 -361
  160. data/vendor/assets/javascripts/_map.js +0 -2401
  161. data/vendor/assets/javascripts/_popover.js +0 -99
  162. data/vendor/assets/javascripts/_scrollspy.js +0 -163
  163. data/vendor/assets/javascripts/_slider.js +0 -1572
  164. data/vendor/assets/javascripts/_sort.js +0 -1432
  165. data/vendor/assets/javascripts/_swoggle.js +0 -415
  166. data/vendor/assets/javascripts/_tab.js +0 -146
  167. data/vendor/assets/javascripts/_tablespy.js +0 -1883
  168. data/vendor/assets/javascripts/_time_ago.js +0 -206
  169. data/vendor/assets/javascripts/_time_picker.js +0 -1088
  170. data/vendor/assets/javascripts/_tooltip.js +0 -504
  171. data/vendor/assets/javascripts/_transition.js +0 -50
  172. data/vendor/assets/javascripts/_typeahead.js +0 -366
  173. data/vendor/assets/stylesheets/_ad.scss +0 -63
  174. data/vendor/assets/stylesheets/_affix.scss +0 -14
  175. data/vendor/assets/stylesheets/_alert.scss +0 -114
  176. data/vendor/assets/stylesheets/_animation.scss +0 -1370
  177. data/vendor/assets/stylesheets/_breadcrumb.scss +0 -100
  178. data/vendor/assets/stylesheets/_button.scss +0 -386
  179. data/vendor/assets/stylesheets/_canvas.scss +0 -182
  180. data/vendor/assets/stylesheets/_carousel.scss +0 -158
  181. data/vendor/assets/stylesheets/_chart.scss +0 -15
  182. data/vendor/assets/stylesheets/_code.scss +0 -150
  183. data/vendor/assets/stylesheets/_collapse.scss +0 -14
  184. data/vendor/assets/stylesheets/_color.scss +0 -55
  185. data/vendor/assets/stylesheets/_colorpicker.scss +0 -63
  186. data/vendor/assets/stylesheets/_datepicker.scss +0 -122
  187. data/vendor/assets/stylesheets/_dropdown.scss +0 -248
  188. data/vendor/assets/stylesheets/_footer.scss +0 -71
  189. data/vendor/assets/stylesheets/_form.scss +0 -661
  190. data/vendor/assets/stylesheets/_grid.scss +0 -184
  191. data/vendor/assets/stylesheets/_header.scss +0 -156
  192. data/vendor/assets/stylesheets/_icon.scss +0 -362
  193. data/vendor/assets/stylesheets/_image.scss +0 -33
  194. data/vendor/assets/stylesheets/_label_and_badge.scss +0 -104
  195. data/vendor/assets/stylesheets/_link.scss +0 -55
  196. data/vendor/assets/stylesheets/_list.scss +0 -122
  197. data/vendor/assets/stylesheets/_loader.scss +0 -71
  198. data/vendor/assets/stylesheets/_map.scss +0 -44
  199. data/vendor/assets/stylesheets/_missive.scss +0 -74
  200. data/vendor/assets/stylesheets/_modal.scss +0 -204
  201. data/vendor/assets/stylesheets/_nav_and_tab.scss +0 -230
  202. data/vendor/assets/stylesheets/_navbar.scss +0 -73
  203. data/vendor/assets/stylesheets/_pagination.scss +0 -79
  204. data/vendor/assets/stylesheets/_panel.scss +0 -80
  205. data/vendor/assets/stylesheets/_placeholder.scss +0 -63
  206. data/vendor/assets/stylesheets/_popover.scss +0 -128
  207. data/vendor/assets/stylesheets/_progress.scss +0 -86
  208. data/vendor/assets/stylesheets/_reset.scss +0 -140
  209. data/vendor/assets/stylesheets/_sidebar.scss +0 -148
  210. data/vendor/assets/stylesheets/_slider.scss +0 -151
  211. data/vendor/assets/stylesheets/_spinner.scss +0 -572
  212. data/vendor/assets/stylesheets/_subheader.scss +0 -112
  213. data/vendor/assets/stylesheets/_swoggle.scss +0 -120
  214. data/vendor/assets/stylesheets/_table.scss +0 -210
  215. data/vendor/assets/stylesheets/_timepicker.scss +0 -77
  216. data/vendor/assets/stylesheets/_toolbar.scss +0 -130
  217. data/vendor/assets/stylesheets/_tooltip.scss +0 -105
  218. data/vendor/assets/stylesheets/_transition.scss +0 -11
  219. data/vendor/assets/stylesheets/_trunk.scss +0 -147
  220. data/vendor/assets/stylesheets/_typeahead.scss +0 -18
  221. data/vendor/assets/stylesheets/_typography.scss +0 -233
@@ -1,228 +0,0 @@
1
- +function ($) {
2
- 'use strict';
3
-
4
- // CAROUSEL CLASS DEFINITION
5
- // =========================
6
-
7
- var Carousel = function (element, options) {
8
- this.$element = $(element)
9
- this.$indicators = this.$element.find('.carousel-indicators')
10
- this.options = options
11
- this.paused = null
12
- this.sliding = null
13
- this.interval = null
14
- this.$active = null
15
- this.$items = null
16
-
17
- this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
18
-
19
- this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
20
- .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
21
- .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
22
- }
23
-
24
- Carousel.VERSION = '3.3.6'
25
-
26
- Carousel.TRANSITION_DURATION = 600
27
-
28
- Carousel.DEFAULTS = {
29
- interval: 5000,
30
- pause: 'hover',
31
- wrap: true,
32
- keyboard: true
33
- }
34
-
35
- Carousel.prototype.keydown = function (e) {
36
- if (/input|textarea/i.test(e.target.tagName)) return
37
- switch (e.which) {
38
- case 37: this.prev(); break
39
- case 39: this.next(); break
40
- default: return
41
- }
42
-
43
- e.preventDefault()
44
- }
45
-
46
- Carousel.prototype.cycle = function (e) {
47
- e || (this.paused = false)
48
-
49
- this.interval && clearInterval(this.interval)
50
-
51
- this.options.interval
52
- && !this.paused
53
- && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
54
-
55
- return this
56
- }
57
-
58
- Carousel.prototype.getItemIndex = function (item) {
59
- this.$items = item.parent().children('.item')
60
- return this.$items.index(item || this.$active)
61
- }
62
-
63
- Carousel.prototype.getItemForDirection = function (direction, active) {
64
- var activeIndex = this.getItemIndex(active)
65
- var willWrap = (direction == 'prev' && activeIndex === 0)
66
- || (direction == 'next' && activeIndex == (this.$items.length - 1))
67
- if (willWrap && !this.options.wrap) return active
68
- var delta = direction == 'prev' ? -1 : 1
69
- var itemIndex = (activeIndex + delta) % this.$items.length
70
- return this.$items.eq(itemIndex)
71
- }
72
-
73
- Carousel.prototype.to = function (pos) {
74
- var that = this
75
- var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
76
-
77
- if (pos > (this.$items.length - 1) || pos < 0) return
78
-
79
- if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
80
- if (activeIndex == pos) return this.pause().cycle()
81
-
82
- return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
83
- }
84
-
85
- Carousel.prototype.pause = function (e) {
86
- e || (this.paused = true)
87
-
88
- if (this.$element.find('.next, .prev').length && $.support.transition) {
89
- this.$element.trigger($.support.transition.end)
90
- this.cycle(true)
91
- }
92
-
93
- this.interval = clearInterval(this.interval)
94
-
95
- return this
96
- }
97
-
98
- Carousel.prototype.next = function () {
99
- if (this.sliding) return
100
- return this.slide('next')
101
- }
102
-
103
- Carousel.prototype.prev = function () {
104
- if (this.sliding) return
105
- return this.slide('prev')
106
- }
107
-
108
- Carousel.prototype.slide = function (type, next) {
109
- var $active = this.$element.find('.item.active')
110
- var $next = next || this.getItemForDirection(type, $active)
111
- var isCycling = this.interval
112
- var direction = type == 'next' ? 'left' : 'right'
113
- var that = this
114
-
115
- if ($next.hasClass('active')) return (this.sliding = false)
116
-
117
- var relatedTarget = $next[0]
118
- var slideEvent = $.Event('slide.bs.carousel', {
119
- relatedTarget: relatedTarget,
120
- direction: direction
121
- })
122
- this.$element.trigger(slideEvent)
123
- if (slideEvent.isDefaultPrevented()) return
124
-
125
- this.sliding = true
126
-
127
- isCycling && this.pause()
128
-
129
- if (this.$indicators.length) {
130
- this.$indicators.find('.active').removeClass('active')
131
- var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
132
- $nextIndicator && $nextIndicator.addClass('active')
133
- }
134
-
135
- var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
136
- if ($.support.transition && this.$element.hasClass('slide')) {
137
- $next.addClass(type)
138
- $next[0].offsetWidth // force reflow
139
- $active.addClass(direction)
140
- $next.addClass(direction)
141
- $active
142
- .one('bsTransitionEnd', function () {
143
- $next.removeClass([type, direction].join(' ')).addClass('active')
144
- $active.removeClass(['active', direction].join(' '))
145
- that.sliding = false
146
- setTimeout(function () {
147
- that.$element.trigger(slidEvent)
148
- }, 0)
149
- })
150
- .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
151
- } else {
152
- $active.removeClass('active')
153
- $next.addClass('active')
154
- this.sliding = false
155
- this.$element.trigger(slidEvent)
156
- }
157
-
158
- isCycling && this.cycle()
159
-
160
- return this
161
- }
162
-
163
-
164
- // CAROUSEL PLUGIN DEFINITION
165
- // ==========================
166
-
167
- function Plugin(option) {
168
- return this.each(function () {
169
- var $this = $(this)
170
- var data = $this.data('bs.carousel')
171
- var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
172
- var action = typeof option == 'string' ? option : options.slide
173
-
174
- if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
175
- if (typeof option == 'number') data.to(option)
176
- else if (action) data[action]()
177
- else if (options.interval) data.pause().cycle()
178
- })
179
- }
180
-
181
- var old = $.fn.carousel
182
-
183
- $.fn.carousel = Plugin
184
- $.fn.carousel.Constructor = Carousel
185
-
186
-
187
- // CAROUSEL NO CONFLICT
188
- // ====================
189
-
190
- $.fn.carousel.noConflict = function () {
191
- $.fn.carousel = old
192
- return this
193
- }
194
-
195
-
196
- // CAROUSEL DATA-API
197
- // =================
198
-
199
- var clickHandler = function (e) {
200
- var href
201
- var $this = $(this)
202
- var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
203
- if (!$target.hasClass('carousel')) return
204
- var options = $.extend({}, $target.data(), $this.data())
205
- var slideIndex = $this.attr('data-slide-to')
206
- if (slideIndex) options.interval = false
207
-
208
- Plugin.call($target, options)
209
-
210
- if (slideIndex) {
211
- $target.data('bs.carousel').to(slideIndex)
212
- }
213
-
214
- e.preventDefault()
215
- }
216
-
217
- $(document)
218
- .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
219
- .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
220
-
221
- $(window).on('load', function () {
222
- $('[data-ride="carousel"]').each(function () {
223
- var $carousel = $(this)
224
- Plugin.call($carousel, $carousel.data())
225
- })
226
- })
227
-
228
- }(jQuery);
@@ -1,3742 +0,0 @@
1
- (function(){
2
-
3
- "use strict";
4
-
5
- //Declare root variable - window in the browser, global on the server
6
- var root = this,
7
- previous = root.Chart;
8
-
9
- //Occupy the global variable of Chart, and create a simple base class
10
- var Chart = function(context){
11
- var chart = this;
12
- this.canvas = context.canvas;
13
-
14
- this.ctx = context;
15
-
16
- //Variables global to the chart
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;
34
- this.aspectRatio = this.width / this.height;
35
- //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
36
- helpers.retinaScale(this);
37
-
38
- return this;
39
- };
40
- //Globally expose the defaults to allow for user updating/changing
41
- Chart.defaults = {
42
- global: {
43
- // Boolean - Whether to animate the chart
44
- animation: true,
45
-
46
- // Number - Number of animation steps
47
- animationSteps: 60,
48
-
49
- // String - Animation easing effect
50
- animationEasing: "easeOutQuart",
51
-
52
- // Boolean - If we should show the scale at all
53
- showScale: true,
54
-
55
- // Boolean - If we want to override with a hard coded scale
56
- scaleOverride: false,
57
-
58
- // ** Required if scaleOverride is true **
59
- // Number - The number of steps in a hard coded scale
60
- scaleSteps: null,
61
- // Number - The value jump in the hard coded scale
62
- scaleStepWidth: null,
63
- // Number - The scale starting value
64
- scaleStartValue: null,
65
-
66
- // String - Colour of the scale line
67
- scaleLineColor: "rgba(235,237,239,1)",
68
-
69
- // Number - Pixel width of the scale line
70
- scaleLineWidth: 1,
71
-
72
- // Boolean - Whether to show labels on the scale
73
- scaleShowLabels: true,
74
-
75
- // Interpolated JS string - can access value
76
- scaleLabel: "<%=value%>",
77
-
78
- // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
79
- scaleIntegersOnly: true,
80
-
81
- // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
82
- scaleBeginAtZero: false,
83
-
84
- // String - Scale label font declaration for the scale label
85
- scaleFontFamily: "'Gotham Round', 'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
86
-
87
- // Number - Scale label font size in pixels
88
- scaleFontSize: 13,
89
-
90
- // String - Scale label font weight style
91
- scaleFontStyle: "bold",
92
-
93
- // String - Scale label font colour
94
- scaleFontColor: "rgba(27,30,34,1)",
95
-
96
- // Boolean - whether or not the chart should be responsive and resize when the browser does.
97
- responsive: true,
98
-
99
- // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
100
- maintainAspectRatio: true,
101
-
102
- // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
103
- showTooltips: true,
104
-
105
- // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
106
- customTooltips: false,
107
-
108
- // Array - Array of string names to attach tooltip events
109
- tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
110
-
111
- // String - Tooltip background colour
112
- tooltipFillColor: "rgba(27,30,34,1)",
113
-
114
- // String - Tooltip label font declaration for the scale label
115
- tooltipFontFamily: "'Gotham Round', 'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
116
-
117
- // Number - Tooltip label font size in pixels
118
- tooltipFontSize: 12,
119
-
120
- // String - Tooltip font weight style
121
- tooltipFontStyle: "bold",
122
-
123
- // String - Tooltip label font colour
124
- tooltipFontColor: "rgba(255,255,255,1)",
125
-
126
- // String - Tooltip title font declaration for the scale label
127
- tooltipTitleFontFamily: "'Gotham Round', 'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
128
-
129
- // Number - Tooltip title font size in pixels
130
- tooltipTitleFontSize: 12,
131
-
132
- // String - Tooltip title font weight style
133
- tooltipTitleFontStyle: "bold",
134
-
135
- // String - Tooltip title font colour
136
- tooltipTitleFontColor: "rgba(255,255,255,1)",
137
-
138
- // String - Tooltip title template
139
- tooltipTitleTemplate: "<%= label %>",
140
-
141
- // Number - pixel width of padding around tooltip text
142
- tooltipYPadding: 10,
143
-
144
- // Number - pixel width of padding around tooltip text
145
- tooltipXPadding: 10,
146
-
147
- // Number - Size of the caret on the tooltip
148
- tooltipCaretSize: 5,
149
-
150
- // Number - Pixel radius of the tooltip border
151
- tooltipCornerRadius: 3,
152
-
153
- // Number - Pixel offset from point x to tooltip edge
154
- tooltipXOffset: 10,
155
-
156
- // String - Template string for single tooltips
157
- tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
158
-
159
- // String - Template string for single tooltips
160
- multiTooltipTemplate: "<%= value %>",
161
-
162
- // String - Colour behind the legend colour block
163
- multiTooltipKeyBackground: 'rgba(255,255,255,1)',
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(30,196,214,1)",
170
- "rgba(0,102,255,1)",
171
- "rgba(86,21,237,1)",
172
- "rgba(124,39,243,1)",
173
- "rgba(255,0,102,1)",
174
- "rgba(240,35,17,1)",
175
- "rgba(248,122,9,1)",
176
- "rgba(245,200,0,1)",
177
- "rgba(45,50,57,1)",
178
- "rgba(126,137,150,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(30,196,214,0.1)",
186
- "rgba(0,102,255,0.1)",
187
- "rgba(86,21,237,0.1)",
188
- "rgba(124,39,243,0.1)",
189
- "rgba(255,0,102,0.1)",
190
- "rgba(240,35,17,0.1)",
191
- "rgba(248,122,9,0.1)",
192
- "rgba(245,200,0,0.1)",
193
- "rgba(45,50,57,0.1)",
194
- "rgba(126,137,150,0.1)"
195
- ],
196
-
197
- // Function - Will fire on animation progression.
198
- onAnimationProgress: function(){},
199
-
200
- // Function - Will fire on animation completion.
201
- onAnimationComplete: function(){}
202
-
203
- }
204
- };
205
-
206
- //Create a dictionary of chart types, to allow for extension of existing types
207
- Chart.types = {};
208
-
209
- //Global Chart helpers object for utility methods and classes
210
- var helpers = Chart.helpers = {};
211
-
212
- //-- Basic js utility methods
213
- var each = helpers.each = function(loopable,callback,self){
214
- var additionalArgs = Array.prototype.slice.call(arguments, 3);
215
- // Check to see if null or undefined firstly.
216
- if (loopable){
217
- if (loopable.length === +loopable.length){
218
- var i;
219
- for (i=0; i<loopable.length; i++){
220
- callback.apply(self,[loopable[i], i].concat(additionalArgs));
221
- }
222
- }
223
- else{
224
- for (var item in loopable){
225
- callback.apply(self,[loopable[item],item].concat(additionalArgs));
226
- }
227
- }
228
- }
229
- },
230
- clone = helpers.clone = function(obj){
231
- var objClone = {};
232
- each(obj,function(value,key){
233
- if (obj.hasOwnProperty(key)){
234
- objClone[key] = value;
235
- }
236
- });
237
- return objClone;
238
- },
239
- extend = helpers.extend = function(base){
240
- each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
241
- each(extensionObject,function(value,key){
242
- if (extensionObject.hasOwnProperty(key)){
243
- base[key] = value;
244
- }
245
- });
246
- });
247
- return base;
248
- },
249
- merge = helpers.merge = function(base,master){
250
- //Merge properties in left object over to a shallow clone of object right.
251
- var args = Array.prototype.slice.call(arguments,0);
252
- args.unshift({});
253
- return extend.apply(null, args);
254
- },
255
- indexOf = helpers.indexOf = function(arrayToSearch, item){
256
- if (Array.prototype.indexOf) {
257
- return arrayToSearch.indexOf(item);
258
- }
259
- else{
260
- for (var i = 0; i < arrayToSearch.length; i++) {
261
- if (arrayToSearch[i] === item) return i;
262
- }
263
- return -1;
264
- }
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
- },
301
- inherits = helpers.inherits = function(extensions){
302
- //Basic javascript inheritance based on the model created in Backbone.js
303
- var parent = this;
304
- var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
305
-
306
- var Surrogate = function(){ this.constructor = ChartElement;};
307
- Surrogate.prototype = parent.prototype;
308
- ChartElement.prototype = new Surrogate();
309
-
310
- ChartElement.extend = inherits;
311
-
312
- if (extensions) extend(ChartElement.prototype, extensions);
313
-
314
- ChartElement.__super__ = parent.prototype;
315
-
316
- return ChartElement;
317
- },
318
- noop = helpers.noop = function(){},
319
- uid = helpers.uid = (function(){
320
- var id=0;
321
- return function(){
322
- return "chart-" + id++;
323
- };
324
- })(),
325
- warn = helpers.warn = function(str){
326
- //Method for warning of errors
327
- if (window.console && typeof window.console.warn === "function") console.warn(str);
328
- },
329
- amd = helpers.amd = (typeof define === 'function' && define.amd),
330
- //-- Math methods
331
- isNumber = helpers.isNumber = function(n){
332
- return !isNaN(parseFloat(n)) && isFinite(n);
333
- },
334
- max = helpers.max = function(array){
335
- return Math.max.apply( Math, array );
336
- },
337
- min = helpers.min = function(array){
338
- return Math.min.apply( Math, array );
339
- },
340
- cap = helpers.cap = function(valueToCap,maxValue,minValue){
341
- if(isNumber(maxValue)) {
342
- if( valueToCap > maxValue ) {
343
- return maxValue;
344
- }
345
- }
346
- else if(isNumber(minValue)){
347
- if ( valueToCap < minValue ){
348
- return minValue;
349
- }
350
- }
351
- return valueToCap;
352
- },
353
- getDecimalPlaces = helpers.getDecimalPlaces = function(num){
354
- if (num%1!==0 && isNumber(num)){
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
- }
369
- }
370
- else {
371
- return 0;
372
- }
373
- },
374
- toRadians = helpers.radians = function(degrees){
375
- return degrees * (Math.PI/180);
376
- },
377
- // Gets the angle from vertical upright to the point about a centre.
378
- getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
379
- var distanceFromXCenter = anglePoint.x - centrePoint.x,
380
- distanceFromYCenter = anglePoint.y - centrePoint.y,
381
- radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
382
-
383
-
384
- var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
385
-
386
- //If the segment is in the top left quadrant, we need to add another rotation to the angle
387
- if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
388
- angle += Math.PI*2;
389
- }
390
-
391
- return {
392
- angle: angle,
393
- distance: radialDistanceFromCenter
394
- };
395
- },
396
- aliasPixel = helpers.aliasPixel = function(pixelWidth){
397
- return (pixelWidth % 2 === 0) ? 0 : 0.5;
398
- },
399
- splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
400
- //Props to Rob Spencer at scaled innovation for his post on splining between points
401
- //http://scaledinnovation.com/analytics/splines/aboutSplines.html
402
- var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
403
- d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
404
- fa=t*d01/(d01+d12),// scaling factor for triangle Ta
405
- fb=t*d12/(d01+d12);
406
- return {
407
- inner : {
408
- x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
409
- y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
410
- },
411
- outer : {
412
- x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
413
- y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
414
- }
415
- };
416
- },
417
- calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
418
- return Math.floor(Math.log(val) / Math.LN10);
419
- },
420
- calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
421
-
422
- //Set a minimum step of two - a point at the top of the graph, and a point at the base
423
- var minSteps = 2,
424
- maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
425
- skipFitting = (minSteps >= maxSteps);
426
-
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);
434
-
435
- // We need some degree of separation here to calculate the scales if all the values are the same
436
- // Adding/minusing 0.5 will give us a range of 1.
437
- if (maxValue === minValue){
438
- maxValue += 0.5;
439
- // So we don't end up with a graph with a negative start value if we've said always start from zero
440
- if (minValue >= 0.5 && !startFromZero){
441
- minValue -= 0.5;
442
- }
443
- else{
444
- // Make up a whole number above the values
445
- maxValue += 0.5;
446
- }
447
- }
448
-
449
- var valueRange = Math.abs(maxValue - minValue),
450
- rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
451
- graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
452
- graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
453
- graphRange = graphMax - graphMin,
454
- stepValue = Math.pow(10, rangeOrderOfMagnitude),
455
- numberOfSteps = Math.round(graphRange / stepValue);
456
-
457
- //If we have more space on the graph we'll use it to give more definition to the data
458
- while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
459
- if(numberOfSteps > maxSteps){
460
- stepValue *=2;
461
- numberOfSteps = Math.round(graphRange/stepValue);
462
- // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
463
- if (numberOfSteps % 1 !== 0){
464
- skipFitting = true;
465
- }
466
- }
467
- //We can fit in double the amount of scale points on the scale
468
- else{
469
- //If user has declared ints only, and the step value isn't a decimal
470
- if (integersOnly && rangeOrderOfMagnitude >= 0){
471
- //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
472
- if(stepValue/2 % 1 === 0){
473
- stepValue /=2;
474
- numberOfSteps = Math.round(graphRange/stepValue);
475
- }
476
- //If it would make it a float break out of the loop
477
- else{
478
- break;
479
- }
480
- }
481
- //If the scale doesn't have to be an int, make the scale more granular anyway.
482
- else{
483
- stepValue /=2;
484
- numberOfSteps = Math.round(graphRange/stepValue);
485
- }
486
-
487
- }
488
- }
489
-
490
- if (skipFitting){
491
- numberOfSteps = minSteps;
492
- stepValue = graphRange / numberOfSteps;
493
- }
494
-
495
- return {
496
- steps : numberOfSteps,
497
- stepValue : stepValue,
498
- min : graphMin,
499
- max : graphMin + (numberOfSteps * stepValue)
500
- };
501
-
502
- },
503
- /* jshint ignore:start */
504
- // Blows up jshint errors based on the new Function constructor
505
- //Templating methods
506
- //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
507
- template = helpers.template = function(templateString, valuesObject){
508
-
509
- // If templateString is function rather than string-template - call the function for valuesObject
510
-
511
- if(templateString instanceof Function){
512
- return templateString(valuesObject);
513
- }
514
-
515
- var cache = {};
516
- function tmpl(str, data){
517
- // Figure out if we're getting a template, or if we need to
518
- // load the template - and be sure to cache the result.
519
- var fn = !/\W/.test(str) ?
520
- cache[str] = cache[str] :
521
-
522
- // Generate a reusable function that will serve as a template
523
- // generator (and which will be cached).
524
- new Function("obj",
525
- "var p=[],print=function(){p.push.apply(p,arguments);};" +
526
-
527
- // Introduce the data as local variables using with(){}
528
- "with(obj){p.push('" +
529
-
530
- // Convert the template into pure JavaScript
531
- str
532
- .replace(/[\r\t\n]/g, " ")
533
- .split("<%").join("\t")
534
- .replace(/((^|%>)[^\t]*)'/g, "$1\r")
535
- .replace(/\t=(.*?)%>/g, "',$1,'")
536
- .split("\t").join("');")
537
- .split("%>").join("p.push('")
538
- .split("\r").join("\\'") +
539
- "');}return p.join('');"
540
- );
541
-
542
- // Provide some basic currying to the user
543
- return data ? fn( data ) : fn;
544
- }
545
- return tmpl(templateString,valuesObject);
546
- },
547
- /* jshint ignore:end */
548
- generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
549
- var labelsArray = new Array(numberOfSteps);
550
- if (templateString){
551
- each(labelsArray,function(val,index){
552
- labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
553
- });
554
- }
555
- return labelsArray;
556
- },
557
- //--Animation methods
558
- //Easing functions adapted from Robert Penner's easing equations
559
- //http://www.robertpenner.com/easing/
560
- easingEffects = helpers.easingEffects = {
561
- linear: function (t) {
562
- return t;
563
- },
564
- easeInQuad: function (t) {
565
- return t * t;
566
- },
567
- easeOutQuad: function (t) {
568
- return -1 * t * (t - 2);
569
- },
570
- easeInOutQuad: function (t) {
571
- if ((t /= 1 / 2) < 1){
572
- return 1 / 2 * t * t;
573
- }
574
- return -1 / 2 * ((--t) * (t - 2) - 1);
575
- },
576
- easeInCubic: function (t) {
577
- return t * t * t;
578
- },
579
- easeOutCubic: function (t) {
580
- return 1 * ((t = t / 1 - 1) * t * t + 1);
581
- },
582
- easeInOutCubic: function (t) {
583
- if ((t /= 1 / 2) < 1){
584
- return 1 / 2 * t * t * t;
585
- }
586
- return 1 / 2 * ((t -= 2) * t * t + 2);
587
- },
588
- easeInQuart: function (t) {
589
- return t * t * t * t;
590
- },
591
- easeOutQuart: function (t) {
592
- return -1 * ((t = t / 1 - 1) * t * t * t - 1);
593
- },
594
- easeInOutQuart: function (t) {
595
- if ((t /= 1 / 2) < 1){
596
- return 1 / 2 * t * t * t * t;
597
- }
598
- return -1 / 2 * ((t -= 2) * t * t * t - 2);
599
- },
600
- easeInQuint: function (t) {
601
- return 1 * (t /= 1) * t * t * t * t;
602
- },
603
- easeOutQuint: function (t) {
604
- return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
605
- },
606
- easeInOutQuint: function (t) {
607
- if ((t /= 1 / 2) < 1){
608
- return 1 / 2 * t * t * t * t * t;
609
- }
610
- return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
611
- },
612
- easeInSine: function (t) {
613
- return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
614
- },
615
- easeOutSine: function (t) {
616
- return 1 * Math.sin(t / 1 * (Math.PI / 2));
617
- },
618
- easeInOutSine: function (t) {
619
- return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
620
- },
621
- easeInExpo: function (t) {
622
- return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
623
- },
624
- easeOutExpo: function (t) {
625
- return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
626
- },
627
- easeInOutExpo: function (t) {
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
- }
637
- return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
638
- },
639
- easeInCirc: function (t) {
640
- if (t >= 1){
641
- return t;
642
- }
643
- return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
644
- },
645
- easeOutCirc: function (t) {
646
- return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
647
- },
648
- easeInOutCirc: function (t) {
649
- if ((t /= 1 / 2) < 1){
650
- return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
651
- }
652
- return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
653
- },
654
- easeInElastic: function (t) {
655
- var s = 1.70158;
656
- var p = 0;
657
- var a = 1;
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
- }
667
- if (a < Math.abs(1)) {
668
- a = 1;
669
- s = p / 4;
670
- } else{
671
- s = p / (2 * Math.PI) * Math.asin(1 / a);
672
- }
673
- return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
674
- },
675
- easeOutElastic: function (t) {
676
- var s = 1.70158;
677
- var p = 0;
678
- var a = 1;
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
- }
688
- if (a < Math.abs(1)) {
689
- a = 1;
690
- s = p / 4;
691
- } else{
692
- s = p / (2 * Math.PI) * Math.asin(1 / a);
693
- }
694
- return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
695
- },
696
- easeInOutElastic: function (t) {
697
- var s = 1.70158;
698
- var p = 0;
699
- var a = 1;
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
- }
709
- if (a < Math.abs(1)) {
710
- a = 1;
711
- s = p / 4;
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));}
717
- return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
718
- },
719
- easeInBack: function (t) {
720
- var s = 1.70158;
721
- return 1 * (t /= 1) * t * ((s + 1) * t - s);
722
- },
723
- easeOutBack: function (t) {
724
- var s = 1.70158;
725
- return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
726
- },
727
- easeInOutBack: function (t) {
728
- var s = 1.70158;
729
- if ((t /= 1 / 2) < 1){
730
- return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
731
- }
732
- return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
733
- },
734
- easeInBounce: function (t) {
735
- return 1 - easingEffects.easeOutBounce(1 - t);
736
- },
737
- easeOutBounce: function (t) {
738
- if ((t /= 1) < (1 / 2.75)) {
739
- return 1 * (7.5625 * t * t);
740
- } else if (t < (2 / 2.75)) {
741
- return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
742
- } else if (t < (2.5 / 2.75)) {
743
- return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
744
- } else {
745
- return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
746
- }
747
- },
748
- easeInOutBounce: function (t) {
749
- if (t < 1 / 2){
750
- return easingEffects.easeInBounce(t * 2) * 0.5;
751
- }
752
- return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
753
- }
754
- },
755
- //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
756
- requestAnimFrame = helpers.requestAnimFrame = (function(){
757
- return window.requestAnimationFrame ||
758
- window.webkitRequestAnimationFrame ||
759
- window.mozRequestAnimationFrame ||
760
- window.oRequestAnimationFrame ||
761
- window.msRequestAnimationFrame ||
762
- function(callback) {
763
- return window.setTimeout(callback, 1000 / 60);
764
- };
765
- })(),
766
- cancelAnimFrame = helpers.cancelAnimFrame = (function(){
767
- return window.cancelAnimationFrame ||
768
- window.webkitCancelAnimationFrame ||
769
- window.mozCancelAnimationFrame ||
770
- window.oCancelAnimationFrame ||
771
- window.msCancelAnimationFrame ||
772
- function(callback) {
773
- return window.clearTimeout(callback, 1000 / 60);
774
- };
775
- })(),
776
- animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
777
-
778
- var currentStep = 0,
779
- easingFunction = easingEffects[easingString] || easingEffects.linear;
780
-
781
- var animationFrame = function(){
782
- currentStep++;
783
- var stepDecimal = currentStep/totalSteps;
784
- var easeDecimal = easingFunction(stepDecimal);
785
-
786
- callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
787
- onProgress.call(chartInstance,easeDecimal,stepDecimal);
788
- if (currentStep < totalSteps){
789
- chartInstance.animationFrame = requestAnimFrame(animationFrame);
790
- } else{
791
- onComplete.apply(chartInstance);
792
- }
793
- };
794
- requestAnimFrame(animationFrame);
795
- },
796
- //-- DOM methods
797
- getRelativePosition = helpers.getRelativePosition = function(evt){
798
- var mouseX, mouseY;
799
- var e = evt.originalEvent || evt,
800
- canvas = evt.currentTarget || evt.srcElement,
801
- boundingRect = canvas.getBoundingClientRect();
802
-
803
- if (e.touches){
804
- mouseX = e.touches[0].clientX - boundingRect.left;
805
- mouseY = e.touches[0].clientY - boundingRect.top;
806
-
807
- }
808
- else{
809
- mouseX = e.clientX - boundingRect.left;
810
- mouseY = e.clientY - boundingRect.top;
811
- }
812
-
813
- return {
814
- x : mouseX,
815
- y : mouseY
816
- };
817
-
818
- },
819
- addEvent = helpers.addEvent = function(node,eventType,method){
820
- if (node.addEventListener){
821
- node.addEventListener(eventType,method);
822
- } else if (node.attachEvent){
823
- node.attachEvent("on"+eventType, method);
824
- } else {
825
- node["on"+eventType] = method;
826
- }
827
- },
828
- removeEvent = helpers.removeEvent = function(node, eventType, handler){
829
- if (node.removeEventListener){
830
- node.removeEventListener(eventType, handler, false);
831
- } else if (node.detachEvent){
832
- node.detachEvent("on"+eventType,handler);
833
- } else{
834
- node["on" + eventType] = noop;
835
- }
836
- },
837
- bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
838
- // Create the events object if it's not already present
839
- if (!chartInstance.events) chartInstance.events = {};
840
-
841
- each(arrayOfEvents,function(eventName){
842
- chartInstance.events[eventName] = function(){
843
- handler.apply(chartInstance, arguments);
844
- };
845
- addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
846
- });
847
- },
848
- unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
849
- each(arrayOfEvents, function(handler,eventName){
850
- removeEvent(chartInstance.chart.canvas, eventName, handler);
851
- });
852
- },
853
- getMaximumWidth = helpers.getMaximumWidth = function(domNode){
854
- var container = domNode.parentNode,
855
- padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right'));
856
- // TODO = check cross browser stuff with this.
857
- return container.clientWidth - padding;
858
- },
859
- getMaximumHeight = helpers.getMaximumHeight = function(domNode){
860
- var container = domNode.parentNode,
861
- padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top'));
862
- // TODO = check cross browser stuff with this.
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);
869
- },
870
- getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
871
- retinaScale = helpers.retinaScale = function(chart){
872
- var ctx = chart.ctx,
873
- width = chart.canvas.width,
874
- height = chart.canvas.height;
875
-
876
- if (window.devicePixelRatio) {
877
- ctx.canvas.style.width = width + "px";
878
- ctx.canvas.style.height = height + "px";
879
- ctx.canvas.height = height * window.devicePixelRatio;
880
- ctx.canvas.width = width * window.devicePixelRatio;
881
- ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
882
- }
883
- },
884
- //-- Canvas methods
885
- clear = helpers.clear = function(chart){
886
- chart.ctx.clearRect(0,0,chart.width,chart.height);
887
- },
888
- fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
889
- return fontStyle + " " + pixelSize+"px " + fontFamily;
890
- },
891
- longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
892
- ctx.font = font;
893
- var longest = 0;
894
- each(arrayOfStrings,function(string){
895
- var textWidth = ctx.measureText(string).width;
896
- longest = (textWidth > longest) ? textWidth : longest;
897
- });
898
- return longest;
899
- },
900
- drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
901
- ctx.beginPath();
902
- ctx.moveTo(x + radius, y);
903
- ctx.lineTo(x + width - radius, y);
904
- ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
905
- ctx.lineTo(x + width, y + height - radius);
906
- ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
907
- ctx.lineTo(x + radius, y + height);
908
- ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
909
- ctx.lineTo(x, y + radius);
910
- ctx.quadraticCurveTo(x, y, x + radius, y);
911
- ctx.closePath();
912
- };
913
-
914
-
915
- //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
916
- //Destroy method on the chart will remove the instance of the chart from this reference.
917
- Chart.instances = {};
918
-
919
- Chart.Type = function(data,options,chart){
920
- this.options = options;
921
- this.chart = chart;
922
- this.id = uid();
923
- //Add the chart instance to the global namespace
924
- Chart.instances[this.id] = this;
925
-
926
- // Initialize is always called when a chart type is created
927
- // By default it is a no op, but it should be extended
928
- if (options.responsive){
929
- this.resize();
930
- }
931
- this.initialize.call(this,data);
932
- };
933
-
934
- //Core methods that'll be a part of every chart type
935
- extend(Chart.Type.prototype,{
936
- initialize : function(){return this;},
937
- clear : function(){
938
- clear(this.chart);
939
- return this;
940
- },
941
- stop : function(){
942
- // Stops any current animation loop occuring
943
- Chart.animationService.cancelAnimation(this);
944
- return this;
945
- },
946
- resize : function(callback){
947
- this.stop();
948
- var canvas = this.chart.canvas,
949
- newWidth = getMaximumWidth(this.chart.canvas),
950
- newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
951
-
952
- canvas.width = this.chart.width = newWidth;
953
- canvas.height = this.chart.height = newHeight;
954
-
955
- retinaScale(this.chart);
956
-
957
- if (typeof callback === "function"){
958
- callback.apply(this, Array.prototype.slice.call(arguments, 1));
959
- }
960
- return this;
961
- },
962
- reflow : noop,
963
- render : function(reflow){
964
- if (reflow){
965
- this.reflow();
966
- }
967
-
968
- if (this.options.animation && !reflow){
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);
987
- }
988
- else{
989
- this.draw();
990
- this.options.onAnimationComplete.call(this);
991
- }
992
- return this;
993
- },
994
- generateLegend : function(){
995
- return template(this.options.legendTemplate,this);
996
- },
997
- destroy : function(){
998
- this.clear();
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
-
1015
- delete Chart.instances[this.id];
1016
- },
1017
- showTooltip : function(ChartElements, forceRedraw){
1018
- // Only redraw the chart if we've actually changed what we're hovering on.
1019
- if (typeof this.activeElements === 'undefined') this.activeElements = [];
1020
-
1021
- var isChanged = (function(Elements){
1022
- var changed = false;
1023
-
1024
- if (Elements.length !== this.activeElements.length){
1025
- changed = true;
1026
- return changed;
1027
- }
1028
-
1029
- each(Elements, function(element, index){
1030
- if (element !== this.activeElements[index]){
1031
- changed = true;
1032
- }
1033
- }, this);
1034
- return changed;
1035
- }).call(this, ChartElements);
1036
-
1037
- if (!isChanged && !forceRedraw){
1038
- return;
1039
- }
1040
- else{
1041
- this.activeElements = ChartElements;
1042
- }
1043
- this.draw();
1044
- if(this.options.customTooltips){
1045
- this.options.customTooltips(false);
1046
- }
1047
- if (ChartElements.length > 0){
1048
- // If we have multiple datasets, show a MultiTooltip for all of the data points at that index
1049
- if (this.datasets && this.datasets.length > 1) {
1050
- var dataArray,
1051
- dataIndex;
1052
-
1053
- for (var i = this.datasets.length - 1; i >= 0; i--) {
1054
- dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
1055
- dataIndex = indexOf(dataArray, ChartElements[0]);
1056
- if (dataIndex !== -1){
1057
- break;
1058
- }
1059
- }
1060
- var tooltipLabels = [],
1061
- tooltipColors = [],
1062
- medianPosition = (function(index) {
1063
-
1064
- // Get all the points at that particular index
1065
- var Elements = [],
1066
- dataCollection,
1067
- xPositions = [],
1068
- yPositions = [],
1069
- xMax,
1070
- yMax,
1071
- xMin,
1072
- yMin;
1073
- helpers.each(this.datasets, function(dataset){
1074
- dataCollection = dataset.points || dataset.bars || dataset.segments;
1075
- if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
1076
- Elements.push(dataCollection[dataIndex]);
1077
- }
1078
- });
1079
-
1080
- helpers.each(Elements, function(element) {
1081
- xPositions.push(element.x);
1082
- yPositions.push(element.y);
1083
-
1084
-
1085
- //Include any colour information about the element
1086
- tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
1087
- tooltipColors.push({
1088
- fill: element._saved.fillColor || element.fillColor,
1089
- stroke: element._saved.strokeColor || element.strokeColor
1090
- });
1091
-
1092
- }, this);
1093
-
1094
- yMin = min(yPositions);
1095
- yMax = max(yPositions);
1096
-
1097
- xMin = min(xPositions);
1098
- xMax = max(xPositions);
1099
-
1100
- return {
1101
- x: (xMin > this.chart.width/2) ? xMin : xMax,
1102
- y: (yMin + yMax)/2
1103
- };
1104
- }).call(this, dataIndex);
1105
-
1106
- new Chart.MultiTooltip({
1107
- x: medianPosition.x,
1108
- y: medianPosition.y,
1109
- xPadding: this.options.tooltipXPadding,
1110
- yPadding: this.options.tooltipYPadding,
1111
- xOffset: this.options.tooltipXOffset,
1112
- fillColor: this.options.tooltipFillColor,
1113
- textColor: this.options.tooltipFontColor,
1114
- fontFamily: this.options.tooltipFontFamily,
1115
- fontStyle: this.options.tooltipFontStyle,
1116
- fontSize: this.options.tooltipFontSize,
1117
- titleTextColor: this.options.tooltipTitleFontColor,
1118
- titleFontFamily: this.options.tooltipTitleFontFamily,
1119
- titleFontStyle: this.options.tooltipTitleFontStyle,
1120
- titleFontSize: this.options.tooltipTitleFontSize,
1121
- cornerRadius: this.options.tooltipCornerRadius,
1122
- labels: tooltipLabels,
1123
- legendColors: tooltipColors,
1124
- legendColorBackground : this.options.multiTooltipKeyBackground,
1125
- title: template(this.options.tooltipTitleTemplate,ChartElements[0]),
1126
- chart: this.chart,
1127
- ctx: this.chart.ctx,
1128
- custom: this.options.customTooltips
1129
- }).draw();
1130
-
1131
- } else {
1132
- each(ChartElements, function(Element) {
1133
- var tooltipPosition = Element.tooltipPosition();
1134
- new Chart.Tooltip({
1135
- x: Math.round(tooltipPosition.x),
1136
- y: Math.round(tooltipPosition.y),
1137
- xPadding: this.options.tooltipXPadding,
1138
- yPadding: this.options.tooltipYPadding,
1139
- fillColor: this.options.tooltipFillColor,
1140
- textColor: this.options.tooltipFontColor,
1141
- fontFamily: this.options.tooltipFontFamily,
1142
- fontStyle: this.options.tooltipFontStyle,
1143
- fontSize: this.options.tooltipFontSize,
1144
- caretHeight: this.options.tooltipCaretSize,
1145
- cornerRadius: this.options.tooltipCornerRadius,
1146
- text: template(this.options.tooltipTemplate, Element),
1147
- chart: this.chart,
1148
- custom: this.options.customTooltips
1149
- }).draw();
1150
- }, this);
1151
- }
1152
- }
1153
- return this;
1154
- },
1155
- toBase64Image : function(){
1156
- return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
1157
- }
1158
- });
1159
-
1160
- Chart.Type.extend = function(extensions){
1161
-
1162
- var parent = this;
1163
-
1164
- var ChartType = function(){
1165
- return parent.apply(this,arguments);
1166
- };
1167
-
1168
- //Copy the prototype object of the this class
1169
- ChartType.prototype = clone(parent.prototype);
1170
- //Now overwrite some of the properties in the base class with the new extensions
1171
- extend(ChartType.prototype, extensions);
1172
-
1173
- ChartType.extend = Chart.Type.extend;
1174
-
1175
- if (extensions.name || parent.prototype.name){
1176
-
1177
- var chartName = extensions.name || parent.prototype.name;
1178
- //Assign any potential default values of the new chart type
1179
-
1180
- //If none are defined, we'll use a clone of the chart type this is being extended from.
1181
- //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
1182
- //doesn't define some defaults of their own.
1183
-
1184
- var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
1185
-
1186
- Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
1187
-
1188
- Chart.types[chartName] = ChartType;
1189
-
1190
- //Register this new chart type in the Chart prototype
1191
- Chart.prototype[chartName] = function(data,options){
1192
- var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
1193
- return new ChartType(data,config,this);
1194
- };
1195
- } else{
1196
- warn("Name not provided for this chart, so it hasn't been registered");
1197
- }
1198
- return parent;
1199
- };
1200
-
1201
- Chart.Element = function(configuration){
1202
- extend(this,configuration);
1203
- this.initialize.apply(this,arguments);
1204
- this.save();
1205
- };
1206
- extend(Chart.Element.prototype,{
1207
- initialize : function(){},
1208
- restore : function(props){
1209
- if (!props){
1210
- extend(this,this._saved);
1211
- } else {
1212
- each(props,function(key){
1213
- this[key] = this._saved[key];
1214
- },this);
1215
- }
1216
- return this;
1217
- },
1218
- save : function(){
1219
- this._saved = clone(this);
1220
- delete this._saved._saved;
1221
- return this;
1222
- },
1223
- update : function(newProps){
1224
- each(newProps,function(value,key){
1225
- this._saved[key] = this[key];
1226
- this[key] = value;
1227
- },this);
1228
- return this;
1229
- },
1230
- transition : function(props,ease){
1231
- each(props,function(value,key){
1232
- this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
1233
- },this);
1234
- return this;
1235
- },
1236
- tooltipPosition : function(){
1237
- return {
1238
- x : this.x,
1239
- y : this.y
1240
- };
1241
- },
1242
- hasValue: function(){
1243
- return isNumber(this.value);
1244
- }
1245
- });
1246
-
1247
- Chart.Element.extend = inherits;
1248
-
1249
-
1250
- Chart.Point = Chart.Element.extend({
1251
- display: true,
1252
- inRange: function(chartX,chartY){
1253
- var hitDetectionRange = this.hitDetectionRadius + this.radius;
1254
- return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
1255
- },
1256
- draw : function(){
1257
- if (this.display){
1258
- var ctx = this.ctx;
1259
- ctx.beginPath();
1260
-
1261
- ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
1262
- ctx.closePath();
1263
-
1264
- ctx.strokeStyle = this.strokeColor;
1265
- ctx.lineWidth = this.strokeWidth;
1266
-
1267
- ctx.fillStyle = this.fillColor;
1268
-
1269
- ctx.fill();
1270
- ctx.stroke();
1271
- }
1272
-
1273
-
1274
- //Quick debug for bezier curve splining
1275
- //Highlights control points and the line between them.
1276
- //Handy for dev - stripped in the min version.
1277
-
1278
- // ctx.save();
1279
- // ctx.fillStyle = "black";
1280
- // ctx.strokeStyle = "black"
1281
- // ctx.beginPath();
1282
- // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
1283
- // ctx.fill();
1284
-
1285
- // ctx.beginPath();
1286
- // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
1287
- // ctx.fill();
1288
-
1289
- // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
1290
- // ctx.lineTo(this.x, this.y);
1291
- // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
1292
- // ctx.stroke();
1293
-
1294
- // ctx.restore();
1295
-
1296
-
1297
-
1298
- }
1299
- });
1300
-
1301
- Chart.Arc = Chart.Element.extend({
1302
- inRange : function(chartX,chartY){
1303
-
1304
- var pointRelativePosition = helpers.getAngleFromPoint(this, {
1305
- x: chartX,
1306
- y: chartY
1307
- });
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
-
1319
- //Check if within the range of the open/close angle
1320
- var withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
1321
-
1322
- return (betweenAngles && withinRadius);
1323
- //Ensure within the outside of the arc centre, but inside arc outer
1324
- },
1325
- tooltipPosition : function(){
1326
- var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
1327
- rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
1328
- return {
1329
- x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
1330
- y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
1331
- };
1332
- },
1333
- draw : function(animationPercent){
1334
-
1335
- var easingDecimal = animationPercent || 1;
1336
-
1337
- var ctx = this.ctx;
1338
-
1339
- ctx.beginPath();
1340
-
1341
- ctx.arc(this.x, this.y, this.outerRadius < 0 ? 0 : this.outerRadius, this.startAngle, this.endAngle);
1342
-
1343
- ctx.arc(this.x, this.y, this.innerRadius < 0 ? 0 : this.innerRadius, this.endAngle, this.startAngle, true);
1344
-
1345
- ctx.closePath();
1346
- ctx.strokeStyle = this.strokeColor;
1347
- ctx.lineWidth = this.strokeWidth;
1348
-
1349
- ctx.fillStyle = this.fillColor;
1350
-
1351
- ctx.fill();
1352
- ctx.lineJoin = 'bevel';
1353
-
1354
- if (this.showStroke){
1355
- ctx.stroke();
1356
- }
1357
- }
1358
- });
1359
-
1360
- Chart.Rectangle = Chart.Element.extend({
1361
- draw : function(){
1362
- var ctx = this.ctx,
1363
- halfWidth = this.width/2,
1364
- leftX = this.x - halfWidth,
1365
- rightX = this.x + halfWidth,
1366
- top = this.base - (this.base - this.y),
1367
- halfStroke = this.strokeWidth / 2;
1368
-
1369
- // Canvas doesn't allow us to stroke inside the width so we can
1370
- // adjust the sizes to fit if we're setting a stroke on the line
1371
- if (this.showStroke){
1372
- leftX += halfStroke;
1373
- rightX -= halfStroke;
1374
- top += halfStroke;
1375
- }
1376
-
1377
- ctx.beginPath();
1378
-
1379
- ctx.fillStyle = this.fillColor;
1380
- ctx.strokeStyle = this.strokeColor;
1381
- ctx.lineWidth = this.strokeWidth;
1382
-
1383
- // It'd be nice to keep this class totally generic to any rectangle
1384
- // and simply specify which border to miss out.
1385
- ctx.moveTo(leftX, this.base);
1386
- ctx.lineTo(leftX, top);
1387
- ctx.lineTo(rightX, top);
1388
- ctx.lineTo(rightX, this.base);
1389
- ctx.fill();
1390
- if (this.showStroke){
1391
- ctx.stroke();
1392
- }
1393
- },
1394
- height : function(){
1395
- return this.base - this.y;
1396
- },
1397
- inRange : function(chartX,chartY){
1398
- return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
1399
- }
1400
- });
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
-
1412
- Chart.Tooltip = Chart.Element.extend({
1413
- draw : function(){
1414
-
1415
- var ctx = this.chart.ctx;
1416
-
1417
- ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1418
-
1419
- this.xAlign = "center";
1420
- this.yAlign = "above";
1421
-
1422
- //Distance between the actual element.y position and the start of the tooltip caret
1423
- var caretPadding = this.caretPadding = 2;
1424
-
1425
- var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
1426
- tooltipRectHeight = this.fontSize + 2*this.yPadding,
1427
- tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
1428
-
1429
- if (this.x + tooltipWidth/2 >this.chart.width){
1430
- this.xAlign = "left";
1431
- } else if (this.x - tooltipWidth/2 < 0){
1432
- this.xAlign = "right";
1433
- }
1434
-
1435
- if (this.y - tooltipHeight < 0){
1436
- this.yAlign = "below";
1437
- }
1438
-
1439
-
1440
- var tooltipX = this.x - tooltipWidth/2,
1441
- tooltipY = this.y - tooltipHeight;
1442
-
1443
- ctx.fillStyle = this.fillColor;
1444
-
1445
- // Custom Tooltips
1446
- if(this.custom){
1447
- this.custom(this);
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
- }
1472
-
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
- }
1482
-
1483
- drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
1484
-
1485
- ctx.fill();
1486
-
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
- }
1492
- }
1493
- });
1494
-
1495
- Chart.MultiTooltip = Chart.Element.extend({
1496
- initialize : function(){
1497
- this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1498
-
1499
- this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
1500
-
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;
1503
-
1504
- this.ctx.font = this.titleFont;
1505
-
1506
- var titleWidth = this.ctx.measureText(this.title).width,
1507
- //Label has a legend square as well so account for this.
1508
- labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
1509
- longestTextWidth = max([labelWidth,titleWidth]);
1510
-
1511
- this.width = longestTextWidth + (this.xPadding*2);
1512
-
1513
-
1514
- var halfHeight = this.height/2;
1515
-
1516
- //Check to ensure the height will fit on the canvas
1517
- if (this.y - halfHeight < 0 ){
1518
- this.y = halfHeight;
1519
- } else if (this.y + halfHeight > this.chart.height){
1520
- this.y = this.chart.height - halfHeight;
1521
- }
1522
-
1523
- //Decide whether to align left or right based on position on canvas
1524
- if (this.x > this.chart.width/2){
1525
- this.x -= this.xOffset + this.width;
1526
- } else {
1527
- this.x += this.xOffset;
1528
- }
1529
-
1530
-
1531
- },
1532
- getLineHeight : function(index){
1533
- var baseLineHeight = this.y - (this.height/2) + this.yPadding,
1534
- afterTitleIndex = index-1;
1535
-
1536
- //If the index is zero, we're getting the title
1537
- if (index === 0){
1538
- return baseLineHeight + this.titleHeight / 3;
1539
- } else{
1540
- return baseLineHeight + ((this.fontSize * 1.5 * afterTitleIndex) + this.fontSize / 2) + this.titleHeight;
1541
- }
1542
-
1543
- },
1544
- draw : function(){
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();
1555
-
1556
- ctx.textAlign = "left";
1557
- ctx.textBaseline = "middle";
1558
- ctx.fillStyle = this.titleTextColor;
1559
- ctx.font = this.titleFont;
1560
-
1561
- ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
1562
-
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));
1567
-
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.
1571
-
1572
- ctx.fillStyle = this.legendColorBackground;
1573
- ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1574
-
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);
1577
-
1578
-
1579
- },this);
1580
- }
1581
- }
1582
- });
1583
-
1584
- Chart.Scale = Chart.Element.extend({
1585
- initialize : function(){
1586
- this.fit();
1587
- },
1588
- buildYLabels : function(){
1589
- this.yLabels = [];
1590
-
1591
- var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1592
-
1593
- for (var i=0; i<=this.steps; i++){
1594
- this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1595
- }
1596
- this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) + 10 : 0;
1597
- },
1598
- addXLabel : function(label){
1599
- this.xLabels.push(label);
1600
- this.valuesCount++;
1601
- this.fit();
1602
- },
1603
- removeXLabel : function(){
1604
- this.xLabels.shift();
1605
- this.valuesCount--;
1606
- this.fit();
1607
- },
1608
- // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
1609
- fit: function(){
1610
- // First we need the width of the yLabels, assuming the xLabels aren't rotated
1611
-
1612
- // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
1613
- this.startPoint = (this.display) ? this.fontSize : 0;
1614
- this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
1615
-
1616
- // Apply padding settings to the start and end point.
1617
- this.startPoint += this.padding;
1618
- this.endPoint -= this.padding;
1619
-
1620
- // Cache the starting endpoint, excluding the space for x labels
1621
- var cachedEndPoint = this.endPoint;
1622
-
1623
- // Cache the starting height, so can determine if we need to recalculate the scale yAxis
1624
- var cachedHeight = this.endPoint - this.startPoint,
1625
- cachedYLabelWidth;
1626
-
1627
- // Build the current yLabels so we have an idea of what size they'll be to start
1628
- /*
1629
- * This sets what is returned from calculateScaleRange as static properties of this class:
1630
- *
1631
- this.steps;
1632
- this.stepValue;
1633
- this.min;
1634
- this.max;
1635
- *
1636
- */
1637
- this.calculateYRange(cachedHeight);
1638
-
1639
- // With these properties set we can now build the array of yLabels
1640
- // and also the width of the largest yLabel
1641
- this.buildYLabels();
1642
-
1643
- this.calculateXLabelRotation();
1644
-
1645
- while((cachedHeight > this.endPoint - this.startPoint)){
1646
- cachedHeight = this.endPoint - this.startPoint;
1647
- cachedYLabelWidth = this.yLabelWidth;
1648
-
1649
- this.calculateYRange(cachedHeight);
1650
- this.buildYLabels();
1651
-
1652
- // Only go through the xLabel loop again if the yLabel width has changed
1653
- if (cachedYLabelWidth < this.yLabelWidth){
1654
- this.endPoint = cachedEndPoint;
1655
- this.calculateXLabelRotation();
1656
- }
1657
- }
1658
-
1659
- },
1660
- calculateXLabelRotation : function(){
1661
- //Get the width of each grid by calculating the difference
1662
- //between x offsets between 0 and 1.
1663
-
1664
- this.ctx.font = this.font;
1665
-
1666
- var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
1667
- lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
1668
- firstRotated,
1669
- lastRotated;
1670
-
1671
-
1672
- this.xScalePaddingRight = lastWidth/2 + 3;
1673
- this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth) ? firstWidth/2 : this.yLabelWidth;
1674
-
1675
- this.xLabelRotation = 0;
1676
- if (this.display){
1677
- var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
1678
- cosRotation,
1679
- firstRotatedWidth;
1680
- this.xLabelWidth = originalLabelWidth;
1681
- //Allow 3 pixels x2 padding either side for label readability
1682
- var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
1683
-
1684
- //Max label rotate should be 90 - also act as a loop counter
1685
- while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
1686
- cosRotation = Math.cos(toRadians(this.xLabelRotation));
1687
-
1688
- firstRotated = cosRotation * firstWidth;
1689
- lastRotated = cosRotation * lastWidth;
1690
-
1691
- // We're right aligning the text now.
1692
- if (firstRotated + this.fontSize / 2 > this.yLabelWidth){
1693
- this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
1694
- }
1695
- this.xScalePaddingRight = this.fontSize/2;
1696
-
1697
-
1698
- this.xLabelRotation++;
1699
- this.xLabelWidth = cosRotation * originalLabelWidth;
1700
-
1701
- }
1702
- if (this.xLabelRotation > 0){
1703
- this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
1704
- }
1705
- }
1706
- else{
1707
- this.xLabelWidth = 0;
1708
- this.xScalePaddingRight = this.padding;
1709
- this.xScalePaddingLeft = this.padding;
1710
- }
1711
-
1712
- },
1713
- // Needs to be overidden in each Chart type
1714
- // Otherwise we need to pass all the data into the scale class
1715
- calculateYRange: noop,
1716
- drawingArea: function(){
1717
- return this.startPoint - this.endPoint;
1718
- },
1719
- calculateY : function(value){
1720
- var scalingFactor = this.drawingArea() / (this.min - this.max);
1721
- return this.endPoint - (scalingFactor * (value - this.min));
1722
- },
1723
- calculateX : function(index){
1724
- var isRotated = (this.xLabelRotation > 0),
1725
- // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
1726
- innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
1727
- valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
1728
- valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
1729
-
1730
- if (this.offsetGridLines){
1731
- valueOffset += (valueWidth/2);
1732
- }
1733
-
1734
- return Math.round(valueOffset);
1735
- },
1736
- update : function(newProps){
1737
- helpers.extend(this, newProps);
1738
- this.fit();
1739
- },
1740
- draw : function(){
1741
- var ctx = this.ctx,
1742
- yLabelGap = (this.endPoint - this.startPoint) / this.steps,
1743
- xStart = Math.round(this.xScalePaddingLeft);
1744
- if (this.display){
1745
- ctx.fillStyle = this.textColor;
1746
- ctx.font = this.font;
1747
- each(this.yLabels,function(labelString,index){
1748
- var yLabelCenter = this.endPoint - (yLabelGap * index),
1749
- linePositionY = Math.round(yLabelCenter),
1750
- drawHorizontalLine = this.showHorizontalLines;
1751
-
1752
- ctx.textAlign = "right";
1753
- ctx.textBaseline = "middle";
1754
- if (this.showLabels){
1755
- ctx.fillText(labelString,xStart - 10,yLabelCenter);
1756
- }
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
-
1767
- if (index > 0){
1768
- // This is a grid line in the centre, so drop that
1769
- ctx.lineWidth = this.gridLineWidth;
1770
- ctx.strokeStyle = this.gridLineColor;
1771
- } else {
1772
- // This is the first line on the scale
1773
- ctx.lineWidth = this.lineWidth;
1774
- ctx.strokeStyle = this.lineColor;
1775
- }
1776
-
1777
- linePositionY += helpers.aliasPixel(ctx.lineWidth);
1778
-
1779
- if(drawHorizontalLine){
1780
- ctx.moveTo(xStart, linePositionY);
1781
- ctx.lineTo(this.width, linePositionY);
1782
- ctx.stroke();
1783
- ctx.closePath();
1784
- }
1785
-
1786
- ctx.lineWidth = this.lineWidth;
1787
- ctx.strokeStyle = this.lineColor;
1788
- ctx.beginPath();
1789
- ctx.moveTo(xStart - 5, linePositionY);
1790
- ctx.lineTo(xStart, linePositionY);
1791
- ctx.stroke();
1792
- ctx.closePath();
1793
-
1794
- },this);
1795
-
1796
- each(this.xLabels,function(label,index){
1797
- var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
1798
- // Check to see if line/bar here and decide where to place the line
1799
- linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
1800
- isRotated = (this.xLabelRotation > 0),
1801
- drawVerticalLine = this.showVerticalLines;
1802
-
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
- }
1811
-
1812
- if (index > 0){
1813
- // This is a grid line in the centre, so drop that
1814
- ctx.lineWidth = this.gridLineWidth;
1815
- ctx.strokeStyle = this.gridLineColor;
1816
- } else {
1817
- // This is the first line on the scale
1818
- ctx.lineWidth = this.lineWidth;
1819
- ctx.strokeStyle = this.lineColor;
1820
- }
1821
-
1822
- if (drawVerticalLine){
1823
- ctx.moveTo(linePos,this.endPoint);
1824
- ctx.lineTo(linePos,this.startPoint - 3);
1825
- ctx.stroke();
1826
- ctx.closePath();
1827
- }
1828
-
1829
-
1830
- ctx.lineWidth = this.lineWidth;
1831
- ctx.strokeStyle = this.lineColor;
1832
-
1833
-
1834
- // Small lines at the bottom of the base grid line
1835
- ctx.beginPath();
1836
- ctx.moveTo(linePos,this.endPoint);
1837
- ctx.lineTo(linePos,this.endPoint + 5);
1838
- ctx.stroke();
1839
- ctx.closePath();
1840
-
1841
- ctx.save();
1842
- ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
1843
- ctx.rotate(toRadians(this.xLabelRotation)*-1);
1844
- ctx.font = this.font;
1845
- ctx.textAlign = (isRotated) ? "right" : "center";
1846
- ctx.textBaseline = (isRotated) ? "middle" : "top";
1847
- ctx.fillText(label, 0, 0);
1848
- ctx.restore();
1849
- },this);
1850
-
1851
- }
1852
- }
1853
-
1854
- });
1855
-
1856
- Chart.RadialScale = Chart.Element.extend({
1857
- initialize: function(){
1858
- this.size = min([this.height, this.width]);
1859
- this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1860
- },
1861
- calculateCenterOffset: function(value){
1862
- // Take into account half font size + the yPadding of the top value
1863
- var scalingFactor = this.drawingArea / (this.max - this.min);
1864
-
1865
- return (value - this.min) * scalingFactor;
1866
- },
1867
- update : function(){
1868
- if (!this.lineArc){
1869
- this.setScaleSize();
1870
- } else {
1871
- this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1872
- }
1873
- this.buildYLabels();
1874
- },
1875
- buildYLabels: function(){
1876
- this.yLabels = [];
1877
-
1878
- var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1879
-
1880
- for (var i=0; i<=this.steps; i++){
1881
- this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1882
- }
1883
- },
1884
- getCircumference : function(){
1885
- return ((Math.PI*2) / this.valuesCount);
1886
- },
1887
- setScaleSize: function(){
1888
- /*
1889
- * Right, this is really confusing and there is a lot of maths going on here
1890
- * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
1891
- *
1892
- * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
1893
- *
1894
- * Solution:
1895
- *
1896
- * We assume the radius of the polygon is half the size of the canvas at first
1897
- * at each index we check if the text overlaps.
1898
- *
1899
- * Where it does, we store that angle and that index.
1900
- *
1901
- * After finding the largest index and angle we calculate how much we need to remove
1902
- * from the shape radius to move the point inwards by that x.
1903
- *
1904
- * We average the left and right distances to get the maximum shape radius that can fit in the box
1905
- * along with labels.
1906
- *
1907
- * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
1908
- * on each side, removing that from the size, halving it and adding the left x protrusion width.
1909
- *
1910
- * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
1911
- * and position it in the most space efficient manner
1912
- *
1913
- * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
1914
- */
1915
-
1916
-
1917
- // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
1918
- // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
1919
- var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
1920
- pointPosition,
1921
- i,
1922
- textWidth,
1923
- halfTextWidth,
1924
- furthestRight = this.width,
1925
- furthestRightIndex,
1926
- furthestRightAngle,
1927
- furthestLeft = 0,
1928
- furthestLeftIndex,
1929
- furthestLeftAngle,
1930
- xProtrusionLeft,
1931
- xProtrusionRight,
1932
- radiusReductionRight,
1933
- radiusReductionLeft,
1934
- maxWidthRadius;
1935
- this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
1936
- for (i=0;i<this.valuesCount;i++){
1937
- // 5px to space the text slightly out - similar to what we do in the draw function.
1938
- pointPosition = this.getPointPosition(i, largestPossibleRadius);
1939
- textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
1940
- if (i === 0 || i === this.valuesCount/2){
1941
- // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
1942
- // of the radar chart, so text will be aligned centrally, so we'll half it and compare
1943
- // w/left and right text sizes
1944
- halfTextWidth = textWidth/2;
1945
- if (pointPosition.x + halfTextWidth > furthestRight) {
1946
- furthestRight = pointPosition.x + halfTextWidth;
1947
- furthestRightIndex = i;
1948
- }
1949
- if (pointPosition.x - halfTextWidth < furthestLeft) {
1950
- furthestLeft = pointPosition.x - halfTextWidth;
1951
- furthestLeftIndex = i;
1952
- }
1953
- }
1954
- else if (i < this.valuesCount/2) {
1955
- // Less than half the values means we'll left align the text
1956
- if (pointPosition.x + textWidth > furthestRight) {
1957
- furthestRight = pointPosition.x + textWidth;
1958
- furthestRightIndex = i;
1959
- }
1960
- }
1961
- else if (i > this.valuesCount/2){
1962
- // More than half the values means we'll right align the text
1963
- if (pointPosition.x - textWidth < furthestLeft) {
1964
- furthestLeft = pointPosition.x - textWidth;
1965
- furthestLeftIndex = i;
1966
- }
1967
- }
1968
- }
1969
-
1970
- xProtrusionLeft = furthestLeft;
1971
-
1972
- xProtrusionRight = Math.ceil(furthestRight - this.width);
1973
-
1974
- furthestRightAngle = this.getIndexAngle(furthestRightIndex);
1975
-
1976
- furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
1977
-
1978
- radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
1979
-
1980
- radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
1981
-
1982
- // Ensure we actually need to reduce the size of the chart
1983
- radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
1984
- radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
1985
-
1986
- this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
1987
-
1988
- //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
1989
- this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
1990
-
1991
- },
1992
- setCenterPoint: function(leftMovement, rightMovement){
1993
-
1994
- var maxRight = this.width - rightMovement - this.drawingArea,
1995
- maxLeft = leftMovement + this.drawingArea;
1996
-
1997
- this.xCenter = (maxLeft + maxRight)/2;
1998
- // Always vertically in the centre as the text height doesn't change
1999
- this.yCenter = (this.height/2);
2000
- },
2001
-
2002
- getIndexAngle : function(index){
2003
- var angleMultiplier = (Math.PI * 2) / this.valuesCount;
2004
- // Start from the top instead of right, so remove a quarter of the circle
2005
-
2006
- return index * angleMultiplier - (Math.PI/2);
2007
- },
2008
- getPointPosition : function(index, distanceFromCenter){
2009
- var thisAngle = this.getIndexAngle(index);
2010
- return {
2011
- x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
2012
- y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
2013
- };
2014
- },
2015
- draw: function(){
2016
- if (this.display){
2017
- var ctx = this.ctx;
2018
- each(this.yLabels, function(label, index){
2019
- // Don't draw a centre value
2020
- if (index > 0){
2021
- var yCenterOffset = index * (this.drawingArea/this.steps),
2022
- yHeight = this.yCenter - yCenterOffset,
2023
- pointPosition;
2024
-
2025
- // Draw circular lines around the scale
2026
- if (this.lineWidth > 0){
2027
- ctx.strokeStyle = this.lineColor;
2028
- ctx.lineWidth = this.lineWidth;
2029
-
2030
- if(this.lineArc){
2031
- ctx.beginPath();
2032
- ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
2033
- ctx.closePath();
2034
- ctx.stroke();
2035
- } else{
2036
- ctx.beginPath();
2037
- for (var i=0;i<this.valuesCount;i++)
2038
- {
2039
- pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
2040
- if (i === 0){
2041
- ctx.moveTo(pointPosition.x, pointPosition.y);
2042
- } else {
2043
- ctx.lineTo(pointPosition.x, pointPosition.y);
2044
- }
2045
- }
2046
- ctx.closePath();
2047
- ctx.stroke();
2048
- }
2049
- }
2050
- if(this.showLabels){
2051
- ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
2052
- if (this.showLabelBackdrop){
2053
- var labelWidth = ctx.measureText(label).width;
2054
- ctx.fillStyle = this.backdropColor;
2055
- ctx.fillRect(
2056
- this.xCenter - labelWidth/2 - this.backdropPaddingX,
2057
- yHeight - this.fontSize/2 - this.backdropPaddingY,
2058
- labelWidth + this.backdropPaddingX*2,
2059
- this.fontSize + this.backdropPaddingY*2
2060
- );
2061
- }
2062
- ctx.textAlign = 'center';
2063
- ctx.textBaseline = "middle";
2064
- ctx.fillStyle = this.fontColor;
2065
- ctx.fillText(label, this.xCenter, yHeight);
2066
- }
2067
- }
2068
- }, this);
2069
-
2070
- if (!this.lineArc){
2071
- ctx.lineWidth = this.angleLineWidth;
2072
- ctx.strokeStyle = this.angleLineColor;
2073
- for (var i = this.valuesCount - 1; i >= 0; i--) {
2074
- var centerOffset = null, outerPosition = null;
2075
-
2076
- if (this.angleLineWidth > 0){
2077
- centerOffset = this.calculateCenterOffset(this.max);
2078
- outerPosition = this.getPointPosition(i, centerOffset);
2079
- ctx.beginPath();
2080
- ctx.moveTo(this.xCenter, this.yCenter);
2081
- ctx.lineTo(outerPosition.x, outerPosition.y);
2082
- ctx.stroke();
2083
- ctx.closePath();
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
- }
2108
- // Extra 3px out for some label spacing
2109
- var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
2110
- ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
2111
- ctx.fillStyle = this.pointLabelFontColor;
2112
-
2113
- var labelsCount = this.labels.length,
2114
- halfLabelsCount = this.labels.length/2,
2115
- quarterLabelsCount = halfLabelsCount/2,
2116
- upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
2117
- exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
2118
- if (i === 0){
2119
- ctx.textAlign = 'center';
2120
- } else if(i === halfLabelsCount){
2121
- ctx.textAlign = 'center';
2122
- } else if (i < halfLabelsCount){
2123
- ctx.textAlign = 'left';
2124
- } else {
2125
- ctx.textAlign = 'right';
2126
- }
2127
-
2128
- // Set the correct text baseline based on outer positioning
2129
- if (exactQuarter){
2130
- ctx.textBaseline = 'middle';
2131
- } else if (upperHalf){
2132
- ctx.textBaseline = 'bottom';
2133
- } else {
2134
- ctx.textBaseline = 'top';
2135
- }
2136
-
2137
- ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
2138
- }
2139
- }
2140
- }
2141
- }
2142
- });
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
-
2231
- // Attach global event to resize each chart instance when the browser resizes
2232
- helpers.addEvent(window, "resize", (function(){
2233
- // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
2234
- var timeout;
2235
- return function(){
2236
- clearTimeout(timeout);
2237
- timeout = setTimeout(function(){
2238
- each(Chart.instances,function(instance){
2239
- // If the responsive flag is set in the chart instance config
2240
- // Cascade the resize event down to the chart.
2241
- if (instance.options.responsive){
2242
- instance.resize(instance.render, true);
2243
- }
2244
- });
2245
- }, 50);
2246
- };
2247
- })());
2248
-
2249
-
2250
- if (amd) {
2251
- define(function(){
2252
- return Chart;
2253
- });
2254
- } else if (typeof module === 'object' && module.exports) {
2255
- module.exports = Chart;
2256
- }
2257
-
2258
- root.Chart = Chart;
2259
-
2260
- Chart.noConflict = function(){
2261
- root.Chart = previous;
2262
- return Chart;
2263
- };
2264
-
2265
- }).call(this);
2266
-
2267
- (function(){
2268
- "use strict";
2269
-
2270
- var root = this,
2271
- Chart = root.Chart,
2272
- helpers = Chart.helpers;
2273
-
2274
-
2275
- var defaultConfig = {
2276
- //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
2277
- scaleBeginAtZero : true,
2278
-
2279
- //Boolean - Whether grid lines are shown across the chart
2280
- scaleShowGridLines : true,
2281
-
2282
- //String - Colour of the grid lines
2283
- scaleGridLineColor : "rgba(235,237,239,1)",
2284
-
2285
- //Number - Width of the grid lines
2286
- scaleGridLineWidth : 1,
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
-
2294
- //Boolean - If there is a stroke on each bar
2295
- barShowStroke : true,
2296
-
2297
- //Number - Pixel width of the bar stroke
2298
- barStrokeWidth : 2,
2299
-
2300
- //Number - Spacing between each of the X value sets
2301
- barValueSpacing : 5,
2302
-
2303
- //Number - Spacing between data sets within X values
2304
- barDatasetSpacing : 1,
2305
-
2306
- //String - A legend template
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>"
2308
-
2309
- };
2310
-
2311
-
2312
- Chart.Type.extend({
2313
- name: "Bar",
2314
- defaults : defaultConfig,
2315
- initialize: function(data){
2316
-
2317
- //Expose options as a scope variable here so we can access it in the ScaleClass
2318
- var options = this.options;
2319
-
2320
- this.ScaleClass = Chart.Scale.extend({
2321
- offsetGridLines : true,
2322
- calculateBarX : function(datasetCount, datasetIndex, barIndex){
2323
- //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
2324
- var xWidth = this.calculateBaseWidth(),
2325
- xAbsolute = this.calculateX(barIndex) - (xWidth/2),
2326
- barWidth = this.calculateBarWidth(datasetCount);
2327
-
2328
- return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
2329
- },
2330
- calculateBaseWidth : function(){
2331
- return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
2332
- },
2333
- calculateBarWidth : function(datasetCount){
2334
- //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
2335
- var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
2336
-
2337
- return (baseWidth / datasetCount);
2338
- }
2339
- });
2340
-
2341
- this.datasets = [];
2342
-
2343
- //Set up tooltip events on the chart
2344
- if (this.options.showTooltips){
2345
- helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2346
- var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
2347
-
2348
- this.eachBars(function(bar){
2349
- bar.restore(['fillColor', 'strokeColor']);
2350
- });
2351
- helpers.each(activeBars, function(activeBar){
2352
- activeBar.fillColor = activeBar.highlightFill;
2353
- activeBar.strokeColor = activeBar.highlightStroke;
2354
- });
2355
- this.showTooltip(activeBars);
2356
- });
2357
- }
2358
-
2359
- //Declare the extension of the default point, to cater for the options passed in to the constructor
2360
- this.BarClass = Chart.Rectangle.extend({
2361
- strokeWidth : this.options.barStrokeWidth,
2362
- showStroke : this.options.barShowStroke,
2363
- ctx : this.chart.ctx
2364
- });
2365
-
2366
- //Iterate through each of the datasets, and build this into a property of the chart
2367
- helpers.each(data.datasets,function(dataset,datasetIndex){
2368
-
2369
- var datasetObject = {
2370
- label : dataset.label || null,
2371
- fillColor : dataset.fillColor,
2372
- strokeColor : dataset.strokeColor,
2373
- bars : []
2374
- };
2375
-
2376
- this.datasets.push(datasetObject);
2377
-
2378
- helpers.each(dataset.data,function(dataPoint,index){
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
- }));
2389
- },this);
2390
-
2391
- },this);
2392
-
2393
- this.buildScale(data.labels);
2394
-
2395
- this.BarClass.prototype.base = this.scale.endPoint;
2396
-
2397
- this.eachBars(function(bar, index, datasetIndex){
2398
- helpers.extend(bar, {
2399
- width : this.scale.calculateBarWidth(this.datasets.length),
2400
- x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2401
- y: this.scale.endPoint
2402
- });
2403
- bar.save();
2404
- }, this);
2405
-
2406
- this.render();
2407
- },
2408
- update : function(){
2409
- this.scale.update();
2410
- // Reset any highlight colours before updating.
2411
- helpers.each(this.activeElements, function(activeElement){
2412
- activeElement.restore(['fillColor', 'strokeColor']);
2413
- });
2414
-
2415
- this.eachBars(function(bar){
2416
- bar.save();
2417
- });
2418
- this.render();
2419
- },
2420
- eachBars : function(callback){
2421
- helpers.each(this.datasets,function(dataset, datasetIndex){
2422
- helpers.each(dataset.bars, callback, this, datasetIndex);
2423
- },this);
2424
- },
2425
- getBarsAtEvent : function(e){
2426
- var barsArray = [],
2427
- eventPosition = helpers.getRelativePosition(e),
2428
- datasetIterator = function(dataset){
2429
- barsArray.push(dataset.bars[barIndex]);
2430
- },
2431
- barIndex;
2432
-
2433
- for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
2434
- for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
2435
- if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
2436
- helpers.each(this.datasets, datasetIterator);
2437
- return barsArray;
2438
- }
2439
- }
2440
- }
2441
-
2442
- return barsArray;
2443
- },
2444
- buildScale : function(labels){
2445
- var self = this;
2446
-
2447
- var dataTotal = function(){
2448
- var values = [];
2449
- self.eachBars(function(bar){
2450
- values.push(bar.value);
2451
- });
2452
- return values;
2453
- };
2454
-
2455
- var scaleOptions = {
2456
- templateString : this.options.scaleLabel,
2457
- height : this.chart.height,
2458
- width : this.chart.width,
2459
- ctx : this.chart.ctx,
2460
- textColor : this.options.scaleFontColor,
2461
- fontSize : this.options.scaleFontSize,
2462
- fontStyle : this.options.scaleFontStyle,
2463
- fontFamily : this.options.scaleFontFamily,
2464
- valuesCount : labels.length,
2465
- beginAtZero : this.options.scaleBeginAtZero,
2466
- integersOnly : this.options.scaleIntegersOnly,
2467
- calculateYRange: function(currentHeight){
2468
- var updatedRanges = helpers.calculateScaleRange(
2469
- dataTotal(),
2470
- currentHeight,
2471
- this.fontSize,
2472
- this.beginAtZero,
2473
- this.integersOnly
2474
- );
2475
- helpers.extend(this, updatedRanges);
2476
- },
2477
- xLabels : labels,
2478
- font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2479
- lineWidth : this.options.scaleLineWidth,
2480
- lineColor : this.options.scaleLineColor,
2481
- showHorizontalLines : this.options.scaleShowHorizontalLines,
2482
- showVerticalLines : this.options.scaleShowVerticalLines,
2483
- gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2484
- gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2485
- padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
2486
- showLabels : this.options.scaleShowLabels,
2487
- display : this.options.showScale
2488
- };
2489
-
2490
- if (this.options.scaleOverride){
2491
- helpers.extend(scaleOptions, {
2492
- calculateYRange: helpers.noop,
2493
- steps: this.options.scaleSteps,
2494
- stepValue: this.options.scaleStepWidth,
2495
- min: this.options.scaleStartValue,
2496
- max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2497
- });
2498
- }
2499
-
2500
- this.scale = new this.ScaleClass(scaleOptions);
2501
- },
2502
- addData : function(valuesArray,label){
2503
- //Map the values array for each of the datasets
2504
- helpers.each(valuesArray,function(value,datasetIndex){
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
- }));
2517
- },this);
2518
-
2519
- this.scale.addXLabel(label);
2520
- //Then re-render the chart.
2521
- this.update();
2522
- },
2523
- removeData : function(){
2524
- this.scale.removeXLabel();
2525
- //Then re-render the chart.
2526
- helpers.each(this.datasets,function(dataset){
2527
- dataset.bars.shift();
2528
- },this);
2529
- this.update();
2530
- },
2531
- reflow : function(){
2532
- helpers.extend(this.BarClass.prototype,{
2533
- y: this.scale.endPoint,
2534
- base : this.scale.endPoint
2535
- });
2536
- var newScaleProps = helpers.extend({
2537
- height : this.chart.height,
2538
- width : this.chart.width
2539
- });
2540
- this.scale.update(newScaleProps);
2541
- },
2542
- draw : function(ease){
2543
- var easingDecimal = ease || 1;
2544
- this.clear();
2545
-
2546
- var ctx = this.chart.ctx;
2547
-
2548
- this.scale.draw(easingDecimal);
2549
-
2550
- //Draw all the bars for each dataset
2551
- helpers.each(this.datasets,function(dataset,datasetIndex){
2552
- helpers.each(dataset.bars,function(bar,index){
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
- }
2562
- },this);
2563
-
2564
- },this);
2565
- }
2566
- });
2567
-
2568
-
2569
- }).call(this);
2570
-
2571
- (function(){
2572
- "use strict";
2573
-
2574
- var root = this,
2575
- Chart = root.Chart,
2576
- //Cache a local reference to Chart.helpers
2577
- helpers = Chart.helpers;
2578
-
2579
- var defaultConfig = {
2580
- //Boolean - Whether we should show a stroke on each segment
2581
- segmentShowStroke : true,
2582
-
2583
- //String - The colour of each segment stroke
2584
- segmentStrokeColor : "rgba(255,255,255,1)",
2585
-
2586
- //Number - The width of each segment stroke
2587
- segmentStrokeWidth : 2,
2588
-
2589
- //The percentage of the chart that we cut out of the middle.
2590
- percentageInnerCutout : 50,
2591
-
2592
- //Number - Amount of animation steps
2593
- animationSteps : 100,
2594
-
2595
- //String - Animation easing effect
2596
- animationEasing : "easeOutBounce",
2597
-
2598
- //Boolean - Whether we animate the rotation of the Doughnut
2599
- animateRotate : true,
2600
-
2601
- //Boolean - Whether we animate scaling the Doughnut from the centre
2602
- animateScale : false,
2603
-
2604
- //String - A legend template
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>"
2606
-
2607
- };
2608
-
2609
- Chart.Type.extend({
2610
- //Passing in a name registers this chart in the Chart namespace
2611
- name: "Doughnut",
2612
- //Providing a defaults will also register the deafults in the chart namespace
2613
- defaults : defaultConfig,
2614
- //Initialize is fired when the chart is initialized - Data is passed in as a parameter
2615
- //Config is automatically merged by the core of Chart.js, and is available at this.options
2616
- initialize: function(data){
2617
-
2618
- //Declare segments as a static property to prevent inheriting across the Chart type prototype
2619
- this.segments = [];
2620
- this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
2621
-
2622
- this.SegmentArc = Chart.Arc.extend({
2623
- ctx : this.chart.ctx,
2624
- x : this.chart.width/2,
2625
- y : this.chart.height/2
2626
- });
2627
-
2628
- //Set up tooltip events on the chart
2629
- if (this.options.showTooltips){
2630
- helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2631
- var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
2632
-
2633
- helpers.each(this.segments,function(segment){
2634
- segment.restore(["fillColor"]);
2635
- });
2636
- helpers.each(activeSegments,function(activeSegment){
2637
- activeSegment.fillColor = activeSegment.highlightColor;
2638
- });
2639
- this.showTooltip(activeSegments);
2640
- });
2641
- }
2642
- this.calculateTotal(data);
2643
-
2644
- helpers.each(data,function(datapoint, index){
2645
- if (!datapoint.color) {
2646
- datapoint.color = 'hsl(' + (360 * index / data.length) + ', 100%, 50%)';
2647
- }
2648
- this.addData(datapoint, index, true);
2649
- },this);
2650
-
2651
- this.render();
2652
- },
2653
- getSegmentsAtEvent : function(e){
2654
- var segmentsArray = [];
2655
-
2656
- var location = helpers.getRelativePosition(e);
2657
-
2658
- helpers.each(this.segments,function(segment){
2659
- if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
2660
- },this);
2661
- return segmentsArray;
2662
- },
2663
- addData : function(segment, atIndex, silent){
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
- }
2669
- this.segments.splice(index, 0, new this.SegmentArc({
2670
- value : segment.value,
2671
- outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
2672
- innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
2673
- fillColor : segment.color,
2674
- highlightColor : segment.highlight || segment.color,
2675
- showStroke : this.options.segmentShowStroke,
2676
- strokeWidth : this.options.segmentStrokeWidth,
2677
- strokeColor : this.options.segmentStrokeColor,
2678
- startAngle : Math.PI * 1.5,
2679
- circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
2680
- label : segment.label
2681
- }));
2682
- if (!silent){
2683
- this.reflow();
2684
- this.update();
2685
- }
2686
- },
2687
- calculateCircumference : function(value) {
2688
- if ( this.total > 0 ) {
2689
- return (Math.PI*2)*(value / this.total);
2690
- } else {
2691
- return 0;
2692
- }
2693
- },
2694
- calculateTotal : function(data){
2695
- this.total = 0;
2696
- helpers.each(data,function(segment){
2697
- this.total += Math.abs(segment.value);
2698
- },this);
2699
- },
2700
- update : function(){
2701
- this.calculateTotal(this.segments);
2702
-
2703
- // Reset any highlight colours before updating.
2704
- helpers.each(this.activeElements, function(activeElement){
2705
- activeElement.restore(['fillColor']);
2706
- });
2707
-
2708
- helpers.each(this.segments,function(segment){
2709
- segment.save();
2710
- });
2711
- this.render();
2712
- },
2713
-
2714
- removeData: function(atIndex){
2715
- var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
2716
- this.segments.splice(indexToDelete, 1);
2717
- this.reflow();
2718
- this.update();
2719
- },
2720
-
2721
- reflow : function(){
2722
- helpers.extend(this.SegmentArc.prototype,{
2723
- x : this.chart.width/2,
2724
- y : this.chart.height/2
2725
- });
2726
- this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
2727
- helpers.each(this.segments, function(segment){
2728
- segment.update({
2729
- outerRadius : this.outerRadius,
2730
- innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
2731
- });
2732
- }, this);
2733
- },
2734
- draw : function(easeDecimal){
2735
- var animDecimal = (easeDecimal) ? easeDecimal : 1;
2736
- this.clear();
2737
- helpers.each(this.segments,function(segment,index){
2738
- segment.transition({
2739
- circumference : this.calculateCircumference(segment.value),
2740
- outerRadius : this.outerRadius,
2741
- innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
2742
- },animDecimal);
2743
-
2744
- segment.endAngle = segment.startAngle + segment.circumference;
2745
-
2746
- segment.draw();
2747
- if (index === 0){
2748
- segment.startAngle = Math.PI * 1.5;
2749
- }
2750
- //Check to see if it's the last segment, if not get the next and update the start angle
2751
- if (index < this.segments.length-1){
2752
- this.segments[index+1].startAngle = segment.endAngle;
2753
- }
2754
- },this);
2755
-
2756
- }
2757
- });
2758
-
2759
- Chart.types.Doughnut.extend({
2760
- name : "Pie",
2761
- defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
2762
- });
2763
-
2764
- }).call(this);
2765
-
2766
- (function(){
2767
- "use strict";
2768
-
2769
- var root = this,
2770
- Chart = root.Chart,
2771
- helpers = Chart.helpers;
2772
-
2773
- var defaultConfig = {
2774
-
2775
- ///Boolean - Whether grid lines are shown across the chart
2776
- scaleShowGridLines : true,
2777
-
2778
- //String - Colour of the grid lines
2779
- scaleGridLineColor : "rgba(235,237,239,1)",
2780
-
2781
- //Number - Width of the grid lines
2782
- scaleGridLineWidth : 1,
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
-
2790
- //Boolean - Whether the line is curved between points
2791
- bezierCurve : false,
2792
-
2793
- //Number - Tension of the bezier curve between points
2794
- bezierCurveTension : 0.4,
2795
-
2796
- //Boolean - Whether to show a dot for each point
2797
- pointDot : true,
2798
-
2799
- //Number - Radius of each point dot in pixels
2800
- pointDotRadius : 4,
2801
-
2802
- //Number - Pixel width of point dot stroke
2803
- pointDotStrokeWidth : 2,
2804
-
2805
- //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
2806
- pointHitDetectionRadius : 20,
2807
-
2808
- //Boolean - Whether to show a stroke for datasets
2809
- datasetStroke : true,
2810
-
2811
- //Number - Pixel width of dataset stroke
2812
- datasetStrokeWidth : 2,
2813
-
2814
- //Boolean - Whether to fill the dataset with a colour
2815
- datasetFill : true,
2816
-
2817
- //String - A legend template
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
2822
-
2823
- };
2824
-
2825
-
2826
- Chart.Type.extend({
2827
- name: "Line",
2828
- defaults : defaultConfig,
2829
- initialize: function(data){
2830
- //Declare the extension of the default point, to cater for the options passed in to the constructor
2831
- this.PointClass = Chart.Point.extend({
2832
- offsetGridLines : this.options.offsetGridLines,
2833
- strokeWidth : this.options.pointDotStrokeWidth,
2834
- radius : this.options.pointDotRadius,
2835
- display: this.options.pointDot,
2836
- hitDetectionRadius : this.options.pointHitDetectionRadius,
2837
- ctx : this.chart.ctx,
2838
- inRange : function(mouseX){
2839
- return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
2840
- }
2841
- });
2842
-
2843
- this.datasets = [];
2844
-
2845
- //Set up tooltip events on the chart
2846
- if (this.options.showTooltips){
2847
- helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2848
- var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
2849
- this.eachPoints(function(point){
2850
- point.restore(['fillColor', 'strokeColor']);
2851
- });
2852
- helpers.each(activePoints, function(activePoint){
2853
- activePoint.fillColor = activePoint.highlightFill;
2854
- activePoint.strokeColor = activePoint.highlightStroke;
2855
- });
2856
- this.showTooltip(activePoints);
2857
- });
2858
- }
2859
-
2860
- //Iterate through each of the datasets, and build this into a property of the chart
2861
- helpers.each(data.datasets,function(dataset){
2862
-
2863
- var datasetObject = {
2864
- label : dataset.label || null,
2865
- fillColor : dataset.fillColor,
2866
- strokeColor : dataset.strokeColor,
2867
- pointColor : dataset.pointColor,
2868
- pointStrokeColor : dataset.pointStrokeColor,
2869
- points : []
2870
- };
2871
-
2872
- this.datasets.push(datasetObject);
2873
-
2874
-
2875
- helpers.each(dataset.data,function(dataPoint,index){
2876
- //Add a new point for each piece of data, passing any required data to draw.
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
- }));
2886
- },this);
2887
-
2888
- this.buildScale(data.labels);
2889
-
2890
-
2891
- this.eachPoints(function(point, index){
2892
- helpers.extend(point, {
2893
- x: this.scale.calculateX(index),
2894
- y: this.scale.endPoint
2895
- });
2896
- point.save();
2897
- }, this);
2898
-
2899
- },this);
2900
-
2901
-
2902
- this.render();
2903
- },
2904
- update : function(){
2905
- this.scale.update();
2906
- // Reset any highlight colours before updating.
2907
- helpers.each(this.activeElements, function(activeElement){
2908
- activeElement.restore(['fillColor', 'strokeColor']);
2909
- });
2910
- this.eachPoints(function(point){
2911
- point.save();
2912
- });
2913
- this.render();
2914
- },
2915
- eachPoints : function(callback){
2916
- helpers.each(this.datasets,function(dataset){
2917
- helpers.each(dataset.points,callback,this);
2918
- },this);
2919
- },
2920
- getPointsAtEvent : function(e){
2921
- var pointsArray = [],
2922
- eventPosition = helpers.getRelativePosition(e);
2923
- helpers.each(this.datasets,function(dataset){
2924
- helpers.each(dataset.points,function(point){
2925
- if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
2926
- });
2927
- },this);
2928
- return pointsArray;
2929
- },
2930
- buildScale : function(labels){
2931
- var self = this;
2932
-
2933
- var dataTotal = function(){
2934
- var values = [];
2935
- self.eachPoints(function(point){
2936
- values.push(point.value);
2937
- });
2938
-
2939
- return values;
2940
- };
2941
-
2942
- var scaleOptions = {
2943
- templateString : this.options.scaleLabel,
2944
- height : this.chart.height,
2945
- width : this.chart.width,
2946
- ctx : this.chart.ctx,
2947
- textColor : this.options.scaleFontColor,
2948
- offsetGridLines : this.options.offsetGridLines,
2949
- fontSize : this.options.scaleFontSize,
2950
- fontStyle : this.options.scaleFontStyle,
2951
- fontFamily : this.options.scaleFontFamily,
2952
- valuesCount : labels.length,
2953
- beginAtZero : this.options.scaleBeginAtZero,
2954
- integersOnly : this.options.scaleIntegersOnly,
2955
- calculateYRange : function(currentHeight){
2956
- var updatedRanges = helpers.calculateScaleRange(
2957
- dataTotal(),
2958
- currentHeight,
2959
- this.fontSize,
2960
- this.beginAtZero,
2961
- this.integersOnly
2962
- );
2963
- helpers.extend(this, updatedRanges);
2964
- },
2965
- xLabels : labels,
2966
- font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2967
- lineWidth : this.options.scaleLineWidth,
2968
- lineColor : this.options.scaleLineColor,
2969
- showHorizontalLines : this.options.scaleShowHorizontalLines,
2970
- showVerticalLines : this.options.scaleShowVerticalLines,
2971
- gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2972
- gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2973
- padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
2974
- showLabels : this.options.scaleShowLabels,
2975
- display : this.options.showScale
2976
- };
2977
-
2978
- if (this.options.scaleOverride){
2979
- helpers.extend(scaleOptions, {
2980
- calculateYRange: helpers.noop,
2981
- steps: this.options.scaleSteps,
2982
- stepValue: this.options.scaleStepWidth,
2983
- min: this.options.scaleStartValue,
2984
- max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2985
- });
2986
- }
2987
-
2988
-
2989
- this.scale = new Chart.Scale(scaleOptions);
2990
- },
2991
- addData : function(valuesArray,label){
2992
- //Map the values array for each of the datasets
2993
-
2994
- helpers.each(valuesArray,function(value,datasetIndex){
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
- }));
3005
- },this);
3006
-
3007
- this.scale.addXLabel(label);
3008
- //Then re-render the chart.
3009
- this.update();
3010
- },
3011
- removeData : function(){
3012
- this.scale.removeXLabel();
3013
- //Then re-render the chart.
3014
- helpers.each(this.datasets,function(dataset){
3015
- dataset.points.shift();
3016
- },this);
3017
- this.update();
3018
- },
3019
- reflow : function(){
3020
- var newScaleProps = helpers.extend({
3021
- height : this.chart.height,
3022
- width : this.chart.width
3023
- });
3024
- this.scale.update(newScaleProps);
3025
- },
3026
- draw : function(ease){
3027
- var easingDecimal = ease || 1;
3028
- this.clear();
3029
-
3030
- var ctx = this.chart.ctx;
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;
3044
- this.scale.draw(easingDecimal);
3045
-
3046
-
3047
- helpers.each(this.datasets,function(dataset){
3048
- var pointsWithValues = helpers.where(dataset.points, hasValue);
3049
-
3050
- //Transition each point first so that the line and point drawing isn't out of sync
3051
- //We can use this extra loop to calculate the control points of this dataset also in this loop
3052
-
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
- }
3060
- },this);
3061
-
3062
-
3063
- // Control points need to be calculated in a separate loop, because we need to know the current x/y of the point
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
3065
- if (this.options.bezierCurve){
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;
3080
- }
3081
- else if (point.controlPoints.outer.y < this.scale.startPoint){
3082
- point.controlPoints.outer.y = this.scale.startPoint;
3083
- }
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;
3091
- }
3092
- },this);
3093
- }
3094
-
3095
-
3096
- //Draw the line between all the points
3097
- ctx.lineWidth = this.options.datasetStrokeWidth;
3098
- ctx.strokeStyle = dataset.strokeColor;
3099
- ctx.beginPath();
3100
-
3101
- helpers.each(pointsWithValues, function(point, index){
3102
- if (index === 0){
3103
- ctx.moveTo(point.x, point.y);
3104
- }
3105
- else{
3106
- if(this.options.bezierCurve){
3107
- var previous = previousPoint(point, pointsWithValues, index);
3108
-
3109
- ctx.bezierCurveTo(
3110
- previous.controlPoints.outer.x,
3111
- previous.controlPoints.outer.y,
3112
- point.controlPoints.inner.x,
3113
- point.controlPoints.inner.y,
3114
- point.x,
3115
- point.y
3116
- );
3117
- }
3118
- else{
3119
- ctx.lineTo(point.x,point.y);
3120
- }
3121
- }
3122
- }, this);
3123
-
3124
- if (this.options.datasetStroke) {
3125
- ctx.stroke();
3126
- }
3127
-
3128
- if (this.options.datasetFill && pointsWithValues.length > 0){
3129
- //Round off the line by going to the base of the chart, back to the start, then fill.
3130
- ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
3131
- ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
3132
- ctx.fillStyle = dataset.fillColor;
3133
- ctx.closePath();
3134
- ctx.fill();
3135
- }
3136
-
3137
- //Now draw the points over the line
3138
- //A little inefficient double looping, but better than the line
3139
- //lagging behind the point positions
3140
- helpers.each(pointsWithValues,function(point){
3141
- point.draw();
3142
- });
3143
- },this);
3144
- }
3145
- });
3146
-
3147
-
3148
- }).call(this);
3149
-
3150
- (function(){
3151
- "use strict";
3152
-
3153
- var root = this,
3154
- Chart = root.Chart,
3155
- //Cache a local reference to Chart.helpers
3156
- helpers = Chart.helpers;
3157
-
3158
- var defaultConfig = {
3159
- //Boolean - Show a backdrop to the scale label
3160
- scaleShowLabelBackdrop : true,
3161
-
3162
- //String - The colour of the label backdrop
3163
- scaleBackdropColor : "rgba(255,255,255,0.75)",
3164
-
3165
- // Boolean - Whether the scale should begin at zero
3166
- scaleBeginAtZero : true,
3167
-
3168
- //Number - The backdrop padding above & below the label in pixels
3169
- scaleBackdropPaddingY : 2,
3170
-
3171
- //Number - The backdrop padding to the side of the label in pixels
3172
- scaleBackdropPaddingX : 2,
3173
-
3174
- //Boolean - Show line for each value in the scale
3175
- scaleShowLine : true,
3176
-
3177
- //Boolean - Stroke a line around each segment in the chart
3178
- segmentShowStroke : true,
3179
-
3180
- //String - The colour of the stroke on each segment.
3181
- segmentStrokeColor : "rgba(255,255,255,1)",
3182
-
3183
- //Number - The width of the stroke value in pixels
3184
- segmentStrokeWidth : 2,
3185
-
3186
- //Number - Amount of animation steps
3187
- animationSteps : 100,
3188
-
3189
- //String - Animation easing effect.
3190
- animationEasing : "easeOutBounce",
3191
-
3192
- //Boolean - Whether to animate the rotation of the chart
3193
- animateRotate : true,
3194
-
3195
- //Boolean - Whether to animate scaling the chart from the centre
3196
- animateScale : false,
3197
-
3198
- //String - A legend template
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>"
3200
- };
3201
-
3202
-
3203
- Chart.Type.extend({
3204
- //Passing in a name registers this chart in the Chart namespace
3205
- name: "PolarArea",
3206
- //Providing a defaults will also register the deafults in the chart namespace
3207
- defaults : defaultConfig,
3208
- //Initialize is fired when the chart is initialized - Data is passed in as a parameter
3209
- //Config is automatically merged by the core of Chart.js, and is available at this.options
3210
- initialize: function(data){
3211
- this.segments = [];
3212
- //Declare segment class as a chart instance specific class, so it can share props for this instance
3213
- this.SegmentArc = Chart.Arc.extend({
3214
- showStroke : this.options.segmentShowStroke,
3215
- strokeWidth : this.options.segmentStrokeWidth,
3216
- strokeColor : this.options.segmentStrokeColor,
3217
- ctx : this.chart.ctx,
3218
- innerRadius : 0,
3219
- x : this.chart.width/2,
3220
- y : this.chart.height/2
3221
- });
3222
- this.scale = new Chart.RadialScale({
3223
- display: this.options.showScale,
3224
- fontStyle: this.options.scaleFontStyle,
3225
- fontSize: this.options.scaleFontSize,
3226
- fontFamily: this.options.scaleFontFamily,
3227
- fontColor: this.options.scaleFontColor,
3228
- showLabels: this.options.scaleShowLabels,
3229
- showLabelBackdrop: this.options.scaleShowLabelBackdrop,
3230
- backdropColor: this.options.scaleBackdropColor,
3231
- backdropPaddingY : this.options.scaleBackdropPaddingY,
3232
- backdropPaddingX: this.options.scaleBackdropPaddingX,
3233
- lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
3234
- lineColor: this.options.scaleLineColor,
3235
- lineArc: true,
3236
- width: this.chart.width,
3237
- height: this.chart.height,
3238
- xCenter: this.chart.width/2,
3239
- yCenter: this.chart.height/2,
3240
- ctx : this.chart.ctx,
3241
- templateString: this.options.scaleLabel,
3242
- valuesCount: data.length
3243
- });
3244
-
3245
- this.updateScaleRange(data);
3246
-
3247
- this.scale.update();
3248
-
3249
- helpers.each(data,function(segment,index){
3250
- this.addData(segment,index,true);
3251
- },this);
3252
-
3253
- //Set up tooltip events on the chart
3254
- if (this.options.showTooltips){
3255
- helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3256
- var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
3257
- helpers.each(this.segments,function(segment){
3258
- segment.restore(["fillColor"]);
3259
- });
3260
- helpers.each(activeSegments,function(activeSegment){
3261
- activeSegment.fillColor = activeSegment.highlightColor;
3262
- });
3263
- this.showTooltip(activeSegments);
3264
- });
3265
- }
3266
-
3267
- this.render();
3268
- },
3269
- getSegmentsAtEvent : function(e){
3270
- var segmentsArray = [];
3271
-
3272
- var location = helpers.getRelativePosition(e);
3273
-
3274
- helpers.each(this.segments,function(segment){
3275
- if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
3276
- },this);
3277
- return segmentsArray;
3278
- },
3279
- addData : function(segment, atIndex, silent){
3280
- var index = atIndex || this.segments.length;
3281
-
3282
- this.segments.splice(index, 0, new this.SegmentArc({
3283
- fillColor: segment.color,
3284
- highlightColor: segment.highlight || segment.color,
3285
- label: segment.label,
3286
- value: segment.value,
3287
- outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
3288
- circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
3289
- startAngle: Math.PI * 1.5
3290
- }));
3291
- if (!silent){
3292
- this.reflow();
3293
- this.update();
3294
- }
3295
- },
3296
- removeData: function(atIndex){
3297
- var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
3298
- this.segments.splice(indexToDelete, 1);
3299
- this.reflow();
3300
- this.update();
3301
- },
3302
- calculateTotal: function(data){
3303
- this.total = 0;
3304
- helpers.each(data,function(segment){
3305
- this.total += segment.value;
3306
- },this);
3307
- this.scale.valuesCount = this.segments.length;
3308
- },
3309
- updateScaleRange: function(datapoints){
3310
- var valuesArray = [];
3311
- helpers.each(datapoints,function(segment){
3312
- valuesArray.push(segment.value);
3313
- });
3314
-
3315
- var scaleSizes = (this.options.scaleOverride) ?
3316
- {
3317
- steps: this.options.scaleSteps,
3318
- stepValue: this.options.scaleStepWidth,
3319
- min: this.options.scaleStartValue,
3320
- max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3321
- } :
3322
- helpers.calculateScaleRange(
3323
- valuesArray,
3324
- helpers.min([this.chart.width, this.chart.height])/2,
3325
- this.options.scaleFontSize,
3326
- this.options.scaleBeginAtZero,
3327
- this.options.scaleIntegersOnly
3328
- );
3329
-
3330
- helpers.extend(
3331
- this.scale,
3332
- scaleSizes,
3333
- {
3334
- size: helpers.min([this.chart.width, this.chart.height]),
3335
- xCenter: this.chart.width/2,
3336
- yCenter: this.chart.height/2
3337
- }
3338
- );
3339
-
3340
- },
3341
- update : function(){
3342
- this.calculateTotal(this.segments);
3343
-
3344
- helpers.each(this.segments,function(segment){
3345
- segment.save();
3346
- });
3347
-
3348
- this.reflow();
3349
- this.render();
3350
- },
3351
- reflow : function(){
3352
- helpers.extend(this.SegmentArc.prototype,{
3353
- x : this.chart.width/2,
3354
- y : this.chart.height/2
3355
- });
3356
- this.updateScaleRange(this.segments);
3357
- this.scale.update();
3358
-
3359
- helpers.extend(this.scale,{
3360
- xCenter: this.chart.width/2,
3361
- yCenter: this.chart.height/2
3362
- });
3363
-
3364
- helpers.each(this.segments, function(segment){
3365
- segment.update({
3366
- outerRadius : this.scale.calculateCenterOffset(segment.value)
3367
- });
3368
- }, this);
3369
-
3370
- },
3371
- draw : function(ease){
3372
- var easingDecimal = ease || 1;
3373
- //Clear & draw the canvas
3374
- this.clear();
3375
- helpers.each(this.segments,function(segment, index){
3376
- segment.transition({
3377
- circumference : this.scale.getCircumference(),
3378
- outerRadius : this.scale.calculateCenterOffset(segment.value)
3379
- },easingDecimal);
3380
-
3381
- segment.endAngle = segment.startAngle + segment.circumference;
3382
-
3383
- // If we've removed the first segment we need to set the first one to
3384
- // start at the top.
3385
- if (index === 0){
3386
- segment.startAngle = Math.PI * 1.5;
3387
- }
3388
-
3389
- //Check to see if it's the last segment, if not get the next and update the start angle
3390
- if (index < this.segments.length - 1){
3391
- this.segments[index+1].startAngle = segment.endAngle;
3392
- }
3393
- segment.draw();
3394
- }, this);
3395
- this.scale.draw();
3396
- }
3397
- });
3398
-
3399
- }).call(this);
3400
-
3401
- (function(){
3402
- "use strict";
3403
-
3404
- var root = this,
3405
- Chart = root.Chart,
3406
- helpers = Chart.helpers;
3407
-
3408
-
3409
-
3410
- Chart.Type.extend({
3411
- name: "Radar",
3412
- defaults:{
3413
- //Boolean - Whether to show lines for each scale point
3414
- scaleShowLine : true,
3415
-
3416
- //Boolean - Whether we show the angle lines out of the radar
3417
- angleShowLineOut : true,
3418
-
3419
- //Boolean - Whether to show labels on the scale
3420
- scaleShowLabels : false,
3421
-
3422
- // Boolean - Whether the scale should begin at zero
3423
- scaleBeginAtZero : true,
3424
-
3425
- //String - Colour of the angle line
3426
- angleLineColor : "rgba(235,237,239,1)",
3427
-
3428
- //Number - Pixel width of the angle line
3429
- angleLineWidth : 1,
3430
-
3431
- //String - Point label font declaration
3432
- pointLabelFontFamily : "'Gotham Round', 'Gotham', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
3433
-
3434
- //String - Point label font weight
3435
- pointLabelFontStyle : "bold",
3436
-
3437
- //Number - Point label font size in pixels
3438
- pointLabelFontSize : 13,
3439
-
3440
- //String - Point label font colour
3441
- pointLabelFontColor : "rgba(27,30,34,1)",
3442
-
3443
- //Boolean - Whether to show a dot for each point
3444
- pointDot : true,
3445
-
3446
- //Number - Radius of each point dot in pixels
3447
- pointDotRadius : 3,
3448
-
3449
- //Number - Pixel width of point dot stroke
3450
- pointDotStrokeWidth : 2,
3451
-
3452
- //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
3453
- pointHitDetectionRadius : 20,
3454
-
3455
- //Boolean - Whether to show a stroke for datasets
3456
- datasetStroke : true,
3457
-
3458
- //Number - Pixel width of dataset stroke
3459
- datasetStrokeWidth : 2,
3460
-
3461
- //Boolean - Whether to fill the dataset with a colour
3462
- datasetFill : true,
3463
-
3464
- //String - A legend template
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>"
3466
-
3467
- },
3468
-
3469
- initialize: function(data){
3470
- this.PointClass = Chart.Point.extend({
3471
- strokeWidth : this.options.pointDotStrokeWidth,
3472
- radius : this.options.pointDotRadius,
3473
- display: this.options.pointDot,
3474
- hitDetectionRadius : this.options.pointHitDetectionRadius,
3475
- ctx : this.chart.ctx
3476
- });
3477
-
3478
- this.datasets = [];
3479
-
3480
- this.buildScale(data);
3481
-
3482
- //Set up tooltip events on the chart
3483
- if (this.options.showTooltips){
3484
- helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3485
- var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
3486
-
3487
- this.eachPoints(function(point){
3488
- point.restore(['fillColor', 'strokeColor']);
3489
- });
3490
- helpers.each(activePointsCollection, function(activePoint){
3491
- activePoint.fillColor = activePoint.highlightFill;
3492
- activePoint.strokeColor = activePoint.highlightStroke;
3493
- });
3494
-
3495
- this.showTooltip(activePointsCollection);
3496
- });
3497
- }
3498
-
3499
- //Iterate through each of the datasets, and build this into a property of the chart
3500
- helpers.each(data.datasets,function(dataset){
3501
-
3502
- var datasetObject = {
3503
- label: dataset.label || null,
3504
- fillColor : dataset.fillColor,
3505
- strokeColor : dataset.strokeColor,
3506
- pointColor : dataset.pointColor,
3507
- pointStrokeColor : dataset.pointStrokeColor,
3508
- points : []
3509
- };
3510
-
3511
- this.datasets.push(datasetObject);
3512
-
3513
- helpers.each(dataset.data,function(dataPoint,index){
3514
- //Add a new point for each piece of data, passing any required data to draw.
3515
- var pointPosition;
3516
- if (!this.scale.animation){
3517
- pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
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
- }));
3530
- },this);
3531
-
3532
- },this);
3533
-
3534
- this.render();
3535
- },
3536
- eachPoints : function(callback){
3537
- helpers.each(this.datasets,function(dataset){
3538
- helpers.each(dataset.points,callback,this);
3539
- },this);
3540
- },
3541
-
3542
- getPointsAtEvent : function(evt){
3543
- var mousePosition = helpers.getRelativePosition(evt),
3544
- fromCenter = helpers.getAngleFromPoint({
3545
- x: this.scale.xCenter,
3546
- y: this.scale.yCenter
3547
- }, mousePosition);
3548
-
3549
- var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
3550
- pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
3551
- activePointsCollection = [];
3552
-
3553
- // If we're at the top, make the pointIndex 0 to get the first of the array.
3554
- if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
3555
- pointIndex = 0;
3556
- }
3557
-
3558
- if (fromCenter.distance <= this.scale.drawingArea){
3559
- helpers.each(this.datasets, function(dataset){
3560
- activePointsCollection.push(dataset.points[pointIndex]);
3561
- });
3562
- }
3563
-
3564
- return activePointsCollection;
3565
- },
3566
-
3567
- buildScale : function(data){
3568
- this.scale = new Chart.RadialScale({
3569
- display: this.options.showScale,
3570
- fontStyle: this.options.scaleFontStyle,
3571
- fontSize: this.options.scaleFontSize,
3572
- fontFamily: this.options.scaleFontFamily,
3573
- fontColor: this.options.scaleFontColor,
3574
- showLabels: this.options.scaleShowLabels,
3575
- showLabelBackdrop: this.options.scaleShowLabelBackdrop,
3576
- backdropColor: this.options.scaleBackdropColor,
3577
- backgroundColors: this.options.scaleBackgroundColors,
3578
- backdropPaddingY : this.options.scaleBackdropPaddingY,
3579
- backdropPaddingX: this.options.scaleBackdropPaddingX,
3580
- lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
3581
- lineColor: this.options.scaleLineColor,
3582
- angleLineColor : this.options.angleLineColor,
3583
- angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
3584
- // Point labels at the edge of each line
3585
- pointLabelFontColor : this.options.pointLabelFontColor,
3586
- pointLabelFontSize : this.options.pointLabelFontSize,
3587
- pointLabelFontFamily : this.options.pointLabelFontFamily,
3588
- pointLabelFontStyle : this.options.pointLabelFontStyle,
3589
- height : this.chart.height,
3590
- width: this.chart.width,
3591
- xCenter: this.chart.width/2,
3592
- yCenter: this.chart.height/2,
3593
- ctx : this.chart.ctx,
3594
- templateString: this.options.scaleLabel,
3595
- labels: data.labels,
3596
- valuesCount: data.datasets[0].data.length
3597
- });
3598
-
3599
- this.scale.setScaleSize();
3600
- this.updateScaleRange(data.datasets);
3601
- this.scale.buildYLabels();
3602
- },
3603
- updateScaleRange: function(datasets){
3604
- var valuesArray = (function(){
3605
- var totalDataArray = [];
3606
- helpers.each(datasets,function(dataset){
3607
- if (dataset.data){
3608
- totalDataArray = totalDataArray.concat(dataset.data);
3609
- }
3610
- else {
3611
- helpers.each(dataset.points, function(point){
3612
- totalDataArray.push(point.value);
3613
- });
3614
- }
3615
- });
3616
- return totalDataArray;
3617
- })();
3618
-
3619
-
3620
- var scaleSizes = (this.options.scaleOverride) ?
3621
- {
3622
- steps: this.options.scaleSteps,
3623
- stepValue: this.options.scaleStepWidth,
3624
- min: this.options.scaleStartValue,
3625
- max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3626
- } :
3627
- helpers.calculateScaleRange(
3628
- valuesArray,
3629
- helpers.min([this.chart.width, this.chart.height])/2,
3630
- this.options.scaleFontSize,
3631
- this.options.scaleBeginAtZero,
3632
- this.options.scaleIntegersOnly
3633
- );
3634
-
3635
- helpers.extend(
3636
- this.scale,
3637
- scaleSizes
3638
- );
3639
-
3640
- },
3641
- addData : function(valuesArray,label){
3642
- //Map the values array for each of the datasets
3643
- this.scale.valuesCount++;
3644
- helpers.each(valuesArray,function(value,datasetIndex){
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
- }));
3655
- },this);
3656
-
3657
- this.scale.labels.push(label);
3658
-
3659
- this.reflow();
3660
-
3661
- this.update();
3662
- },
3663
- removeData : function(){
3664
- this.scale.valuesCount--;
3665
- this.scale.labels.shift();
3666
- helpers.each(this.datasets,function(dataset){
3667
- dataset.points.shift();
3668
- },this);
3669
- this.reflow();
3670
- this.update();
3671
- },
3672
- update : function(){
3673
- this.eachPoints(function(point){
3674
- point.save();
3675
- });
3676
- this.reflow();
3677
- this.render();
3678
- },
3679
- reflow: function(){
3680
- helpers.extend(this.scale, {
3681
- width : this.chart.width,
3682
- height: this.chart.height,
3683
- size : helpers.min([this.chart.width, this.chart.height]),
3684
- xCenter: this.chart.width/2,
3685
- yCenter: this.chart.height/2
3686
- });
3687
- this.updateScaleRange(this.datasets);
3688
- this.scale.setScaleSize();
3689
- this.scale.buildYLabels();
3690
- },
3691
- draw : function(ease){
3692
- var easeDecimal = ease || 1,
3693
- ctx = this.chart.ctx;
3694
- this.clear();
3695
- this.scale.draw();
3696
-
3697
- helpers.each(this.datasets,function(dataset){
3698
-
3699
- //Transition each point first so that the line and point drawing isn't out of sync
3700
- helpers.each(dataset.points,function(point,index){
3701
- if (point.hasValue()){
3702
- point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
3703
- }
3704
- },this);
3705
-
3706
-
3707
-
3708
- //Draw the line between all the points
3709
- ctx.lineWidth = this.options.datasetStrokeWidth;
3710
- ctx.strokeStyle = dataset.strokeColor;
3711
- ctx.beginPath();
3712
- helpers.each(dataset.points,function(point,index){
3713
- if (index === 0){
3714
- ctx.moveTo(point.x,point.y);
3715
- }
3716
- else{
3717
- ctx.lineTo(point.x,point.y);
3718
- }
3719
- },this);
3720
- ctx.closePath();
3721
- ctx.stroke();
3722
-
3723
- ctx.fillStyle = dataset.fillColor;
3724
- if(this.options.datasetFill){
3725
- ctx.fill();
3726
- }
3727
- //Now draw the points over the line
3728
- //A little inefficient double looping, but better than the line
3729
- //lagging behind the point positions
3730
- helpers.each(dataset.points,function(point){
3731
- if (point.hasValue()){
3732
- point.draw();
3733
- }
3734
- });
3735
-
3736
- },this);
3737
-
3738
- }
3739
-
3740
- });
3741
-
3742
- }).call(this);