adminlte2-rails 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/README.md +19 -0
  4. data/lib/adminlte2/rails/version.rb +1 -1
  5. data/lib/generators/admin_lte_plugins/USAGE +28 -0
  6. data/lib/generators/admin_lte_plugins/admin_lte_plugins_generator.rb +100 -0
  7. data/lib/generators/admin_lte_plugins/templates/ChartJS/Chart.js +3457 -0
  8. data/lib/generators/admin_lte_plugins/templates/Sparkline/jquery.sparkline.js +3054 -0
  9. data/lib/generators/admin_lte_plugins/templates/bootstrap-slider/bootstrap-slider.js +1167 -0
  10. data/lib/generators/admin_lte_plugins/templates/bootstrap-slider/slider.css +169 -0
  11. data/lib/generators/admin_lte_plugins/templates/bootstrap-wysihtml5/bootstrap3-wysihtml5.css +102 -0
  12. data/lib/generators/admin_lte_plugins/templates/bootstrap-wysihtml5/bootstrap3-wysihtml5.js +350 -0
  13. data/lib/generators/admin_lte_plugins/templates/colorpicker/bootstrap-colorpicker.css +214 -0
  14. data/lib/generators/admin_lte_plugins/templates/colorpicker/bootstrap-colorpicker.js +949 -0
  15. data/lib/generators/admin_lte_plugins/templates/colorpicker/img/alpha-horizontal.png +0 -0
  16. data/lib/generators/admin_lte_plugins/templates/colorpicker/img/alpha.png +0 -0
  17. data/lib/generators/admin_lte_plugins/templates/colorpicker/img/hue-horizontal.png +0 -0
  18. data/lib/generators/admin_lte_plugins/templates/colorpicker/img/hue.png +0 -0
  19. data/lib/generators/admin_lte_plugins/templates/colorpicker/img/saturation.png +0 -0
  20. data/lib/generators/admin_lte_plugins/templates/datepicker/bootstrap-datepicker.js +1671 -0
  21. data/lib/generators/admin_lte_plugins/templates/datepicker/datepicker3.css +790 -0
  22. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.ar.js +15 -0
  23. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.az.js +12 -0
  24. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.bg.js +14 -0
  25. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.ca.js +14 -0
  26. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.cs.js +15 -0
  27. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.cy.js +14 -0
  28. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.da.js +15 -0
  29. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.de.js +17 -0
  30. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.el.js +13 -0
  31. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.es.js +14 -0
  32. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.et.js +18 -0
  33. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.fa.js +17 -0
  34. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.fi.js +16 -0
  35. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.fr.js +17 -0
  36. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.gl.js +11 -0
  37. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.he.js +15 -0
  38. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.hr.js +13 -0
  39. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.hu.js +16 -0
  40. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.id.js +15 -0
  41. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.is.js +14 -0
  42. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.it.js +17 -0
  43. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.ja.js +15 -0
  44. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.ka.js +17 -0
  45. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.kk.js +15 -0
  46. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.kr.js +13 -0
  47. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.lt.js +16 -0
  48. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.lv.js +16 -0
  49. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.mk.js +15 -0
  50. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.ms.js +14 -0
  51. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.nb.js +14 -0
  52. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.nl-BE.js +17 -0
  53. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.nl.js +14 -0
  54. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.no.js +16 -0
  55. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.pl.js +15 -0
  56. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.pt-BR.js +15 -0
  57. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.pt.js +16 -0
  58. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.ro.js +16 -0
  59. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.rs-latin.js +14 -0
  60. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.rs.js +14 -0
  61. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.ru.js +15 -0
  62. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.sk.js +15 -0
  63. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.sl.js +14 -0
  64. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.sq.js +15 -0
  65. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.sv.js +16 -0
  66. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.sw.js +15 -0
  67. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.th.js +14 -0
  68. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.tr.js +16 -0
  69. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.ua.js +15 -0
  70. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.vi.js +16 -0
  71. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.zh-CN.js +16 -0
  72. data/lib/generators/admin_lte_plugins/templates/datepicker/locales/bootstrap-datepicker.zh-TW.js +17 -0
  73. data/lib/generators/admin_lte_plugins/templates/daterangepicker/daterangepicker-bs3.css +245 -0
  74. data/lib/generators/admin_lte_plugins/templates/daterangepicker/daterangepicker.js +883 -0
  75. data/lib/generators/admin_lte_plugins/templates/fullcalendar/fullcalendar.css +977 -0
  76. data/lib/generators/admin_lte_plugins/templates/fullcalendar/fullcalendar.js +9732 -0
  77. data/lib/generators/admin_lte_plugins/templates/fullcalendar/fullcalendar.print.css +202 -0
  78. data/lib/generators/admin_lte_plugins/templates/jVectorMap/jquery-jvectormap-1.2.2.css +40 -0
  79. data/lib/generators/admin_lte_plugins/templates/jVectorMap/jquery-jvectormap-1.2.2.min.js +8 -0
  80. data/lib/generators/admin_lte_plugins/templates/jVectorMap/jquery-jvectormap-world-mill-en.js +1 -0
  81. data/lib/generators/admin_lte_plugins/templates/knob/jquery.knob.js +805 -0
  82. data/lib/generators/admin_lte_plugins/templates/morris/morris.css +2 -0
  83. data/lib/generators/admin_lte_plugins/templates/morris/morris.js +1892 -0
  84. data/lib/generators/admin_lte_plugins/templates/pace/pace.js +2 -0
  85. data/lib/generators/admin_lte_plugins/templates/timepicker/bootstrap-timepicker.css +121 -0
  86. data/lib/generators/admin_lte_plugins/templates/timepicker/bootstrap-timepicker.js +903 -0
  87. metadata +83 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b440281ff427a84ff8061d6bad94da20e0f2d8d0
4
- data.tar.gz: 6e4a4ed56adc1d8751fb404afd33c4187658e8a8
3
+ metadata.gz: a59c95f2a105b62eecbc05d5436af6ff1a1e6f24
4
+ data.tar.gz: ea0595f462e7a426ec0134ab616d81feb9fa9043
5
5
  SHA512:
6
- metadata.gz: 6dfbcef05995351eb3762a3acd8f18128233b2959b2c8ac6900763cc1ddb33d6c623676ed7b4af50b3381ba2495256525f3f1a879efef96f51ac03b3ab5d6c78
7
- data.tar.gz: 55eea36dece68a39722a25ab93f0a588886395333b9fff5e7d34636594912dd6e7b2d546d44268ada2f560c61d76246f8122b1b4106425cb698ea7500d3f922b
6
+ metadata.gz: a0a375c02f3930c25c25202c227ff71352ddbd54ae673436d3c529bbc24a4ab70dd6d0e8bb2a1fdd3670149d66dcb8aa6bc42c321d71bb1f1656253089519edb
7
+ data.tar.gz: 2f6f53d1f486db7b2667c3db076e7f704f1ee7cd6782f03d6f0b0a35aeb270f0305329f2887b8ae005ce11059c3d9895c2284a6566b9d4214f1d30ea3483ab05
data/.gitignore CHANGED
@@ -12,3 +12,6 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
+
16
+ .DS_Store
17
+ .idea
data/README.md CHANGED
@@ -18,6 +18,25 @@ Or install it yourself as:
18
18
 
19
19
  $ gem install adminlte2-rails
20
20
 
21
+ ## Plugins
22
+
23
+ # Add a new plugins
24
+
25
+ By default, there's no plugin installed. However, I've created a generator to help you install them
26
+
27
+ $ rails g admin_lte_plugins PLUGIN_NAME
28
+
29
+ # Missing plugins
30
+
31
+ * [ ] ckeditor
32
+ * [ ] datatables
33
+ * [ ] fastclick
34
+ * [ ] flot
35
+ * [ ] iCheck
36
+ * [ ] input-mask
37
+ * [ ] ionslider
38
+ * [ ] slimScroll
39
+
21
40
  ## Usage
22
41
 
23
42
  TODO: Write usage instructions here
@@ -1,5 +1,5 @@
1
1
  module AdminLTE2
2
2
  module Rails
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
4
4
  end
5
5
  end
