Completed
Push — master ( 7682df...b1b8c7 )
by Timo
23:42 queued 14:36
created

Resources/Public/JavaScript/Chart.js   F

Complexity

Total Complexity 1987
Complexity/F 2.69

Size

Lines of Code 10388
Function Count 740

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
nc 0
dl 0
loc 10388
rs 2.4
c 1
b 0
f 0
wmc 1987
mnd 7
bc 1555
fnc 740
bpm 2.1013
cpm 2.6851
noi 98

How to fix   Complexity   

Complexity

Complex classes like Resources/Public/JavaScript/Chart.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * Chart.js
3
 * http://chartjs.org/
4
 * Version: 2.2.1
5
 *
6
 * Copyright 2016 Nick Downie
7
 * Released under the MIT license
8
 * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
9
 */
10
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
0 ignored issues
show
Unused Code introduced by
The parameter require is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter exports is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Unused Code introduced by
The variable define seems to be never used. Consider removing it.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Unused Code introduced by
The parameter module is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The variable module seems to be never used. Consider removing it.
Loading history...
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
11
12
},{}],2:[function(require,module,exports){
13
/* MIT license */
14
var colorNames = require(6);
15
16
module.exports = {
17
   getRgba: getRgba,
18
   getHsla: getHsla,
19
   getRgb: getRgb,
20
   getHsl: getHsl,
21
   getHwb: getHwb,
22
   getAlpha: getAlpha,
23
24
   hexString: hexString,
25
   rgbString: rgbString,
26
   rgbaString: rgbaString,
27
   percentString: percentString,
28
   percentaString: percentaString,
29
   hslString: hslString,
30
   hslaString: hslaString,
31
   hwbString: hwbString,
32
   keyword: keyword
33
}
34
35
function getRgba(string) {
36
   if (!string) {
37
      return;
38
   }
39
   var abbr =  /^#([a-fA-F0-9]{3})$/,
40
       hex =  /^#([a-fA-F0-9]{6})$/,
41
       rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
42
       per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
43
       keyword = /(\w+)/;
44
45
   var rgb = [0, 0, 0],
46
       a = 1,
47
       match = string.match(abbr);
48
   if (match) {
49
      match = match[1];
50
      for (var i = 0; i < rgb.length; i++) {
51
         rgb[i] = parseInt(match[i] + match[i], 16);
52
      }
53
   }
54
   else if (match = string.match(hex)) {
55
      match = match[1];
56
      for (var i = 0; i < rgb.length; i++) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 50. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
57
         rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
58
      }
59
   }
60
   else if (match = string.match(rgba)) {
61
      for (var i = 0; i < rgb.length; i++) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 50. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
62
         rgb[i] = parseInt(match[i + 1]);
63
      }
64
      a = parseFloat(match[4]);
65
   }
66
   else if (match = string.match(per)) {
67
      for (var i = 0; i < rgb.length; i++) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 50. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
68
         rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
69
      }
70
      a = parseFloat(match[4]);
71
   }
72
   else if (match = string.match(keyword)) {
73
      if (match[1] == "transparent") {
74
         return [0, 0, 0, 0];
75
      }
76
      rgb = colorNames[match[1]];
77
      if (!rgb) {
78
         return;
79
      }
80
   }
81
82
   for (var i = 0; i < rgb.length; i++) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 50. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
83
      rgb[i] = scale(rgb[i], 0, 255);
84
   }
85
   if (!a && a != 0) {
86
      a = 1;
87
   }
88
   else {
89
      a = scale(a, 0, 1);
90
   }
91
   rgb[3] = a;
92
   return rgb;
93
}
94
95
function getHsla(string) {
96
   if (!string) {
97
      return;
98
   }
99
   var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
100
   var match = string.match(hsl);
101
   if (match) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if match is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
102
      var alpha = parseFloat(match[4]);
103
      var h = scale(parseInt(match[1]), 0, 360),
104
          s = scale(parseFloat(match[2]), 0, 100),
105
          l = scale(parseFloat(match[3]), 0, 100),
106
          a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
107
      return [h, s, l, a];
108
   }
109
}
110
111
function getHwb(string) {
112
   if (!string) {
113
      return;
114
   }
115
   var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
116
   var match = string.match(hwb);
117
   if (match) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if match is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
118
    var alpha = parseFloat(match[4]);
119
      var h = scale(parseInt(match[1]), 0, 360),
120
          w = scale(parseFloat(match[2]), 0, 100),
121
          b = scale(parseFloat(match[3]), 0, 100),
122
          a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
123
      return [h, w, b, a];
124
   }
125
}
126
127
function getRgb(string) {
128
   var rgba = getRgba(string);
129
   return rgba && rgba.slice(0, 3);
130
}
131
132
function getHsl(string) {
133
  var hsla = getHsla(string);
134
  return hsla && hsla.slice(0, 3);
135
}
136
137
function getAlpha(string) {
138
   var vals = getRgba(string);
139
   if (vals) {
140
      return vals[3];
141
   }
142
   else if (vals = getHsla(string)) {
143
      return vals[3];
144
   }
145
   else if (vals = getHwb(string)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if vals = getHwb(string) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
146
      return vals[3];
147
   }
148
}
149
150
// generators
151
function hexString(rgb) {
152
   return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
153
              + hexDouble(rgb[2]);
154
}
155
156
function rgbString(rgba, alpha) {
157
   if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
158
      return rgbaString(rgba, alpha);
159
   }
160
   return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
161
}
162
163
function rgbaString(rgba, alpha) {
164
   if (alpha === undefined) {
165
      alpha = (rgba[3] !== undefined ? rgba[3] : 1);
166
   }
167
   return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
168
           + ", " + alpha + ")";
169
}
170
171
function percentString(rgba, alpha) {
172
   if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
173
      return percentaString(rgba, alpha);
174
   }
175
   var r = Math.round(rgba[0]/255 * 100),
176
       g = Math.round(rgba[1]/255 * 100),
177
       b = Math.round(rgba[2]/255 * 100);
178
179
   return "rgb(" + r + "%, " + g + "%, " + b + "%)";
180
}
181
182
function percentaString(rgba, alpha) {
183
   var r = Math.round(rgba[0]/255 * 100),
184
       g = Math.round(rgba[1]/255 * 100),
185
       b = Math.round(rgba[2]/255 * 100);
186
   return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
187
}
188
189
function hslString(hsla, alpha) {
190
   if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
191
      return hslaString(hsla, alpha);
192
   }
193
   return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
194
}
195
196
function hslaString(hsla, alpha) {
197
   if (alpha === undefined) {
198
      alpha = (hsla[3] !== undefined ? hsla[3] : 1);
199
   }
200
   return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
201
           + alpha + ")";
202
}
203
204
// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
205
// (hwb have alpha optional & 1 is default value)
206
function hwbString(hwb, alpha) {
207
   if (alpha === undefined) {
208
      alpha = (hwb[3] !== undefined ? hwb[3] : 1);
209
   }
210
   return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
211
           + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
212
}
213
214
function keyword(rgb) {
215
  return reverseNames[rgb.slice(0, 3)];
216
}
217
218
// helpers
219
function scale(num, min, max) {
220
   return Math.min(Math.max(min, num), max);
221
}
222
223
function hexDouble(num) {
224
  var str = num.toString(16).toUpperCase();
225
  return (str.length < 2) ? "0" + str : str;
226
}
227
228
229
//create a list of reverse color names
230
var reverseNames = {};
231
for (var name in colorNames) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
232
   reverseNames[colorNames[name]] = name;
233
}
234
235
},{"6":6}],3:[function(require,module,exports){
236
/* MIT license */
237
var convert = require(5);
238
var string = require(2);
239
240
var Color = function (obj) {
241
	if (obj instanceof Color) {
242
		return obj;
243
	}
244
	if (!(this instanceof Color)) {
245
		return new Color(obj);
246
	}
247
248
	this.values = {
249
		rgb: [0, 0, 0],
250
		hsl: [0, 0, 0],
251
		hsv: [0, 0, 0],
252
		hwb: [0, 0, 0],
253
		cmyk: [0, 0, 0, 0],
254
		alpha: 1
255
	};
256
257
	// parse Color() argument
258
	var vals;
259
	if (typeof obj === 'string') {
260
		vals = string.getRgba(obj);
261
		if (vals) {
262
			this.setValues('rgb', vals);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
263
		} else if (vals = string.getHsla(obj)) {
264
			this.setValues('hsl', vals);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
265
		} else if (vals = string.getHwb(obj)) {
266
			this.setValues('hwb', vals);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
267
		} else {
268
			throw new Error('Unable to parse color from string "' + obj + '"');
269
		}
270
	} else if (typeof obj === 'object') {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if typeof obj === "object" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
271
		vals = obj;
272
		if (vals.r !== undefined || vals.red !== undefined) {
273
			this.setValues('rgb', vals);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
274
		} else if (vals.l !== undefined || vals.lightness !== undefined) {
275
			this.setValues('hsl', vals);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
276
		} else if (vals.v !== undefined || vals.value !== undefined) {
277
			this.setValues('hsv', vals);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
278
		} else if (vals.w !== undefined || vals.whiteness !== undefined) {
279
			this.setValues('hwb', vals);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
280
		} else if (vals.c !== undefined || vals.cyan !== undefined) {
281
			this.setValues('cmyk', vals);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
282
		} else {
283
			throw new Error('Unable to parse color from object ' + JSON.stringify(obj));
284
		}
285
	}
286
};
287
288
Color.prototype = {
289
	rgb: function () {
290
		return this.setSpace('rgb', arguments);
291
	},
292
	hsl: function () {
293
		return this.setSpace('hsl', arguments);
294
	},
295
	hsv: function () {
296
		return this.setSpace('hsv', arguments);
297
	},
298
	hwb: function () {
299
		return this.setSpace('hwb', arguments);
300
	},
301
	cmyk: function () {
302
		return this.setSpace('cmyk', arguments);
303
	},
304
305
	rgbArray: function () {
306
		return this.values.rgb;
307
	},
308
	hslArray: function () {
309
		return this.values.hsl;
310
	},
311
	hsvArray: function () {
312
		return this.values.hsv;
313
	},
314
	hwbArray: function () {
315
		var values = this.values;
316
		if (values.alpha !== 1) {
317
			return values.hwb.concat([values.alpha]);
318
		}
319
		return values.hwb;
320
	},
321
	cmykArray: function () {
322
		return this.values.cmyk;
323
	},
324
	rgbaArray: function () {
325
		var values = this.values;
326
		return values.rgb.concat([values.alpha]);
327
	},
328
	hslaArray: function () {
329
		var values = this.values;
330
		return values.hsl.concat([values.alpha]);
331
	},
332
	alpha: function (val) {
333
		if (val === undefined) {
334
			return this.values.alpha;
335
		}
336
		this.setValues('alpha', val);
337
		return this;
338
	},
339
340
	red: function (val) {
341
		return this.setChannel('rgb', 0, val);
342
	},
343
	green: function (val) {
344
		return this.setChannel('rgb', 1, val);
345
	},
346
	blue: function (val) {
347
		return this.setChannel('rgb', 2, val);
348
	},
349
	hue: function (val) {
350
		if (val) {
351
			val %= 360;
352
			val = val < 0 ? 360 + val : val;
353
		}
354
		return this.setChannel('hsl', 0, val);
355
	},
356
	saturation: function (val) {
357
		return this.setChannel('hsl', 1, val);
358
	},
359
	lightness: function (val) {
360
		return this.setChannel('hsl', 2, val);
361
	},
362
	saturationv: function (val) {
363
		return this.setChannel('hsv', 1, val);
364
	},
365
	whiteness: function (val) {
366
		return this.setChannel('hwb', 1, val);
367
	},
368
	blackness: function (val) {
369
		return this.setChannel('hwb', 2, val);
370
	},
371
	value: function (val) {
372
		return this.setChannel('hsv', 2, val);
373
	},
374
	cyan: function (val) {
375
		return this.setChannel('cmyk', 0, val);
376
	},
377
	magenta: function (val) {
378
		return this.setChannel('cmyk', 1, val);
379
	},
380
	yellow: function (val) {
381
		return this.setChannel('cmyk', 2, val);
382
	},
383
	black: function (val) {
384
		return this.setChannel('cmyk', 3, val);
385
	},
386
387
	hexString: function () {
388
		return string.hexString(this.values.rgb);
389
	},
390
	rgbString: function () {
391
		return string.rgbString(this.values.rgb, this.values.alpha);
392
	},
393
	rgbaString: function () {
394
		return string.rgbaString(this.values.rgb, this.values.alpha);
395
	},
396
	percentString: function () {
397
		return string.percentString(this.values.rgb, this.values.alpha);
398
	},
399
	hslString: function () {
400
		return string.hslString(this.values.hsl, this.values.alpha);
401
	},
402
	hslaString: function () {
403
		return string.hslaString(this.values.hsl, this.values.alpha);
404
	},
405
	hwbString: function () {
406
		return string.hwbString(this.values.hwb, this.values.alpha);
407
	},
408
	keyword: function () {
409
		return string.keyword(this.values.rgb, this.values.alpha);
410
	},
411
412
	rgbNumber: function () {
413
		var rgb = this.values.rgb;
414
		return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
415
	},
416
417
	luminosity: function () {
418
		// http://www.w3.org/TR/WCAG20/#relativeluminancedef
419
		var rgb = this.values.rgb;
420
		var lum = [];
421
		for (var i = 0; i < rgb.length; i++) {
422
			var chan = rgb[i] / 255;
423
			lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
424
		}
425
		return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
426
	},
427
428
	contrast: function (color2) {
429
		// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
430
		var lum1 = this.luminosity();
431
		var lum2 = color2.luminosity();
432
		if (lum1 > lum2) {
433
			return (lum1 + 0.05) / (lum2 + 0.05);
434
		}
435
		return (lum2 + 0.05) / (lum1 + 0.05);
436
	},
437
438
	level: function (color2) {
439
		var contrastRatio = this.contrast(color2);
440
		if (contrastRatio >= 7.1) {
441
			return 'AAA';
442
		}
443
444
		return (contrastRatio >= 4.5) ? 'AA' : '';
445
	},
446
447
	dark: function () {
448
		// YIQ equation from http://24ways.org/2010/calculating-color-contrast
449
		var rgb = this.values.rgb;
450
		var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
451
		return yiq < 128;
452
	},
453
454
	light: function () {
455
		return !this.dark();
456
	},
457
458
	negate: function () {
459
		var rgb = [];
460
		for (var i = 0; i < 3; i++) {
461
			rgb[i] = 255 - this.values.rgb[i];
462
		}
463
		this.setValues('rgb', rgb);
464
		return this;
465
	},
466
467
	lighten: function (ratio) {
468
		var hsl = this.values.hsl;
469
		hsl[2] += hsl[2] * ratio;
470
		this.setValues('hsl', hsl);
471
		return this;
472
	},
473
474
	darken: function (ratio) {
475
		var hsl = this.values.hsl;
476
		hsl[2] -= hsl[2] * ratio;
477
		this.setValues('hsl', hsl);
478
		return this;
479
	},
480
481
	saturate: function (ratio) {
482
		var hsl = this.values.hsl;
483
		hsl[1] += hsl[1] * ratio;
484
		this.setValues('hsl', hsl);
485
		return this;
486
	},
487
488
	desaturate: function (ratio) {
489
		var hsl = this.values.hsl;
490
		hsl[1] -= hsl[1] * ratio;
491
		this.setValues('hsl', hsl);
492
		return this;
493
	},
494
495
	whiten: function (ratio) {
496
		var hwb = this.values.hwb;
497
		hwb[1] += hwb[1] * ratio;
498
		this.setValues('hwb', hwb);
499
		return this;
500
	},
501
502
	blacken: function (ratio) {
503
		var hwb = this.values.hwb;
504
		hwb[2] += hwb[2] * ratio;
505
		this.setValues('hwb', hwb);
506
		return this;
507
	},
508
509
	greyscale: function () {
510
		var rgb = this.values.rgb;
511
		// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
512
		var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
513
		this.setValues('rgb', [val, val, val]);
514
		return this;
515
	},
516
517
	clearer: function (ratio) {
518
		var alpha = this.values.alpha;
519
		this.setValues('alpha', alpha - (alpha * ratio));
520
		return this;
521
	},
522
523
	opaquer: function (ratio) {
524
		var alpha = this.values.alpha;
525
		this.setValues('alpha', alpha + (alpha * ratio));
526
		return this;
527
	},
528
529
	rotate: function (degrees) {
530
		var hsl = this.values.hsl;
531
		var hue = (hsl[0] + degrees) % 360;
532
		hsl[0] = hue < 0 ? 360 + hue : hue;
533
		this.setValues('hsl', hsl);
534
		return this;
535
	},
536
537
	/**
538
	 * Ported from sass implementation in C
539
	 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
540
	 */
541
	mix: function (mixinColor, weight) {
542
		var color1 = this;
543
		var color2 = mixinColor;
544
		var p = weight === undefined ? 0.5 : weight;
545
546
		var w = 2 * p - 1;
547
		var a = color1.alpha() - color2.alpha();
548
549
		var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
550
		var w2 = 1 - w1;
551
552
		return this
553
			.rgb(
554
				w1 * color1.red() + w2 * color2.red(),
555
				w1 * color1.green() + w2 * color2.green(),
556
				w1 * color1.blue() + w2 * color2.blue()
557
			)
558
			.alpha(color1.alpha() * p + color2.alpha() * (1 - p));
559
	},
560
561
	toJSON: function () {
562
		return this.rgb();
563
	},
564
565
	clone: function () {
566
		// NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
567
		// making the final build way to big to embed in Chart.js. So let's do it manually,
568
		// assuming that values to clone are 1 dimension arrays containing only numbers,
569
		// except 'alpha' which is a number.
570
		var result = new Color();
571
		var source = this.values;
572
		var target = result.values;
573
		var value, type;
574
575
		for (var prop in source) {
576
			if (source.hasOwnProperty(prop)) {
577
				value = source[prop];
578
				type = ({}).toString.call(value);
579
				if (type === '[object Array]') {
580
					target[prop] = value.slice(0);
581
				} else if (type === '[object Number]') {
582
					target[prop] = value;
583
				} else {
584
					console.error('unexpected color value:', value);
585
				}
586
			}
587
		}
588
589
		return result;
590
	}
591
};
592
593
Color.prototype.spaces = {
594
	rgb: ['red', 'green', 'blue'],
595
	hsl: ['hue', 'saturation', 'lightness'],
596
	hsv: ['hue', 'saturation', 'value'],
597
	hwb: ['hue', 'whiteness', 'blackness'],
598
	cmyk: ['cyan', 'magenta', 'yellow', 'black']
599
};
600
601
Color.prototype.maxes = {
602
	rgb: [255, 255, 255],
603
	hsl: [360, 100, 100],
604
	hsv: [360, 100, 100],
605
	hwb: [360, 100, 100],
606
	cmyk: [100, 100, 100, 100]
607
};
608
609
Color.prototype.getValues = function (space) {
610
	var values = this.values;
611
	var vals = {};
612
613
	for (var i = 0; i < space.length; i++) {
614
		vals[space.charAt(i)] = values[space][i];
615
	}
616
617
	if (values.alpha !== 1) {
618
		vals.a = values.alpha;
619
	}
620
621
	// {r: 255, g: 255, b: 255, a: 0.4}
622
	return vals;
623
};
624
625
Color.prototype.setValues = function (space, vals) {
626
	var values = this.values;
627
	var spaces = this.spaces;
628
	var maxes = this.maxes;
629
	var alpha = 1;
630
	var i;
631
632
	if (space === 'alpha') {
633
		alpha = vals;
634
	} else if (vals.length) {
635
		// [10, 10, 10]
636
		values[space] = vals.slice(0, space.length);
637
		alpha = vals[space.length];
638
	} else if (vals[space.charAt(0)] !== undefined) {
639
		// {r: 10, g: 10, b: 10}
640
		for (i = 0; i < space.length; i++) {
641
			values[space][i] = vals[space.charAt(i)];
642
		}
643
644
		alpha = vals.a;
645
	} else if (vals[spaces[space][0]] !== undefined) {
646
		// {red: 10, green: 10, blue: 10}
647
		var chans = spaces[space];
648
649
		for (i = 0; i < space.length; i++) {
650
			values[space][i] = vals[chans[i]];
651
		}
652
653
		alpha = vals.alpha;
654
	}
655
656
	values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
657
658
	if (space === 'alpha') {
659
		return false;
660
	}
661
662
	var capped;
663
664
	// cap values of the space prior converting all values
665
	for (i = 0; i < space.length; i++) {
666
		capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
667
		values[space][i] = Math.round(capped);
668
	}
669
670
	// convert to all the other color spaces
671
	for (var sname in spaces) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
672
		if (sname !== space) {
673
			values[sname] = convert[space][sname](values[space]);
674
		}
675
	}
676
677
	return true;
678
};
679
680
Color.prototype.setSpace = function (space, args) {
681
	var vals = args[0];
682
683
	if (vals === undefined) {
684
		// color.rgb()
685
		return this.getValues(space);
686
	}
687
688
	// color.rgb(10, 10, 10)
689
	if (typeof vals === 'number') {
690
		vals = Array.prototype.slice.call(args);
691
	}
692
693
	this.setValues(space, vals);
694
	return this;
695
};
696
697
Color.prototype.setChannel = function (space, index, val) {
698
	var svalues = this.values[space];
699
	if (val === undefined) {
700
		// color.red()
701
		return svalues[index];
702
	} else if (val === svalues[index]) {
703
		// color.red(color.red())
704
		return this;
705
	}
706
707
	// color.red(100)
708
	svalues[index] = val;
709
	this.setValues(space, svalues);
710
711
	return this;
712
};
713
714
if (typeof window !== 'undefined') {
715
	window.Color = Color;
716
}
717
718
module.exports = Color;
719
720
},{"2":2,"5":5}],4:[function(require,module,exports){
721
/* MIT license */
722
723
module.exports = {
724
  rgb2hsl: rgb2hsl,
725
  rgb2hsv: rgb2hsv,
726
  rgb2hwb: rgb2hwb,
727
  rgb2cmyk: rgb2cmyk,
728
  rgb2keyword: rgb2keyword,
729
  rgb2xyz: rgb2xyz,
730
  rgb2lab: rgb2lab,
731
  rgb2lch: rgb2lch,
732
733
  hsl2rgb: hsl2rgb,
734
  hsl2hsv: hsl2hsv,
735
  hsl2hwb: hsl2hwb,
736
  hsl2cmyk: hsl2cmyk,
737
  hsl2keyword: hsl2keyword,
738
739
  hsv2rgb: hsv2rgb,
740
  hsv2hsl: hsv2hsl,
741
  hsv2hwb: hsv2hwb,
742
  hsv2cmyk: hsv2cmyk,
743
  hsv2keyword: hsv2keyword,
744
745
  hwb2rgb: hwb2rgb,
746
  hwb2hsl: hwb2hsl,
747
  hwb2hsv: hwb2hsv,
748
  hwb2cmyk: hwb2cmyk,
749
  hwb2keyword: hwb2keyword,
750
751
  cmyk2rgb: cmyk2rgb,
752
  cmyk2hsl: cmyk2hsl,
753
  cmyk2hsv: cmyk2hsv,
754
  cmyk2hwb: cmyk2hwb,
755
  cmyk2keyword: cmyk2keyword,
756
757
  keyword2rgb: keyword2rgb,
758
  keyword2hsl: keyword2hsl,
759
  keyword2hsv: keyword2hsv,
760
  keyword2hwb: keyword2hwb,
761
  keyword2cmyk: keyword2cmyk,
762
  keyword2lab: keyword2lab,
763
  keyword2xyz: keyword2xyz,
764
765
  xyz2rgb: xyz2rgb,
766
  xyz2lab: xyz2lab,
767
  xyz2lch: xyz2lch,
768
769
  lab2xyz: lab2xyz,
770
  lab2rgb: lab2rgb,
771
  lab2lch: lab2lch,
772
773
  lch2lab: lch2lab,
774
  lch2xyz: lch2xyz,
775
  lch2rgb: lch2rgb
776
}
777
778
779
function rgb2hsl(rgb) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
780
  var r = rgb[0]/255,
781
      g = rgb[1]/255,
782
      b = rgb[2]/255,
783
      min = Math.min(r, g, b),
784
      max = Math.max(r, g, b),
785
      delta = max - min,
786
      h, s, l;
787
788
  if (max == min)
789
    h = 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
790
  else if (r == max)
791
    h = (g - b) / delta;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
792
  else if (g == max)
793
    h = 2 + (b - r) / delta;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
794
  else if (b == max)
795
    h = 4 + (r - g)/ delta;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
796
797
  h = Math.min(h * 60, 360);
0 ignored issues
show
Bug introduced by
The variable h does not seem to be initialized in case b == max on line 794 is false. Are you sure this can never be the case?
Loading history...
798
799
  if (h < 0)
800
    h += 360;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
801
802
  l = (min + max) / 2;
803
804
  if (max == min)
805
    s = 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
806
  else if (l <= 0.5)
807
    s = delta / (max + min);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
808
  else
809
    s = delta / (2 - max - min);
810
811
  return [h, s * 100, l * 100];
812
}
813
814
function rgb2hsv(rgb) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
815
  var r = rgb[0],
816
      g = rgb[1],
817
      b = rgb[2],
818
      min = Math.min(r, g, b),
819
      max = Math.max(r, g, b),
820
      delta = max - min,
821
      h, s, v;
822
823
  if (max == 0)
824
    s = 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
825
  else
826
    s = (delta/max * 1000)/10;
827
828
  if (max == min)
829
    h = 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
830
  else if (r == max)
831
    h = (g - b) / delta;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
832
  else if (g == max)
833
    h = 2 + (b - r) / delta;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
834
  else if (b == max)
835
    h = 4 + (r - g) / delta;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
836
837
  h = Math.min(h * 60, 360);
0 ignored issues
show
Bug introduced by
The variable h does not seem to be initialized in case b == max on line 834 is false. Are you sure this can never be the case?
Loading history...
838
839
  if (h < 0)
840
    h += 360;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
841
842
  v = ((max / 255) * 1000) / 10;
843
844
  return [h, s, v];
845
}
846
847
function rgb2hwb(rgb) {
848
  var r = rgb[0],
849
      g = rgb[1],
850
      b = rgb[2],
851
      h = rgb2hsl(rgb)[0],
852
      w = 1/255 * Math.min(r, Math.min(g, b)),
853
      b = 1 - 1/255 * Math.max(r, Math.max(g, b));
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable b already seems to be declared on line 850. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
854
855
  return [h, w * 100, b * 100];
856
}
857
858
function rgb2cmyk(rgb) {
859
  var r = rgb[0] / 255,
860
      g = rgb[1] / 255,
861
      b = rgb[2] / 255,
862
      c, m, y, k;
863
864
  k = Math.min(1 - r, 1 - g, 1 - b);
865
  c = (1 - r - k) / (1 - k) || 0;
866
  m = (1 - g - k) / (1 - k) || 0;
867
  y = (1 - b - k) / (1 - k) || 0;
868
  return [c * 100, m * 100, y * 100, k * 100];
869
}
870
871
function rgb2keyword(rgb) {
872
  return reverseKeywords[JSON.stringify(rgb)];
873
}
874
875
function rgb2xyz(rgb) {
876
  var r = rgb[0] / 255,
877
      g = rgb[1] / 255,
878
      b = rgb[2] / 255;
879
880
  // assume sRGB
881
  r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
882
  g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
883
  b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
884
885
  var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
886
  var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
887
  var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
888
889
  return [x * 100, y *100, z * 100];
890
}
891
892
function rgb2lab(rgb) {
893
  var xyz = rgb2xyz(rgb),
894
        x = xyz[0],
895
        y = xyz[1],
896
        z = xyz[2],
897
        l, a, b;
898
899
  x /= 95.047;
900
  y /= 100;
901
  z /= 108.883;
902
903
  x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
904
  y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
905
  z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
906
907
  l = (116 * y) - 16;
908
  a = 500 * (x - y);
909
  b = 200 * (y - z);
910
911
  return [l, a, b];
912
}
913
914
function rgb2lch(args) {
915
  return lab2lch(rgb2lab(args));
916
}
917
918
function hsl2rgb(hsl) {
919
  var h = hsl[0] / 360,
920
      s = hsl[1] / 100,
921
      l = hsl[2] / 100,
922
      t1, t2, t3, rgb, val;
923
924
  if (s == 0) {
925
    val = l * 255;
926
    return [val, val, val];
927
  }
928
929
  if (l < 0.5)
930
    t2 = l * (1 + s);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
931
  else
932
    t2 = l + s - l * s;
933
  t1 = 2 * l - t2;
934
935
  rgb = [0, 0, 0];
936
  for (var i = 0; i < 3; i++) {
937
    t3 = h + 1 / 3 * - (i - 1);
938
    t3 < 0 && t3++;
939
    t3 > 1 && t3--;
940
941
    if (6 * t3 < 1)
942
      val = t1 + (t2 - t1) * 6 * t3;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
943
    else if (2 * t3 < 1)
944
      val = t2;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
945
    else if (3 * t3 < 2)
946
      val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
947
    else
948
      val = t1;
949
950
    rgb[i] = val * 255;
951
  }
952
953
  return rgb;
954
}
955
956
function hsl2hsv(hsl) {
957
  var h = hsl[0],
958
      s = hsl[1] / 100,
959
      l = hsl[2] / 100,
960
      sv, v;
961
962
  if(l === 0) {
963
      // no need to do calc on black
964
      // also avoids divide by 0 error
965
      return [0, 0, 0];
966
  }
967
968
  l *= 2;
969
  s *= (l <= 1) ? l : 2 - l;
970
  v = (l + s) / 2;
971
  sv = (2 * s) / (l + s);
972
  return [h, sv * 100, v * 100];
973
}
974
975
function hsl2hwb(args) {
976
  return rgb2hwb(hsl2rgb(args));
977
}
978
979
function hsl2cmyk(args) {
980
  return rgb2cmyk(hsl2rgb(args));
981
}
982
983
function hsl2keyword(args) {
984
  return rgb2keyword(hsl2rgb(args));
985
}
986
987
988
function hsv2rgb(hsv) {
989
  var h = hsv[0] / 60,
990
      s = hsv[1] / 100,
991
      v = hsv[2] / 100,
992
      hi = Math.floor(h) % 6;
993
994
  var f = h - Math.floor(h),
995
      p = 255 * v * (1 - s),
996
      q = 255 * v * (1 - (s * f)),
997
      t = 255 * v * (1 - (s * (1 - f))),
998
      v = 255 * v;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable v already seems to be declared on line 991. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
999
1000
  switch(hi) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1001
    case 0:
1002
      return [v, t, p];
1003
    case 1:
1004
      return [q, v, p];
1005
    case 2:
1006
      return [p, v, t];
1007
    case 3:
1008
      return [p, q, v];
1009
    case 4:
1010
      return [t, p, v];
1011
    case 5:
1012
      return [v, p, q];
1013
  }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
1014
}
1015
1016
function hsv2hsl(hsv) {
1017
  var h = hsv[0],
1018
      s = hsv[1] / 100,
1019
      v = hsv[2] / 100,
1020
      sl, l;
1021
1022
  l = (2 - s) * v;
1023
  sl = s * v;
1024
  sl /= (l <= 1) ? l : 2 - l;
1025
  sl = sl || 0;
1026
  l /= 2;
1027
  return [h, sl * 100, l * 100];
1028
}
1029
1030
function hsv2hwb(args) {
1031
  return rgb2hwb(hsv2rgb(args))
1032
}
1033
1034
function hsv2cmyk(args) {
1035
  return rgb2cmyk(hsv2rgb(args));
1036
}
1037
1038
function hsv2keyword(args) {
1039
  return rgb2keyword(hsv2rgb(args));
1040
}
1041
1042
// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
1043
function hwb2rgb(hwb) {
1044
  var h = hwb[0] / 360,
1045
      wh = hwb[1] / 100,
1046
      bl = hwb[2] / 100,
1047
      ratio = wh + bl,
1048
      i, v, f, n;
1049
1050
  // wh + bl cant be > 1
1051
  if (ratio > 1) {
1052
    wh /= ratio;
1053
    bl /= ratio;
1054
  }
1055
1056
  i = Math.floor(6 * h);
1057
  v = 1 - bl;
1058
  f = 6 * h - i;
1059
  if ((i & 0x01) != 0) {
1060
    f = 1 - f;
1061
  }
1062
  n = wh + f * (v - wh);  // linear interpolation
1063
1064
  switch (i) {
1065
    default:
0 ignored issues
show
Coding Style Comprehensibility introduced by
The default case is not the last statement in this switch statement. For the sake of readability, you might want to move it to the end of the statement.
Loading history...
1066
    case 6:
1067
    case 0: r = v; g = n; b = wh; break;
0 ignored issues
show
Bug introduced by
The variable g seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.g.
Loading history...
Bug introduced by
The variable b seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.b.
Loading history...
Bug introduced by
The variable r seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.r.
Loading history...
1068
    case 1: r = n; g = v; b = wh; break;
1069
    case 2: r = wh; g = v; b = n; break;
1070
    case 3: r = wh; g = n; b = v; break;
1071
    case 4: r = n; g = wh; b = v; break;
1072
    case 5: r = v; g = wh; b = n; break;
1073
  }
1074
1075
  return [r * 255, g * 255, b * 255];
1076
}
1077
1078
function hwb2hsl(args) {
1079
  return rgb2hsl(hwb2rgb(args));
1080
}
1081
1082
function hwb2hsv(args) {
1083
  return rgb2hsv(hwb2rgb(args));
1084
}
1085
1086
function hwb2cmyk(args) {
1087
  return rgb2cmyk(hwb2rgb(args));
1088
}
1089
1090
function hwb2keyword(args) {
1091
  return rgb2keyword(hwb2rgb(args));
1092
}
1093
1094
function cmyk2rgb(cmyk) {
1095
  var c = cmyk[0] / 100,
1096
      m = cmyk[1] / 100,
1097
      y = cmyk[2] / 100,
1098
      k = cmyk[3] / 100,
1099
      r, g, b;
1100
1101
  r = 1 - Math.min(1, c * (1 - k) + k);
1102
  g = 1 - Math.min(1, m * (1 - k) + k);
1103
  b = 1 - Math.min(1, y * (1 - k) + k);
1104
  return [r * 255, g * 255, b * 255];
1105
}
1106
1107
function cmyk2hsl(args) {
1108
  return rgb2hsl(cmyk2rgb(args));
1109
}
1110
1111
function cmyk2hsv(args) {
1112
  return rgb2hsv(cmyk2rgb(args));
1113
}
1114
1115
function cmyk2hwb(args) {
1116
  return rgb2hwb(cmyk2rgb(args));
1117
}
1118
1119
function cmyk2keyword(args) {
1120
  return rgb2keyword(cmyk2rgb(args));
1121
}
1122
1123
1124
function xyz2rgb(xyz) {
1125
  var x = xyz[0] / 100,
1126
      y = xyz[1] / 100,
1127
      z = xyz[2] / 100,
1128
      r, g, b;
1129
1130
  r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
1131
  g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
1132
  b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
1133
1134
  // assume sRGB
1135
  r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
1136
    : r = (r * 12.92);
0 ignored issues
show
Unused Code introduced by
The assignment to variable r seems to be never used. Consider removing it.
Loading history...
1137
1138
  g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
1139
    : g = (g * 12.92);
0 ignored issues
show
Unused Code introduced by
The assignment to variable g seems to be never used. Consider removing it.
Loading history...
1140
1141
  b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
1142
    : b = (b * 12.92);
0 ignored issues
show
Unused Code introduced by
The assignment to variable b seems to be never used. Consider removing it.
Loading history...
1143
1144
  r = Math.min(Math.max(0, r), 1);
1145
  g = Math.min(Math.max(0, g), 1);
1146
  b = Math.min(Math.max(0, b), 1);
1147
1148
  return [r * 255, g * 255, b * 255];
1149
}
1150
1151
function xyz2lab(xyz) {
1152
  var x = xyz[0],
1153
      y = xyz[1],
1154
      z = xyz[2],
1155
      l, a, b;
1156
1157
  x /= 95.047;
1158
  y /= 100;
1159
  z /= 108.883;
1160
1161
  x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
1162
  y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
1163
  z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
1164
1165
  l = (116 * y) - 16;
1166
  a = 500 * (x - y);
1167
  b = 200 * (y - z);
1168
1169
  return [l, a, b];
1170
}
1171
1172
function xyz2lch(args) {
1173
  return lab2lch(xyz2lab(args));
1174
}
1175
1176
function lab2xyz(lab) {
1177
  var l = lab[0],
1178
      a = lab[1],
1179
      b = lab[2],
1180
      x, y, z, y2;
1181
1182
  if (l <= 8) {
1183
    y = (l * 100) / 903.3;
1184
    y2 = (7.787 * (y / 100)) + (16 / 116);
1185
  } else {
1186
    y = 100 * Math.pow((l + 16) / 116, 3);
1187
    y2 = Math.pow(y / 100, 1/3);
1188
  }
1189
1190
  x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
0 ignored issues
show
Bug introduced by
The variable x seems to be never initialized.
Loading history...
Unused Code introduced by
The assignment to variable x seems to be never used. Consider removing it.
Loading history...
1191
1192
  z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
0 ignored issues
show
Unused Code introduced by
The assignment to variable z seems to be never used. Consider removing it.
Loading history...
Bug introduced by
The variable z seems to be never initialized.
Loading history...
1193
1194
  return [x, y, z];
1195
}
1196
1197
function lab2lch(lab) {
1198
  var l = lab[0],
1199
      a = lab[1],
1200
      b = lab[2],
1201
      hr, h, c;
1202
1203
  hr = Math.atan2(b, a);
1204
  h = hr * 360 / 2 / Math.PI;
1205
  if (h < 0) {
1206
    h += 360;
1207
  }
1208
  c = Math.sqrt(a * a + b * b);
1209
  return [l, c, h];
1210
}
1211
1212
function lab2rgb(args) {
1213
  return xyz2rgb(lab2xyz(args));
1214
}
1215
1216
function lch2lab(lch) {
1217
  var l = lch[0],
1218
      c = lch[1],
1219
      h = lch[2],
1220
      a, b, hr;
1221
1222
  hr = h / 360 * 2 * Math.PI;
1223
  a = c * Math.cos(hr);
1224
  b = c * Math.sin(hr);
1225
  return [l, a, b];
1226
}
1227
1228
function lch2xyz(args) {
1229
  return lab2xyz(lch2lab(args));
1230
}
1231
1232
function lch2rgb(args) {
1233
  return lab2rgb(lch2lab(args));
1234
}
1235
1236
function keyword2rgb(keyword) {
1237
  return cssKeywords[keyword];
1238
}
1239
1240
function keyword2hsl(args) {
1241
  return rgb2hsl(keyword2rgb(args));
1242
}
1243
1244
function keyword2hsv(args) {
1245
  return rgb2hsv(keyword2rgb(args));
1246
}
1247
1248
function keyword2hwb(args) {
1249
  return rgb2hwb(keyword2rgb(args));
1250
}
1251
1252
function keyword2cmyk(args) {
1253
  return rgb2cmyk(keyword2rgb(args));
1254
}
1255
1256
function keyword2lab(args) {
1257
  return rgb2lab(keyword2rgb(args));
1258
}
1259
1260
function keyword2xyz(args) {
1261
  return rgb2xyz(keyword2rgb(args));
1262
}
1263
1264
var cssKeywords = {
1265
  aliceblue:  [240,248,255],
1266
  antiquewhite: [250,235,215],
1267
  aqua: [0,255,255],
1268
  aquamarine: [127,255,212],
1269
  azure:  [240,255,255],
1270
  beige:  [245,245,220],
1271
  bisque: [255,228,196],
1272
  black:  [0,0,0],
1273
  blanchedalmond: [255,235,205],
1274
  blue: [0,0,255],
1275
  blueviolet: [138,43,226],
1276
  brown:  [165,42,42],
1277
  burlywood:  [222,184,135],
1278
  cadetblue:  [95,158,160],
1279
  chartreuse: [127,255,0],
1280
  chocolate:  [210,105,30],
1281
  coral:  [255,127,80],
1282
  cornflowerblue: [100,149,237],
1283
  cornsilk: [255,248,220],
1284
  crimson:  [220,20,60],
1285
  cyan: [0,255,255],
1286
  darkblue: [0,0,139],
1287
  darkcyan: [0,139,139],
1288
  darkgoldenrod:  [184,134,11],
1289
  darkgray: [169,169,169],
1290
  darkgreen:  [0,100,0],
1291
  darkgrey: [169,169,169],
1292
  darkkhaki:  [189,183,107],
1293
  darkmagenta:  [139,0,139],
1294
  darkolivegreen: [85,107,47],
1295
  darkorange: [255,140,0],
1296
  darkorchid: [153,50,204],
1297
  darkred:  [139,0,0],
1298
  darksalmon: [233,150,122],
1299
  darkseagreen: [143,188,143],
1300
  darkslateblue:  [72,61,139],
1301
  darkslategray:  [47,79,79],
1302
  darkslategrey:  [47,79,79],
1303
  darkturquoise:  [0,206,209],
1304
  darkviolet: [148,0,211],
1305
  deeppink: [255,20,147],
1306
  deepskyblue:  [0,191,255],
1307
  dimgray:  [105,105,105],
1308
  dimgrey:  [105,105,105],
1309
  dodgerblue: [30,144,255],
1310
  firebrick:  [178,34,34],
1311
  floralwhite:  [255,250,240],
1312
  forestgreen:  [34,139,34],
1313
  fuchsia:  [255,0,255],
1314
  gainsboro:  [220,220,220],
1315
  ghostwhite: [248,248,255],
1316
  gold: [255,215,0],
1317
  goldenrod:  [218,165,32],
1318
  gray: [128,128,128],
1319
  green:  [0,128,0],
1320
  greenyellow:  [173,255,47],
1321
  grey: [128,128,128],
1322
  honeydew: [240,255,240],
1323
  hotpink:  [255,105,180],
1324
  indianred:  [205,92,92],
1325
  indigo: [75,0,130],
1326
  ivory:  [255,255,240],
1327
  khaki:  [240,230,140],
1328
  lavender: [230,230,250],
1329
  lavenderblush:  [255,240,245],
1330
  lawngreen:  [124,252,0],
1331
  lemonchiffon: [255,250,205],
1332
  lightblue:  [173,216,230],
1333
  lightcoral: [240,128,128],
1334
  lightcyan:  [224,255,255],
1335
  lightgoldenrodyellow: [250,250,210],
1336
  lightgray:  [211,211,211],
1337
  lightgreen: [144,238,144],
1338
  lightgrey:  [211,211,211],
1339
  lightpink:  [255,182,193],
1340
  lightsalmon:  [255,160,122],
1341
  lightseagreen:  [32,178,170],
1342
  lightskyblue: [135,206,250],
1343
  lightslategray: [119,136,153],
1344
  lightslategrey: [119,136,153],
1345
  lightsteelblue: [176,196,222],
1346
  lightyellow:  [255,255,224],
1347
  lime: [0,255,0],
1348
  limegreen:  [50,205,50],
1349
  linen:  [250,240,230],
1350
  magenta:  [255,0,255],
1351
  maroon: [128,0,0],
1352
  mediumaquamarine: [102,205,170],
1353
  mediumblue: [0,0,205],
1354
  mediumorchid: [186,85,211],
1355
  mediumpurple: [147,112,219],
1356
  mediumseagreen: [60,179,113],
1357
  mediumslateblue:  [123,104,238],
1358
  mediumspringgreen:  [0,250,154],
1359
  mediumturquoise:  [72,209,204],
1360
  mediumvioletred:  [199,21,133],
1361
  midnightblue: [25,25,112],
1362
  mintcream:  [245,255,250],
1363
  mistyrose:  [255,228,225],
1364
  moccasin: [255,228,181],
1365
  navajowhite:  [255,222,173],
1366
  navy: [0,0,128],
1367
  oldlace:  [253,245,230],
1368
  olive:  [128,128,0],
1369
  olivedrab:  [107,142,35],
1370
  orange: [255,165,0],
1371
  orangered:  [255,69,0],
1372
  orchid: [218,112,214],
1373
  palegoldenrod:  [238,232,170],
1374
  palegreen:  [152,251,152],
1375
  paleturquoise:  [175,238,238],
1376
  palevioletred:  [219,112,147],
1377
  papayawhip: [255,239,213],
1378
  peachpuff:  [255,218,185],
1379
  peru: [205,133,63],
1380
  pink: [255,192,203],
1381
  plum: [221,160,221],
1382
  powderblue: [176,224,230],
1383
  purple: [128,0,128],
1384
  rebeccapurple: [102, 51, 153],
1385
  red:  [255,0,0],
1386
  rosybrown:  [188,143,143],
1387
  royalblue:  [65,105,225],
1388
  saddlebrown:  [139,69,19],
1389
  salmon: [250,128,114],
1390
  sandybrown: [244,164,96],
1391
  seagreen: [46,139,87],
1392
  seashell: [255,245,238],
1393
  sienna: [160,82,45],
1394
  silver: [192,192,192],
1395
  skyblue:  [135,206,235],
1396
  slateblue:  [106,90,205],
1397
  slategray:  [112,128,144],
1398
  slategrey:  [112,128,144],
1399
  snow: [255,250,250],
1400
  springgreen:  [0,255,127],
1401
  steelblue:  [70,130,180],
1402
  tan:  [210,180,140],
1403
  teal: [0,128,128],
1404
  thistle:  [216,191,216],
1405
  tomato: [255,99,71],
1406
  turquoise:  [64,224,208],
1407
  violet: [238,130,238],
1408
  wheat:  [245,222,179],
1409
  white:  [255,255,255],
1410
  whitesmoke: [245,245,245],
1411
  yellow: [255,255,0],
1412
  yellowgreen:  [154,205,50]
1413
};
1414
1415
var reverseKeywords = {};
1416
for (var key in cssKeywords) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
1417
  reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
1418
}
1419
1420
},{}],5:[function(require,module,exports){
1421
var conversions = require(4);
1422
1423
var convert = function() {
1424
   return new Converter();
1425
}
1426
1427
for (var func in conversions) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
1428
  // export Raw versions
1429
  convert[func + "Raw"] =  (function(func) {
1430
    // accept array or plain args
1431
    return function(arg) {
1432
      if (typeof arg == "number")
1433
        arg = Array.prototype.slice.call(arguments);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1434
      return conversions[func](arg);
1435
    }
1436
  })(func);
1437
1438
  var pair = /(\w+)2(\w+)/.exec(func),
1439
      from = pair[1],
1440
      to = pair[2];
1441
1442
  // export rgb2hsl and ["rgb"]["hsl"]
1443
  convert[from] = convert[from] || {};
1444
1445
  convert[from][to] = convert[func] = (function(func) { 
1446
    return function(arg) {
1447
      if (typeof arg == "number")
1448
        arg = Array.prototype.slice.call(arguments);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1449
      
1450
      var val = conversions[func](arg);
1451
      if (typeof val == "string" || val === undefined)
1452
        return val; // keyword
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1453
1454
      for (var i = 0; i < val.length; i++)
1455
        val[i] = Math.round(val[i]);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1456
      return val;
1457
    }
1458
  })(func);
1459
}
1460
1461
1462
/* Converter does lazy conversion and caching */
1463
var Converter = function() {
1464
   this.convs = {};
1465
};
1466
1467
/* Either get the values for a space or
1468
  set the values for a space, depending on args */
1469
Converter.prototype.routeSpace = function(space, args) {
1470
   var values = args[0];
1471
   if (values === undefined) {
1472
      // color.rgb()
1473
      return this.getValues(space);
1474
   }
1475
   // color.rgb(10, 10, 10)
1476
   if (typeof values == "number") {
1477
      values = Array.prototype.slice.call(args);        
1478
   }
1479
1480
   return this.setValues(space, values);
1481
};
1482
  
1483
/* Set the values for a space, invalidating cache */
1484
Converter.prototype.setValues = function(space, values) {
1485
   this.space = space;
1486
   this.convs = {};
1487
   this.convs[space] = values;
1488
   return this;
1489
};
1490
1491
/* Get the values for a space. If there's already
1492
  a conversion for the space, fetch it, otherwise
1493
  compute it */
1494
Converter.prototype.getValues = function(space) {
1495
   var vals = this.convs[space];
1496
   if (!vals) {
1497
      var fspace = this.space,
1498
          from = this.convs[fspace];
1499
      vals = convert[fspace][space](from);
1500
1501
      this.convs[space] = vals;
1502
   }
1503
  return vals;
1504
};
1505
1506
["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
1507
   Converter.prototype[space] = function(vals) {
0 ignored issues
show
Unused Code introduced by
The parameter vals is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1508
      return this.routeSpace(space, arguments);
1509
   }
1510
});
1511
1512
module.exports = convert;
1513
},{"4":4}],6:[function(require,module,exports){
1514
module.exports = {
1515
	"aliceblue": [240, 248, 255],
1516
	"antiquewhite": [250, 235, 215],
1517
	"aqua": [0, 255, 255],
1518
	"aquamarine": [127, 255, 212],
1519
	"azure": [240, 255, 255],
1520
	"beige": [245, 245, 220],
1521
	"bisque": [255, 228, 196],
1522
	"black": [0, 0, 0],
1523
	"blanchedalmond": [255, 235, 205],
1524
	"blue": [0, 0, 255],
1525
	"blueviolet": [138, 43, 226],
1526
	"brown": [165, 42, 42],
1527
	"burlywood": [222, 184, 135],
1528
	"cadetblue": [95, 158, 160],
1529
	"chartreuse": [127, 255, 0],
1530
	"chocolate": [210, 105, 30],
1531
	"coral": [255, 127, 80],
1532
	"cornflowerblue": [100, 149, 237],
1533
	"cornsilk": [255, 248, 220],
1534
	"crimson": [220, 20, 60],
1535
	"cyan": [0, 255, 255],
1536
	"darkblue": [0, 0, 139],
1537
	"darkcyan": [0, 139, 139],
1538
	"darkgoldenrod": [184, 134, 11],
1539
	"darkgray": [169, 169, 169],
1540
	"darkgreen": [0, 100, 0],
1541
	"darkgrey": [169, 169, 169],
1542
	"darkkhaki": [189, 183, 107],
1543
	"darkmagenta": [139, 0, 139],
1544
	"darkolivegreen": [85, 107, 47],
1545
	"darkorange": [255, 140, 0],
1546
	"darkorchid": [153, 50, 204],
1547
	"darkred": [139, 0, 0],
1548
	"darksalmon": [233, 150, 122],
1549
	"darkseagreen": [143, 188, 143],
1550
	"darkslateblue": [72, 61, 139],
1551
	"darkslategray": [47, 79, 79],
1552
	"darkslategrey": [47, 79, 79],
1553
	"darkturquoise": [0, 206, 209],
1554
	"darkviolet": [148, 0, 211],
1555
	"deeppink": [255, 20, 147],
1556
	"deepskyblue": [0, 191, 255],
1557
	"dimgray": [105, 105, 105],
1558
	"dimgrey": [105, 105, 105],
1559
	"dodgerblue": [30, 144, 255],
1560
	"firebrick": [178, 34, 34],
1561
	"floralwhite": [255, 250, 240],
1562
	"forestgreen": [34, 139, 34],
1563
	"fuchsia": [255, 0, 255],
1564
	"gainsboro": [220, 220, 220],
1565
	"ghostwhite": [248, 248, 255],
1566
	"gold": [255, 215, 0],
1567
	"goldenrod": [218, 165, 32],
1568
	"gray": [128, 128, 128],
1569
	"green": [0, 128, 0],
1570
	"greenyellow": [173, 255, 47],
1571
	"grey": [128, 128, 128],
1572
	"honeydew": [240, 255, 240],
1573
	"hotpink": [255, 105, 180],
1574
	"indianred": [205, 92, 92],
1575
	"indigo": [75, 0, 130],
1576
	"ivory": [255, 255, 240],
1577
	"khaki": [240, 230, 140],
1578
	"lavender": [230, 230, 250],
1579
	"lavenderblush": [255, 240, 245],
1580
	"lawngreen": [124, 252, 0],
1581
	"lemonchiffon": [255, 250, 205],
1582
	"lightblue": [173, 216, 230],
1583
	"lightcoral": [240, 128, 128],
1584
	"lightcyan": [224, 255, 255],
1585
	"lightgoldenrodyellow": [250, 250, 210],
1586
	"lightgray": [211, 211, 211],
1587
	"lightgreen": [144, 238, 144],
1588
	"lightgrey": [211, 211, 211],
1589
	"lightpink": [255, 182, 193],
1590
	"lightsalmon": [255, 160, 122],
1591
	"lightseagreen": [32, 178, 170],
1592
	"lightskyblue": [135, 206, 250],
1593
	"lightslategray": [119, 136, 153],
1594
	"lightslategrey": [119, 136, 153],
1595
	"lightsteelblue": [176, 196, 222],
1596
	"lightyellow": [255, 255, 224],
1597
	"lime": [0, 255, 0],
1598
	"limegreen": [50, 205, 50],
1599
	"linen": [250, 240, 230],
1600
	"magenta": [255, 0, 255],
1601
	"maroon": [128, 0, 0],
1602
	"mediumaquamarine": [102, 205, 170],
1603
	"mediumblue": [0, 0, 205],
1604
	"mediumorchid": [186, 85, 211],
1605
	"mediumpurple": [147, 112, 219],
1606
	"mediumseagreen": [60, 179, 113],
1607
	"mediumslateblue": [123, 104, 238],
1608
	"mediumspringgreen": [0, 250, 154],
1609
	"mediumturquoise": [72, 209, 204],
1610
	"mediumvioletred": [199, 21, 133],
1611
	"midnightblue": [25, 25, 112],
1612
	"mintcream": [245, 255, 250],
1613
	"mistyrose": [255, 228, 225],
1614
	"moccasin": [255, 228, 181],
1615
	"navajowhite": [255, 222, 173],
1616
	"navy": [0, 0, 128],
1617
	"oldlace": [253, 245, 230],
1618
	"olive": [128, 128, 0],
1619
	"olivedrab": [107, 142, 35],
1620
	"orange": [255, 165, 0],
1621
	"orangered": [255, 69, 0],
1622
	"orchid": [218, 112, 214],
1623
	"palegoldenrod": [238, 232, 170],
1624
	"palegreen": [152, 251, 152],
1625
	"paleturquoise": [175, 238, 238],
1626
	"palevioletred": [219, 112, 147],
1627
	"papayawhip": [255, 239, 213],
1628
	"peachpuff": [255, 218, 185],
1629
	"peru": [205, 133, 63],
1630
	"pink": [255, 192, 203],
1631
	"plum": [221, 160, 221],
1632
	"powderblue": [176, 224, 230],
1633
	"purple": [128, 0, 128],
1634
	"rebeccapurple": [102, 51, 153],
1635
	"red": [255, 0, 0],
1636
	"rosybrown": [188, 143, 143],
1637
	"royalblue": [65, 105, 225],
1638
	"saddlebrown": [139, 69, 19],
1639
	"salmon": [250, 128, 114],
1640
	"sandybrown": [244, 164, 96],
1641
	"seagreen": [46, 139, 87],
1642
	"seashell": [255, 245, 238],
1643
	"sienna": [160, 82, 45],
1644
	"silver": [192, 192, 192],
1645
	"skyblue": [135, 206, 235],
1646
	"slateblue": [106, 90, 205],
1647
	"slategray": [112, 128, 144],
1648
	"slategrey": [112, 128, 144],
1649
	"snow": [255, 250, 250],
1650
	"springgreen": [0, 255, 127],
1651
	"steelblue": [70, 130, 180],
1652
	"tan": [210, 180, 140],
1653
	"teal": [0, 128, 128],
1654
	"thistle": [216, 191, 216],
1655
	"tomato": [255, 99, 71],
1656
	"turquoise": [64, 224, 208],
1657
	"violet": [238, 130, 238],
1658
	"wheat": [245, 222, 179],
1659
	"white": [255, 255, 255],
1660
	"whitesmoke": [245, 245, 245],
1661
	"yellow": [255, 255, 0],
1662
	"yellowgreen": [154, 205, 50]
1663
};
1664
},{}],7:[function(require,module,exports){
1665
/**
1666
 * @namespace Chart
1667
 */
1668
var Chart = require(27)();
1669
1670
require(26)(Chart);
1671
require(22)(Chart);
1672
require(25)(Chart);
1673
require(21)(Chart);
1674
require(23)(Chart);
1675
require(24)(Chart);
1676
require(28)(Chart);
1677
require(32)(Chart);
1678
require(30)(Chart);
1679
require(31)(Chart);
1680
require(33)(Chart);
1681
require(29)(Chart);
1682
require(34)(Chart);
1683
1684
require(35)(Chart);
1685
require(36)(Chart);
1686
require(37)(Chart);
1687
require(38)(Chart);
1688
1689
require(41)(Chart);
1690
require(39)(Chart);
1691
require(40)(Chart);
1692
require(42)(Chart);
1693
require(43)(Chart);
1694
require(44)(Chart);
1695
1696
// Controllers must be loaded after elements
1697
// See Chart.core.datasetController.dataElementType
1698
require(15)(Chart);
1699
require(16)(Chart);
1700
require(17)(Chart);
1701
require(18)(Chart);
1702
require(19)(Chart);
1703
require(20)(Chart);
1704
1705
require(8)(Chart);
1706
require(9)(Chart);
1707
require(10)(Chart);
1708
require(11)(Chart);
1709
require(12)(Chart);
1710
require(13)(Chart);
1711
require(14)(Chart);
1712
1713
window.Chart = module.exports = Chart;
1714
1715
},{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"8":8,"9":9}],8:[function(require,module,exports){
1716
"use strict";
1717
1718
module.exports = function(Chart) {
1719
1720
	Chart.Bar = function(context, config) {
1721
		config.type = 'bar';
1722
1723
		return new Chart(context, config);
1724
	};
1725
1726
};
1727
},{}],9:[function(require,module,exports){
1728
"use strict";
1729
1730
module.exports = function(Chart) {
1731
1732
	Chart.Bubble = function(context, config) {
1733
		config.type = 'bubble';
1734
		return new Chart(context, config);
1735
	};
1736
1737
};
1738
},{}],10:[function(require,module,exports){
1739
"use strict";
1740
1741
module.exports = function(Chart) {
1742
1743
	Chart.Doughnut = function(context, config) {
1744
		config.type = 'doughnut';
1745
1746
		return new Chart(context, config);
1747
	};
1748
1749
};
1750
},{}],11:[function(require,module,exports){
1751
"use strict";
1752
1753
module.exports = function(Chart) {
1754
1755
	Chart.Line = function(context, config) {
1756
		config.type = 'line';
1757
1758
		return new Chart(context, config);
1759
	};
1760
1761
};
1762
},{}],12:[function(require,module,exports){
1763
"use strict";
1764
1765
module.exports = function(Chart) {
1766
1767
	Chart.PolarArea = function(context, config) {
1768
		config.type = 'polarArea';
1769
1770
		return new Chart(context, config);
1771
	};
1772
1773
};
1774
},{}],13:[function(require,module,exports){
1775
"use strict";
1776
1777
module.exports = function(Chart) {
1778
	
1779
	Chart.Radar = function(context, config) {
1780
		config.options = Chart.helpers.configMerge({ aspectRatio: 1 }, config.options);
1781
		config.type = 'radar';
1782
1783
		return new Chart(context, config);
1784
	};
1785
1786
};
1787
1788
},{}],14:[function(require,module,exports){
1789
"use strict";
1790
1791
module.exports = function(Chart) {
1792
1793
	var defaultConfig = {
1794
		hover: {
1795
			mode: 'single'
1796
		},
1797
1798
		scales: {
1799
			xAxes: [{
1800
				type: "linear", // scatter should not use a category axis
1801
				position: "bottom",
1802
				id: "x-axis-1" // need an ID so datasets can reference the scale
1803
			}],
1804
			yAxes: [{
1805
				type: "linear",
1806
				position: "left",
1807
				id: "y-axis-1"
1808
			}]
1809
		},
1810
1811
		tooltips: {
1812
			callbacks: {
1813
				title: function() {
1814
					// Title doesn't make sense for scatter since we format the data as a point
1815
					return '';
1816
				},
1817
				label: function(tooltipItem) {
1818
					return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')';
1819
				}
1820
			}
1821
		}
1822
	};
1823
1824
	// Register the default config for this type
1825
	Chart.defaults.scatter = defaultConfig;
1826
1827
	// Scatter charts use line controllers
1828
	Chart.controllers.scatter = Chart.controllers.line;
1829
1830
	Chart.Scatter = function(context, config) {
1831
		config.type = 'scatter';
1832
		return new Chart(context, config);
1833
	};
1834
1835
};
1836
},{}],15:[function(require,module,exports){
1837
"use strict";
1838
1839
module.exports = function(Chart) {
1840
1841
	var helpers = Chart.helpers;
1842
1843
	Chart.defaults.bar = {
1844
		hover: {
1845
			mode: "label"
1846
		},
1847
1848
		scales: {
1849
			xAxes: [{
1850
				type: "category",
1851
1852
				// Specific to Bar Controller
1853
				categoryPercentage: 0.8,
1854
				barPercentage: 0.9,
1855
1856
				// grid line settings
1857
				gridLines: {
1858
					offsetGridLines: true
1859
				}
1860
			}],
1861
			yAxes: [{
1862
				type: "linear"
1863
			}]
1864
		}
1865
	};
1866
1867
	Chart.controllers.bar = Chart.DatasetController.extend({
1868
1869
		dataElementType: Chart.elements.Rectangle,
1870
1871
		initialize: function(chart, datasetIndex) {
1872
			Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);
1873
1874
			// Use this to indicate that this is a bar dataset.
1875
			this.getMeta().bar = true;
1876
		},
1877
1878
		// Get the number of datasets that display bars. We use this to correctly calculate the bar width
1879
		getBarCount: function() {
1880
			var me = this;
1881
			var barCount = 0;
1882
			helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
1883
				var meta = me.chart.getDatasetMeta(datasetIndex);
1884
				if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) {
1885
					++barCount;
1886
				}
1887
			}, me);
1888
			return barCount;
1889
		},
1890
1891
		update: function(reset) {
1892
			var me = this;
1893
			helpers.each(me.getMeta().data, function(rectangle, index) {
1894
				me.updateElement(rectangle, index, reset);
1895
			}, me);
1896
		},
1897
1898
		updateElement: function(rectangle, index, reset) {
1899
			var me = this;
1900
			var meta = me.getMeta();
1901
			var xScale = me.getScaleForId(meta.xAxisID);
1902
			var yScale = me.getScaleForId(meta.yAxisID);
1903
			var scaleBase = yScale.getBasePixel();
1904
			var rectangleElementOptions = me.chart.options.elements.rectangle;
1905
			var custom = rectangle.custom || {};
1906
			var dataset = me.getDataset();
1907
1908
			helpers.extend(rectangle, {
1909
				// Utility
1910
				_xScale: xScale,
1911
				_yScale: yScale,
1912
				_datasetIndex: me.index,
1913
				_index: index,
1914
1915
				// Desired view properties
1916
				_model: {
1917
					x: me.calculateBarX(index, me.index),
1918
					y: reset ? scaleBase : me.calculateBarY(index, me.index),
1919
1920
					// Tooltip
1921
					label: me.chart.data.labels[index],
1922
					datasetLabel: dataset.label,
1923
1924
					// Appearance
1925
					base: reset ? scaleBase : me.calculateBarBase(me.index, index),
1926
					width: me.calculateBarWidth(index),
1927
					backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
1928
					borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
1929
					borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
1930
					borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
1931
				}
1932
			});
1933
			rectangle.pivot();
1934
		},
1935
1936
		calculateBarBase: function(datasetIndex, index) {
1937
			var me = this;
1938
			var meta = me.getMeta();
1939
			var yScale = me.getScaleForId(meta.yAxisID);
1940
			var base = 0;
1941
1942
			if (yScale.options.stacked) {
1943
				var chart = me.chart;
1944
				var datasets = chart.data.datasets;
1945
				var value = Number(datasets[datasetIndex].data[index]);
1946
1947
				for (var i = 0; i < datasetIndex; i++) {
1948
					var currentDs = datasets[i];
1949
					var currentDsMeta = chart.getDatasetMeta(i);
1950
					if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
1951
						var currentVal = Number(currentDs.data[index]);
1952
						base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
1953
					}
1954
				}
1955
1956
				return yScale.getPixelForValue(base);
1957
			}
1958
1959
			return yScale.getBasePixel();
1960
		},
1961
1962
		getRuler: function(index) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1963
			var me = this;
1964
			var meta = me.getMeta();
1965
			var xScale = me.getScaleForId(meta.xAxisID);
1966
			var datasetCount = me.getBarCount();
1967
1968
			var tickWidth;
1969
1970
			if (xScale.options.type === 'category') {
1971
				tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index);
1972
			} else {
1973
				// Average width
1974
				tickWidth = xScale.width / xScale.ticks.length;
1975
			}
1976
			var categoryWidth = tickWidth * xScale.options.categoryPercentage;
1977
			var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
1978
			var fullBarWidth = categoryWidth / datasetCount;
1979
1980
			if (xScale.ticks.length !== me.chart.data.labels.length) {
1981
			    var perc = xScale.ticks.length / me.chart.data.labels.length;
1982
			    fullBarWidth = fullBarWidth * perc;
1983
			}
1984
1985
			var barWidth = fullBarWidth * xScale.options.barPercentage;
1986
			var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
1987
1988
			return {
1989
				datasetCount: datasetCount,
1990
				tickWidth: tickWidth,
1991
				categoryWidth: categoryWidth,
1992
				categorySpacing: categorySpacing,
1993
				fullBarWidth: fullBarWidth,
1994
				barWidth: barWidth,
1995
				barSpacing: barSpacing
1996
			};
1997
		},
1998
1999
		calculateBarWidth: function(index) {
2000
			var xScale = this.getScaleForId(this.getMeta().xAxisID);
2001
			if (xScale.options.barThickness) {
2002
				return xScale.options.barThickness;
2003
			}
2004
			var ruler = this.getRuler(index);
2005
			return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
2006
		},
2007
2008
		// Get bar index from the given dataset index accounting for the fact that not all bars are visible
2009
		getBarIndex: function(datasetIndex) {
2010
			var barIndex = 0;
2011
			var meta, j;
2012
2013
			for (j = 0; j < datasetIndex; ++j) {
2014
				meta = this.chart.getDatasetMeta(j);
2015
				if (meta.bar && this.chart.isDatasetVisible(j)) {
2016
					++barIndex;
2017
				}
2018
			}
2019
2020
			return barIndex;
2021
		},
2022
2023
		calculateBarX: function(index, datasetIndex) {
2024
			var me = this;
2025
			var meta = me.getMeta();
2026
			var xScale = me.getScaleForId(meta.xAxisID);
2027
			var barIndex = me.getBarIndex(datasetIndex);
2028
2029
			var ruler = me.getRuler(index);
2030
			var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
2031
			leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0;
2032
2033
			if (xScale.options.stacked) {
2034
				return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
2035
			}
2036
2037
			return leftTick +
2038
				(ruler.barWidth / 2) +
2039
				ruler.categorySpacing +
2040
				(ruler.barWidth * barIndex) +
2041
				(ruler.barSpacing / 2) +
2042
				(ruler.barSpacing * barIndex);
2043
		},
2044
2045
		calculateBarY: function(index, datasetIndex) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2046
			var me = this;
2047
			var meta = me.getMeta();
2048
			var yScale = me.getScaleForId(meta.yAxisID);
2049
			var value = Number(me.getDataset().data[index]);
2050
2051
			if (yScale.options.stacked) {
2052
2053
				var sumPos = 0,
2054
					sumNeg = 0;
2055
2056
				for (var i = 0; i < datasetIndex; i++) {
2057
					var ds = me.chart.data.datasets[i];
2058
					var dsMeta = me.chart.getDatasetMeta(i);
2059
					if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i)) {
2060
						var stackedVal = Number(ds.data[index]);
2061
						if (stackedVal < 0) {
2062
							sumNeg += stackedVal || 0;
2063
						} else {
2064
							sumPos += stackedVal || 0;
2065
						}
2066
					}
2067
				}
2068
2069
				if (value < 0) {
2070
					return yScale.getPixelForValue(sumNeg + value);
2071
				} else {
2072
					return yScale.getPixelForValue(sumPos + value);
2073
				}
2074
			}
2075
2076
			return yScale.getPixelForValue(value);
2077
		},
2078
2079
		draw: function(ease) {
2080
			var me = this;
2081
			var easingDecimal = ease || 1;
2082
			helpers.each(me.getMeta().data, function(rectangle, index) {
2083
				var d = me.getDataset().data[index];
2084
				if (d !== null && d !== undefined && !isNaN(d)) {
2085
					rectangle.transition(easingDecimal).draw();
2086
				}
2087
			}, me);
2088
		},
2089
2090
		setHoverStyle: function(rectangle) {
2091
			var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2092
			var index = rectangle._index;
2093
2094
			var custom = rectangle.custom || {};
2095
			var model = rectangle._model;
2096
			model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
2097
			model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
2098
			model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
2099
		},
2100
2101
		removeHoverStyle: function(rectangle) {
2102
			var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2103
			var index = rectangle._index;
2104
			var custom = rectangle.custom || {};
2105
			var model = rectangle._model;
2106
			var rectangleElementOptions = this.chart.options.elements.rectangle;
2107
2108
			model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
2109
			model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
2110
			model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
2111
		}
2112
2113
	});
2114
2115
2116
	// including horizontalBar in the bar file, instead of a file of its own
2117
	// it extends bar (like pie extends doughnut)
2118
	Chart.defaults.horizontalBar = {
2119
		hover: {
2120
			mode: "label"
2121
		},
2122
2123
		scales: {
2124
			xAxes: [{
2125
				type: "linear",
2126
				position: "bottom"
2127
			}],
2128
			yAxes: [{
2129
				position: "left",
2130
				type: "category",
2131
2132
				// Specific to Horizontal Bar Controller
2133
				categoryPercentage: 0.8,
2134
				barPercentage: 0.9,
2135
2136
				// grid line settings
2137
				gridLines: {
2138
					offsetGridLines: true
2139
				}
2140
			}]
2141
		},
2142
		elements: {
2143
			rectangle: {
2144
				borderSkipped: 'left'
2145
			}
2146
		},
2147
		tooltips: {
2148
			callbacks: {
2149
				title: function(tooltipItems, data) {
2150
					// Pick first xLabel for now
2151
					var title = '';
2152
2153
					if (tooltipItems.length > 0) {
2154
						if (tooltipItems[0].yLabel) {
2155
							title = tooltipItems[0].yLabel;
2156
						} else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) {
2157
							title = data.labels[tooltipItems[0].index];
2158
						}
2159
					}
2160
2161
					return title;
2162
				},
2163
				label: function(tooltipItem, data) {
2164
					var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
2165
				return datasetLabel + ': ' + tooltipItem.xLabel;
2166
				}
2167
			}
2168
		}
2169
	};
2170
2171
	Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
2172
		updateElement: function(rectangle, index, reset) {
2173
			var me = this;
2174
			var meta = me.getMeta();
2175
			var xScale = me.getScaleForId(meta.xAxisID);
2176
			var yScale = me.getScaleForId(meta.yAxisID);
2177
			var scaleBase = xScale.getBasePixel();
2178
			var custom = rectangle.custom || {};
2179
			var dataset = me.getDataset();
2180
			var rectangleElementOptions = me.chart.options.elements.rectangle;
2181
2182
			helpers.extend(rectangle, {
2183
				// Utility
2184
				_xScale: xScale,
2185
				_yScale: yScale,
2186
				_datasetIndex: me.index,
2187
				_index: index,
2188
2189
				// Desired view properties
2190
				_model: {
2191
					x: reset ? scaleBase : me.calculateBarX(index, me.index),
2192
					y: me.calculateBarY(index, me.index),
2193
2194
					// Tooltip
2195
					label: me.chart.data.labels[index],
2196
					datasetLabel: dataset.label,
2197
2198
					// Appearance
2199
					base: reset ? scaleBase : me.calculateBarBase(me.index, index),
2200
					height: me.calculateBarHeight(index),
2201
					backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
2202
					borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
2203
					borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
2204
					borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
2205
				},
2206
2207
				draw: function () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2208
					var ctx = this._chart.ctx;
2209
					var vm = this._view;
2210
2211
					var halfHeight = vm.height / 2,
2212
						topY = vm.y - halfHeight,
2213
						bottomY = vm.y + halfHeight,
2214
						right = vm.base - (vm.base - vm.x),
2215
						halfStroke = vm.borderWidth / 2;
2216
2217
					// Canvas doesn't allow us to stroke inside the width so we can
2218
					// adjust the sizes to fit if we're setting a stroke on the line
2219
					if (vm.borderWidth) {
2220
						topY += halfStroke;
2221
						bottomY -= halfStroke;
2222
						right += halfStroke;
2223
					}
2224
2225
					ctx.beginPath();
2226
2227
					ctx.fillStyle = vm.backgroundColor;
2228
					ctx.strokeStyle = vm.borderColor;
2229
					ctx.lineWidth = vm.borderWidth;
2230
2231
					// Corner points, from bottom-left to bottom-right clockwise
2232
					// | 1 2 |
2233
					// | 0 3 |
2234
					var corners = [
2235
						[vm.base, bottomY],
2236
						[vm.base, topY],
2237
						[right, topY],
2238
						[right, bottomY]
2239
					];
2240
2241
					// Find first (starting) corner with fallback to 'bottom'
2242
					var borders = ['bottom', 'left', 'top', 'right'];
2243
					var startCorner = borders.indexOf(vm.borderSkipped, 0);
2244
					if (startCorner === -1)
2245
						startCorner = 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
2246
2247
					function cornerAt(index) {
2248
						return corners[(startCorner + index) % 4];
2249
					}
2250
2251
					// Draw rectangle from 'startCorner'
2252
					ctx.moveTo.apply(ctx, cornerAt(0));
2253
					for (var i = 1; i < 4; i++)
2254
						ctx.lineTo.apply(ctx, cornerAt(i));
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
2255
2256
					ctx.fill();
2257
					if (vm.borderWidth) {
2258
						ctx.stroke();
2259
					}
2260
				},
2261
2262
				inRange: function (mouseX, mouseY) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2263
					var vm = this._view;
2264
					var inRange = false;
2265
2266
					if (vm) {
2267
						if (vm.x < vm.base) {
2268
							inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base);
2269
						} else {
2270
							inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x);
2271
						}
2272
					}
2273
2274
					return inRange;
2275
				}
2276
			});
2277
2278
			rectangle.pivot();
2279
		},
2280
2281
		calculateBarBase: function (datasetIndex, index) {
2282
			var me = this;
2283
			var meta = me.getMeta();
2284
			var xScale = me.getScaleForId(meta.xAxisID);
2285
			var base = 0;
2286
2287
			if (xScale.options.stacked) {
2288
				var chart = me.chart;
2289
				var datasets = chart.data.datasets;
2290
				var value = Number(datasets[datasetIndex].data[index]);
2291
2292
				for (var i = 0; i < datasetIndex; i++) {
2293
					var currentDs = datasets[i];
2294
					var currentDsMeta = chart.getDatasetMeta(i);
2295
					if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i)) {
2296
						var currentVal = Number(currentDs.data[index]);
2297
						base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
2298
					}
2299
				}
2300
2301
				return xScale.getPixelForValue(base);
2302
			}
2303
2304
			return xScale.getBasePixel();
2305
		},
2306
2307
		getRuler: function (index) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2308
			var me = this;
2309
			var meta = me.getMeta();
2310
			var yScale = me.getScaleForId(meta.yAxisID);
2311
			var datasetCount = me.getBarCount();
2312
2313
			var tickHeight;
2314
			if (yScale.options.type === 'category') {
2315
				tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index);
2316
			} else {
2317
				// Average width
2318
				tickHeight = yScale.width / yScale.ticks.length;
2319
			}
2320
			var categoryHeight = tickHeight * yScale.options.categoryPercentage;
2321
			var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;
2322
			var fullBarHeight = categoryHeight / datasetCount;
2323
2324
			if (yScale.ticks.length !== me.chart.data.labels.length) {
2325
				var perc = yScale.ticks.length / me.chart.data.labels.length;
2326
				fullBarHeight = fullBarHeight * perc;
2327
			}
2328
2329
			var barHeight = fullBarHeight * yScale.options.barPercentage;
2330
			var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage);
2331
2332
			return {
2333
				datasetCount: datasetCount,
2334
				tickHeight: tickHeight,
2335
				categoryHeight: categoryHeight,
2336
				categorySpacing: categorySpacing,
2337
				fullBarHeight: fullBarHeight,
2338
				barHeight: barHeight,
2339
				barSpacing: barSpacing,
2340
			};
2341
		},
2342
2343
		calculateBarHeight: function (index) {
2344
			var me = this;
2345
			var yScale = me.getScaleForId(me.getMeta().yAxisID);
2346
			if (yScale.options.barThickness) {
2347
				return yScale.options.barThickness;
2348
			}
2349
			var ruler = me.getRuler(index);
2350
			return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight;
2351
		},
2352
2353
		calculateBarX: function (index, datasetIndex) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2354
			var me = this;
2355
			var meta = me.getMeta();
2356
			var xScale = me.getScaleForId(meta.xAxisID);
2357
			var value = Number(me.getDataset().data[index]);
2358
2359
			if (xScale.options.stacked) {
2360
2361
				var sumPos = 0,
2362
					sumNeg = 0;
2363
2364
				for (var i = 0; i < datasetIndex; i++) {
2365
					var ds = me.chart.data.datasets[i];
2366
					var dsMeta = me.chart.getDatasetMeta(i);
2367
					if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i)) {
2368
						var stackedVal = Number(ds.data[index]);
2369
						if (stackedVal < 0) {
2370
							sumNeg += stackedVal || 0;
2371
						} else {
2372
							sumPos += stackedVal || 0;
2373
						}
2374
					}
2375
				}
2376
2377
				if (value < 0) {
2378
					return xScale.getPixelForValue(sumNeg + value);
2379
				} else {
2380
					return xScale.getPixelForValue(sumPos + value);
2381
				}
2382
			}
2383
2384
			return xScale.getPixelForValue(value);
2385
		},
2386
2387
		calculateBarY: function (index, datasetIndex) {
2388
			var me = this;
2389
			var meta = me.getMeta();
2390
			var yScale = me.getScaleForId(meta.yAxisID);
2391
			var barIndex = me.getBarIndex(datasetIndex);
2392
2393
			var ruler = me.getRuler(index);
2394
			var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
2395
			topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0;
2396
2397
			if (yScale.options.stacked) {
2398
				return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing;
2399
			}
2400
2401
			return topTick +
2402
				(ruler.barHeight / 2) +
2403
				ruler.categorySpacing +
2404
				(ruler.barHeight * barIndex) +
2405
				(ruler.barSpacing / 2) +
2406
				(ruler.barSpacing * barIndex);
2407
		}
2408
	});
2409
};
2410
2411
},{}],16:[function(require,module,exports){
2412
"use strict";
2413
2414
module.exports = function(Chart) {
2415
2416
	var helpers = Chart.helpers;
2417
2418
	Chart.defaults.bubble = {
2419
		hover: {
2420
			mode: "single"
2421
		},
2422
2423
		scales: {
2424
			xAxes: [{
2425
				type: "linear", // bubble should probably use a linear scale by default
2426
				position: "bottom",
2427
				id: "x-axis-0" // need an ID so datasets can reference the scale
2428
			}],
2429
			yAxes: [{
2430
				type: "linear",
2431
				position: "left",
2432
				id: "y-axis-0"
2433
			}]
2434
		},
2435
2436
		tooltips: {
2437
			callbacks: {
2438
				title: function() {
2439
					// Title doesn't make sense for scatter since we format the data as a point
2440
					return '';
2441
				},
2442
				label: function(tooltipItem, data) {
2443
					var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
2444
					var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2445
					return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')';
2446
				}
2447
			}
2448
		}
2449
	};
2450
2451
	Chart.controllers.bubble = Chart.DatasetController.extend({
2452
2453
		dataElementType: Chart.elements.Point,
2454
2455
		update: function(reset) {
2456
			var me = this;
2457
			var meta = me.getMeta();
2458
			var points = meta.data;
2459
2460
			// Update Points
2461
			helpers.each(points, function(point, index) {
2462
				me.updateElement(point, index, reset);
2463
			});
2464
		},
2465
2466
		updateElement: function(point, index, reset) {
2467
			var me = this;
2468
			var meta = me.getMeta();
2469
			var xScale = me.getScaleForId(meta.xAxisID);
2470
			var yScale = me.getScaleForId(meta.yAxisID);
2471
2472
			var custom = point.custom || {};
2473
			var dataset = me.getDataset();
2474
			var data = dataset.data[index];
2475
			var pointElementOptions = me.chart.options.elements.point;
2476
			var dsIndex = me.index;
2477
2478
			helpers.extend(point, {
2479
				// Utility
2480
				_xScale: xScale,
2481
				_yScale: yScale,
2482
				_datasetIndex: dsIndex,
2483
				_index: index,
2484
2485
				// Desired view properties
2486
				_model: {
2487
					x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex, me.chart.isCombo),
2488
					y: reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex),
2489
					// Appearance
2490
					radius: reset ? 0 : custom.radius ? custom.radius : me.getRadius(data),
2491
2492
					// Tooltip
2493
					hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)
2494
				}
2495
			});
2496
2497
			// Trick to reset the styles of the point
2498
			Chart.DatasetController.prototype.removeHoverStyle.call(me, point, pointElementOptions);
2499
2500
			var model = point._model;
2501
			model.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y));
2502
2503
			point.pivot();
2504
		},
2505
2506
		getRadius: function(value) {
2507
			return value.r || this.chart.options.elements.point.radius;
2508
		},
2509
2510
		setHoverStyle: function(point) {
2511
			var me = this;
2512
			Chart.DatasetController.prototype.setHoverStyle.call(me, point);
2513
2514
			// Radius
2515
			var dataset = me.chart.data.datasets[point._datasetIndex];
2516
			var index = point._index;
2517
			var custom = point.custom || {};
2518
			var model = point._model;
2519
			model.radius = custom.hoverRadius ? custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, me.chart.options.elements.point.hoverRadius)) + me.getRadius(dataset.data[index]);
2520
		},
2521
2522
		removeHoverStyle: function(point) {
2523
			var me = this;
2524
			Chart.DatasetController.prototype.removeHoverStyle.call(me, point, me.chart.options.elements.point);
2525
2526
			var dataVal = me.chart.data.datasets[point._datasetIndex].data[point._index];
2527
			var custom = point.custom || {};
2528
			var model = point._model;
2529
2530
			model.radius = custom.radius ? custom.radius : me.getRadius(dataVal);
2531
		}
2532
	});
2533
};
2534
2535
},{}],17:[function(require,module,exports){
2536
"use strict";
2537
2538
module.exports = function(Chart) {
2539
2540
	var helpers = Chart.helpers,
2541
		defaults = Chart.defaults;
2542
2543
	defaults.doughnut = {
2544
		animation: {
2545
			//Boolean - Whether we animate the rotation of the Doughnut
2546
			animateRotate: true,
2547
			//Boolean - Whether we animate scaling the Doughnut from the centre
2548
			animateScale: false
2549
		},
2550
		aspectRatio: 1,
2551
		hover: {
2552
			mode: 'single'
2553
		},
2554
		legendCallback: function(chart) {
2555
			var text = [];
2556
			text.push('<ul class="' + chart.id + '-legend">');
2557
2558
			var data = chart.data;
2559
			var datasets = data.datasets;
2560
			var labels = data.labels;
2561
2562
			if (datasets.length) {
2563
				for (var i = 0; i < datasets[0].data.length; ++i) {
2564
					text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
2565
					if (labels[i]) {
2566
						text.push(labels[i]);
2567
					}
2568
					text.push('</li>');
2569
				}
2570
			}
2571
2572
			text.push('</ul>');
2573
			return text.join("");
2574
		},
2575
		legend: {
2576
			labels: {
2577
				generateLabels: function(chart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2578
					var data = chart.data;
2579
					if (data.labels.length && data.datasets.length) {
2580
						return data.labels.map(function(label, i) {
2581
							var meta = chart.getDatasetMeta(0);
2582
							var ds = data.datasets[0];
2583
							var arc = meta.data[i];
2584
							var custom = arc && arc.custom || {};
2585
							var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
2586
							var arcOpts = chart.options.elements.arc;
2587
							var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
2588
							var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
2589
							var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
2590
2591
							return {
2592
								text: label,
2593
								fillStyle: fill,
2594
								strokeStyle: stroke,
2595
								lineWidth: bw,
2596
								hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
2597
2598
								// Extra data used for toggling the correct item
2599
								index: i
2600
							};
2601
						});
2602
					} else {
2603
						return [];
2604
					}
2605
				}
2606
			},
2607
2608
			onClick: function(e, legendItem) {
2609
				var index = legendItem.index;
2610
				var chart = this.chart;
2611
				var i, ilen, meta;
2612
2613
				for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
2614
					meta = chart.getDatasetMeta(i);
2615
					meta.data[index].hidden = !meta.data[index].hidden;
2616
				}
2617
2618
				chart.update();
2619
			}
2620
		},
2621
2622
		//The percentage of the chart that we cut out of the middle.
2623
		cutoutPercentage: 50,
2624
2625
		//The rotation of the chart, where the first data arc begins.
2626
		rotation: Math.PI * -0.5,
2627
2628
		//The total circumference of the chart.
2629
		circumference: Math.PI * 2.0,
2630
2631
		// Need to override these to give a nice default
2632
		tooltips: {
2633
			callbacks: {
2634
				title: function() {
2635
					return '';
2636
				},
2637
				label: function(tooltipItem, data) {
2638
					return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2639
				}
2640
			}
2641
		}
2642
	};
2643
2644
	defaults.pie = helpers.clone(defaults.doughnut);
2645
	helpers.extend(defaults.pie, {
2646
		cutoutPercentage: 0
2647
	});
2648
2649
2650
	Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
2651
2652
		dataElementType: Chart.elements.Arc,
2653
2654
		linkScales: helpers.noop,
2655
2656
		// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
2657
		getRingIndex: function(datasetIndex) {
2658
			var ringIndex = 0;
2659
2660
			for (var j = 0; j < datasetIndex; ++j) {
2661
				if (this.chart.isDatasetVisible(j)) {
2662
					++ringIndex;
2663
				}
2664
			}
2665
2666
			return ringIndex;
2667
		},
2668
2669
		update: function(reset) {
2670
			var me = this;
2671
			var chart = me.chart,
2672
				chartArea = chart.chartArea,
2673
				opts = chart.options,
2674
				arcOpts = opts.elements.arc,
2675
				availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth,
2676
				availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth,
2677
				minSize = Math.min(availableWidth, availableHeight),
2678
				offset = {
2679
					x: 0,
2680
					y: 0
2681
				},
2682
				meta = me.getMeta(),
2683
				cutoutPercentage = opts.cutoutPercentage,
2684
				circumference = opts.circumference;
2685
2686
			// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
2687
			if (circumference < Math.PI * 2.0) {
2688
				var startAngle = opts.rotation % (Math.PI * 2.0);
2689
				startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
2690
				var endAngle = startAngle + circumference;
2691
				var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
2692
				var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
2693
				var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
2694
				var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
2695
				var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
2696
				var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
2697
				var cutout = cutoutPercentage / 100.0;
2698
				var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
2699
				var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
2700
				var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
2701
				minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
2702
				offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
2703
			}
2704
            chart.borderWidth = me.getMaxBorderWidth(meta.data);
2705
2706
			chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
2707
			chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0);
2708
			chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
2709
			chart.offsetX = offset.x * chart.outerRadius;
2710
			chart.offsetY = offset.y * chart.outerRadius;
2711
2712
			meta.total = me.calculateTotal();
2713
2714
			me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
2715
			me.innerRadius = me.outerRadius - chart.radiusLength;
2716
2717
			helpers.each(meta.data, function(arc, index) {
2718
				me.updateElement(arc, index, reset);
2719
			});
2720
		},
2721
2722
		updateElement: function(arc, index, reset) {
2723
			var me = this;
2724
			var chart = me.chart,
2725
				chartArea = chart.chartArea,
2726
				opts = chart.options,
2727
				animationOpts = opts.animation,
2728
				centerX = (chartArea.left + chartArea.right) / 2,
2729
				centerY = (chartArea.top + chartArea.bottom) / 2,
2730
				startAngle = opts.rotation, // non reset case handled later
2731
				endAngle = opts.rotation, // non reset case handled later
2732
				dataset = me.getDataset(),
2733
				circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)),
2734
				innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius,
2735
				outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius,
2736
				valueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
2737
2738
			helpers.extend(arc, {
2739
				// Utility
2740
				_datasetIndex: me.index,
2741
				_index: index,
2742
2743
				// Desired view properties
2744
				_model: {
2745
					x: centerX + chart.offsetX,
2746
					y: centerY + chart.offsetY,
2747
					startAngle: startAngle,
2748
					endAngle: endAngle,
2749
					circumference: circumference,
2750
					outerRadius: outerRadius,
2751
					innerRadius: innerRadius,
2752
					label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
2753
				}
2754
			});
2755
2756
			var model = arc._model;
2757
			// Resets the visual styles
2758
			this.removeHoverStyle(arc);
2759
2760
			// Set correct angles if not resetting
2761
			if (!reset || !animationOpts.animateRotate) {
2762
				if (index === 0) {
2763
					model.startAngle = opts.rotation;
2764
				} else {
2765
					model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
2766
				}
2767
2768
				model.endAngle = model.startAngle + model.circumference;
2769
			}
2770
2771
			arc.pivot();
2772
		},
2773
2774
		removeHoverStyle: function(arc) {
2775
			Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
2776
		},
2777
2778
		calculateTotal: function() {
2779
			var dataset = this.getDataset();
2780
			var meta = this.getMeta();
2781
			var total = 0;
2782
			var value;
2783
2784
			helpers.each(meta.data, function(element, index) {
2785
				value = dataset.data[index];
2786
				if (!isNaN(value) && !element.hidden) {
2787
					total += Math.abs(value);
2788
				}
2789
			});
2790
2791
			/*if (total === 0) {
2792
				total = NaN;
2793
			}*/
2794
2795
			return total;
2796
		},
2797
2798
		calculateCircumference: function(value) {
2799
			var total = this.getMeta().total;
2800
			if (total > 0 && !isNaN(value)) {
2801
				return (Math.PI * 2.0) * (value / total);
2802
			} else {
2803
				return 0;
2804
			}
2805
		},
2806
		
2807
		//gets the max border or hover width to properly scale pie charts
2808
        getMaxBorderWidth: function (elements) {
2809
            var max = 0,
2810
				index = this.index,
2811
				length = elements.length,
2812
				borderWidth,
2813
				hoverWidth;
2814
2815
            for (var i = 0; i < length; i++) {
2816
               	borderWidth = elements[i]._model ? elements[i]._model.borderWidth : 0;
2817
                hoverWidth = elements[i]._chart ? elements[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
2818
				
2819
                max = borderWidth > max ? borderWidth : max;
2820
                max = hoverWidth > max ? hoverWidth : max;
2821
            }
2822
            return max;
2823
        }
2824
	});
2825
};
2826
2827
},{}],18:[function(require,module,exports){
2828
"use strict";
2829
2830
module.exports = function(Chart) {
2831
2832
	var helpers = Chart.helpers;
2833
2834
	Chart.defaults.line = {
2835
		showLines: true,
2836
		spanGaps: false,
2837
2838
		hover: {
2839
			mode: "label"
2840
		},
2841
2842
		scales: {
2843
			xAxes: [{
2844
				type: "category",
2845
				id: 'x-axis-0'
2846
			}],
2847
			yAxes: [{
2848
				type: "linear",
2849
				id: 'y-axis-0'
2850
			}]
2851
		}
2852
	};
2853
2854
	function lineEnabled(dataset, options) {
2855
		return helpers.getValueOrDefault(dataset.showLine, options.showLines);
2856
	}
2857
2858
	Chart.controllers.line = Chart.DatasetController.extend({
2859
2860
		datasetElementType: Chart.elements.Line,
2861
2862
		dataElementType: Chart.elements.Point,
2863
2864
		addElementAndReset: function(index) {
2865
			var me = this;
2866
			var options = me.chart.options;
2867
			var meta = me.getMeta();
2868
2869
			Chart.DatasetController.prototype.addElementAndReset.call(me, index);
2870
2871
			// Make sure bezier control points are updated
2872
			if (lineEnabled(me.getDataset(), options) && meta.dataset._model.tension !== 0) {
2873
				me.updateBezierControlPoints();
2874
			}
2875
		},
2876
2877
		update: function(reset) {
2878
			var me = this;
2879
			var meta = me.getMeta();
2880
			var line = meta.dataset;
2881
			var points = meta.data || [];
2882
			var options = me.chart.options;
2883
			var lineElementOptions = options.elements.line;
2884
			var scale = me.getScaleForId(meta.yAxisID);
2885
			var i, ilen, custom;
2886
			var dataset = me.getDataset();
2887
			var showLine = lineEnabled(dataset, options);
2888
2889
			// Update Line
2890
			if (showLine) {
2891
				custom = line.custom || {};
2892
2893
				// Compatibility: If the properties are defined with only the old name, use those values
2894
				if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
2895
					dataset.lineTension = dataset.tension;
2896
				}
2897
2898
				// Utility
2899
				line._scale = scale;
2900
				line._datasetIndex = me.index;
2901
				// Data
2902
				line._children = points;
2903
				// Model
2904
				line._model = {
2905
					// Appearance
2906
					// The default behavior of lines is to break at null values, according
2907
					// to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
2908
					// This option gives linse the ability to span gaps
2909
					spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
2910
					tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),
2911
					backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
2912
					borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
2913
					borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
2914
					borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
2915
					borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
2916
					borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
2917
					borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
2918
					fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
2919
					steppedLine: custom.steppedLine ? custom.steppedLine : helpers.getValueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
2920
					// Scale
2921
					scaleTop: scale.top,
2922
					scaleBottom: scale.bottom,
2923
					scaleZero: scale.getBasePixel()
2924
				};
2925
2926
				line.pivot();
2927
			}
2928
2929
			// Update Points
2930
			for (i=0, ilen=points.length; i<ilen; ++i) {
2931
				me.updateElement(points[i], i, reset);
2932
			}
2933
2934
			if (showLine && line._model.tension !== 0) {
2935
				me.updateBezierControlPoints();
2936
			}
2937
2938
			// Now pivot the point for animation
2939
			for (i=0, ilen=points.length; i<ilen; ++i) {
2940
				points[i].pivot();
2941
			}
2942
		},
2943
2944
		getPointBackgroundColor: function(point, index) {
2945
			var backgroundColor = this.chart.options.elements.point.backgroundColor;
2946
			var dataset = this.getDataset();
2947
			var custom = point.custom || {};
2948
2949
			if (custom.backgroundColor) {
2950
				backgroundColor = custom.backgroundColor;
2951
			} else if (dataset.pointBackgroundColor) {
2952
				backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
2953
			} else if (dataset.backgroundColor) {
2954
				backgroundColor = dataset.backgroundColor;
2955
			}
2956
2957
			return backgroundColor;
2958
		},
2959
2960
		getPointBorderColor: function(point, index) {
2961
			var borderColor = this.chart.options.elements.point.borderColor;
2962
			var dataset = this.getDataset();
2963
			var custom = point.custom || {};
2964
2965
			if (custom.borderColor) {
2966
				borderColor = custom.borderColor;
2967
			} else if (dataset.pointBorderColor) {
2968
				borderColor = helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
2969
			} else if (dataset.borderColor) {
2970
				borderColor = dataset.borderColor;
2971
			}
2972
2973
			return borderColor;
2974
		},
2975
2976
		getPointBorderWidth: function(point, index) {
2977
			var borderWidth = this.chart.options.elements.point.borderWidth;
2978
			var dataset = this.getDataset();
2979
			var custom = point.custom || {};
2980
2981
			if (custom.borderWidth) {
2982
				borderWidth = custom.borderWidth;
2983
			} else if (dataset.pointBorderWidth) {
2984
				borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
2985
			} else if (dataset.borderWidth) {
2986
				borderWidth = dataset.borderWidth;
2987
			}
2988
2989
			return borderWidth;
2990
		},
2991
2992
		updateElement: function(point, index, reset) {
2993
			var me = this;
2994
			var meta = me.getMeta();
2995
			var custom = point.custom || {};
2996
			var dataset = me.getDataset();
2997
			var datasetIndex = me.index;
2998
			var value = dataset.data[index];
2999
			var yScale = me.getScaleForId(meta.yAxisID);
3000
			var xScale = me.getScaleForId(meta.xAxisID);
3001
			var pointOptions = me.chart.options.elements.point;
3002
			var x, y;
3003
3004
			// Compatibility: If the properties are defined with only the old name, use those values
3005
			if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3006
				dataset.pointRadius = dataset.radius;
3007
			}
3008
			if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
3009
				dataset.pointHitRadius = dataset.hitRadius;
3010
			}
3011
3012
			x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex, me.chart.isCombo);
3013
			y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
3014
3015
			// Utility
3016
			point._xScale = xScale;
3017
			point._yScale = yScale;
3018
			point._datasetIndex = datasetIndex;
3019
			point._index = index;
3020
3021
			// Desired view properties
3022
			point._model = {
3023
				x: x,
3024
				y: y,
3025
				skip: custom.skip || isNaN(x) || isNaN(y),
3026
				// Appearance
3027
				radius: custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
3028
				pointStyle: custom.pointStyle || helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
3029
				backgroundColor: me.getPointBackgroundColor(point, index),
3030
				borderColor: me.getPointBorderColor(point, index),
3031
				borderWidth: me.getPointBorderWidth(point, index),
3032
				tension: meta.dataset._model ? meta.dataset._model.tension : 0,
3033
				steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
3034
				// Tooltip
3035
				hitRadius: custom.hitRadius || helpers.getValueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
3036
			};
3037
		},
3038
3039
		calculatePointY: function(value, index, datasetIndex) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3040
			var me = this;
3041
			var chart = me.chart;
3042
			var meta = me.getMeta();
3043
			var yScale = me.getScaleForId(meta.yAxisID);
3044
			var sumPos = 0;
3045
			var sumNeg = 0;
3046
			var i, ds, dsMeta;
3047
3048
			if (yScale.options.stacked) {
3049
				for (i = 0; i < datasetIndex; i++) {
3050
					ds = chart.data.datasets[i];
3051
					dsMeta = chart.getDatasetMeta(i);
3052
					if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
3053
						var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
3054
						if (stackedRightValue < 0) {
3055
							sumNeg += stackedRightValue || 0;
3056
						} else {
3057
							sumPos += stackedRightValue || 0;
3058
						}
3059
					}
3060
				}
3061
3062
				var rightValue = Number(yScale.getRightValue(value));
3063
				if (rightValue < 0) {
3064
					return yScale.getPixelForValue(sumNeg + rightValue);
3065
				} else {
3066
					return yScale.getPixelForValue(sumPos + rightValue);
3067
				}
3068
			}
3069
3070
			return yScale.getPixelForValue(value);
3071
		},
3072
3073
		updateBezierControlPoints: function() {
3074
			var me = this;
3075
			var meta = me.getMeta();
3076
			var area = me.chart.chartArea;
3077
3078
			// only consider points that are drawn in case the spanGaps option is ued
3079
			var points = (meta.data || []).filter(function(pt) { return !pt._model.skip; });
3080
			var i, ilen, point, model, controlPoints;
3081
3082
			var needToCap = me.chart.options.elements.line.capBezierPoints;
3083
			function capIfNecessary(pt, min, max) {
3084
				return needToCap ? Math.max(Math.min(pt, max), min) : pt;
3085
			}
3086
3087
			for (i=0, ilen=points.length; i<ilen; ++i) {
3088
				point = points[i];
3089
				model = point._model;
3090
				controlPoints = helpers.splineCurve(
3091
					helpers.previousItem(points, i)._model,
3092
					model,
3093
					helpers.nextItem(points, i)._model,
3094
					meta.dataset._model.tension
3095
				);
3096
3097
				model.controlPointPreviousX = capIfNecessary(controlPoints.previous.x, area.left, area.right);
3098
				model.controlPointPreviousY = capIfNecessary(controlPoints.previous.y, area.top, area.bottom);
3099
				model.controlPointNextX = capIfNecessary(controlPoints.next.x, area.left, area.right);
3100
				model.controlPointNextY = capIfNecessary(controlPoints.next.y, area.top, area.bottom);
3101
			}
3102
		},
3103
3104
		draw: function(ease) {
3105
			var me = this;
3106
			var meta = me.getMeta();
3107
			var points = meta.data || [];
3108
			var easingDecimal = ease || 1;
3109
			var i, ilen;
3110
3111
			// Transition Point Locations
3112
			for (i=0, ilen=points.length; i<ilen; ++i) {
3113
				points[i].transition(easingDecimal);
3114
			}
3115
3116
			// Transition and Draw the line
3117
			if (lineEnabled(me.getDataset(), me.chart.options)) {
3118
				meta.dataset.transition(easingDecimal).draw();
3119
			}
3120
3121
			// Draw the points
3122
			for (i=0, ilen=points.length; i<ilen; ++i) {
3123
				points[i].draw();
3124
			}
3125
		},
3126
3127
		setHoverStyle: function(point) {
3128
			// Point
3129
			var dataset = this.chart.data.datasets[point._datasetIndex];
3130
			var index = point._index;
3131
			var custom = point.custom || {};
3132
			var model = point._model;
3133
3134
			model.radius = custom.hoverRadius || helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3135
			model.backgroundColor = custom.hoverBackgroundColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3136
			model.borderColor = custom.hoverBorderColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3137
			model.borderWidth = custom.hoverBorderWidth || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3138
		},
3139
3140
		removeHoverStyle: function(point) {
3141
			var me = this;
3142
			var dataset = me.chart.data.datasets[point._datasetIndex];
3143
			var index = point._index;
3144
			var custom = point.custom || {};
3145
			var model = point._model;
3146
3147
			// Compatibility: If the properties are defined with only the old name, use those values
3148
			if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3149
				dataset.pointRadius = dataset.radius;
3150
			}
3151
3152
			model.radius = custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
3153
			model.backgroundColor = me.getPointBackgroundColor(point, index);
3154
			model.borderColor = me.getPointBorderColor(point, index);
3155
			model.borderWidth = me.getPointBorderWidth(point, index);
3156
		}
3157
	});
3158
};
3159
3160
},{}],19:[function(require,module,exports){
3161
"use strict";
3162
3163
module.exports = function(Chart) {
3164
3165
	var helpers = Chart.helpers;
3166
3167
	Chart.defaults.polarArea = {
3168
3169
		scale: {
3170
			type: "radialLinear",
3171
			lineArc: true, // so that lines are circular
3172
			ticks: {
3173
				beginAtZero: true
3174
			}
3175
		},
3176
3177
		//Boolean - Whether to animate the rotation of the chart
3178
		animation: {
3179
			animateRotate: true,
3180
			animateScale: true
3181
		},
3182
3183
		startAngle: -0.5 * Math.PI,
3184
		aspectRatio: 1,
3185
		legendCallback: function(chart) {
3186
			var text = [];
3187
			text.push('<ul class="' + chart.id + '-legend">');
3188
3189
			var data = chart.data;
3190
			var datasets = data.datasets;
3191
			var labels = data.labels;
3192
3193
			if (datasets.length) {
3194
				for (var i = 0; i < datasets[0].data.length; ++i) {
3195
					text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '">');
3196
					if (labels[i]) {
3197
						text.push(labels[i]);
3198
					}
3199
					text.push('</span></li>');
3200
				}
3201
			}
3202
3203
			text.push('</ul>');
3204
			return text.join("");
3205
		},
3206
		legend: {
3207
			labels: {
3208
				generateLabels: function(chart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3209
					var data = chart.data;
3210
					if (data.labels.length && data.datasets.length) {
3211
						return data.labels.map(function(label, i) {
3212
							var meta = chart.getDatasetMeta(0);
3213
							var ds = data.datasets[0];
3214
							var arc = meta.data[i];
3215
							var custom = arc.custom || {};
3216
							var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
3217
							var arcOpts = chart.options.elements.arc;
3218
							var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
3219
							var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
3220
							var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
3221
3222
							return {
3223
								text: label,
3224
								fillStyle: fill,
3225
								strokeStyle: stroke,
3226
								lineWidth: bw,
3227
								hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
3228
3229
								// Extra data used for toggling the correct item
3230
								index: i
3231
							};
3232
						});
3233
					} else {
3234
						return [];
3235
					}
3236
				}
3237
			},
3238
3239
			onClick: function(e, legendItem) {
3240
				var index = legendItem.index;
3241
				var chart = this.chart;
3242
				var i, ilen, meta;
3243
3244
				for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
3245
					meta = chart.getDatasetMeta(i);
3246
					meta.data[index].hidden = !meta.data[index].hidden;
3247
				}
3248
3249
				chart.update();
3250
			}
3251
		},
3252
3253
		// Need to override these to give a nice default
3254
		tooltips: {
3255
			callbacks: {
3256
				title: function() {
3257
					return '';
3258
				},
3259
				label: function(tooltipItem, data) {
3260
					return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel;
3261
				}
3262
			}
3263
		}
3264
	};
3265
3266
	Chart.controllers.polarArea = Chart.DatasetController.extend({
3267
3268
		dataElementType: Chart.elements.Arc,
3269
3270
		linkScales: helpers.noop,
3271
3272
		update: function(reset) {
3273
			var me = this;
3274
			var chart = me.chart;
3275
			var chartArea = chart.chartArea;
3276
			var meta = me.getMeta();
3277
			var opts = chart.options;
3278
			var arcOpts = opts.elements.arc;
3279
			var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
3280
			chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
3281
			chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
3282
			chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
3283
3284
			me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
3285
			me.innerRadius = me.outerRadius - chart.radiusLength;
3286
3287
			meta.count = me.countVisibleElements();
3288
3289
			helpers.each(meta.data, function(arc, index) {
3290
				me.updateElement(arc, index, reset);
3291
			});
3292
		},
3293
3294
		updateElement: function(arc, index, reset) {
3295
			var me = this;
3296
			var chart = me.chart;
3297
			var dataset = me.getDataset();
3298
			var opts = chart.options;
3299
			var animationOpts = opts.animation;
3300
			var scale = chart.scale;
3301
			var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
3302
			var labels = chart.data.labels;
3303
3304
			var circumference = me.calculateCircumference(dataset.data[index]);
3305
			var centerX = scale.xCenter;
3306
			var centerY = scale.yCenter;
3307
3308
			// If there is NaN data before us, we need to calculate the starting angle correctly.
3309
			// We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
3310
			var visibleCount = 0;
3311
			var meta = me.getMeta();
3312
			for (var i = 0; i < index; ++i) {
3313
				if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
3314
					++visibleCount;
3315
				}
3316
			}
3317
3318
			//var negHalfPI = -0.5 * Math.PI;
3319
			var datasetStartAngle = opts.startAngle;
3320
			var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3321
			var startAngle = datasetStartAngle + (circumference * visibleCount);
3322
			var endAngle = startAngle + (arc.hidden ? 0 : circumference);
3323
3324
			var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3325
3326
			helpers.extend(arc, {
3327
				// Utility
3328
				_datasetIndex: me.index,
3329
				_index: index,
3330
				_scale: scale,
3331
3332
				// Desired view properties
3333
				_model: {
3334
					x: centerX,
3335
					y: centerY,
3336
					innerRadius: 0,
3337
					outerRadius: reset ? resetRadius : distance,
3338
					startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
3339
					endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
3340
					label: getValueAtIndexOrDefault(labels, index, labels[index])
3341
				}
3342
			});
3343
3344
			// Apply border and fill style
3345
			me.removeHoverStyle(arc);
3346
3347
			arc.pivot();
3348
		},
3349
3350
		removeHoverStyle: function(arc) {
3351
			Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
3352
		},
3353
3354
		countVisibleElements: function() {
3355
			var dataset = this.getDataset();
3356
			var meta = this.getMeta();
3357
			var count = 0;
3358
3359
			helpers.each(meta.data, function(element, index) {
3360
				if (!isNaN(dataset.data[index]) && !element.hidden) {
3361
					count++;
3362
				}
3363
			});
3364
3365
			return count;
3366
		},
3367
3368
		calculateCircumference: function(value) {
3369
			var count = this.getMeta().count;
3370
			if (count > 0 && !isNaN(value)) {
3371
				return (2 * Math.PI) / count;
3372
			} else {
3373
				return 0;
3374
			}
3375
		}
3376
	});
3377
};
3378
3379
},{}],20:[function(require,module,exports){
3380
"use strict";
3381
3382
module.exports = function(Chart) {
3383
3384
	var helpers = Chart.helpers;
3385
3386
	Chart.defaults.radar = {
3387
		scale: {
3388
			type: "radialLinear"
3389
		},
3390
		elements: {
3391
			line: {
3392
				tension: 0 // no bezier in radar
3393
			}
3394
		}
3395
	};
3396
3397
	Chart.controllers.radar = Chart.DatasetController.extend({
3398
3399
		datasetElementType: Chart.elements.Line,
3400
3401
		dataElementType: Chart.elements.Point,
3402
3403
		linkScales: helpers.noop,
3404
3405
		addElementAndReset: function(index) {
3406
			Chart.DatasetController.prototype.addElementAndReset.call(this, index);
3407
3408
			// Make sure bezier control points are updated
3409
			this.updateBezierControlPoints();
3410
		},
3411
3412
		update: function(reset) {
3413
			var me = this;
3414
			var meta = me.getMeta();
3415
			var line = meta.dataset;
3416
			var points = meta.data;
3417
			var custom = line.custom || {};
3418
			var dataset = me.getDataset();
3419
			var lineElementOptions = me.chart.options.elements.line;
3420
			var scale = me.chart.scale;
3421
3422
			// Compatibility: If the properties are defined with only the old name, use those values
3423
			if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
3424
				dataset.lineTension = dataset.tension;
3425
			}
3426
3427
			helpers.extend(meta.dataset, {
3428
				// Utility
3429
				_datasetIndex: me.index,
3430
				// Data
3431
				_children: points,
3432
				_loop: true,
3433
				// Model
3434
				_model: {
3435
					// Appearance
3436
					tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),
3437
					backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
3438
					borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
3439
					borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
3440
					fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
3441
					borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
3442
					borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
3443
					borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
3444
					borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
3445
3446
					// Scale
3447
					scaleTop: scale.top,
3448
					scaleBottom: scale.bottom,
3449
					scaleZero: scale.getBasePosition()
3450
				}
3451
			});
3452
3453
			meta.dataset.pivot();
3454
3455
			// Update Points
3456
			helpers.each(points, function(point, index) {
3457
				me.updateElement(point, index, reset);
3458
			}, me);
3459
3460
3461
			// Update bezier control points
3462
			me.updateBezierControlPoints();
3463
		},
3464
		updateElement: function(point, index, reset) {
3465
			var me = this;
3466
			var custom = point.custom || {};
3467
			var dataset = me.getDataset();
3468
			var scale = me.chart.scale;
3469
			var pointElementOptions = me.chart.options.elements.point;
3470
			var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
3471
3472
			helpers.extend(point, {
3473
				// Utility
3474
				_datasetIndex: me.index,
3475
				_index: index,
3476
				_scale: scale,
3477
3478
				// Desired view properties
3479
				_model: {
3480
					x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
3481
					y: reset ? scale.yCenter : pointPosition.y,
3482
3483
					// Appearance
3484
					tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, me.chart.options.elements.line.tension),
3485
					radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
3486
					backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
3487
					borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
3488
					borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
3489
					pointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
3490
3491
					// Tooltip
3492
					hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)
3493
				}
3494
			});
3495
3496
			point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
3497
		},
3498
		updateBezierControlPoints: function() {
3499
			var chartArea = this.chart.chartArea;
3500
			var meta = this.getMeta();
3501
3502
			helpers.each(meta.data, function(point, index) {
3503
				var model = point._model;
3504
				var controlPoints = helpers.splineCurve(
3505
					helpers.previousItem(meta.data, index, true)._model,
3506
					model,
3507
					helpers.nextItem(meta.data, index, true)._model,
3508
					model.tension
3509
				);
3510
3511
				// Prevent the bezier going outside of the bounds of the graph
3512
				model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
3513
				model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
3514
3515
				model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
3516
				model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
3517
3518
				// Now pivot the point for animation
3519
				point.pivot();
3520
			});
3521
		},
3522
3523
		draw: function(ease) {
3524
			var meta = this.getMeta();
3525
			var easingDecimal = ease || 1;
3526
3527
			// Transition Point Locations
3528
			helpers.each(meta.data, function(point) {
3529
				point.transition(easingDecimal);
3530
			});
3531
3532
			// Transition and Draw the line
3533
			meta.dataset.transition(easingDecimal).draw();
3534
3535
			// Draw the points
3536
			helpers.each(meta.data, function(point) {
3537
				point.draw();
3538
			});
3539
		},
3540
3541
		setHoverStyle: function(point) {
3542
			// Point
3543
			var dataset = this.chart.data.datasets[point._datasetIndex];
3544
			var custom = point.custom || {};
3545
			var index = point._index;
3546
			var model = point._model;
3547
3548
			model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3549
			model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3550
			model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3551
			model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3552
		},
3553
3554
		removeHoverStyle: function(point) {
3555
			var dataset = this.chart.data.datasets[point._datasetIndex];
3556
			var custom = point.custom || {};
3557
			var index = point._index;
3558
			var model = point._model;
3559
			var pointElementOptions = this.chart.options.elements.point;
3560
3561
			model.radius = custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, pointElementOptions.radius);
3562
			model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
3563
			model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
3564
			model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
3565
		}
3566
	});
3567
};
3568
3569
},{}],21:[function(require,module,exports){
3570
/*global window: false */
3571
"use strict";
3572
3573
module.exports = function(Chart) {
3574
3575
	var helpers = Chart.helpers;
3576
3577
	Chart.defaults.global.animation = {
3578
		duration: 1000,
3579
		easing: "easeOutQuart",
3580
		onProgress: helpers.noop,
3581
		onComplete: helpers.noop
3582
	};
3583
3584
	Chart.Animation = Chart.Element.extend({
3585
		currentStep: null, // the current animation step
3586
		numSteps: 60, // default number of steps
3587
		easing: "", // the easing to use for this animation
3588
		render: null, // render function used by the animation service
3589
3590
		onAnimationProgress: null, // user specified callback to fire on each step of the animation
3591
		onAnimationComplete: null // user specified callback to fire when the animation finishes
3592
	});
3593
3594
	Chart.animationService = {
3595
		frameDuration: 17,
3596
		animations: [],
3597
		dropFrames: 0,
3598
		request: null,
3599
		addAnimation: function(chartInstance, animationObject, duration, lazy) {
3600
			var me = this;
3601
3602
			if (!lazy) {
3603
				chartInstance.animating = true;
3604
			}
3605
3606
			for (var index = 0; index < me.animations.length; ++index) {
3607
				if (me.animations[index].chartInstance === chartInstance) {
3608
					// replacing an in progress animation
3609
					me.animations[index].animationObject = animationObject;
3610
					return;
3611
				}
3612
			}
3613
3614
			me.animations.push({
3615
				chartInstance: chartInstance,
3616
				animationObject: animationObject
3617
			});
3618
3619
			// If there are no animations queued, manually kickstart a digest, for lack of a better word
3620
			if (me.animations.length === 1) {
3621
				me.requestAnimationFrame();
3622
			}
3623
		},
3624
		// Cancel the animation for a given chart instance
3625
		cancelAnimation: function(chartInstance) {
3626
			var index = helpers.findIndex(this.animations, function(animationWrapper) {
3627
				return animationWrapper.chartInstance === chartInstance;
3628
			});
3629
3630
			if (index !== -1) {
3631
				this.animations.splice(index, 1);
3632
				chartInstance.animating = false;
3633
			}
3634
		},
3635
		requestAnimationFrame: function() {
3636
			var me = this;
3637
			if (me.request === null) {
3638
				// Skip animation frame requests until the active one is executed.
3639
				// This can happen when processing mouse events, e.g. 'mousemove'
3640
				// and 'mouseout' events will trigger multiple renders.
3641
				me.request = helpers.requestAnimFrame.call(window, function() {
3642
					me.request = null;
3643
					me.startDigest();
3644
				});
3645
			}
3646
		},
3647
		startDigest: function() {
3648
			var me = this;
3649
3650
			var startTime = Date.now();
3651
			var framesToDrop = 0;
3652
3653
			if (me.dropFrames > 1) {
3654
				framesToDrop = Math.floor(me.dropFrames);
3655
				me.dropFrames = me.dropFrames % 1;
3656
			}
3657
3658
			var i = 0;
3659
			while (i < me.animations.length) {
3660
				if (me.animations[i].animationObject.currentStep === null) {
3661
					me.animations[i].animationObject.currentStep = 0;
3662
				}
3663
3664
				me.animations[i].animationObject.currentStep += 1 + framesToDrop;
3665
3666
				if (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) {
3667
					me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps;
3668
				}
3669
3670
				me.animations[i].animationObject.render(me.animations[i].chartInstance, me.animations[i].animationObject);
3671
				if (me.animations[i].animationObject.onAnimationProgress && me.animations[i].animationObject.onAnimationProgress.call) {
3672
					me.animations[i].animationObject.onAnimationProgress.call(me.animations[i].chartInstance, me.animations[i]);
3673
				}
3674
3675
				if (me.animations[i].animationObject.currentStep === me.animations[i].animationObject.numSteps) {
3676
					if (me.animations[i].animationObject.onAnimationComplete && me.animations[i].animationObject.onAnimationComplete.call) {
3677
						me.animations[i].animationObject.onAnimationComplete.call(me.animations[i].chartInstance, me.animations[i]);
3678
					}
3679
3680
					// executed the last frame. Remove the animation.
3681
					me.animations[i].chartInstance.animating = false;
3682
3683
					me.animations.splice(i, 1);
3684
				} else {
3685
					++i;
3686
				}
3687
			}
3688
3689
			var endTime = Date.now();
3690
			var dropFrames = (endTime - startTime) / me.frameDuration;
3691
3692
			me.dropFrames += dropFrames;
3693
3694
			// Do we have more stuff to animate?
3695
			if (me.animations.length > 0) {
3696
				me.requestAnimationFrame();
3697
			}
3698
		}
3699
	};
3700
};
3701
},{}],22:[function(require,module,exports){
3702
"use strict";
3703
3704
module.exports = function(Chart) {
3705
	// Global Chart canvas helpers object for drawing items to canvas
3706
	var helpers = Chart.canvasHelpers = {};
3707
3708
	helpers.drawPoint = function(ctx, pointStyle, radius, x, y) {
3709
		var type, edgeLength, xOffset, yOffset, height, size;
3710
3711
		if (typeof pointStyle === 'object') {
3712
			type = pointStyle.toString();
3713
			if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
3714
				ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2);
3715
				return;
3716
			}
3717
		}
3718
3719
		if (isNaN(radius) || radius <= 0) {
3720
			return;
3721
		}
3722
3723
		switch (pointStyle) {
3724
		// Default includes circle
3725
		default:
0 ignored issues
show
Coding Style Comprehensibility introduced by
The default case is not the last statement in this switch statement. For the sake of readability, you might want to move it to the end of the statement.
Loading history...
3726
			ctx.beginPath();
3727
			ctx.arc(x, y, radius, 0, Math.PI * 2);
3728
			ctx.closePath();
3729
			ctx.fill();
3730
			break;
3731
		case 'triangle':
3732
			ctx.beginPath();
3733
			edgeLength = 3 * radius / Math.sqrt(3);
3734
			height = edgeLength * Math.sqrt(3) / 2;
3735
			ctx.moveTo(x - edgeLength / 2, y + height / 3);
3736
			ctx.lineTo(x + edgeLength / 2, y + height / 3);
3737
			ctx.lineTo(x, y - 2 * height / 3);
3738
			ctx.closePath();
3739
			ctx.fill();
3740
			break;
3741
		case 'rect':
3742
			size = 1 / Math.SQRT2 * radius;
3743
			ctx.beginPath();
3744
			ctx.fillRect(x - size, y - size, 2 * size,  2 * size);
3745
			ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
3746
			break;
3747
		case 'rectRot':
3748
			size = 1 / Math.SQRT2 * radius;
3749
			ctx.beginPath();
3750
			ctx.moveTo(x - size, y);
3751
			ctx.lineTo(x, y + size);
3752
			ctx.lineTo(x + size, y);
3753
			ctx.lineTo(x, y - size);
3754
			ctx.closePath();
3755
			ctx.fill();
3756
			break;
3757
		case 'cross':
3758
			ctx.beginPath();
3759
			ctx.moveTo(x, y + radius);
3760
			ctx.lineTo(x, y - radius);
3761
			ctx.moveTo(x - radius, y);
3762
			ctx.lineTo(x + radius, y);
3763
			ctx.closePath();
3764
			break;
3765
		case 'crossRot':
3766
			ctx.beginPath();
3767
			xOffset = Math.cos(Math.PI / 4) * radius;
3768
			yOffset = Math.sin(Math.PI / 4) * radius;
3769
			ctx.moveTo(x - xOffset, y - yOffset);
3770
			ctx.lineTo(x + xOffset, y + yOffset);
3771
			ctx.moveTo(x - xOffset, y + yOffset);
3772
			ctx.lineTo(x + xOffset, y - yOffset);
3773
			ctx.closePath();
3774
			break;
3775
		case 'star':
3776
			ctx.beginPath();
3777
			ctx.moveTo(x, y + radius);
3778
			ctx.lineTo(x, y - radius);
3779
			ctx.moveTo(x - radius, y);
3780
			ctx.lineTo(x + radius, y);
3781
			xOffset = Math.cos(Math.PI / 4) * radius;
3782
			yOffset = Math.sin(Math.PI / 4) * radius;
3783
			ctx.moveTo(x - xOffset, y - yOffset);
3784
			ctx.lineTo(x + xOffset, y + yOffset);
3785
			ctx.moveTo(x - xOffset, y + yOffset);
3786
			ctx.lineTo(x + xOffset, y - yOffset);
3787
			ctx.closePath();
3788
			break;
3789
		case 'line':
3790
			ctx.beginPath();
3791
			ctx.moveTo(x - radius, y);
3792
			ctx.lineTo(x + radius, y);
3793
			ctx.closePath();
3794
			break;
3795
		case 'dash':
3796
			ctx.beginPath();
3797
			ctx.moveTo(x, y);
3798
			ctx.lineTo(x + radius, y);
3799
			ctx.closePath();
3800
			break;
3801
		}
3802
3803
		ctx.stroke();
3804
	};
3805
};
3806
},{}],23:[function(require,module,exports){
3807
"use strict";
3808
3809
module.exports = function(Chart) {
3810
3811
	var helpers = Chart.helpers;
3812
	//Create a dictionary of chart types, to allow for extension of existing types
3813
	Chart.types = {};
3814
3815
	//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
3816
	//Destroy method on the chart will remove the instance of the chart from this reference.
3817
	Chart.instances = {};
3818
3819
	// Controllers available for dataset visualization eg. bar, line, slice, etc.
3820
	Chart.controllers = {};
3821
3822
	/**
3823
	 * @class Chart.Controller
3824
	 * The main controller of a chart.
3825
	 */
3826
	Chart.Controller = function(instance) {
3827
3828
		this.chart = instance;
3829
		this.config = instance.config;
3830
		this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {});
3831
		this.id = helpers.uid();
3832
3833
		Object.defineProperty(this, 'data', {
3834
			get: function() {
3835
				return this.config.data;
3836
			}
3837
		});
3838
3839
		//Add the chart instance to the global namespace
3840
		Chart.instances[this.id] = this;
3841
3842
		if (this.options.responsive) {
3843
			// Silent resize before chart draws
3844
			this.resize(true);
3845
		}
3846
3847
		this.initialize();
3848
3849
		return this;
3850
	};
3851
3852
	helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ {
3853
3854
		initialize: function() {
3855
			var me = this;
3856
			// Before init plugin notification
3857
			Chart.plugins.notify('beforeInit', [me]);
3858
3859
			me.bindEvents();
3860
3861
			// Make sure controllers are built first so that each dataset is bound to an axis before the scales
3862
			// are built
3863
			me.ensureScalesHaveIDs();
3864
			me.buildOrUpdateControllers();
3865
			me.buildScales();
3866
			me.updateLayout();
3867
			me.resetElements();
3868
			me.initToolTip();
3869
			me.update();
3870
3871
			// After init plugin notification
3872
			Chart.plugins.notify('afterInit', [me]);
3873
3874
			return me;
3875
		},
3876
3877
		clear: function() {
3878
			helpers.clear(this.chart);
3879
			return this;
3880
		},
3881
3882
		stop: function() {
3883
			// Stops any current animation loop occuring
3884
			Chart.animationService.cancelAnimation(this);
3885
			return this;
3886
		},
3887
3888
		resize: function resize(silent) {
3889
			var me = this;
3890
			var chart = me.chart;
3891
			var canvas = chart.canvas;
3892
			var newWidth = helpers.getMaximumWidth(canvas);
3893
			var aspectRatio = chart.aspectRatio;
3894
			var newHeight = (me.options.maintainAspectRatio && isNaN(aspectRatio) === false && isFinite(aspectRatio) && aspectRatio !== 0) ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas);
3895
3896
			var sizeChanged = chart.width !== newWidth || chart.height !== newHeight;
3897
3898
			if (!sizeChanged) {
3899
				return me;
3900
			}
3901
3902
			canvas.width = chart.width = newWidth;
3903
			canvas.height = chart.height = newHeight;
3904
3905
			helpers.retinaScale(chart);
3906
3907
			// Notify any plugins about the resize
3908
			var newSize = { width: newWidth, height: newHeight };
3909
			Chart.plugins.notify('resize', [me, newSize]);
3910
3911
			// Notify of resize
3912
			if (me.options.onResize) {
3913
				me.options.onResize(me, newSize);
3914
			}
3915
3916
			if (!silent) {
3917
				me.stop();
3918
				me.update(me.options.responsiveAnimationDuration);
3919
			}
3920
3921
			return me;
3922
		},
3923
3924
		ensureScalesHaveIDs: function() {
3925
			var options = this.options;
3926
			var scalesOptions = options.scales || {};
3927
			var scaleOptions = options.scale;
3928
3929
			helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
3930
				xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
3931
			});
3932
3933
			helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
3934
				yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
3935
			});
3936
3937
			if (scaleOptions) {
3938
				scaleOptions.id = scaleOptions.id || 'scale';
3939
			}
3940
		},
3941
3942
		/**
3943
		 * Builds a map of scale ID to scale object for future lookup.
3944
		 */
3945
		buildScales: function() {
3946
			var me = this;
3947
			var options = me.options;
3948
			var scales = me.scales = {};
3949
			var items = [];
3950
3951
			if (options.scales) {
3952
				items = items.concat(
3953
					(options.scales.xAxes || []).map(function(xAxisOptions) {
3954
						return { options: xAxisOptions, dtype: 'category' }; }),
3955
					(options.scales.yAxes || []).map(function(yAxisOptions) {
3956
						return { options: yAxisOptions, dtype: 'linear' }; }));
3957
			}
3958
3959
			if (options.scale) {
3960
				items.push({ options: options.scale, dtype: 'radialLinear', isDefault: true });
3961
			}
3962
3963
			helpers.each(items, function(item) {
3964
				var scaleOptions = item.options;
3965
				var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype);
3966
				var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
3967
				if (!scaleClass) {
3968
					return;
3969
				}
3970
3971
				var scale = new scaleClass({
0 ignored issues
show
Coding Style Best Practice introduced by
By convention, constructors like scaleClass should be capitalized.
Loading history...
3972
					id: scaleOptions.id,
3973
					options: scaleOptions,
3974
					ctx: me.chart.ctx,
3975
					chart: me
3976
				});
3977
3978
				scales[scale.id] = scale;
3979
3980
				// TODO(SB): I think we should be able to remove this custom case (options.scale)
3981
				// and consider it as a regular scale part of the "scales"" map only! This would
3982
				// make the logic easier and remove some useless? custom code.
3983
				if (item.isDefault) {
3984
					me.scale = scale;
3985
				}
3986
			});
3987
3988
			Chart.scaleService.addScalesToLayout(this);
3989
		},
3990
3991
		updateLayout: function() {
3992
			Chart.layoutService.update(this, this.chart.width, this.chart.height);
3993
		},
3994
3995
		buildOrUpdateControllers: function() {
3996
			var me = this;
3997
			var types = [];
3998
			var newControllers = [];
3999
4000
			helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4001
				var meta = me.getDatasetMeta(datasetIndex);
4002
				if (!meta.type) {
4003
					meta.type = dataset.type || me.config.type;
4004
				}
4005
4006
				types.push(meta.type);
4007
4008
				if (meta.controller) {
4009
					meta.controller.updateIndex(datasetIndex);
4010
				} else {
4011
					meta.controller = new Chart.controllers[meta.type](me, datasetIndex);
4012
					newControllers.push(meta.controller);
4013
				}
4014
			}, me);
4015
4016
			if (types.length > 1) {
4017
				for (var i = 1; i < types.length; i++) {
4018
					if (types[i] !== types[i - 1]) {
4019
						me.isCombo = true;
4020
						break;
4021
					}
4022
				}
4023
			}
4024
4025
			return newControllers;
4026
		},
4027
4028
		resetElements: function() {
4029
			var me = this;
4030
			helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4031
				me.getDatasetMeta(datasetIndex).controller.reset();
4032
			}, me);
4033
		},
4034
4035
		update: function update(animationDuration, lazy) {
4036
			var me = this;
4037
			Chart.plugins.notify('beforeUpdate', [me]);
4038
4039
			// In case the entire data object changed
4040
			me.tooltip._data = me.data;
4041
4042
			// Make sure dataset controllers are updated and new controllers are reset
4043
			var newControllers = me.buildOrUpdateControllers();
4044
4045
			// Make sure all dataset controllers have correct meta data counts
4046
			helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4047
				me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
4048
			}, me);
4049
4050
			Chart.layoutService.update(me, me.chart.width, me.chart.height);
4051
4052
			// Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages
4053
			Chart.plugins.notify('afterScaleUpdate', [me]);
4054
4055
			// Can only reset the new controllers after the scales have been updated
4056
			helpers.each(newControllers, function(controller) {
4057
				controller.reset();
4058
			});
4059
4060
			me.updateDatasets();
4061
4062
			// Do this before render so that any plugins that need final scale updates can use it
4063
			Chart.plugins.notify('afterUpdate', [me]);
4064
4065
			me.render(animationDuration, lazy);
4066
		},
4067
4068
		/**
4069
		 * @method beforeDatasetsUpdate
4070
		 * @description Called before all datasets are updated. If a plugin returns false,
4071
		 * the datasets update will be cancelled until another chart update is triggered.
4072
		 * @param {Object} instance the chart instance being updated.
4073
		 * @returns {Boolean} false to cancel the datasets update.
4074
		 * @memberof Chart.PluginBase
4075
		 * @since version 2.1.5
4076
		 * @instance
4077
		 */
4078
4079
		/**
4080
		 * @method afterDatasetsUpdate
4081
		 * @description Called after all datasets have been updated. Note that this
4082
		 * extension will not be called if the datasets update has been cancelled.
4083
		 * @param {Object} instance the chart instance being updated.
4084
		 * @memberof Chart.PluginBase
4085
		 * @since version 2.1.5
4086
		 * @instance
4087
		 */
4088
4089
		/**
4090
		 * Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate
4091
		 * extension, in which case no datasets will be updated and the afterDatasetsUpdate
4092
		 * notification will be skipped.
4093
		 * @protected
4094
		 * @instance
4095
		 */
4096
		updateDatasets: function() {
4097
			var me = this;
4098
			var i, ilen;
4099
4100
			if (Chart.plugins.notify('beforeDatasetsUpdate', [ me ])) {
4101
				for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4102
					me.getDatasetMeta(i).controller.update();
4103
				}
4104
4105
				Chart.plugins.notify('afterDatasetsUpdate', [ me ]);
4106
			}
4107
		},
4108
4109
		render: function render(duration, lazy) {
4110
			var me = this;
4111
			Chart.plugins.notify('beforeRender', [me]);
4112
4113
			var animationOptions = me.options.animation;
4114
			if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
4115
				var animation = new Chart.Animation();
4116
				animation.numSteps = (duration || animationOptions.duration) / 16.66; //60 fps
4117
				animation.easing = animationOptions.easing;
4118
4119
				// render function
4120
				animation.render = function(chartInstance, animationObject) {
4121
					var easingFunction = helpers.easingEffects[animationObject.easing];
4122
					var stepDecimal = animationObject.currentStep / animationObject.numSteps;
4123
					var easeDecimal = easingFunction(stepDecimal);
4124
4125
					chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
4126
				};
4127
4128
				// user events
4129
				animation.onAnimationProgress = animationOptions.onProgress;
4130
				animation.onAnimationComplete = animationOptions.onComplete;
4131
4132
				Chart.animationService.addAnimation(me, animation, duration, lazy);
4133
			} else {
4134
				me.draw();
4135
				if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) {
4136
					animationOptions.onComplete.call(me);
4137
				}
4138
			}
4139
			return me;
4140
		},
4141
4142
		draw: function(ease) {
4143
			var me = this;
4144
			var easingDecimal = ease || 1;
4145
			me.clear();
4146
4147
			Chart.plugins.notify('beforeDraw', [me, easingDecimal]);
4148
4149
			// Draw all the scales
4150
			helpers.each(me.boxes, function(box) {
4151
				box.draw(me.chartArea);
4152
			}, me);
4153
			if (me.scale) {
4154
				me.scale.draw();
4155
			}
4156
4157
			Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]);
4158
4159
			// Draw each dataset via its respective controller (reversed to support proper line stacking)
4160
			helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4161
				if (me.isDatasetVisible(datasetIndex)) {
4162
					me.getDatasetMeta(datasetIndex).controller.draw(ease);
4163
				}
4164
			}, me, true);
4165
4166
			Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]);
4167
4168
			// Finally draw the tooltip
4169
			me.tooltip.transition(easingDecimal).draw();
4170
4171
			Chart.plugins.notify('afterDraw', [me, easingDecimal]);
4172
		},
4173
4174
		// Get the single element that was clicked on
4175
		// @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
4176
		getElementAtEvent: function(e) {
4177
			var me = this;
4178
			var eventPosition = helpers.getRelativePosition(e, me.chart);
4179
			var elementsArray = [];
4180
4181
			helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4182
				if (me.isDatasetVisible(datasetIndex)) {
4183
					var meta = me.getDatasetMeta(datasetIndex);
4184
					helpers.each(meta.data, function(element) {
4185
						if (element.inRange(eventPosition.x, eventPosition.y)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if element.inRange(eventPosition.x, eventPosition.y) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
4186
							elementsArray.push(element);
4187
							return elementsArray;
4188
						}
4189
					});
4190
				}
4191
			});
4192
4193
			return elementsArray.slice(0, 1);
4194
		},
4195
4196
		getElementsAtEvent: function(e) {
4197
			var me = this;
4198
			var eventPosition = helpers.getRelativePosition(e, me.chart);
4199
			var elementsArray = [];
4200
4201
			var found = (function() {
4202
				if (me.data.datasets) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if me.data.datasets is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
4203
					for (var i = 0; i < me.data.datasets.length; i++) {
4204
						var meta = me.getDatasetMeta(i);
4205
						if (me.isDatasetVisible(i)) {
4206
							for (var j = 0; j < meta.data.length; j++) {
4207
								if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) {
4208
									return meta.data[j];
4209
								}
4210
							}
4211
						}
4212
					}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
4213
				}
4214
			}).call(me);
4215
4216
			if (!found) {
4217
				return elementsArray;
4218
			}
4219
4220
			helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4221
				if (me.isDatasetVisible(datasetIndex)) {
4222
					var meta = me.getDatasetMeta(datasetIndex),
4223
						element = meta.data[found._index];
4224
					if(element && !element._view.skip){
4225
						elementsArray.push(element);
4226
					}
4227
				}
4228
			}, me);
4229
4230
			return elementsArray;
4231
		},
4232
4233
		getElementsAtXAxis: function(e) {
4234
			var me = this;
4235
			var eventPosition = helpers.getRelativePosition(e, me.chart);
4236
			var elementsArray = [];
4237
4238
			var found = (function() {
4239
				if (me.data.datasets) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if me.data.datasets is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
4240
					for (var i = 0; i < me.data.datasets.length; i++) {
4241
						var meta = me.getDatasetMeta(i);
4242
						if (me.isDatasetVisible(i)) {
4243
							for (var j = 0; j < meta.data.length; j++) {
4244
								if (meta.data[j].inLabelRange(eventPosition.x, eventPosition.y)) {
4245
									return meta.data[j];
4246
								}
4247
							}
4248
						}
4249
					}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
4250
				}
4251
			}).call(me);
4252
4253
			if (!found) {
4254
				return elementsArray;
4255
			}
4256
4257
			helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4258
				if (me.isDatasetVisible(datasetIndex)) {
4259
					var meta = me.getDatasetMeta(datasetIndex);
4260
					var index = helpers.findIndex(meta.data, function (it) {
4261
						return found._model.x === it._model.x;
4262
					});
4263
					if(index !== -1 && !meta.data[index]._view.skip) {
4264
						elementsArray.push(meta.data[index]);
4265
					}
4266
				}
4267
			}, me);
4268
4269
			return elementsArray;
4270
		},		
4271
4272
		getElementsAtEventForMode: function(e, mode) {
4273
			var me = this;
4274
			switch (mode) {
4275
			case 'single':
4276
				return me.getElementAtEvent(e);
4277
			case 'label':
4278
				return me.getElementsAtEvent(e);
4279
			case 'dataset':
4280
				return me.getDatasetAtEvent(e);
4281
            case 'x-axis':
4282
                return me.getElementsAtXAxis(e);
4283
			default:
4284
				return e;
4285
			}
4286
		},
4287
4288
		getDatasetAtEvent: function(e) {
4289
			var elementsArray = this.getElementAtEvent(e);
4290
4291
			if (elementsArray.length > 0) {
4292
				elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data;
4293
			}
4294
4295
			return elementsArray;
4296
		},
4297
4298
		getDatasetMeta: function(datasetIndex) {
4299
			var me = this;
4300
			var dataset = me.data.datasets[datasetIndex];
4301
			if (!dataset._meta) {
4302
				dataset._meta = {};
4303
			}
4304
4305
			var meta = dataset._meta[me.id];
4306
			if (!meta) {
4307
				meta = dataset._meta[me.id] = {
4308
				type: null,
4309
				data: [],
4310
				dataset: null,
4311
				controller: null,
4312
				hidden: null,			// See isDatasetVisible() comment
4313
				xAxisID: null,
4314
				yAxisID: null
4315
			};
4316
			}
4317
4318
			return meta;
4319
		},
4320
4321
		getVisibleDatasetCount: function() {
4322
			var count = 0;
4323
			for (var i = 0, ilen = this.data.datasets.length; i<ilen; ++i) {
4324
				 if (this.isDatasetVisible(i)) {
4325
					count++;
4326
				}
4327
			}
4328
			return count;
4329
		},
4330
4331
		isDatasetVisible: function(datasetIndex) {
4332
			var meta = this.getDatasetMeta(datasetIndex);
4333
4334
			// meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
4335
			// the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
4336
			return typeof meta.hidden === 'boolean'? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
4337
		},
4338
4339
		generateLegend: function() {
4340
			return this.options.legendCallback(this);
4341
		},
4342
4343
		destroy: function() {
4344
			var me = this;
4345
			me.stop();
4346
			me.clear();
4347
			helpers.unbindEvents(me, me.events);
4348
			helpers.removeResizeListener(me.chart.canvas.parentNode);
4349
4350
			// Reset canvas height/width attributes
4351
			var canvas = me.chart.canvas;
4352
			canvas.width = me.chart.width;
4353
			canvas.height = me.chart.height;
4354
4355
			// if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here
4356
			if (me.chart.originalDevicePixelRatio !== undefined) {
4357
				me.chart.ctx.scale(1 / me.chart.originalDevicePixelRatio, 1 / me.chart.originalDevicePixelRatio);
4358
			}
4359
4360
			// Reset to the old style since it may have been changed by the device pixel ratio changes
4361
			canvas.style.width = me.chart.originalCanvasStyleWidth;
4362
			canvas.style.height = me.chart.originalCanvasStyleHeight;
4363
4364
			Chart.plugins.notify('destroy', [me]);
4365
4366
			delete Chart.instances[me.id];
4367
		},
4368
4369
		toBase64Image: function() {
4370
			return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
4371
		},
4372
4373
		initToolTip: function() {
4374
			var me = this;
4375
			me.tooltip = new Chart.Tooltip({
4376
				_chart: me.chart,
4377
				_chartInstance: me,
4378
				_data: me.data,
4379
				_options: me.options.tooltips
4380
			}, me);
4381
		},
4382
4383
		bindEvents: function() {
4384
			var me = this;
4385
			helpers.bindEvents(me, me.options.events, function(evt) {
4386
				me.eventHandler(evt);
4387
			});
4388
		},
4389
4390
		updateHoverStyle: function(elements, mode, enabled) {
4391
			var method = enabled? 'setHoverStyle' : 'removeHoverStyle';
4392
			var element, i, ilen;
4393
4394
			switch (mode) {
4395
			case 'single':
4396
				elements = [ elements[0] ];
4397
				break;
4398
			case 'label':
4399
			case 'dataset':
4400
            case 'x-axis':
4401
				// elements = elements;
4402
				break;
4403
			default:
4404
				// unsupported mode
4405
				return;
4406
			}
4407
4408
			for (i=0, ilen=elements.length; i<ilen; ++i) {
4409
				element = elements[i];
4410
				if (element) {
4411
					this.getDatasetMeta(element._datasetIndex).controller[method](element);
4412
				}
4413
			}
4414
		},
4415
4416
		eventHandler: function eventHandler(e) {
4417
			var me = this;
4418
			var tooltip = me.tooltip;
4419
			var options = me.options || {};
4420
			var hoverOptions = options.hover;
4421
			var tooltipsOptions = options.tooltips;
4422
4423
			me.lastActive = me.lastActive || [];
4424
			me.lastTooltipActive = me.lastTooltipActive || [];
4425
4426
			// Find Active Elements for hover and tooltips
4427
			if (e.type === 'mouseout') {
4428
				me.active = [];
4429
				me.tooltipActive = [];
4430
			} else {
4431
				me.active = me.getElementsAtEventForMode(e, hoverOptions.mode);
4432
				me.tooltipActive =  me.getElementsAtEventForMode(e, tooltipsOptions.mode);
4433
			}
4434
4435
			// On Hover hook
4436
			if (hoverOptions.onHover) {
4437
				hoverOptions.onHover.call(me, me.active);
4438
			}
4439
4440
			if (e.type === 'mouseup' || e.type === 'click') {
4441
				if (options.onClick) {
4442
					options.onClick.call(me, e, me.active);
4443
				}
4444
				if (me.legend && me.legend.handleEvent) {
4445
					me.legend.handleEvent(e);
4446
				}
4447
			}
4448
4449
			// Remove styling for last active (even if it may still be active)
4450
			if (me.lastActive.length) {
4451
				me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
4452
			}
4453
4454
			// Built in hover styling
4455
			if (me.active.length && hoverOptions.mode) {
4456
				me.updateHoverStyle(me.active, hoverOptions.mode, true);
4457
			}
4458
4459
			// Built in Tooltips
4460
			if (tooltipsOptions.enabled || tooltipsOptions.custom) {
4461
				tooltip.initialize();
4462
				tooltip._active = me.tooltipActive;
4463
				tooltip.update(true);
4464
			}
4465
4466
			// Hover animations
4467
			tooltip.pivot();
4468
4469
			if (!me.animating) {
4470
				// If entering, leaving, or changing elements, animate the change via pivot
4471
				if (!helpers.arrayEquals(me.active, me.lastActive) ||
4472
					!helpers.arrayEquals(me.tooltipActive, me.lastTooltipActive)) {
4473
4474
					me.stop();
4475
4476
					if (tooltipsOptions.enabled || tooltipsOptions.custom) {
4477
						tooltip.update(true);
4478
					}
4479
4480
					// We only need to render at this point. Updating will cause scales to be
4481
					// recomputed generating flicker & using more memory than necessary.
4482
					me.render(hoverOptions.animationDuration, true);
4483
				}
4484
			}
4485
4486
			// Remember Last Actives
4487
			me.lastActive = me.active;
4488
			me.lastTooltipActive = me.tooltipActive;
4489
			return me;
4490
		}
4491
	});
4492
};
4493
4494
},{}],24:[function(require,module,exports){
4495
"use strict";
4496
4497
module.exports = function(Chart) {
4498
4499
	var helpers = Chart.helpers;
4500
	var noop = helpers.noop;
4501
4502
	// Base class for all dataset controllers (line, bar, etc)
4503
	Chart.DatasetController = function(chart, datasetIndex) {
4504
		this.initialize.call(this, chart, datasetIndex);
4505
	};
4506
4507
	helpers.extend(Chart.DatasetController.prototype, {
4508
4509
		/**
4510
		 * Element type used to generate a meta dataset (e.g. Chart.element.Line).
4511
		 * @type {Chart.core.element}
4512
		 */
4513
		datasetElementType: null,
4514
4515
		/**
4516
		 * Element type used to generate a meta data (e.g. Chart.element.Point).
4517
		 * @type {Chart.core.element}
4518
		 */
4519
		dataElementType: null,
4520
4521
		initialize: function(chart, datasetIndex) {
4522
			var me = this;
4523
			me.chart = chart;
4524
			me.index = datasetIndex;
4525
			me.linkScales();
4526
			me.addElements();
4527
		},
4528
4529
		updateIndex: function(datasetIndex) {
4530
			this.index = datasetIndex;
4531
		},
4532
4533
		linkScales: function() {
4534
			var me = this;
4535
			var meta = me.getMeta();
4536
			var dataset = me.getDataset();
4537
4538
			if (meta.xAxisID === null) {
4539
				meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
4540
			}
4541
			if (meta.yAxisID === null) {
4542
				meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
4543
			}
4544
		},
4545
4546
		getDataset: function() {
4547
			return this.chart.data.datasets[this.index];
4548
		},
4549
4550
		getMeta: function() {
4551
			return this.chart.getDatasetMeta(this.index);
4552
		},
4553
4554
		getScaleForId: function(scaleID) {
4555
			return this.chart.scales[scaleID];
4556
		},
4557
4558
		reset: function() {
4559
			this.update(true);
4560
		},
4561
4562
		createMetaDataset: function() {
4563
			var me = this;
4564
			var type = me.datasetElementType;
4565
			return type && new type({
0 ignored issues
show
Coding Style Best Practice introduced by
By convention, constructors like type should be capitalized.
Loading history...
4566
				_chart: me.chart.chart,
4567
				_datasetIndex: me.index
4568
			});
4569
		},
4570
4571
		createMetaData: function(index) {
4572
			var me = this;
4573
			var type = me.dataElementType;
4574
			return type && new type({
0 ignored issues
show
Coding Style Best Practice introduced by
By convention, constructors like type should be capitalized.
Loading history...
4575
				_chart: me.chart.chart,
4576
				_datasetIndex: me.index,
4577
				_index: index
4578
			});
4579
		},
4580
4581
		addElements: function() {
4582
			var me = this;
4583
			var meta = me.getMeta();
4584
			var data = me.getDataset().data || [];
4585
			var metaData = meta.data;
4586
			var i, ilen;
4587
4588
			for (i=0, ilen=data.length; i<ilen; ++i) {
4589
				metaData[i] = metaData[i] || me.createMetaData(meta, i);
4590
			}
4591
4592
			meta.dataset = meta.dataset || me.createMetaDataset();
4593
		},
4594
4595
		addElementAndReset: function(index) {
4596
			var me = this;
4597
			var element = me.createMetaData(index);
4598
			me.getMeta().data.splice(index, 0, element);
4599
			me.updateElement(element, index, true);
4600
		},
4601
4602
		buildOrUpdateElements: function() {
4603
			// Handle the number of data points changing
4604
			var meta = this.getMeta(),
4605
				md = meta.data,
4606
				numData = this.getDataset().data.length,
4607
				numMetaData = md.length;
4608
4609
			// Make sure that we handle number of datapoints changing
4610
			if (numData < numMetaData) {
4611
				// Remove excess bars for data points that have been removed
4612
				md.splice(numData, numMetaData - numData);
4613
			} else if (numData > numMetaData) {
4614
				// Add new elements
4615
				for (var index = numMetaData; index < numData; ++index) {
4616
					this.addElementAndReset(index);
4617
				}
4618
			}
4619
		},
4620
4621
		update: noop,
4622
4623
		draw: function(ease) {
4624
			var easingDecimal = ease || 1;
4625
			helpers.each(this.getMeta().data, function(element) {
4626
				element.transition(easingDecimal).draw();
4627
			});
4628
		},
4629
4630
		removeHoverStyle: function(element, elementOpts) {
4631
			var dataset = this.chart.data.datasets[element._datasetIndex],
4632
				index = element._index,
4633
				custom = element.custom || {},
4634
				valueOrDefault = helpers.getValueAtIndexOrDefault,
4635
				model = element._model;
4636
4637
			model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
4638
			model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
4639
			model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
4640
		},
4641
4642
		setHoverStyle: function(element) {
4643
			var dataset = this.chart.data.datasets[element._datasetIndex],
4644
				index = element._index,
4645
				custom = element.custom || {},
4646
				valueOrDefault = helpers.getValueAtIndexOrDefault,
4647
				getHoverColor = helpers.getHoverColor,
4648
				model = element._model;
4649
4650
			model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
4651
			model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
4652
			model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
4653
		}
4654
		
4655
    });
4656
	
4657
4658
	Chart.DatasetController.extend = helpers.inherits;
4659
};
4660
},{}],25:[function(require,module,exports){
4661
"use strict";
4662
4663
module.exports = function(Chart) {
4664
4665
  var helpers = Chart.helpers;
4666
4667
  Chart.elements = {};
4668
4669
  Chart.Element = function(configuration) {
4670
    helpers.extend(this, configuration);
4671
    this.initialize.apply(this, arguments);
4672
  };
4673
4674
  helpers.extend(Chart.Element.prototype, {
4675
4676
    initialize: function() {
4677
      this.hidden = false;
4678
    },
4679
4680
    pivot: function() {
4681
      var me = this;
4682
      if (!me._view) {
4683
        me._view = helpers.clone(me._model);
4684
      }
4685
      me._start = helpers.clone(me._view);
4686
      return me;
4687
    },
4688
4689
    transition: function(ease) {
4690
      var me = this;
4691
      
4692
      if (!me._view) {
4693
        me._view = helpers.clone(me._model);
4694
      }
4695
4696
      // No animation -> No Transition
4697
      if (ease === 1) {
4698
        me._view = me._model;
4699
        me._start = null;
4700
        return me;
4701
      }
4702
4703
      if (!me._start) {
4704
        me.pivot();
4705
      }
4706
4707
      helpers.each(me._model, function(value, key) {
4708
4709
        if (key[0] === '_') {
4710
          // Only non-underscored properties
4711
        }
4712
4713
        // Init if doesn't exist
4714
        else if (!me._view.hasOwnProperty(key)) {
4715
          if (typeof value === 'number' && !isNaN(me._view[key])) {
4716
            me._view[key] = value * ease;
4717
          } else {
4718
            me._view[key] = value;
4719
          }
4720
        }
4721
4722
        // No unnecessary computations
4723
        else if (value === me._view[key]) {
4724
          // It's the same! Woohoo!
4725
        }
4726
4727
        // Color transitions if possible
4728
        else if (typeof value === 'string') {
4729
          try {
4730
            var color = helpers.color(me._model[key]).mix(helpers.color(me._start[key]), ease);
4731
            me._view[key] = color.rgbString();
4732
          } catch (err) {
4733
            me._view[key] = value;
4734
          }
4735
        }
4736
        // Number transitions
4737
        else if (typeof value === 'number') {
4738
          var startVal = me._start[key] !== undefined && isNaN(me._start[key]) === false ? me._start[key] : 0;
4739
          me._view[key] = ((me._model[key] - startVal) * ease) + startVal;
4740
        }
4741
        // Everything else
4742
        else {
4743
          me._view[key] = value;
4744
        }
4745
      }, me);
4746
4747
      return me;
4748
    },
4749
4750
    tooltipPosition: function() {
4751
      return {
4752
        x: this._model.x,
4753
        y: this._model.y
4754
      };
4755
    },
4756
4757
    hasValue: function() {
4758
      return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
4759
    }
4760
  });
4761
4762
  Chart.Element.extend = helpers.inherits;
4763
4764
};
4765
4766
},{}],26:[function(require,module,exports){
4767
/*global window: false */
4768
/*global document: false */
4769
"use strict";
4770
4771
var color = require(3);
4772
4773
module.exports = function(Chart) {
4774
	//Global Chart helpers object for utility methods and classes
4775
	var helpers = Chart.helpers = {};
4776
4777
	//-- Basic js utility methods
4778
	helpers.each = function(loopable, callback, self, reverse) {
4779
		// Check to see if null or undefined firstly.
4780
		var i, len;
4781
		if (helpers.isArray(loopable)) {
4782
			len = loopable.length;
4783
			if (reverse) {
4784
				for (i = len - 1; i >= 0; i--) {
4785
					callback.call(self, loopable[i], i);
4786
				}
4787
			} else {
4788
				for (i = 0; i < len; i++) {
4789
					callback.call(self, loopable[i], i);
4790
				}
4791
			}
4792
		} else if (typeof loopable === 'object') {
4793
			var keys = Object.keys(loopable);
4794
			len = keys.length;
4795
			for (i = 0; i < len; i++) {
4796
				callback.call(self, loopable[keys[i]], keys[i]);
4797
			}
4798
		}
4799
	};
4800
	helpers.clone = function(obj) {
4801
		var objClone = {};
4802
		helpers.each(obj, function(value, key) {
4803
			if (helpers.isArray(value)) {
4804
				objClone[key] = value.slice(0);
4805
			} else if (typeof value === 'object' && value !== null) {
4806
				objClone[key] = helpers.clone(value);
4807
			} else {
4808
				objClone[key] = value;
4809
			}
4810
		});
4811
		return objClone;
4812
	};
4813
	helpers.extend = function(base) {
4814
		var setFn = function(value, key) { base[key] = value; };
4815
		for (var i = 1, ilen = arguments.length; i < ilen; i++) {
4816
			helpers.each(arguments[i], setFn);
4817
		}
4818
		return base;
4819
	};
4820
	// Need a special merge function to chart configs since they are now grouped
4821
	helpers.configMerge = function(_base) {
4822
		var base = helpers.clone(_base);
4823
		helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {
4824
			helpers.each(extension, function(value, key) {
4825
				if (key === 'scales') {
4826
					// Scale config merging is complex. Add out own function here for that
4827
					base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value);
4828
4829
				} else if (key === 'scale') {
4830
					// Used in polar area & radar charts since there is only one scale
4831
					base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value);
4832
				} else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) {
4833
					// In this case we have an array of objects replacing another array. Rather than doing a strict replace,
4834
					// merge. This allows easy scale option merging
4835
					var baseArray = base[key];
4836
4837
					helpers.each(value, function(valueObj, index) {
4838
4839
						if (index < baseArray.length) {
4840
							if (typeof baseArray[index] === 'object' && baseArray[index] !== null && typeof valueObj === 'object' && valueObj !== null) {
4841
								// Two objects are coming together. Do a merge of them.
4842
								baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
4843
							} else {
4844
								// Just overwrite in this case since there is nothing to merge
4845
								baseArray[index] = valueObj;
4846
							}
4847
						} else {
4848
							baseArray.push(valueObj); // nothing to merge
4849
						}
4850
					});
4851
4852
				} else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") {
4853
					// If we are overwriting an object with an object, do a merge of the properties.
4854
					base[key] = helpers.configMerge(base[key], value);
4855
4856
				} else {
4857
					// can just overwrite the value in this case
4858
					base[key] = value;
4859
				}
4860
			});
4861
		});
4862
4863
		return base;
4864
	};
4865
	helpers.scaleMerge = function(_base, extension) {
4866
		var base = helpers.clone(_base);
4867
4868
		helpers.each(extension, function(value, key) {
4869
			if (key === 'xAxes' || key === 'yAxes') {
4870
				// These properties are arrays of items
4871
				if (base.hasOwnProperty(key)) {
4872
					helpers.each(value, function(valueObj, index) {
4873
						var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
4874
						var axisDefaults = Chart.scaleService.getScaleDefaults(axisType);
4875
						if (index >= base[key].length || !base[key][index].type) {
4876
							base[key].push(helpers.configMerge(axisDefaults, valueObj));
4877
						} else if (valueObj.type && valueObj.type !== base[key][index].type) {
4878
							// Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults
4879
							base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj);
4880
						} else {
4881
							// Type is the same
4882
							base[key][index] = helpers.configMerge(base[key][index], valueObj);
4883
						}
4884
					});
4885
				} else {
4886
					base[key] = [];
4887
					helpers.each(value, function(valueObj) {
4888
						var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
4889
						base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj));
4890
					});
4891
				}
4892
			} else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") {
4893
				// If we are overwriting an object with an object, do a merge of the properties.
4894
				base[key] = helpers.configMerge(base[key], value);
4895
4896
			} else {
4897
				// can just overwrite the value in this case
4898
				base[key] = value;
4899
			}
4900
		});
4901
4902
		return base;
4903
	};
4904
	helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {
4905
		if (value === undefined || value === null) {
4906
			return defaultValue;
4907
		}
4908
4909
		if (helpers.isArray(value)) {
4910
			return index < value.length ? value[index] : defaultValue;
4911
		}
4912
4913
		return value;
4914
	};
4915
	helpers.getValueOrDefault = function(value, defaultValue) {
4916
		return value === undefined ? defaultValue : value;
4917
	};
4918
	helpers.indexOf = Array.prototype.indexOf?
4919
		function(array, item) { return array.indexOf(item); } :
4920
		function(array, item) {
4921
			for (var i = 0, ilen = array.length; i < ilen; ++i) {
4922
				if (array[i] === item) {
4923
					return i;
4924
				}
4925
			}
4926
			return -1;
4927
		};
4928
	helpers.where = function(collection, filterCallback) {
4929
		if (helpers.isArray(collection) && Array.prototype.filter) {
4930
			return collection.filter(filterCallback);
4931
		} else {
4932
			var filtered = [];
4933
4934
			helpers.each(collection, function(item) {
4935
				if (filterCallback(item)) {
4936
					filtered.push(item);
4937
				}
4938
			});
4939
4940
			return filtered;
4941
		}
4942
	};
4943
	helpers.findIndex = Array.prototype.findIndex?
4944
		function(array, callback, scope) { return array.findIndex(callback, scope); } :
4945
		function(array, callback, scope) {
4946
			scope = scope === undefined? array : scope;
4947
			for (var i = 0, ilen = array.length; i < ilen; ++i) {
4948
				if (callback.call(scope, array[i], i, array)) {
4949
					return i;
4950
				}
4951
			}
4952
			return -1;
4953
		};
4954
	helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
4955
		// Default to start of the array
4956
		if (startIndex === undefined || startIndex === null) {
4957
			startIndex = -1;
4958
		}
4959
		for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
4960
			var currentItem = arrayToSearch[i];
4961
			if (filterCallback(currentItem)) {
4962
				return currentItem;
4963
			}
4964
		}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
4965
	};
4966
	helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
4967
		// Default to end of the array
4968
		if (startIndex === undefined || startIndex === null) {
4969
			startIndex = arrayToSearch.length;
4970
		}
4971
		for (var i = startIndex - 1; i >= 0; i--) {
4972
			var currentItem = arrayToSearch[i];
4973
			if (filterCallback(currentItem)) {
4974
				return currentItem;
4975
			}
4976
		}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
4977
	};
4978
	helpers.inherits = function(extensions) {
4979
		//Basic javascript inheritance based on the model created in Backbone.js
4980
		var parent = this;
4981
		var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() {
4982
			return parent.apply(this, arguments);
4983
		};
4984
4985
		var Surrogate = function() {
4986
			this.constructor = ChartElement;
4987
		};
4988
		Surrogate.prototype = parent.prototype;
4989
		ChartElement.prototype = new Surrogate();
4990
4991
		ChartElement.extend = helpers.inherits;
4992
4993
		if (extensions) {
4994
			helpers.extend(ChartElement.prototype, extensions);
4995
		}
4996
4997
		ChartElement.__super__ = parent.prototype;
4998
4999
		return ChartElement;
5000
	};
5001
	helpers.noop = function() {};
5002
	helpers.uid = (function() {
5003
		var id = 0;
5004
		return function() {
5005
			return id++;
5006
		};
5007
	})();
5008
	//-- Math methods
5009
	helpers.isNumber = function(n) {
5010
		return !isNaN(parseFloat(n)) && isFinite(n);
5011
	};
5012
	helpers.almostEquals = function(x, y, epsilon) {
5013
		return Math.abs(x - y) < epsilon;
5014
	};
5015
	helpers.max = function(array) {
5016
		return array.reduce(function(max, value) {
5017
			if (!isNaN(value)) {
5018
				return Math.max(max, value);
5019
			} else {
5020
				return max;
5021
			}
5022
		}, Number.NEGATIVE_INFINITY);
5023
	};
5024
	helpers.min = function(array) {
5025
		return array.reduce(function(min, value) {
5026
			if (!isNaN(value)) {
5027
				return Math.min(min, value);
5028
			} else {
5029
				return min;
5030
			}
5031
		}, Number.POSITIVE_INFINITY);
5032
	};
5033
	helpers.sign = Math.sign?
5034
		function(x) { return Math.sign(x); } :
5035
		function(x) {
5036
			x = +x; // convert to a number
5037
			if (x === 0 || isNaN(x)) {
5038
				return x;
5039
			}
5040
			return x > 0 ? 1 : -1;
5041
		};
5042
	helpers.log10 = Math.log10?
5043
		function(x) { return Math.log10(x); } :
5044
		function(x) {
5045
			return Math.log(x) / Math.LN10;
5046
		};
5047
	helpers.toRadians = function(degrees) {
5048
		return degrees * (Math.PI / 180);
5049
	};
5050
	helpers.toDegrees = function(radians) {
5051
		return radians * (180 / Math.PI);
5052
	};
5053
	// Gets the angle from vertical upright to the point about a centre.
5054
	helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
5055
		var distanceFromXCenter = anglePoint.x - centrePoint.x,
5056
			distanceFromYCenter = anglePoint.y - centrePoint.y,
5057
			radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
5058
5059
		var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
5060
5061
		if (angle < (-0.5 * Math.PI)) {
5062
			angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
5063
		}
5064
5065
		return {
5066
			angle: angle,
5067
			distance: radialDistanceFromCenter
5068
		};
5069
	};
5070
	helpers.aliasPixel = function(pixelWidth) {
5071
		return (pixelWidth % 2 === 0) ? 0 : 0.5;
5072
	};
5073
	helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
5074
		//Props to Rob Spencer at scaled innovation for his post on splining between points
5075
		//http://scaledinnovation.com/analytics/splines/aboutSplines.html
5076
5077
		// This function must also respect "skipped" points
5078
5079
		var previous = firstPoint.skip ? middlePoint : firstPoint,
5080
			current = middlePoint,
5081
			next = afterPoint.skip ? middlePoint : afterPoint;
5082
5083
		var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
5084
		var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
5085
5086
		var s01 = d01 / (d01 + d12);
5087
		var s12 = d12 / (d01 + d12);
5088
5089
		// If all points are the same, s01 & s02 will be inf
5090
		s01 = isNaN(s01) ? 0 : s01;
5091
		s12 = isNaN(s12) ? 0 : s12;
5092
5093
		var fa = t * s01; // scaling factor for triangle Ta
5094
		var fb = t * s12;
5095
5096
		return {
5097
			previous: {
5098
				x: current.x - fa * (next.x - previous.x),
5099
				y: current.y - fa * (next.y - previous.y)
5100
			},
5101
			next: {
5102
				x: current.x + fb * (next.x - previous.x),
5103
				y: current.y + fb * (next.y - previous.y)
5104
			}
5105
		};
5106
	};
5107
	helpers.nextItem = function(collection, index, loop) {
5108
		if (loop) {
5109
			return index >= collection.length - 1 ? collection[0] : collection[index + 1];
5110
		}
5111
5112
		return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
5113
	};
5114
	helpers.previousItem = function(collection, index, loop) {
5115
		if (loop) {
5116
			return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
5117
		}
5118
		return index <= 0 ? collection[0] : collection[index - 1];
5119
	};
5120
	// Implementation of the nice number algorithm used in determining where axis labels will go
5121
	helpers.niceNum = function(range, round) {
5122
		var exponent = Math.floor(helpers.log10(range));
5123
		var fraction = range / Math.pow(10, exponent);
5124
		var niceFraction;
5125
5126
		if (round) {
5127
			if (fraction < 1.5) {
5128
				niceFraction = 1;
5129
			} else if (fraction < 3) {
5130
				niceFraction = 2;
5131
			} else if (fraction < 7) {
5132
				niceFraction = 5;
5133
			} else {
5134
				niceFraction = 10;
5135
			}
5136
		} else {
5137
			if (fraction <= 1.0) {
5138
				niceFraction = 1;
5139
			} else if (fraction <= 2) {
5140
				niceFraction = 2;
5141
			} else if (fraction <= 5) {
5142
				niceFraction = 5;
5143
			} else {
5144
				niceFraction = 10;
5145
			}
5146
		}
5147
5148
		return niceFraction * Math.pow(10, exponent);
5149
	};
5150
	//Easing functions adapted from Robert Penner's easing equations
5151
	//http://www.robertpenner.com/easing/
5152
	var easingEffects = helpers.easingEffects = {
5153
		linear: function(t) {
5154
			return t;
5155
		},
5156
		easeInQuad: function(t) {
5157
			return t * t;
5158
		},
5159
		easeOutQuad: function(t) {
5160
			return -1 * t * (t - 2);
5161
		},
5162
		easeInOutQuad: function(t) {
5163
			if ((t /= 1 / 2) < 1) {
5164
				return 1 / 2 * t * t;
5165
			}
5166
			return -1 / 2 * ((--t) * (t - 2) - 1);
5167
		},
5168
		easeInCubic: function(t) {
5169
			return t * t * t;
5170
		},
5171
		easeOutCubic: function(t) {
5172
			return 1 * ((t = t / 1 - 1) * t * t + 1);
5173
		},
5174
		easeInOutCubic: function(t) {
5175
			if ((t /= 1 / 2) < 1) {
5176
				return 1 / 2 * t * t * t;
5177
			}
5178
			return 1 / 2 * ((t -= 2) * t * t + 2);
5179
		},
5180
		easeInQuart: function(t) {
5181
			return t * t * t * t;
5182
		},
5183
		easeOutQuart: function(t) {
5184
			return -1 * ((t = t / 1 - 1) * t * t * t - 1);
5185
		},
5186
		easeInOutQuart: function(t) {
5187
			if ((t /= 1 / 2) < 1) {
5188
				return 1 / 2 * t * t * t * t;
5189
			}
5190
			return -1 / 2 * ((t -= 2) * t * t * t - 2);
5191
		},
5192
		easeInQuint: function(t) {
5193
			return 1 * (t /= 1) * t * t * t * t;
5194
		},
5195
		easeOutQuint: function(t) {
5196
			return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
5197
		},
5198
		easeInOutQuint: function(t) {
5199
			if ((t /= 1 / 2) < 1) {
5200
				return 1 / 2 * t * t * t * t * t;
5201
			}
5202
			return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
5203
		},
5204
		easeInSine: function(t) {
5205
			return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
5206
		},
5207
		easeOutSine: function(t) {
5208
			return 1 * Math.sin(t / 1 * (Math.PI / 2));
5209
		},
5210
		easeInOutSine: function(t) {
5211
			return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
5212
		},
5213
		easeInExpo: function(t) {
5214
			return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
5215
		},
5216
		easeOutExpo: function(t) {
5217
			return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
5218
		},
5219
		easeInOutExpo: function(t) {
5220
			if (t === 0) {
5221
				return 0;
5222
			}
5223
			if (t === 1) {
5224
				return 1;
5225
			}
5226
			if ((t /= 1 / 2) < 1) {
5227
				return 1 / 2 * Math.pow(2, 10 * (t - 1));
5228
			}
5229
			return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
5230
		},
5231
		easeInCirc: function(t) {
5232
			if (t >= 1) {
5233
				return t;
5234
			}
5235
			return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
5236
		},
5237
		easeOutCirc: function(t) {
5238
			return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
5239
		},
5240
		easeInOutCirc: function(t) {
5241
			if ((t /= 1 / 2) < 1) {
5242
				return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
5243
			}
5244
			return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
5245
		},
5246
		easeInElastic: function(t) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
5247
			var s = 1.70158;
0 ignored issues
show
Unused Code introduced by
The assignment to variable s seems to be never used. Consider removing it.
Loading history...
5248
			var p = 0;
5249
			var a = 1;
5250
			if (t === 0) {
5251
				return 0;
5252
			}
5253
			if ((t /= 1) === 1) {
5254
				return 1;
5255
			}
5256
			if (!p) {
5257
				p = 1 * 0.3;
5258
			}
5259
			if (a < Math.abs(1)) {
5260
				a = 1;
5261
				s = p / 4;
5262
			} else {
5263
				s = p / (2 * Math.PI) * Math.asin(1 / a);
5264
			}
5265
			return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
5266
		},
5267
		easeOutElastic: function(t) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
5268
			var s = 1.70158;
0 ignored issues
show
Unused Code introduced by
The assignment to variable s seems to be never used. Consider removing it.
Loading history...
5269
			var p = 0;
5270
			var a = 1;
5271
			if (t === 0) {
5272
				return 0;
5273
			}
5274
			if ((t /= 1) === 1) {
5275
				return 1;
5276
			}
5277
			if (!p) {
5278
				p = 1 * 0.3;
5279
			}
5280
			if (a < Math.abs(1)) {
5281
				a = 1;
5282
				s = p / 4;
5283
			} else {
5284
				s = p / (2 * Math.PI) * Math.asin(1 / a);
5285
			}
5286
			return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
5287
		},
5288
		easeInOutElastic: function(t) {
5289
			var s = 1.70158;
0 ignored issues
show
Unused Code introduced by
The assignment to variable s seems to be never used. Consider removing it.
Loading history...
5290
			var p = 0;
5291
			var a = 1;
5292
			if (t === 0) {
5293
				return 0;
5294
			}
5295
			if ((t /= 1 / 2) === 2) {
5296
				return 1;
5297
			}
5298
			if (!p) {
5299
				p = 1 * (0.3 * 1.5);
5300
			}
5301
			if (a < Math.abs(1)) {
5302
				a = 1;
5303
				s = p / 4;
5304
			} else {
5305
				s = p / (2 * Math.PI) * Math.asin(1 / a);
5306
			}
5307
			if (t < 1) {
5308
				return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
5309
			}
5310
			return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
5311
		},
5312
		easeInBack: function(t) {
5313
			var s = 1.70158;
5314
			return 1 * (t /= 1) * t * ((s + 1) * t - s);
5315
		},
5316
		easeOutBack: function(t) {
5317
			var s = 1.70158;
5318
			return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
5319
		},
5320
		easeInOutBack: function(t) {
5321
			var s = 1.70158;
5322
			if ((t /= 1 / 2) < 1) {
5323
				return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
5324
			}
5325
			return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
5326
		},
5327
		easeInBounce: function(t) {
5328
			return 1 - easingEffects.easeOutBounce(1 - t);
5329
		},
5330
		easeOutBounce: function(t) {
5331
			if ((t /= 1) < (1 / 2.75)) {
5332
				return 1 * (7.5625 * t * t);
5333
			} else if (t < (2 / 2.75)) {
5334
				return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
5335
			} else if (t < (2.5 / 2.75)) {
5336
				return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
5337
			} else {
5338
				return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
5339
			}
5340
		},
5341
		easeInOutBounce: function(t) {
5342
			if (t < 1 / 2) {
5343
				return easingEffects.easeInBounce(t * 2) * 0.5;
5344
			}
5345
			return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
5346
		}
5347
	};
5348
	//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
5349
	helpers.requestAnimFrame = (function() {
5350
		return window.requestAnimationFrame ||
5351
			window.webkitRequestAnimationFrame ||
5352
			window.mozRequestAnimationFrame ||
5353
			window.oRequestAnimationFrame ||
5354
			window.msRequestAnimationFrame ||
5355
			function(callback) {
5356
				return window.setTimeout(callback, 1000 / 60);
5357
			};
5358
	})();
5359
	helpers.cancelAnimFrame = (function() {
5360
		return window.cancelAnimationFrame ||
5361
			window.webkitCancelAnimationFrame ||
5362
			window.mozCancelAnimationFrame ||
5363
			window.oCancelAnimationFrame ||
5364
			window.msCancelAnimationFrame ||
5365
			function(callback) {
5366
				return window.clearTimeout(callback, 1000 / 60);
5367
			};
5368
	})();
5369
	//-- DOM methods
5370
	helpers.getRelativePosition = function(evt, chart) {
5371
		var mouseX, mouseY;
5372
		var e = evt.originalEvent || evt,
5373
			canvas = evt.currentTarget || evt.srcElement,
5374
			boundingRect = canvas.getBoundingClientRect();
5375
5376
		var touches = e.touches;
5377
		if (touches && touches.length > 0) {
5378
			mouseX = touches[0].clientX;
5379
			mouseY = touches[0].clientY;
5380
5381
		} else {
5382
			mouseX = e.clientX;
5383
			mouseY = e.clientY;
5384
		}
5385
5386
		// Scale mouse coordinates into canvas coordinates
5387
		// by following the pattern laid out by 'jerryj' in the comments of
5388
		// http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
5389
		var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
5390
		var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
5391
		var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
5392
		var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
5393
		var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
5394
		var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
5395
5396
		// We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
5397
		// the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
5398
		mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
5399
		mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
5400
5401
		return {
5402
			x: mouseX,
5403
			y: mouseY
5404
		};
5405
5406
	};
5407
	helpers.addEvent = function(node, eventType, method) {
5408
		if (node.addEventListener) {
5409
			node.addEventListener(eventType, method);
5410
		} else if (node.attachEvent) {
5411
			node.attachEvent("on" + eventType, method);
5412
		} else {
5413
			node["on" + eventType] = method;
5414
		}
5415
	};
5416
	helpers.removeEvent = function(node, eventType, handler) {
5417
		if (node.removeEventListener) {
5418
			node.removeEventListener(eventType, handler, false);
5419
		} else if (node.detachEvent) {
5420
			node.detachEvent("on" + eventType, handler);
5421
		} else {
5422
			node["on" + eventType] = helpers.noop;
5423
		}
5424
	};
5425
	helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) {
5426
		// Create the events object if it's not already present
5427
		var events = chartInstance.events = chartInstance.events || {};
5428
5429
		helpers.each(arrayOfEvents, function(eventName) {
5430
			events[eventName] = function() {
5431
				handler.apply(chartInstance, arguments);
5432
			};
5433
			helpers.addEvent(chartInstance.chart.canvas, eventName, events[eventName]);
5434
		});
5435
	};
5436
	helpers.unbindEvents = function(chartInstance, arrayOfEvents) {
5437
		var canvas = chartInstance.chart.canvas;
5438
		helpers.each(arrayOfEvents, function(handler, eventName) {
5439
			helpers.removeEvent(canvas, eventName, handler);
5440
		});
5441
	};
5442
5443
	// Private helper function to convert max-width/max-height values that may be percentages into a number
5444
	function parseMaxStyle(styleValue, node, parentProperty) {
5445
		var valueInPixels;
5446
		if (typeof(styleValue) === 'string') {
5447
			valueInPixels = parseInt(styleValue, 10);
5448
5449
			if (styleValue.indexOf('%') != -1) {
5450
				// percentage * size in dimension
5451
				valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
5452
			}
5453
		} else {
5454
			valueInPixels = styleValue;
5455
		}
5456
5457
		return valueInPixels;
5458
	}
5459
5460
	/**
5461
	 * Returns if the given value contains an effective constraint.
5462
	 * @private
5463
	 */
5464
	function isConstrainedValue(value) {
5465
		return value !== undefined &&  value !== null && value !== 'none';
5466
	}
5467
5468
	// Private helper to get a constraint dimension
5469
	// @param domNode : the node to check the constraint on
5470
	// @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
5471
	// @param percentageProperty : property of parent to use when calculating width as a percentage
5472
	// @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
5473
	function getConstraintDimension(domNode, maxStyle, percentageProperty) {
5474
		var view = document.defaultView;
5475
		var parentNode = domNode.parentNode;
5476
		var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
5477
		var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
5478
		var hasCNode = isConstrainedValue(constrainedNode);
5479
		var hasCContainer = isConstrainedValue(constrainedContainer);
5480
		var infinity = Number.POSITIVE_INFINITY;
5481
5482
		if (hasCNode || hasCContainer) {
5483
			return Math.min(
5484
				hasCNode? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
5485
				hasCContainer? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
5486
		}
5487
5488
		return 'none';
5489
	}
5490
	// returns Number or undefined if no constraint
5491
	helpers.getConstraintWidth = function(domNode) {
5492
		return getConstraintDimension(domNode, 'max-width', 'clientWidth');
5493
	};
5494
	// returns Number or undefined if no constraint
5495
	helpers.getConstraintHeight = function(domNode) {
5496
		return getConstraintDimension(domNode, 'max-height', 'clientHeight');
5497
	};
5498
	helpers.getMaximumWidth = function(domNode) {
5499
		var container = domNode.parentNode;
5500
		var padding = parseInt(helpers.getStyle(container, 'padding-left')) + parseInt(helpers.getStyle(container, 'padding-right'));
5501
		var w = container.clientWidth - padding;
5502
		var cw = helpers.getConstraintWidth(domNode);
5503
		return isNaN(cw)? w : Math.min(w, cw);
5504
	};
5505
	helpers.getMaximumHeight = function(domNode) {
5506
		var container = domNode.parentNode;
5507
		var padding = parseInt(helpers.getStyle(container, 'padding-top')) + parseInt(helpers.getStyle(container, 'padding-bottom'));
5508
		var h = container.clientHeight - padding;
5509
		var ch = helpers.getConstraintHeight(domNode);
5510
		return isNaN(ch)? h : Math.min(h, ch);
5511
	};
5512
	helpers.getStyle = function(el, property) {
5513
		return el.currentStyle ?
5514
			el.currentStyle[property] :
5515
			document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
5516
	};
5517
	helpers.retinaScale = function(chart) {
5518
		var ctx = chart.ctx;
5519
		var canvas = chart.canvas;
5520
		var width = canvas.width;
5521
		var height = canvas.height;
5522
		var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1;
5523
5524
		if (pixelRatio !== 1) {
5525
			canvas.height = height * pixelRatio;
5526
			canvas.width = width * pixelRatio;
5527
			ctx.scale(pixelRatio, pixelRatio);
5528
5529
			// Store the device pixel ratio so that we can go backwards in `destroy`.
5530
			// The devicePixelRatio changes with zoom, so there are no guarantees that it is the same
5531
			// when destroy is called
5532
			chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio;
5533
		}
5534
5535
		canvas.style.width = width + 'px';
5536
		canvas.style.height = height + 'px';
5537
	};
5538
	//-- Canvas methods
5539
	helpers.clear = function(chart) {
5540
		chart.ctx.clearRect(0, 0, chart.width, chart.height);
5541
	};
5542
	helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
5543
		return fontStyle + " " + pixelSize + "px " + fontFamily;
5544
	};
5545
	helpers.longestText = function(ctx, font, arrayOfThings, cache) {
5546
		cache = cache || {};
5547
		var data = cache.data = cache.data || {};
5548
		var gc = cache.garbageCollect = cache.garbageCollect || [];
5549
5550
		if (cache.font !== font) {
5551
			data = cache.data = {};
5552
			gc = cache.garbageCollect = [];
5553
			cache.font = font;
5554
		}
5555
5556
		ctx.font = font;
5557
		var longest = 0;
5558
		helpers.each(arrayOfThings, function(thing) {
5559
			// Undefined strings and arrays should not be measured
5560
			if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
5561
				longest = helpers.measureText(ctx, data, gc, longest, thing);
5562
			} else if (helpers.isArray(thing)) {
5563
				// if it is an array lets measure each element
5564
				// to do maybe simplify this function a bit so we can do this more recursively?
5565
				helpers.each(thing, function(nestedThing) {
5566
					// Undefined strings and arrays should not be measured
5567
					if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
5568
						longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
5569
					}
5570
				});
5571
			}
5572
		});
5573
5574
		var gcLen = gc.length / 2;
5575
		if (gcLen > arrayOfThings.length) {
5576
			for (var i = 0; i < gcLen; i++) {
5577
				delete data[gc[i]];
5578
			}
5579
			gc.splice(0, gcLen);
5580
		}
5581
		return longest;
5582
	};
5583
	helpers.measureText = function (ctx, data, gc, longest, string) {
5584
		var textWidth = data[string];
5585
		if (!textWidth) {
5586
			textWidth = data[string] = ctx.measureText(string).width;
5587
			gc.push(string);
5588
		}
5589
		if (textWidth > longest) {
5590
			longest = textWidth;
5591
		}
5592
		return longest;
5593
	};
5594
	helpers.numberOfLabelLines = function(arrayOfThings) {
5595
		var numberOfLines = 1;
5596
		helpers.each(arrayOfThings, function(thing) {
5597
			if (helpers.isArray(thing)) {
5598
				if (thing.length > numberOfLines) {
5599
					numberOfLines = thing.length;
5600
				}
5601
			}
5602
		});
5603
		return numberOfLines;
5604
	};
5605
	helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
5606
		ctx.beginPath();
5607
		ctx.moveTo(x + radius, y);
5608
		ctx.lineTo(x + width - radius, y);
5609
		ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
5610
		ctx.lineTo(x + width, y + height - radius);
5611
		ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
5612
		ctx.lineTo(x + radius, y + height);
5613
		ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
5614
		ctx.lineTo(x, y + radius);
5615
		ctx.quadraticCurveTo(x, y, x + radius, y);
5616
		ctx.closePath();
5617
	};
5618
	helpers.color = function(c) {
5619
		if (!color) {
5620
			console.log('Color.js not found!');
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
5621
			return c;
5622
		}
5623
5624
		/* global CanvasGradient */
5625
		if (c instanceof CanvasGradient) {
5626
			return color(Chart.defaults.global.defaultColor);
5627
		}
5628
5629
		return color(c);
5630
	};
5631
	helpers.addResizeListener = function(node, callback) {
5632
		// Hide an iframe before the node
5633
		var hiddenIframe = document.createElement('iframe');
5634
		var hiddenIframeClass = 'chartjs-hidden-iframe';
5635
5636
		if (hiddenIframe.classlist) {
5637
			// can use classlist
5638
			hiddenIframe.classlist.add(hiddenIframeClass);
5639
		} else {
5640
			hiddenIframe.setAttribute('class', hiddenIframeClass);
5641
		}
5642
5643
		// Set the style
5644
		var style = hiddenIframe.style;
5645
		style.width = '100%';
5646
		style.display = 'block';
5647
		style.border = 0;
5648
		style.height = 0;
5649
		style.margin = 0;
5650
		style.position = 'absolute';
5651
		style.left = 0;
5652
		style.right = 0;
5653
		style.top = 0;
5654
		style.bottom = 0;
5655
5656
		// Insert the iframe so that contentWindow is available
5657
		node.insertBefore(hiddenIframe, node.firstChild);
5658
5659
		(hiddenIframe.contentWindow || hiddenIframe).onresize = function() {
5660
			if (callback) {
5661
				callback();
5662
			}
5663
		};
5664
	};
5665
	helpers.removeResizeListener = function(node) {
5666
		var hiddenIframe = node.querySelector('.chartjs-hidden-iframe');
5667
5668
		// Remove the resize detect iframe
5669
		if (hiddenIframe) {
5670
			hiddenIframe.parentNode.removeChild(hiddenIframe);
5671
		}
5672
	};
5673
	helpers.isArray = Array.isArray?
5674
		function(obj) { return Array.isArray(obj); } :
5675
		function(obj) {
5676
			return Object.prototype.toString.call(obj) === '[object Array]';
5677
		};
5678
	//! @see http://stackoverflow.com/a/14853974
5679
	helpers.arrayEquals = function(a0, a1) {
5680
		var i, ilen, v0, v1;
5681
5682
		if (!a0 || !a1 || a0.length != a1.length) {
5683
			return false;
5684
		}
5685
5686
		for (i = 0, ilen=a0.length; i < ilen; ++i) {
5687
			v0 = a0[i];
5688
			v1 = a1[i];
5689
5690
			if (v0 instanceof Array && v1 instanceof Array) {
5691
				if (!helpers.arrayEquals(v0, v1)) {
5692
					return false;
5693
				}
5694
			} else if (v0 != v1) {
5695
				// NOTE: two different object instances will never be equal: {x:20} != {x:20}
5696
				return false;
5697
			}
5698
		}
5699
5700
		return true;
5701
	};
5702
	helpers.callCallback = function(fn, args, _tArg) {
5703
		if (fn && typeof fn.call === 'function') {
5704
			fn.apply(_tArg, args);
5705
		}
5706
	};
5707
	helpers.getHoverColor = function(color) {
5708
		/* global CanvasPattern */
5709
		return (color instanceof CanvasPattern) ?
5710
			color :
5711
			helpers.color(color).saturate(0.5).darken(0.1).rgbString();
5712
	};
5713
};
5714
5715
},{"3":3}],27:[function(require,module,exports){
5716
"use strict";
5717
5718
module.exports = function() {
5719
5720
	//Occupy the global variable of Chart, and create a simple base class
5721
	var Chart = function(context, config) {
5722
		var me = this;
5723
		var helpers = Chart.helpers;
5724
		me.config = config || { 
5725
			data: {
5726
				datasets: []
5727
			}
5728
		};
5729
5730
		// Support a jQuery'd canvas element
5731
		if (context.length && context[0].getContext) {
5732
			context = context[0];
5733
		}
5734
5735
		// Support a canvas domnode
5736
		if (context.getContext) {
5737
			context = context.getContext("2d");
5738
		}
5739
5740
		me.ctx = context;
5741
		me.canvas = context.canvas;
5742
5743
		context.canvas.style.display = context.canvas.style.display || 'block';
5744
5745
		// Figure out what the size of the chart will be.
5746
		// If the canvas has a specified width and height, we use those else
5747
		// we look to see if the canvas node has a CSS width and height.
5748
		// If there is still no height, fill the parent container
5749
		me.width = context.canvas.width || parseInt(helpers.getStyle(context.canvas, 'width'), 10) || helpers.getMaximumWidth(context.canvas);
5750
		me.height = context.canvas.height || parseInt(helpers.getStyle(context.canvas, 'height'), 10) || helpers.getMaximumHeight(context.canvas);
5751
5752
		me.aspectRatio = me.width / me.height;
5753
5754
		if (isNaN(me.aspectRatio) || isFinite(me.aspectRatio) === false) {
5755
			// If the canvas has no size, try and figure out what the aspect ratio will be.
5756
			// Some charts prefer square canvases (pie, radar, etc). If that is specified, use that
5757
			// else use the canvas default ratio of 2
5758
			me.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2;
5759
		}
5760
5761
		// Store the original style of the element so we can set it back
5762
		me.originalCanvasStyleWidth = context.canvas.style.width;
5763
		me.originalCanvasStyleHeight = context.canvas.style.height;
5764
5765
		// High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
5766
		helpers.retinaScale(me);
5767
		me.controller = new Chart.Controller(me);
5768
5769
		// Always bind this so that if the responsive state changes we still work
5770
		helpers.addResizeListener(context.canvas.parentNode, function() {
5771
			if (me.controller && me.controller.config.options.responsive) {
5772
				me.controller.resize();
5773
			}
5774
		});
5775
5776
		return me.controller ? me.controller : me;
5777
5778
	};
5779
5780
	//Globally expose the defaults to allow for user updating/changing
5781
	Chart.defaults = {
5782
		global: {
5783
			responsive: true,
5784
			responsiveAnimationDuration: 0,
5785
			maintainAspectRatio: true,
5786
			events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"],
5787
			hover: {
5788
				onHover: null,
5789
				mode: 'single',
5790
				animationDuration: 400
5791
			},
5792
			onClick: null,
5793
			defaultColor: 'rgba(0,0,0,0.1)',
5794
			defaultFontColor: '#666',
5795
			defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
5796
			defaultFontSize: 12,
5797
			defaultFontStyle: 'normal',
5798
			showLines: true,
5799
5800
			// Element defaults defined in element extensions
5801
			elements: {},
5802
5803
			// Legend callback string
5804
			legendCallback: function(chart) {
5805
				var text = [];
5806
				text.push('<ul class="' + chart.id + '-legend">');
5807
				for (var i = 0; i < chart.data.datasets.length; i++) {
5808
					text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
5809
					if (chart.data.datasets[i].label) {
5810
						text.push(chart.data.datasets[i].label);
5811
					}
5812
					text.push('</li>');
5813
				}
5814
				text.push('</ul>');
5815
5816
				return text.join("");
5817
			}
5818
		}
5819
	};
5820
5821
	Chart.Chart = Chart;
5822
5823
	return Chart;
5824
5825
};
5826
5827
},{}],28:[function(require,module,exports){
5828
"use strict";
5829
5830
module.exports = function(Chart) {
5831
5832
	var helpers = Chart.helpers;
5833
5834
	// The layout service is very self explanatory.  It's responsible for the layout within a chart.
5835
	// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
5836
	// It is this service's responsibility of carrying out that layout.
5837
	Chart.layoutService = {
5838
		defaults: {},
5839
5840
		// Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
5841
		addBox: function(chartInstance, box) {
5842
			if (!chartInstance.boxes) {
5843
				chartInstance.boxes = [];
5844
			}
5845
			chartInstance.boxes.push(box);
5846
		},
5847
5848
		removeBox: function(chartInstance, box) {
5849
			if (!chartInstance.boxes) {
5850
				return;
5851
			}
5852
			chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);
5853
		},
5854
5855
		// The most important function
5856
		update: function(chartInstance, width, height) {
5857
5858
			if (!chartInstance) {
5859
				return;
5860
			}
5861
5862
			var xPadding = 0;
5863
			var yPadding = 0;
5864
5865
			var leftBoxes = helpers.where(chartInstance.boxes, function(box) {
5866
				return box.options.position === "left";
5867
			});
5868
			var rightBoxes = helpers.where(chartInstance.boxes, function(box) {
5869
				return box.options.position === "right";
5870
			});
5871
			var topBoxes = helpers.where(chartInstance.boxes, function(box) {
5872
				return box.options.position === "top";
5873
			});
5874
			var bottomBoxes = helpers.where(chartInstance.boxes, function(box) {
5875
				return box.options.position === "bottom";
5876
			});
5877
5878
			// Boxes that overlay the chartarea such as the radialLinear scale
5879
			var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {
5880
				return box.options.position === "chartArea";
5881
			});
5882
5883
			// Ensure that full width boxes are at the very top / bottom
5884
			topBoxes.sort(function(a, b) {
5885
				return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
5886
			});
5887
			bottomBoxes.sort(function(a, b) {
5888
				return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
5889
			});
5890
5891
			// Essentially we now have any number of boxes on each of the 4 sides.
5892
			// Our canvas looks like the following.
5893
			// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
5894
			// B1 is the bottom axis
5895
			// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
5896
			// These locations are single-box locations only, when trying to register a chartArea location that is already taken,
5897
			// an error will be thrown.
5898
			//
5899
			// |----------------------------------------------------|
5900
			// |                  T1 (Full Width)                   |
5901
			// |----------------------------------------------------|
5902
			// |    |    |                 T2                  |    |
5903
			// |    |----|-------------------------------------|----|
5904
			// |    |    | C1 |                           | C2 |    |
5905
			// |    |    |----|                           |----|    |
5906
			// |    |    |                                     |    |
5907
			// | L1 | L2 |           ChartArea (C0)            | R1 |
5908
			// |    |    |                                     |    |
5909
			// |    |    |----|                           |----|    |
5910
			// |    |    | C3 |                           | C4 |    |
5911
			// |    |----|-------------------------------------|----|
5912
			// |    |    |                 B1                  |    |
5913
			// |----------------------------------------------------|
5914
			// |                  B2 (Full Width)                   |
5915
			// |----------------------------------------------------|
5916
			//
5917
			// What we do to find the best sizing, we do the following
5918
			// 1. Determine the minimum size of the chart area.
5919
			// 2. Split the remaining width equally between each vertical axis
5920
			// 3. Split the remaining height equally between each horizontal axis
5921
			// 4. Give each layout the maximum size it can be. The layout will return it's minimum size
5922
			// 5. Adjust the sizes of each axis based on it's minimum reported size.
5923
			// 6. Refit each axis
5924
			// 7. Position each axis in the final location
5925
			// 8. Tell the chart the final location of the chart area
5926
			// 9. Tell any axes that overlay the chart area the positions of the chart area
5927
5928
			// Step 1
5929
			var chartWidth = width - (2 * xPadding);
5930
			var chartHeight = height - (2 * yPadding);
5931
			var chartAreaWidth = chartWidth / 2; // min 50%
5932
			var chartAreaHeight = chartHeight / 2; // min 50%
5933
5934
			// Step 2
5935
			var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
5936
5937
			// Step 3
5938
			var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
5939
5940
			// Step 4
5941
			var maxChartAreaWidth = chartWidth;
5942
			var maxChartAreaHeight = chartHeight;
5943
			var minBoxSizes = [];
5944
5945
			helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
5946
5947
			function getMinimumBoxSize(box) {
5948
				var minSize;
5949
				var isHorizontal = box.isHorizontal();
5950
5951
				if (isHorizontal) {
5952
					minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
5953
					maxChartAreaHeight -= minSize.height;
5954
				} else {
5955
					minSize = box.update(verticalBoxWidth, chartAreaHeight);
5956
					maxChartAreaWidth -= minSize.width;
5957
				}
5958
5959
				minBoxSizes.push({
5960
					horizontal: isHorizontal,
5961
					minSize: minSize,
5962
					box: box
5963
				});
5964
			}
5965
5966
			// At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
5967
			// be if the axes are drawn at their minimum sizes.
5968
5969
			// Steps 5 & 6
5970
			var totalLeftBoxesWidth = xPadding;
5971
			var totalRightBoxesWidth = xPadding;
5972
			var totalTopBoxesHeight = yPadding;
5973
			var totalBottomBoxesHeight = yPadding;
5974
5975
			// Update, and calculate the left and right margins for the horizontal boxes
5976
			helpers.each(leftBoxes.concat(rightBoxes), fitBox);
5977
5978
			helpers.each(leftBoxes, function(box) {
5979
				totalLeftBoxesWidth += box.width;
5980
			});
5981
5982
			helpers.each(rightBoxes, function(box) {
5983
				totalRightBoxesWidth += box.width;
5984
			});
5985
5986
			// Set the Left and Right margins for the horizontal boxes
5987
			helpers.each(topBoxes.concat(bottomBoxes), fitBox);
5988
5989
			// Function to fit a box
5990
			function fitBox(box) {
5991
				var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
5992
					return minBoxSize.box === box;
5993
				});
5994
5995
				if (minBoxSize) {
5996
					if (box.isHorizontal()) {
5997
						var scaleMargin = {
5998
							left: totalLeftBoxesWidth,
5999
							right: totalRightBoxesWidth,
6000
							top: 0,
6001
							bottom: 0
6002
						};
6003
6004
						// Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
6005
						// on the margin. Sometimes they need to increase in size slightly
6006
						box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
6007
					} else {
6008
						box.update(minBoxSize.minSize.width, maxChartAreaHeight);
6009
					}
6010
				}
6011
			}
6012
6013
			// Figure out how much margin is on the top and bottom of the vertical boxes
6014
			helpers.each(topBoxes, function(box) {
6015
				totalTopBoxesHeight += box.height;
6016
			});
6017
6018
			helpers.each(bottomBoxes, function(box) {
6019
				totalBottomBoxesHeight += box.height;
6020
			});
6021
6022
			// Let the left layout know the final margin
6023
			helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
6024
6025
			function finalFitVerticalBox(box) {
6026
				var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
6027
					return minBoxSize.box === box;
6028
				});
6029
6030
				var scaleMargin = {
6031
					left: 0,
6032
					right: 0,
6033
					top: totalTopBoxesHeight,
6034
					bottom: totalBottomBoxesHeight
6035
				};
6036
6037
				if (minBoxSize) {
6038
					box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
6039
				}
6040
			}
6041
6042
			// Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
6043
			totalLeftBoxesWidth = xPadding;
6044
			totalRightBoxesWidth = xPadding;
6045
			totalTopBoxesHeight = yPadding;
6046
			totalBottomBoxesHeight = yPadding;
6047
6048
			helpers.each(leftBoxes, function(box) {
6049
				totalLeftBoxesWidth += box.width;
6050
			});
6051
6052
			helpers.each(rightBoxes, function(box) {
6053
				totalRightBoxesWidth += box.width;
6054
			});
6055
6056
			helpers.each(topBoxes, function(box) {
6057
				totalTopBoxesHeight += box.height;
6058
			});
6059
			helpers.each(bottomBoxes, function(box) {
6060
				totalBottomBoxesHeight += box.height;
6061
			});
6062
6063
			// Figure out if our chart area changed. This would occur if the dataset layout label rotation
6064
			// changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
6065
			// without calling `fit` again
6066
			var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
6067
			var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
6068
6069
			if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
6070
				helpers.each(leftBoxes, function(box) {
6071
					box.height = newMaxChartAreaHeight;
6072
				});
6073
6074
				helpers.each(rightBoxes, function(box) {
6075
					box.height = newMaxChartAreaHeight;
6076
				});
6077
6078
				helpers.each(topBoxes, function(box) {
6079
					if (!box.options.fullWidth) {
6080
						box.width = newMaxChartAreaWidth;
6081
					}
6082
				});
6083
6084
				helpers.each(bottomBoxes, function(box) {
6085
					if (!box.options.fullWidth) {
6086
						box.width = newMaxChartAreaWidth;
6087
					}
6088
				});
6089
6090
				maxChartAreaHeight = newMaxChartAreaHeight;
6091
				maxChartAreaWidth = newMaxChartAreaWidth;
6092
			}
6093
6094
			// Step 7 - Position the boxes
6095
			var left = xPadding;
6096
			var top = yPadding;
6097
6098
			helpers.each(leftBoxes.concat(topBoxes), placeBox);
6099
6100
			// Account for chart width and height
6101
			left += maxChartAreaWidth;
6102
			top += maxChartAreaHeight;
6103
6104
			helpers.each(rightBoxes, placeBox);
6105
			helpers.each(bottomBoxes, placeBox);
6106
6107
			function placeBox(box) {
6108
				if (box.isHorizontal()) {
6109
					box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth;
6110
					box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth;
6111
					box.top = top;
6112
					box.bottom = top + box.height;
6113
6114
					// Move to next point
6115
					top = box.bottom;
6116
6117
				} else {
6118
6119
					box.left = left;
6120
					box.right = left + box.width;
6121
					box.top = totalTopBoxesHeight;
6122
					box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
6123
6124
					// Move to next point
6125
					left = box.right;
6126
				}
6127
			}
6128
6129
			// Step 8
6130
			chartInstance.chartArea = {
6131
				left: totalLeftBoxesWidth,
6132
				top: totalTopBoxesHeight,
6133
				right: totalLeftBoxesWidth + maxChartAreaWidth,
6134
				bottom: totalTopBoxesHeight + maxChartAreaHeight
6135
			};
6136
6137
			// Step 9
6138
			helpers.each(chartAreaBoxes, function(box) {
6139
				box.left = chartInstance.chartArea.left;
6140
				box.top = chartInstance.chartArea.top;
6141
				box.right = chartInstance.chartArea.right;
6142
				box.bottom = chartInstance.chartArea.bottom;
6143
6144
				box.update(maxChartAreaWidth, maxChartAreaHeight);
6145
			});
6146
		}
6147
	};
6148
};
6149
6150
},{}],29:[function(require,module,exports){
6151
"use strict";
6152
6153
module.exports = function(Chart) {
6154
6155
	var helpers = Chart.helpers;
6156
	var noop = helpers.noop;
6157
6158
	Chart.defaults.global.legend = {
6159
6160
		display: true,
6161
		position: 'top',
6162
		fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
6163
		reverse: false,
6164
6165
		// a callback that will handle
6166
		onClick: function(e, legendItem) {
6167
			var index = legendItem.datasetIndex;
6168
			var ci = this.chart;
6169
			var meta = ci.getDatasetMeta(index);
6170
6171
			// See controller.isDatasetVisible comment
6172
			meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
6173
6174
			// We hid a dataset ... rerender the chart
6175
			ci.update();
6176
		},
6177
6178
		labels: {
6179
			boxWidth: 40,
6180
			padding: 10,
6181
			// Generates labels shown in the legend
6182
			// Valid properties to return:
6183
			// text : text to display
6184
			// fillStyle : fill of coloured box
6185
			// strokeStyle: stroke of coloured box
6186
			// hidden : if this legend item refers to a hidden item
6187
			// lineCap : cap style for line
6188
			// lineDash
6189
			// lineDashOffset :
6190
			// lineJoin :
6191
			// lineWidth :
6192
			generateLabels: function(chart) {
6193
				var data = chart.data;
6194
				return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
6195
					return {
6196
						text: dataset.label,
6197
						fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
6198
						hidden: !chart.isDatasetVisible(i),
6199
						lineCap: dataset.borderCapStyle,
6200
						lineDash: dataset.borderDash,
6201
						lineDashOffset: dataset.borderDashOffset,
6202
						lineJoin: dataset.borderJoinStyle,
6203
						lineWidth: dataset.borderWidth,
6204
						strokeStyle: dataset.borderColor,
6205
						pointStyle: dataset.pointStyle,
6206
6207
						// Below is extra data used for toggling the datasets
6208
						datasetIndex: i
6209
					};
6210
				}, this) : [];
6211
			}
6212
		}
6213
	};
6214
6215
	Chart.Legend = Chart.Element.extend({
6216
6217
		initialize: function(config) {
6218
			helpers.extend(this, config);
6219
6220
			// Contains hit boxes for each dataset (in dataset order)
6221
			this.legendHitBoxes = [];
6222
6223
			// Are we in doughnut mode which has a different data type
6224
			this.doughnutMode = false;
6225
		},
6226
6227
		// These methods are ordered by lifecyle. Utilities then follow.
6228
		// Any function defined here is inherited by all legend types.
6229
		// Any function can be extended by the legend type
6230
6231
		beforeUpdate: noop,
6232
		update: function(maxWidth, maxHeight, margins) {
6233
			var me = this;
6234
6235
			// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
6236
			me.beforeUpdate();
6237
6238
			// Absorb the master measurements
6239
			me.maxWidth = maxWidth;
6240
			me.maxHeight = maxHeight;
6241
			me.margins = margins;
6242
6243
			// Dimensions
6244
			me.beforeSetDimensions();
6245
			me.setDimensions();
6246
			me.afterSetDimensions();
6247
			// Labels
6248
			me.beforeBuildLabels();
6249
			me.buildLabels();
6250
			me.afterBuildLabels();
6251
6252
			// Fit
6253
			me.beforeFit();
6254
			me.fit();
6255
			me.afterFit();
6256
			//
6257
			me.afterUpdate();
6258
6259
			return me.minSize;
6260
		},
6261
		afterUpdate: noop,
6262
6263
		//
6264
6265
		beforeSetDimensions: noop,
6266
		setDimensions: function() {
6267
			var me = this;
6268
			// Set the unconstrained dimension before label rotation
6269
			if (me.isHorizontal()) {
6270
				// Reset position before calculating rotation
6271
				me.width = me.maxWidth;
6272
				me.left = 0;
6273
				me.right = me.width;
6274
			} else {
6275
				me.height = me.maxHeight;
6276
6277
				// Reset position before calculating rotation
6278
				me.top = 0;
6279
				me.bottom = me.height;
6280
			}
6281
6282
			// Reset padding
6283
			me.paddingLeft = 0;
6284
			me.paddingTop = 0;
6285
			me.paddingRight = 0;
6286
			me.paddingBottom = 0;
6287
6288
			// Reset minSize
6289
			me.minSize = {
6290
				width: 0,
6291
				height: 0
6292
			};
6293
		},
6294
		afterSetDimensions: noop,
6295
6296
		//
6297
6298
		beforeBuildLabels: noop,
6299
		buildLabels: function() {
6300
			var me = this;
6301
			me.legendItems = me.options.labels.generateLabels.call(me, me.chart);
6302
			if(me.options.reverse){
6303
				me.legendItems.reverse();
6304
			}
6305
		},
6306
		afterBuildLabels: noop,
6307
6308
		//
6309
6310
		beforeFit: noop,
6311
		fit: function() {
6312
			var me = this;
6313
			var opts = me.options;
6314
			var labelOpts = opts.labels;
6315
			var display = opts.display;
6316
6317
			var ctx = me.ctx;
6318
6319
			var globalDefault = Chart.defaults.global,
6320
				itemOrDefault = helpers.getValueOrDefault,
6321
				fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
6322
				fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
6323
				fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
6324
				labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
6325
6326
			// Reset hit boxes
6327
			var hitboxes = me.legendHitBoxes = [];
6328
6329
			var minSize = me.minSize;
6330
			var isHorizontal = me.isHorizontal();
6331
6332
			if (isHorizontal) {
6333
				minSize.width = me.maxWidth; // fill all the width
6334
				minSize.height = display ? 10 : 0;
6335
			} else {
6336
				minSize.width = display ? 10 : 0;
6337
				minSize.height = me.maxHeight; // fill all the height
6338
			}
6339
6340
			// Increase sizes here
6341
			if (display) {
6342
				ctx.font = labelFont;
6343
6344
				if (isHorizontal) {
6345
					// Labels
6346
6347
					// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
6348
					var lineWidths = me.lineWidths = [0];
6349
					var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
6350
6351
					ctx.textAlign = "left";
6352
					ctx.textBaseline = 'top';
6353
6354
					helpers.each(me.legendItems, function(legendItem, i) {
6355
						var boxWidth = labelOpts.usePointStyle ?
6356
							fontSize * Math.sqrt(2) :
6357
							labelOpts.boxWidth;
6358
6359
						var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
6360
						if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
6361
							totalHeight += fontSize + (labelOpts.padding);
6362
							lineWidths[lineWidths.length] = me.left;
6363
						}
6364
6365
						// Store the hitbox width and height here. Final position will be updated in `draw`
6366
						hitboxes[i] = {
6367
							left: 0,
6368
							top: 0,
6369
							width: width,
6370
							height: fontSize
6371
						};
6372
6373
						lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
6374
					});
6375
6376
					minSize.height += totalHeight;
6377
6378
				} else {
6379
					var vPadding = labelOpts.padding;
6380
					var columnWidths = me.columnWidths = [];
6381
					var totalWidth = labelOpts.padding;
6382
					var currentColWidth = 0;
6383
					var currentColHeight = 0;
6384
					var itemHeight = fontSize + vPadding;
6385
6386
					helpers.each(me.legendItems, function(legendItem, i) {
6387
						// If usePointStyle is set, multiple boxWidth by 2 since it represents
6388
						// the radius and not truly the width
6389
						var boxWidth = labelOpts.usePointStyle ? 2 * labelOpts.boxWidth : labelOpts.boxWidth;
6390
6391
						var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
6392
6393
						// If too tall, go to new column
6394
						if (currentColHeight + itemHeight > minSize.height) {
6395
							totalWidth += currentColWidth + labelOpts.padding;
6396
							columnWidths.push(currentColWidth); // previous column width
6397
6398
							currentColWidth = 0;
6399
							currentColHeight = 0;
6400
						}
6401
6402
						// Get max width
6403
						currentColWidth = Math.max(currentColWidth, itemWidth);
6404
						currentColHeight += itemHeight;
6405
6406
						// Store the hitbox width and height here. Final position will be updated in `draw`
6407
						hitboxes[i] = {
6408
							left: 0,
6409
							top: 0,
6410
							width: itemWidth,
6411
							height: fontSize
6412
						};
6413
					});
6414
6415
					totalWidth += currentColWidth;
6416
					columnWidths.push(currentColWidth);
6417
					minSize.width += totalWidth;
6418
				}
6419
			}
6420
6421
			me.width = minSize.width;
6422
			me.height = minSize.height;
6423
		},
6424
		afterFit: noop,
6425
6426
		// Shared Methods
6427
		isHorizontal: function() {
6428
			return this.options.position === "top" || this.options.position === "bottom";
6429
		},
6430
6431
		// Actualy draw the legend on the canvas
6432
		draw: function() {
6433
			var me = this;
6434
			var opts = me.options;
6435
			var labelOpts = opts.labels;
6436
			var globalDefault = Chart.defaults.global,
6437
				lineDefault = globalDefault.elements.line,
6438
				legendWidth = me.width,
6439
				lineWidths = me.lineWidths;
6440
6441
			if (opts.display) {
6442
				var ctx = me.ctx,
6443
					cursor,
6444
					itemOrDefault = helpers.getValueOrDefault,
6445
					fontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor),
6446
					fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
6447
					fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
6448
					fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
6449
					labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
6450
6451
				// Canvas setup
6452
				ctx.textAlign = "left";
6453
				ctx.textBaseline = 'top';
6454
				ctx.lineWidth = 0.5;
6455
				ctx.strokeStyle = fontColor; // for strikethrough effect
6456
				ctx.fillStyle = fontColor; // render in correct colour
6457
				ctx.font = labelFont;
6458
6459
				var boxWidth = labelOpts.boxWidth,
6460
					hitboxes = me.legendHitBoxes;
6461
6462
				// current position
6463
				var drawLegendBox = function(x, y, legendItem) {
6464
					if (isNaN(boxWidth) || boxWidth <= 0) {
6465
						return;
6466
					}
6467
6468
					// Set the ctx for the box
6469
					ctx.save();
6470
6471
					ctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
6472
					ctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
6473
					ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
6474
					ctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
6475
					ctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
6476
					ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
6477
6478
					if (ctx.setLineDash) {
6479
						// IE 9 and 10 do not support line dash
6480
						ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash));
6481
					}
6482
6483
					if (opts.labels && opts.labels.usePointStyle) {
6484
						// Recalulate x and y for drawPoint() because its expecting
6485
						// x and y to be center of figure (instead of top left)
6486
						var radius = fontSize * Math.SQRT2 / 2;
6487
						var offSet = radius / Math.SQRT2;
6488
						var centerX = x + offSet;
6489
						var centerY = y + offSet;
6490
6491
						// Draw pointStyle as legend symbol
6492
						Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
6493
					}
6494
					else {
6495
						// Draw box as legend symbol
6496
						ctx.strokeRect(x, y, boxWidth, fontSize);
6497
						ctx.fillRect(x, y, boxWidth, fontSize);
6498
					}
6499
6500
					ctx.restore();
6501
				};
6502
				var fillText = function(x, y, legendItem, textWidth) {
6503
					ctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y);
6504
6505
					if (legendItem.hidden) {
6506
						// Strikethrough the text if hidden
6507
						ctx.beginPath();
6508
						ctx.lineWidth = 2;
6509
						ctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2));
6510
						ctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2));
6511
						ctx.stroke();
6512
					}
6513
				};
6514
6515
				// Horizontal
6516
				var isHorizontal = me.isHorizontal();
6517
				if (isHorizontal) {
6518
					cursor = {
6519
						x: me.left + ((legendWidth - lineWidths[0]) / 2),
6520
						y: me.top + labelOpts.padding,
6521
						line: 0
6522
					};
6523
				} else {
6524
					cursor = {
6525
						x: me.left + labelOpts.padding,
6526
						y: me.top + labelOpts.padding,
6527
						line: 0
6528
					};
6529
				}
6530
6531
				var itemHeight = fontSize + labelOpts.padding;
6532
				helpers.each(me.legendItems, function(legendItem, i) {
6533
					var textWidth = ctx.measureText(legendItem.text).width,
6534
						width = labelOpts.usePointStyle ?
6535
							fontSize + (fontSize / 2) + textWidth :
6536
							boxWidth + (fontSize / 2) + textWidth,
6537
						x = cursor.x,
6538
						y = cursor.y;
6539
6540
					if (isHorizontal) {
6541
						if (x + width >= legendWidth) {
6542
							y = cursor.y += itemHeight;
6543
							cursor.line++;
6544
							x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
6545
						}
6546
					} else {
6547
						if (y + itemHeight > me.bottom) {
6548
							x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
6549
							y = cursor.y = me.top;
6550
							cursor.line++;
6551
						}
6552
					}
6553
6554
					drawLegendBox(x, y, legendItem);
6555
6556
					hitboxes[i].left = x;
6557
					hitboxes[i].top = y;
6558
6559
					// Fill the actual label
6560
					fillText(x, y, legendItem, textWidth);
6561
6562
					if (isHorizontal) {
6563
						cursor.x += width + (labelOpts.padding);
6564
					} else {
6565
						cursor.y += itemHeight;
6566
					}
6567
6568
				});
6569
			}
6570
		},
6571
6572
		// Handle an event
6573
		handleEvent: function(e) {
6574
			var me = this;
6575
			var position = helpers.getRelativePosition(e, me.chart.chart),
6576
				x = position.x,
6577
				y = position.y,
6578
				opts = me.options;
6579
6580
			if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
6581
				// See if we are touching one of the dataset boxes
6582
				var lh = me.legendHitBoxes;
6583
				for (var i = 0; i < lh.length; ++i) {
6584
					var hitBox = lh[i];
6585
6586
					if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
6587
						// Touching an element
6588
						if (opts.onClick) {
6589
							opts.onClick.call(me, e, me.legendItems[i]);
6590
						}
6591
						break;
6592
					}
6593
				}
6594
			}
6595
		}
6596
	});
6597
6598
	// Register the legend plugin
6599
	Chart.plugins.register({
6600
		beforeInit: function(chartInstance) {
6601
			var opts = chartInstance.options;
6602
			var legendOpts = opts.legend;
6603
6604
			if (legendOpts) {
6605
				chartInstance.legend = new Chart.Legend({
6606
					ctx: chartInstance.chart.ctx,
6607
					options: legendOpts,
6608
					chart: chartInstance
6609
				});
6610
6611
				Chart.layoutService.addBox(chartInstance, chartInstance.legend);
6612
			}
6613
		}
6614
	});
6615
};
6616
6617
},{}],30:[function(require,module,exports){
6618
"use strict";
6619
6620
module.exports = function(Chart) {
6621
6622
	var noop = Chart.helpers.noop;
6623
6624
	/**
6625
	 * The plugin service singleton
6626
	 * @namespace Chart.plugins
6627
	 * @since 2.1.0
6628
	 */
6629
	Chart.plugins = {
6630
		_plugins: [],
6631
6632
		/**
6633
		 * Registers the given plugin(s) if not already registered.
6634
		 * @param {Array|Object} plugins plugin instance(s).
6635
		 */
6636
		register: function(plugins) {
6637
			var p = this._plugins;
6638
			([]).concat(plugins).forEach(function(plugin) {
6639
				if (p.indexOf(plugin) === -1) {
6640
					p.push(plugin);
6641
				}
6642
			});
6643
		},
6644
6645
		/**
6646
		 * Unregisters the given plugin(s) only if registered.
6647
		 * @param {Array|Object} plugins plugin instance(s).
6648
		 */
6649
		unregister: function(plugins) {
6650
			var p = this._plugins;
6651
			([]).concat(plugins).forEach(function(plugin) {
6652
				var idx = p.indexOf(plugin);
6653
				if (idx !== -1) {
6654
					p.splice(idx, 1);
6655
				}
6656
			});
6657
		},
6658
6659
		/**
6660
		 * Remove all registered p^lugins.
6661
		 * @since 2.1.5
6662
		 */
6663
		clear: function() {
6664
			this._plugins = [];
6665
		},
6666
6667
		/**
6668
		 * Returns the number of registered plugins?
6669
		 * @returns {Number}
6670
		 * @since 2.1.5
6671
		 */
6672
		count: function() {
6673
			return this._plugins.length;
6674
		},
6675
6676
		/**
6677
		 * Returns all registered plugin intances.
6678
		 * @returns {Array} array of plugin objects.
6679
		 * @since 2.1.5
6680
		 */
6681
		getAll: function() {
6682
			return this._plugins;
6683
		},
6684
6685
		/**
6686
		 * Calls registered plugins on the specified extension, with the given args. This
6687
		 * method immediately returns as soon as a plugin explicitly returns false. The
6688
		 * returned value can be used, for instance, to interrupt the current action.
6689
		 * @param {String} extension the name of the plugin method to call (e.g. 'beforeUpdate').
6690
		 * @param {Array} [args] extra arguments to apply to the extension call.
0 ignored issues
show
Documentation introduced by
The parameter [args] does not exist. Did you maybe forget to remove this comment?
Loading history...
6691
		 * @returns {Boolean} false if any of the plugins return false, else returns true.
6692
		 */
6693
		notify: function(extension, args) {
6694
			var plugins = this._plugins;
6695
			var ilen = plugins.length;
6696
			var i, plugin;
6697
6698
			for (i=0; i<ilen; ++i) {
6699
				plugin = plugins[i];
6700
				if (typeof plugin[extension] === 'function') {
6701
					if (plugin[extension].apply(plugin, args || []) === false) {
6702
						return false;
6703
					}
6704
				}
6705
			}
6706
6707
			return true;
6708
		}
6709
	};
6710
6711
	/**
6712
	 * Plugin extension methods.
6713
	 * @interface Chart.PluginBase
6714
	 * @since 2.1.0
6715
	 */
6716
	Chart.PluginBase = Chart.Element.extend({
6717
		// Called at start of chart init
6718
		beforeInit: noop,
6719
6720
		// Called at end of chart init
6721
		afterInit: noop,
6722
6723
		// Called at start of update
6724
		beforeUpdate: noop,
6725
6726
		// Called at end of update
6727
		afterUpdate: noop,
6728
6729
		// Called at start of draw
6730
		beforeDraw: noop,
6731
6732
		// Called at end of draw
6733
		afterDraw: noop,
6734
6735
		// Called during destroy
6736
		destroy: noop
6737
	});
6738
6739
	/**
6740
	 * Provided for backward compatibility, use Chart.plugins instead
6741
	 * @namespace Chart.pluginService
6742
	 * @deprecated since version 2.1.5
6743
	 * @todo remove me at version 3
6744
	 */
6745
	Chart.pluginService = Chart.plugins;
6746
};
6747
6748
},{}],31:[function(require,module,exports){
6749
"use strict";
6750
6751
module.exports = function(Chart) {
6752
6753
	var helpers = Chart.helpers;
6754
6755
	Chart.defaults.scale = {
6756
		display: true,
6757
		position: "left",
6758
6759
		// grid line settings
6760
		gridLines: {
6761
			display: true,
6762
			color: "rgba(0, 0, 0, 0.1)",
6763
			lineWidth: 1,
6764
			drawBorder: true,
6765
			drawOnChartArea: true,
6766
			drawTicks: true,
6767
			tickMarkLength: 10,
6768
			zeroLineWidth: 1,
6769
			zeroLineColor: "rgba(0,0,0,0.25)",
6770
			offsetGridLines: false
6771
		},
6772
6773
		// scale label
6774
		scaleLabel: {
6775
			// actual label
6776
			labelString: '',
6777
6778
			// display property
6779
			display: false
6780
		},
6781
6782
		// label settings
6783
		ticks: {
6784
			beginAtZero: false,
6785
			minRotation: 0,
6786
			maxRotation: 50,
6787
			mirror: false,
6788
			padding: 10,
6789
			reverse: false,
6790
			display: true,
6791
			autoSkip: true,
6792
			autoSkipPadding: 0,
6793
			labelOffset: 0,
6794
			// We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
6795
			callback: function(value) {
6796
				return helpers.isArray(value) ? value : '' + value;
6797
			}
6798
		}
6799
	};
6800
6801
	Chart.Scale = Chart.Element.extend({
6802
6803
		// These methods are ordered by lifecyle. Utilities then follow.
6804
		// Any function defined here is inherited by all scale types.
6805
		// Any function can be extended by the scale type
6806
6807
		beforeUpdate: function() {
6808
			helpers.callCallback(this.options.beforeUpdate, [this]);
6809
		},
6810
		update: function(maxWidth, maxHeight, margins) {
6811
			var me = this;
6812
6813
			// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
6814
			me.beforeUpdate();
6815
6816
			// Absorb the master measurements
6817
			me.maxWidth = maxWidth;
6818
			me.maxHeight = maxHeight;
6819
			me.margins = helpers.extend({
6820
				left: 0,
6821
				right: 0,
6822
				top: 0,
6823
				bottom: 0
6824
			}, margins);
6825
6826
			// Dimensions
6827
			me.beforeSetDimensions();
6828
			me.setDimensions();
6829
			me.afterSetDimensions();
6830
6831
			// Data min/max
6832
			me.beforeDataLimits();
6833
			me.determineDataLimits();
6834
			me.afterDataLimits();
6835
6836
			// Ticks
6837
			me.beforeBuildTicks();
6838
			me.buildTicks();
6839
			me.afterBuildTicks();
6840
6841
			me.beforeTickToLabelConversion();
6842
			me.convertTicksToLabels();
6843
			me.afterTickToLabelConversion();
6844
6845
			// Tick Rotation
6846
			me.beforeCalculateTickRotation();
6847
			me.calculateTickRotation();
6848
			me.afterCalculateTickRotation();
6849
			// Fit
6850
			me.beforeFit();
6851
			me.fit();
6852
			me.afterFit();
6853
			//
6854
			me.afterUpdate();
6855
6856
			return me.minSize;
6857
6858
		},
6859
		afterUpdate: function() {
6860
			helpers.callCallback(this.options.afterUpdate, [this]);
6861
		},
6862
6863
		//
6864
6865
		beforeSetDimensions: function() {
6866
			helpers.callCallback(this.options.beforeSetDimensions, [this]);
6867
		},
6868
		setDimensions: function() {
6869
			var me = this;
6870
			// Set the unconstrained dimension before label rotation
6871
			if (me.isHorizontal()) {
6872
				// Reset position before calculating rotation
6873
				me.width = me.maxWidth;
6874
				me.left = 0;
6875
				me.right = me.width;
6876
			} else {
6877
				me.height = me.maxHeight;
6878
6879
				// Reset position before calculating rotation
6880
				me.top = 0;
6881
				me.bottom = me.height;
6882
			}
6883
6884
			// Reset padding
6885
			me.paddingLeft = 0;
6886
			me.paddingTop = 0;
6887
			me.paddingRight = 0;
6888
			me.paddingBottom = 0;
6889
		},
6890
		afterSetDimensions: function() {
6891
			helpers.callCallback(this.options.afterSetDimensions, [this]);
6892
		},
6893
6894
		// Data limits
6895
		beforeDataLimits: function() {
6896
			helpers.callCallback(this.options.beforeDataLimits, [this]);
6897
		},
6898
		determineDataLimits: helpers.noop,
6899
		afterDataLimits: function() {
6900
			helpers.callCallback(this.options.afterDataLimits, [this]);
6901
		},
6902
6903
		//
6904
		beforeBuildTicks: function() {
6905
			helpers.callCallback(this.options.beforeBuildTicks, [this]);
6906
		},
6907
		buildTicks: helpers.noop,
6908
		afterBuildTicks: function() {
6909
			helpers.callCallback(this.options.afterBuildTicks, [this]);
6910
		},
6911
6912
		beforeTickToLabelConversion: function() {
6913
			helpers.callCallback(this.options.beforeTickToLabelConversion, [this]);
6914
		},
6915
		convertTicksToLabels: function() {
6916
			var me = this;
6917
			// Convert ticks to strings
6918
			me.ticks = me.ticks.map(function(numericalTick, index, ticks) {
6919
					if (me.options.ticks.userCallback) {
6920
						return me.options.ticks.userCallback(numericalTick, index, ticks);
6921
					}
6922
					return me.options.ticks.callback(numericalTick, index, ticks);
6923
				},
6924
				me);
6925
		},
6926
		afterTickToLabelConversion: function() {
6927
			helpers.callCallback(this.options.afterTickToLabelConversion, [this]);
6928
		},
6929
6930
		//
6931
6932
		beforeCalculateTickRotation: function() {
6933
			helpers.callCallback(this.options.beforeCalculateTickRotation, [this]);
6934
		},
6935
		calculateTickRotation: function() {
6936
			var me = this;
6937
			var context = me.ctx;
6938
			var globalDefaults = Chart.defaults.global;
6939
			var optionTicks = me.options.ticks;
6940
6941
			//Get the width of each grid by calculating the difference
6942
			//between x offsets between 0 and 1.
6943
			var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize);
6944
			var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle);
6945
			var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily);
6946
			var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
6947
			context.font = tickLabelFont;
6948
6949
			var firstWidth = context.measureText(me.ticks[0]).width;
6950
			var lastWidth = context.measureText(me.ticks[me.ticks.length - 1]).width;
6951
			var firstRotated;
6952
6953
			me.labelRotation = optionTicks.minRotation || 0;
6954
			me.paddingRight = 0;
6955
			me.paddingLeft = 0;
6956
6957
			if (me.options.display) {
6958
				if (me.isHorizontal()) {
6959
					me.paddingRight = lastWidth / 2 + 3;
6960
					me.paddingLeft = firstWidth / 2 + 3;
6961
6962
					if (!me.longestTextCache) {
6963
						me.longestTextCache = {};
6964
					}
6965
					var originalLabelWidth = helpers.longestText(context, tickLabelFont, me.ticks, me.longestTextCache);
6966
					var labelWidth = originalLabelWidth;
6967
					var cosRotation;
6968
					var sinRotation;
6969
6970
					// Allow 3 pixels x2 padding either side for label readability
6971
					// only the index matters for a dataset scale, but we want a consistent interface between scales
6972
					var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
6973
6974
					//Max label rotation can be set or default to 90 - also act as a loop counter
6975
					while (labelWidth > tickWidth && me.labelRotation < optionTicks.maxRotation) {
6976
						cosRotation = Math.cos(helpers.toRadians(me.labelRotation));
6977
						sinRotation = Math.sin(helpers.toRadians(me.labelRotation));
6978
6979
						firstRotated = cosRotation * firstWidth;
6980
6981
						// We're right aligning the text now.
6982
						if (firstRotated + tickFontSize / 2 > me.yLabelWidth) {
6983
							me.paddingLeft = firstRotated + tickFontSize / 2;
6984
						}
6985
6986
						me.paddingRight = tickFontSize / 2;
6987
6988
						if (sinRotation * originalLabelWidth > me.maxHeight) {
6989
							// go back one step
6990
							me.labelRotation--;
6991
							break;
6992
						}
6993
6994
						me.labelRotation++;
6995
						labelWidth = cosRotation * originalLabelWidth;
6996
					}
6997
				}
6998
			}
6999
7000
			if (me.margins) {
7001
				me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7002
				me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7003
			}
7004
		},
7005
		afterCalculateTickRotation: function() {
7006
			helpers.callCallback(this.options.afterCalculateTickRotation, [this]);
7007
		},
7008
7009
		//
7010
7011
		beforeFit: function() {
7012
			helpers.callCallback(this.options.beforeFit, [this]);
7013
		},
7014
		fit: function() {
7015
			var me = this;
7016
			// Reset
7017
			var minSize = me.minSize = {
7018
				width: 0,
7019
				height: 0
7020
			};
7021
7022
			var opts = me.options;
7023
			var globalDefaults = Chart.defaults.global;
7024
			var tickOpts = opts.ticks;
7025
			var scaleLabelOpts = opts.scaleLabel;
7026
			var display = opts.display;
7027
			var isHorizontal = me.isHorizontal();
7028
7029
			var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
7030
			var tickFontStyle = helpers.getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
7031
			var tickFontFamily = helpers.getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
7032
			var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
7033
7034
			var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize);
7035
7036
			var tickMarkLength = opts.gridLines.tickMarkLength;
7037
7038
			// Width
7039
			if (isHorizontal) {
7040
				// subtract the margins to line up with the chartArea if we are a full width scale
7041
				minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
7042
			} else {
7043
				minSize.width = display ? tickMarkLength : 0;
7044
			}
7045
7046
			// height
7047
			if (isHorizontal) {
7048
				minSize.height = display ? tickMarkLength : 0;
7049
			} else {
7050
				minSize.height = me.maxHeight; // fill all the height
7051
			}
7052
7053
			// Are we showing a title for the scale?
7054
			if (scaleLabelOpts.display && display) {
7055
				if (isHorizontal) {
7056
					minSize.height += (scaleLabelFontSize * 1.5);
7057
				} else {
7058
					minSize.width += (scaleLabelFontSize * 1.5);
7059
				}
7060
			}
7061
7062
			if (tickOpts.display && display) {
7063
				// Don't bother fitting the ticks if we are not showing them
7064
				if (!me.longestTextCache) {
7065
					me.longestTextCache = {};
7066
				}
7067
7068
				var largestTextWidth = helpers.longestText(me.ctx, tickLabelFont, me.ticks, me.longestTextCache);
7069
				var tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks);
7070
				var lineSpace = tickFontSize * 0.5;
7071
7072
				if (isHorizontal) {
7073
					// A horizontal axis is more constrained by the height.
7074
					me.longestLabelWidth = largestTextWidth;
7075
7076
					// TODO - improve this calculation
7077
					var labelHeight = (Math.sin(helpers.toRadians(me.labelRotation)) * me.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines);
7078
7079
					minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight);
7080
					me.ctx.font = tickLabelFont;
7081
7082
					var firstLabelWidth = me.ctx.measureText(me.ticks[0]).width;
7083
					var lastLabelWidth = me.ctx.measureText(me.ticks[me.ticks.length - 1]).width;
7084
7085
					// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated
7086
					// by the font height
7087
					var cosRotation = Math.cos(helpers.toRadians(me.labelRotation));
7088
					var sinRotation = Math.sin(helpers.toRadians(me.labelRotation));
7089
					me.paddingLeft = me.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
7090
					me.paddingRight = me.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated
7091
				} else {
7092
					// A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first
7093
					var maxLabelWidth = me.maxWidth - minSize.width;
7094
7095
					// Account for padding
7096
					var mirror = tickOpts.mirror;
7097
					if (!mirror) {
7098
						largestTextWidth += me.options.ticks.padding;
7099
					} else {
7100
						// If mirrored text is on the inside so don't expand
7101
						largestTextWidth = 0;
7102
					}
7103
7104
					if (largestTextWidth < maxLabelWidth) {
7105
						// We don't need all the room
7106
						minSize.width += largestTextWidth;
7107
					} else {
7108
						// Expand to max size
7109
						minSize.width = me.maxWidth;
7110
					}
7111
7112
					me.paddingTop = tickFontSize / 2;
7113
					me.paddingBottom = tickFontSize / 2;
7114
				}
7115
			}
7116
7117
			if (me.margins) {
7118
				me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7119
				me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
7120
				me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7121
				me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
7122
			}
7123
7124
			me.width = minSize.width;
7125
			me.height = minSize.height;
7126
7127
		},
7128
		afterFit: function() {
7129
			helpers.callCallback(this.options.afterFit, [this]);
7130
		},
7131
7132
		// Shared Methods
7133
		isHorizontal: function() {
7134
			return this.options.position === "top" || this.options.position === "bottom";
7135
		},
7136
		isFullWidth: function() {
7137
			return (this.options.fullWidth);
7138
		},
7139
7140
		// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
7141
		getRightValue: function(rawValue) {
7142
			// Null and undefined values first
7143
			if (rawValue === null || typeof(rawValue) === 'undefined') {
7144
				return NaN;
7145
			}
7146
			// isNaN(object) returns true, so make sure NaN is checking for a number
7147
			if (typeof(rawValue) === 'number' && isNaN(rawValue)) {
7148
				return NaN;
7149
			}
7150
			// If it is in fact an object, dive in one more level
7151
			if (typeof(rawValue) === "object") {
7152
				if ((rawValue instanceof Date) || (rawValue.isValid)) {
7153
					return rawValue;
7154
				} else {
7155
					return this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y);
7156
				}
7157
			}
7158
7159
			// Value is good, return it
7160
			return rawValue;
7161
		},
7162
7163
		// Used to get the value to display in the tooltip for the data at the given index
7164
		// function getLabelForIndex(index, datasetIndex)
7165
		getLabelForIndex: helpers.noop,
7166
7167
		// Used to get data value locations.  Value can either be an index or a numerical value
7168
		getPixelForValue: helpers.noop,
7169
7170
		// Used to get the data value from a given pixel. This is the inverse of getPixelForValue
7171
		getValueForPixel: helpers.noop,
7172
7173
		// Used for tick location, should
7174
		getPixelForTick: function(index, includeOffset) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
7175
			var me = this;
7176
			if (me.isHorizontal()) {
7177
				var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7178
				var tickWidth = innerWidth / Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
7179
				var pixel = (tickWidth * index) + me.paddingLeft;
7180
7181
				if (includeOffset) {
7182
					pixel += tickWidth / 2;
7183
				}
7184
7185
				var finalVal = me.left + Math.round(pixel);
7186
				finalVal += me.isFullWidth() ? me.margins.left : 0;
7187
				return finalVal;
7188
			} else {
7189
				var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
7190
				return me.top + (index * (innerHeight / (me.ticks.length - 1)));
7191
			}
7192
		},
7193
7194
		// Utility for getting the pixel location of a percentage of scale
7195
		getPixelForDecimal: function(decimal /*, includeOffset*/ ) {
7196
			var me = this;
7197
			if (me.isHorizontal()) {
7198
				var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7199
				var valueOffset = (innerWidth * decimal) + me.paddingLeft;
7200
7201
				var finalVal = me.left + Math.round(valueOffset);
7202
				finalVal += me.isFullWidth() ? me.margins.left : 0;
7203
				return finalVal;
7204
			} else {
7205
				return me.top + (decimal * me.height);
7206
			}
7207
		},
7208
7209
		getBasePixel: function() {
7210
			var me = this;
7211
			var min = me.min;
7212
			var max = me.max;
7213
7214
			return me.getPixelForValue(
7215
				me.beginAtZero? 0:
7216
				min < 0 && max < 0? max :
7217
				min > 0 && max > 0? min :
7218
				0);
7219
		},
7220
7221
		// Actualy draw the scale on the canvas
7222
		// @param {rectangle} chartArea : the area of the chart to draw full grid lines on
7223
		draw: function(chartArea) {
7224
			var me = this;
7225
			var options = me.options;
7226
			if (!options.display) {
7227
				return;
7228
			}
7229
7230
			var context = me.ctx;
7231
			var globalDefaults = Chart.defaults.global;
7232
			var optionTicks = options.ticks;
7233
			var gridLines = options.gridLines;
7234
			var scaleLabel = options.scaleLabel;
7235
7236
			var isRotated = me.labelRotation !== 0;
7237
			var skipRatio;
7238
			var useAutoskipper = optionTicks.autoSkip;
7239
			var isHorizontal = me.isHorizontal();
7240
7241
			// figure out the maximum number of gridlines to show
7242
			var maxTicks;
7243
			if (optionTicks.maxTicksLimit) {
7244
				maxTicks = optionTicks.maxTicksLimit;
7245
			}
7246
7247
			var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
7248
			var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize);
7249
			var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle);
7250
			var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily);
7251
			var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
7252
			var tl = gridLines.tickMarkLength;
7253
7254
			var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
7255
			var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabel.fontSize, globalDefaults.defaultFontSize);
7256
			var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabel.fontStyle, globalDefaults.defaultFontStyle);
7257
			var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabel.fontFamily, globalDefaults.defaultFontFamily);
7258
			var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily);
7259
7260
			var labelRotationRadians = helpers.toRadians(me.labelRotation);
7261
			var cosRotation = Math.cos(labelRotationRadians);
7262
			var longestRotatedLabel = me.longestLabelWidth * cosRotation;
7263
7264
			// Make sure we draw text in the correct color and font
7265
			context.fillStyle = tickFontColor;
7266
7267
			var itemsToDraw = [];
7268
7269
			if (isHorizontal) {
7270
				skipRatio = false;
7271
7272
				// Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation
7273
				// See #2584
7274
				if (isRotated) {
7275
					longestRotatedLabel /= 2;
7276
				}
7277
7278
				if ((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length > (me.width - (me.paddingLeft + me.paddingRight))) {
7279
					skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length) / (me.width - (me.paddingLeft + me.paddingRight)));
7280
				}
7281
7282
				// if they defined a max number of optionTicks,
7283
				// increase skipRatio until that number is met
7284
				if (maxTicks && me.ticks.length > maxTicks) {
7285
					while (!skipRatio || me.ticks.length / (skipRatio || 1) > maxTicks) {
7286
						if (!skipRatio) {
7287
							skipRatio = 1;
7288
						}
7289
						skipRatio += 1;
7290
					}
7291
				}
7292
7293
				if (!useAutoskipper) {
7294
					skipRatio = false;
7295
				}
7296
			}
7297
7298
7299
			var xTickStart = options.position === "right" ? me.left : me.right - tl;
7300
			var xTickEnd = options.position === "right" ? me.left + tl : me.right;
7301
			var yTickStart = options.position === "bottom" ? me.top : me.bottom - tl;
7302
			var yTickEnd = options.position === "bottom" ? me.top + tl : me.bottom;
7303
7304
			helpers.each(me.ticks, function(label, index) {
7305
				// If the callback returned a null or undefined value, do not draw this line
7306
				if (label === undefined || label === null) {
7307
					return;
7308
				}
7309
7310
				var isLastTick = me.ticks.length === index + 1;
7311
7312
				// Since we always show the last tick,we need may need to hide the last shown one before
7313
				var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= me.ticks.length);
0 ignored issues
show
Bug introduced by
The variable skipRatio does not seem to be initialized in case isHorizontal on line 7269 is false. Are you sure this can never be the case?
Loading history...
7314
				if (shouldSkip && !isLastTick || (label === undefined || label === null)) {
7315
					return;
7316
				}
7317
7318
				var lineWidth, lineColor;
7319
				if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) {
7320
					// Draw the first index specially
7321
					lineWidth = gridLines.zeroLineWidth;
7322
					lineColor = gridLines.zeroLineColor;
7323
				} else  {
7324
					lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, index);
7325
					lineColor = helpers.getValueAtIndexOrDefault(gridLines.color, index);
7326
				}
7327
7328
				// Common properties
7329
				var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
7330
				var textAlign, textBaseline = 'middle';
7331
7332
				if (isHorizontal) {
7333
					if (!isRotated) {
7334
						textBaseline = options.position === 'top' ? 'bottom' : 'top';
7335
					}
7336
7337
					textAlign = isRotated ? 'right' : 'center';
7338
7339
					var xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines
7340
					labelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
7341
					labelY = (isRotated) ? me.top + 12 : options.position === 'top' ? me.bottom - tl : me.top + tl;
7342
7343
					tx1 = tx2 = x1 = x2 = xLineValue;
7344
					ty1 = yTickStart;
7345
					ty2 = yTickEnd;
7346
					y1 = chartArea.top;
7347
					y2 = chartArea.bottom;
7348
				} else {
7349
					if (options.position === 'left') {
7350
						if (optionTicks.mirror) {
7351
							labelX = me.right + optionTicks.padding;
7352
							textAlign = 'left';
7353
						} else {
7354
							labelX = me.right - optionTicks.padding;
7355
							textAlign = 'right';
7356
						}
7357
					} else {
7358
						// right side
7359
						if (optionTicks.mirror) {
7360
							labelX = me.left - optionTicks.padding;
7361
							textAlign = 'right';
7362
						} else {
7363
							labelX = me.left + optionTicks.padding;
7364
							textAlign = 'left';
7365
						}
7366
					}
7367
7368
					var yLineValue = me.getPixelForTick(index); // xvalues for grid lines
7369
					yLineValue += helpers.aliasPixel(lineWidth);
7370
					labelY = me.getPixelForTick(index, gridLines.offsetGridLines);
7371
7372
					tx1 = xTickStart;
7373
					tx2 = xTickEnd;
7374
					x1 = chartArea.left;
7375
					x2 = chartArea.right;
7376
					ty1 = ty2 = y1 = y2 = yLineValue;
7377
				}
7378
7379
				itemsToDraw.push({
7380
					tx1: tx1,
7381
					ty1: ty1,
7382
					tx2: tx2,
7383
					ty2: ty2,
7384
					x1: x1,
7385
					y1: y1,
7386
					x2: x2,
7387
					y2: y2,
7388
					labelX: labelX,
7389
					labelY: labelY,
7390
					glWidth: lineWidth,
7391
					glColor: lineColor,
7392
					rotation: -1 * labelRotationRadians,
7393
					label: label,
7394
					textBaseline: textBaseline,
7395
					textAlign: textAlign
7396
				});
7397
			});
7398
7399
			// Draw all of the tick labels, tick marks, and grid lines at the correct places
7400
			helpers.each(itemsToDraw, function(itemToDraw) {
7401
				if (gridLines.display) {
7402
					context.lineWidth = itemToDraw.glWidth;
7403
					context.strokeStyle = itemToDraw.glColor;
7404
7405
					context.beginPath();
7406
7407
					if (gridLines.drawTicks) {
7408
						context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
7409
						context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
7410
					}
7411
7412
					if (gridLines.drawOnChartArea) {
7413
						context.moveTo(itemToDraw.x1, itemToDraw.y1);
7414
						context.lineTo(itemToDraw.x2, itemToDraw.y2);
7415
					}
7416
7417
					context.stroke();
7418
				}
7419
7420
				if (optionTicks.display) {
7421
					context.save();
7422
					context.translate(itemToDraw.labelX, itemToDraw.labelY);
7423
					context.rotate(itemToDraw.rotation);
7424
					context.font = tickLabelFont;
7425
					context.textBaseline = itemToDraw.textBaseline;
7426
					context.textAlign = itemToDraw.textAlign;
7427
7428
					var label = itemToDraw.label;
7429
					if (helpers.isArray(label)) {
7430
						for (var i = 0, y = 0; i < label.length; ++i) {
0 ignored issues
show
Unused Code introduced by
The loop variable y is initialized by the loop but not used in the test. Consider using another type of loop if this is the intended behavior.
Loading history...
7431
							// We just make sure the multiline element is a string here..
7432
							context.fillText('' + label[i], 0, y);
7433
							// apply same lineSpacing as calculated @ L#320
7434
							y += (tickFontSize * 1.5);
7435
						}
7436
					} else {
7437
						context.fillText(label, 0, 0);
7438
					}
7439
					context.restore();
7440
				}
7441
			});
7442
7443
			if (scaleLabel.display) {
7444
				// Draw the scale label
7445
				var scaleLabelX;
7446
				var scaleLabelY;
7447
				var rotation = 0;
7448
7449
				if (isHorizontal) {
7450
					scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
7451
					scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFontSize / 2) : me.top + (scaleLabelFontSize / 2);
7452
				} else {
7453
					var isLeft = options.position === 'left';
7454
					scaleLabelX = isLeft ? me.left + (scaleLabelFontSize / 2) : me.right - (scaleLabelFontSize / 2);
7455
					scaleLabelY = me.top + ((me.bottom - me.top) / 2);
7456
					rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
7457
				}
7458
7459
				context.save();
7460
				context.translate(scaleLabelX, scaleLabelY);
7461
				context.rotate(rotation);
7462
				context.textAlign = 'center';
7463
				context.textBaseline = 'middle';
7464
				context.fillStyle = scaleLabelFontColor; // render in correct colour
7465
				context.font = scaleLabelFont;
7466
				context.fillText(scaleLabel.labelString, 0, 0);
7467
				context.restore();
7468
			}
7469
7470
			if (gridLines.drawBorder) {
7471
				// Draw the line at the edge of the axis
7472
				context.lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, 0);
7473
				context.strokeStyle = helpers.getValueAtIndexOrDefault(gridLines.color, 0);
7474
				var x1 = me.left,
7475
					x2 = me.right,
7476
					y1 = me.top,
7477
					y2 = me.bottom;
7478
7479
				var aliasPixel = helpers.aliasPixel(context.lineWidth);
7480
				if (isHorizontal) {
7481
					y1 = y2 = options.position === 'top' ? me.bottom : me.top;
7482
					y1 += aliasPixel;
7483
					y2 += aliasPixel;
7484
				} else {
7485
					x1 = x2 = options.position === 'left' ? me.right : me.left;
7486
					x1 += aliasPixel;
7487
					x2 += aliasPixel;
7488
				}
7489
7490
				context.beginPath();
7491
				context.moveTo(x1, y1);
7492
				context.lineTo(x2, y2);
7493
				context.stroke();
7494
			}
7495
		}
7496
	});
7497
};
7498
7499
},{}],32:[function(require,module,exports){
7500
"use strict";
7501
7502
module.exports = function(Chart) {
7503
7504
	var helpers = Chart.helpers;
7505
7506
	Chart.scaleService = {
7507
		// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
7508
		// use the new chart options to grab the correct scale
7509
		constructors: {},
7510
		// Use a registration function so that we can move to an ES6 map when we no longer need to support
7511
		// old browsers
7512
7513
		// Scale config defaults
7514
		defaults: {},
7515
		registerScaleType: function(type, scaleConstructor, defaults) {
7516
			this.constructors[type] = scaleConstructor;
7517
			this.defaults[type] = helpers.clone(defaults);
7518
		},
7519
		getScaleConstructor: function(type) {
7520
			return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
7521
		},
7522
		getScaleDefaults: function(type) {
7523
			// Return the scale defaults merged with the global settings so that we always use the latest ones
7524
			return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
7525
		},
7526
		updateScaleDefaults: function(type, additions) {
7527
			var defaults = this.defaults;
7528
			if (defaults.hasOwnProperty(type)) {
7529
				defaults[type] = helpers.extend(defaults[type], additions);
7530
			}
7531
		},
7532
		addScalesToLayout: function(chartInstance) {
7533
			// Adds each scale to the chart.boxes array to be sized accordingly
7534
			helpers.each(chartInstance.scales, function(scale) {
7535
				Chart.layoutService.addBox(chartInstance, scale);
7536
			});
7537
		}
7538
	};
7539
};
7540
},{}],33:[function(require,module,exports){
7541
"use strict";
7542
7543
module.exports = function(Chart) {
7544
7545
	var helpers = Chart.helpers;
7546
7547
	Chart.defaults.global.title = {
7548
		display: false,
7549
		position: 'top',
7550
		fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
7551
7552
		fontStyle: 'bold',
7553
		padding: 10,
7554
7555
		// actual title
7556
		text: ''
7557
	};
7558
7559
	var noop = helpers.noop;
7560
	Chart.Title = Chart.Element.extend({
7561
7562
		initialize: function(config) {
7563
			var me = this;
7564
			helpers.extend(me, config);
7565
			me.options = helpers.configMerge(Chart.defaults.global.title, config.options);
7566
7567
			// Contains hit boxes for each dataset (in dataset order)
7568
			me.legendHitBoxes = [];
7569
		},
7570
7571
		// These methods are ordered by lifecyle. Utilities then follow.
7572
7573
		beforeUpdate: function () {
7574
			var chartOpts = this.chart.options;
7575
			if (chartOpts && chartOpts.title) {
7576
				this.options = helpers.configMerge(Chart.defaults.global.title, chartOpts.title);
7577
			}
7578
		},
7579
		update: function(maxWidth, maxHeight, margins) {
7580
			var me = this;
7581
7582
			// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
7583
			me.beforeUpdate();
7584
7585
			// Absorb the master measurements
7586
			me.maxWidth = maxWidth;
7587
			me.maxHeight = maxHeight;
7588
			me.margins = margins;
7589
7590
			// Dimensions
7591
			me.beforeSetDimensions();
7592
			me.setDimensions();
7593
			me.afterSetDimensions();
7594
			// Labels
7595
			me.beforeBuildLabels();
7596
			me.buildLabels();
7597
			me.afterBuildLabels();
7598
7599
			// Fit
7600
			me.beforeFit();
7601
			me.fit();
7602
			me.afterFit();
7603
			//
7604
			me.afterUpdate();
7605
7606
			return me.minSize;
7607
7608
		},
7609
		afterUpdate: noop,
7610
7611
		//
7612
7613
		beforeSetDimensions: noop,
7614
		setDimensions: function() {
7615
			var me = this;
7616
			// Set the unconstrained dimension before label rotation
7617
			if (me.isHorizontal()) {
7618
				// Reset position before calculating rotation
7619
				me.width = me.maxWidth;
7620
				me.left = 0;
7621
				me.right = me.width;
7622
			} else {
7623
				me.height = me.maxHeight;
7624
7625
				// Reset position before calculating rotation
7626
				me.top = 0;
7627
				me.bottom = me.height;
7628
			}
7629
7630
			// Reset padding
7631
			me.paddingLeft = 0;
7632
			me.paddingTop = 0;
7633
			me.paddingRight = 0;
7634
			me.paddingBottom = 0;
7635
7636
			// Reset minSize
7637
			me.minSize = {
7638
				width: 0,
7639
				height: 0
7640
			};
7641
		},
7642
		afterSetDimensions: noop,
7643
7644
		//
7645
7646
		beforeBuildLabels: noop,
7647
		buildLabels: noop,
7648
		afterBuildLabels: noop,
7649
7650
		//
7651
7652
		beforeFit: noop,
7653
		fit: function() {
7654
			var me = this,
7655
				valueOrDefault = helpers.getValueOrDefault,
7656
				opts = me.options,
7657
				globalDefaults = Chart.defaults.global,
7658
				display = opts.display,
7659
				fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),
7660
				minSize = me.minSize;
7661
7662
			if (me.isHorizontal()) {
7663
				minSize.width = me.maxWidth; // fill all the width
7664
				minSize.height = display ? fontSize + (opts.padding * 2) : 0;
7665
			} else {
7666
				minSize.width = display ? fontSize + (opts.padding * 2) : 0;
7667
				minSize.height = me.maxHeight; // fill all the height
7668
			}
7669
7670
			me.width = minSize.width;
7671
			me.height = minSize.height;
7672
7673
		},
7674
		afterFit: noop,
7675
7676
		// Shared Methods
7677
		isHorizontal: function() {
7678
			var pos = this.options.position;
7679
			return pos === "top" || pos === "bottom";
7680
		},
7681
7682
		// Actualy draw the title block on the canvas
7683
		draw: function() {
7684
			var me = this,
7685
				ctx = me.ctx,
7686
				valueOrDefault = helpers.getValueOrDefault,
7687
				opts = me.options,
7688
				globalDefaults = Chart.defaults.global;
7689
7690
			if (opts.display) {
7691
				var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),
7692
					fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle),
7693
					fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily),
7694
					titleFont = helpers.fontString(fontSize, fontStyle, fontFamily),
7695
					rotation = 0,
7696
					titleX,
7697
					titleY,
7698
					top = me.top,
7699
					left = me.left,
7700
					bottom = me.bottom,
7701
					right = me.right;
7702
7703
				ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
7704
				ctx.font = titleFont;
7705
7706
				// Horizontal
7707
				if (me.isHorizontal()) {
7708
					titleX = left + ((right - left) / 2); // midpoint of the width
7709
					titleY = top + ((bottom - top) / 2); // midpoint of the height
7710
				} else {
7711
					titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2);
7712
					titleY = top + ((bottom - top) / 2);
7713
					rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
7714
				}
7715
7716
				ctx.save();
7717
				ctx.translate(titleX, titleY);
7718
				ctx.rotate(rotation);
7719
				ctx.textAlign = 'center';
7720
				ctx.textBaseline = 'middle';
7721
				ctx.fillText(opts.text, 0, 0);
7722
				ctx.restore();
7723
			}
7724
		}
7725
	});
7726
7727
	// Register the title plugin
7728
	Chart.plugins.register({
7729
		beforeInit: function(chartInstance) {
7730
			var opts = chartInstance.options;
7731
			var titleOpts = opts.title;
7732
7733
			if (titleOpts) {
7734
				chartInstance.titleBlock = new Chart.Title({
7735
					ctx: chartInstance.chart.ctx,
7736
					options: titleOpts,
7737
					chart: chartInstance
7738
				});
7739
7740
				Chart.layoutService.addBox(chartInstance, chartInstance.titleBlock);
7741
			}
7742
		}
7743
	});
7744
};
7745
7746
},{}],34:[function(require,module,exports){
7747
"use strict";
7748
7749
module.exports = function(Chart) {
7750
7751
	var helpers = Chart.helpers;
7752
7753
	Chart.defaults.global.tooltips = {
7754
		enabled: true,
7755
		custom: null,
7756
		mode: 'single',
7757
		backgroundColor: "rgba(0,0,0,0.8)",
7758
		titleFontStyle: "bold",
7759
		titleSpacing: 2,
7760
		titleMarginBottom: 6,
7761
		titleFontColor: "#fff",
7762
		titleAlign: "left",
7763
		bodySpacing: 2,
7764
		bodyFontColor: "#fff",
7765
		bodyAlign: "left",
7766
		footerFontStyle: "bold",
7767
		footerSpacing: 2,
7768
		footerMarginTop: 6,
7769
		footerFontColor: "#fff",
7770
		footerAlign: "left",
7771
		yPadding: 6,
7772
		xPadding: 6,
7773
		yAlign : 'center',
7774
		xAlign : 'center',
7775
		caretSize: 5,
7776
		cornerRadius: 6,
7777
		multiKeyBackground: '#fff',
7778
		callbacks: {
7779
			// Args are: (tooltipItems, data)
7780
			beforeTitle: helpers.noop,
7781
			title: function(tooltipItems, data) {
7782
				// Pick first xLabel for now
7783
				var title = '';
7784
				var labels = data.labels;
7785
				var labelCount = labels ? labels.length : 0;
7786
7787
				if (tooltipItems.length > 0) {
7788
					var item = tooltipItems[0];
7789
7790
					if (item.xLabel) {
7791
						title = item.xLabel;
7792
					} else if (labelCount > 0 && item.index < labelCount) {
7793
						title = labels[item.index];
7794
					}
7795
				}
7796
7797
				return title;
7798
			},
7799
			afterTitle: helpers.noop,
7800
7801
			// Args are: (tooltipItems, data)
7802
			beforeBody: helpers.noop,
7803
7804
			// Args are: (tooltipItem, data)
7805
			beforeLabel: helpers.noop,
7806
			label: function(tooltipItem, data) {
7807
				var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
7808
				return datasetLabel + ': ' + tooltipItem.yLabel;
7809
			},
7810
			labelColor: function(tooltipItem, chartInstance) {
7811
				var meta = chartInstance.getDatasetMeta(tooltipItem.datasetIndex);
7812
				var activeElement = meta.data[tooltipItem.index];
7813
				var view = activeElement._view;
7814
				return {
7815
					borderColor: view.borderColor,
7816
					backgroundColor: view.backgroundColor
7817
				};
7818
			},
7819
			afterLabel: helpers.noop,
7820
7821
			// Args are: (tooltipItems, data)
7822
			afterBody: helpers.noop,
7823
7824
			// Args are: (tooltipItems, data)
7825
			beforeFooter: helpers.noop,
7826
			footer: helpers.noop,
7827
			afterFooter: helpers.noop
7828
		}
7829
	};
7830
7831
	// Helper to push or concat based on if the 2nd parameter is an array or not
7832
	function pushOrConcat(base, toPush) {
7833
		if (toPush) {
7834
			if (helpers.isArray(toPush)) {
7835
				//base = base.concat(toPush);
7836
				Array.prototype.push.apply(base, toPush);
7837
			} else {
7838
				base.push(toPush);
7839
			}
7840
		}
7841
7842
		return base;
7843
	}
7844
7845
	function getAveragePosition(elements) {
7846
		if (!elements.length) {
7847
			return false;
7848
		}
7849
7850
		var i, len;
7851
		var xPositions = [];
7852
		var yPositions = [];
7853
7854
		for (i = 0, len = elements.length; i < len; ++i) {
7855
			var el = elements[i];
7856
			if (el && el.hasValue()){
7857
				var pos = el.tooltipPosition();
7858
				xPositions.push(pos.x);
7859
				yPositions.push(pos.y);
7860
			}
7861
		}
7862
7863
		var x = 0,
7864
			y = 0;
7865
		for (i = 0; i < xPositions.length; ++i) {
7866
			if (xPositions[ i ]) {
7867
				x += xPositions[i];
7868
				y += yPositions[i];
7869
			}
7870
		}
7871
7872
		return {
7873
			x: Math.round(x / xPositions.length),
7874
			y: Math.round(y / xPositions.length)
7875
		};
7876
	}
7877
7878
	// Private helper to create a tooltip iteam model
7879
	// @param element : the chart element (point, arc, bar) to create the tooltip item for
7880
	// @return : new tooltip item
7881
	function createTooltipItem(element) {
7882
		var xScale = element._xScale;
7883
		var yScale = element._yScale || element._scale; // handle radar || polarArea charts
7884
		var index = element._index,
7885
			datasetIndex = element._datasetIndex;
7886
7887
		return {
7888
			xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
7889
			yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
7890
			index: index,
7891
			datasetIndex: datasetIndex
7892
		};
7893
	}
7894
7895
	Chart.Tooltip = Chart.Element.extend({
7896
		initialize: function() {
7897
			var me = this;
7898
			var globalDefaults = Chart.defaults.global;
7899
			var tooltipOpts = me._options;
7900
			var getValueOrDefault = helpers.getValueOrDefault;
7901
7902
			helpers.extend(me, {
7903
				_model: {
7904
					// Positioning
7905
					xPadding: tooltipOpts.xPadding,
7906
					yPadding: tooltipOpts.yPadding,
7907
					xAlign : tooltipOpts.xAlign,
7908
					yAlign : tooltipOpts.yAlign,
7909
7910
					// Body
7911
					bodyFontColor: tooltipOpts.bodyFontColor,
7912
					_bodyFontFamily: getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
7913
					_bodyFontStyle: getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
7914
					_bodyAlign: tooltipOpts.bodyAlign,
7915
					bodyFontSize: getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
7916
					bodySpacing: tooltipOpts.bodySpacing,
7917
7918
					// Title
7919
					titleFontColor: tooltipOpts.titleFontColor,
7920
					_titleFontFamily: getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
7921
					_titleFontStyle: getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
7922
					titleFontSize: getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
7923
					_titleAlign: tooltipOpts.titleAlign,
7924
					titleSpacing: tooltipOpts.titleSpacing,
7925
					titleMarginBottom: tooltipOpts.titleMarginBottom,
7926
7927
					// Footer
7928
					footerFontColor: tooltipOpts.footerFontColor,
7929
					_footerFontFamily: getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
7930
					_footerFontStyle: getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
7931
					footerFontSize: getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
7932
					_footerAlign: tooltipOpts.footerAlign,
7933
					footerSpacing: tooltipOpts.footerSpacing,
7934
					footerMarginTop: tooltipOpts.footerMarginTop,
7935
7936
					// Appearance
7937
					caretSize: tooltipOpts.caretSize,
7938
					cornerRadius: tooltipOpts.cornerRadius,
7939
					backgroundColor: tooltipOpts.backgroundColor,
7940
					opacity: 0,
7941
					legendColorBackground: tooltipOpts.multiKeyBackground
7942
				}
7943
			});
7944
		},
7945
7946
		// Get the title
7947
		// Args are: (tooltipItem, data)
7948
		getTitle: function() {
7949
			var me = this;
7950
			var opts = me._options;
7951
			var callbacks = opts.callbacks;
7952
7953
			var beforeTitle = callbacks.beforeTitle.apply(me, arguments),
7954
				title = callbacks.title.apply(me, arguments),
7955
				afterTitle = callbacks.afterTitle.apply(me, arguments);
7956
7957
			var lines = [];
7958
			lines = pushOrConcat(lines, beforeTitle);
7959
			lines = pushOrConcat(lines, title);
7960
			lines = pushOrConcat(lines, afterTitle);
7961
7962
			return lines;
7963
		},
7964
7965
		// Args are: (tooltipItem, data)
7966
		getBeforeBody: function() {
7967
			var lines = this._options.callbacks.beforeBody.apply(this, arguments);
7968
			return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
7969
		},
7970
7971
		// Args are: (tooltipItem, data)
7972
		getBody: function(tooltipItems, data) {
7973
			var me = this;
7974
			var callbacks = me._options.callbacks;
7975
			var bodyItems = [];
7976
7977
			helpers.each(tooltipItems, function(tooltipItem) {
7978
				var bodyItem = {
7979
					before: [],
7980
					lines: [],
7981
					after: []
7982
				};
7983
				pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
7984
				pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
7985
				pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
7986
7987
				bodyItems.push(bodyItem);
7988
			});
7989
7990
			return bodyItems;
7991
		},
7992
7993
		// Args are: (tooltipItem, data)
7994
		getAfterBody: function() {
7995
			var lines = this._options.callbacks.afterBody.apply(this, arguments);
7996
			return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
7997
		},
7998
7999
		// Get the footer and beforeFooter and afterFooter lines
8000
		// Args are: (tooltipItem, data)
8001
		getFooter: function() {
8002
			var me = this;
8003
			var callbacks = me._options.callbacks;
8004
8005
			var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
8006
			var footer = callbacks.footer.apply(me, arguments);
8007
			var afterFooter = callbacks.afterFooter.apply(me, arguments);
8008
8009
			var lines = [];
8010
			lines = pushOrConcat(lines, beforeFooter);
8011
			lines = pushOrConcat(lines, footer);
8012
			lines = pushOrConcat(lines, afterFooter);
8013
8014
			return lines;
8015
		},
8016
8017
		update: function(changed) {
8018
			var me = this;
8019
			var opts = me._options;
8020
			var model = me._model;
8021
			var active = me._active;
8022
8023
			var data = me._data;
8024
			var chartInstance = me._chartInstance;
8025
8026
			var i, len;
8027
8028
			if (active.length) {
8029
				model.opacity = 1;
8030
8031
				var labelColors = [],
8032
					tooltipPosition = getAveragePosition(active);
8033
8034
				var tooltipItems = [];
8035
				for (i = 0, len = active.length; i < len; ++i) {
8036
					tooltipItems.push(createTooltipItem(active[i]));
8037
				}
8038
8039
				// If the user provided a sorting function, use it to modify the tooltip items
8040
				if (opts.itemSort) {
8041
					tooltipItems = tooltipItems.sort(opts.itemSort);
8042
				}
8043
8044
				// If there is more than one item, show color items
8045
				if (active.length > 1) {
8046
					helpers.each(tooltipItems, function(tooltipItem) {
8047
						labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance));
8048
					});
8049
				}
8050
8051
				// Build the Text Lines
8052
				helpers.extend(model, {
8053
					title: me.getTitle(tooltipItems, data),
8054
					beforeBody: me.getBeforeBody(tooltipItems, data),
8055
					body: me.getBody(tooltipItems, data),
8056
					afterBody: me.getAfterBody(tooltipItems, data),
8057
					footer: me.getFooter(tooltipItems, data),
8058
					x: Math.round(tooltipPosition.x),
8059
					y: Math.round(tooltipPosition.y),
8060
					caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2),
8061
					labelColors: labelColors
8062
				});
8063
8064
				// We need to determine alignment of
8065
				var tooltipSize = me.getTooltipSize(model);
8066
				me.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas
8067
8068
				helpers.extend(model, me.getBackgroundPoint(model, tooltipSize));
8069
			} else {
8070
				me._model.opacity = 0;
8071
			}
8072
8073
			if (changed && opts.custom) {
8074
				opts.custom.call(me, model);
8075
			}
8076
8077
			return me;
8078
		},
8079
		getTooltipSize: function(vm) {
8080
			var ctx = this._chart.ctx;
8081
8082
			var size = {
8083
				height: vm.yPadding * 2, // Tooltip Padding
8084
				width: 0
8085
			};
8086
8087
			// Count of all lines in the body
8088
			var body = vm.body;
8089
			var combinedBodyLength = body.reduce(function(count, bodyItem) {
8090
				return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
8091
			}, 0);
8092
			combinedBodyLength += vm.beforeBody.length + vm.afterBody.length;
8093
8094
			var titleLineCount = vm.title.length;
8095
			var footerLineCount = vm.footer.length;
8096
			var titleFontSize = vm.titleFontSize,
8097
				bodyFontSize = vm.bodyFontSize,
8098
				footerFontSize = vm.footerFontSize;
8099
8100
			size.height += titleLineCount * titleFontSize; // Title Lines
8101
			size.height += (titleLineCount - 1) * vm.titleSpacing; // Title Line Spacing
8102
			size.height += titleLineCount ? vm.titleMarginBottom : 0; // Title's bottom Margin
8103
			size.height += combinedBodyLength * bodyFontSize; // Body Lines
8104
			size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing
8105
			size.height += footerLineCount ? vm.footerMarginTop : 0; // Footer Margin
8106
			size.height += footerLineCount * (footerFontSize); // Footer Lines
8107
			size.height += footerLineCount ? (footerLineCount - 1) * vm.footerSpacing : 0; // Footer Line Spacing
8108
8109
			// Title width
8110
			var widthPadding = 0;
8111
			var maxLineWidth = function(line) {
8112
				size.width = Math.max(size.width, ctx.measureText(line).width + widthPadding);
8113
			};
8114
8115
			ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8116
			helpers.each(vm.title, maxLineWidth);
8117
8118
			// Body width
8119
			ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8120
			helpers.each(vm.beforeBody.concat(vm.afterBody), maxLineWidth);
8121
8122
			// Body lines may include some extra width due to the color box
8123
			widthPadding = body.length > 1 ? (bodyFontSize + 2) : 0;
8124
			helpers.each(body, function(bodyItem) {
8125
				helpers.each(bodyItem.before, maxLineWidth);
8126
				helpers.each(bodyItem.lines, maxLineWidth);
8127
				helpers.each(bodyItem.after, maxLineWidth);
8128
			});
8129
8130
			// Reset back to 0
8131
			widthPadding = 0;
8132
8133
			// Footer width
8134
			ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8135
			helpers.each(vm.footer, maxLineWidth);
8136
8137
			// Add padding
8138
			size.width += 2 * vm.xPadding;
8139
8140
			return size;
8141
		},
8142
		determineAlignment: function(size) {
8143
			var me = this;
8144
			var model = me._model;
8145
			var chart = me._chart;
8146
			var chartArea = me._chartInstance.chartArea;
8147
8148
			if (model.y < size.height) {
8149
				model.yAlign = 'top';
8150
			} else if (model.y > (chart.height - size.height)) {
8151
				model.yAlign = 'bottom';
8152
			}
8153
8154
			var lf, rf; // functions to determine left, right alignment
8155
			var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
8156
			var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
8157
			var midX = (chartArea.left + chartArea.right) / 2;
8158
			var midY = (chartArea.top + chartArea.bottom) / 2;
8159
8160
			if (model.yAlign === 'center') {
8161
				lf = function(x) {
8162
					return x <= midX;
8163
				};
8164
				rf = function(x) {
8165
					return x > midX;
8166
				};
8167
			} else {
8168
				lf = function(x) {
8169
					return x <= (size.width / 2);
8170
				};
8171
				rf = function(x) {
8172
					return x >= (chart.width - (size.width / 2));
8173
				};
8174
			}
8175
8176
			olf = function(x) {
8177
				return x + size.width > chart.width;
8178
			};
8179
			orf = function(x) {
8180
				return x - size.width < 0;
8181
			};
8182
			yf = function(y) {
8183
				return y <= midY ? 'top' : 'bottom';
8184
			};
8185
8186
			if (lf(model.x)) {
8187
				model.xAlign = 'left';
8188
8189
				// Is tooltip too wide and goes over the right side of the chart.?
8190
				if (olf(model.x)) {
8191
					model.xAlign = 'center';
8192
					model.yAlign = yf(model.y);
8193
				}
8194
			} else if (rf(model.x)) {
8195
				model.xAlign = 'right';
8196
8197
				// Is tooltip too wide and goes outside left edge of canvas?
8198
				if (orf(model.x)) {
8199
					model.xAlign = 'center';
8200
					model.yAlign = yf(model.y);
8201
				}
8202
			}
8203
		},
8204
		getBackgroundPoint: function(vm, size) {
8205
			// Background Position
8206
			var pt = {
8207
				x: vm.x,
8208
				y: vm.y
8209
			};
8210
8211
			var caretSize = vm.caretSize,
8212
				caretPadding = vm.caretPadding,
8213
				cornerRadius = vm.cornerRadius,
8214
				xAlign = vm.xAlign,
8215
				yAlign = vm.yAlign,
8216
				paddingAndSize = caretSize + caretPadding,
8217
				radiusAndPadding = cornerRadius + caretPadding;
8218
8219
			if (xAlign === 'right') {
8220
				pt.x -= size.width;
8221
			} else if (xAlign === 'center') {
8222
				pt.x -= (size.width / 2);
8223
			}
8224
8225
			if (yAlign === 'top') {
8226
				pt.y += paddingAndSize;
8227
			} else if (yAlign === 'bottom') {
8228
				pt.y -= size.height + paddingAndSize;
8229
			} else {
8230
				pt.y -= (size.height / 2);
8231
			}
8232
8233
			if (yAlign === 'center') {
8234
				if (xAlign === 'left') {
8235
					pt.x += paddingAndSize;
8236
				} else if (xAlign === 'right') {
8237
					pt.x -= paddingAndSize;
8238
				}
8239
			} else {
8240
				if (xAlign === 'left') {
8241
					pt.x -= radiusAndPadding;
8242
				} else if (xAlign === 'right') {
8243
					pt.x += radiusAndPadding;
8244
				}
8245
			}
8246
8247
			return pt;
8248
		},
8249
		drawCaret: function(tooltipPoint, size, opacity) {
8250
			var vm = this._view;
8251
			var ctx = this._chart.ctx;
8252
			var x1, x2, x3;
8253
			var y1, y2, y3;
8254
			var caretSize = vm.caretSize;
8255
			var cornerRadius = vm.cornerRadius;
8256
			var xAlign = vm.xAlign,
8257
				yAlign = vm.yAlign;
8258
			var ptX = tooltipPoint.x,
8259
				ptY = tooltipPoint.y;
8260
			var width = size.width,
8261
				height = size.height;
8262
8263
			if (yAlign === 'center') {
8264
				// Left or right side
8265
				if (xAlign === 'left') {
8266
					x1 = ptX;
8267
					x2 = x1 - caretSize;
8268
					x3 = x1;
8269
				} else {
8270
					x1 = ptX + width;
8271
					x2 = x1 + caretSize;
8272
					x3 = x1;
8273
				}
8274
8275
				y2 = ptY + (height / 2);
8276
				y1 = y2 - caretSize;
8277
				y3 = y2 + caretSize;
8278
			} else {
8279
				if (xAlign === 'left') {
8280
					x1 = ptX + cornerRadius;
8281
					x2 = x1 + caretSize;
8282
					x3 = x2 + caretSize;
8283
				} else if (xAlign === 'right') {
8284
					x1 = ptX + width - cornerRadius;
8285
					x2 = x1 - caretSize;
8286
					x3 = x2 - caretSize;
8287
				} else {
8288
					x2 = ptX + (width / 2);
8289
					x1 = x2 - caretSize;
8290
					x3 = x2 + caretSize;
8291
				}
8292
8293
				if (yAlign === 'top') {
8294
					y1 = ptY;
8295
					y2 = y1 - caretSize;
8296
					y3 = y1;
8297
				} else {
8298
					y1 = ptY + height;
8299
					y2 = y1 + caretSize;
8300
					y3 = y1;
8301
				}
8302
			}
8303
8304
			var bgColor = helpers.color(vm.backgroundColor);
8305
			ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
8306
			ctx.beginPath();
8307
			ctx.moveTo(x1, y1);
8308
			ctx.lineTo(x2, y2);
8309
			ctx.lineTo(x3, y3);
8310
			ctx.closePath();
8311
			ctx.fill();
8312
		},
8313
		drawTitle: function(pt, vm, ctx, opacity) {
8314
			var title = vm.title;
8315
8316
			if (title.length) {
8317
				ctx.textAlign = vm._titleAlign;
8318
				ctx.textBaseline = "top";
8319
8320
				var titleFontSize = vm.titleFontSize,
8321
					titleSpacing = vm.titleSpacing;
8322
8323
				var titleFontColor = helpers.color(vm.titleFontColor);
8324
				ctx.fillStyle = titleFontColor.alpha(opacity * titleFontColor.alpha()).rgbString();
8325
				ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8326
8327
				var i, len;
8328
				for (i = 0, len = title.length; i < len; ++i) {
8329
					ctx.fillText(title[i], pt.x, pt.y);
8330
					pt.y += titleFontSize + titleSpacing; // Line Height and spacing
8331
8332
					if (i + 1 === title.length) {
8333
						pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
8334
					}
8335
				}
8336
			}
8337
		},
8338
		drawBody: function(pt, vm, ctx, opacity) {
8339
			var bodyFontSize = vm.bodyFontSize;
8340
			var bodySpacing = vm.bodySpacing;
8341
			var body = vm.body;
8342
8343
			ctx.textAlign = vm._bodyAlign;
8344
			ctx.textBaseline = "top";
8345
8346
			var bodyFontColor = helpers.color(vm.bodyFontColor);
8347
			var textColor = bodyFontColor.alpha(opacity * bodyFontColor.alpha()).rgbString();
8348
			ctx.fillStyle = textColor;
8349
			ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8350
8351
			// Before Body
8352
			var xLinePadding = 0;
8353
			var fillLineOfText = function(line) {
8354
				ctx.fillText(line, pt.x + xLinePadding, pt.y);
8355
				pt.y += bodyFontSize + bodySpacing;
8356
			};
8357
8358
			// Before body lines
8359
			helpers.each(vm.beforeBody, fillLineOfText);
8360
8361
			var drawColorBoxes = body.length > 1;
8362
			xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
8363
8364
			// Draw body lines now
8365
			helpers.each(body, function(bodyItem, i) {
8366
				helpers.each(bodyItem.before, fillLineOfText);
8367
8368
				helpers.each(bodyItem.lines, function(line) {
8369
					// Draw Legend-like boxes if needed
8370
					if (drawColorBoxes) {
8371
						// Fill a white rect so that colours merge nicely if the opacity is < 1
8372
						ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString();
8373
						ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8374
8375
						// Border
8376
						ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
8377
						ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8378
8379
						// Inner square
8380
						ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString();
8381
						ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
8382
8383
						ctx.fillStyle = textColor;
8384
					}
8385
8386
					fillLineOfText(line);
8387
				});
8388
8389
				helpers.each(bodyItem.after, fillLineOfText);
8390
			});
8391
8392
			// Reset back to 0 for after body
8393
			xLinePadding = 0;
8394
8395
			// After body lines
8396
			helpers.each(vm.afterBody, fillLineOfText);
8397
			pt.y -= bodySpacing; // Remove last body spacing
8398
		},
8399
		drawFooter: function(pt, vm, ctx, opacity) {
8400
			var footer = vm.footer;
8401
8402
			if (footer.length) {
8403
				pt.y += vm.footerMarginTop;
8404
8405
				ctx.textAlign = vm._footerAlign;
8406
				ctx.textBaseline = "top";
8407
8408
				var footerFontColor = helpers.color(vm.footerFontColor);
8409
				ctx.fillStyle = footerFontColor.alpha(opacity * footerFontColor.alpha()).rgbString();
8410
				ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8411
8412
				helpers.each(footer, function(line) {
8413
					ctx.fillText(line, pt.x, pt.y);
8414
					pt.y += vm.footerFontSize + vm.footerSpacing;
8415
				});
8416
			}
8417
		},
8418
		draw: function() {
8419
			var ctx = this._chart.ctx;
8420
			var vm = this._view;
8421
8422
			if (vm.opacity === 0) {
8423
				return;
8424
			}
8425
8426
			var tooltipSize = this.getTooltipSize(vm);
8427
			var pt = {
8428
				x: vm.x,
8429
				y: vm.y
8430
			};
8431
8432
			// IE11/Edge does not like very small opacities, so snap to 0
8433
			var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
8434
8435
			if (this._options.enabled) {
8436
				// Draw Background
8437
				var bgColor = helpers.color(vm.backgroundColor);
8438
				ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
8439
				helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
8440
				ctx.fill();
8441
8442
				// Draw Caret
8443
				this.drawCaret(pt, tooltipSize, opacity);
8444
8445
				// Draw Title, Body, and Footer
8446
				pt.x += vm.xPadding;
8447
				pt.y += vm.yPadding;
8448
8449
				// Titles
8450
				this.drawTitle(pt, vm, ctx, opacity);
8451
8452
				// Body
8453
				this.drawBody(pt, vm, ctx, opacity);
8454
8455
				// Footer
8456
				this.drawFooter(pt, vm, ctx, opacity);
8457
			}
8458
		}
8459
	});
8460
};
8461
8462
},{}],35:[function(require,module,exports){
8463
"use strict";
8464
8465
module.exports = function(Chart) {
8466
8467
  var helpers = Chart.helpers,
8468
    globalOpts = Chart.defaults.global;
8469
8470
  globalOpts.elements.arc = {
8471
    backgroundColor: globalOpts.defaultColor,
8472
    borderColor: "#fff",
8473
    borderWidth: 2
8474
  };
8475
8476
  Chart.elements.Arc = Chart.Element.extend({
8477
    inLabelRange: function(mouseX) {
8478
      var vm = this._view;
8479
8480
      if (vm) {
8481
        return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
8482
      } else {
8483
        return false;
8484
      }
8485
    },
8486
    inRange: function(chartX, chartY) {
8487
      var vm = this._view;
8488
8489
      if (vm) {
8490
        var pointRelativePosition = helpers.getAngleFromPoint(vm, {
8491
            x: chartX,
8492
            y: chartY
8493
          }),
8494
          angle = pointRelativePosition.angle,
8495
          distance = pointRelativePosition.distance;
8496
8497
        //Sanitise angle range
8498
        var startAngle = vm.startAngle;
8499
        var endAngle = vm.endAngle;
8500
        while (endAngle < startAngle) {
8501
          endAngle += 2.0 * Math.PI;
8502
        }
8503
        while (angle > endAngle) {
8504
          angle -= 2.0 * Math.PI;
8505
        }
8506
        while (angle < startAngle) {
8507
          angle += 2.0 * Math.PI;
8508
        }
8509
8510
        //Check if within the range of the open/close angle
8511
        var betweenAngles = (angle >= startAngle && angle <= endAngle),
8512
          withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
8513
8514
        return (betweenAngles && withinRadius);
8515
      } else {
8516
        return false;
8517
      }
8518
    },
8519
    tooltipPosition: function() {
8520
      var vm = this._view;
8521
8522
      var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
8523
        rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
8524
      return {
8525
        x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
8526
        y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
8527
      };
8528
    },
8529
    draw: function() {
8530
8531
      var ctx = this._chart.ctx,
8532
        vm = this._view,
8533
        sA = vm.startAngle,
8534
        eA = vm.endAngle;
8535
8536
      ctx.beginPath();
8537
8538
      ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
8539
      ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
8540
8541
      ctx.closePath();
8542
      ctx.strokeStyle = vm.borderColor;
8543
      ctx.lineWidth = vm.borderWidth;
8544
8545
      ctx.fillStyle = vm.backgroundColor;
8546
8547
      ctx.fill();
8548
      ctx.lineJoin = 'bevel';
8549
8550
      if (vm.borderWidth) {
8551
        ctx.stroke();
8552
      }
8553
    }
8554
  });
8555
};
8556
8557
},{}],36:[function(require,module,exports){
8558
"use strict";
8559
8560
module.exports = function(Chart) {
8561
8562
	var helpers = Chart.helpers;
8563
	var globalDefaults = Chart.defaults.global;
8564
8565
	Chart.defaults.global.elements.line = {
8566
		tension: 0.4,
8567
		backgroundColor: globalDefaults.defaultColor,
8568
		borderWidth: 3,
8569
		borderColor: globalDefaults.defaultColor,
8570
		borderCapStyle: 'butt',
8571
		borderDash: [],
8572
		borderDashOffset: 0.0,
8573
		borderJoinStyle: 'miter',
8574
		capBezierPoints: true,
8575
		fill: true // do we fill in the area between the line and its base axis
8576
	};
8577
8578
	Chart.elements.Line = Chart.Element.extend({
8579
		draw: function() {
8580
			var me = this;
8581
			var vm = me._view;
8582
			var spanGaps = vm.spanGaps;
8583
			var scaleZero = vm.scaleZero;
8584
			var loop = me._loop;
8585
8586
			var ctx = me._chart.ctx;
8587
			ctx.save();
8588
8589
			// Helper function to draw a line to a point
8590
			function lineToPoint(previousPoint, point) {
8591
				var vm = point._view;
8592
				if (point._view.steppedLine === true) {
8593
					ctx.lineTo(point._view.x, previousPoint._view.y);
8594
					ctx.lineTo(point._view.x, point._view.y);				
8595
				} else if (point._view.tension === 0) {
8596
					ctx.lineTo(vm.x, vm.y);
8597
				} else {
8598
					ctx.bezierCurveTo(
8599
						previousPoint._view.controlPointNextX,
8600
						previousPoint._view.controlPointNextY,
8601
						vm.controlPointPreviousX,
8602
						vm.controlPointPreviousY,
8603
						vm.x,
8604
						vm.y
8605
					);
8606
				}
8607
			}
8608
8609
			var points = me._children.slice(); // clone array
8610
			var lastDrawnIndex = -1;
8611
8612
			// If we are looping, adding the first point again
8613
			if (loop && points.length) {
8614
				points.push(points[0]);
8615
			}
8616
8617
			var index, current, previous, currentVM;
8618
8619
			// Fill Line
8620
			if (points.length && vm.fill) {
8621
				ctx.beginPath();
8622
8623
				for (index = 0; index < points.length; ++index) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
8624
					current = points[index];
8625
					previous = helpers.previousItem(points, index);
8626
					currentVM = current._view;
8627
8628
					// First point moves to it's starting position no matter what
8629
					if (index === 0) {
8630
						if (loop) {
8631
							ctx.moveTo(scaleZero.x, scaleZero.y);
8632
						} else {
8633
							ctx.moveTo(currentVM.x, scaleZero);
8634
						}
8635
8636
						if (!currentVM.skip) {
8637
							lastDrawnIndex = index;
8638
							ctx.lineTo(currentVM.x, currentVM.y);
8639
						}
8640
					} else {
8641
						previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
8642
8643
						if (currentVM.skip) {
8644
							// Only do this if this is the first point that is skipped
8645
							if (!spanGaps && lastDrawnIndex === (index - 1)) {
8646
								if (loop) {
8647
									ctx.lineTo(scaleZero.x, scaleZero.y);
8648
								} else {
8649
									ctx.lineTo(previous._view.x, scaleZero);
8650
								}
8651
							}
8652
						} else {
8653
							if (lastDrawnIndex !== (index - 1)) {
8654
								// There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case. 
8655
								// If the first data point is NaN, then there is no real gap to skip
8656
								if (spanGaps && lastDrawnIndex !== -1) {
8657
									// We are spanning the gap, so simple draw a line to this point
8658
									lineToPoint(previous, current);
8659
								} else {
8660
									if (loop) {
8661
										ctx.lineTo(currentVM.x, currentVM.y);
8662
									} else {
8663
										ctx.lineTo(currentVM.x, scaleZero);
8664
										ctx.lineTo(currentVM.x, currentVM.y);
8665
									}
8666
								}
8667
							} else {
8668
								// Line to next point
8669
								lineToPoint(previous, current);
8670
							}
8671
							lastDrawnIndex = index;
8672
						}
8673
					}
8674
				}
8675
8676
				if (!loop) {
8677
					ctx.lineTo(points[lastDrawnIndex]._view.x, scaleZero);
8678
				}
8679
8680
				ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor;
8681
				ctx.closePath();
8682
				ctx.fill();
8683
			}
8684
8685
			// Stroke Line Options
8686
			var globalOptionLineElements = globalDefaults.elements.line;
8687
			ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
8688
8689
			// IE 9 and 10 do not support line dash
8690
			if (ctx.setLineDash) {
8691
				ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
8692
			}
8693
8694
			ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
8695
			ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
8696
			ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
8697
			ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
8698
8699
			// Stroke Line
8700
			ctx.beginPath();
8701
			lastDrawnIndex = -1;
8702
8703
			for (index = 0; index < points.length; ++index) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
8704
				current = points[index];
8705
				previous = helpers.previousItem(points, index);
8706
				currentVM = current._view;
8707
8708
				// First point moves to it's starting position no matter what
8709
				if (index === 0) {
8710
					if (currentVM.skip) {
0 ignored issues
show
Comprehensibility Documentation Best Practice introduced by
This code block is empty. Consider removing it or adding a comment to explain.
Loading history...
8711
						
8712
					} else {
8713
						ctx.moveTo(currentVM.x, currentVM.y);
8714
						lastDrawnIndex = index;
8715
					}
8716
				} else {
8717
					previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
8718
8719
					if (!currentVM.skip) {
8720
						if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
8721
							// There was a gap and this is the first point after the gap
8722
							ctx.moveTo(currentVM.x, currentVM.y);
8723
						} else {
8724
							// Line to next point
8725
							lineToPoint(previous, current);
8726
						}
8727
						lastDrawnIndex = index;
8728
					}
8729
				}
8730
			}
8731
8732
			ctx.stroke();
8733
			ctx.restore();
8734
		}
8735
	});
8736
};
8737
},{}],37:[function(require,module,exports){
8738
"use strict";
8739
8740
module.exports = function(Chart) {
8741
8742
	var helpers = Chart.helpers,
8743
		globalOpts = Chart.defaults.global,
8744
		defaultColor = globalOpts.defaultColor;
8745
8746
	globalOpts.elements.point = {
8747
		radius: 3,
8748
		pointStyle: 'circle',
8749
		backgroundColor: defaultColor,
8750
		borderWidth: 1,
8751
		borderColor: defaultColor,
8752
		// Hover
8753
		hitRadius: 1,
8754
		hoverRadius: 4,
8755
		hoverBorderWidth: 1
8756
	};
8757
8758
	Chart.elements.Point = Chart.Element.extend({
8759
		inRange: function(mouseX, mouseY) {
8760
			var vm = this._view;
8761
			return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
8762
		},
8763
		inLabelRange: function(mouseX) {
8764
			var vm = this._view;
8765
			return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
8766
		},
8767
		tooltipPosition: function() {
8768
			var vm = this._view;
8769
			return {
8770
				x: vm.x,
8771
				y: vm.y,
8772
				padding: vm.radius + vm.borderWidth
8773
			};
8774
		},
8775
		draw: function() {
8776
			var vm = this._view;
8777
			var ctx = this._chart.ctx;
8778
			var pointStyle = vm.pointStyle;
8779
			var radius = vm.radius;
8780
			var x = vm.x;
8781
			var y = vm.y;
8782
8783
			if (vm.skip) {
8784
				return;
8785
			}
8786
8787
			ctx.strokeStyle = vm.borderColor || defaultColor;
8788
			ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth);
8789
			ctx.fillStyle = vm.backgroundColor || defaultColor;
8790
8791
			Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y);
8792
		}
8793
	});
8794
};
8795
8796
},{}],38:[function(require,module,exports){
8797
"use strict";
8798
8799
module.exports = function(Chart) {
8800
8801
	var globalOpts = Chart.defaults.global;
8802
8803
	globalOpts.elements.rectangle = {
8804
		backgroundColor: globalOpts.defaultColor,
8805
		borderWidth: 0,
8806
		borderColor: globalOpts.defaultColor,
8807
		borderSkipped: 'bottom'
8808
	};
8809
8810
	Chart.elements.Rectangle = Chart.Element.extend({
8811
		draw: function() {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
8812
			var ctx = this._chart.ctx;
8813
			var vm = this._view;
8814
8815
			var halfWidth = vm.width / 2,
8816
				leftX = vm.x - halfWidth,
8817
				rightX = vm.x + halfWidth,
8818
				top = vm.base - (vm.base - vm.y),
8819
				halfStroke = vm.borderWidth / 2;
8820
8821
			// Canvas doesn't allow us to stroke inside the width so we can
8822
			// adjust the sizes to fit if we're setting a stroke on the line
8823
			if (vm.borderWidth) {
8824
				leftX += halfStroke;
8825
				rightX -= halfStroke;
8826
				top += halfStroke;
8827
			}
8828
8829
			ctx.beginPath();
8830
			ctx.fillStyle = vm.backgroundColor;
8831
			ctx.strokeStyle = vm.borderColor;
8832
			ctx.lineWidth = vm.borderWidth;
8833
8834
			// Corner points, from bottom-left to bottom-right clockwise
8835
			// | 1 2 |
8836
			// | 0 3 |
8837
			var corners = [
8838
				[leftX, vm.base],
8839
				[leftX, top],
8840
				[rightX, top],
8841
				[rightX, vm.base]
8842
			];
8843
8844
			// Find first (starting) corner with fallback to 'bottom'
8845
			var borders = ['bottom', 'left', 'top', 'right'];
8846
			var startCorner = borders.indexOf(vm.borderSkipped, 0);
8847
			if (startCorner === -1)
8848
				startCorner = 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8849
8850
			function cornerAt(index) {
8851
				return corners[(startCorner + index) % 4];
8852
			}
8853
8854
			// Draw rectangle from 'startCorner'
8855
			ctx.moveTo.apply(ctx, cornerAt(0));
8856
			for (var i = 1; i < 4; i++)
8857
				ctx.lineTo.apply(ctx, cornerAt(i));
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8858
8859
			ctx.fill();
8860
			if (vm.borderWidth) {
8861
				ctx.stroke();
8862
			}
8863
		},
8864
		height: function() {
8865
			var vm = this._view;
8866
			return vm.base - vm.y;
8867
		},
8868
		inRange: function(mouseX, mouseY) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
8869
			var vm = this._view;
8870
			return vm ?
8871
					(vm.y < vm.base ?
8872
						(mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base) :
8873
						(mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y)) :
8874
					false;
8875
		},
8876
		inLabelRange: function(mouseX) {
8877
			var vm = this._view;
8878
			return vm ? (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) : false;
8879
		},
8880
		tooltipPosition: function() {
8881
			var vm = this._view;
8882
			return {
8883
				x: vm.x,
8884
				y: vm.y
8885
			};
8886
		}
8887
	});
8888
8889
};
8890
},{}],39:[function(require,module,exports){
8891
"use strict";
8892
8893
module.exports = function(Chart) {
8894
8895
	var helpers = Chart.helpers;
8896
	// Default config for a category scale
8897
	var defaultConfig = {
8898
		position: "bottom"
8899
	};
8900
8901
	var DatasetScale = Chart.Scale.extend({
8902
		/**
8903
		* Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use tose
8904
		* else fall back to data.labels
8905
		* @private
8906
		*/
8907
		getLabels: function() {
8908
			var data = this.chart.data;
8909
			return (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
8910
		},
8911
		// Implement this so that
8912
		determineDataLimits: function() {
8913
			var me = this;
8914
			var labels = me.getLabels(); 
8915
			me.minIndex = 0;
8916
			me.maxIndex = labels.length - 1;
8917
			var findIndex;
8918
8919
			if (me.options.ticks.min !== undefined) {
8920
				// user specified min value
8921
				findIndex = helpers.indexOf(labels, me.options.ticks.min);
8922
				me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
8923
			}
8924
8925
			if (me.options.ticks.max !== undefined) {
8926
				// user specified max value
8927
				findIndex = helpers.indexOf(labels, me.options.ticks.max);
8928
				me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
8929
			}
8930
8931
			me.min = labels[me.minIndex];
8932
			me.max = labels[me.maxIndex];
8933
		},
8934
8935
		buildTicks: function() {
8936
			var me = this;
8937
			var labels = me.getLabels();
8938
			// If we are viewing some subset of labels, slice the original array
8939
			me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
8940
		},
8941
8942
		getLabelForIndex: function(index) {
8943
			return this.ticks[index];
8944
		},
8945
8946
		// Used to get data value locations.  Value can either be an index or a numerical value
8947
		getPixelForValue: function(value, index, datasetIndex, includeOffset) {
8948
			var me = this;
8949
			// 1 is added because we need the length but we have the indexes
8950
			var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
8951
8952
			if (value !== undefined) {
8953
				var labels = me.getLabels();
8954
				var idx = labels.indexOf(value);
8955
				index = idx !== -1 ? idx : index;
8956
			}
8957
8958
			if (me.isHorizontal()) {
8959
				var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
8960
				var valueWidth = innerWidth / offsetAmt;
8961
				var widthOffset = (valueWidth * (index - me.minIndex)) + me.paddingLeft;
8962
8963
				if (me.options.gridLines.offsetGridLines && includeOffset) {
8964
					widthOffset += (valueWidth / 2);
8965
				}
8966
8967
				return me.left + Math.round(widthOffset);
8968
			} else {
8969
				var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
8970
				var valueHeight = innerHeight / offsetAmt;
8971
				var heightOffset = (valueHeight * (index - me.minIndex)) + me.paddingTop;
8972
8973
				if (me.options.gridLines.offsetGridLines && includeOffset) {
8974
					heightOffset += (valueHeight / 2);
8975
				}
8976
8977
				return me.top + Math.round(heightOffset);
8978
			}
8979
		},
8980
		getPixelForTick: function(index, includeOffset) {
8981
			return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset);
8982
		},
8983
		getValueForPixel: function(pixel) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
8984
			var me = this;
8985
			var value;
8986
			var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
8987
			var horz = me.isHorizontal();
8988
			var innerDimension = horz ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
8989
			var valueDimension = innerDimension / offsetAmt;
8990
8991
			pixel -= horz ? me.left : me.top;
8992
8993
			if (me.options.gridLines.offsetGridLines) {
8994
				pixel -= (valueDimension / 2);
8995
			}
8996
			pixel -= horz ? me.paddingLeft : me.paddingTop;
8997
8998
			if (pixel <= 0) {
8999
				value = 0;
9000
			} else {
9001
				value = Math.round(pixel / valueDimension);
9002
			}
9003
9004
			return value;
9005
		},
9006
		getBasePixel: function() {
9007
			return this.bottom;
9008
		}
9009
	});
9010
9011
	Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig);
9012
9013
};
9014
},{}],40:[function(require,module,exports){
9015
"use strict";
9016
9017
module.exports = function(Chart) {
9018
9019
	var helpers = Chart.helpers;
9020
9021
	var defaultConfig = {
9022
		position: "left",
9023
		ticks: {
9024
			callback: function(tickValue, index, ticks) {
9025
				// If we have lots of ticks, don't use the ones
9026
				var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
9027
9028
				// If we have a number like 2.5 as the delta, figure out how many decimal places we need
9029
				if (Math.abs(delta) > 1) {
9030
					if (tickValue !== Math.floor(tickValue)) {
9031
						// not an integer
9032
						delta = tickValue - Math.floor(tickValue);
9033
					}
9034
				}
9035
9036
				var logDelta = helpers.log10(Math.abs(delta));
9037
				var tickString = '';
9038
9039
				if (tickValue !== 0) {
9040
					var numDecimal = -1 * Math.floor(logDelta);
9041
					numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
9042
					tickString = tickValue.toFixed(numDecimal);
9043
				} else {
9044
					tickString = '0'; // never show decimal places for 0
9045
				}
9046
9047
				return tickString;
9048
			}
9049
		}
9050
	};
9051
9052
	var LinearScale = Chart.LinearScaleBase.extend({
9053
		determineDataLimits: function() {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
9054
			var me = this;
9055
			var opts = me.options;
9056
			var chart = me.chart;
9057
			var data = chart.data;
9058
			var datasets = data.datasets;
9059
			var isHorizontal = me.isHorizontal();
9060
9061
			function IDMatches(meta) {
9062
				return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
9063
			}
9064
9065
			// First Calculate the range
9066
			me.min = null;
9067
			me.max = null;
9068
9069
			if (opts.stacked) {
9070
				var valuesPerType = {};
9071
				var hasPositiveValues = false;
0 ignored issues
show
Unused Code introduced by
The variable hasPositiveValues seems to be never used. Consider removing it.
Loading history...
9072
				var hasNegativeValues = false;
0 ignored issues
show
Unused Code introduced by
The variable hasNegativeValues seems to be never used. Consider removing it.
Loading history...
9073
9074
				helpers.each(datasets, function(dataset, datasetIndex) {
9075
					var meta = chart.getDatasetMeta(datasetIndex);
9076
					if (valuesPerType[meta.type] === undefined) {
9077
						valuesPerType[meta.type] = {
9078
							positiveValues: [],
9079
							negativeValues: []
9080
						};
9081
					}
9082
9083
					// Store these per type
9084
					var positiveValues = valuesPerType[meta.type].positiveValues;
9085
					var negativeValues = valuesPerType[meta.type].negativeValues;
9086
9087
					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
9088
						helpers.each(dataset.data, function(rawValue, index) {
9089
							var value = +me.getRightValue(rawValue);
9090
							if (isNaN(value) || meta.data[index].hidden) {
9091
								return;
9092
							}
9093
9094
							positiveValues[index] = positiveValues[index] || 0;
9095
							negativeValues[index] = negativeValues[index] || 0;
9096
9097
							if (opts.relativePoints) {
9098
								positiveValues[index] = 100;
9099
							} else {
9100
								if (value < 0) {
9101
									hasNegativeValues = true;
0 ignored issues
show
Unused Code introduced by
The variable hasNegativeValues seems to be never used. Consider removing it.
Loading history...
9102
									negativeValues[index] += value;
9103
								} else {
9104
									hasPositiveValues = true;
0 ignored issues
show
Unused Code introduced by
The variable hasPositiveValues seems to be never used. Consider removing it.
Loading history...
9105
									positiveValues[index] += value;
9106
								}
9107
							}
9108
						});
9109
					}
9110
				});
9111
9112
				helpers.each(valuesPerType, function(valuesForType) {
9113
					var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
9114
					var minVal = helpers.min(values);
9115
					var maxVal = helpers.max(values);
9116
					me.min = me.min === null ? minVal : Math.min(me.min, minVal);
9117
					me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
9118
				});
9119
9120
			} else {
9121
				helpers.each(datasets, function(dataset, datasetIndex) {
9122
					var meta = chart.getDatasetMeta(datasetIndex);
9123
					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
9124
						helpers.each(dataset.data, function(rawValue, index) {
9125
							var value = +me.getRightValue(rawValue);
9126
							if (isNaN(value) || meta.data[index].hidden) {
9127
								return;
9128
							}
9129
9130
							if (me.min === null) {
9131
								me.min = value;
9132
							} else if (value < me.min) {
9133
								me.min = value;
9134
							}
9135
9136
							if (me.max === null) {
9137
								me.max = value;
9138
							} else if (value > me.max) {
9139
								me.max = value;
9140
							}
9141
						});
9142
					}
9143
				});
9144
			}
9145
9146
			// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
9147
			this.handleTickRangeOptions();
9148
		},
9149
		getTickLimit: function() {
9150
			var maxTicks;
9151
			var me = this;
9152
			var tickOpts = me.options.ticks;
9153
9154
			if (me.isHorizontal()) {
9155
				maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
9156
			} else {
9157
				// The factor of 2 used to scale the font size has been experimentally determined.
9158
				var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize);
9159
				maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
9160
			}
9161
9162
			return maxTicks;
9163
		},
9164
		// Called after the ticks are built. We need
9165
		handleDirectionalChanges: function() {
9166
			if (!this.isHorizontal()) {
9167
				// We are in a vertical orientation. The top value is the highest. So reverse the array
9168
				this.ticks.reverse();
9169
			}
9170
		},
9171
		getLabelForIndex: function(index, datasetIndex) {
9172
			return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
9173
		},
9174
		// Utils
9175
		getPixelForValue: function(value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
9176
			// This must be called after fit has been run so that
9177
			// this.left, this.top, this.right, and this.bottom have been defined
9178
			var me = this;
9179
			var paddingLeft = me.paddingLeft;
9180
			var paddingBottom = me.paddingBottom;
9181
			var start = me.start;
9182
9183
			var rightValue = +me.getRightValue(value);
9184
			var pixel;
9185
			var innerDimension;
9186
			var range = me.end - start;
9187
9188
			if (me.isHorizontal()) {
9189
				innerDimension = me.width - (paddingLeft + me.paddingRight);
9190
				pixel = me.left + (innerDimension / range * (rightValue - start));
9191
				return Math.round(pixel + paddingLeft);
9192
			} else {
9193
				innerDimension = me.height - (me.paddingTop + paddingBottom);
9194
				pixel = (me.bottom - paddingBottom) - (innerDimension / range * (rightValue - start));
9195
				return Math.round(pixel);
9196
			}
9197
		},
9198
		getValueForPixel: function(pixel) {
9199
			var me = this;
9200
			var isHorizontal = me.isHorizontal();
9201
			var paddingLeft = me.paddingLeft;
9202
			var paddingBottom = me.paddingBottom;
9203
			var innerDimension = isHorizontal ? me.width - (paddingLeft + me.paddingRight) : me.height - (me.paddingTop + paddingBottom);
9204
			var offset = (isHorizontal ? pixel - me.left - paddingLeft : me.bottom - paddingBottom - pixel) / innerDimension;
9205
			return me.start + ((me.end - me.start) * offset);
9206
		},
9207
		getPixelForTick: function(index) {
9208
			return this.getPixelForValue(this.ticksAsNumbers[index]);
9209
		}
9210
	});
9211
	Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig);
9212
9213
};
9214
},{}],41:[function(require,module,exports){
9215
"use strict";
9216
9217
module.exports = function(Chart) {
9218
9219
	var helpers = Chart.helpers,
9220
		noop = helpers.noop;
9221
9222
	Chart.LinearScaleBase = Chart.Scale.extend({
9223
		handleTickRangeOptions: function() {
9224
			var me = this;
9225
			var opts = me.options;
9226
			var tickOpts = opts.ticks;
9227
9228
			// If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
9229
			// do nothing since that would make the chart weird. If the user really wants a weird chart
9230
			// axis, they can manually override it
9231
			if (tickOpts.beginAtZero) {
9232
				var minSign = helpers.sign(me.min);
9233
				var maxSign = helpers.sign(me.max);
9234
9235
				if (minSign < 0 && maxSign < 0) {
9236
					// move the top up to 0
9237
					me.max = 0;
9238
				} else if (minSign > 0 && maxSign > 0) {
9239
					// move the botttom down to 0
9240
					me.min = 0;
9241
				}
9242
			}
9243
9244
			if (tickOpts.min !== undefined) {
9245
				me.min = tickOpts.min;
9246
			} else if (tickOpts.suggestedMin !== undefined) {
9247
				me.min = Math.min(me.min, tickOpts.suggestedMin);
9248
			}
9249
9250
			if (tickOpts.max !== undefined) {
9251
				me.max = tickOpts.max;
9252
			} else if (tickOpts.suggestedMax !== undefined) {
9253
				me.max = Math.max(me.max, tickOpts.suggestedMax);
9254
			}
9255
9256
			if (me.min === me.max) {
9257
				me.max++;
9258
9259
				if (!tickOpts.beginAtZero) {
9260
					me.min--;
9261
				}
9262
			}
9263
		},
9264
		getTickLimit: noop,
9265
		handleDirectionalChanges: noop,
9266
9267
		buildTicks: function() {
9268
			var me = this;
9269
			var opts = me.options;
9270
			var ticks = me.ticks = [];
9271
			var tickOpts = opts.ticks;
9272
			var getValueOrDefault = helpers.getValueOrDefault;
9273
9274
			// Figure out what the max number of ticks we can support it is based on the size of
9275
			// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
9276
			// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
9277
			// the graph
9278
9279
			var maxTicks = me.getTickLimit();
9280
9281
			// Make sure we always have at least 2 ticks
9282
			maxTicks = Math.max(2, maxTicks);
9283
9284
			// To get a "nice" value for the tick spacing, we will use the appropriately named
9285
			// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
9286
			// for details.
9287
9288
			var spacing;
9289
			var fixedStepSizeSet = (tickOpts.fixedStepSize && tickOpts.fixedStepSize > 0) || (tickOpts.stepSize && tickOpts.stepSize > 0);
9290
			if (fixedStepSizeSet) {
9291
				spacing = getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize);
9292
			} else {
9293
				var niceRange = helpers.niceNum(me.max - me.min, false);
9294
				spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
9295
			}
9296
			var niceMin = Math.floor(me.min / spacing) * spacing;
9297
			var niceMax = Math.ceil(me.max / spacing) * spacing;
9298
			var numSpaces = (niceMax - niceMin) / spacing;
9299
9300
			// If very close to our rounded value, use it.
9301
			if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
9302
				numSpaces = Math.round(numSpaces);
9303
			} else {
9304
				numSpaces = Math.ceil(numSpaces);
9305
			}
9306
9307
			// Put the values into the ticks array
9308
			ticks.push(tickOpts.min !== undefined ? tickOpts.min : niceMin);
9309
			for (var j = 1; j < numSpaces; ++j) {
9310
				ticks.push(niceMin + (j * spacing));
9311
			}
9312
			ticks.push(tickOpts.max !== undefined ? tickOpts.max : niceMax);
9313
9314
			me.handleDirectionalChanges();
9315
9316
			// At this point, we need to update our max and min given the tick values since we have expanded the
9317
			// range of the scale
9318
			me.max = helpers.max(ticks);
9319
			me.min = helpers.min(ticks);
9320
9321
			if (tickOpts.reverse) {
9322
				ticks.reverse();
9323
9324
				me.start = me.max;
9325
				me.end = me.min;
9326
			} else {
9327
				me.start = me.min;
9328
				me.end = me.max;
9329
			}
9330
		},
9331
		convertTicksToLabels: function() {
9332
			var me = this;
9333
			me.ticksAsNumbers = me.ticks.slice();
9334
			me.zeroLineIndex = me.ticks.indexOf(0);
9335
9336
			Chart.Scale.prototype.convertTicksToLabels.call(me);
9337
		},
9338
	});
9339
};
9340
},{}],42:[function(require,module,exports){
9341
"use strict";
9342
9343
module.exports = function(Chart) {
9344
9345
	var helpers = Chart.helpers;
9346
9347
	var defaultConfig = {
9348
		position: "left",
9349
9350
		// label settings
9351
		ticks: {
9352
			callback: function(value, index, arr) {
9353
				var remain = value / (Math.pow(10, Math.floor(helpers.log10(value))));
9354
9355
				if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) {
9356
					return value.toExponential();
9357
				} else {
9358
					return '';
9359
				}
9360
			}
9361
		}
9362
	};
9363
9364
	var LogarithmicScale = Chart.Scale.extend({
9365
		determineDataLimits: function() {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
9366
			var me = this;
9367
			var opts = me.options;
9368
			var tickOpts = opts.ticks;
9369
			var chart = me.chart;
9370
			var data = chart.data;
9371
			var datasets = data.datasets;
9372
			var getValueOrDefault = helpers.getValueOrDefault;
9373
			var isHorizontal = me.isHorizontal();
9374
			function IDMatches(meta) {
9375
				return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
9376
			}
9377
9378
			// Calculate Range
9379
			me.min = null;
9380
			me.max = null;
9381
9382
			if (opts.stacked) {
9383
				var valuesPerType = {};
9384
9385
				helpers.each(datasets, function(dataset, datasetIndex) {
9386
					var meta = chart.getDatasetMeta(datasetIndex);
9387
					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
9388
						if (valuesPerType[meta.type] === undefined) {
9389
							valuesPerType[meta.type] = [];
9390
						}
9391
9392
						helpers.each(dataset.data, function(rawValue, index) {
9393
							var values = valuesPerType[meta.type];
9394
							var value = +me.getRightValue(rawValue);
9395
							if (isNaN(value) || meta.data[index].hidden) {
9396
								return;
9397
							}
9398
9399
							values[index] = values[index] || 0;
9400
9401
							if (opts.relativePoints) {
9402
								values[index] = 100;
9403
							} else {
9404
								// Don't need to split positive and negative since the log scale can't handle a 0 crossing
9405
								values[index] += value;
9406
							}
9407
						});
9408
					}
9409
				});
9410
9411
				helpers.each(valuesPerType, function(valuesForType) {
9412
					var minVal = helpers.min(valuesForType);
9413
					var maxVal = helpers.max(valuesForType);
9414
					me.min = me.min === null ? minVal : Math.min(me.min, minVal);
9415
					me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
9416
				});
9417
9418
			} else {
9419
				helpers.each(datasets, function(dataset, datasetIndex) {
9420
					var meta = chart.getDatasetMeta(datasetIndex);
9421
					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
9422
						helpers.each(dataset.data, function(rawValue, index) {
9423
							var value = +me.getRightValue(rawValue);
9424
							if (isNaN(value) || meta.data[index].hidden) {
9425
								return;
9426
							}
9427
9428
							if (me.min === null) {
9429
								me.min = value;
9430
							} else if (value < me.min) {
9431
								me.min = value;
9432
							}
9433
9434
							if (me.max === null) {
9435
								me.max = value;
9436
							} else if (value > me.max) {
9437
								me.max = value;
9438
							}
9439
						});
9440
					}
9441
				});
9442
			}
9443
9444
			me.min = getValueOrDefault(tickOpts.min, me.min);
9445
			me.max = getValueOrDefault(tickOpts.max, me.max);
9446
9447
			if (me.min === me.max) {
9448
				if (me.min !== 0 && me.min !== null) {
9449
					me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
9450
					me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
9451
				} else {
9452
					me.min = 1;
9453
					me.max = 10;
9454
				}
9455
			}
9456
		},
9457
		buildTicks: function() {
9458
			var me = this;
9459
			var opts = me.options;
9460
			var tickOpts = opts.ticks;
9461
			var getValueOrDefault = helpers.getValueOrDefault;
9462
9463
			// Reset the ticks array. Later on, we will draw a grid line at these positions
9464
			// The array simply contains the numerical value of the spots where ticks will be
9465
			var ticks = me.ticks = [];
9466
9467
			// Figure out what the max number of ticks we can support it is based on the size of
9468
			// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
9469
			// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
9470
			// the graph
9471
9472
			var tickVal = getValueOrDefault(tickOpts.min, Math.pow(10, Math.floor(helpers.log10(me.min))));
9473
9474
			while (tickVal < me.max) {
9475
				ticks.push(tickVal);
9476
9477
				var exp = Math.floor(helpers.log10(tickVal));
9478
				var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
9479
9480
				if (significand === 10) {
9481
					significand = 1;
9482
					++exp;
9483
				}
9484
9485
				tickVal = significand * Math.pow(10, exp);
9486
			}
9487
9488
			var lastTick = getValueOrDefault(tickOpts.max, tickVal);
9489
			ticks.push(lastTick);
9490
9491
			if (!me.isHorizontal()) {
9492
				// We are in a vertical orientation. The top value is the highest. So reverse the array
9493
				ticks.reverse();
9494
			}
9495
9496
			// At this point, we need to update our max and min given the tick values since we have expanded the
9497
			// range of the scale
9498
			me.max = helpers.max(ticks);
9499
			me.min = helpers.min(ticks);
9500
9501
			if (tickOpts.reverse) {
9502
				ticks.reverse();
9503
9504
				me.start = me.max;
9505
				me.end = me.min;
9506
			} else {
9507
				me.start = me.min;
9508
				me.end = me.max;
9509
			}
9510
		},
9511
		convertTicksToLabels: function() {
9512
			this.tickValues = this.ticks.slice();
9513
9514
			Chart.Scale.prototype.convertTicksToLabels.call(this);
9515
		},
9516
		// Get the correct tooltip label
9517
		getLabelForIndex: function(index, datasetIndex) {
9518
			return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
9519
		},
9520
		getPixelForTick: function(index) {
9521
			return this.getPixelForValue(this.tickValues[index]);
9522
		},
9523
		getPixelForValue: function(value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
9524
			var me = this;
9525
			var innerDimension;
9526
			var pixel;
9527
9528
			var start = me.start;
9529
			var newVal = +me.getRightValue(value);
9530
			var range = helpers.log10(me.end) - helpers.log10(start);
9531
			var paddingTop = me.paddingTop;
9532
			var paddingBottom = me.paddingBottom;
9533
			var paddingLeft = me.paddingLeft;
9534
9535
			if (me.isHorizontal()) {
9536
9537
				if (newVal === 0) {
9538
					pixel = me.left + paddingLeft;
9539
				} else {
9540
					innerDimension = me.width - (paddingLeft + me.paddingRight);
9541
					pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
9542
					pixel += paddingLeft;
9543
				}
9544
			} else {
9545
				// Bottom - top since pixels increase downard on a screen
9546
				if (newVal === 0) {
9547
					pixel = me.top + paddingTop;
9548
				} else {
9549
					innerDimension = me.height - (paddingTop + paddingBottom);
9550
					pixel = (me.bottom - paddingBottom) - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
9551
				}
9552
			}
9553
9554
			return pixel;
9555
		},
9556
		getValueForPixel: function(pixel) {
9557
			var me = this;
9558
			var range = helpers.log10(me.end) - helpers.log10(me.start);
9559
			var value, innerDimension;
9560
9561
			if (me.isHorizontal()) {
9562
				innerDimension = me.width - (me.paddingLeft + me.paddingRight);
9563
				value = me.start * Math.pow(10, (pixel - me.left - me.paddingLeft) * range / innerDimension);
9564
			} else {
9565
				innerDimension = me.height - (me.paddingTop + me.paddingBottom);
9566
				value = Math.pow(10, (me.bottom - me.paddingBottom - pixel) * range / innerDimension) / me.start;
9567
			}
9568
9569
			return value;
9570
		}
9571
	});
9572
	Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig);
9573
9574
};
9575
},{}],43:[function(require,module,exports){
9576
"use strict";
9577
9578
module.exports = function(Chart) {
9579
9580
	var helpers = Chart.helpers;
9581
	var globalDefaults = Chart.defaults.global;
9582
9583
	var defaultConfig = {
9584
		display: true,
9585
9586
		//Boolean - Whether to animate scaling the chart from the centre
9587
		animate: true,
9588
		lineArc: false,
9589
		position: "chartArea",
9590
9591
		angleLines: {
9592
			display: true,
9593
			color: "rgba(0, 0, 0, 0.1)",
9594
			lineWidth: 1
9595
		},
9596
9597
		// label settings
9598
		ticks: {
9599
			//Boolean - Show a backdrop to the scale label
9600
			showLabelBackdrop: true,
9601
9602
			//String - The colour of the label backdrop
9603
			backdropColor: "rgba(255,255,255,0.75)",
9604
9605
			//Number - The backdrop padding above & below the label in pixels
9606
			backdropPaddingY: 2,
9607
9608
			//Number - The backdrop padding to the side of the label in pixels
9609
			backdropPaddingX: 2
9610
		},
9611
9612
		pointLabels: {
9613
			//Number - Point label font size in pixels
9614
			fontSize: 10,
9615
9616
			//Function - Used to convert point labels
9617
			callback: function(label) {
9618
				return label;
9619
			}
9620
		}
9621
	};
9622
9623
	var LinearRadialScale = Chart.LinearScaleBase.extend({
9624
		getValueCount: function() {
9625
			return this.chart.data.labels.length;
9626
		},
9627
		setDimensions: function() {
9628
			var me = this;
9629
			var opts = me.options;
9630
			var tickOpts = opts.ticks;
9631
			// Set the unconstrained dimension before label rotation
9632
			me.width = me.maxWidth;
9633
			me.height = me.maxHeight;
9634
			me.xCenter = Math.round(me.width / 2);
9635
			me.yCenter = Math.round(me.height / 2);
9636
9637
			var minSize = helpers.min([me.height, me.width]);
9638
			var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
9639
			me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
9640
		},
9641
		determineDataLimits: function() {
9642
			var me = this;
9643
			var chart = me.chart;
9644
			me.min = null;
9645
			me.max = null;
9646
9647
9648
			helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
9649
				if (chart.isDatasetVisible(datasetIndex)) {
9650
					var meta = chart.getDatasetMeta(datasetIndex);
9651
9652
					helpers.each(dataset.data, function(rawValue, index) {
9653
						var value = +me.getRightValue(rawValue);
9654
						if (isNaN(value) || meta.data[index].hidden) {
9655
							return;
9656
						}
9657
9658
						if (me.min === null) {
9659
							me.min = value;
9660
						} else if (value < me.min) {
9661
							me.min = value;
9662
						}
9663
9664
						if (me.max === null) {
9665
							me.max = value;
9666
						} else if (value > me.max) {
9667
							me.max = value;
9668
						}
9669
					});
9670
				}
9671
			});
9672
9673
			// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
9674
			me.handleTickRangeOptions();
9675
		},
9676
		getTickLimit: function() {
9677
			var tickOpts = this.options.ticks;
9678
			var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
9679
			return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
9680
		},
9681
		convertTicksToLabels: function() {
9682
			var me = this;
9683
			Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
9684
9685
			// Point labels
9686
			me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
9687
		},
9688
		getLabelForIndex: function(index, datasetIndex) {
9689
			return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
9690
		},
9691
		fit: function() {
9692
			/*
9693
			 * Right, this is really confusing and there is a lot of maths going on here
9694
			 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
9695
			 *
9696
			 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
9697
			 *
9698
			 * Solution:
9699
			 *
9700
			 * We assume the radius of the polygon is half the size of the canvas at first
9701
			 * at each index we check if the text overlaps.
9702
			 *
9703
			 * Where it does, we store that angle and that index.
9704
			 *
9705
			 * After finding the largest index and angle we calculate how much we need to remove
9706
			 * from the shape radius to move the point inwards by that x.
9707
			 *
9708
			 * We average the left and right distances to get the maximum shape radius that can fit in the box
9709
			 * along with labels.
9710
			 *
9711
			 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
9712
			 * on each side, removing that from the size, halving it and adding the left x protrusion width.
9713
			 *
9714
			 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
9715
			 * and position it in the most space efficient manner
9716
			 *
9717
			 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
9718
			 */
9719
9720
			var pointLabels = this.options.pointLabels;
9721
			var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize);
9722
			var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle);
9723
			var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily);
9724
			var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
9725
9726
			// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
9727
			// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
9728
			var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]),
9729
				pointPosition,
9730
				i,
9731
				textWidth,
9732
				halfTextWidth,
9733
				furthestRight = this.width,
9734
				furthestRightIndex,
9735
				furthestRightAngle,
9736
				furthestLeft = 0,
9737
				furthestLeftIndex,
9738
				furthestLeftAngle,
9739
				xProtrusionLeft,
9740
				xProtrusionRight,
9741
				radiusReductionRight,
9742
				radiusReductionLeft;
9743
			this.ctx.font = pointLabeFont;
9744
9745
			for (i = 0; i < this.getValueCount(); i++) {
9746
				// 5px to space the text slightly out - similar to what we do in the draw function.
9747
				pointPosition = this.getPointPosition(i, largestPossibleRadius);
9748
				textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5;
9749
9750
				// Add quarter circle to make degree 0 mean top of circle
9751
				var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
9752
				var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
9753
9754
				if (angle === 0 || angle === 180) {
9755
					// At angle 0 and 180, we're at exactly the top/bottom
9756
					// of the radar chart, so text will be aligned centrally, so we'll half it and compare
9757
					// w/left and right text sizes
9758
					halfTextWidth = textWidth / 2;
9759
					if (pointPosition.x + halfTextWidth > furthestRight) {
9760
						furthestRight = pointPosition.x + halfTextWidth;
9761
						furthestRightIndex = i;
9762
					}
9763
					if (pointPosition.x - halfTextWidth < furthestLeft) {
9764
						furthestLeft = pointPosition.x - halfTextWidth;
9765
						furthestLeftIndex = i;
9766
					}
9767
				} else if (angle < 180) {
9768
					// Less than half the values means we'll left align the text
9769
					if (pointPosition.x + textWidth > furthestRight) {
9770
						furthestRight = pointPosition.x + textWidth;
9771
						furthestRightIndex = i;
9772
					}
9773
				} else {
9774
					// More than half the values means we'll right align the text
9775
					if (pointPosition.x - textWidth < furthestLeft) {
9776
						furthestLeft = pointPosition.x - textWidth;
9777
						furthestLeftIndex = i;
9778
					}
9779
				}
9780
			}
9781
9782
			xProtrusionLeft = furthestLeft;
9783
			xProtrusionRight = Math.ceil(furthestRight - this.width);
9784
9785
			furthestRightAngle = this.getIndexAngle(furthestRightIndex);
0 ignored issues
show
Bug introduced by
The variable furthestRightIndex seems to not be initialized for all possible execution paths. Are you sure getIndexAngle handles undefined variables?
Loading history...
9786
			furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
0 ignored issues
show
Bug introduced by
The variable furthestLeftIndex seems to not be initialized for all possible execution paths. Are you sure getIndexAngle handles undefined variables?
Loading history...
9787
9788
			radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
9789
			radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
9790
9791
			// Ensure we actually need to reduce the size of the chart
9792
			radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
9793
			radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
9794
9795
			this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
9796
			this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
9797
		},
9798
		setCenterPoint: function(leftMovement, rightMovement) {
9799
			var me = this;
9800
			var maxRight = me.width - rightMovement - me.drawingArea,
9801
				maxLeft = leftMovement + me.drawingArea;
9802
9803
			me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
9804
			// Always vertically in the centre as the text height doesn't change
9805
			me.yCenter = Math.round((me.height / 2) + me.top);
9806
		},
9807
9808
		getIndexAngle: function(index) {
9809
			var angleMultiplier = (Math.PI * 2) / this.getValueCount();
9810
			var startAngle = this.chart.options && this.chart.options.startAngle ?
9811
				this.chart.options.startAngle :
9812
				0;
9813
9814
			var startAngleRadians = startAngle * Math.PI * 2 / 360;
9815
9816
			// Start from the top instead of right, so remove a quarter of the circle
9817
			return index * angleMultiplier - (Math.PI / 2) + startAngleRadians;
9818
		},
9819
		getDistanceFromCenterForValue: function(value) {
9820
			var me = this;
9821
9822
			if (value === null) {
9823
				return 0; // null always in center
9824
			}
9825
9826
			// Take into account half font size + the yPadding of the top value
9827
			var scalingFactor = me.drawingArea / (me.max - me.min);
9828
			if (me.options.reverse) {
9829
				return (me.max - value) * scalingFactor;
9830
			} else {
9831
				return (value - me.min) * scalingFactor;
9832
			}
9833
		},
9834
		getPointPosition: function(index, distanceFromCenter) {
9835
			var me = this;
9836
			var thisAngle = me.getIndexAngle(index);
9837
			return {
9838
				x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
9839
				y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
9840
			};
9841
		},
9842
		getPointPositionForValue: function(index, value) {
9843
			return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
9844
		},
9845
9846
		getBasePosition: function() {
9847
			var me = this;
9848
			var min = me.min;
9849
			var max = me.max;
9850
9851
			return me.getPointPositionForValue(0,
9852
				me.beginAtZero? 0:
9853
				min < 0 && max < 0? max :
9854
				min > 0 && max > 0? min :
9855
				0);
9856
		},
9857
9858
		draw: function() {
9859
			var me = this;
9860
			var opts = me.options;
9861
			var gridLineOpts = opts.gridLines;
9862
			var tickOpts = opts.ticks;
9863
			var angleLineOpts = opts.angleLines;
9864
			var pointLabelOpts = opts.pointLabels;
9865
			var getValueOrDefault = helpers.getValueOrDefault;
9866
9867
			if (opts.display) {
9868
				var ctx = me.ctx;
9869
9870
				// Tick Font
9871
				var tickFontSize = getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
9872
				var tickFontStyle = getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
9873
				var tickFontFamily = getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
9874
				var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
9875
9876
				helpers.each(me.ticks, function(label, index) {
9877
					// Don't draw a centre value (if it is minimum)
9878
					if (index > 0 || opts.reverse) {
9879
						var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
9880
						var yHeight = me.yCenter - yCenterOffset;
9881
9882
						// Draw circular lines around the scale
9883
						if (gridLineOpts.display && index !== 0) {
9884
							ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1);
9885
							ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
9886
9887
							if (opts.lineArc) {
9888
								// Draw circular arcs between the points
9889
								ctx.beginPath();
9890
								ctx.arc(me.xCenter, me.yCenter, yCenterOffset, 0, Math.PI * 2);
9891
								ctx.closePath();
9892
								ctx.stroke();
9893
							} else {
9894
								// Draw straight lines connecting each index
9895
								ctx.beginPath();
9896
								for (var i = 0; i < me.getValueCount(); i++) {
9897
									var pointPosition = me.getPointPosition(i, yCenterOffset);
9898
									if (i === 0) {
9899
										ctx.moveTo(pointPosition.x, pointPosition.y);
9900
									} else {
9901
										ctx.lineTo(pointPosition.x, pointPosition.y);
9902
									}
9903
								}
9904
								ctx.closePath();
9905
								ctx.stroke();
9906
							}
9907
						}
9908
9909
						if (tickOpts.display) {
9910
							var tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
9911
							ctx.font = tickLabelFont;
9912
9913
							if (tickOpts.showLabelBackdrop) {
9914
								var labelWidth = ctx.measureText(label).width;
9915
								ctx.fillStyle = tickOpts.backdropColor;
9916
								ctx.fillRect(
9917
									me.xCenter - labelWidth / 2 - tickOpts.backdropPaddingX,
9918
									yHeight - tickFontSize / 2 - tickOpts.backdropPaddingY,
9919
									labelWidth + tickOpts.backdropPaddingX * 2,
9920
									tickFontSize + tickOpts.backdropPaddingY * 2
9921
								);
9922
							}
9923
9924
							ctx.textAlign = 'center';
9925
							ctx.textBaseline = "middle";
9926
							ctx.fillStyle = tickFontColor;
9927
							ctx.fillText(label, me.xCenter, yHeight);
9928
						}
9929
					}
9930
				});
9931
9932
				if (!opts.lineArc) {
9933
					ctx.lineWidth = angleLineOpts.lineWidth;
9934
					ctx.strokeStyle = angleLineOpts.color;
9935
9936
					var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max);
9937
9938
					// Point Label Font
9939
					var pointLabelFontSize = getValueOrDefault(pointLabelOpts.fontSize, globalDefaults.defaultFontSize);
9940
					var pointLabeFontStyle = getValueOrDefault(pointLabelOpts.fontStyle, globalDefaults.defaultFontStyle);
9941
					var pointLabeFontFamily = getValueOrDefault(pointLabelOpts.fontFamily, globalDefaults.defaultFontFamily);
9942
					var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
9943
9944
					for (var i = me.getValueCount() - 1; i >= 0; i--) {
9945
						if (angleLineOpts.display) {
9946
							var outerPosition = me.getPointPosition(i, outerDistance);
9947
							ctx.beginPath();
9948
							ctx.moveTo(me.xCenter, me.yCenter);
9949
							ctx.lineTo(outerPosition.x, outerPosition.y);
9950
							ctx.stroke();
9951
							ctx.closePath();
9952
						}
9953
						// Extra 3px out for some label spacing
9954
						var pointLabelPosition = me.getPointPosition(i, outerDistance + 5);
9955
9956
						// Keep this in loop since we may support array properties here
9957
						var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
9958
						ctx.font = pointLabeFont;
9959
						ctx.fillStyle = pointLabelFontColor;
9960
9961
						var pointLabels = me.pointLabels;
9962
9963
						// Add quarter circle to make degree 0 mean top of circle
9964
						var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
9965
						var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
9966
9967
						if (angle === 0 || angle === 180) {
9968
							ctx.textAlign = 'center';
9969
						} else if (angle < 180) {
9970
							ctx.textAlign = 'left';
9971
						} else {
9972
							ctx.textAlign = 'right';
9973
						}
9974
9975
						// Set the correct text baseline based on outer positioning
9976
						if (angle === 90 || angle === 270) {
9977
							ctx.textBaseline = 'middle';
9978
						} else if (angle > 270 || angle < 90) {
9979
							ctx.textBaseline = 'bottom';
9980
						} else {
9981
							ctx.textBaseline = 'top';
9982
						}
9983
9984
						ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y);
9985
					}
9986
				}
9987
			}
9988
		}
9989
	});
9990
	Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig);
9991
9992
};
9993
9994
},{}],44:[function(require,module,exports){
9995
/*global window: false */
9996
"use strict";
9997
9998
var moment = require(1);
9999
moment = typeof(moment) === 'function' ? moment : window.moment;
10000
10001
module.exports = function(Chart) {
10002
10003
	var helpers = Chart.helpers;
10004
	var time = {
10005
		units: [{
10006
			name: 'millisecond',
10007
			steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
10008
		}, {
10009
			name: 'second',
10010
			steps: [1, 2, 5, 10, 30]
10011
		}, {
10012
			name: 'minute',
10013
			steps: [1, 2, 5, 10, 30]
10014
		}, {
10015
			name: 'hour',
10016
			steps: [1, 2, 3, 6, 12]
10017
		}, {
10018
			name: 'day',
10019
			steps: [1, 2, 5]
10020
		}, {
10021
			name: 'week',
10022
			maxStep: 4
10023
		}, {
10024
			name: 'month',
10025
			maxStep: 3
10026
		}, {
10027
			name: 'quarter',
10028
			maxStep: 4
10029
		}, {
10030
			name: 'year',
10031
			maxStep: false
10032
		}]
10033
	};
10034
10035
	var defaultConfig = {
10036
		position: "bottom",
10037
10038
		time: {
10039
			parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
10040
			format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
10041
			unit: false, // false == automatic or override with week, month, year, etc.
10042
			round: false, // none, or override with week, month, year, etc.
10043
			displayFormat: false, // DEPRECATED
10044
			isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
10045
10046
			// defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
10047
			displayFormats: {
10048
				'millisecond': 'h:mm:ss.SSS a', // 11:20:01.123 AM,
10049
				'second': 'h:mm:ss a', // 11:20:01 AM
10050
				'minute': 'h:mm:ss a', // 11:20:01 AM
10051
				'hour': 'MMM D, hA', // Sept 4, 5PM
10052
				'day': 'll', // Sep 4 2015
10053
				'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ?
10054
				'month': 'MMM YYYY', // Sept 2015
10055
				'quarter': '[Q]Q - YYYY', // Q3
10056
				'year': 'YYYY' // 2015
10057
			}
10058
		},
10059
		ticks: {
10060
			autoSkip: false
10061
		}
10062
	};
10063
10064
	var TimeScale = Chart.Scale.extend({
10065
		initialize: function() {
10066
			if (!moment) {
10067
				throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
10068
			}
10069
10070
			Chart.Scale.prototype.initialize.call(this);
10071
		},
10072
		getLabelMoment: function(datasetIndex, index) {
10073
			if (typeof this.labelMoments[datasetIndex] != 'undefined') {
10074
				return this.labelMoments[datasetIndex][index];
10075
			}
10076
10077
			return null;
10078
		},
10079
		getMomentStartOf: function(tick) {
10080
			var me = this;
10081
			if (me.options.time.unit === 'week' && me.options.time.isoWeekday !== false) {
10082
				return tick.clone().startOf('isoWeek').isoWeekday(me.options.time.isoWeekday);
10083
			} else {
10084
				return tick.clone().startOf(me.tickUnit);
10085
			}
10086
		},
10087
		determineDataLimits: function() {
10088
			var me = this;
10089
			me.labelMoments = [];
10090
10091
			// Only parse these once. If the dataset does not have data as x,y pairs, we will use
10092
			// these
10093
			var scaleLabelMoments = [];
10094
			if (me.chart.data.labels && me.chart.data.labels.length > 0) {
10095
				helpers.each(me.chart.data.labels, function(label) {
10096
					var labelMoment = me.parseTime(label);
10097
10098
					if (labelMoment.isValid()) {
10099
						if (me.options.time.round) {
10100
							labelMoment.startOf(me.options.time.round);
10101
						}
10102
						scaleLabelMoments.push(labelMoment);
10103
					}
10104
				}, me);
10105
10106
				me.firstTick = moment.min.call(me, scaleLabelMoments);
10107
				me.lastTick = moment.max.call(me, scaleLabelMoments);
10108
			} else {
10109
				me.firstTick = null;
10110
				me.lastTick = null;
10111
			}
10112
10113
			helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
10114
				var momentsForDataset = [];
10115
				var datasetVisible = me.chart.isDatasetVisible(datasetIndex);
10116
10117
				if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) {
10118
					helpers.each(dataset.data, function(value) {
10119
						var labelMoment = me.parseTime(me.getRightValue(value));
10120
10121
						if (labelMoment.isValid()) {
10122
							if (me.options.time.round) {
10123
								labelMoment.startOf(me.options.time.round);
10124
							}
10125
							momentsForDataset.push(labelMoment);
10126
10127
							if (datasetVisible) {
10128
								// May have gone outside the scale ranges, make sure we keep the first and last ticks updated
10129
								me.firstTick = me.firstTick !== null ? moment.min(me.firstTick, labelMoment) : labelMoment;
10130
								me.lastTick = me.lastTick !== null ? moment.max(me.lastTick, labelMoment) : labelMoment;
10131
							}
10132
						}
10133
					}, me);
10134
				} else {
10135
					// We have no labels. Use the ones from the scale
10136
					momentsForDataset = scaleLabelMoments;
10137
				}
10138
10139
				me.labelMoments.push(momentsForDataset);
10140
			}, me);
10141
10142
			// Set these after we've done all the data
10143
			if (me.options.time.min) {
10144
				me.firstTick = me.parseTime(me.options.time.min);
10145
			}
10146
10147
			if (me.options.time.max) {
10148
				me.lastTick = me.parseTime(me.options.time.max);
10149
			}
10150
10151
			// We will modify these, so clone for later
10152
			me.firstTick = (me.firstTick || moment()).clone();
10153
			me.lastTick = (me.lastTick || moment()).clone();
10154
		},
10155
		buildTicks: function() {
10156
			var me = this;
10157
10158
			me.ctx.save();
10159
			var tickFontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
10160
			var tickFontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
10161
			var tickFontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
10162
			var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
10163
			me.ctx.font = tickLabelFont;
10164
10165
			me.ticks = [];
10166
			me.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step
10167
			me.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc)
10168
10169
			// Set unit override if applicable
10170
			if (me.options.time.unit) {
10171
				me.tickUnit = me.options.time.unit || 'day';
10172
				me.displayFormat = me.options.time.displayFormats[me.tickUnit];
10173
				me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
10174
				me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, 1);
10175
			} else {
10176
				// Determine the smallest needed unit of the time
10177
				var innerWidth = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
10178
10179
				// Crude approximation of what the label length might be
10180
				var tempFirstLabel = me.tickFormatFunction(me.firstTick, 0, []);
10181
				var tickLabelWidth = me.ctx.measureText(tempFirstLabel).width;
10182
				var cosRotation = Math.cos(helpers.toRadians(me.options.ticks.maxRotation));
10183
				var sinRotation = Math.sin(helpers.toRadians(me.options.ticks.maxRotation));
10184
				tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
10185
				var labelCapacity = innerWidth / (tickLabelWidth);
10186
10187
				// Start as small as possible
10188
				me.tickUnit = 'millisecond';
10189
				me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
10190
				me.displayFormat = me.options.time.displayFormats[me.tickUnit];
10191
10192
				var unitDefinitionIndex = 0;
10193
				var unitDefinition = time.units[unitDefinitionIndex];
10194
10195
				// While we aren't ideal and we don't have units left
10196
				while (unitDefinitionIndex < time.units.length) {
10197
					// Can we scale this unit. If `false` we can scale infinitely
10198
					me.unitScale = 1;
10199
10200
					if (helpers.isArray(unitDefinition.steps) && Math.ceil(me.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) {
10201
						// Use one of the prefedined steps
10202
						for (var idx = 0; idx < unitDefinition.steps.length; ++idx) {
10203
							if (unitDefinition.steps[idx] >= Math.ceil(me.scaleSizeInUnits / labelCapacity)) {
10204
								me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, unitDefinition.steps[idx]);
10205
								break;
10206
							}
10207
						}
10208
10209
						break;
10210
					} else if ((unitDefinition.maxStep === false) || (Math.ceil(me.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) {
10211
						// We have a max step. Scale this unit
10212
						me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, Math.ceil(me.scaleSizeInUnits / labelCapacity));
10213
						break;
10214
					} else {
10215
						// Move to the next unit up
10216
						++unitDefinitionIndex;
10217
						unitDefinition = time.units[unitDefinitionIndex];
10218
10219
						me.tickUnit = unitDefinition.name;
10220
						var leadingUnitBuffer = me.firstTick.diff(me.getMomentStartOf(me.firstTick), me.tickUnit, true);
10221
						var trailingUnitBuffer = me.getMomentStartOf(me.lastTick.clone().add(1, me.tickUnit)).diff(me.lastTick, me.tickUnit, true);
10222
						me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true) + leadingUnitBuffer + trailingUnitBuffer;
10223
						me.displayFormat = me.options.time.displayFormats[unitDefinition.name];
10224
					}
10225
				}
10226
			}
10227
10228
			var roundedStart;
10229
10230
			// Only round the first tick if we have no hard minimum
10231
			if (!me.options.time.min) {
10232
				me.firstTick = me.getMomentStartOf(me.firstTick);
10233
				roundedStart = me.firstTick;
10234
			} else {
10235
				roundedStart = me.getMomentStartOf(me.firstTick);
10236
			}
10237
10238
			// Only round the last tick if we have no hard maximum
10239
			if (!me.options.time.max) {
10240
				var roundedEnd = me.getMomentStartOf(me.lastTick);
10241
				var delta = roundedEnd.diff(me.lastTick, me.tickUnit, true);
10242
				if (delta < 0) {
10243
					// Do not use end of because we need me to be in the next time unit
10244
					me.lastTick = me.getMomentStartOf(me.lastTick.add(1, me.tickUnit));
10245
				} else if (delta >= 0) {
10246
					me.lastTick = roundedEnd;
10247
				}
10248
10249
				me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
10250
			}
10251
10252
			me.smallestLabelSeparation = me.width;
10253
10254
			helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
10255
				for (var i = 1; i < me.labelMoments[datasetIndex].length; i++) {
10256
					me.smallestLabelSeparation = Math.min(me.smallestLabelSeparation, me.labelMoments[datasetIndex][i].diff(me.labelMoments[datasetIndex][i - 1], me.tickUnit, true));
10257
				}
10258
			}, me);
10259
10260
			// Tick displayFormat override
10261
			if (me.options.time.displayFormat) {
10262
				me.displayFormat = me.options.time.displayFormat;
10263
			}
10264
10265
			// first tick. will have been rounded correctly if options.time.min is not specified
10266
			me.ticks.push(me.firstTick.clone());
10267
10268
			// For every unit in between the first and last moment, create a moment and add it to the ticks tick
10269
			for (var i = 1; i <= me.scaleSizeInUnits; ++i) {
10270
				var newTick = roundedStart.clone().add(i, me.tickUnit);
10271
10272
				// Are we greater than the max time
10273
				if (me.options.time.max && newTick.diff(me.lastTick, me.tickUnit, true) >= 0) {
10274
					break;
10275
				}
10276
10277
				if (i % me.unitScale === 0) {
10278
					me.ticks.push(newTick);
10279
				}
10280
			}
10281
10282
			// Always show the right tick
10283
			var diff = me.ticks[me.ticks.length - 1].diff(me.lastTick, me.tickUnit);
10284
			if (diff !== 0 || me.scaleSizeInUnits === 0) {
10285
				// this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart
10286
				// but the last tick was not rounded.
10287
				if (me.options.time.max) {
10288
					me.ticks.push(me.lastTick.clone());
10289
					me.scaleSizeInUnits = me.lastTick.diff(me.ticks[0], me.tickUnit, true);
10290
				} else {
10291
					me.ticks.push(me.lastTick.clone());
10292
					me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
10293
				}
10294
			}
10295
10296
			me.ctx.restore();
10297
		},
10298
		// Get tooltip label
10299
		getLabelForIndex: function(index, datasetIndex) {
10300
			var me = this;
10301
			var label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : '';
10302
10303
			if (typeof me.chart.data.datasets[datasetIndex].data[0] === 'object') {
10304
				label = me.getRightValue(me.chart.data.datasets[datasetIndex].data[index]);
10305
			}
10306
10307
			// Format nicely
10308
			if (me.options.time.tooltipFormat) {
10309
				label = me.parseTime(label).format(me.options.time.tooltipFormat);
10310
			}
10311
10312
			return label;
10313
		},
10314
		// Function to format an individual tick mark
10315
		tickFormatFunction: function(tick, index, ticks) {
10316
			var formattedTick = tick.format(this.displayFormat);
10317
			var tickOpts = this.options.ticks;
10318
			var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback);
10319
10320
			if (callback) {
10321
				return callback(formattedTick, index, ticks);
10322
			} else {
10323
				return formattedTick;
10324
			}
10325
		},
10326
		convertTicksToLabels: function() {
10327
			var me = this;
10328
			me.tickMoments = me.ticks;
10329
			me.ticks = me.ticks.map(me.tickFormatFunction, me);
10330
		},
10331
		getPixelForValue: function(value, index, datasetIndex) {
10332
			var me = this;
10333
			if (!value || !value.isValid) {
10334
				// not already a moment object
10335
				value = moment(me.getRightValue(value));
10336
			}
10337
			var labelMoment = value && value.isValid && value.isValid() ? value : me.getLabelMoment(datasetIndex, index);
10338
10339
			if (labelMoment) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if labelMoment is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
10340
				var offset = labelMoment.diff(me.firstTick, me.tickUnit, true);
10341
10342
				var decimal = offset !== 0 ? offset / me.scaleSizeInUnits : offset;
10343
10344
				if (me.isHorizontal()) {
10345
					var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
10346
					var valueOffset = (innerWidth * decimal) + me.paddingLeft;
10347
10348
					return me.left + Math.round(valueOffset);
10349
				} else {
10350
					var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
10351
					var heightOffset = (innerHeight * decimal) + me.paddingTop;
10352
10353
					return me.top + Math.round(heightOffset);
10354
				}
10355
			}
10356
		},
10357
		getPixelForTick: function(index) {
10358
			return this.getPixelForValue(this.tickMoments[index], null, null);
10359
		},
10360
		getValueForPixel: function(pixel) {
10361
			var me = this;
10362
			var innerDimension = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
10363
			var offset = (pixel - (me.isHorizontal() ? me.left + me.paddingLeft : me.top + me.paddingTop)) / innerDimension;
10364
			offset *= me.scaleSizeInUnits;
10365
			return me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds');
10366
		},
10367
		parseTime: function(label) {
10368
			var me = this;
10369
			if (typeof me.options.time.parser === 'string') {
10370
				return moment(label, me.options.time.parser);
10371
			}
10372
			if (typeof me.options.time.parser === 'function') {
10373
				return me.options.time.parser(label);
10374
			}
10375
			// Date objects
10376
			if (typeof label.getMonth === 'function' || typeof label === 'number') {
10377
				return moment(label);
10378
			}
10379
			// Moment support
10380
			if (label.isValid && label.isValid()) {
10381
				return label;
10382
			}
10383
			// Custom parsing (return an instance of moment)
10384
			if (typeof me.options.time.format !== 'string' && me.options.time.format.call) {
10385
				console.warn("options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale");
10386
				return me.options.time.format(label);
10387
			}
10388
			// Moment format parsing
10389
			return moment(label, me.options.time.format);
10390
		}
10391
	});
10392
	Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig);
10393
10394
};
10395
10396
},{"1":1}]},{},[7])(7)
10397
});