boletia_ui 0.1.1

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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +42 -0
  3. data/lib/boletia_ui/version.rb +3 -0
  4. data/lib/boletia_ui.rb +8 -0
  5. data/vendor/assets/boletia_ui/fonts/ss-standard.css +53 -0
  6. data/vendor/assets/boletia_ui/fonts/ss-standard.eot +0 -0
  7. data/vendor/assets/boletia_ui/fonts/ss-standard.js +84 -0
  8. data/vendor/assets/boletia_ui/fonts/ss-standard.svg +404 -0
  9. data/vendor/assets/boletia_ui/fonts/ss-standard.ttf +0 -0
  10. data/vendor/assets/boletia_ui/fonts/ss-standard.woff +0 -0
  11. data/vendor/assets/boletia_ui/images/cancel.png +0 -0
  12. data/vendor/assets/boletia_ui/images/check.png +0 -0
  13. data/vendor/assets/boletia_ui/images/default-search.png +0 -0
  14. data/vendor/assets/boletia_ui/images/download.png +0 -0
  15. data/vendor/assets/boletia_ui/images/edit.png +0 -0
  16. data/vendor/assets/boletia_ui/images/icon-logo140.png +0 -0
  17. data/vendor/assets/boletia_ui/images/logo/header-logo-black.png +0 -0
  18. data/vendor/assets/boletia_ui/images/logo/header-logo-white.png +0 -0
  19. data/vendor/assets/boletia_ui/images/logo/icon-logo140.png +0 -0
  20. data/vendor/assets/boletia_ui/images/resend.png +0 -0
  21. data/vendor/assets/boletia_ui/images/trash.png +0 -0
  22. data/vendor/assets/boletia_ui/images/trophy.png +0 -0
  23. data/vendor/assets/boletia_ui/images/white-resend.png +0 -0
  24. data/vendor/assets/boletia_ui/images/white-search.png +0 -0
  25. data/vendor/assets/boletia_ui/javascripts/boletia-ui/boletia-ui-components.js +117 -0
  26. data/vendor/assets/boletia_ui/javascripts/boletia-ui/classie.js +80 -0
  27. data/vendor/assets/boletia_ui/javascripts/boletia-ui/select_fx.js +335 -0
  28. data/vendor/assets/boletia_ui/javascripts/charts/Chart.Legend.js +56 -0
  29. data/vendor/assets/boletia_ui/javascripts/charts/chart.min.js +11 -0
  30. data/vendor/assets/boletia_ui/javascripts/charts/refundCharts.js +167 -0
  31. data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Bar.js +302 -0
  32. data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Core.js +2002 -0
  33. data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Doughnut.js +184 -0
  34. data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Line.js +374 -0
  35. data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.PolarArea.js +250 -0
  36. data/vendor/assets/boletia_ui/javascripts/charts/src/Chart.Radar.js +343 -0
  37. data/vendor/assets/boletia_ui/javascripts/counter/counter.js +12 -0
  38. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  39. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_flat_0_eeeeee_40x100.png +0 -0
  40. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_flat_55_ffffff_40x100.png +0 -0
  41. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  42. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  43. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png +0 -0
  44. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_highlight-soft_25_0073ea_1x100.png +0 -0
  45. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-bg_highlight-soft_50_dddddd_1x100.png +0 -0
  46. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_0073ea_256x240.png +0 -0
  47. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_454545_256x240.png +0 -0
  48. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_666666_256x240.png +0 -0
  49. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_ff0084_256x240.png +0 -0
  50. data/vendor/assets/boletia_ui/javascripts/jquery-ui/images/ui-icons_ffffff_256x240.png +0 -0
  51. data/vendor/assets/boletia_ui/javascripts/jquery-ui/jquery-ui.min.js +8 -0
  52. data/vendor/assets/boletia_ui/javascripts/scrolling-header/scrolling-header.js +41 -0
  53. data/vendor/assets/boletia_ui/javascripts/styleguide/styleguide.js +87 -0
  54. data/vendor/assets/boletia_ui/javascripts/tooltips/jquery.tooltipster.min.js +1 -0
  55. data/vendor/assets/boletia_ui/javascripts/vendor/magnific-popup/core.js +966 -0
  56. data/vendor/assets/boletia_ui/javascripts/vendor/magnific-popup/fastclick.js +93 -0
  57. data/vendor/assets/boletia_ui/javascripts/vendor/magnific-popup/image.js +245 -0
  58. data/vendor/assets/boletia_ui/javascripts/vendor/magnific-popup/inline.js +66 -0
  59. data/vendor/assets/boletia_ui/stylesheets/boletia-admin.css.scss +41 -0
  60. data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.css +53 -0
  61. data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.eot +0 -0
  62. data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.js +84 -0
  63. data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.svg +404 -0
  64. data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.ttf +0 -0
  65. data/vendor/assets/boletia_ui/stylesheets/fonts/ss-standard.woff +0 -0
  66. data/vendor/assets/boletia_ui/stylesheets/jquery-ui/jquery-ui.min.css +7 -0
  67. data/vendor/assets/boletia_ui/stylesheets/jquery-ui/jquery-ui.structure.min.css +5 -0
  68. data/vendor/assets/boletia_ui/stylesheets/jquery-ui/jquery-ui.theme.css +410 -0
  69. data/vendor/assets/boletia_ui/stylesheets/layouts/_event_details.scss +100 -0
  70. data/vendor/assets/boletia_ui/stylesheets/layouts/_refund-wizard.scss +290 -0
  71. data/vendor/assets/boletia_ui/stylesheets/layouts/_sales.scss +17 -0
  72. data/vendor/assets/boletia_ui/stylesheets/layouts/_style_guide.scss +216 -0
  73. data/vendor/assets/boletia_ui/stylesheets/menu/_footer.scss +117 -0
  74. data/vendor/assets/boletia_ui/stylesheets/menu/_header.scss +314 -0
  75. data/vendor/assets/boletia_ui/stylesheets/menu/_new_front_footer.scss +117 -0
  76. data/vendor/assets/boletia_ui/stylesheets/menu/_new_front_header.scss +314 -0
  77. data/vendor/assets/boletia_ui/stylesheets/partials/_alerts.scss +183 -0
  78. data/vendor/assets/boletia_ui/stylesheets/partials/_base.scss +376 -0
  79. data/vendor/assets/boletia_ui/stylesheets/partials/_boletia-cards.scss +401 -0
  80. data/vendor/assets/boletia_ui/stylesheets/partials/_buttons.scss +200 -0
  81. data/vendor/assets/boletia_ui/stylesheets/partials/_dashboard.scss +192 -0
  82. data/vendor/assets/boletia_ui/stylesheets/partials/_forms.scss +395 -0
  83. data/vendor/assets/boletia_ui/stylesheets/partials/_grid.scss +85 -0
  84. data/vendor/assets/boletia_ui/stylesheets/partials/_tables.scss +139 -0
  85. data/vendor/assets/boletia_ui/stylesheets/partials/_wizard.scss +125 -0
  86. data/vendor/assets/boletia_ui/stylesheets/vendor/_select.scss +109 -0
  87. data/vendor/assets/boletia_ui/stylesheets/vendor/magnific-popup/_magnific.scss +642 -0
  88. data/vendor/assets/boletia_ui/stylesheets/vendor/magnific-popup/_settings.scss +45 -0
  89. data/vendor/assets/boletia_ui/stylesheets/vendor/tooltipster.css +274 -0
  90. metadata +174 -0