@@ -0,0 +1,28 @@
1
+ Description:
2
+ Install a specific plugin
3
+
4
+ Plugins Available:
5
+ - jVectorMap (j_vector_map)
6
+ - ChartJS (chart_js)
7
+ - Sparkline (sparkline)
8
+ - Bootstrap Slider (bootstrap_slider)
9
+ - Bootstrap WYSIHTML5 (bootstrap_wysihtml5)
10
+ - Full Calendar (fullcalendar)
11
+ - Knob (knob)
12
+ - TimePicker (timepicker)
13
+ - Pace (pace)
14
+ - MorrisJS (morris)
15
+ - Date Range Picker (daterangepicker)
16
+ - Color Picker (colorpicker)
17
+ - Date Picker (datepicker)
18
+
19
+ Example:
20
+ rails generate admin_lte_plugins colorpicker
21
+
22
+ This will create:
23
+ create vendor/assets/stylesheets/img
24
+ create vendor/assets/stylesheets/img/alpha-horizontal.png
25
+ create vendor/assets/stylesheets/img/alpha.png
26
+ create vendor/assets/stylesheets/img/hue-horizontal.png
27
+ create vendor/assets/stylesheets/img/hue.png
28
+ create vendor/assets/stylesheets/img/saturation.png
@@ -0,0 +1,100 @@
1
+ class AdminLtePluginsGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('../templates', __FILE__)
3
+ argument :plugin_name, type: :string
4
+
5
+ #
6
+ def main
7
+ begin
8
+ send("install_#{plugin_name}")
9
+ rescue NoMethodError => e
10
+ puts "Unknown plugin : '#{plugin_name}'"
11
+ rescue => e
12
+ puts e
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def install_j_vector_map
19
+ add_plugin('jVectorMap', 'css', 'jquery-jvectormap-1.2.2')
20
+ add_plugin('jVectorMap', 'js', 'jquery-jvectormap-1.2.2.min')
21
+ add_plugin('jVectorMap', 'js', 'jquery-jvectormap-world-mill-en')
22
+ end
23
+
24
+ def install_chart_js
25
+ add_plugin('ChartJS', 'js', 'chart')
26
+ end
27
+
28
+ def install_sparkline
29
+ add_plugin('Sparkline', 'js', 'jquery.sparkline')
30
+ end
31
+
32
+ def install_bootstrap_slider
33
+ add_plugin('bootstrap-slider', 'css', 'slider')
34
+ add_plugin('bootstrap-slider', 'js', 'bootstrap-slider')
35
+ end
36
+
37
+ def install_bootstrap_wysihtml5
38
+ add_plugin('bootstrap-wysihtml5', 'css', 'bootstrap3-wysihtml5')
39
+ add_plugin('bootstrap-wysihtml5', 'js', 'bootstrap3-wysihtml5')
40
+ end
41
+
42
+ def install_fullcalendar
43
+ add_plugin('fullcalendar', 'css')
44
+ add_plugin('fullcalendar', 'js')
45
+ end
46
+
47
+ def install_knob
48
+ add_plugin('knob', 'js', 'jquery.knob')
49
+ end
50
+
51
+ def install_timepicker
52
+ add_plugin('timepicker', 'css', 'bootstrap-timepicker')
53
+ add_plugin('timepicker', 'js', 'bootstrap-timepicker')
54
+ end
55
+
56
+ def install_pace
57
+ add_plugin('pace', 'js')
58
+ end
59
+
60
+ def install_morris
61
+ add_plugin('morris', 'js')
62
+ add_plugin('morris', 'css')
63
+ end
64
+
65
+ def install_daterangepicker
66
+ add_plugin('daterangepicker', 'js')
67
+ add_plugin('daterangepicker', 'css', 'daterangepicker-bs3')
68
+ end
69
+
70
+ def install_colorpicker
71
+ add_plugin('colorpicker', 'js', 'bootstrap-colorpicker')
72
+ add_plugin('colorpicker', 'css', 'bootstrap-colorpicker')
73
+
74
+ plugin_directory = File.expand_path('../templates', __FILE__) + '/colorpicker'
75
+ directory "#{plugin_directory}/img", "vendor/assets/stylesheets/img"
76
+ end
77
+
78
+ def install_datepicker
79
+ add_plugin('datepicker', 'js', 'bootstrap-datepicker')
80
+ add_plugin('datepicker', 'css', 'datepicker3')
81
+
82
+ plugin_directory = File.expand_path('../templates', __FILE__) + '/datepicker'
83
+ directory "#{plugin_directory}/locales", 'vendor/assets/javascripts/locales'
84
+ end
85
+
86
+ # ------------------------------ #
87
+
88
+ def add_plugin(plugin_directory, type, plugin_file = nil)
89
+ plugin_file ||= plugin_directory
90
+ plugin_file_with_extension = "#{plugin_file}.#{type}"
91
+
92
+ if type == 'css'
93
+ inject_into_file 'app/assets/stylesheets/application.css', " *= require #{plugin_file}\n", before: ' *= require_self'
94
+ copy_file "#{plugin_directory}/#{plugin_file_with_extension}", "vendor/assets/stylesheets/#{plugin_file_with_extension}"
95
+ else
96
+ inject_into_file 'app/assets/javascripts/application.js', "//= require #{plugin_file}\n", before: '//= require_tree .'
97
+ copy_file "#{plugin_directory}/#{plugin_file_with_extension}", "vendor/assets/javascripts/#{plugin_file_with_extension}"
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,3457 @@
1
+ /*!
2
+ * Chart.js
3
+ * http://chartjs.org/
4
+ * Version: 1.0.1
5
+ *
6
+ * Copyright 2015 Nick Downie
7
+ * Released under the MIT license
8
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
9
+ */
10
+
11
+
12
+ (function(){
13
+
14
+ "use strict";
15
+
16
+ //Declare root variable - window in the browser, global on the server
17
+ var root = this,
18
+ previous = root.Chart;
19
+
20
+ //Occupy the global variable of Chart, and create a simple base class
21
+ var Chart = function(context){
22
+ var chart = this;
23
+ this.canvas = context.canvas;
24
+
25
+ this.ctx = context;
26
+
27
+ //Variables global to the chart
28
+ var width = this.width = context.canvas.width;
29
+ var height = this.height = context.canvas.height;
30
+ this.aspectRatio = this.width / this.height;
31
+ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
32
+ helpers.retinaScale(this);
33
+
34
+ return this;
35
+ };
36
+ //Globally expose the defaults to allow for user updating/changing
37
+ Chart.defaults = {
38
+ global: {
39
+ // Boolean - Whether to animate the chart
40
+ animation: true,
41
+
42
+ // Number - Number of animation steps
43
+ animationSteps: 60,
44
+
45
+ // String - Animation easing effect
46
+ animationEasing: "easeOutQuart",
47
+
48
+ // Boolean - If we should show the scale at all
49
+ showScale: true,
50
+
51
+ // Boolean - If we want to override with a hard coded scale
52
+ scaleOverride: false,
53
+
54
+ // ** Required if scaleOverride is true **
55
+ // Number - The number of steps in a hard coded scale
56
+ scaleSteps: null,
57
+ // Number - The value jump in the hard coded scale
58
+ scaleStepWidth: null,
59
+ // Number - The scale starting value
60
+ scaleStartValue: null,
61
+
62
+ // String - Colour of the scale line
63
+ scaleLineColor: "rgba(0,0,0,.1)",
64
+
65
+ // Number - Pixel width of the scale line
66
+ scaleLineWidth: 1,
67
+
68
+ // Boolean - Whether to show labels on the scale
69
+ scaleShowLabels: true,
70
+
71
+ // Interpolated JS string - can access value
72
+ scaleLabel: "<%=value%>",
73
+
74
+ // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
75
+ scaleIntegersOnly: true,
76
+
77
+ // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
78
+ scaleBeginAtZero: false,
79
+
80
+ // String - Scale label font declaration for the scale label
81
+ scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
82
+
83
+ // Number - Scale label font size in pixels
84
+ scaleFontSize: 12,
85
+
86
+ // String - Scale label font weight style
87
+ scaleFontStyle: "normal",
88
+
89
+ // String - Scale label font colour
90
+ scaleFontColor: "#666",
91
+
92
+ // Boolean - whether or not the chart should be responsive and resize when the browser does.
93
+ responsive: false,
94
+
95
+ // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
96
+ maintainAspectRatio: true,
97
+
98
+ // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
99
+ showTooltips: true,
100
+
101
+ // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
102
+ customTooltips: false,
103
+
104
+ // Array - Array of string names to attach tooltip events
105
+ tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
106
+
107
+ // String - Tooltip background colour
108
+ tooltipFillColor: "rgba(0,0,0,0.8)",
109
+
110
+ // String - Tooltip label font declaration for the scale label
111
+ tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
112
+
113
+ // Number - Tooltip label font size in pixels
114
+ tooltipFontSize: 14,
115
+
116
+ // String - Tooltip font weight style
117
+ tooltipFontStyle: "normal",
118
+
119
+ // String - Tooltip label font colour
120
+ tooltipFontColor: "#fff",
121
+
122
+ // String - Tooltip title font declaration for the scale label
123
+ tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
124
+
125
+ // Number - Tooltip title font size in pixels
126
+ tooltipTitleFontSize: 14,
127
+
128
+ // String - Tooltip title font weight style
129
+ tooltipTitleFontStyle: "bold",
130
+
131
+ // String - Tooltip title font colour
132
+ tooltipTitleFontColor: "#fff",
133
+
134
+ // Number - pixel width of padding around tooltip text
135
+ tooltipYPadding: 6,
136
+
137
+ // Number - pixel width of padding around tooltip text
138
+ tooltipXPadding: 6,
139
+
140
+ // Number - Size of the caret on the tooltip
141
+ tooltipCaretSize: 8,
142
+
143
+ // Number - Pixel radius of the tooltip border
144
+ tooltipCornerRadius: 6,
145
+
146
+ // Number - Pixel offset from point x to tooltip edge
147
+ tooltipXOffset: 10,
148
+
149
+ // String - Template string for single tooltips
150
+ tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
151
+
152
+ // String - Template string for single tooltips
153
+ multiTooltipTemplate: "<%= value %>",
154
+
155
+ // String - Colour behind the legend colour block
156
+ multiTooltipKeyBackground: '#fff',
157
+
158
+ // Function - Will fire on animation progression.
159
+ onAnimationProgress: function(){},
160
+
161
+ // Function - Will fire on animation completion.
162
+ onAnimationComplete: function(){}
163
+
164
+ }
165
+ };
166
+
167
+ //Create a dictionary of chart types, to allow for extension of existing types
168
+ Chart.types = {};
169
+
170
+ //Global Chart helpers object for utility methods and classes
171
+ var helpers = Chart.helpers = {};
172
+
173
+ //-- Basic js utility methods
174
+ var each = helpers.each = function(loopable,callback,self){
175
+ var additionalArgs = Array.prototype.slice.call(arguments, 3);
176
+ // Check to see if null or undefined firstly.
177
+ if (loopable){
178
+ if (loopable.length === +loopable.length){
179
+ var i;
180
+ for (i=0; i<loopable.length; i++){
181
+ callback.apply(self,[loopable[i], i].concat(additionalArgs));
182
+ }
183
+ }
184
+ else{
185
+ for (var item in loopable){
186
+ callback.apply(self,[loopable[item],item].concat(additionalArgs));
187
+ }
188
+ }
189
+ }
190
+ },
191
+ clone = helpers.clone = function(obj){
192
+ var objClone = {};
193
+ each(obj,function(value,key){
194
+ if (obj.hasOwnProperty(key)) objClone[key] = value;
195
+ });
196
+ return objClone;
197
+ },
198
+ extend = helpers.extend = function(base){
199
+ each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
200
+ each(extensionObject,function(value,key){
201
+ if (extensionObject.hasOwnProperty(key)) base[key] = value;
202
+ });
203
+ });
204
+ return base;
205
+ },
206
+ merge = helpers.merge = function(base,master){
207
+ //Merge properties in left object over to a shallow clone of object right.
208
+ var args = Array.prototype.slice.call(arguments,0);
209
+ args.unshift({});
210
+ return extend.apply(null, args);
211
+ },
212
+ indexOf = helpers.indexOf = function(arrayToSearch, item){
213
+ if (Array.prototype.indexOf) {
214
+ return arrayToSearch.indexOf(item);
215
+ }
216
+ else{
217
+ for (var i = 0; i < arrayToSearch.length; i++) {
218
+ if (arrayToSearch[i] === item) return i;
219
+ }
220
+ return -1;
221
+ }
222
+ },
223
+ where = helpers.where = function(collection, filterCallback){
224
+ var filtered = [];
225
+
226
+ helpers.each(collection, function(item){
227
+ if (filterCallback(item)){
228
+ filtered.push(item);
229
+ }
230
+ });
231
+
232
+ return filtered;
233
+ },
234
+ findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
235
+ // Default to start of the array
236
+ if (!startIndex){
237
+ startIndex = -1;
238
+ }
239
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
240
+ var currentItem = arrayToSearch[i];
241
+ if (filterCallback(currentItem)){
242
+ return currentItem;
243
+ }
244
+ }
245
+ },
246
+ findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
247
+ // Default to end of the array
248
+ if (!startIndex){
249
+ startIndex = arrayToSearch.length;
250
+ }
251
+ for (var i = startIndex - 1; i >= 0; i--) {
252
+ var currentItem = arrayToSearch[i];
253
+ if (filterCallback(currentItem)){
254
+ return currentItem;
255
+ }
256
+ }
257
+ },
258
+ inherits = helpers.inherits = function(extensions){
259
+ //Basic javascript inheritance based on the model created in Backbone.js
260
+ var parent = this;
261
+ var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
262
+
263
+ var Surrogate = function(){ this.constructor = ChartElement;};
264
+ Surrogate.prototype = parent.prototype;
265
+ ChartElement.prototype = new Surrogate();
266
+
267
+ ChartElement.extend = inherits;
268
+
269
+ if (extensions) extend(ChartElement.prototype, extensions);
270
+
271
+ ChartElement.__super__ = parent.prototype;
272
+
273
+ return ChartElement;
274
+ },
275
+ noop = helpers.noop = function(){},
276
+ uid = helpers.uid = (function(){
277
+ var id=0;
278
+ return function(){
279
+ return "chart-" + id++;
280
+ };
281
+ })(),
282
+ warn = helpers.warn = function(str){
283
+ //Method for warning of errors
284
+ if (window.console && typeof window.console.warn == "function") console.warn(str);
285
+ },
286
+ amd = helpers.amd = (typeof define == 'function' && define.amd),
287
+ //-- Math methods
288
+ isNumber = helpers.isNumber = function(n){
289
+ return !isNaN(parseFloat(n)) && isFinite(n);
290
+ },
291
+ max = helpers.max = function(array){
292
+ return Math.max.apply( Math, array );
293
+ },
294
+ min = helpers.min = function(array){
295
+ return Math.min.apply( Math, array );
296
+ },
297
+ cap = helpers.cap = function(valueToCap,maxValue,minValue){
298
+ if(isNumber(maxValue)) {
299
+ if( valueToCap > maxValue ) {
300
+ return maxValue;
301
+ }
302
+ }
303
+ else if(isNumber(minValue)){
304
+ if ( valueToCap < minValue ){
305
+ return minValue;
306
+ }
307
+ }
308
+ return valueToCap;
309
+ },
310
+ getDecimalPlaces = helpers.getDecimalPlaces = function(num){
311
+ if (num%1!==0 && isNumber(num)){
312
+ return num.toString().split(".")[1].length;
313
+ }
314
+ else {
315
+ return 0;
316
+ }
317
+ },
318
+ toRadians = helpers.radians = function(degrees){
319
+ return degrees * (Math.PI/180);
320
+ },
321
+ // Gets the angle from vertical upright to the point about a centre.
322
+ getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
323
+ var distanceFromXCenter = anglePoint.x - centrePoint.x,
324
+ distanceFromYCenter = anglePoint.y - centrePoint.y,
325
+ radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
326
+
327
+
328
+ var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
329
+
330
+ //If the segment is in the top left quadrant, we need to add another rotation to the angle
331
+ if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
332
+ angle += Math.PI*2;
333
+ }
334
+
335
+ return {
336
+ angle: angle,
337
+ distance: radialDistanceFromCenter
338
+ };
339
+ },
340
+ aliasPixel = helpers.aliasPixel = function(pixelWidth){
341
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
342
+ },
343
+ splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
344
+ //Props to Rob Spencer at scaled innovation for his post on splining between points
345
+ //http://scaledinnovation.com/analytics/splines/aboutSplines.html
346
+ var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
347
+ d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
348
+ fa=t*d01/(d01+d12),// scaling factor for triangle Ta
349
+ fb=t*d12/(d01+d12);
350
+ return {
351
+ inner : {
352
+ x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
353
+ y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
354
+ },
355
+ outer : {
356
+ x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
357
+ y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
358
+ }
359
+ };
360
+ },
361
+ calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
362
+ return Math.floor(Math.log(val) / Math.LN10);
363
+ },
364
+ calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
365
+
366
+ //Set a minimum step of two - a point at the top of the graph, and a point at the base
367
+ var minSteps = 2,
368
+ maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
369
+ skipFitting = (minSteps >= maxSteps);
370
+
371
+ var maxValue = max(valuesArray),
372
+ minValue = min(valuesArray);
373
+
374
+ // We need some degree of seperation here to calculate the scales if all the values are the same
375
+ // Adding/minusing 0.5 will give us a range of 1.
376
+ if (maxValue === minValue){
377
+ maxValue += 0.5;
378
+ // So we don't end up with a graph with a negative start value if we've said always start from zero
379
+ if (minValue >= 0.5 && !startFromZero){
380
+ minValue -= 0.5;
381
+ }
382
+ else{
383
+ // Make up a whole number above the values
384
+ maxValue += 0.5;
385
+ }
386
+ }
387
+
388
+ var valueRange = Math.abs(maxValue - minValue),
389
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
390
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
391
+ graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
392
+ graphRange = graphMax - graphMin,
393
+ stepValue = Math.pow(10, rangeOrderOfMagnitude),
394
+ numberOfSteps = Math.round(graphRange / stepValue);
395
+
396
+ //If we have more space on the graph we'll use it to give more definition to the data
397
+ while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
398
+ if(numberOfSteps > maxSteps){
399
+ stepValue *=2;
400
+ numberOfSteps = Math.round(graphRange/stepValue);
401
+ // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
402
+ if (numberOfSteps % 1 !== 0){
403
+ skipFitting = true;
404
+ }
405
+ }
406
+ //We can fit in double the amount of scale points on the scale
407
+ else{
408
+ //If user has declared ints only, and the step value isn't a decimal
409
+ if (integersOnly && rangeOrderOfMagnitude >= 0){
410
+ //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
411
+ if(stepValue/2 % 1 === 0){
412
+ stepValue /=2;
413
+ numberOfSteps = Math.round(graphRange/stepValue);
414
+ }
415
+ //If it would make it a float break out of the loop
416
+ else{
417
+ break;
418
+ }
419
+ }
420
+ //If the scale doesn't have to be an int, make the scale more granular anyway.
421
+ else{
422
+ stepValue /=2;
423
+ numberOfSteps = Math.round(graphRange/stepValue);
424
+ }
425
+
426
+ }
427
+ }
428
+
429
+ if (skipFitting){
430
+ numberOfSteps = minSteps;
431
+ stepValue = graphRange / numberOfSteps;
432
+ }
433
+
434
+ return {
435
+ steps : numberOfSteps,
436
+ stepValue : stepValue,
437
+ min : graphMin,
438
+ max : graphMin + (numberOfSteps * stepValue)
439
+ };
440
+
441
+ },
442
+ /* jshint ignore:start */
443
+ // Blows up jshint errors based on the new Function constructor
444
+ //Templating methods
445
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
446
+ template = helpers.template = function(templateString, valuesObject){
447
+
448
+ // If templateString is function rather than string-template - call the function for valuesObject
449
+
450
+ if(templateString instanceof Function){
451
+ return templateString(valuesObject);
452
+ }
453
+
454
+ var cache = {};
455
+ function tmpl(str, data){
456
+ // Figure out if we're getting a template, or if we need to
457
+ // load the template - and be sure to cache the result.
458
+ var fn = !/\W/.test(str) ?
459
+ cache[str] = cache[str] :
460
+
461
+ // Generate a reusable function that will serve as a template
462
+ // generator (and which will be cached).
463
+ new Function("obj",
464
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
465
+
466
+ // Introduce the data as local variables using with(){}
467
+ "with(obj){p.push('" +
468
+
469
+ // Convert the template into pure JavaScript
470
+ str
471
+ .replace(/[\r\t\n]/g, " ")
472
+ .split("<%").join("\t")
473
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
474
+ .replace(/\t=(.*?)%>/g, "',$1,'")
475
+ .split("\t").join("');")
476
+ .split("%>").join("p.push('")
477
+ .split("\r").join("\\'") +
478
+ "');}return p.join('');"
479
+ );
480
+
481
+ // Provide some basic currying to the user
482
+ return data ? fn( data ) : fn;
483
+ }
484
+ return tmpl(templateString,valuesObject);
485
+ },
486
+ /* jshint ignore:end */
487
+ generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
488
+ var labelsArray = new Array(numberOfSteps);
489
+ if (labelTemplateString){
490
+ each(labelsArray,function(val,index){
491
+ labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
492
+ });
493
+ }
494
+ return labelsArray;
495
+ },
496
+ //--Animation methods
497
+ //Easing functions adapted from Robert Penner's easing equations
498
+ //http://www.robertpenner.com/easing/
499
+ easingEffects = helpers.easingEffects = {
500
+ linear: function (t) {
501
+ return t;
502
+ },
503
+ easeInQuad: function (t) {
504
+ return t * t;
505
+ },
506
+ easeOutQuad: function (t) {
507
+ return -1 * t * (t - 2);
508
+ },
509
+ easeInOutQuad: function (t) {
510
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
511
+ return -1 / 2 * ((--t) * (t - 2) - 1);
512
+ },
513
+ easeInCubic: function (t) {
514
+ return t * t * t;
515
+ },
516
+ easeOutCubic: function (t) {
517
+ return 1 * ((t = t / 1 - 1) * t * t + 1);
518
+ },
519
+ easeInOutCubic: function (t) {
520
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
521
+ return 1 / 2 * ((t -= 2) * t * t + 2);
522
+ },
523
+ easeInQuart: function (t) {
524
+ return t * t * t * t;
525
+ },
526
+ easeOutQuart: function (t) {
527
+ return -1 * ((t = t / 1 - 1) * t * t * t - 1);
528
+ },
529
+ easeInOutQuart: function (t) {
530
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
531
+ return -1 / 2 * ((t -= 2) * t * t * t - 2);
532
+ },
533
+ easeInQuint: function (t) {
534
+ return 1 * (t /= 1) * t * t * t * t;
535
+ },
536
+ easeOutQuint: function (t) {
537
+ return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
538
+ },
539
+ easeInOutQuint: function (t) {
540
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
541
+ return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
542
+ },
543
+ easeInSine: function (t) {
544
+ return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
545
+ },
546
+ easeOutSine: function (t) {
547
+ return 1 * Math.sin(t / 1 * (Math.PI / 2));
548
+ },
549
+ easeInOutSine: function (t) {
550
+ return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
551
+ },
552
+ easeInExpo: function (t) {
553
+ return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
554
+ },
555
+ easeOutExpo: function (t) {
556
+ return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
557
+ },
558
+ easeInOutExpo: function (t) {
559
+ if (t === 0) return 0;
560
+ if (t === 1) return 1;
561
+ if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
562
+ return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
563
+ },
564
+ easeInCirc: function (t) {
565
+ if (t >= 1) return t;
566
+ return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
567
+ },
568
+ easeOutCirc: function (t) {
569
+ return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
570
+ },
571
+ easeInOutCirc: function (t) {
572
+ if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
573
+ return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
574
+ },
575
+ easeInElastic: function (t) {
576
+ var s = 1.70158;
577
+ var p = 0;
578
+ var a = 1;
579
+ if (t === 0) return 0;
580
+ if ((t /= 1) == 1) return 1;
581
+ if (!p) p = 1 * 0.3;
582
+ if (a < Math.abs(1)) {
583
+ a = 1;
584
+ s = p / 4;
585
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
586
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
587
+ },
588
+ easeOutElastic: function (t) {
589
+ var s = 1.70158;
590
+ var p = 0;
591
+ var a = 1;
592
+ if (t === 0) return 0;
593
+ if ((t /= 1) == 1) return 1;
594
+ if (!p) p = 1 * 0.3;
595
+ if (a < Math.abs(1)) {
596
+ a = 1;
597
+ s = p / 4;
598
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
599
+ return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
600
+ },
601
+ easeInOutElastic: function (t) {
602
+ var s = 1.70158;
603
+ var p = 0;
604
+ var a = 1;
605
+ if (t === 0) return 0;
606
+ if ((t /= 1 / 2) == 2) return 1;
607
+ if (!p) p = 1 * (0.3 * 1.5);
608
+ if (a < Math.abs(1)) {
609
+ a = 1;
610
+ s = p / 4;
611
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
612
+ if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
613
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
614
+ },
615
+ easeInBack: function (t) {
616
+ var s = 1.70158;
617
+ return 1 * (t /= 1) * t * ((s + 1) * t - s);
618
+ },
619
+ easeOutBack: function (t) {
620
+ var s = 1.70158;
621
+ return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
622
+ },
623
+ easeInOutBack: function (t) {
624
+ var s = 1.70158;
625
+ if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
626
+ return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
627
+ },
628
+ easeInBounce: function (t) {
629
+ return 1 - easingEffects.easeOutBounce(1 - t);
630
+ },
631
+ easeOutBounce: function (t) {
632
+ if ((t /= 1) < (1 / 2.75)) {
633
+ return 1 * (7.5625 * t * t);
634
+ } else if (t < (2 / 2.75)) {
635
+ return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
636
+ } else if (t < (2.5 / 2.75)) {
637
+ return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
638
+ } else {
639
+ return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
640
+ }
641
+ },
642
+ easeInOutBounce: function (t) {
643
+ if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
644
+ return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
645
+ }
646
+ },
647
+ //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
648
+ requestAnimFrame = helpers.requestAnimFrame = (function(){
649
+ return window.requestAnimationFrame ||
650
+ window.webkitRequestAnimationFrame ||
651
+ window.mozRequestAnimationFrame ||
652
+ window.oRequestAnimationFrame ||
653
+ window.msRequestAnimationFrame ||
654
+ function(callback) {
655
+ return window.setTimeout(callback, 1000 / 60);
656
+ };
657
+ })(),
658
+ cancelAnimFrame = helpers.cancelAnimFrame = (function(){
659
+ return window.cancelAnimationFrame ||
660
+ window.webkitCancelAnimationFrame ||
661
+ window.mozCancelAnimationFrame ||
662
+ window.oCancelAnimationFrame ||
663
+ window.msCancelAnimationFrame ||
664
+ function(callback) {
665
+ return window.clearTimeout(callback, 1000 / 60);
666
+ };
667
+ })(),
668
+ animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
669
+
670
+ var currentStep = 0,
671
+ easingFunction = easingEffects[easingString] || easingEffects.linear;
672
+
673
+ var animationFrame = function(){
674
+ currentStep++;
675
+ var stepDecimal = currentStep/totalSteps;
676
+ var easeDecimal = easingFunction(stepDecimal);
677
+
678
+ callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
679
+ onProgress.call(chartInstance,easeDecimal,stepDecimal);
680
+ if (currentStep < totalSteps){
681
+ chartInstance.animationFrame = requestAnimFrame(animationFrame);
682
+ } else{
683
+ onComplete.apply(chartInstance);
684
+ }
685
+ };
686
+ requestAnimFrame(animationFrame);
687
+ },
688
+ //-- DOM methods
689
+ getRelativePosition = helpers.getRelativePosition = function(evt){
690
+ var mouseX, mouseY;
691
+ var e = evt.originalEvent || evt,
692
+ canvas = evt.currentTarget || evt.srcElement,
693
+ boundingRect = canvas.getBoundingClientRect();
694
+
695
+ if (e.touches){
696
+ mouseX = e.touches[0].clientX - boundingRect.left;
697
+ mouseY = e.touches[0].clientY - boundingRect.top;
698
+
699
+ }
700
+ else{
701
+ mouseX = e.clientX - boundingRect.left;
702
+ mouseY = e.clientY - boundingRect.top;
703
+ }
704
+
705
+ return {
706
+ x : mouseX,
707
+ y : mouseY
708
+ };
709
+
710
+ },
711
+ addEvent = helpers.addEvent = function(node,eventType,method){
712
+ if (node.addEventListener){
713
+ node.addEventListener(eventType,method);
714
+ } else if (node.attachEvent){
715
+ node.attachEvent("on"+eventType, method);
716
+ } else {
717
+ node["on"+eventType] = method;
718
+ }
719
+ },
720
+ removeEvent = helpers.removeEvent = function(node, eventType, handler){
721
+ if (node.removeEventListener){
722
+ node.removeEventListener(eventType, handler, false);
723
+ } else if (node.detachEvent){
724
+ node.detachEvent("on"+eventType,handler);
725
+ } else{
726
+ node["on" + eventType] = noop;
727
+ }
728
+ },
729
+ bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
730
+ // Create the events object if it's not already present
731
+ if (!chartInstance.events) chartInstance.events = {};
732
+
733
+ each(arrayOfEvents,function(eventName){
734
+ chartInstance.events[eventName] = function(){
735
+ handler.apply(chartInstance, arguments);
736
+ };
737
+ addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
738
+ });
739
+ },
740
+ unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
741
+ each(arrayOfEvents, function(handler,eventName){
742
+ removeEvent(chartInstance.chart.canvas, eventName, handler);
743
+ });
744
+ },
745
+ getMaximumWidth = helpers.getMaximumWidth = function(domNode){
746
+ var container = domNode.parentNode;
747
+ // TODO = check cross browser stuff with this.
748
+ return container.clientWidth;
749
+ },
750
+ getMaximumHeight = helpers.getMaximumHeight = function(domNode){
751
+ var container = domNode.parentNode;
752
+ // TODO = check cross browser stuff with this.
753
+ return container.clientHeight;
754
+ },
755
+ getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
756
+ retinaScale = helpers.retinaScale = function(chart){
757
+ var ctx = chart.ctx,
758
+ width = chart.canvas.width,
759
+ height = chart.canvas.height;
760
+
761
+ if (window.devicePixelRatio) {
762
+ ctx.canvas.style.width = width + "px";
763
+ ctx.canvas.style.height = height + "px";
764
+ ctx.canvas.height = height * window.devicePixelRatio;
765
+ ctx.canvas.width = width * window.devicePixelRatio;
766
+ ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
767
+ }
768
+ },
769
+ //-- Canvas methods
770
+ clear = helpers.clear = function(chart){
771
+ chart.ctx.clearRect(0,0,chart.width,chart.height);
772
+ },
773
+ fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
774
+ return fontStyle + " " + pixelSize+"px " + fontFamily;
775
+ },
776
+ longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
777
+ ctx.font = font;
778
+ var longest = 0;
779
+ each(arrayOfStrings,function(string){
780
+ var textWidth = ctx.measureText(string).width;
781
+ longest = (textWidth > longest) ? textWidth : longest;
782
+ });
783
+ return longest;
784
+ },
785
+ drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
786
+ ctx.beginPath();
787
+ ctx.moveTo(x + radius, y);
788
+ ctx.lineTo(x + width - radius, y);
789
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
790
+ ctx.lineTo(x + width, y + height - radius);
791
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
792
+ ctx.lineTo(x + radius, y + height);
793
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
794
+ ctx.lineTo(x, y + radius);
795
+ ctx.quadraticCurveTo(x, y, x + radius, y);
796
+ ctx.closePath();
797
+ };
798
+
799
+
800
+ //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
801
+ //Destroy method on the chart will remove the instance of the chart from this reference.
802
+ Chart.instances = {};
803
+
804
+ Chart.Type = function(data,options,chart){
805
+ this.options = options;
806
+ this.chart = chart;
807
+ this.id = uid();
808
+ //Add the chart instance to the global namespace
809
+ Chart.instances[this.id] = this;
810
+
811
+ // Initialize is always called when a chart type is created
812
+ // By default it is a no op, but it should be extended
813
+ if (options.responsive){
814
+ this.resize();
815
+ }
816
+ this.initialize.call(this,data);
817
+ };
818
+
819
+ //Core methods that'll be a part of every chart type
820
+ extend(Chart.Type.prototype,{
821
+ initialize : function(){return this;},
822
+ clear : function(){
823
+ clear(this.chart);
824
+ return this;
825
+ },
826
+ stop : function(){
827
+ // Stops any current animation loop occuring
828
+ helpers.cancelAnimFrame.call(root, this.animationFrame);
829
+ return this;
830
+ },
831
+ resize : function(callback){
832
+ this.stop();
833
+ var canvas = this.chart.canvas,
834
+ newWidth = getMaximumWidth(this.chart.canvas),
835
+ newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
836
+
837
+ canvas.width = this.chart.width = newWidth;
838
+ canvas.height = this.chart.height = newHeight;
839
+
840
+ retinaScale(this.chart);
841
+
842
+ if (typeof callback === "function"){
843
+ callback.apply(this, Array.prototype.slice.call(arguments, 1));
844
+ }
845
+ return this;
846
+ },
847
+ reflow : noop,
848
+ render : function(reflow){
849
+ if (reflow){
850
+ this.reflow();
851
+ }
852
+ if (this.options.animation && !reflow){
853
+ helpers.animationLoop(
854
+ this.draw,
855
+ this.options.animationSteps,
856
+ this.options.animationEasing,
857
+ this.options.onAnimationProgress,
858
+ this.options.onAnimationComplete,
859
+ this
860
+ );
861
+ }
862
+ else{
863
+ this.draw();
864
+ this.options.onAnimationComplete.call(this);
865
+ }
866
+ return this;
867
+ },
868
+ generateLegend : function(){
869
+ return template(this.options.legendTemplate,this);
870
+ },
871
+ destroy : function(){
872
+ this.clear();
873
+ unbindEvents(this, this.events);
874
+ var canvas = this.chart.canvas;
875
+
876
+ // Reset canvas height/width attributes starts a fresh with the canvas context
877
+ canvas.width = this.chart.width;
878
+ canvas.height = this.chart.height;
879
+
880
+ // < IE9 doesn't support removeProperty
881
+ if (canvas.style.removeProperty) {
882
+ canvas.style.removeProperty('width');
883
+ canvas.style.removeProperty('height');
884
+ } else {
885
+ canvas.style.removeAttribute('width');
886
+ canvas.style.removeAttribute('height');
887
+ }
888
+
889
+ delete Chart.instances[this.id];
890
+ },
891
+ showTooltip : function(ChartElements, forceRedraw){
892
+ // Only redraw the chart if we've actually changed what we're hovering on.
893
+ if (typeof this.activeElements === 'undefined') this.activeElements = [];
894
+
895
+ var isChanged = (function(Elements){
896
+ var changed = false;
897
+
898
+ if (Elements.length !== this.activeElements.length){
899
+ changed = true;
900
+ return changed;
901
+ }
902
+
903
+ each(Elements, function(element, index){
904
+ if (element !== this.activeElements[index]){
905
+ changed = true;
906
+ }
907
+ }, this);
908
+ return changed;
909
+ }).call(this, ChartElements);
910
+
911
+ if (!isChanged && !forceRedraw){
912
+ return;
913
+ }
914
+ else{
915
+ this.activeElements = ChartElements;
916
+ }
917
+ this.draw();
918
+ if(this.options.customTooltips){
919
+ this.options.customTooltips(false);
920
+ }
921
+ if (ChartElements.length > 0){
922
+ // If we have multiple datasets, show a MultiTooltip for all of the data points at that index
923
+ if (this.datasets && this.datasets.length > 1) {
924
+ var dataArray,
925
+ dataIndex;
926
+
927
+ for (var i = this.datasets.length - 1; i >= 0; i--) {
928
+ dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
929
+ dataIndex = indexOf(dataArray, ChartElements[0]);
930
+ if (dataIndex !== -1){
931
+ break;
932
+ }
933
+ }
934
+ var tooltipLabels = [],
935
+ tooltipColors = [],
936
+ medianPosition = (function(index) {
937
+
938
+ // Get all the points at that particular index
939
+ var Elements = [],
940
+ dataCollection,
941
+ xPositions = [],
942
+ yPositions = [],
943
+ xMax,
944
+ yMax,
945
+ xMin,
946
+ yMin;
947
+ helpers.each(this.datasets, function(dataset){
948
+ dataCollection = dataset.points || dataset.bars || dataset.segments;
949
+ if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
950
+ Elements.push(dataCollection[dataIndex]);
951
+ }
952
+ });
953
+
954
+ helpers.each(Elements, function(element) {
955
+ xPositions.push(element.x);
956
+ yPositions.push(element.y);
957
+
958
+
959
+ //Include any colour information about the element
960
+ tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
961
+ tooltipColors.push({
962
+ fill: element._saved.fillColor || element.fillColor,
963
+ stroke: element._saved.strokeColor || element.strokeColor
964
+ });
965
+
966
+ }, this);
967
+
968
+ yMin = min(yPositions);
969
+ yMax = max(yPositions);
970
+
971
+ xMin = min(xPositions);
972
+ xMax = max(xPositions);
973
+
974
+ return {
975
+ x: (xMin > this.chart.width/2) ? xMin : xMax,
976
+ y: (yMin + yMax)/2
977
+ };
978
+ }).call(this, dataIndex);
979
+
980
+ new Chart.MultiTooltip({
981
+ x: medianPosition.x,
982
+ y: medianPosition.y,
983
+ xPadding: this.options.tooltipXPadding,
984
+ yPadding: this.options.tooltipYPadding,
985
+ xOffset: this.options.tooltipXOffset,
986
+ fillColor: this.options.tooltipFillColor,
987
+ textColor: this.options.tooltipFontColor,
988
+ fontFamily: this.options.tooltipFontFamily,
989
+ fontStyle: this.options.tooltipFontStyle,
990
+ fontSize: this.options.tooltipFontSize,
991
+ titleTextColor: this.options.tooltipTitleFontColor,
992
+ titleFontFamily: this.options.tooltipTitleFontFamily,
993
+ titleFontStyle: this.options.tooltipTitleFontStyle,
994
+ titleFontSize: this.options.tooltipTitleFontSize,
995
+ cornerRadius: this.options.tooltipCornerRadius,
996
+ labels: tooltipLabels,
997
+ legendColors: tooltipColors,
998
+ legendColorBackground : this.options.multiTooltipKeyBackground,
999
+ title: ChartElements[0].label,
1000
+ chart: this.chart,
1001
+ ctx: this.chart.ctx,
1002
+ custom: this.options.customTooltips
1003
+ }).draw();
1004
+
1005
+ } else {
1006
+ each(ChartElements, function(Element) {
1007
+ var tooltipPosition = Element.tooltipPosition();
1008
+ new Chart.Tooltip({
1009
+ x: Math.round(tooltipPosition.x),
1010
+ y: Math.round(tooltipPosition.y),
1011
+ xPadding: this.options.tooltipXPadding,
1012
+ yPadding: this.options.tooltipYPadding,
1013
+ fillColor: this.options.tooltipFillColor,
1014
+ textColor: this.options.tooltipFontColor,
1015
+ fontFamily: this.options.tooltipFontFamily,
1016
+ fontStyle: this.options.tooltipFontStyle,
1017
+ fontSize: this.options.tooltipFontSize,
1018
+ caretHeight: this.options.tooltipCaretSize,
1019
+ cornerRadius: this.options.tooltipCornerRadius,
1020
+ text: template(this.options.tooltipTemplate, Element),
1021
+ chart: this.chart,
1022
+ custom: this.options.customTooltips
1023
+ }).draw();
1024
+ }, this);
1025
+ }
1026
+ }
1027
+ return this;
1028
+ },
1029
+ toBase64Image : function(){
1030
+ return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
1031
+ }
1032
+ });
1033
+
1034
+ Chart.Type.extend = function(extensions){
1035
+
1036
+ var parent = this;
1037
+
1038
+ var ChartType = function(){
1039
+ return parent.apply(this,arguments);
1040
+ };
1041
+
1042
+ //Copy the prototype object of the this class
1043
+ ChartType.prototype = clone(parent.prototype);
1044
+ //Now overwrite some of the properties in the base class with the new extensions
1045
+ extend(ChartType.prototype, extensions);
1046
+
1047
+ ChartType.extend = Chart.Type.extend;
1048
+
1049
+ if (extensions.name || parent.prototype.name){
1050
+
1051
+ var chartName = extensions.name || parent.prototype.name;
1052
+ //Assign any potential default values of the new chart type
1053
+
1054
+ //If none are defined, we'll use a clone of the chart type this is being extended from.
1055
+ //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
1056
+ //doesn't define some defaults of their own.
1057
+
1058
+ var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
1059
+
1060
+ Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
1061
+
1062
+ Chart.types[chartName] = ChartType;
1063
+
1064
+ //Register this new chart type in the Chart prototype
1065
+ Chart.prototype[chartName] = function(data,options){
1066
+ var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
1067
+ return new ChartType(data,config,this);
1068
+ };
1069
+ } else{
1070
+ warn("Name not provided for this chart, so it hasn't been registered");
1071
+ }
1072
+ return parent;
1073
+ };
1074
+
1075
+ Chart.Element = function(configuration){
1076
+ extend(this,configuration);
1077
+ this.initialize.apply(this,arguments);
1078
+ this.save();
1079
+ };
1080
+ extend(Chart.Element.prototype,{
1081
+ initialize : function(){},
1082
+ restore : function(props){
1083
+ if (!props){
1084
+ extend(this,this._saved);
1085
+ } else {
1086
+ each(props,function(key){
1087
+ this[key] = this._saved[key];
1088
+ },this);
1089
+ }
1090
+ return this;
1091
+ },
1092
+ save : function(){
1093
+ this._saved = clone(this);
1094
+ delete this._saved._saved;
1095
+ return this;
1096
+ },
1097
+ update : function(newProps){
1098
+ each(newProps,function(value,key){
1099
+ this._saved[key] = this[key];
1100
+ this[key] = value;
1101
+ },this);
1102
+ return this;
1103
+ },
1104
+ transition : function(props,ease){
1105
+ each(props,function(value,key){
1106
+ this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
1107
+ },this);
1108
+ return this;
1109
+ },
1110
+ tooltipPosition : function(){
1111
+ return {
1112
+ x : this.x,
1113
+ y : this.y
1114
+ };
1115
+ },
1116
+ hasValue: function(){
1117
+ return isNumber(this.value);
1118
+ }
1119
+ });
1120
+
1121
+ Chart.Element.extend = inherits;
1122
+
1123
+
1124
+ Chart.Point = Chart.Element.extend({
1125
+ display: true,
1126
+ inRange: function(chartX,chartY){
1127
+ var hitDetectionRange = this.hitDetectionRadius + this.radius;
1128
+ return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
1129
+ },
1130
+ draw : function(){
1131
+ if (this.display){
1132
+ var ctx = this.ctx;
1133
+ ctx.beginPath();
1134
+
1135
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
1136
+ ctx.closePath();
1137
+
1138
+ ctx.strokeStyle = this.strokeColor;
1139
+ ctx.lineWidth = this.strokeWidth;
1140
+
1141
+ ctx.fillStyle = this.fillColor;
1142
+
1143
+ ctx.fill();
1144
+ ctx.stroke();
1145
+ }
1146
+
1147
+
1148
+ //Quick debug for bezier curve splining
1149
+ //Highlights control points and the line between them.
1150
+ //Handy for dev - stripped in the min version.
1151
+
1152
+ // ctx.save();
1153
+ // ctx.fillStyle = "black";
1154
+ // ctx.strokeStyle = "black"
1155
+ // ctx.beginPath();
1156
+ // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
1157
+ // ctx.fill();
1158
+
1159
+ // ctx.beginPath();
1160
+ // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
1161
+ // ctx.fill();
1162
+
1163
+ // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
1164
+ // ctx.lineTo(this.x, this.y);
1165
+ // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
1166
+ // ctx.stroke();
1167
+
1168
+ // ctx.restore();
1169
+
1170
+
1171
+
1172
+ }
1173
+ });
1174
+
1175
+ Chart.Arc = Chart.Element.extend({
1176
+ inRange : function(chartX,chartY){
1177
+
1178
+ var pointRelativePosition = helpers.getAngleFromPoint(this, {
1179
+ x: chartX,
1180
+ y: chartY
1181
+ });
1182
+
1183
+ //Check if within the range of the open/close angle
1184
+ var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
1185
+ withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
1186
+
1187
+ return (betweenAngles && withinRadius);
1188
+ //Ensure within the outside of the arc centre, but inside arc outer
1189
+ },
1190
+ tooltipPosition : function(){
1191
+ var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
1192
+ rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
1193
+ return {
1194
+ x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
1195
+ y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
1196
+ };
1197
+ },
1198
+ draw : function(animationPercent){
1199
+
1200
+ var easingDecimal = animationPercent || 1;
1201
+
1202
+ var ctx = this.ctx;
1203
+
1204
+ ctx.beginPath();
1205
+
1206
+ ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
1207
+
1208
+ ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
1209
+
1210
+ ctx.closePath();
1211
+ ctx.strokeStyle = this.strokeColor;
1212
+ ctx.lineWidth = this.strokeWidth;
1213
+
1214
+ ctx.fillStyle = this.fillColor;
1215
+
1216
+ ctx.fill();
1217
+ ctx.lineJoin = 'bevel';
1218
+
1219
+ if (this.showStroke){
1220
+ ctx.stroke();
1221
+ }
1222
+ }
1223
+ });
1224
+
1225
+ Chart.Rectangle = Chart.Element.extend({
1226
+ draw : function(){
1227
+ var ctx = this.ctx,
1228
+ halfWidth = this.width/2,
1229
+ leftX = this.x - halfWidth,
1230
+ rightX = this.x + halfWidth,
1231
+ top = this.base - (this.base - this.y),
1232
+ halfStroke = this.strokeWidth / 2;
1233
+
1234
+ // Canvas doesn't allow us to stroke inside the width so we can
1235
+ // adjust the sizes to fit if we're setting a stroke on the line
1236
+ if (this.showStroke){
1237
+ leftX += halfStroke;
1238
+ rightX -= halfStroke;
1239
+ top += halfStroke;
1240
+ }
1241
+
1242
+ ctx.beginPath();
1243
+
1244
+ ctx.fillStyle = this.fillColor;
1245
+ ctx.strokeStyle = this.strokeColor;
1246
+ ctx.lineWidth = this.strokeWidth;
1247
+
1248
+ // It'd be nice to keep this class totally generic to any rectangle
1249
+ // and simply specify which border to miss out.
1250
+ ctx.moveTo(leftX, this.base);
1251
+ ctx.lineTo(leftX, top);
1252
+ ctx.lineTo(rightX, top);
1253
+ ctx.lineTo(rightX, this.base);
1254
+ ctx.fill();
1255
+ if (this.showStroke){
1256
+ ctx.stroke();
1257
+ }
1258
+ },
1259
+ height : function(){
1260
+ return this.base - this.y;
1261
+ },
1262
+ inRange : function(chartX,chartY){
1263
+ return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
1264
+ }
1265
+ });
1266
+
1267
+ Chart.Tooltip = Chart.Element.extend({
1268
+ draw : function(){
1269
+
1270
+ var ctx = this.chart.ctx;
1271
+
1272
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1273
+
1274
+ this.xAlign = "center";
1275
+ this.yAlign = "above";
1276
+
1277
+ //Distance between the actual element.y position and the start of the tooltip caret
1278
+ var caretPadding = this.caretPadding = 2;
1279
+
1280
+ var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
1281
+ tooltipRectHeight = this.fontSize + 2*this.yPadding,
1282
+ tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
1283
+
1284
+ if (this.x + tooltipWidth/2 >this.chart.width){
1285
+ this.xAlign = "left";
1286
+ } else if (this.x - tooltipWidth/2 < 0){
1287
+ this.xAlign = "right";
1288
+ }
1289
+
1290
+ if (this.y - tooltipHeight < 0){
1291
+ this.yAlign = "below";
1292
+ }
1293
+
1294
+
1295
+ var tooltipX = this.x - tooltipWidth/2,
1296
+ tooltipY = this.y - tooltipHeight;
1297
+
1298
+ ctx.fillStyle = this.fillColor;
1299
+
1300
+ // Custom Tooltips
1301
+ if(this.custom){
1302
+ this.custom(this);
1303
+ }
1304
+ else{
1305
+ switch(this.yAlign)
1306
+ {
1307
+ case "above":
1308
+ //Draw a caret above the x/y
1309
+ ctx.beginPath();
1310
+ ctx.moveTo(this.x,this.y - caretPadding);
1311
+ ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
1312
+ ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
1313
+ ctx.closePath();
1314
+ ctx.fill();
1315
+ break;
1316
+ case "below":
1317
+ tooltipY = this.y + caretPadding + this.caretHeight;
1318
+ //Draw a caret below the x/y
1319
+ ctx.beginPath();
1320
+ ctx.moveTo(this.x, this.y + caretPadding);
1321
+ ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
1322
+ ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
1323
+ ctx.closePath();
1324
+ ctx.fill();
1325
+ break;
1326
+ }
1327
+
1328
+ switch(this.xAlign)
1329
+ {
1330
+ case "left":
1331
+ tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
1332
+ break;
1333
+ case "right":
1334
+ tooltipX = this.x - (this.cornerRadius + this.caretHeight);
1335
+ break;
1336
+ }
1337
+
1338
+ drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
1339
+
1340
+ ctx.fill();
1341
+
1342
+ ctx.fillStyle = this.textColor;
1343
+ ctx.textAlign = "center";
1344
+ ctx.textBaseline = "middle";
1345
+ ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
1346
+ }
1347
+ }
1348
+ });
1349
+
1350
+ Chart.MultiTooltip = Chart.Element.extend({
1351
+ initialize : function(){
1352
+ this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1353
+
1354
+ this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
1355
+
1356
+ this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
1357
+
1358
+ this.ctx.font = this.titleFont;
1359
+
1360
+ var titleWidth = this.ctx.measureText(this.title).width,
1361
+ //Label has a legend square as well so account for this.
1362
+ labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
1363
+ longestTextWidth = max([labelWidth,titleWidth]);
1364
+
1365
+ this.width = longestTextWidth + (this.xPadding*2);
1366
+
1367
+
1368
+ var halfHeight = this.height/2;
1369
+
1370
+ //Check to ensure the height will fit on the canvas
1371
+ //The three is to buffer form the very
1372
+ if (this.y - halfHeight < 0 ){
1373
+ this.y = halfHeight;
1374
+ } else if (this.y + halfHeight > this.chart.height){
1375
+ this.y = this.chart.height - halfHeight;
1376
+ }
1377
+
1378
+ //Decide whether to align left or right based on position on canvas
1379
+ if (this.x > this.chart.width/2){
1380
+ this.x -= this.xOffset + this.width;
1381
+ } else {
1382
+ this.x += this.xOffset;
1383
+ }
1384
+
1385
+
1386
+ },
1387
+ getLineHeight : function(index){
1388
+ var baseLineHeight = this.y - (this.height/2) + this.yPadding,
1389
+ afterTitleIndex = index-1;
1390
+
1391
+ //If the index is zero, we're getting the title
1392
+ if (index === 0){
1393
+ return baseLineHeight + this.titleFontSize/2;
1394
+ } else{
1395
+ return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
1396
+ }
1397
+
1398
+ },
1399
+ draw : function(){
1400
+ // Custom Tooltips
1401
+ if(this.custom){
1402
+ this.custom(this);
1403
+ }
1404
+ else{
1405
+ drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
1406
+ var ctx = this.ctx;
1407
+ ctx.fillStyle = this.fillColor;
1408
+ ctx.fill();
1409
+ ctx.closePath();
1410
+
1411
+ ctx.textAlign = "left";
1412
+ ctx.textBaseline = "middle";
1413
+ ctx.fillStyle = this.titleTextColor;
1414
+ ctx.font = this.titleFont;
1415
+
1416
+ ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
1417
+
1418
+ ctx.font = this.font;
1419
+ helpers.each(this.labels,function(label,index){
1420
+ ctx.fillStyle = this.textColor;
1421
+ ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
1422
+
1423
+ //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
1424
+ //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1425
+ //Instead we'll make a white filled block to put the legendColour palette over.
1426
+
1427
+ ctx.fillStyle = this.legendColorBackground;
1428
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1429
+
1430
+ ctx.fillStyle = this.legendColors[index].fill;
1431
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1432
+
1433
+
1434
+ },this);
1435
+ }
1436
+ }
1437
+ });
1438
+
1439
+ Chart.Scale = Chart.Element.extend({
1440
+ initialize : function(){
1441
+ this.fit();
1442
+ },
1443
+ buildYLabels : function(){
1444
+ this.yLabels = [];
1445
+
1446
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1447
+
1448
+ for (var i=0; i<=this.steps; i++){
1449
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1450
+ }
1451
+ this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
1452
+ },
1453
+ addXLabel : function(label){
1454
+ this.xLabels.push(label);
1455
+ this.valuesCount++;
1456
+ this.fit();
1457
+ },
1458
+ removeXLabel : function(){
1459
+ this.xLabels.shift();
1460
+ this.valuesCount--;
1461
+ this.fit();
1462
+ },
1463
+ // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
1464
+ fit: function(){
1465
+ // First we need the width of the yLabels, assuming the xLabels aren't rotated
1466
+
1467
+ // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
1468
+ this.startPoint = (this.display) ? this.fontSize : 0;
1469
+ this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
1470
+
1471
+ // Apply padding settings to the start and end point.
1472
+ this.startPoint += this.padding;
1473
+ this.endPoint -= this.padding;
1474
+
1475
+ // Cache the starting height, so can determine if we need to recalculate the scale yAxis
1476
+ var cachedHeight = this.endPoint - this.startPoint,
1477
+ cachedYLabelWidth;
1478
+
1479
+ // Build the current yLabels so we have an idea of what size they'll be to start
1480
+ /*
1481
+ * This sets what is returned from calculateScaleRange as static properties of this class:
1482
+ *
1483
+ this.steps;
1484
+ this.stepValue;
1485
+ this.min;
1486
+ this.max;
1487
+ *
1488
+ */
1489
+ this.calculateYRange(cachedHeight);
1490
+
1491
+ // With these properties set we can now build the array of yLabels
1492
+ // and also the width of the largest yLabel
1493
+ this.buildYLabels();
1494
+
1495
+ this.calculateXLabelRotation();
1496
+
1497
+ while((cachedHeight > this.endPoint - this.startPoint)){
1498
+ cachedHeight = this.endPoint - this.startPoint;
1499
+ cachedYLabelWidth = this.yLabelWidth;
1500
+
1501
+ this.calculateYRange(cachedHeight);
1502
+ this.buildYLabels();
1503
+
1504
+ // Only go through the xLabel loop again if the yLabel width has changed
1505
+ if (cachedYLabelWidth < this.yLabelWidth){
1506
+ this.calculateXLabelRotation();
1507
+ }
1508
+ }
1509
+
1510
+ },
1511
+ calculateXLabelRotation : function(){
1512
+ //Get the width of each grid by calculating the difference
1513
+ //between x offsets between 0 and 1.
1514
+
1515
+ this.ctx.font = this.font;
1516
+
1517
+ var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
1518
+ lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
1519
+ firstRotated,
1520
+ lastRotated;
1521
+
1522
+
1523
+ this.xScalePaddingRight = lastWidth/2 + 3;
1524
+ this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
1525
+
1526
+ this.xLabelRotation = 0;
1527
+ if (this.display){
1528
+ var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
1529
+ cosRotation,
1530
+ firstRotatedWidth;
1531
+ this.xLabelWidth = originalLabelWidth;
1532
+ //Allow 3 pixels x2 padding either side for label readability
1533
+ var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
1534
+
1535
+ //Max label rotate should be 90 - also act as a loop counter
1536
+ while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
1537
+ cosRotation = Math.cos(toRadians(this.xLabelRotation));
1538
+
1539
+ firstRotated = cosRotation * firstWidth;
1540
+ lastRotated = cosRotation * lastWidth;
1541
+
1542
+ // We're right aligning the text now.
1543
+ if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
1544
+ this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
1545
+ }
1546
+ this.xScalePaddingRight = this.fontSize/2;
1547
+
1548
+
1549
+ this.xLabelRotation++;
1550
+ this.xLabelWidth = cosRotation * originalLabelWidth;
1551
+
1552
+ }
1553
+ if (this.xLabelRotation > 0){
1554
+ this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
1555
+ }
1556
+ }
1557
+ else{
1558
+ this.xLabelWidth = 0;
1559
+ this.xScalePaddingRight = this.padding;
1560
+ this.xScalePaddingLeft = this.padding;
1561
+ }
1562
+
1563
+ },
1564
+ // Needs to be overidden in each Chart type
1565
+ // Otherwise we need to pass all the data into the scale class
1566
+ calculateYRange: noop,
1567
+ drawingArea: function(){
1568
+ return this.startPoint - this.endPoint;
1569
+ },
1570
+ calculateY : function(value){
1571
+ var scalingFactor = this.drawingArea() / (this.min - this.max);
1572
+ return this.endPoint - (scalingFactor * (value - this.min));
1573
+ },
1574
+ calculateX : function(index){
1575
+ var isRotated = (this.xLabelRotation > 0),
1576
+ // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
1577
+ innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
1578
+ valueWidth = innerWidth/(this.valuesCount - ((this.offsetGridLines) ? 0 : 1)),
1579
+ valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
1580
+
1581
+ if (this.offsetGridLines){
1582
+ valueOffset += (valueWidth/2);
1583
+ }
1584
+
1585
+ return Math.round(valueOffset);
1586
+ },
1587
+ update : function(newProps){
1588
+ helpers.extend(this, newProps);
1589
+ this.fit();
1590
+ },
1591
+ draw : function(){
1592
+ var ctx = this.ctx,
1593
+ yLabelGap = (this.endPoint - this.startPoint) / this.steps,
1594
+ xStart = Math.round(this.xScalePaddingLeft);
1595
+ if (this.display){
1596
+ ctx.fillStyle = this.textColor;
1597
+ ctx.font = this.font;
1598
+ each(this.yLabels,function(labelString,index){
1599
+ var yLabelCenter = this.endPoint - (yLabelGap * index),
1600
+ linePositionY = Math.round(yLabelCenter),
1601
+ drawHorizontalLine = this.showHorizontalLines;
1602
+
1603
+ ctx.textAlign = "right";
1604
+ ctx.textBaseline = "middle";
1605
+ if (this.showLabels){
1606
+ ctx.fillText(labelString,xStart - 10,yLabelCenter);
1607
+ }
1608
+
1609
+ // This is X axis, so draw it
1610
+ if (index === 0 && !drawHorizontalLine){
1611
+ drawHorizontalLine = true;
1612
+ }
1613
+
1614
+ if (drawHorizontalLine){
1615
+ ctx.beginPath();
1616
+ }
1617
+
1618
+ if (index > 0){
1619
+ // This is a grid line in the centre, so drop that
1620
+ ctx.lineWidth = this.gridLineWidth;
1621
+ ctx.strokeStyle = this.gridLineColor;
1622
+ } else {
1623
+ // This is the first line on the scale
1624
+ ctx.lineWidth = this.lineWidth;
1625
+ ctx.strokeStyle = this.lineColor;
1626
+ }
1627
+
1628
+ linePositionY += helpers.aliasPixel(ctx.lineWidth);
1629
+
1630
+ if(drawHorizontalLine){
1631
+ ctx.moveTo(xStart, linePositionY);
1632
+ ctx.lineTo(this.width, linePositionY);
1633
+ ctx.stroke();
1634
+ ctx.closePath();
1635
+ }
1636
+
1637
+ ctx.lineWidth = this.lineWidth;
1638
+ ctx.strokeStyle = this.lineColor;
1639
+ ctx.beginPath();
1640
+ ctx.moveTo(xStart - 5, linePositionY);
1641
+ ctx.lineTo(xStart, linePositionY);
1642
+ ctx.stroke();
1643
+ ctx.closePath();
1644
+
1645
+ },this);
1646
+
1647
+ each(this.xLabels,function(label,index){
1648
+ var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
1649
+ // Check to see if line/bar here and decide where to place the line
1650
+ linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
1651
+ isRotated = (this.xLabelRotation > 0),
1652
+ drawVerticalLine = this.showVerticalLines;
1653
+
1654
+ // This is Y axis, so draw it
1655
+ if (index === 0 && !drawVerticalLine){
1656
+ drawVerticalLine = true;
1657
+ }
1658
+
1659
+ if (drawVerticalLine){
1660
+ ctx.beginPath();
1661
+ }
1662
+
1663
+ if (index > 0){
1664
+ // This is a grid line in the centre, so drop that
1665
+ ctx.lineWidth = this.gridLineWidth;
1666
+ ctx.strokeStyle = this.gridLineColor;
1667
+ } else {
1668
+ // This is the first line on the scale
1669
+ ctx.lineWidth = this.lineWidth;
1670
+ ctx.strokeStyle = this.lineColor;
1671
+ }
1672
+
1673
+ if (drawVerticalLine){
1674
+ ctx.moveTo(linePos,this.endPoint);
1675
+ ctx.lineTo(linePos,this.startPoint - 3);
1676
+ ctx.stroke();
1677
+ ctx.closePath();
1678
+ }
1679
+
1680
+
1681
+ ctx.lineWidth = this.lineWidth;
1682
+ ctx.strokeStyle = this.lineColor;
1683
+
1684
+
1685
+ // Small lines at the bottom of the base grid line
1686
+ ctx.beginPath();
1687
+ ctx.moveTo(linePos,this.endPoint);
1688
+ ctx.lineTo(linePos,this.endPoint + 5);
1689
+ ctx.stroke();
1690
+ ctx.closePath();
1691
+
1692
+ ctx.save();
1693
+ ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
1694
+ ctx.rotate(toRadians(this.xLabelRotation)*-1);
1695
+ ctx.font = this.font;
1696
+ ctx.textAlign = (isRotated) ? "right" : "center";
1697
+ ctx.textBaseline = (isRotated) ? "middle" : "top";
1698
+ ctx.fillText(label, 0, 0);
1699
+ ctx.restore();
1700
+ },this);
1701
+
1702
+ }
1703
+ }
1704
+
1705
+ });
1706
+
1707
+ Chart.RadialScale = Chart.Element.extend({
1708
+ initialize: function(){
1709
+ this.size = min([this.height, this.width]);
1710
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1711
+ },
1712
+ calculateCenterOffset: function(value){
1713
+ // Take into account half font size + the yPadding of the top value
1714
+ var scalingFactor = this.drawingArea / (this.max - this.min);
1715
+
1716
+ return (value - this.min) * scalingFactor;
1717
+ },
1718
+ update : function(){
1719
+ if (!this.lineArc){
1720
+ this.setScaleSize();
1721
+ } else {
1722
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1723
+ }
1724
+ this.buildYLabels();
1725
+ },
1726
+ buildYLabels: function(){
1727
+ this.yLabels = [];
1728
+
1729
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1730
+
1731
+ for (var i=0; i<=this.steps; i++){
1732
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1733
+ }
1734
+ },
1735
+ getCircumference : function(){
1736
+ return ((Math.PI*2) / this.valuesCount);
1737
+ },
1738
+ setScaleSize: function(){
1739
+ /*
1740
+ * Right, this is really confusing and there is a lot of maths going on here
1741
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
1742
+ *
1743
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
1744
+ *
1745
+ * Solution:
1746
+ *
1747
+ * We assume the radius of the polygon is half the size of the canvas at first
1748
+ * at each index we check if the text overlaps.
1749
+ *
1750
+ * Where it does, we store that angle and that index.
1751
+ *
1752
+ * After finding the largest index and angle we calculate how much we need to remove
1753
+ * from the shape radius to move the point inwards by that x.
1754
+ *
1755
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
1756
+ * along with labels.
1757
+ *
1758
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
1759
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
1760
+ *
1761
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
1762
+ * and position it in the most space efficient manner
1763
+ *
1764
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
1765
+ */
1766
+
1767
+
1768
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
1769
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
1770
+ var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
1771
+ pointPosition,
1772
+ i,
1773
+ textWidth,
1774
+ halfTextWidth,
1775
+ furthestRight = this.width,
1776
+ furthestRightIndex,
1777
+ furthestRightAngle,
1778
+ furthestLeft = 0,
1779
+ furthestLeftIndex,
1780
+ furthestLeftAngle,
1781
+ xProtrusionLeft,
1782
+ xProtrusionRight,
1783
+ radiusReductionRight,
1784
+ radiusReductionLeft,
1785
+ maxWidthRadius;
1786
+ this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
1787
+ for (i=0;i<this.valuesCount;i++){
1788
+ // 5px to space the text slightly out - similar to what we do in the draw function.
1789
+ pointPosition = this.getPointPosition(i, largestPossibleRadius);
1790
+ textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
1791
+ if (i === 0 || i === this.valuesCount/2){
1792
+ // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
1793
+ // of the radar chart, so text will be aligned centrally, so we'll half it and compare
1794
+ // w/left and right text sizes
1795
+ halfTextWidth = textWidth/2;
1796
+ if (pointPosition.x + halfTextWidth > furthestRight) {
1797
+ furthestRight = pointPosition.x + halfTextWidth;
1798
+ furthestRightIndex = i;
1799
+ }
1800
+ if (pointPosition.x - halfTextWidth < furthestLeft) {
1801
+ furthestLeft = pointPosition.x - halfTextWidth;
1802
+ furthestLeftIndex = i;
1803
+ }
1804
+ }
1805
+ else if (i < this.valuesCount/2) {
1806
+ // Less than half the values means we'll left align the text
1807
+ if (pointPosition.x + textWidth > furthestRight) {
1808
+ furthestRight = pointPosition.x + textWidth;
1809
+ furthestRightIndex = i;
1810
+ }
1811
+ }
1812
+ else if (i > this.valuesCount/2){
1813
+ // More than half the values means we'll right align the text
1814
+ if (pointPosition.x - textWidth < furthestLeft) {
1815
+ furthestLeft = pointPosition.x - textWidth;
1816
+ furthestLeftIndex = i;
1817
+ }
1818
+ }
1819
+ }
1820
+
1821
+ xProtrusionLeft = furthestLeft;
1822
+
1823
+ xProtrusionRight = Math.ceil(furthestRight - this.width);
1824
+
1825
+ furthestRightAngle = this.getIndexAngle(furthestRightIndex);
1826
+
1827
+ furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
1828
+
1829
+ radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
1830
+
1831
+ radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
1832
+
1833
+ // Ensure we actually need to reduce the size of the chart
1834
+ radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
1835
+ radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
1836
+
1837
+ this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
1838
+
1839
+ //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
1840
+ this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
1841
+
1842
+ },
1843
+ setCenterPoint: function(leftMovement, rightMovement){
1844
+
1845
+ var maxRight = this.width - rightMovement - this.drawingArea,
1846
+ maxLeft = leftMovement + this.drawingArea;
1847
+
1848
+ this.xCenter = (maxLeft + maxRight)/2;
1849
+ // Always vertically in the centre as the text height doesn't change
1850
+ this.yCenter = (this.height/2);
1851
+ },
1852
+
1853
+ getIndexAngle : function(index){
1854
+ var angleMultiplier = (Math.PI * 2) / this.valuesCount;
1855
+ // Start from the top instead of right, so remove a quarter of the circle
1856
+
1857
+ return index * angleMultiplier - (Math.PI/2);
1858
+ },
1859
+ getPointPosition : function(index, distanceFromCenter){
1860
+ var thisAngle = this.getIndexAngle(index);
1861
+ return {
1862
+ x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
1863
+ y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
1864
+ };
1865
+ },
1866
+ draw: function(){
1867
+ if (this.display){
1868
+ var ctx = this.ctx;
1869
+ each(this.yLabels, function(label, index){
1870
+ // Don't draw a centre value
1871
+ if (index > 0){
1872
+ var yCenterOffset = index * (this.drawingArea/this.steps),
1873
+ yHeight = this.yCenter - yCenterOffset,
1874
+ pointPosition;
1875
+
1876
+ // Draw circular lines around the scale
1877
+ if (this.lineWidth > 0){
1878
+ ctx.strokeStyle = this.lineColor;
1879
+ ctx.lineWidth = this.lineWidth;
1880
+
1881
+ if(this.lineArc){
1882
+ ctx.beginPath();
1883
+ ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
1884
+ ctx.closePath();
1885
+ ctx.stroke();
1886
+ } else{
1887
+ ctx.beginPath();
1888
+ for (var i=0;i<this.valuesCount;i++)
1889
+ {
1890
+ pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
1891
+ if (i === 0){
1892
+ ctx.moveTo(pointPosition.x, pointPosition.y);
1893
+ } else {
1894
+ ctx.lineTo(pointPosition.x, pointPosition.y);
1895
+ }
1896
+ }
1897
+ ctx.closePath();
1898
+ ctx.stroke();
1899
+ }
1900
+ }
1901
+ if(this.showLabels){
1902
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1903
+ if (this.showLabelBackdrop){
1904
+ var labelWidth = ctx.measureText(label).width;
1905
+ ctx.fillStyle = this.backdropColor;
1906
+ ctx.fillRect(
1907
+ this.xCenter - labelWidth/2 - this.backdropPaddingX,
1908
+ yHeight - this.fontSize/2 - this.backdropPaddingY,
1909
+ labelWidth + this.backdropPaddingX*2,
1910
+ this.fontSize + this.backdropPaddingY*2
1911
+ );
1912
+ }
1913
+ ctx.textAlign = 'center';
1914
+ ctx.textBaseline = "middle";
1915
+ ctx.fillStyle = this.fontColor;
1916
+ ctx.fillText(label, this.xCenter, yHeight);
1917
+ }
1918
+ }
1919
+ }, this);
1920
+
1921
+ if (!this.lineArc){
1922
+ ctx.lineWidth = this.angleLineWidth;
1923
+ ctx.strokeStyle = this.angleLineColor;
1924
+ for (var i = this.valuesCount - 1; i >= 0; i--) {
1925
+ if (this.angleLineWidth > 0){
1926
+ var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
1927
+ ctx.beginPath();
1928
+ ctx.moveTo(this.xCenter, this.yCenter);
1929
+ ctx.lineTo(outerPosition.x, outerPosition.y);
1930
+ ctx.stroke();
1931
+ ctx.closePath();
1932
+ }
1933
+ // Extra 3px out for some label spacing
1934
+ var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
1935
+ ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
1936
+ ctx.fillStyle = this.pointLabelFontColor;
1937
+
1938
+ var labelsCount = this.labels.length,
1939
+ halfLabelsCount = this.labels.length/2,
1940
+ quarterLabelsCount = halfLabelsCount/2,
1941
+ upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
1942
+ exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
1943
+ if (i === 0){
1944
+ ctx.textAlign = 'center';
1945
+ } else if(i === halfLabelsCount){
1946
+ ctx.textAlign = 'center';
1947
+ } else if (i < halfLabelsCount){
1948
+ ctx.textAlign = 'left';
1949
+ } else {
1950
+ ctx.textAlign = 'right';
1951
+ }
1952
+
1953
+ // Set the correct text baseline based on outer positioning
1954
+ if (exactQuarter){
1955
+ ctx.textBaseline = 'middle';
1956
+ } else if (upperHalf){
1957
+ ctx.textBaseline = 'bottom';
1958
+ } else {
1959
+ ctx.textBaseline = 'top';
1960
+ }
1961
+
1962
+ ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
1963
+ }
1964
+ }
1965
+ }
1966
+ }
1967
+ });
1968
+
1969
+ // Attach global event to resize each chart instance when the browser resizes
1970
+ helpers.addEvent(window, "resize", (function(){
1971
+ // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
1972
+ var timeout;
1973
+ return function(){
1974
+ clearTimeout(timeout);
1975
+ timeout = setTimeout(function(){
1976
+ each(Chart.instances,function(instance){
1977
+ // If the responsive flag is set in the chart instance config
1978
+ // Cascade the resize event down to the chart.
1979
+ if (instance.options.responsive){
1980
+ instance.resize(instance.render, true);
1981
+ }
1982
+ });
1983
+ }, 50);
1984
+ };
1985
+ })());
1986
+
1987
+
1988
+ if (amd) {
1989
+ define(function(){
1990
+ return Chart;
1991
+ });
1992
+ } else if (typeof module === 'object' && module.exports) {
1993
+ module.exports = Chart;
1994
+ }
1995
+
1996
+ root.Chart = Chart;
1997
+
1998
+ Chart.noConflict = function(){
1999
+ root.Chart = previous;
2000
+ return Chart;
2001
+ };
2002
+
2003
+ }).call(this);
2004
+
2005
+ (function(){
2006
+ "use strict";
2007
+
2008
+ var root = this,
2009
+ Chart = root.Chart,
2010
+ helpers = Chart.helpers;
2011
+
2012
+
2013
+ var defaultConfig = {
2014
+ //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
2015
+ scaleBeginAtZero : true,
2016
+
2017
+ //Boolean - Whether grid lines are shown across the chart
2018
+ scaleShowGridLines : true,
2019
+
2020
+ //String - Colour of the grid lines
2021
+ scaleGridLineColor : "rgba(0,0,0,.05)",
2022
+
2023
+ //Number - Width of the grid lines
2024
+ scaleGridLineWidth : 1,
2025
+
2026
+ //Boolean - Whether to show horizontal lines (except X axis)
2027
+ scaleShowHorizontalLines: true,
2028
+
2029
+ //Boolean - Whether to show vertical lines (except Y axis)
2030
+ scaleShowVerticalLines: true,
2031
+
2032
+ //Boolean - If there is a stroke on each bar
2033
+ barShowStroke : true,
2034
+
2035
+ //Number - Pixel width of the bar stroke
2036
+ barStrokeWidth : 2,
2037
+
2038
+ //Number - Spacing between each of the X value sets
2039
+ barValueSpacing : 5,
2040
+
2041
+ //Number - Spacing between data sets within X values
2042
+ barDatasetSpacing : 1,
2043
+
2044
+ //String - A legend template
2045
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
2046
+
2047
+ };
2048
+
2049
+
2050
+ Chart.Type.extend({
2051
+ name: "Bar",
2052
+ defaults : defaultConfig,
2053
+ initialize: function(data){
2054
+
2055
+ //Expose options as a scope variable here so we can access it in the ScaleClass
2056
+ var options = this.options;
2057
+
2058
+ this.ScaleClass = Chart.Scale.extend({
2059
+ offsetGridLines : true,
2060
+ calculateBarX : function(datasetCount, datasetIndex, barIndex){
2061
+ //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
2062
+ var xWidth = this.calculateBaseWidth(),
2063
+ xAbsolute = this.calculateX(barIndex) - (xWidth/2),
2064
+ barWidth = this.calculateBarWidth(datasetCount);
2065
+
2066
+ return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
2067
+ },
2068
+ calculateBaseWidth : function(){
2069
+ return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
2070
+ },
2071
+ calculateBarWidth : function(datasetCount){
2072
+ //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
2073
+ var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
2074
+
2075
+ return (baseWidth / datasetCount);
2076
+ }
2077
+ });
2078
+
2079
+ this.datasets = [];
2080
+
2081
+ //Set up tooltip events on the chart
2082
+ if (this.options.showTooltips){
2083
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2084
+ var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
2085
+
2086
+ this.eachBars(function(bar){
2087
+ bar.restore(['fillColor', 'strokeColor']);
2088
+ });
2089
+ helpers.each(activeBars, function(activeBar){
2090
+ activeBar.fillColor = activeBar.highlightFill;
2091
+ activeBar.strokeColor = activeBar.highlightStroke;
2092
+ });
2093
+ this.showTooltip(activeBars);
2094
+ });
2095
+ }
2096
+
2097
+ //Declare the extension of the default point, to cater for the options passed in to the constructor
2098
+ this.BarClass = Chart.Rectangle.extend({
2099
+ strokeWidth : this.options.barStrokeWidth,
2100
+ showStroke : this.options.barShowStroke,
2101
+ ctx : this.chart.ctx
2102
+ });
2103
+
2104
+ //Iterate through each of the datasets, and build this into a property of the chart
2105
+ helpers.each(data.datasets,function(dataset,datasetIndex){
2106
+
2107
+ var datasetObject = {
2108
+ label : dataset.label || null,
2109
+ fillColor : dataset.fillColor,
2110
+ strokeColor : dataset.strokeColor,
2111
+ bars : []
2112
+ };
2113
+
2114
+ this.datasets.push(datasetObject);
2115
+
2116
+ helpers.each(dataset.data,function(dataPoint,index){
2117
+ //Add a new point for each piece of data, passing any required data to draw.
2118
+ datasetObject.bars.push(new this.BarClass({
2119
+ value : dataPoint,
2120
+ label : data.labels[index],
2121
+ datasetLabel: dataset.label,
2122
+ strokeColor : dataset.strokeColor,
2123
+ fillColor : dataset.fillColor,
2124
+ highlightFill : dataset.highlightFill || dataset.fillColor,
2125
+ highlightStroke : dataset.highlightStroke || dataset.strokeColor
2126
+ }));
2127
+ },this);
2128
+
2129
+ },this);
2130
+
2131
+ this.buildScale(data.labels);
2132
+
2133
+ this.BarClass.prototype.base = this.scale.endPoint;
2134
+
2135
+ this.eachBars(function(bar, index, datasetIndex){
2136
+ helpers.extend(bar, {
2137
+ width : this.scale.calculateBarWidth(this.datasets.length),
2138
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2139
+ y: this.scale.endPoint
2140
+ });
2141
+ bar.save();
2142
+ }, this);
2143
+
2144
+ this.render();
2145
+ },
2146
+ update : function(){
2147
+ this.scale.update();
2148
+ // Reset any highlight colours before updating.
2149
+ helpers.each(this.activeElements, function(activeElement){
2150
+ activeElement.restore(['fillColor', 'strokeColor']);
2151
+ });
2152
+
2153
+ this.eachBars(function(bar){
2154
+ bar.save();
2155
+ });
2156
+ this.render();
2157
+ },
2158
+ eachBars : function(callback){
2159
+ helpers.each(this.datasets,function(dataset, datasetIndex){
2160
+ helpers.each(dataset.bars, callback, this, datasetIndex);
2161
+ },this);
2162
+ },
2163
+ getBarsAtEvent : function(e){
2164
+ var barsArray = [],
2165
+ eventPosition = helpers.getRelativePosition(e),
2166
+ datasetIterator = function(dataset){
2167
+ barsArray.push(dataset.bars[barIndex]);
2168
+ },
2169
+ barIndex;
2170
+
2171
+ for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
2172
+ for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
2173
+ if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
2174
+ helpers.each(this.datasets, datasetIterator);
2175
+ return barsArray;
2176
+ }
2177
+ }
2178
+ }
2179
+
2180
+ return barsArray;
2181
+ },
2182
+ buildScale : function(labels){
2183
+ var self = this;
2184
+
2185
+ var dataTotal = function(){
2186
+ var values = [];
2187
+ self.eachBars(function(bar){
2188
+ values.push(bar.value);
2189
+ });
2190
+ return values;
2191
+ };
2192
+
2193
+ var scaleOptions = {
2194
+ templateString : this.options.scaleLabel,
2195
+ height : this.chart.height,
2196
+ width : this.chart.width,
2197
+ ctx : this.chart.ctx,
2198
+ textColor : this.options.scaleFontColor,
2199
+ fontSize : this.options.scaleFontSize,
2200
+ fontStyle : this.options.scaleFontStyle,
2201
+ fontFamily : this.options.scaleFontFamily,
2202
+ valuesCount : labels.length,
2203
+ beginAtZero : this.options.scaleBeginAtZero,
2204
+ integersOnly : this.options.scaleIntegersOnly,
2205
+ calculateYRange: function(currentHeight){
2206
+ var updatedRanges = helpers.calculateScaleRange(
2207
+ dataTotal(),
2208
+ currentHeight,
2209
+ this.fontSize,
2210
+ this.beginAtZero,
2211
+ this.integersOnly
2212
+ );
2213
+ helpers.extend(this, updatedRanges);
2214
+ },
2215
+ xLabels : labels,
2216
+ font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2217
+ lineWidth : this.options.scaleLineWidth,
2218
+ lineColor : this.options.scaleLineColor,
2219
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
2220
+ showVerticalLines : this.options.scaleShowVerticalLines,
2221
+ gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2222
+ gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2223
+ padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
2224
+ showLabels : this.options.scaleShowLabels,
2225
+ display : this.options.showScale
2226
+ };
2227
+
2228
+ if (this.options.scaleOverride){
2229
+ helpers.extend(scaleOptions, {
2230
+ calculateYRange: helpers.noop,
2231
+ steps: this.options.scaleSteps,
2232
+ stepValue: this.options.scaleStepWidth,
2233
+ min: this.options.scaleStartValue,
2234
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2235
+ });
2236
+ }
2237
+
2238
+ this.scale = new this.ScaleClass(scaleOptions);
2239
+ },
2240
+ addData : function(valuesArray,label){
2241
+ //Map the values array for each of the datasets
2242
+ helpers.each(valuesArray,function(value,datasetIndex){
2243
+ //Add a new point for each piece of data, passing any required data to draw.
2244
+ this.datasets[datasetIndex].bars.push(new this.BarClass({
2245
+ value : value,
2246
+ label : label,
2247
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
2248
+ y: this.scale.endPoint,
2249
+ width : this.scale.calculateBarWidth(this.datasets.length),
2250
+ base : this.scale.endPoint,
2251
+ strokeColor : this.datasets[datasetIndex].strokeColor,
2252
+ fillColor : this.datasets[datasetIndex].fillColor
2253
+ }));
2254
+ },this);
2255
+
2256
+ this.scale.addXLabel(label);
2257
+ //Then re-render the chart.
2258
+ this.update();
2259
+ },
2260
+ removeData : function(){
2261
+ this.scale.removeXLabel();
2262
+ //Then re-render the chart.
2263
+ helpers.each(this.datasets,function(dataset){
2264
+ dataset.bars.shift();
2265
+ },this);
2266
+ this.update();
2267
+ },
2268
+ reflow : function(){
2269
+ helpers.extend(this.BarClass.prototype,{
2270
+ y: this.scale.endPoint,
2271
+ base : this.scale.endPoint
2272
+ });
2273
+ var newScaleProps = helpers.extend({
2274
+ height : this.chart.height,
2275
+ width : this.chart.width
2276
+ });
2277
+ this.scale.update(newScaleProps);
2278
+ },
2279
+ draw : function(ease){
2280
+ var easingDecimal = ease || 1;
2281
+ this.clear();
2282
+
2283
+ var ctx = this.chart.ctx;
2284
+
2285
+ this.scale.draw(easingDecimal);
2286
+
2287
+ //Draw all the bars for each dataset
2288
+ helpers.each(this.datasets,function(dataset,datasetIndex){
2289
+ helpers.each(dataset.bars,function(bar,index){
2290
+ if (bar.hasValue()){
2291
+ bar.base = this.scale.endPoint;
2292
+ //Transition then draw
2293
+ bar.transition({
2294
+ x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2295
+ y : this.scale.calculateY(bar.value),
2296
+ width : this.scale.calculateBarWidth(this.datasets.length)
2297
+ }, easingDecimal).draw();
2298
+ }
2299
+ },this);
2300
+
2301
+ },this);
2302
+ }
2303
+ });
2304
+
2305
+
2306
+ }).call(this);
2307
+
2308
+ (function(){
2309
+ "use strict";
2310
+
2311
+ var root = this,
2312
+ Chart = root.Chart,
2313
+ //Cache a local reference to Chart.helpers
2314
+ helpers = Chart.helpers;
2315
+
2316
+ var defaultConfig = {
2317
+ //Boolean - Whether we should show a stroke on each segment
2318
+ segmentShowStroke : true,
2319
+
2320
+ //String - The colour of each segment stroke
2321
+ segmentStrokeColor : "#fff",
2322
+
2323
+ //Number - The width of each segment stroke
2324
+ segmentStrokeWidth : 2,
2325
+
2326
+ //The percentage of the chart that we cut out of the middle.
2327
+ percentageInnerCutout : 50,
2328
+
2329
+ //Number - Amount of animation steps
2330
+ animationSteps : 100,
2331
+
2332
+ //String - Animation easing effect
2333
+ animationEasing : "easeOutBounce",
2334
+
2335
+ //Boolean - Whether we animate the rotation of the Doughnut
2336
+ animateRotate : true,
2337
+
2338
+ //Boolean - Whether we animate scaling the Doughnut from the centre
2339
+ animateScale : false,
2340
+
2341
+ //String - A legend template
2342
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
2343
+
2344
+ };
2345
+
2346
+
2347
+ Chart.Type.extend({
2348
+ //Passing in a name registers this chart in the Chart namespace
2349
+ name: "Doughnut",
2350
+ //Providing a defaults will also register the deafults in the chart namespace
2351
+ defaults : defaultConfig,
2352
+ //Initialize is fired when the chart is initialized - Data is passed in as a parameter
2353
+ //Config is automatically merged by the core of Chart.js, and is available at this.options
2354
+ initialize: function(data){
2355
+
2356
+ //Declare segments as a static property to prevent inheriting across the Chart type prototype
2357
+ this.segments = [];
2358
+ this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
2359
+
2360
+ this.SegmentArc = Chart.Arc.extend({
2361
+ ctx : this.chart.ctx,
2362
+ x : this.chart.width/2,
2363
+ y : this.chart.height/2
2364
+ });
2365
+
2366
+ //Set up tooltip events on the chart
2367
+ if (this.options.showTooltips){
2368
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2369
+ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
2370
+
2371
+ helpers.each(this.segments,function(segment){
2372
+ segment.restore(["fillColor"]);
2373
+ });
2374
+ helpers.each(activeSegments,function(activeSegment){
2375
+ activeSegment.fillColor = activeSegment.highlightColor;
2376
+ });
2377
+ this.showTooltip(activeSegments);
2378
+ });
2379
+ }
2380
+ this.calculateTotal(data);
2381
+
2382
+ helpers.each(data,function(datapoint, index){
2383
+ this.addData(datapoint, index, true);
2384
+ },this);
2385
+
2386
+ this.render();
2387
+ },
2388
+ getSegmentsAtEvent : function(e){
2389
+ var segmentsArray = [];
2390
+
2391
+ var location = helpers.getRelativePosition(e);
2392
+
2393
+ helpers.each(this.segments,function(segment){
2394
+ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
2395
+ },this);
2396
+ return segmentsArray;
2397
+ },
2398
+ addData : function(segment, atIndex, silent){
2399
+ var index = atIndex || this.segments.length;
2400
+ this.segments.splice(index, 0, new this.SegmentArc({
2401
+ value : segment.value,
2402
+ outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
2403
+ innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
2404
+ fillColor : segment.color,
2405
+ highlightColor : segment.highlight || segment.color,
2406
+ showStroke : this.options.segmentShowStroke,
2407
+ strokeWidth : this.options.segmentStrokeWidth,
2408
+ strokeColor : this.options.segmentStrokeColor,
2409
+ startAngle : Math.PI * 1.5,
2410
+ circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
2411
+ label : segment.label
2412
+ }));
2413
+ if (!silent){
2414
+ this.reflow();
2415
+ this.update();
2416
+ }
2417
+ },
2418
+ calculateCircumference : function(value){
2419
+ return (Math.PI*2)*(value / this.total);
2420
+ },
2421
+ calculateTotal : function(data){
2422
+ this.total = 0;
2423
+ helpers.each(data,function(segment){
2424
+ this.total += segment.value;
2425
+ },this);
2426
+ },
2427
+ update : function(){
2428
+ this.calculateTotal(this.segments);
2429
+
2430
+ // Reset any highlight colours before updating.
2431
+ helpers.each(this.activeElements, function(activeElement){
2432
+ activeElement.restore(['fillColor']);
2433
+ });
2434
+
2435
+ helpers.each(this.segments,function(segment){
2436
+ segment.save();
2437
+ });
2438
+ this.render();
2439
+ },
2440
+
2441
+ removeData: function(atIndex){
2442
+ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
2443
+ this.segments.splice(indexToDelete, 1);
2444
+ this.reflow();
2445
+ this.update();
2446
+ },
2447
+
2448
+ reflow : function(){
2449
+ helpers.extend(this.SegmentArc.prototype,{
2450
+ x : this.chart.width/2,
2451
+ y : this.chart.height/2
2452
+ });
2453
+ this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
2454
+ helpers.each(this.segments, function(segment){
2455
+ segment.update({
2456
+ outerRadius : this.outerRadius,
2457
+ innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
2458
+ });
2459
+ }, this);
2460
+ },
2461
+ draw : function(easeDecimal){
2462
+ var animDecimal = (easeDecimal) ? easeDecimal : 1;
2463
+ this.clear();
2464
+ helpers.each(this.segments,function(segment,index){
2465
+ segment.transition({
2466
+ circumference : this.calculateCircumference(segment.value),
2467
+ outerRadius : this.outerRadius,
2468
+ innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
2469
+ },animDecimal);
2470
+
2471
+ segment.endAngle = segment.startAngle + segment.circumference;
2472
+
2473
+ segment.draw();
2474
+ if (index === 0){
2475
+ segment.startAngle = Math.PI * 1.5;
2476
+ }
2477
+ //Check to see if it's the last segment, if not get the next and update the start angle
2478
+ if (index < this.segments.length-1){
2479
+ this.segments[index+1].startAngle = segment.endAngle;
2480
+ }
2481
+ },this);
2482
+
2483
+ }
2484
+ });
2485
+
2486
+ Chart.types.Doughnut.extend({
2487
+ name : "Pie",
2488
+ defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
2489
+ });
2490
+
2491
+ }).call(this);
2492
+ (function(){
2493
+ "use strict";
2494
+
2495
+ var root = this,
2496
+ Chart = root.Chart,
2497
+ helpers = Chart.helpers;
2498
+
2499
+ var defaultConfig = {
2500
+
2501
+ ///Boolean - Whether grid lines are shown across the chart
2502
+ scaleShowGridLines : true,
2503
+
2504
+ //String - Colour of the grid lines
2505
+ scaleGridLineColor : "rgba(0,0,0,.05)",
2506
+
2507
+ //Number - Width of the grid lines
2508
+ scaleGridLineWidth : 1,
2509
+
2510
+ //Boolean - Whether to show horizontal lines (except X axis)
2511
+ scaleShowHorizontalLines: true,
2512
+
2513
+ //Boolean - Whether to show vertical lines (except Y axis)
2514
+ scaleShowVerticalLines: true,
2515
+
2516
+ //Boolean - Whether the line is curved between points
2517
+ bezierCurve : true,
2518
+
2519
+ //Number - Tension of the bezier curve between points
2520
+ bezierCurveTension : 0.4,
2521
+
2522
+ //Boolean - Whether to show a dot for each point
2523
+ pointDot : true,
2524
+
2525
+ //Number - Radius of each point dot in pixels
2526
+ pointDotRadius : 4,
2527
+
2528
+ //Number - Pixel width of point dot stroke
2529
+ pointDotStrokeWidth : 1,
2530
+
2531
+ //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
2532
+ pointHitDetectionRadius : 20,
2533
+
2534
+ //Boolean - Whether to show a stroke for datasets
2535
+ datasetStroke : true,
2536
+
2537
+ //Number - Pixel width of dataset stroke
2538
+ datasetStrokeWidth : 2,
2539
+
2540
+ //Boolean - Whether to fill the dataset with a colour
2541
+ datasetFill : true,
2542
+
2543
+ //String - A legend template
2544
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
2545
+
2546
+ };
2547
+
2548
+
2549
+ Chart.Type.extend({
2550
+ name: "Line",
2551
+ defaults : defaultConfig,
2552
+ initialize: function(data){
2553
+ //Declare the extension of the default point, to cater for the options passed in to the constructor
2554
+ this.PointClass = Chart.Point.extend({
2555
+ strokeWidth : this.options.pointDotStrokeWidth,
2556
+ radius : this.options.pointDotRadius,
2557
+ display: this.options.pointDot,
2558
+ hitDetectionRadius : this.options.pointHitDetectionRadius,
2559
+ ctx : this.chart.ctx,
2560
+ inRange : function(mouseX){
2561
+ return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
2562
+ }
2563
+ });
2564
+
2565
+ this.datasets = [];
2566
+
2567
+ //Set up tooltip events on the chart
2568
+ if (this.options.showTooltips){
2569
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2570
+ var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
2571
+ this.eachPoints(function(point){
2572
+ point.restore(['fillColor', 'strokeColor']);
2573
+ });
2574
+ helpers.each(activePoints, function(activePoint){
2575
+ activePoint.fillColor = activePoint.highlightFill;
2576
+ activePoint.strokeColor = activePoint.highlightStroke;
2577
+ });
2578
+ this.showTooltip(activePoints);
2579
+ });
2580
+ }
2581
+
2582
+ //Iterate through each of the datasets, and build this into a property of the chart
2583
+ helpers.each(data.datasets,function(dataset){
2584
+
2585
+ var datasetObject = {
2586
+ label : dataset.label || null,
2587
+ fillColor : dataset.fillColor,
2588
+ strokeColor : dataset.strokeColor,
2589
+ pointColor : dataset.pointColor,
2590
+ pointStrokeColor : dataset.pointStrokeColor,
2591
+ points : []
2592
+ };
2593
+
2594
+ this.datasets.push(datasetObject);
2595
+
2596
+
2597
+ helpers.each(dataset.data,function(dataPoint,index){
2598
+ //Add a new point for each piece of data, passing any required data to draw.
2599
+ datasetObject.points.push(new this.PointClass({
2600
+ value : dataPoint,
2601
+ label : data.labels[index],
2602
+ datasetLabel: dataset.label,
2603
+ strokeColor : dataset.pointStrokeColor,
2604
+ fillColor : dataset.pointColor,
2605
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
2606
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
2607
+ }));
2608
+ },this);
2609
+
2610
+ this.buildScale(data.labels);
2611
+
2612
+
2613
+ this.eachPoints(function(point, index){
2614
+ helpers.extend(point, {
2615
+ x: this.scale.calculateX(index),
2616
+ y: this.scale.endPoint
2617
+ });
2618
+ point.save();
2619
+ }, this);
2620
+
2621
+ },this);
2622
+
2623
+
2624
+ this.render();
2625
+ },
2626
+ update : function(){
2627
+ this.scale.update();
2628
+ // Reset any highlight colours before updating.
2629
+ helpers.each(this.activeElements, function(activeElement){
2630
+ activeElement.restore(['fillColor', 'strokeColor']);
2631
+ });
2632
+ this.eachPoints(function(point){
2633
+ point.save();
2634
+ });
2635
+ this.render();
2636
+ },
2637
+ eachPoints : function(callback){
2638
+ helpers.each(this.datasets,function(dataset){
2639
+ helpers.each(dataset.points,callback,this);
2640
+ },this);
2641
+ },
2642
+ getPointsAtEvent : function(e){
2643
+ var pointsArray = [],
2644
+ eventPosition = helpers.getRelativePosition(e);
2645
+ helpers.each(this.datasets,function(dataset){
2646
+ helpers.each(dataset.points,function(point){
2647
+ if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
2648
+ });
2649
+ },this);
2650
+ return pointsArray;
2651
+ },
2652
+ buildScale : function(labels){
2653
+ var self = this;
2654
+
2655
+ var dataTotal = function(){
2656
+ var values = [];
2657
+ self.eachPoints(function(point){
2658
+ values.push(point.value);
2659
+ });
2660
+
2661
+ return values;
2662
+ };
2663
+
2664
+ var scaleOptions = {
2665
+ templateString : this.options.scaleLabel,
2666
+ height : this.chart.height,
2667
+ width : this.chart.width,
2668
+ ctx : this.chart.ctx,
2669
+ textColor : this.options.scaleFontColor,
2670
+ fontSize : this.options.scaleFontSize,
2671
+ fontStyle : this.options.scaleFontStyle,
2672
+ fontFamily : this.options.scaleFontFamily,
2673
+ valuesCount : labels.length,
2674
+ beginAtZero : this.options.scaleBeginAtZero,
2675
+ integersOnly : this.options.scaleIntegersOnly,
2676
+ calculateYRange : function(currentHeight){
2677
+ var updatedRanges = helpers.calculateScaleRange(
2678
+ dataTotal(),
2679
+ currentHeight,
2680
+ this.fontSize,
2681
+ this.beginAtZero,
2682
+ this.integersOnly
2683
+ );
2684
+ helpers.extend(this, updatedRanges);
2685
+ },
2686
+ xLabels : labels,
2687
+ font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2688
+ lineWidth : this.options.scaleLineWidth,
2689
+ lineColor : this.options.scaleLineColor,
2690
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
2691
+ showVerticalLines : this.options.scaleShowVerticalLines,
2692
+ gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2693
+ gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2694
+ padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
2695
+ showLabels : this.options.scaleShowLabels,
2696
+ display : this.options.showScale
2697
+ };
2698
+
2699
+ if (this.options.scaleOverride){
2700
+ helpers.extend(scaleOptions, {
2701
+ calculateYRange: helpers.noop,
2702
+ steps: this.options.scaleSteps,
2703
+ stepValue: this.options.scaleStepWidth,
2704
+ min: this.options.scaleStartValue,
2705
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2706
+ });
2707
+ }
2708
+
2709
+
2710
+ this.scale = new Chart.Scale(scaleOptions);
2711
+ },
2712
+ addData : function(valuesArray,label){
2713
+ //Map the values array for each of the datasets
2714
+
2715
+ helpers.each(valuesArray,function(value,datasetIndex){
2716
+ //Add a new point for each piece of data, passing any required data to draw.
2717
+ this.datasets[datasetIndex].points.push(new this.PointClass({
2718
+ value : value,
2719
+ label : label,
2720
+ x: this.scale.calculateX(this.scale.valuesCount+1),
2721
+ y: this.scale.endPoint,
2722
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
2723
+ fillColor : this.datasets[datasetIndex].pointColor
2724
+ }));
2725
+ },this);
2726
+
2727
+ this.scale.addXLabel(label);
2728
+ //Then re-render the chart.
2729
+ this.update();
2730
+ },
2731
+ removeData : function(){
2732
+ this.scale.removeXLabel();
2733
+ //Then re-render the chart.
2734
+ helpers.each(this.datasets,function(dataset){
2735
+ dataset.points.shift();
2736
+ },this);
2737
+ this.update();
2738
+ },
2739
+ reflow : function(){
2740
+ var newScaleProps = helpers.extend({
2741
+ height : this.chart.height,
2742
+ width : this.chart.width
2743
+ });
2744
+ this.scale.update(newScaleProps);
2745
+ },
2746
+ draw : function(ease){
2747
+ var easingDecimal = ease || 1;
2748
+ this.clear();
2749
+
2750
+ var ctx = this.chart.ctx;
2751
+
2752
+ // Some helper methods for getting the next/prev points
2753
+ var hasValue = function(item){
2754
+ return item.value !== null;
2755
+ },
2756
+ nextPoint = function(point, collection, index){
2757
+ return helpers.findNextWhere(collection, hasValue, index) || point;
2758
+ },
2759
+ previousPoint = function(point, collection, index){
2760
+ return helpers.findPreviousWhere(collection, hasValue, index) || point;
2761
+ };
2762
+
2763
+ this.scale.draw(easingDecimal);
2764
+
2765
+
2766
+ helpers.each(this.datasets,function(dataset){
2767
+ var pointsWithValues = helpers.where(dataset.points, hasValue);
2768
+
2769
+ //Transition each point first so that the line and point drawing isn't out of sync
2770
+ //We can use this extra loop to calculate the control points of this dataset also in this loop
2771
+
2772
+ helpers.each(dataset.points, function(point, index){
2773
+ if (point.hasValue()){
2774
+ point.transition({
2775
+ y : this.scale.calculateY(point.value),
2776
+ x : this.scale.calculateX(index)
2777
+ }, easingDecimal);
2778
+ }
2779
+ },this);
2780
+
2781
+
2782
+ // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
2783
+ // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
2784
+ if (this.options.bezierCurve){
2785
+ helpers.each(pointsWithValues, function(point, index){
2786
+ var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
2787
+ point.controlPoints = helpers.splineCurve(
2788
+ previousPoint(point, pointsWithValues, index),
2789
+ point,
2790
+ nextPoint(point, pointsWithValues, index),
2791
+ tension
2792
+ );
2793
+
2794
+ // Prevent the bezier going outside of the bounds of the graph
2795
+
2796
+ // Cap puter bezier handles to the upper/lower scale bounds
2797
+ if (point.controlPoints.outer.y > this.scale.endPoint){
2798
+ point.controlPoints.outer.y = this.scale.endPoint;
2799
+ }
2800
+ else if (point.controlPoints.outer.y < this.scale.startPoint){
2801
+ point.controlPoints.outer.y = this.scale.startPoint;
2802
+ }
2803
+
2804
+ // Cap inner bezier handles to the upper/lower scale bounds
2805
+ if (point.controlPoints.inner.y > this.scale.endPoint){
2806
+ point.controlPoints.inner.y = this.scale.endPoint;
2807
+ }
2808
+ else if (point.controlPoints.inner.y < this.scale.startPoint){
2809
+ point.controlPoints.inner.y = this.scale.startPoint;
2810
+ }
2811
+ },this);
2812
+ }
2813
+
2814
+
2815
+ //Draw the line between all the points
2816
+ ctx.lineWidth = this.options.datasetStrokeWidth;
2817
+ ctx.strokeStyle = dataset.strokeColor;
2818
+ ctx.beginPath();
2819
+
2820
+ helpers.each(pointsWithValues, function(point, index){
2821
+ if (index === 0){
2822
+ ctx.moveTo(point.x, point.y);
2823
+ }
2824
+ else{
2825
+ if(this.options.bezierCurve){
2826
+ var previous = previousPoint(point, pointsWithValues, index);
2827
+
2828
+ ctx.bezierCurveTo(
2829
+ previous.controlPoints.outer.x,
2830
+ previous.controlPoints.outer.y,
2831
+ point.controlPoints.inner.x,
2832
+ point.controlPoints.inner.y,
2833
+ point.x,
2834
+ point.y
2835
+ );
2836
+ }
2837
+ else{
2838
+ ctx.lineTo(point.x,point.y);
2839
+ }
2840
+ }
2841
+ }, this);
2842
+
2843
+ ctx.stroke();
2844
+
2845
+ if (this.options.datasetFill && pointsWithValues.length > 0){
2846
+ //Round off the line by going to the base of the chart, back to the start, then fill.
2847
+ ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
2848
+ ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
2849
+ ctx.fillStyle = dataset.fillColor;
2850
+ ctx.closePath();
2851
+ ctx.fill();
2852
+ }
2853
+
2854
+ //Now draw the points over the line
2855
+ //A little inefficient double looping, but better than the line
2856
+ //lagging behind the point positions
2857
+ helpers.each(pointsWithValues,function(point){
2858
+ point.draw();
2859
+ });
2860
+ },this);
2861
+ }
2862
+ });
2863
+
2864
+
2865
+ }).call(this);
2866
+
2867
+ (function(){
2868
+ "use strict";
2869
+
2870
+ var root = this,
2871
+ Chart = root.Chart,
2872
+ //Cache a local reference to Chart.helpers
2873
+ helpers = Chart.helpers;
2874
+
2875
+ var defaultConfig = {
2876
+ //Boolean - Show a backdrop to the scale label
2877
+ scaleShowLabelBackdrop : true,
2878
+
2879
+ //String - The colour of the label backdrop
2880
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
2881
+
2882
+ // Boolean - Whether the scale should begin at zero
2883
+ scaleBeginAtZero : true,
2884
+
2885
+ //Number - The backdrop padding above & below the label in pixels
2886
+ scaleBackdropPaddingY : 2,
2887
+
2888
+ //Number - The backdrop padding to the side of the label in pixels
2889
+ scaleBackdropPaddingX : 2,
2890
+
2891
+ //Boolean - Show line for each value in the scale
2892
+ scaleShowLine : true,
2893
+
2894
+ //Boolean - Stroke a line around each segment in the chart
2895
+ segmentShowStroke : true,
2896
+
2897
+ //String - The colour of the stroke on each segement.
2898
+ segmentStrokeColor : "#fff",
2899
+
2900
+ //Number - The width of the stroke value in pixels
2901
+ segmentStrokeWidth : 2,
2902
+
2903
+ //Number - Amount of animation steps
2904
+ animationSteps : 100,
2905
+
2906
+ //String - Animation easing effect.
2907
+ animationEasing : "easeOutBounce",
2908
+
2909
+ //Boolean - Whether to animate the rotation of the chart
2910
+ animateRotate : true,
2911
+
2912
+ //Boolean - Whether to animate scaling the chart from the centre
2913
+ animateScale : false,
2914
+
2915
+ //String - A legend template
2916
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
2917
+ };
2918
+
2919
+
2920
+ Chart.Type.extend({
2921
+ //Passing in a name registers this chart in the Chart namespace
2922
+ name: "PolarArea",
2923
+ //Providing a defaults will also register the deafults in the chart namespace
2924
+ defaults : defaultConfig,
2925
+ //Initialize is fired when the chart is initialized - Data is passed in as a parameter
2926
+ //Config is automatically merged by the core of Chart.js, and is available at this.options
2927
+ initialize: function(data){
2928
+ this.segments = [];
2929
+ //Declare segment class as a chart instance specific class, so it can share props for this instance
2930
+ this.SegmentArc = Chart.Arc.extend({
2931
+ showStroke : this.options.segmentShowStroke,
2932
+ strokeWidth : this.options.segmentStrokeWidth,
2933
+ strokeColor : this.options.segmentStrokeColor,
2934
+ ctx : this.chart.ctx,
2935
+ innerRadius : 0,
2936
+ x : this.chart.width/2,
2937
+ y : this.chart.height/2
2938
+ });
2939
+ this.scale = new Chart.RadialScale({
2940
+ display: this.options.showScale,
2941
+ fontStyle: this.options.scaleFontStyle,
2942
+ fontSize: this.options.scaleFontSize,
2943
+ fontFamily: this.options.scaleFontFamily,
2944
+ fontColor: this.options.scaleFontColor,
2945
+ showLabels: this.options.scaleShowLabels,
2946
+ showLabelBackdrop: this.options.scaleShowLabelBackdrop,
2947
+ backdropColor: this.options.scaleBackdropColor,
2948
+ backdropPaddingY : this.options.scaleBackdropPaddingY,
2949
+ backdropPaddingX: this.options.scaleBackdropPaddingX,
2950
+ lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
2951
+ lineColor: this.options.scaleLineColor,
2952
+ lineArc: true,
2953
+ width: this.chart.width,
2954
+ height: this.chart.height,
2955
+ xCenter: this.chart.width/2,
2956
+ yCenter: this.chart.height/2,
2957
+ ctx : this.chart.ctx,
2958
+ templateString: this.options.scaleLabel,
2959
+ valuesCount: data.length
2960
+ });
2961
+
2962
+ this.updateScaleRange(data);
2963
+
2964
+ this.scale.update();
2965
+
2966
+ helpers.each(data,function(segment,index){
2967
+ this.addData(segment,index,true);
2968
+ },this);
2969
+
2970
+ //Set up tooltip events on the chart
2971
+ if (this.options.showTooltips){
2972
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2973
+ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
2974
+ helpers.each(this.segments,function(segment){
2975
+ segment.restore(["fillColor"]);
2976
+ });
2977
+ helpers.each(activeSegments,function(activeSegment){
2978
+ activeSegment.fillColor = activeSegment.highlightColor;
2979
+ });
2980
+ this.showTooltip(activeSegments);
2981
+ });
2982
+ }
2983
+
2984
+ this.render();
2985
+ },
2986
+ getSegmentsAtEvent : function(e){
2987
+ var segmentsArray = [];
2988
+
2989
+ var location = helpers.getRelativePosition(e);
2990
+
2991
+ helpers.each(this.segments,function(segment){
2992
+ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
2993
+ },this);
2994
+ return segmentsArray;
2995
+ },
2996
+ addData : function(segment, atIndex, silent){
2997
+ var index = atIndex || this.segments.length;
2998
+
2999
+ this.segments.splice(index, 0, new this.SegmentArc({
3000
+ fillColor: segment.color,
3001
+ highlightColor: segment.highlight || segment.color,
3002
+ label: segment.label,
3003
+ value: segment.value,
3004
+ outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
3005
+ circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
3006
+ startAngle: Math.PI * 1.5
3007
+ }));
3008
+ if (!silent){
3009
+ this.reflow();
3010
+ this.update();
3011
+ }
3012
+ },
3013
+ removeData: function(atIndex){
3014
+ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
3015
+ this.segments.splice(indexToDelete, 1);
3016
+ this.reflow();
3017
+ this.update();
3018
+ },
3019
+ calculateTotal: function(data){
3020
+ this.total = 0;
3021
+ helpers.each(data,function(segment){
3022
+ this.total += segment.value;
3023
+ },this);
3024
+ this.scale.valuesCount = this.segments.length;
3025
+ },
3026
+ updateScaleRange: function(datapoints){
3027
+ var valuesArray = [];
3028
+ helpers.each(datapoints,function(segment){
3029
+ valuesArray.push(segment.value);
3030
+ });
3031
+
3032
+ var scaleSizes = (this.options.scaleOverride) ?
3033
+ {
3034
+ steps: this.options.scaleSteps,
3035
+ stepValue: this.options.scaleStepWidth,
3036
+ min: this.options.scaleStartValue,
3037
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3038
+ } :
3039
+ helpers.calculateScaleRange(
3040
+ valuesArray,
3041
+ helpers.min([this.chart.width, this.chart.height])/2,
3042
+ this.options.scaleFontSize,
3043
+ this.options.scaleBeginAtZero,
3044
+ this.options.scaleIntegersOnly
3045
+ );
3046
+
3047
+ helpers.extend(
3048
+ this.scale,
3049
+ scaleSizes,
3050
+ {
3051
+ size: helpers.min([this.chart.width, this.chart.height]),
3052
+ xCenter: this.chart.width/2,
3053
+ yCenter: this.chart.height/2
3054
+ }
3055
+ );
3056
+
3057
+ },
3058
+ update : function(){
3059
+ this.calculateTotal(this.segments);
3060
+
3061
+ helpers.each(this.segments,function(segment){
3062
+ segment.save();
3063
+ });
3064
+ this.render();
3065
+ },
3066
+ reflow : function(){
3067
+ helpers.extend(this.SegmentArc.prototype,{
3068
+ x : this.chart.width/2,
3069
+ y : this.chart.height/2
3070
+ });
3071
+ this.updateScaleRange(this.segments);
3072
+ this.scale.update();
3073
+
3074
+ helpers.extend(this.scale,{
3075
+ xCenter: this.chart.width/2,
3076
+ yCenter: this.chart.height/2
3077
+ });
3078
+
3079
+ helpers.each(this.segments, function(segment){
3080
+ segment.update({
3081
+ outerRadius : this.scale.calculateCenterOffset(segment.value)
3082
+ });
3083
+ }, this);
3084
+
3085
+ },
3086
+ draw : function(ease){
3087
+ var easingDecimal = ease || 1;
3088
+ //Clear & draw the canvas
3089
+ this.clear();
3090
+ helpers.each(this.segments,function(segment, index){
3091
+ segment.transition({
3092
+ circumference : this.scale.getCircumference(),
3093
+ outerRadius : this.scale.calculateCenterOffset(segment.value)
3094
+ },easingDecimal);
3095
+
3096
+ segment.endAngle = segment.startAngle + segment.circumference;
3097
+
3098
+ // If we've removed the first segment we need to set the first one to
3099
+ // start at the top.
3100
+ if (index === 0){
3101
+ segment.startAngle = Math.PI * 1.5;
3102
+ }
3103
+
3104
+ //Check to see if it's the last segment, if not get the next and update the start angle
3105
+ if (index < this.segments.length - 1){
3106
+ this.segments[index+1].startAngle = segment.endAngle;
3107
+ }
3108
+ segment.draw();
3109
+ }, this);
3110
+ this.scale.draw();
3111
+ }
3112
+ });
3113
+
3114
+ }).call(this);
3115
+ (function(){
3116
+ "use strict";
3117
+
3118
+ var root = this,
3119
+ Chart = root.Chart,
3120
+ helpers = Chart.helpers;
3121
+
3122
+
3123
+
3124
+ Chart.Type.extend({
3125
+ name: "Radar",
3126
+ defaults:{
3127
+ //Boolean - Whether to show lines for each scale point
3128
+ scaleShowLine : true,
3129
+
3130
+ //Boolean - Whether we show the angle lines out of the radar
3131
+ angleShowLineOut : true,
3132
+
3133
+ //Boolean - Whether to show labels on the scale
3134
+ scaleShowLabels : false,
3135
+
3136
+ // Boolean - Whether the scale should begin at zero
3137
+ scaleBeginAtZero : true,
3138
+
3139
+ //String - Colour of the angle line
3140
+ angleLineColor : "rgba(0,0,0,.1)",
3141
+
3142
+ //Number - Pixel width of the angle line
3143
+ angleLineWidth : 1,
3144
+
3145
+ //String - Point label font declaration
3146
+ pointLabelFontFamily : "'Arial'",
3147
+
3148
+ //String - Point label font weight
3149
+ pointLabelFontStyle : "normal",
3150
+
3151
+ //Number - Point label font size in pixels
3152
+ pointLabelFontSize : 10,
3153
+
3154
+ //String - Point label font colour
3155
+ pointLabelFontColor : "#666",
3156
+
3157
+ //Boolean - Whether to show a dot for each point
3158
+ pointDot : true,
3159
+
3160
+ //Number - Radius of each point dot in pixels
3161
+ pointDotRadius : 3,
3162
+
3163
+ //Number - Pixel width of point dot stroke
3164
+ pointDotStrokeWidth : 1,
3165
+
3166
+ //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
3167
+ pointHitDetectionRadius : 20,
3168
+
3169
+ //Boolean - Whether to show a stroke for datasets
3170
+ datasetStroke : true,
3171
+
3172
+ //Number - Pixel width of dataset stroke
3173
+ datasetStrokeWidth : 2,
3174
+
3175
+ //Boolean - Whether to fill the dataset with a colour
3176
+ datasetFill : true,
3177
+
3178
+ //String - A legend template
3179
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
3180
+
3181
+ },
3182
+
3183
+ initialize: function(data){
3184
+ this.PointClass = Chart.Point.extend({
3185
+ strokeWidth : this.options.pointDotStrokeWidth,
3186
+ radius : this.options.pointDotRadius,
3187
+ display: this.options.pointDot,
3188
+ hitDetectionRadius : this.options.pointHitDetectionRadius,
3189
+ ctx : this.chart.ctx
3190
+ });
3191
+
3192
+ this.datasets = [];
3193
+
3194
+ this.buildScale(data);
3195
+
3196
+ //Set up tooltip events on the chart
3197
+ if (this.options.showTooltips){
3198
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3199
+ var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
3200
+
3201
+ this.eachPoints(function(point){
3202
+ point.restore(['fillColor', 'strokeColor']);
3203
+ });
3204
+ helpers.each(activePointsCollection, function(activePoint){
3205
+ activePoint.fillColor = activePoint.highlightFill;
3206
+ activePoint.strokeColor = activePoint.highlightStroke;
3207
+ });
3208
+
3209
+ this.showTooltip(activePointsCollection);
3210
+ });
3211
+ }
3212
+
3213
+ //Iterate through each of the datasets, and build this into a property of the chart
3214
+ helpers.each(data.datasets,function(dataset){
3215
+
3216
+ var datasetObject = {
3217
+ label: dataset.label || null,
3218
+ fillColor : dataset.fillColor,
3219
+ strokeColor : dataset.strokeColor,
3220
+ pointColor : dataset.pointColor,
3221
+ pointStrokeColor : dataset.pointStrokeColor,
3222
+ points : []
3223
+ };
3224
+
3225
+ this.datasets.push(datasetObject);
3226
+
3227
+ helpers.each(dataset.data,function(dataPoint,index){
3228
+ //Add a new point for each piece of data, passing any required data to draw.
3229
+ var pointPosition;
3230
+ if (!this.scale.animation){
3231
+ pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
3232
+ }
3233
+ datasetObject.points.push(new this.PointClass({
3234
+ value : dataPoint,
3235
+ label : data.labels[index],
3236
+ datasetLabel: dataset.label,
3237
+ x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
3238
+ y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
3239
+ strokeColor : dataset.pointStrokeColor,
3240
+ fillColor : dataset.pointColor,
3241
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
3242
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
3243
+ }));
3244
+ },this);
3245
+
3246
+ },this);
3247
+
3248
+ this.render();
3249
+ },
3250
+ eachPoints : function(callback){
3251
+ helpers.each(this.datasets,function(dataset){
3252
+ helpers.each(dataset.points,callback,this);
3253
+ },this);
3254
+ },
3255
+
3256
+ getPointsAtEvent : function(evt){
3257
+ var mousePosition = helpers.getRelativePosition(evt),
3258
+ fromCenter = helpers.getAngleFromPoint({
3259
+ x: this.scale.xCenter,
3260
+ y: this.scale.yCenter
3261
+ }, mousePosition);
3262
+
3263
+ var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
3264
+ pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
3265
+ activePointsCollection = [];
3266
+
3267
+ // If we're at the top, make the pointIndex 0 to get the first of the array.
3268
+ if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
3269
+ pointIndex = 0;
3270
+ }
3271
+
3272
+ if (fromCenter.distance <= this.scale.drawingArea){
3273
+ helpers.each(this.datasets, function(dataset){
3274
+ activePointsCollection.push(dataset.points[pointIndex]);
3275
+ });
3276
+ }
3277
+
3278
+ return activePointsCollection;
3279
+ },
3280
+
3281
+ buildScale : function(data){
3282
+ this.scale = new Chart.RadialScale({
3283
+ display: this.options.showScale,
3284
+ fontStyle: this.options.scaleFontStyle,
3285
+ fontSize: this.options.scaleFontSize,
3286
+ fontFamily: this.options.scaleFontFamily,
3287
+ fontColor: this.options.scaleFontColor,
3288
+ showLabels: this.options.scaleShowLabels,
3289
+ showLabelBackdrop: this.options.scaleShowLabelBackdrop,
3290
+ backdropColor: this.options.scaleBackdropColor,
3291
+ backdropPaddingY : this.options.scaleBackdropPaddingY,
3292
+ backdropPaddingX: this.options.scaleBackdropPaddingX,
3293
+ lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
3294
+ lineColor: this.options.scaleLineColor,
3295
+ angleLineColor : this.options.angleLineColor,
3296
+ angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
3297
+ // Point labels at the edge of each line
3298
+ pointLabelFontColor : this.options.pointLabelFontColor,
3299
+ pointLabelFontSize : this.options.pointLabelFontSize,
3300
+ pointLabelFontFamily : this.options.pointLabelFontFamily,
3301
+ pointLabelFontStyle : this.options.pointLabelFontStyle,
3302
+ height : this.chart.height,
3303
+ width: this.chart.width,
3304
+ xCenter: this.chart.width/2,
3305
+ yCenter: this.chart.height/2,
3306
+ ctx : this.chart.ctx,
3307
+ templateString: this.options.scaleLabel,
3308
+ labels: data.labels,
3309
+ valuesCount: data.datasets[0].data.length
3310
+ });
3311
+
3312
+ this.scale.setScaleSize();
3313
+ this.updateScaleRange(data.datasets);
3314
+ this.scale.buildYLabels();
3315
+ },
3316
+ updateScaleRange: function(datasets){
3317
+ var valuesArray = (function(){
3318
+ var totalDataArray = [];
3319
+ helpers.each(datasets,function(dataset){
3320
+ if (dataset.data){
3321
+ totalDataArray = totalDataArray.concat(dataset.data);
3322
+ }
3323
+ else {
3324
+ helpers.each(dataset.points, function(point){
3325
+ totalDataArray.push(point.value);
3326
+ });
3327
+ }
3328
+ });
3329
+ return totalDataArray;
3330
+ })();
3331
+
3332
+
3333
+ var scaleSizes = (this.options.scaleOverride) ?
3334
+ {
3335
+ steps: this.options.scaleSteps,
3336
+ stepValue: this.options.scaleStepWidth,
3337
+ min: this.options.scaleStartValue,
3338
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3339
+ } :
3340
+ helpers.calculateScaleRange(
3341
+ valuesArray,
3342
+ helpers.min([this.chart.width, this.chart.height])/2,
3343
+ this.options.scaleFontSize,
3344
+ this.options.scaleBeginAtZero,
3345
+ this.options.scaleIntegersOnly
3346
+ );
3347
+
3348
+ helpers.extend(
3349
+ this.scale,
3350
+ scaleSizes
3351
+ );
3352
+
3353
+ },
3354
+ addData : function(valuesArray,label){
3355
+ //Map the values array for each of the datasets
3356
+ this.scale.valuesCount++;
3357
+ helpers.each(valuesArray,function(value,datasetIndex){
3358
+ var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
3359
+ this.datasets[datasetIndex].points.push(new this.PointClass({
3360
+ value : value,
3361
+ label : label,
3362
+ x: pointPosition.x,
3363
+ y: pointPosition.y,
3364
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
3365
+ fillColor : this.datasets[datasetIndex].pointColor
3366
+ }));
3367
+ },this);
3368
+
3369
+ this.scale.labels.push(label);
3370
+
3371
+ this.reflow();
3372
+
3373
+ this.update();
3374
+ },
3375
+ removeData : function(){
3376
+ this.scale.valuesCount--;
3377
+ this.scale.labels.shift();
3378
+ helpers.each(this.datasets,function(dataset){
3379
+ dataset.points.shift();
3380
+ },this);
3381
+ this.reflow();
3382
+ this.update();
3383
+ },
3384
+ update : function(){
3385
+ this.eachPoints(function(point){
3386
+ point.save();
3387
+ });
3388
+ this.reflow();
3389
+ this.render();
3390
+ },
3391
+ reflow: function(){
3392
+ helpers.extend(this.scale, {
3393
+ width : this.chart.width,
3394
+ height: this.chart.height,
3395
+ size : helpers.min([this.chart.width, this.chart.height]),
3396
+ xCenter: this.chart.width/2,
3397
+ yCenter: this.chart.height/2
3398
+ });
3399
+ this.updateScaleRange(this.datasets);
3400
+ this.scale.setScaleSize();
3401
+ this.scale.buildYLabels();
3402
+ },
3403
+ draw : function(ease){
3404
+ var easeDecimal = ease || 1,
3405
+ ctx = this.chart.ctx;
3406
+ this.clear();
3407
+ this.scale.draw();
3408
+
3409
+ helpers.each(this.datasets,function(dataset){
3410
+
3411
+ //Transition each point first so that the line and point drawing isn't out of sync
3412
+ helpers.each(dataset.points,function(point,index){
3413
+ if (point.hasValue()){
3414
+ point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
3415
+ }
3416
+ },this);
3417
+
3418
+
3419
+
3420
+ //Draw the line between all the points
3421
+ ctx.lineWidth = this.options.datasetStrokeWidth;
3422
+ ctx.strokeStyle = dataset.strokeColor;
3423
+ ctx.beginPath();
3424
+ helpers.each(dataset.points,function(point,index){
3425
+ if (index === 0){
3426
+ ctx.moveTo(point.x,point.y);
3427
+ }
3428
+ else{
3429
+ ctx.lineTo(point.x,point.y);
3430
+ }
3431
+ },this);
3432
+ ctx.closePath();
3433
+ ctx.stroke();
3434
+
3435
+ ctx.fillStyle = dataset.fillColor;
3436
+ ctx.fill();
3437
+
3438
+ //Now draw the points over the line
3439
+ //A little inefficient double looping, but better than the line
3440
+ //lagging behind the point positions
3441
+ helpers.each(dataset.points,function(point){
3442
+ if (point.hasValue()){
3443
+ point.draw();
3444
+ }
3445
+ });
3446
+
3447
+ },this);
3448
+
3449
+ }
3450
+
3451
+ });
3452
+
3453
+
3454
+
3455
+
3456
+
3457
+ }).call(this);