adminlte2-rails 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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);