@@ -0,0 +1,2002 @@
1
+ /*!
2
+ * Chart.js
3
+ * http://chartjs.org/
4
+ * Version: {{ version }}
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(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
+ if (this.y - halfHeight < 0 ){
1372
+ this.y = halfHeight;
1373
+ } else if (this.y + halfHeight > this.chart.height){
1374
+ this.y = this.chart.height - halfHeight;
1375
+ }
1376
+
1377
+ //Decide whether to align left or right based on position on canvas
1378
+ if (this.x > this.chart.width/2){
1379
+ this.x -= this.xOffset + this.width;
1380
+ } else {
1381
+ this.x += this.xOffset;
1382
+ }
1383
+
1384
+
1385
+ },
1386
+ getLineHeight : function(index){
1387
+ var baseLineHeight = this.y - (this.height/2) + this.yPadding,
1388
+ afterTitleIndex = index-1;
1389
+
1390
+ //If the index is zero, we're getting the title
1391
+ if (index === 0){
1392
+ return baseLineHeight + this.titleFontSize/2;
1393
+ } else{
1394
+ return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
1395
+ }
1396
+
1397
+ },
1398
+ draw : function(){
1399
+ // Custom Tooltips
1400
+ if(this.custom){
1401
+ this.custom(this);
1402
+ }
1403
+ else{
1404
+ drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
1405
+ var ctx = this.ctx;
1406
+ ctx.fillStyle = this.fillColor;
1407
+ ctx.fill();
1408
+ ctx.closePath();
1409
+
1410
+ ctx.textAlign = "left";
1411
+ ctx.textBaseline = "middle";
1412
+ ctx.fillStyle = this.titleTextColor;
1413
+ ctx.font = this.titleFont;
1414
+
1415
+ ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
1416
+
1417
+ ctx.font = this.font;
1418
+ helpers.each(this.labels,function(label,index){
1419
+ ctx.fillStyle = this.textColor;
1420
+ ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
1421
+
1422
+ //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
1423
+ //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1424
+ //Instead we'll make a white filled block to put the legendColour palette over.
1425
+
1426
+ ctx.fillStyle = this.legendColorBackground;
1427
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1428
+
1429
+ ctx.fillStyle = this.legendColors[index].fill;
1430
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1431
+
1432
+
1433
+ },this);
1434
+ }
1435
+ }
1436
+ });
1437
+
1438
+ Chart.Scale = Chart.Element.extend({
1439
+ initialize : function(){
1440
+ this.fit();
1441
+ },
1442
+ buildYLabels : function(){
1443
+ this.yLabels = [];
1444
+
1445
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1446
+
1447
+ for (var i=0; i<=this.steps; i++){
1448
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1449
+ }
1450
+ this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
1451
+ },
1452
+ addXLabel : function(label){
1453
+ this.xLabels.push(label);
1454
+ this.valuesCount++;
1455
+ this.fit();
1456
+ },
1457
+ removeXLabel : function(){
1458
+ this.xLabels.shift();
1459
+ this.valuesCount--;
1460
+ this.fit();
1461
+ },
1462
+ // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
1463
+ fit: function(){
1464
+ // First we need the width of the yLabels, assuming the xLabels aren't rotated
1465
+
1466
+ // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
1467
+ this.startPoint = (this.display) ? this.fontSize : 0;
1468
+ this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
1469
+
1470
+ // Apply padding settings to the start and end point.
1471
+ this.startPoint += this.padding;
1472
+ this.endPoint -= this.padding;
1473
+
1474
+ // Cache the starting height, so can determine if we need to recalculate the scale yAxis
1475
+ var cachedHeight = this.endPoint - this.startPoint,
1476
+ cachedYLabelWidth;
1477
+
1478
+ // Build the current yLabels so we have an idea of what size they'll be to start
1479
+ /*
1480
+ * This sets what is returned from calculateScaleRange as static properties of this class:
1481
+ *
1482
+ this.steps;
1483
+ this.stepValue;
1484
+ this.min;
1485
+ this.max;
1486
+ *
1487
+ */
1488
+ this.calculateYRange(cachedHeight);
1489
+
1490
+ // With these properties set we can now build the array of yLabels
1491
+ // and also the width of the largest yLabel
1492
+ this.buildYLabels();
1493
+
1494
+ this.calculateXLabelRotation();
1495
+
1496
+ while((cachedHeight > this.endPoint - this.startPoint)){
1497
+ cachedHeight = this.endPoint - this.startPoint;
1498
+ cachedYLabelWidth = this.yLabelWidth;
1499
+
1500
+ this.calculateYRange(cachedHeight);
1501
+ this.buildYLabels();
1502
+
1503
+ // Only go through the xLabel loop again if the yLabel width has changed
1504
+ if (cachedYLabelWidth < this.yLabelWidth){
1505
+ this.calculateXLabelRotation();
1506
+ }
1507
+ }
1508
+
1509
+ },
1510
+ calculateXLabelRotation : function(){
1511
+ //Get the width of each grid by calculating the difference
1512
+ //between x offsets between 0 and 1.
1513
+
1514
+ this.ctx.font = this.font;
1515
+
1516
+ var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
1517
+ lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
1518
+ firstRotated,
1519
+ lastRotated;
1520
+
1521
+
1522
+ this.xScalePaddingRight = lastWidth/2 + 3;
1523
+ this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
1524
+
1525
+ this.xLabelRotation = 0;
1526
+ if (this.display){
1527
+ var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
1528
+ cosRotation,
1529
+ firstRotatedWidth;
1530
+ this.xLabelWidth = originalLabelWidth;
1531
+ //Allow 3 pixels x2 padding either side for label readability
1532
+ var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
1533
+
1534
+ //Max label rotate should be 90 - also act as a loop counter
1535
+ while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
1536
+ cosRotation = Math.cos(toRadians(this.xLabelRotation));
1537
+
1538
+ firstRotated = cosRotation * firstWidth;
1539
+ lastRotated = cosRotation * lastWidth;
1540
+
1541
+ // We're right aligning the text now.
1542
+ if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
1543
+ this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
1544
+ }
1545
+ this.xScalePaddingRight = this.fontSize/2;
1546
+
1547
+
1548
+ this.xLabelRotation++;
1549
+ this.xLabelWidth = cosRotation * originalLabelWidth;
1550
+
1551
+ }
1552
+ if (this.xLabelRotation > 0){
1553
+ this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
1554
+ }
1555
+ }
1556
+ else{
1557
+ this.xLabelWidth = 0;
1558
+ this.xScalePaddingRight = this.padding;
1559
+ this.xScalePaddingLeft = this.padding;
1560
+ }
1561
+
1562
+ },
1563
+ // Needs to be overidden in each Chart type
1564
+ // Otherwise we need to pass all the data into the scale class
1565
+ calculateYRange: noop,
1566
+ drawingArea: function(){
1567
+ return this.startPoint - this.endPoint;
1568
+ },
1569
+ calculateY : function(value){
1570
+ var scalingFactor = this.drawingArea() / (this.min - this.max);
1571
+ return this.endPoint - (scalingFactor * (value - this.min));
1572
+ },
1573
+ calculateX : function(index){
1574
+ var isRotated = (this.xLabelRotation > 0),
1575
+ // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
1576
+ innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
1577
+ valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
1578
+ valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
1579
+
1580
+ if (this.offsetGridLines){
1581
+ valueOffset += (valueWidth/2);
1582
+ }
1583
+
1584
+ return Math.round(valueOffset);
1585
+ },
1586
+ update : function(newProps){
1587
+ helpers.extend(this, newProps);
1588
+ this.fit();
1589
+ },
1590
+ draw : function(){
1591
+ var ctx = this.ctx,
1592
+ yLabelGap = (this.endPoint - this.startPoint) / this.steps,
1593
+ xStart = Math.round(this.xScalePaddingLeft);
1594
+ if (this.display){
1595
+ ctx.fillStyle = this.textColor;
1596
+ ctx.font = this.font;
1597
+ each(this.yLabels,function(labelString,index){
1598
+ var yLabelCenter = this.endPoint - (yLabelGap * index),
1599
+ linePositionY = Math.round(yLabelCenter),
1600
+ drawHorizontalLine = this.showHorizontalLines;
1601
+
1602
+ ctx.textAlign = "right";
1603
+ ctx.textBaseline = "middle";
1604
+ if (this.showLabels){
1605
+ ctx.fillText(labelString,xStart - 10,yLabelCenter);
1606
+ }
1607
+
1608
+ // This is X axis, so draw it
1609
+ if (index === 0 && !drawHorizontalLine){
1610
+ drawHorizontalLine = true;
1611
+ }
1612
+
1613
+ if (drawHorizontalLine){
1614
+ ctx.beginPath();
1615
+ }
1616
+
1617
+ if (index > 0){
1618
+ // This is a grid line in the centre, so drop that
1619
+ ctx.lineWidth = this.gridLineWidth;
1620
+ ctx.strokeStyle = this.gridLineColor;
1621
+ } else {
1622
+ // This is the first line on the scale
1623
+ ctx.lineWidth = this.lineWidth;
1624
+ ctx.strokeStyle = this.lineColor;
1625
+ }
1626
+
1627
+ linePositionY += helpers.aliasPixel(ctx.lineWidth);
1628
+
1629
+ if(drawHorizontalLine){
1630
+ ctx.moveTo(xStart, linePositionY);
1631
+ ctx.lineTo(this.width, linePositionY);
1632
+ ctx.stroke();
1633
+ ctx.closePath();
1634
+ }
1635
+
1636
+ ctx.lineWidth = this.lineWidth;
1637
+ ctx.strokeStyle = this.lineColor;
1638
+ ctx.beginPath();
1639
+ ctx.moveTo(xStart - 5, linePositionY);
1640
+ ctx.lineTo(xStart, linePositionY);
1641
+ ctx.stroke();
1642
+ ctx.closePath();
1643
+
1644
+ },this);
1645
+
1646
+ each(this.xLabels,function(label,index){
1647
+ var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
1648
+ // Check to see if line/bar here and decide where to place the line
1649
+ linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
1650
+ isRotated = (this.xLabelRotation > 0),
1651
+ drawVerticalLine = this.showVerticalLines;
1652
+
1653
+ // This is Y axis, so draw it
1654
+ if (index === 0 && !drawVerticalLine){
1655
+ drawVerticalLine = true;
1656
+ }
1657
+
1658
+ if (drawVerticalLine){
1659
+ ctx.beginPath();
1660
+ }
1661
+
1662
+ if (index > 0){
1663
+ // This is a grid line in the centre, so drop that
1664
+ ctx.lineWidth = this.gridLineWidth;
1665
+ ctx.strokeStyle = this.gridLineColor;
1666
+ } else {
1667
+ // This is the first line on the scale
1668
+ ctx.lineWidth = this.lineWidth;
1669
+ ctx.strokeStyle = this.lineColor;
1670
+ }
1671
+
1672
+ if (drawVerticalLine){
1673
+ ctx.moveTo(linePos,this.endPoint);
1674
+ ctx.lineTo(linePos,this.startPoint - 3);
1675
+ ctx.stroke();
1676
+ ctx.closePath();
1677
+ }
1678
+
1679
+
1680
+ ctx.lineWidth = this.lineWidth;
1681
+ ctx.strokeStyle = this.lineColor;
1682
+
1683
+
1684
+ // Small lines at the bottom of the base grid line
1685
+ ctx.beginPath();
1686
+ ctx.moveTo(linePos,this.endPoint);
1687
+ ctx.lineTo(linePos,this.endPoint + 5);
1688
+ ctx.stroke();
1689
+ ctx.closePath();
1690
+
1691
+ ctx.save();
1692
+ ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
1693
+ ctx.rotate(toRadians(this.xLabelRotation)*-1);
1694
+ ctx.font = this.font;
1695
+ ctx.textAlign = (isRotated) ? "right" : "center";
1696
+ ctx.textBaseline = (isRotated) ? "middle" : "top";
1697
+ ctx.fillText(label, 0, 0);
1698
+ ctx.restore();
1699
+ },this);
1700
+
1701
+ }
1702
+ }
1703
+
1704
+ });
1705
+
1706
+ Chart.RadialScale = Chart.Element.extend({
1707
+ initialize: function(){
1708
+ this.size = min([this.height, this.width]);
1709
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1710
+ },
1711
+ calculateCenterOffset: function(value){
1712
+ // Take into account half font size + the yPadding of the top value
1713
+ var scalingFactor = this.drawingArea / (this.max - this.min);
1714
+
1715
+ return (value - this.min) * scalingFactor;
1716
+ },
1717
+ update : function(){
1718
+ if (!this.lineArc){
1719
+ this.setScaleSize();
1720
+ } else {
1721
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1722
+ }
1723
+ this.buildYLabels();
1724
+ },
1725
+ buildYLabels: function(){
1726
+ this.yLabels = [];
1727
+
1728
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1729
+
1730
+ for (var i=0; i<=this.steps; i++){
1731
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1732
+ }
1733
+ },
1734
+ getCircumference : function(){
1735
+ return ((Math.PI*2) / this.valuesCount);
1736
+ },
1737
+ setScaleSize: function(){
1738
+ /*
1739
+ * Right, this is really confusing and there is a lot of maths going on here
1740
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
1741
+ *
1742
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
1743
+ *
1744
+ * Solution:
1745
+ *
1746
+ * We assume the radius of the polygon is half the size of the canvas at first
1747
+ * at each index we check if the text overlaps.
1748
+ *
1749
+ * Where it does, we store that angle and that index.
1750
+ *
1751
+ * After finding the largest index and angle we calculate how much we need to remove
1752
+ * from the shape radius to move the point inwards by that x.
1753
+ *
1754
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
1755
+ * along with labels.
1756
+ *
1757
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
1758
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
1759
+ *
1760
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
1761
+ * and position it in the most space efficient manner
1762
+ *
1763
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
1764
+ */
1765
+
1766
+
1767
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
1768
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
1769
+ var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
1770
+ pointPosition,
1771
+ i,
1772
+ textWidth,
1773
+ halfTextWidth,
1774
+ furthestRight = this.width,
1775
+ furthestRightIndex,
1776
+ furthestRightAngle,
1777
+ furthestLeft = 0,
1778
+ furthestLeftIndex,
1779
+ furthestLeftAngle,
1780
+ xProtrusionLeft,
1781
+ xProtrusionRight,
1782
+ radiusReductionRight,
1783
+ radiusReductionLeft,
1784
+ maxWidthRadius;
1785
+ this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
1786
+ for (i=0;i<this.valuesCount;i++){
1787
+ // 5px to space the text slightly out - similar to what we do in the draw function.
1788
+ pointPosition = this.getPointPosition(i, largestPossibleRadius);
1789
+ textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
1790
+ if (i === 0 || i === this.valuesCount/2){
1791
+ // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
1792
+ // of the radar chart, so text will be aligned centrally, so we'll half it and compare
1793
+ // w/left and right text sizes
1794
+ halfTextWidth = textWidth/2;
1795
+ if (pointPosition.x + halfTextWidth > furthestRight) {
1796
+ furthestRight = pointPosition.x + halfTextWidth;
1797
+ furthestRightIndex = i;
1798
+ }
1799
+ if (pointPosition.x - halfTextWidth < furthestLeft) {
1800
+ furthestLeft = pointPosition.x - halfTextWidth;
1801
+ furthestLeftIndex = i;
1802
+ }
1803
+ }
1804
+ else if (i < this.valuesCount/2) {
1805
+ // Less than half the values means we'll left align the text
1806
+ if (pointPosition.x + textWidth > furthestRight) {
1807
+ furthestRight = pointPosition.x + textWidth;
1808
+ furthestRightIndex = i;
1809
+ }
1810
+ }
1811
+ else if (i > this.valuesCount/2){
1812
+ // More than half the values means we'll right align the text
1813
+ if (pointPosition.x - textWidth < furthestLeft) {
1814
+ furthestLeft = pointPosition.x - textWidth;
1815
+ furthestLeftIndex = i;
1816
+ }
1817
+ }
1818
+ }
1819
+
1820
+ xProtrusionLeft = furthestLeft;
1821
+
1822
+ xProtrusionRight = Math.ceil(furthestRight - this.width);
1823
+
1824
+ furthestRightAngle = this.getIndexAngle(furthestRightIndex);
1825
+
1826
+ furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
1827
+
1828
+ radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
1829
+
1830
+ radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
1831
+
1832
+ // Ensure we actually need to reduce the size of the chart
1833
+ radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
1834
+ radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
1835
+
1836
+ this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
1837
+
1838
+ //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
1839
+ this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
1840
+
1841
+ },
1842
+ setCenterPoint: function(leftMovement, rightMovement){
1843
+
1844
+ var maxRight = this.width - rightMovement - this.drawingArea,
1845
+ maxLeft = leftMovement + this.drawingArea;
1846
+
1847
+ this.xCenter = (maxLeft + maxRight)/2;
1848
+ // Always vertically in the centre as the text height doesn't change
1849
+ this.yCenter = (this.height/2);
1850
+ },
1851
+
1852
+ getIndexAngle : function(index){
1853
+ var angleMultiplier = (Math.PI * 2) / this.valuesCount;
1854
+ // Start from the top instead of right, so remove a quarter of the circle
1855
+
1856
+ return index * angleMultiplier - (Math.PI/2);
1857
+ },
1858
+ getPointPosition : function(index, distanceFromCenter){
1859
+ var thisAngle = this.getIndexAngle(index);
1860
+ return {
1861
+ x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
1862
+ y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
1863
+ };
1864
+ },
1865
+ draw: function(){
1866
+ if (this.display){
1867
+ var ctx = this.ctx;
1868
+ each(this.yLabels, function(label, index){
1869
+ // Don't draw a centre value
1870
+ if (index > 0){
1871
+ var yCenterOffset = index * (this.drawingArea/this.steps),
1872
+ yHeight = this.yCenter - yCenterOffset,
1873
+ pointPosition;
1874
+
1875
+ // Draw circular lines around the scale
1876
+ if (this.lineWidth > 0){
1877
+ ctx.strokeStyle = this.lineColor;
1878
+ ctx.lineWidth = this.lineWidth;
1879
+
1880
+ if(this.lineArc){
1881
+ ctx.beginPath();
1882
+ ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
1883
+ ctx.closePath();
1884
+ ctx.stroke();
1885
+ } else{
1886
+ ctx.beginPath();
1887
+ for (var i=0;i<this.valuesCount;i++)
1888
+ {
1889
+ pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
1890
+ if (i === 0){
1891
+ ctx.moveTo(pointPosition.x, pointPosition.y);
1892
+ } else {
1893
+ ctx.lineTo(pointPosition.x, pointPosition.y);
1894
+ }
1895
+ }
1896
+ ctx.closePath();
1897
+ ctx.stroke();
1898
+ }
1899
+ }
1900
+ if(this.showLabels){
1901
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1902
+ if (this.showLabelBackdrop){
1903
+ var labelWidth = ctx.measureText(label).width;
1904
+ ctx.fillStyle = this.backdropColor;
1905
+ ctx.fillRect(
1906
+ this.xCenter - labelWidth/2 - this.backdropPaddingX,
1907
+ yHeight - this.fontSize/2 - this.backdropPaddingY,
1908
+ labelWidth + this.backdropPaddingX*2,
1909
+ this.fontSize + this.backdropPaddingY*2
1910
+ );
1911
+ }
1912
+ ctx.textAlign = 'center';
1913
+ ctx.textBaseline = "middle";
1914
+ ctx.fillStyle = this.fontColor;
1915
+ ctx.fillText(label, this.xCenter, yHeight);
1916
+ }
1917
+ }
1918
+ }, this);
1919
+
1920
+ if (!this.lineArc){
1921
+ ctx.lineWidth = this.angleLineWidth;
1922
+ ctx.strokeStyle = this.angleLineColor;
1923
+ for (var i = this.valuesCount - 1; i >= 0; i--) {
1924
+ if (this.angleLineWidth > 0){
1925
+ var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
1926
+ ctx.beginPath();
1927
+ ctx.moveTo(this.xCenter, this.yCenter);
1928
+ ctx.lineTo(outerPosition.x, outerPosition.y);
1929
+ ctx.stroke();
1930
+ ctx.closePath();
1931
+ }
1932
+ // Extra 3px out for some label spacing
1933
+ var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
1934
+ ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
1935
+ ctx.fillStyle = this.pointLabelFontColor;
1936
+
1937
+ var labelsCount = this.labels.length,
1938
+ halfLabelsCount = this.labels.length/2,
1939
+ quarterLabelsCount = halfLabelsCount/2,
1940
+ upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
1941
+ exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
1942
+ if (i === 0){
1943
+ ctx.textAlign = 'center';
1944
+ } else if(i === halfLabelsCount){
1945
+ ctx.textAlign = 'center';
1946
+ } else if (i < halfLabelsCount){
1947
+ ctx.textAlign = 'left';
1948
+ } else {
1949
+ ctx.textAlign = 'right';
1950
+ }
1951
+
1952
+ // Set the correct text baseline based on outer positioning
1953
+ if (exactQuarter){
1954
+ ctx.textBaseline = 'middle';
1955
+ } else if (upperHalf){
1956
+ ctx.textBaseline = 'bottom';
1957
+ } else {
1958
+ ctx.textBaseline = 'top';
1959
+ }
1960
+
1961
+ ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
1962
+ }
1963
+ }
1964
+ }
1965
+ }
1966
+ });
1967
+
1968
+ // Attach global event to resize each chart instance when the browser resizes
1969
+ helpers.addEvent(window, "resize", (function(){
1970
+ // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
1971
+ var timeout;
1972
+ return function(){
1973
+ clearTimeout(timeout);
1974
+ timeout = setTimeout(function(){
1975
+ each(Chart.instances,function(instance){
1976
+ // If the responsive flag is set in the chart instance config
1977
+ // Cascade the resize event down to the chart.
1978
+ if (instance.options.responsive){
1979
+ instance.resize(instance.render, true);
1980
+ }
1981
+ });
1982
+ }, 50);
1983
+ };
1984
+ })());
1985
+
1986
+
1987
+ if (amd) {
1988
+ define(function(){
1989
+ return Chart;
1990
+ });
1991
+ } else if (typeof module === 'object' && module.exports) {
1992
+ module.exports = Chart;
1993
+ }
1994
+
1995
+ root.Chart = Chart;
1996
+
1997
+ Chart.noConflict = function(){
1998
+ root.Chart = previous;
1999
+ return Chart;
2000
+ };
2001
+
2002
+ }).call(this);