| Total Complexity | 2177 | 
| Complexity/F | 2.39 | 
| Lines of Code | 14447 | 
| Function Count | 909 | 
| Duplicated Lines | 1764 | 
| Ratio | 12.21 % | 
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like assets/mdb-dashboard/js/modules/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 | /*! | ||
| 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(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | ||
| 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})$/i, | ||
| 40 |        hex =  /^#([a-fA-F0-9]{6})$/i, | ||
| 41 | rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, | ||
| 42 | per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, | ||
| 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++) { | ||
| 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++) { | ||
| 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++) { | ||
| 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++) { | ||
| 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) { | ||
| 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) { | ||
| 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)) { | ||
| 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) { | ||
| 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.valid = false; | ||
| 249 | 	this.values = { | ||
| 250 | rgb: [0, 0, 0], | ||
| 251 | hsl: [0, 0, 0], | ||
| 252 | hsv: [0, 0, 0], | ||
| 253 | hwb: [0, 0, 0], | ||
| 254 | cmyk: [0, 0, 0, 0], | ||
| 255 | alpha: 1 | ||
| 256 | }; | ||
| 257 | |||
| 258 | // parse Color() argument | ||
| 259 | var vals; | ||
| 260 | 	if (typeof obj === 'string') { | ||
| 261 | vals = string.getRgba(obj); | ||
| 262 | 		if (vals) { | ||
| 263 | 			this.setValues('rgb', vals); | ||
| 264 | 		} else if (vals = string.getHsla(obj)) { | ||
| 265 | 			this.setValues('hsl', vals); | ||
| 266 | 		} else if (vals = string.getHwb(obj)) { | ||
| 267 | 			this.setValues('hwb', vals); | ||
| 268 | } | ||
| 269 | 	} else if (typeof obj === 'object') { | ||
| 270 | vals = obj; | ||
| 271 | 		if (vals.r !== undefined || vals.red !== undefined) { | ||
| 272 | 			this.setValues('rgb', vals); | ||
| 273 | 		} else if (vals.l !== undefined || vals.lightness !== undefined) { | ||
| 274 | 			this.setValues('hsl', vals); | ||
| 275 | 		} else if (vals.v !== undefined || vals.value !== undefined) { | ||
| 276 | 			this.setValues('hsv', vals); | ||
| 277 | 		} else if (vals.w !== undefined || vals.whiteness !== undefined) { | ||
| 278 | 			this.setValues('hwb', vals); | ||
| 279 | 		} else if (vals.c !== undefined || vals.cyan !== undefined) { | ||
| 280 | 			this.setValues('cmyk', vals); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | }; | ||
| 284 | |||
| 285 | Color.prototype = { | ||
| 286 | 	isValid: function () { | ||
| 287 | return this.valid; | ||
| 288 | }, | ||
| 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 | this.valid = true; | ||
| 633 | |||
| 634 | 	if (space === 'alpha') { | ||
| 635 | alpha = vals; | ||
| 636 | 	} else if (vals.length) { | ||
| 637 | // [10, 10, 10] | ||
| 638 | values[space] = vals.slice(0, space.length); | ||
| 639 | alpha = vals[space.length]; | ||
| 640 | 	} else if (vals[space.charAt(0)] !== undefined) { | ||
| 641 | 		// {r: 10, g: 10, b: 10} | ||
| 642 | 		for (i = 0; i < space.length; i++) { | ||
| 643 | values[space][i] = vals[space.charAt(i)]; | ||
| 644 | } | ||
| 645 | |||
| 646 | alpha = vals.a; | ||
| 647 | 	} else if (vals[spaces[space][0]] !== undefined) { | ||
| 648 | 		// {red: 10, green: 10, blue: 10} | ||
| 649 | var chans = spaces[space]; | ||
| 650 | |||
| 651 | 		for (i = 0; i < space.length; i++) { | ||
| 652 | values[space][i] = vals[chans[i]]; | ||
| 653 | } | ||
| 654 | |||
| 655 | alpha = vals.alpha; | ||
| 656 | } | ||
| 657 | |||
| 658 | values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); | ||
| 659 | |||
| 660 | 	if (space === 'alpha') { | ||
| 661 | return false; | ||
| 662 | } | ||
| 663 | |||
| 664 | var capped; | ||
| 665 | |||
| 666 | // cap values of the space prior converting all values | ||
| 667 | 	for (i = 0; i < space.length; i++) { | ||
| 668 | capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); | ||
| 669 | values[space][i] = Math.round(capped); | ||
| 670 | } | ||
| 671 | |||
| 672 | // convert to all the other color spaces | ||
| 673 | 	for (var sname in spaces) { | ||
| 674 | 		if (sname !== space) { | ||
| 675 | values[sname] = convert[space][sname](values[space]); | ||
| 676 | } | ||
| 677 | } | ||
| 678 | |||
| 679 | return true; | ||
| 680 | }; | ||
| 681 | |||
| 682 | Color.prototype.setSpace = function (space, args) { | ||
| 683 | var vals = args[0]; | ||
| 684 | |||
| 685 | 	if (vals === undefined) { | ||
| 686 | // color.rgb() | ||
| 687 | return this.getValues(space); | ||
| 688 | } | ||
| 689 | |||
| 690 | // color.rgb(10, 10, 10) | ||
| 691 | 	if (typeof vals === 'number') { | ||
| 692 | vals = Array.prototype.slice.call(args); | ||
| 693 | } | ||
| 694 | |||
| 695 | this.setValues(space, vals); | ||
| 696 | return this; | ||
| 697 | }; | ||
| 698 | |||
| 699 | Color.prototype.setChannel = function (space, index, val) { | ||
| 700 | var svalues = this.values[space]; | ||
| 701 | 	if (val === undefined) { | ||
| 702 | // color.red() | ||
| 703 | return svalues[index]; | ||
| 704 | 	} else if (val === svalues[index]) { | ||
| 705 | // color.red(color.red()) | ||
| 706 | return this; | ||
| 707 | } | ||
| 708 | |||
| 709 | // color.red(100) | ||
| 710 | svalues[index] = val; | ||
| 711 | this.setValues(space, svalues); | ||
| 712 | |||
| 713 | return this; | ||
| 714 | }; | ||
| 715 | |||
| 716 | if (typeof window !== 'undefined') { | ||
| 717 | window.Color = Color; | ||
| 718 | } | ||
| 719 | |||
| 720 | module.exports = Color; | ||
| 721 | |||
| 722 | },{"2":2,"5":5}],4:[function(require,module,exports){ | ||
| 723 | /* MIT license */ | ||
| 724 | |||
| 725 | module.exports = { | ||
| 726 | rgb2hsl: rgb2hsl, | ||
| 727 | rgb2hsv: rgb2hsv, | ||
| 728 | rgb2hwb: rgb2hwb, | ||
| 729 | rgb2cmyk: rgb2cmyk, | ||
| 730 | rgb2keyword: rgb2keyword, | ||
| 731 | rgb2xyz: rgb2xyz, | ||
| 732 | rgb2lab: rgb2lab, | ||
| 733 | rgb2lch: rgb2lch, | ||
| 734 | |||
| 735 | hsl2rgb: hsl2rgb, | ||
| 736 | hsl2hsv: hsl2hsv, | ||
| 737 | hsl2hwb: hsl2hwb, | ||
| 738 | hsl2cmyk: hsl2cmyk, | ||
| 739 | hsl2keyword: hsl2keyword, | ||
| 740 | |||
| 741 | hsv2rgb: hsv2rgb, | ||
| 742 | hsv2hsl: hsv2hsl, | ||
| 743 | hsv2hwb: hsv2hwb, | ||
| 744 | hsv2cmyk: hsv2cmyk, | ||
| 745 | hsv2keyword: hsv2keyword, | ||
| 746 | |||
| 747 | hwb2rgb: hwb2rgb, | ||
| 748 | hwb2hsl: hwb2hsl, | ||
| 749 | hwb2hsv: hwb2hsv, | ||
| 750 | hwb2cmyk: hwb2cmyk, | ||
| 751 | hwb2keyword: hwb2keyword, | ||
| 752 | |||
| 753 | cmyk2rgb: cmyk2rgb, | ||
| 754 | cmyk2hsl: cmyk2hsl, | ||
| 755 | cmyk2hsv: cmyk2hsv, | ||
| 756 | cmyk2hwb: cmyk2hwb, | ||
| 757 | cmyk2keyword: cmyk2keyword, | ||
| 758 | |||
| 759 | keyword2rgb: keyword2rgb, | ||
| 760 | keyword2hsl: keyword2hsl, | ||
| 761 | keyword2hsv: keyword2hsv, | ||
| 762 | keyword2hwb: keyword2hwb, | ||
| 763 | keyword2cmyk: keyword2cmyk, | ||
| 764 | keyword2lab: keyword2lab, | ||
| 765 | keyword2xyz: keyword2xyz, | ||
| 766 | |||
| 767 | xyz2rgb: xyz2rgb, | ||
| 768 | xyz2lab: xyz2lab, | ||
| 769 | xyz2lch: xyz2lch, | ||
| 770 | |||
| 771 | lab2xyz: lab2xyz, | ||
| 772 | lab2rgb: lab2rgb, | ||
| 773 | lab2lch: lab2lch, | ||
| 774 | |||
| 775 | lch2lab: lch2lab, | ||
| 776 | lch2xyz: lch2xyz, | ||
| 777 | lch2rgb: lch2rgb | ||
| 778 | } | ||
| 779 | |||
| 780 | |||
| 781 | function rgb2hsl(rgb) { | ||
| 782 | var r = rgb[0]/255, | ||
| 783 | g = rgb[1]/255, | ||
| 784 | b = rgb[2]/255, | ||
| 785 | min = Math.min(r, g, b), | ||
| 786 | max = Math.max(r, g, b), | ||
| 787 | delta = max - min, | ||
| 788 | h, s, l; | ||
| 789 | |||
| 790 | if (max == min) | ||
| 791 | h = 0; | ||
| 792 | else if (r == max) | ||
| 793 | h = (g - b) / delta; | ||
| 794 | else if (g == max) | ||
| 795 | h = 2 + (b - r) / delta; | ||
| 796 | else if (b == max) | ||
| 797 | h = 4 + (r - g)/ delta; | ||
| 798 | |||
| 799 | h = Math.min(h * 60, 360); | ||
| 800 | |||
| 801 | if (h < 0) | ||
| 802 | h += 360; | ||
| 803 | |||
| 804 | l = (min + max) / 2; | ||
| 805 | |||
| 806 | if (max == min) | ||
| 807 | s = 0; | ||
| 808 | else if (l <= 0.5) | ||
| 809 | s = delta / (max + min); | ||
| 810 | else | ||
| 811 | s = delta / (2 - max - min); | ||
| 812 | |||
| 813 | return [h, s * 100, l * 100]; | ||
| 814 | } | ||
| 815 | |||
| 816 | function rgb2hsv(rgb) { | ||
| 817 | var r = rgb[0], | ||
| 818 | g = rgb[1], | ||
| 819 | b = rgb[2], | ||
| 820 | min = Math.min(r, g, b), | ||
| 821 | max = Math.max(r, g, b), | ||
| 822 | delta = max - min, | ||
| 823 | h, s, v; | ||
| 824 | |||
| 825 | if (max == 0) | ||
| 826 | s = 0; | ||
| 827 | else | ||
| 828 | s = (delta/max * 1000)/10; | ||
| 829 | |||
| 830 | if (max == min) | ||
| 831 | h = 0; | ||
| 832 | else if (r == max) | ||
| 833 | h = (g - b) / delta; | ||
| 834 | else if (g == max) | ||
| 835 | h = 2 + (b - r) / delta; | ||
| 836 | else if (b == max) | ||
| 837 | h = 4 + (r - g) / delta; | ||
| 838 | |||
| 839 | h = Math.min(h * 60, 360); | ||
| 840 | |||
| 841 | if (h < 0) | ||
| 842 | h += 360; | ||
| 843 | |||
| 844 | v = ((max / 255) * 1000) / 10; | ||
| 845 | |||
| 846 | return [h, s, v]; | ||
| 847 | } | ||
| 848 | |||
| 849 | function rgb2hwb(rgb) { | ||
| 850 | var r = rgb[0], | ||
| 851 | g = rgb[1], | ||
| 852 | b = rgb[2], | ||
| 853 | h = rgb2hsl(rgb)[0], | ||
| 854 | w = 1/255 * Math.min(r, Math.min(g, b)), | ||
| 855 | b = 1 - 1/255 * Math.max(r, Math.max(g, b)); | ||
| 856 | |||
| 857 | return [h, w * 100, b * 100]; | ||
| 858 | } | ||
| 859 | |||
| 860 | function rgb2cmyk(rgb) { | ||
| 861 | var r = rgb[0] / 255, | ||
| 862 | g = rgb[1] / 255, | ||
| 863 | b = rgb[2] / 255, | ||
| 864 | c, m, y, k; | ||
| 865 | |||
| 866 | k = Math.min(1 - r, 1 - g, 1 - b); | ||
| 867 | c = (1 - r - k) / (1 - k) || 0; | ||
| 868 | m = (1 - g - k) / (1 - k) || 0; | ||
| 869 | y = (1 - b - k) / (1 - k) || 0; | ||
| 870 | return [c * 100, m * 100, y * 100, k * 100]; | ||
| 871 | } | ||
| 872 | |||
| 873 | function rgb2keyword(rgb) { | ||
| 874 | return reverseKeywords[JSON.stringify(rgb)]; | ||
| 875 | } | ||
| 876 | |||
| 877 | function rgb2xyz(rgb) { | ||
| 878 | var r = rgb[0] / 255, | ||
| 879 | g = rgb[1] / 255, | ||
| 880 | b = rgb[2] / 255; | ||
| 881 | |||
| 882 | // assume sRGB | ||
| 883 | r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); | ||
| 884 | g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); | ||
| 885 | b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); | ||
| 886 | |||
| 887 | var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); | ||
| 888 | var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); | ||
| 889 | var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); | ||
| 890 | |||
| 891 | return [x * 100, y *100, z * 100]; | ||
| 892 | } | ||
| 893 | |||
| 894 | function rgb2lab(rgb) { | ||
| 895 | var xyz = rgb2xyz(rgb), | ||
| 896 | x = xyz[0], | ||
| 897 | y = xyz[1], | ||
| 898 | z = xyz[2], | ||
| 899 | l, a, b; | ||
| 900 | |||
| 901 | x /= 95.047; | ||
| 902 | y /= 100; | ||
| 903 | z /= 108.883; | ||
| 904 | |||
| 905 | x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); | ||
| 906 | y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); | ||
| 907 | z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); | ||
| 908 | |||
| 909 | l = (116 * y) - 16; | ||
| 910 | a = 500 * (x - y); | ||
| 911 | b = 200 * (y - z); | ||
| 912 | |||
| 913 | return [l, a, b]; | ||
| 914 | } | ||
| 915 | |||
| 916 | function rgb2lch(args) { | ||
| 917 | return lab2lch(rgb2lab(args)); | ||
| 918 | } | ||
| 919 | |||
| 920 | function hsl2rgb(hsl) { | ||
| 921 | var h = hsl[0] / 360, | ||
| 922 | s = hsl[1] / 100, | ||
| 923 | l = hsl[2] / 100, | ||
| 924 | t1, t2, t3, rgb, val; | ||
| 925 | |||
| 926 |   if (s == 0) { | ||
| 927 | val = l * 255; | ||
| 928 | return [val, val, val]; | ||
| 929 | } | ||
| 930 | |||
| 931 | if (l < 0.5) | ||
| 932 | t2 = l * (1 + s); | ||
| 933 | else | ||
| 934 | t2 = l + s - l * s; | ||
| 935 | t1 = 2 * l - t2; | ||
| 936 | |||
| 937 | rgb = [0, 0, 0]; | ||
| 938 |   for (var i = 0; i < 3; i++) { | ||
| 939 | t3 = h + 1 / 3 * - (i - 1); | ||
| 940 | t3 < 0 && t3++; | ||
| 941 | t3 > 1 && t3--; | ||
| 942 | |||
| 943 | if (6 * t3 < 1) | ||
| 944 | val = t1 + (t2 - t1) * 6 * t3; | ||
| 945 | else if (2 * t3 < 1) | ||
| 946 | val = t2; | ||
| 947 | else if (3 * t3 < 2) | ||
| 948 | val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; | ||
| 949 | else | ||
| 950 | val = t1; | ||
| 951 | |||
| 952 | rgb[i] = val * 255; | ||
| 953 | } | ||
| 954 | |||
| 955 | return rgb; | ||
| 956 | } | ||
| 957 | |||
| 958 | function hsl2hsv(hsl) { | ||
| 959 | var h = hsl[0], | ||
| 960 | s = hsl[1] / 100, | ||
| 961 | l = hsl[2] / 100, | ||
| 962 | sv, v; | ||
| 963 | |||
| 964 |   if(l === 0) { | ||
| 965 | // no need to do calc on black | ||
| 966 | // also avoids divide by 0 error | ||
| 967 | return [0, 0, 0]; | ||
| 968 | } | ||
| 969 | |||
| 970 | l *= 2; | ||
| 971 | s *= (l <= 1) ? l : 2 - l; | ||
| 972 | v = (l + s) / 2; | ||
| 973 | sv = (2 * s) / (l + s); | ||
| 974 | return [h, sv * 100, v * 100]; | ||
| 975 | } | ||
| 976 | |||
| 977 | function hsl2hwb(args) { | ||
| 978 | return rgb2hwb(hsl2rgb(args)); | ||
| 979 | } | ||
| 980 | |||
| 981 | function hsl2cmyk(args) { | ||
| 982 | return rgb2cmyk(hsl2rgb(args)); | ||
| 983 | } | ||
| 984 | |||
| 985 | function hsl2keyword(args) { | ||
| 986 | return rgb2keyword(hsl2rgb(args)); | ||
| 987 | } | ||
| 988 | |||
| 989 | |||
| 990 | function hsv2rgb(hsv) { | ||
| 991 | var h = hsv[0] / 60, | ||
| 992 | s = hsv[1] / 100, | ||
| 993 | v = hsv[2] / 100, | ||
| 994 | hi = Math.floor(h) % 6; | ||
| 995 | |||
| 996 | var f = h - Math.floor(h), | ||
| 997 | p = 255 * v * (1 - s), | ||
| 998 | q = 255 * v * (1 - (s * f)), | ||
| 999 | t = 255 * v * (1 - (s * (1 - f))), | ||
| 1000 | v = 255 * v; | ||
| 1001 | |||
| 1002 |   switch(hi) { | ||
| 1003 | case 0: | ||
| 1004 | return [v, t, p]; | ||
| 1005 | case 1: | ||
| 1006 | return [q, v, p]; | ||
| 1007 | case 2: | ||
| 1008 | return [p, v, t]; | ||
| 1009 | case 3: | ||
| 1010 | return [p, q, v]; | ||
| 1011 | case 4: | ||
| 1012 | return [t, p, v]; | ||
| 1013 | case 5: | ||
| 1014 | return [v, p, q]; | ||
| 1015 | } | ||
| 1016 | } | ||
| 1017 | |||
| 1018 | function hsv2hsl(hsv) { | ||
| 1019 | var h = hsv[0], | ||
| 1020 | s = hsv[1] / 100, | ||
| 1021 | v = hsv[2] / 100, | ||
| 1022 | sl, l; | ||
| 1023 | |||
| 1024 | l = (2 - s) * v; | ||
| 1025 | sl = s * v; | ||
| 1026 | sl /= (l <= 1) ? l : 2 - l; | ||
| 1027 | sl = sl || 0; | ||
| 1028 | l /= 2; | ||
| 1029 | return [h, sl * 100, l * 100]; | ||
| 1030 | } | ||
| 1031 | |||
| 1032 | function hsv2hwb(args) { | ||
| 1033 | return rgb2hwb(hsv2rgb(args)) | ||
| 1034 | } | ||
| 1035 | |||
| 1036 | function hsv2cmyk(args) { | ||
| 1037 | return rgb2cmyk(hsv2rgb(args)); | ||
| 1038 | } | ||
| 1039 | |||
| 1040 | function hsv2keyword(args) { | ||
| 1041 | return rgb2keyword(hsv2rgb(args)); | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | // http://dev.w3.org/csswg/css-color/#hwb-to-rgb | ||
| 1045 | function hwb2rgb(hwb) { | ||
| 1046 | var h = hwb[0] / 360, | ||
| 1047 | wh = hwb[1] / 100, | ||
| 1048 | bl = hwb[2] / 100, | ||
| 1049 | ratio = wh + bl, | ||
| 1050 | i, v, f, n; | ||
| 1051 | |||
| 1052 | // wh + bl cant be > 1 | ||
| 1053 |   if (ratio > 1) { | ||
| 1054 | wh /= ratio; | ||
| 1055 | bl /= ratio; | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | i = Math.floor(6 * h); | ||
| 1059 | v = 1 - bl; | ||
| 1060 | f = 6 * h - i; | ||
| 1061 |   if ((i & 0x01) != 0) { | ||
| 1062 | f = 1 - f; | ||
| 1063 | } | ||
| 1064 | n = wh + f * (v - wh); // linear interpolation | ||
| 1065 | |||
| 1066 |   switch (i) { | ||
| 1067 | default: | ||
| 1068 | case 6: | ||
| 1069 | case 0: r = v; g = n; b = wh; break; | ||
| 1070 | case 1: r = n; g = v; b = wh; break; | ||
| 1071 | case 2: r = wh; g = v; b = n; break; | ||
| 1072 | case 3: r = wh; g = n; b = v; break; | ||
| 1073 | case 4: r = n; g = wh; b = v; break; | ||
| 1074 | case 5: r = v; g = wh; b = n; break; | ||
| 1075 | } | ||
| 1076 | |||
| 1077 | return [r * 255, g * 255, b * 255]; | ||
| 1078 | } | ||
| 1079 | |||
| 1080 | function hwb2hsl(args) { | ||
| 1081 | return rgb2hsl(hwb2rgb(args)); | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | function hwb2hsv(args) { | ||
| 1085 | return rgb2hsv(hwb2rgb(args)); | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | function hwb2cmyk(args) { | ||
| 1089 | return rgb2cmyk(hwb2rgb(args)); | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | function hwb2keyword(args) { | ||
| 1093 | return rgb2keyword(hwb2rgb(args)); | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | function cmyk2rgb(cmyk) { | ||
| 1097 | var c = cmyk[0] / 100, | ||
| 1098 | m = cmyk[1] / 100, | ||
| 1099 | y = cmyk[2] / 100, | ||
| 1100 | k = cmyk[3] / 100, | ||
| 1101 | r, g, b; | ||
| 1102 | |||
| 1103 | r = 1 - Math.min(1, c * (1 - k) + k); | ||
| 1104 | g = 1 - Math.min(1, m * (1 - k) + k); | ||
| 1105 | b = 1 - Math.min(1, y * (1 - k) + k); | ||
| 1106 | return [r * 255, g * 255, b * 255]; | ||
| 1107 | } | ||
| 1108 | |||
| 1109 | function cmyk2hsl(args) { | ||
| 1110 | return rgb2hsl(cmyk2rgb(args)); | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | function cmyk2hsv(args) { | ||
| 1114 | return rgb2hsv(cmyk2rgb(args)); | ||
| 1115 | } | ||
| 1116 | |||
| 1117 | function cmyk2hwb(args) { | ||
| 1118 | return rgb2hwb(cmyk2rgb(args)); | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | function cmyk2keyword(args) { | ||
| 1122 | return rgb2keyword(cmyk2rgb(args)); | ||
| 1123 | } | ||
| 1124 | |||
| 1125 | |||
| 1126 | function xyz2rgb(xyz) { | ||
| 1127 | var x = xyz[0] / 100, | ||
| 1128 | y = xyz[1] / 100, | ||
| 1129 | z = xyz[2] / 100, | ||
| 1130 | r, g, b; | ||
| 1131 | |||
| 1132 | r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); | ||
| 1133 | g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); | ||
| 1134 | b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); | ||
| 1135 | |||
| 1136 | // assume sRGB | ||
| 1137 | r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) | ||
| 1138 | : r = (r * 12.92); | ||
| 1139 | |||
| 1140 | g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) | ||
| 1141 | : g = (g * 12.92); | ||
| 1142 | |||
| 1143 | b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) | ||
| 1144 | : b = (b * 12.92); | ||
| 1145 | |||
| 1146 | r = Math.min(Math.max(0, r), 1); | ||
| 1147 | g = Math.min(Math.max(0, g), 1); | ||
| 1148 | b = Math.min(Math.max(0, b), 1); | ||
| 1149 | |||
| 1150 | return [r * 255, g * 255, b * 255]; | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | function xyz2lab(xyz) { | ||
| 1154 | var x = xyz[0], | ||
| 1155 | y = xyz[1], | ||
| 1156 | z = xyz[2], | ||
| 1157 | l, a, b; | ||
| 1158 | |||
| 1159 | x /= 95.047; | ||
| 1160 | y /= 100; | ||
| 1161 | z /= 108.883; | ||
| 1162 | |||
| 1163 | x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); | ||
| 1164 | y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); | ||
| 1165 | z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); | ||
| 1166 | |||
| 1167 | l = (116 * y) - 16; | ||
| 1168 | a = 500 * (x - y); | ||
| 1169 | b = 200 * (y - z); | ||
| 1170 | |||
| 1171 | return [l, a, b]; | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | function xyz2lch(args) { | ||
| 1175 | return lab2lch(xyz2lab(args)); | ||
| 1176 | } | ||
| 1177 | |||
| 1178 | function lab2xyz(lab) { | ||
| 1179 | var l = lab[0], | ||
| 1180 | a = lab[1], | ||
| 1181 | b = lab[2], | ||
| 1182 | x, y, z, y2; | ||
| 1183 | |||
| 1184 |   if (l <= 8) { | ||
| 1185 | y = (l * 100) / 903.3; | ||
| 1186 | y2 = (7.787 * (y / 100)) + (16 / 116); | ||
| 1187 |   } else { | ||
| 1188 | y = 100 * Math.pow((l + 16) / 116, 3); | ||
| 1189 | y2 = Math.pow(y / 100, 1/3); | ||
| 1190 | } | ||
| 1191 | |||
| 1192 | x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); | ||
| 1193 | |||
| 1194 | z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); | ||
| 1195 | |||
| 1196 | return [x, y, z]; | ||
| 1197 | } | ||
| 1198 | |||
| 1199 | function lab2lch(lab) { | ||
| 1200 | var l = lab[0], | ||
| 1201 | a = lab[1], | ||
| 1202 | b = lab[2], | ||
| 1203 | hr, h, c; | ||
| 1204 | |||
| 1205 | hr = Math.atan2(b, a); | ||
| 1206 | h = hr * 360 / 2 / Math.PI; | ||
| 1207 |   if (h < 0) { | ||
| 1208 | h += 360; | ||
| 1209 | } | ||
| 1210 | c = Math.sqrt(a * a + b * b); | ||
| 1211 | return [l, c, h]; | ||
| 1212 | } | ||
| 1213 | |||
| 1214 | function lab2rgb(args) { | ||
| 1215 | return xyz2rgb(lab2xyz(args)); | ||
| 1216 | } | ||
| 1217 | |||
| 1218 | function lch2lab(lch) { | ||
| 1219 | var l = lch[0], | ||
| 1220 | c = lch[1], | ||
| 1221 | h = lch[2], | ||
| 1222 | a, b, hr; | ||
| 1223 | |||
| 1224 | hr = h / 360 * 2 * Math.PI; | ||
| 1225 | a = c * Math.cos(hr); | ||
| 1226 | b = c * Math.sin(hr); | ||
| 1227 | return [l, a, b]; | ||
| 1228 | } | ||
| 1229 | |||
| 1230 | function lch2xyz(args) { | ||
| 1231 | return lab2xyz(lch2lab(args)); | ||
| 1232 | } | ||
| 1233 | |||
| 1234 | function lch2rgb(args) { | ||
| 1235 | return lab2rgb(lch2lab(args)); | ||
| 1236 | } | ||
| 1237 | |||
| 1238 | function keyword2rgb(keyword) { | ||
| 1239 | return cssKeywords[keyword]; | ||
| 1240 | } | ||
| 1241 | |||
| 1242 | function keyword2hsl(args) { | ||
| 1243 | return rgb2hsl(keyword2rgb(args)); | ||
| 1244 | } | ||
| 1245 | |||
| 1246 | function keyword2hsv(args) { | ||
| 1247 | return rgb2hsv(keyword2rgb(args)); | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | function keyword2hwb(args) { | ||
| 1251 | return rgb2hwb(keyword2rgb(args)); | ||
| 1252 | } | ||
| 1253 | |||
| 1254 | function keyword2cmyk(args) { | ||
| 1255 | return rgb2cmyk(keyword2rgb(args)); | ||
| 1256 | } | ||
| 1257 | |||
| 1258 | function keyword2lab(args) { | ||
| 1259 | return rgb2lab(keyword2rgb(args)); | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | function keyword2xyz(args) { | ||
| 1263 | return rgb2xyz(keyword2rgb(args)); | ||
| 1264 | } | ||
| 1265 | |||
| 1266 | var cssKeywords = { | ||
| 1267 | aliceblue: [240,248,255], | ||
| 1268 | antiquewhite: [250,235,215], | ||
| 1269 | aqua: [0,255,255], | ||
| 1270 | aquamarine: [127,255,212], | ||
| 1271 | azure: [240,255,255], | ||
| 1272 | beige: [245,245,220], | ||
| 1273 | bisque: [255,228,196], | ||
| 1274 | black: [0,0,0], | ||
| 1275 | blanchedalmond: [255,235,205], | ||
| 1276 | blue: [0,0,255], | ||
| 1277 | blueviolet: [138,43,226], | ||
| 1278 | brown: [165,42,42], | ||
| 1279 | burlywood: [222,184,135], | ||
| 1280 | cadetblue: [95,158,160], | ||
| 1281 | chartreuse: [127,255,0], | ||
| 1282 | chocolate: [210,105,30], | ||
| 1283 | coral: [255,127,80], | ||
| 1284 | cornflowerblue: [100,149,237], | ||
| 1285 | cornsilk: [255,248,220], | ||
| 1286 | crimson: [220,20,60], | ||
| 1287 | cyan: [0,255,255], | ||
| 1288 | darkblue: [0,0,139], | ||
| 1289 | darkcyan: [0,139,139], | ||
| 1290 | darkgoldenrod: [184,134,11], | ||
| 1291 | darkgray: [169,169,169], | ||
| 1292 | darkgreen: [0,100,0], | ||
| 1293 | darkgrey: [169,169,169], | ||
| 1294 | darkkhaki: [189,183,107], | ||
| 1295 | darkmagenta: [139,0,139], | ||
| 1296 | darkolivegreen: [85,107,47], | ||
| 1297 | darkorange: [255,140,0], | ||
| 1298 | darkorchid: [153,50,204], | ||
| 1299 | darkred: [139,0,0], | ||
| 1300 | darksalmon: [233,150,122], | ||
| 1301 | darkseagreen: [143,188,143], | ||
| 1302 | darkslateblue: [72,61,139], | ||
| 1303 | darkslategray: [47,79,79], | ||
| 1304 | darkslategrey: [47,79,79], | ||
| 1305 | darkturquoise: [0,206,209], | ||
| 1306 | darkviolet: [148,0,211], | ||
| 1307 | deeppink: [255,20,147], | ||
| 1308 | deepskyblue: [0,191,255], | ||
| 1309 | dimgray: [105,105,105], | ||
| 1310 | dimgrey: [105,105,105], | ||
| 1311 | dodgerblue: [30,144,255], | ||
| 1312 | firebrick: [178,34,34], | ||
| 1313 | floralwhite: [255,250,240], | ||
| 1314 | forestgreen: [34,139,34], | ||
| 1315 | fuchsia: [255,0,255], | ||
| 1316 | gainsboro: [220,220,220], | ||
| 1317 | ghostwhite: [248,248,255], | ||
| 1318 | gold: [255,215,0], | ||
| 1319 | goldenrod: [218,165,32], | ||
| 1320 | gray: [128,128,128], | ||
| 1321 | green: [0,128,0], | ||
| 1322 | greenyellow: [173,255,47], | ||
| 1323 | grey: [128,128,128], | ||
| 1324 | honeydew: [240,255,240], | ||
| 1325 | hotpink: [255,105,180], | ||
| 1326 | indianred: [205,92,92], | ||
| 1327 | indigo: [75,0,130], | ||
| 1328 | ivory: [255,255,240], | ||
| 1329 | khaki: [240,230,140], | ||
| 1330 | lavender: [230,230,250], | ||
| 1331 | lavenderblush: [255,240,245], | ||
| 1332 | lawngreen: [124,252,0], | ||
| 1333 | lemonchiffon: [255,250,205], | ||
| 1334 | lightblue: [173,216,230], | ||
| 1335 | lightcoral: [240,128,128], | ||
| 1336 | lightcyan: [224,255,255], | ||
| 1337 | lightgoldenrodyellow: [250,250,210], | ||
| 1338 | lightgray: [211,211,211], | ||
| 1339 | lightgreen: [144,238,144], | ||
| 1340 | lightgrey: [211,211,211], | ||
| 1341 | lightpink: [255,182,193], | ||
| 1342 | lightsalmon: [255,160,122], | ||
| 1343 | lightseagreen: [32,178,170], | ||
| 1344 | lightskyblue: [135,206,250], | ||
| 1345 | lightslategray: [119,136,153], | ||
| 1346 | lightslategrey: [119,136,153], | ||
| 1347 | lightsteelblue: [176,196,222], | ||
| 1348 | lightyellow: [255,255,224], | ||
| 1349 | lime: [0,255,0], | ||
| 1350 | limegreen: [50,205,50], | ||
| 1351 | linen: [250,240,230], | ||
| 1352 | magenta: [255,0,255], | ||
| 1353 | maroon: [128,0,0], | ||
| 1354 | mediumaquamarine: [102,205,170], | ||
| 1355 | mediumblue: [0,0,205], | ||
| 1356 | mediumorchid: [186,85,211], | ||
| 1357 | mediumpurple: [147,112,219], | ||
| 1358 | mediumseagreen: [60,179,113], | ||
| 1359 | mediumslateblue: [123,104,238], | ||
| 1360 | mediumspringgreen: [0,250,154], | ||
| 1361 | mediumturquoise: [72,209,204], | ||
| 1362 | mediumvioletred: [199,21,133], | ||
| 1363 | midnightblue: [25,25,112], | ||
| 1364 | mintcream: [245,255,250], | ||
| 1365 | mistyrose: [255,228,225], | ||
| 1366 | moccasin: [255,228,181], | ||
| 1367 | navajowhite: [255,222,173], | ||
| 1368 | navy: [0,0,128], | ||
| 1369 | oldlace: [253,245,230], | ||
| 1370 | olive: [128,128,0], | ||
| 1371 | olivedrab: [107,142,35], | ||
| 1372 | orange: [255,165,0], | ||
| 1373 | orangered: [255,69,0], | ||
| 1374 | orchid: [218,112,214], | ||
| 1375 | palegoldenrod: [238,232,170], | ||
| 1376 | palegreen: [152,251,152], | ||
| 1377 | paleturquoise: [175,238,238], | ||
| 1378 | palevioletred: [219,112,147], | ||
| 1379 | papayawhip: [255,239,213], | ||
| 1380 | peachpuff: [255,218,185], | ||
| 1381 | peru: [205,133,63], | ||
| 1382 | pink: [255,192,203], | ||
| 1383 | plum: [221,160,221], | ||
| 1384 | powderblue: [176,224,230], | ||
| 1385 | purple: [128,0,128], | ||
| 1386 | rebeccapurple: [102, 51, 153], | ||
| 1387 | red: [255,0,0], | ||
| 1388 | rosybrown: [188,143,143], | ||
| 1389 | royalblue: [65,105,225], | ||
| 1390 | saddlebrown: [139,69,19], | ||
| 1391 | salmon: [250,128,114], | ||
| 1392 | sandybrown: [244,164,96], | ||
| 1393 | seagreen: [46,139,87], | ||
| 1394 | seashell: [255,245,238], | ||
| 1395 | sienna: [160,82,45], | ||
| 1396 | silver: [192,192,192], | ||
| 1397 | skyblue: [135,206,235], | ||
| 1398 | slateblue: [106,90,205], | ||
| 1399 | slategray: [112,128,144], | ||
| 1400 | slategrey: [112,128,144], | ||
| 1401 | snow: [255,250,250], | ||
| 1402 | springgreen: [0,255,127], | ||
| 1403 | steelblue: [70,130,180], | ||
| 1404 | tan: [210,180,140], | ||
| 1405 | teal: [0,128,128], | ||
| 1406 | thistle: [216,191,216], | ||
| 1407 | tomato: [255,99,71], | ||
| 1408 | turquoise: [64,224,208], | ||
| 1409 | violet: [238,130,238], | ||
| 1410 | wheat: [245,222,179], | ||
| 1411 | white: [255,255,255], | ||
| 1412 | whitesmoke: [245,245,245], | ||
| 1413 | yellow: [255,255,0], | ||
| 1414 | yellowgreen: [154,205,50] | ||
| 1415 | }; | ||
| 1416 | |||
| 1417 | var reverseKeywords = {}; | ||
| 1418 | for (var key in cssKeywords) { | ||
| 1419 | reverseKeywords[JSON.stringify(cssKeywords[key])] = key; | ||
| 1420 | } | ||
| 1421 | |||
| 1422 | },{}],5:[function(require,module,exports){ | ||
| 1423 | var conversions = require(4); | ||
| 1424 | |||
| 1425 | var convert = function() { | ||
| 1426 | return new Converter(); | ||
| 1427 | } | ||
| 1428 | |||
| 1429 | for (var func in conversions) { | ||
| 1430 | // export Raw versions | ||
| 1431 |   convert[func + "Raw"] =  (function(func) { | ||
| 1432 | // accept array or plain args | ||
| 1433 |     return function(arg) { | ||
| 1434 | if (typeof arg == "number") | ||
| 1435 | arg = Array.prototype.slice.call(arguments); | ||
| 1436 | return conversions[func](arg); | ||
| 1437 | } | ||
| 1438 | })(func); | ||
| 1439 | |||
| 1440 | var pair = /(\w+)2(\w+)/.exec(func), | ||
| 1441 | from = pair[1], | ||
| 1442 | to = pair[2]; | ||
| 1443 | |||
| 1444 | // export rgb2hsl and ["rgb"]["hsl"] | ||
| 1445 |   convert[from] = convert[from] || {}; | ||
| 1446 | |||
| 1447 |   convert[from][to] = convert[func] = (function(func) { | ||
| 1448 |     return function(arg) { | ||
| 1449 | if (typeof arg == "number") | ||
| 1450 | arg = Array.prototype.slice.call(arguments); | ||
| 1451 | |||
| 1452 | var val = conversions[func](arg); | ||
| 1453 | if (typeof val == "string" || val === undefined) | ||
| 1454 | return val; // keyword | ||
| 1455 | |||
| 1456 | for (var i = 0; i < val.length; i++) | ||
| 1457 | val[i] = Math.round(val[i]); | ||
| 1458 | return val; | ||
| 1459 | } | ||
| 1460 | })(func); | ||
| 1461 | } | ||
| 1462 | |||
| 1463 | |||
| 1464 | /* Converter does lazy conversion and caching */ | ||
| 1465 | var Converter = function() { | ||
| 1466 |    this.convs = {}; | ||
| 1467 | }; | ||
| 1468 | |||
| 1469 | /* Either get the values for a space or | ||
| 1470 | set the values for a space, depending on args */ | ||
| 1471 | Converter.prototype.routeSpace = function(space, args) { | ||
| 1472 | var values = args[0]; | ||
| 1473 |    if (values === undefined) { | ||
| 1474 | // color.rgb() | ||
| 1475 | return this.getValues(space); | ||
| 1476 | } | ||
| 1477 | // color.rgb(10, 10, 10) | ||
| 1478 |    if (typeof values == "number") { | ||
| 1479 | values = Array.prototype.slice.call(args); | ||
| 1480 | } | ||
| 1481 | |||
| 1482 | return this.setValues(space, values); | ||
| 1483 | }; | ||
| 1484 | |||
| 1485 | /* Set the values for a space, invalidating cache */ | ||
| 1486 | Converter.prototype.setValues = function(space, values) { | ||
| 1487 | this.space = space; | ||
| 1488 |    this.convs = {}; | ||
| 1489 | this.convs[space] = values; | ||
| 1490 | return this; | ||
| 1491 | }; | ||
| 1492 | |||
| 1493 | /* Get the values for a space. If there's already | ||
| 1494 | a conversion for the space, fetch it, otherwise | ||
| 1495 | compute it */ | ||
| 1496 | Converter.prototype.getValues = function(space) { | ||
| 1497 | var vals = this.convs[space]; | ||
| 1498 |    if (!vals) { | ||
| 1499 | var fspace = this.space, | ||
| 1500 | from = this.convs[fspace]; | ||
| 1501 | vals = convert[fspace][space](from); | ||
| 1502 | |||
| 1503 | this.convs[space] = vals; | ||
| 1504 | } | ||
| 1505 | return vals; | ||
| 1506 | }; | ||
| 1507 | |||
| 1508 | ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { | ||
| 1509 |    Converter.prototype[space] = function(vals) { | ||
| 1510 | return this.routeSpace(space, arguments); | ||
| 1511 | } | ||
| 1512 | }); | ||
| 1513 | |||
| 1514 | module.exports = convert; | ||
| 1515 | },{"4":4}],6:[function(require,module,exports){ | ||
| 1516 | 'use strict' | ||
| 1517 | |||
| 1518 | module.exports = { | ||
| 1519 | "aliceblue": [240, 248, 255], | ||
| 1520 | "antiquewhite": [250, 235, 215], | ||
| 1521 | "aqua": [0, 255, 255], | ||
| 1522 | "aquamarine": [127, 255, 212], | ||
| 1523 | "azure": [240, 255, 255], | ||
| 1524 | "beige": [245, 245, 220], | ||
| 1525 | "bisque": [255, 228, 196], | ||
| 1526 | "black": [0, 0, 0], | ||
| 1527 | "blanchedalmond": [255, 235, 205], | ||
| 1528 | "blue": [0, 0, 255], | ||
| 1529 | "blueviolet": [138, 43, 226], | ||
| 1530 | "brown": [165, 42, 42], | ||
| 1531 | "burlywood": [222, 184, 135], | ||
| 1532 | "cadetblue": [95, 158, 160], | ||
| 1533 | "chartreuse": [127, 255, 0], | ||
| 1534 | "chocolate": [210, 105, 30], | ||
| 1535 | "coral": [255, 127, 80], | ||
| 1536 | "cornflowerblue": [100, 149, 237], | ||
| 1537 | "cornsilk": [255, 248, 220], | ||
| 1538 | "crimson": [220, 20, 60], | ||
| 1539 | "cyan": [0, 255, 255], | ||
| 1540 | "darkblue": [0, 0, 139], | ||
| 1541 | "darkcyan": [0, 139, 139], | ||
| 1542 | "darkgoldenrod": [184, 134, 11], | ||
| 1543 | "darkgray": [169, 169, 169], | ||
| 1544 | "darkgreen": [0, 100, 0], | ||
| 1545 | "darkgrey": [169, 169, 169], | ||
| 1546 | "darkkhaki": [189, 183, 107], | ||
| 1547 | "darkmagenta": [139, 0, 139], | ||
| 1548 | "darkolivegreen": [85, 107, 47], | ||
| 1549 | "darkorange": [255, 140, 0], | ||
| 1550 | "darkorchid": [153, 50, 204], | ||
| 1551 | "darkred": [139, 0, 0], | ||
| 1552 | "darksalmon": [233, 150, 122], | ||
| 1553 | "darkseagreen": [143, 188, 143], | ||
| 1554 | "darkslateblue": [72, 61, 139], | ||
| 1555 | "darkslategray": [47, 79, 79], | ||
| 1556 | "darkslategrey": [47, 79, 79], | ||
| 1557 | "darkturquoise": [0, 206, 209], | ||
| 1558 | "darkviolet": [148, 0, 211], | ||
| 1559 | "deeppink": [255, 20, 147], | ||
| 1560 | "deepskyblue": [0, 191, 255], | ||
| 1561 | "dimgray": [105, 105, 105], | ||
| 1562 | "dimgrey": [105, 105, 105], | ||
| 1563 | "dodgerblue": [30, 144, 255], | ||
| 1564 | "firebrick": [178, 34, 34], | ||
| 1565 | "floralwhite": [255, 250, 240], | ||
| 1566 | "forestgreen": [34, 139, 34], | ||
| 1567 | "fuchsia": [255, 0, 255], | ||
| 1568 | "gainsboro": [220, 220, 220], | ||
| 1569 | "ghostwhite": [248, 248, 255], | ||
| 1570 | "gold": [255, 215, 0], | ||
| 1571 | "goldenrod": [218, 165, 32], | ||
| 1572 | "gray": [128, 128, 128], | ||
| 1573 | "green": [0, 128, 0], | ||
| 1574 | "greenyellow": [173, 255, 47], | ||
| 1575 | "grey": [128, 128, 128], | ||
| 1576 | "honeydew": [240, 255, 240], | ||
| 1577 | "hotpink": [255, 105, 180], | ||
| 1578 | "indianred": [205, 92, 92], | ||
| 1579 | "indigo": [75, 0, 130], | ||
| 1580 | "ivory": [255, 255, 240], | ||
| 1581 | "khaki": [240, 230, 140], | ||
| 1582 | "lavender": [230, 230, 250], | ||
| 1583 | "lavenderblush": [255, 240, 245], | ||
| 1584 | "lawngreen": [124, 252, 0], | ||
| 1585 | "lemonchiffon": [255, 250, 205], | ||
| 1586 | "lightblue": [173, 216, 230], | ||
| 1587 | "lightcoral": [240, 128, 128], | ||
| 1588 | "lightcyan": [224, 255, 255], | ||
| 1589 | "lightgoldenrodyellow": [250, 250, 210], | ||
| 1590 | "lightgray": [211, 211, 211], | ||
| 1591 | "lightgreen": [144, 238, 144], | ||
| 1592 | "lightgrey": [211, 211, 211], | ||
| 1593 | "lightpink": [255, 182, 193], | ||
| 1594 | "lightsalmon": [255, 160, 122], | ||
| 1595 | "lightseagreen": [32, 178, 170], | ||
| 1596 | "lightskyblue": [135, 206, 250], | ||
| 1597 | "lightslategray": [119, 136, 153], | ||
| 1598 | "lightslategrey": [119, 136, 153], | ||
| 1599 | "lightsteelblue": [176, 196, 222], | ||
| 1600 | "lightyellow": [255, 255, 224], | ||
| 1601 | "lime": [0, 255, 0], | ||
| 1602 | "limegreen": [50, 205, 50], | ||
| 1603 | "linen": [250, 240, 230], | ||
| 1604 | "magenta": [255, 0, 255], | ||
| 1605 | "maroon": [128, 0, 0], | ||
| 1606 | "mediumaquamarine": [102, 205, 170], | ||
| 1607 | "mediumblue": [0, 0, 205], | ||
| 1608 | "mediumorchid": [186, 85, 211], | ||
| 1609 | "mediumpurple": [147, 112, 219], | ||
| 1610 | "mediumseagreen": [60, 179, 113], | ||
| 1611 | "mediumslateblue": [123, 104, 238], | ||
| 1612 | "mediumspringgreen": [0, 250, 154], | ||
| 1613 | "mediumturquoise": [72, 209, 204], | ||
| 1614 | "mediumvioletred": [199, 21, 133], | ||
| 1615 | "midnightblue": [25, 25, 112], | ||
| 1616 | "mintcream": [245, 255, 250], | ||
| 1617 | "mistyrose": [255, 228, 225], | ||
| 1618 | "moccasin": [255, 228, 181], | ||
| 1619 | "navajowhite": [255, 222, 173], | ||
| 1620 | "navy": [0, 0, 128], | ||
| 1621 | "oldlace": [253, 245, 230], | ||
| 1622 | "olive": [128, 128, 0], | ||
| 1623 | "olivedrab": [107, 142, 35], | ||
| 1624 | "orange": [255, 165, 0], | ||
| 1625 | "orangered": [255, 69, 0], | ||
| 1626 | "orchid": [218, 112, 214], | ||
| 1627 | "palegoldenrod": [238, 232, 170], | ||
| 1628 | "palegreen": [152, 251, 152], | ||
| 1629 | "paleturquoise": [175, 238, 238], | ||
| 1630 | "palevioletred": [219, 112, 147], | ||
| 1631 | "papayawhip": [255, 239, 213], | ||
| 1632 | "peachpuff": [255, 218, 185], | ||
| 1633 | "peru": [205, 133, 63], | ||
| 1634 | "pink": [255, 192, 203], | ||
| 1635 | "plum": [221, 160, 221], | ||
| 1636 | "powderblue": [176, 224, 230], | ||
| 1637 | "purple": [128, 0, 128], | ||
| 1638 | "rebeccapurple": [102, 51, 153], | ||
| 1639 | "red": [255, 0, 0], | ||
| 1640 | "rosybrown": [188, 143, 143], | ||
| 1641 | "royalblue": [65, 105, 225], | ||
| 1642 | "saddlebrown": [139, 69, 19], | ||
| 1643 | "salmon": [250, 128, 114], | ||
| 1644 | "sandybrown": [244, 164, 96], | ||
| 1645 | "seagreen": [46, 139, 87], | ||
| 1646 | "seashell": [255, 245, 238], | ||
| 1647 | "sienna": [160, 82, 45], | ||
| 1648 | "silver": [192, 192, 192], | ||
| 1649 | "skyblue": [135, 206, 235], | ||
| 1650 | "slateblue": [106, 90, 205], | ||
| 1651 | "slategray": [112, 128, 144], | ||
| 1652 | "slategrey": [112, 128, 144], | ||
| 1653 | "snow": [255, 250, 250], | ||
| 1654 | "springgreen": [0, 255, 127], | ||
| 1655 | "steelblue": [70, 130, 180], | ||
| 1656 | "tan": [210, 180, 140], | ||
| 1657 | "teal": [0, 128, 128], | ||
| 1658 | "thistle": [216, 191, 216], | ||
| 1659 | "tomato": [255, 99, 71], | ||
| 1660 | "turquoise": [64, 224, 208], | ||
| 1661 | "violet": [238, 130, 238], | ||
| 1662 | "wheat": [245, 222, 179], | ||
| 1663 | "white": [255, 255, 255], | ||
| 1664 | "whitesmoke": [245, 245, 245], | ||
| 1665 | "yellow": [255, 255, 0], | ||
| 1666 | "yellowgreen": [154, 205, 50] | ||
| 1667 | }; | ||
| 1668 | |||
| 1669 | },{}],7:[function(require,module,exports){ | ||
| 1670 | /** | ||
| 1671 | * @namespace Chart | ||
| 1672 | */ | ||
| 1673 | var Chart = require(30)(); | ||
| 1674 | |||
| 1675 | Chart.helpers = require(46); | ||
| 1676 | |||
| 1677 | // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! | ||
| 1678 | require(28)(Chart); | ||
| 1679 | |||
| 1680 | Chart.Animation = require(22); | ||
| 1681 | Chart.animationService = require(23); | ||
| 1682 | Chart.defaults = require(26); | ||
| 1683 | Chart.Element = require(27); | ||
| 1684 | Chart.elements = require(41); | ||
| 1685 | Chart.Interaction = require(29); | ||
| 1686 | Chart.layouts = require(31); | ||
| 1687 | Chart.platform = require(49); | ||
| 1688 | Chart.plugins = require(32); | ||
| 1689 | Chart.Scale = require(33); | ||
| 1690 | Chart.scaleService = require(34); | ||
| 1691 | Chart.Ticks = require(35); | ||
| 1692 | Chart.Tooltip = require(36); | ||
| 1693 | |||
| 1694 | require(24)(Chart); | ||
| 1695 | require(25)(Chart); | ||
| 1696 | |||
| 1697 | require(56)(Chart); | ||
| 1698 | require(54)(Chart); | ||
| 1699 | require(55)(Chart); | ||
| 1700 | require(57)(Chart); | ||
| 1701 | require(58)(Chart); | ||
| 1702 | require(59)(Chart); | ||
| 1703 | |||
| 1704 | // Controllers must be loaded after elements | ||
| 1705 | // See Chart.core.datasetController.dataElementType | ||
| 1706 | require(15)(Chart); | ||
| 1707 | require(16)(Chart); | ||
| 1708 | require(17)(Chart); | ||
| 1709 | require(18)(Chart); | ||
| 1710 | require(19)(Chart); | ||
| 1711 | require(20)(Chart); | ||
| 1712 | require(21)(Chart); | ||
| 1713 | |||
| 1714 | require(8)(Chart); | ||
| 1715 | require(9)(Chart); | ||
| 1716 | require(10)(Chart); | ||
| 1717 | require(11)(Chart); | ||
| 1718 | require(12)(Chart); | ||
| 1719 | require(13)(Chart); | ||
| 1720 | require(14)(Chart); | ||
| 1721 | |||
| 1722 | // Loading built-in plugins | ||
| 1723 | var plugins = require(50); | ||
| 1724 | for (var k in plugins) { | ||
| 1725 | 	if (plugins.hasOwnProperty(k)) { | ||
| 1726 | Chart.plugins.register(plugins[k]); | ||
| 1727 | } | ||
| 1728 | } | ||
| 1729 | |||
| 1730 | Chart.platform.initialize(); | ||
| 1731 | |||
| 1732 | module.exports = Chart; | ||
| 1733 | if (typeof window !== 'undefined') { | ||
| 1734 | window.Chart = Chart; | ||
| 1735 | } | ||
| 1736 | |||
| 1737 | // DEPRECATIONS | ||
| 1738 | |||
| 1739 | /** | ||
| 1740 | * Provided for backward compatibility, not available anymore | ||
| 1741 | * @namespace Chart.Legend | ||
| 1742 | * @deprecated since version 2.1.5 | ||
| 1743 | * @todo remove at version 3 | ||
| 1744 | * @private | ||
| 1745 | */ | ||
| 1746 | Chart.Legend = plugins.legend._element; | ||
| 1747 | |||
| 1748 | /** | ||
| 1749 | * Provided for backward compatibility, not available anymore | ||
| 1750 | * @namespace Chart.Title | ||
| 1751 | * @deprecated since version 2.1.5 | ||
| 1752 | * @todo remove at version 3 | ||
| 1753 | * @private | ||
| 1754 | */ | ||
| 1755 | Chart.Title = plugins.title._element; | ||
| 1756 | |||
| 1757 | /** | ||
| 1758 | * Provided for backward compatibility, use Chart.plugins instead | ||
| 1759 | * @namespace Chart.pluginService | ||
| 1760 | * @deprecated since version 2.1.5 | ||
| 1761 | * @todo remove at version 3 | ||
| 1762 | * @private | ||
| 1763 | */ | ||
| 1764 | Chart.pluginService = Chart.plugins; | ||
| 1765 | |||
| 1766 | /** | ||
| 1767 | * Provided for backward compatibility, inheriting from Chart.PlugingBase has no | ||
| 1768 | * effect, instead simply create/register plugins via plain JavaScript objects. | ||
| 1769 | * @interface Chart.PluginBase | ||
| 1770 | * @deprecated since version 2.5.0 | ||
| 1771 | * @todo remove at version 3 | ||
| 1772 | * @private | ||
| 1773 | */ | ||
| 1774 | Chart.PluginBase = Chart.Element.extend({}); | ||
| 1775 | |||
| 1776 | /** | ||
| 1777 | * Provided for backward compatibility, use Chart.helpers.canvas instead. | ||
| 1778 | * @namespace Chart.canvasHelpers | ||
| 1779 | * @deprecated since version 2.6.0 | ||
| 1780 | * @todo remove at version 3 | ||
| 1781 | * @private | ||
| 1782 | */ | ||
| 1783 | Chart.canvasHelpers = Chart.helpers.canvas; | ||
| 1784 | |||
| 1785 | /** | ||
| 1786 | * Provided for backward compatibility, use Chart.layouts instead. | ||
| 1787 | * @namespace Chart.layoutService | ||
| 1788 | * @deprecated since version 2.8.0 | ||
| 1789 | * @todo remove at version 3 | ||
| 1790 | * @private | ||
| 1791 | */ | ||
| 1792 | Chart.layoutService = Chart.layouts; | ||
| 1793 | |||
| 1794 | },{"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,"41":41,"46":46,"49":49,"50":50,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"8":8,"9":9}],8:[function(require,module,exports){ | ||
| 1795 | 'use strict'; | ||
| 1796 | |||
| 1797 | module.exports = function(Chart) { | ||
| 1798 | |||
| 1799 | 	Chart.Bar = function(context, config) { | ||
| 1800 | config.type = 'bar'; | ||
| 1801 | |||
| 1802 | return new Chart(context, config); | ||
| 1803 | }; | ||
| 1804 | |||
| 1805 | }; | ||
| 1806 | |||
| 1807 | },{}],9:[function(require,module,exports){ | ||
| 1808 | 'use strict'; | ||
| 1809 | |||
| 1810 | module.exports = function(Chart) { | ||
| 1811 | |||
| 1812 | 	Chart.Bubble = function(context, config) { | ||
| 1813 | config.type = 'bubble'; | ||
| 1814 | return new Chart(context, config); | ||
| 1815 | }; | ||
| 1816 | |||
| 1817 | }; | ||
| 1818 | |||
| 1819 | },{}],10:[function(require,module,exports){ | ||
| 1820 | 'use strict'; | ||
| 1821 | |||
| 1822 | module.exports = function(Chart) { | ||
| 1823 | |||
| 1824 | 	Chart.Doughnut = function(context, config) { | ||
| 1825 | config.type = 'doughnut'; | ||
| 1826 | |||
| 1827 | return new Chart(context, config); | ||
| 1828 | }; | ||
| 1829 | |||
| 1830 | }; | ||
| 1831 | |||
| 1832 | },{}],11:[function(require,module,exports){ | ||
| 1833 | 'use strict'; | ||
| 1834 | |||
| 1835 | module.exports = function(Chart) { | ||
| 1836 | |||
| 1837 | 	Chart.Line = function(context, config) { | ||
| 1838 | config.type = 'line'; | ||
| 1839 | |||
| 1840 | return new Chart(context, config); | ||
| 1841 | }; | ||
| 1842 | |||
| 1843 | }; | ||
| 1844 | |||
| 1845 | },{}],12:[function(require,module,exports){ | ||
| 1846 | 'use strict'; | ||
| 1847 | |||
| 1848 | module.exports = function(Chart) { | ||
| 1849 | |||
| 1850 | 	Chart.PolarArea = function(context, config) { | ||
| 1851 | config.type = 'polarArea'; | ||
| 1852 | |||
| 1853 | return new Chart(context, config); | ||
| 1854 | }; | ||
| 1855 | |||
| 1856 | }; | ||
| 1857 | |||
| 1858 | },{}],13:[function(require,module,exports){ | ||
| 1859 | 'use strict'; | ||
| 1860 | |||
| 1861 | module.exports = function(Chart) { | ||
| 1862 | |||
| 1863 | 	Chart.Radar = function(context, config) { | ||
| 1864 | config.type = 'radar'; | ||
| 1865 | |||
| 1866 | return new Chart(context, config); | ||
| 1867 | }; | ||
| 1868 | |||
| 1869 | }; | ||
| 1870 | |||
| 1871 | },{}],14:[function(require,module,exports){ | ||
| 1872 | 'use strict'; | ||
| 1873 | |||
| 1874 | module.exports = function(Chart) { | ||
| 1875 | 	Chart.Scatter = function(context, config) { | ||
| 1876 | config.type = 'scatter'; | ||
| 1877 | return new Chart(context, config); | ||
| 1878 | }; | ||
| 1879 | }; | ||
| 1880 | |||
| 1881 | },{}],15:[function(require,module,exports){ | ||
| 1882 | 'use strict'; | ||
| 1883 | |||
| 1884 | var defaults = require(26); | ||
| 1885 | var elements = require(41); | ||
| 1886 | var helpers = require(46); | ||
| 1887 | |||
| 1888 | defaults._set('bar', { | ||
| 1889 | 	hover: { | ||
| 1890 | mode: 'label' | ||
| 1891 | }, | ||
| 1892 | |||
| 1893 | 	scales: { | ||
| 1894 | 		xAxes: [{ | ||
| 1895 | type: 'category', | ||
| 1896 | |||
| 1897 | // Specific to Bar Controller | ||
| 1898 | categoryPercentage: 0.8, | ||
| 1899 | barPercentage: 0.9, | ||
| 1900 | |||
| 1901 | // offset settings | ||
| 1902 | offset: true, | ||
| 1903 | |||
| 1904 | // grid line settings | ||
| 1905 | 			gridLines: { | ||
| 1906 | offsetGridLines: true | ||
| 1907 | } | ||
| 1908 | }], | ||
| 1909 | |||
| 1910 | 		yAxes: [{ | ||
| 1911 | type: 'linear' | ||
| 1912 | }] | ||
| 1913 | } | ||
| 1914 | }); | ||
| 1915 | |||
| 1916 | defaults._set('horizontalBar', { | ||
| 1917 | 	hover: { | ||
| 1918 | mode: 'index', | ||
| 1919 | axis: 'y' | ||
| 1920 | }, | ||
| 1921 | |||
| 1922 | 	scales: { | ||
| 1923 | 		xAxes: [{ | ||
| 1924 | type: 'linear', | ||
| 1925 | position: 'bottom' | ||
| 1926 | }], | ||
| 1927 | |||
| 1928 | 		yAxes: [{ | ||
| 1929 | position: 'left', | ||
| 1930 | type: 'category', | ||
| 1931 | |||
| 1932 | // Specific to Horizontal Bar Controller | ||
| 1933 | categoryPercentage: 0.8, | ||
| 1934 | barPercentage: 0.9, | ||
| 1935 | |||
| 1936 | // offset settings | ||
| 1937 | offset: true, | ||
| 1938 | |||
| 1939 | // grid line settings | ||
| 1940 | 			gridLines: { | ||
| 1941 | offsetGridLines: true | ||
| 1942 | } | ||
| 1943 | }] | ||
| 1944 | }, | ||
| 1945 | |||
| 1946 | 	elements: { | ||
| 1947 | 		rectangle: { | ||
| 1948 | borderSkipped: 'left' | ||
| 1949 | } | ||
| 1950 | }, | ||
| 1951 | |||
| 1952 | 	tooltips: { | ||
| 1953 | 		callbacks: { | ||
| 1954 | 			title: function(item, data) { | ||
| 1955 | // Pick first xLabel for now | ||
| 1956 | var title = ''; | ||
| 1957 | |||
| 1958 | 				if (item.length > 0) { | ||
| 1959 | 					if (item[0].yLabel) { | ||
| 1960 | title = item[0].yLabel; | ||
| 1961 | 					} else if (data.labels.length > 0 && item[0].index < data.labels.length) { | ||
| 1962 | title = data.labels[item[0].index]; | ||
| 1963 | } | ||
| 1964 | } | ||
| 1965 | |||
| 1966 | return title; | ||
| 1967 | }, | ||
| 1968 | |||
| 1969 | 			label: function(item, data) { | ||
| 1970 | var datasetLabel = data.datasets[item.datasetIndex].label || ''; | ||
| 1971 | return datasetLabel + ': ' + item.xLabel; | ||
| 1972 | } | ||
| 1973 | }, | ||
| 1974 | mode: 'index', | ||
| 1975 | axis: 'y' | ||
| 1976 | } | ||
| 1977 | }); | ||
| 1978 | |||
| 1979 | /** | ||
| 1980 | * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. | ||
| 1981 | * @private | ||
| 1982 | */ | ||
| 1983 | function computeMinSampleSize(scale, pixels) { | ||
| 1984 | var min = scale.isHorizontal() ? scale.width : scale.height; | ||
| 1985 | var ticks = scale.getTicks(); | ||
| 1986 | var prev, curr, i, ilen; | ||
| 1987 | |||
| 1988 | 	for (i = 1, ilen = pixels.length; i < ilen; ++i) { | ||
| 1989 | min = Math.min(min, pixels[i] - pixels[i - 1]); | ||
| 1990 | } | ||
| 1991 | |||
| 1992 | 	for (i = 0, ilen = ticks.length; i < ilen; ++i) { | ||
| 1993 | curr = scale.getPixelForTick(i); | ||
| 1994 | min = i > 0 ? Math.min(min, curr - prev) : min; | ||
| 1995 | prev = curr; | ||
| 1996 | } | ||
| 1997 | |||
| 1998 | return min; | ||
| 1999 | } | ||
| 2000 | |||
| 2001 | /** | ||
| 2002 | * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, | ||
| 2003 | * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This | ||
| 2004 | * mode currently always generates bars equally sized (until we introduce scriptable options?). | ||
| 2005 | * @private | ||
| 2006 | */ | ||
| 2007 | function computeFitCategoryTraits(index, ruler, options) { | ||
| 2008 | var thickness = options.barThickness; | ||
| 2009 | var count = ruler.stackCount; | ||
| 2010 | var curr = ruler.pixels[index]; | ||
| 2011 | var size, ratio; | ||
| 2012 | |||
| 2013 | 	if (helpers.isNullOrUndef(thickness)) { | ||
| 2014 | size = ruler.min * options.categoryPercentage; | ||
| 2015 | ratio = options.barPercentage; | ||
| 2016 | 	} else { | ||
| 2017 | // When bar thickness is enforced, category and bar percentages are ignored. | ||
| 2018 | // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') | ||
| 2019 | // and deprecate barPercentage since this value is ignored when thickness is absolute. | ||
| 2020 | size = thickness * count; | ||
| 2021 | ratio = 1; | ||
| 2022 | } | ||
| 2023 | |||
| 2024 | 	return { | ||
| 2025 | chunk: size / count, | ||
| 2026 | ratio: ratio, | ||
| 2027 | start: curr - (size / 2) | ||
| 2028 | }; | ||
| 2029 | } | ||
| 2030 | |||
| 2031 | /** | ||
| 2032 | * Computes an "optimal" category that globally arranges bars side by side (no gap when | ||
| 2033 | * percentage options are 1), based on the previous and following categories. This mode | ||
| 2034 | * generates bars with different widths when data are not evenly spaced. | ||
| 2035 | * @private | ||
| 2036 | */ | ||
| 2037 | function computeFlexCategoryTraits(index, ruler, options) { | ||
| 2038 | var pixels = ruler.pixels; | ||
| 2039 | var curr = pixels[index]; | ||
| 2040 | var prev = index > 0 ? pixels[index - 1] : null; | ||
| 2041 | var next = index < pixels.length - 1 ? pixels[index + 1] : null; | ||
| 2042 | var percent = options.categoryPercentage; | ||
| 2043 | var start, size; | ||
| 2044 | |||
| 2045 | 	if (prev === null) { | ||
| 2046 | // first data: its size is double based on the next point or, | ||
| 2047 | // if it's also the last data, we use the scale end extremity. | ||
| 2048 | prev = curr - (next === null ? ruler.end - curr : next - curr); | ||
| 2049 | } | ||
| 2050 | |||
| 2051 | 	if (next === null) { | ||
| 2052 | // last data: its size is also double based on the previous point. | ||
| 2053 | next = curr + curr - prev; | ||
| 2054 | } | ||
| 2055 | |||
| 2056 | start = curr - ((curr - prev) / 2) * percent; | ||
| 2057 | size = ((next - prev) / 2) * percent; | ||
| 2058 | |||
| 2059 | 	return { | ||
| 2060 | chunk: size / ruler.stackCount, | ||
| 2061 | ratio: options.barPercentage, | ||
| 2062 | start: start | ||
| 2063 | }; | ||
| 2064 | } | ||
| 2065 | |||
| 2066 | module.exports = function(Chart) { | ||
| 2067 | |||
| 2068 | 	Chart.controllers.bar = Chart.DatasetController.extend({ | ||
| 2069 | |||
| 2070 | dataElementType: elements.Rectangle, | ||
| 2071 | |||
| 2072 | 		initialize: function() { | ||
| 2073 | var me = this; | ||
| 2074 | var meta; | ||
| 2075 | |||
| 2076 | Chart.DatasetController.prototype.initialize.apply(me, arguments); | ||
| 2077 | |||
| 2078 | meta = me.getMeta(); | ||
| 2079 | meta.stack = me.getDataset().stack; | ||
| 2080 | meta.bar = true; | ||
| 2081 | }, | ||
| 2082 | |||
| 2083 | 		update: function(reset) { | ||
| 2084 | var me = this; | ||
| 2085 | var rects = me.getMeta().data; | ||
| 2086 | var i, ilen; | ||
| 2087 | |||
| 2088 | me._ruler = me.getRuler(); | ||
| 2089 | |||
| 2090 | 			for (i = 0, ilen = rects.length; i < ilen; ++i) { | ||
| 2091 | me.updateElement(rects[i], i, reset); | ||
| 2092 | } | ||
| 2093 | }, | ||
| 2094 | |||
| 2095 | 		updateElement: function(rectangle, index, reset) { | ||
| 2096 | var me = this; | ||
| 2097 | var chart = me.chart; | ||
| 2098 | var meta = me.getMeta(); | ||
| 2099 | var dataset = me.getDataset(); | ||
| 2100 | 			var custom = rectangle.custom || {}; | ||
| 2101 | var rectangleOptions = chart.options.elements.rectangle; | ||
| 2102 | |||
| 2103 | rectangle._xScale = me.getScaleForId(meta.xAxisID); | ||
| 2104 | rectangle._yScale = me.getScaleForId(meta.yAxisID); | ||
| 2105 | rectangle._datasetIndex = me.index; | ||
| 2106 | rectangle._index = index; | ||
| 2107 | |||
| 2108 | 			rectangle._model = { | ||
| 2109 | datasetLabel: dataset.label, | ||
| 2110 | label: chart.data.labels[index], | ||
| 2111 | borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped, | ||
| 2112 | backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor), | ||
| 2113 | borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor), | ||
| 2114 | borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth) | ||
| 2115 | }; | ||
| 2116 | |||
| 2117 | me.updateElementGeometry(rectangle, index, reset); | ||
| 2118 | |||
| 2119 | rectangle.pivot(); | ||
| 2120 | }, | ||
| 2121 | |||
| 2122 | /** | ||
| 2123 | * @private | ||
| 2124 | */ | ||
| 2125 | 		updateElementGeometry: function(rectangle, index, reset) { | ||
| 2126 | var me = this; | ||
| 2127 | var model = rectangle._model; | ||
| 2128 | var vscale = me.getValueScale(); | ||
| 2129 | var base = vscale.getBasePixel(); | ||
| 2130 | var horizontal = vscale.isHorizontal(); | ||
| 2131 | var ruler = me._ruler || me.getRuler(); | ||
| 2132 | var vpixels = me.calculateBarValuePixels(me.index, index); | ||
| 2133 | var ipixels = me.calculateBarIndexPixels(me.index, index, ruler); | ||
| 2134 | |||
| 2135 | model.horizontal = horizontal; | ||
| 2136 | model.base = reset ? base : vpixels.base; | ||
| 2137 | model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; | ||
| 2138 | model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; | ||
| 2139 | model.height = horizontal ? ipixels.size : undefined; | ||
| 2140 | model.width = horizontal ? undefined : ipixels.size; | ||
| 2141 | }, | ||
| 2142 | |||
| 2143 | /** | ||
| 2144 | * @private | ||
| 2145 | */ | ||
| 2146 | 		getValueScaleId: function() { | ||
| 2147 | return this.getMeta().yAxisID; | ||
| 2148 | }, | ||
| 2149 | |||
| 2150 | /** | ||
| 2151 | * @private | ||
| 2152 | */ | ||
| 2153 | 		getIndexScaleId: function() { | ||
| 2154 | return this.getMeta().xAxisID; | ||
| 2155 | }, | ||
| 2156 | |||
| 2157 | /** | ||
| 2158 | * @private | ||
| 2159 | */ | ||
| 2160 | 		getValueScale: function() { | ||
| 2161 | return this.getScaleForId(this.getValueScaleId()); | ||
| 2162 | }, | ||
| 2163 | |||
| 2164 | /** | ||
| 2165 | * @private | ||
| 2166 | */ | ||
| 2167 | 		getIndexScale: function() { | ||
| 2168 | return this.getScaleForId(this.getIndexScaleId()); | ||
| 2169 | }, | ||
| 2170 | |||
| 2171 | /** | ||
| 2172 | * Returns the stacks based on groups and bar visibility. | ||
| 2173 | 		 * @param {Number} [last] - The dataset index | ||
| 2174 | 		 * @returns {Array} The stack list | ||
| 2175 | * @private | ||
| 2176 | */ | ||
| 2177 | 		_getStacks: function(last) { | ||
| 2178 | var me = this; | ||
| 2179 | var chart = me.chart; | ||
| 2180 | var scale = me.getIndexScale(); | ||
| 2181 | var stacked = scale.options.stacked; | ||
| 2182 | var ilen = last === undefined ? chart.data.datasets.length : last + 1; | ||
| 2183 | var stacks = []; | ||
| 2184 | var i, meta; | ||
| 2185 | |||
| 2186 | 			for (i = 0; i < ilen; ++i) { | ||
| 2187 | meta = chart.getDatasetMeta(i); | ||
| 2188 | if (meta.bar && chart.isDatasetVisible(i) && | ||
| 2189 | (stacked === false || | ||
| 2190 | (stacked === true && stacks.indexOf(meta.stack) === -1) || | ||
| 2191 | 					(stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { | ||
| 2192 | stacks.push(meta.stack); | ||
| 2193 | } | ||
| 2194 | } | ||
| 2195 | |||
| 2196 | return stacks; | ||
| 2197 | }, | ||
| 2198 | |||
| 2199 | /** | ||
| 2200 | * Returns the effective number of stacks based on groups and bar visibility. | ||
| 2201 | * @private | ||
| 2202 | */ | ||
| 2203 | 		getStackCount: function() { | ||
| 2204 | return this._getStacks().length; | ||
| 2205 | }, | ||
| 2206 | |||
| 2207 | /** | ||
| 2208 | * Returns the stack index for the given dataset based on groups and bar visibility. | ||
| 2209 | 		 * @param {Number} [datasetIndex] - The dataset index | ||
| 2210 | 		 * @param {String} [name] - The stack name to find | ||
| 2211 | 		 * @returns {Number} The stack index | ||
| 2212 | * @private | ||
| 2213 | */ | ||
| 2214 | 		getStackIndex: function(datasetIndex, name) { | ||
| 2215 | var stacks = this._getStacks(datasetIndex); | ||
| 2216 | var index = (name !== undefined) | ||
| 2217 | ? stacks.indexOf(name) | ||
| 2218 | : -1; // indexOf returns -1 if element is not present | ||
| 2219 | |||
| 2220 | return (index === -1) | ||
| 2221 | ? stacks.length - 1 | ||
| 2222 | : index; | ||
| 2223 | }, | ||
| 2224 | |||
| 2225 | /** | ||
| 2226 | * @private | ||
| 2227 | */ | ||
| 2228 | 		getRuler: function() { | ||
| 2229 | var me = this; | ||
| 2230 | var scale = me.getIndexScale(); | ||
| 2231 | var stackCount = me.getStackCount(); | ||
| 2232 | var datasetIndex = me.index; | ||
| 2233 | var isHorizontal = scale.isHorizontal(); | ||
| 2234 | var start = isHorizontal ? scale.left : scale.top; | ||
| 2235 | var end = start + (isHorizontal ? scale.width : scale.height); | ||
| 2236 | var pixels = []; | ||
| 2237 | var i, ilen, min; | ||
| 2238 | |||
| 2239 | 			for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { | ||
| 2240 | pixels.push(scale.getPixelForValue(null, i, datasetIndex)); | ||
| 2241 | } | ||
| 2242 | |||
| 2243 | min = helpers.isNullOrUndef(scale.options.barThickness) | ||
| 2244 | ? computeMinSampleSize(scale, pixels) | ||
| 2245 | : -1; | ||
| 2246 | |||
| 2247 | 			return { | ||
| 2248 | min: min, | ||
| 2249 | pixels: pixels, | ||
| 2250 | start: start, | ||
| 2251 | end: end, | ||
| 2252 | stackCount: stackCount, | ||
| 2253 | scale: scale | ||
| 2254 | }; | ||
| 2255 | }, | ||
| 2256 | |||
| 2257 | /** | ||
| 2258 | * Note: pixel values are not clamped to the scale area. | ||
| 2259 | * @private | ||
| 2260 | */ | ||
| 2261 | 		calculateBarValuePixels: function(datasetIndex, index) { | ||
| 2262 | var me = this; | ||
| 2263 | var chart = me.chart; | ||
| 2264 | var meta = me.getMeta(); | ||
| 2265 | var scale = me.getValueScale(); | ||
| 2266 | var datasets = chart.data.datasets; | ||
| 2267 | var value = scale.getRightValue(datasets[datasetIndex].data[index]); | ||
| 2268 | var stacked = scale.options.stacked; | ||
| 2269 | var stack = meta.stack; | ||
| 2270 | var start = 0; | ||
| 2271 | var i, imeta, ivalue, base, head, size; | ||
| 2272 | |||
| 2273 | 			if (stacked || (stacked === undefined && stack !== undefined)) { | ||
| 2274 | 				for (i = 0; i < datasetIndex; ++i) { | ||
| 2275 | imeta = chart.getDatasetMeta(i); | ||
| 2276 | |||
| 2277 | if (imeta.bar && | ||
| 2278 | imeta.stack === stack && | ||
| 2279 | imeta.controller.getValueScaleId() === scale.id && | ||
| 2280 | 						chart.isDatasetVisible(i)) { | ||
| 2281 | |||
| 2282 | ivalue = scale.getRightValue(datasets[i].data[index]); | ||
| 2283 | 						if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { | ||
| 2284 | start += ivalue; | ||
| 2285 | } | ||
| 2286 | } | ||
| 2287 | } | ||
| 2288 | } | ||
| 2289 | |||
| 2290 | base = scale.getPixelForValue(start); | ||
| 2291 | head = scale.getPixelForValue(start + value); | ||
| 2292 | size = (head - base) / 2; | ||
| 2293 | |||
| 2294 | 			return { | ||
| 2295 | size: size, | ||
| 2296 | base: base, | ||
| 2297 | head: head, | ||
| 2298 | center: head + size / 2 | ||
| 2299 | }; | ||
| 2300 | }, | ||
| 2301 | |||
| 2302 | /** | ||
| 2303 | * @private | ||
| 2304 | */ | ||
| 2305 | 		calculateBarIndexPixels: function(datasetIndex, index, ruler) { | ||
| 2306 | var me = this; | ||
| 2307 | var options = ruler.scale.options; | ||
| 2308 | var range = options.barThickness === 'flex' | ||
| 2309 | ? computeFlexCategoryTraits(index, ruler, options) | ||
| 2310 | : computeFitCategoryTraits(index, ruler, options); | ||
| 2311 | |||
| 2312 | var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); | ||
| 2313 | var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); | ||
| 2314 | var size = Math.min( | ||
| 2315 | helpers.valueOrDefault(options.maxBarThickness, Infinity), | ||
| 2316 | range.chunk * range.ratio); | ||
| 2317 | |||
| 2318 | 			return { | ||
| 2319 | base: center - size / 2, | ||
| 2320 | head: center + size / 2, | ||
| 2321 | center: center, | ||
| 2322 | size: size | ||
| 2323 | }; | ||
| 2324 | }, | ||
| 2325 | |||
| 2326 | 		draw: function() { | ||
| 2327 | var me = this; | ||
| 2328 | var chart = me.chart; | ||
| 2329 | var scale = me.getValueScale(); | ||
| 2330 | var rects = me.getMeta().data; | ||
| 2331 | var dataset = me.getDataset(); | ||
| 2332 | var ilen = rects.length; | ||
| 2333 | var i = 0; | ||
| 2334 | |||
| 2335 | helpers.canvas.clipArea(chart.ctx, chart.chartArea); | ||
| 2336 | |||
| 2337 | 			for (; i < ilen; ++i) { | ||
| 2338 | 				if (!isNaN(scale.getRightValue(dataset.data[i]))) { | ||
| 2339 | rects[i].draw(); | ||
| 2340 | } | ||
| 2341 | } | ||
| 2342 | |||
| 2343 | helpers.canvas.unclipArea(chart.ctx); | ||
| 2344 | }, | ||
| 2345 | }); | ||
| 2346 | |||
| 2347 | 	Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ | ||
| 2348 | /** | ||
| 2349 | * @private | ||
| 2350 | */ | ||
| 2351 | 		getValueScaleId: function() { | ||
| 2352 | return this.getMeta().xAxisID; | ||
| 2353 | }, | ||
| 2354 | |||
| 2355 | /** | ||
| 2356 | * @private | ||
| 2357 | */ | ||
| 2358 | 		getIndexScaleId: function() { | ||
| 2359 | return this.getMeta().yAxisID; | ||
| 2360 | } | ||
| 2361 | }); | ||
| 2362 | }; | ||
| 2363 | |||
| 2364 | },{"26":26,"41":41,"46":46}],16:[function(require,module,exports){ | ||
| 2365 | 'use strict'; | ||
| 2366 | |||
| 2367 | var defaults = require(26); | ||
| 2368 | var elements = require(41); | ||
| 2369 | var helpers = require(46); | ||
| 2370 | |||
| 2371 | defaults._set('bubble', { | ||
| 2372 | 	hover: { | ||
| 2373 | mode: 'single' | ||
| 2374 | }, | ||
| 2375 | |||
| 2376 | 	scales: { | ||
| 2377 | 		xAxes: [{ | ||
| 2378 | type: 'linear', // bubble should probably use a linear scale by default | ||
| 2379 | position: 'bottom', | ||
| 2380 | id: 'x-axis-0' // need an ID so datasets can reference the scale | ||
| 2381 | }], | ||
| 2382 | 		yAxes: [{ | ||
| 2383 | type: 'linear', | ||
| 2384 | position: 'left', | ||
| 2385 | id: 'y-axis-0' | ||
| 2386 | }] | ||
| 2387 | }, | ||
| 2388 | |||
| 2389 | 	tooltips: { | ||
| 2390 | 		callbacks: { | ||
| 2391 | 			title: function() { | ||
| 2392 | // Title doesn't make sense for scatter since we format the data as a point | ||
| 2393 | return ''; | ||
| 2394 | }, | ||
| 2395 | 			label: function(item, data) { | ||
| 2396 | var datasetLabel = data.datasets[item.datasetIndex].label || ''; | ||
| 2397 | var dataPoint = data.datasets[item.datasetIndex].data[item.index]; | ||
| 2398 | 				return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; | ||
| 2399 | } | ||
| 2400 | } | ||
| 2401 | } | ||
| 2402 | }); | ||
| 2403 | |||
| 2404 | |||
| 2405 | module.exports = function(Chart) { | ||
| 2406 | |||
| 2407 | 	Chart.controllers.bubble = Chart.DatasetController.extend({ | ||
| 2408 | /** | ||
| 2409 | * @protected | ||
| 2410 | */ | ||
| 2411 | dataElementType: elements.Point, | ||
| 2412 | |||
| 2413 | /** | ||
| 2414 | * @protected | ||
| 2415 | */ | ||
| 2416 | 		update: function(reset) { | ||
| 2417 | var me = this; | ||
| 2418 | var meta = me.getMeta(); | ||
| 2419 | var points = meta.data; | ||
| 2420 | |||
| 2421 | // Update Points | ||
| 2422 | 			helpers.each(points, function(point, index) { | ||
| 2423 | me.updateElement(point, index, reset); | ||
| 2424 | }); | ||
| 2425 | }, | ||
| 2426 | |||
| 2427 | /** | ||
| 2428 | * @protected | ||
| 2429 | */ | ||
| 2430 | 		updateElement: function(point, index, reset) { | ||
| 2431 | var me = this; | ||
| 2432 | var meta = me.getMeta(); | ||
| 2433 | 			var custom = point.custom || {}; | ||
| 2434 | var xScale = me.getScaleForId(meta.xAxisID); | ||
| 2435 | var yScale = me.getScaleForId(meta.yAxisID); | ||
| 2436 | var options = me._resolveElementOptions(point, index); | ||
| 2437 | var data = me.getDataset().data[index]; | ||
| 2438 | var dsIndex = me.index; | ||
| 2439 | |||
| 2440 | var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); | ||
| 2441 | var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); | ||
| 2442 | |||
| 2443 | point._xScale = xScale; | ||
| 2444 | point._yScale = yScale; | ||
| 2445 | point._options = options; | ||
| 2446 | point._datasetIndex = dsIndex; | ||
| 2447 | point._index = index; | ||
| 2448 | 			point._model = { | ||
| 2449 | backgroundColor: options.backgroundColor, | ||
| 2450 | borderColor: options.borderColor, | ||
| 2451 | borderWidth: options.borderWidth, | ||
| 2452 | hitRadius: options.hitRadius, | ||
| 2453 | pointStyle: options.pointStyle, | ||
| 2454 | rotation: options.rotation, | ||
| 2455 | radius: reset ? 0 : options.radius, | ||
| 2456 | skip: custom.skip || isNaN(x) || isNaN(y), | ||
| 2457 | x: x, | ||
| 2458 | y: y, | ||
| 2459 | }; | ||
| 2460 | |||
| 2461 | point.pivot(); | ||
| 2462 | }, | ||
| 2463 | |||
| 2464 | /** | ||
| 2465 | * @protected | ||
| 2466 | */ | ||
| 2467 | 		setHoverStyle: function(point) { | ||
| 2468 | var model = point._model; | ||
| 2469 | var options = point._options; | ||
| 2470 | 			point.$previousStyle = { | ||
| 2471 | backgroundColor: model.backgroundColor, | ||
| 2472 | borderColor: model.borderColor, | ||
| 2473 | borderWidth: model.borderWidth, | ||
| 2474 | radius: model.radius | ||
| 2475 | }; | ||
| 2476 | model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor)); | ||
| 2477 | model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor)); | ||
| 2478 | model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth); | ||
| 2479 | model.radius = options.radius + options.hoverRadius; | ||
| 2480 | }, | ||
| 2481 | |||
| 2482 | /** | ||
| 2483 | * @private | ||
| 2484 | */ | ||
| 2485 | 		_resolveElementOptions: function(point, index) { | ||
| 2486 | var me = this; | ||
| 2487 | var chart = me.chart; | ||
| 2488 | var datasets = chart.data.datasets; | ||
| 2489 | var dataset = datasets[me.index]; | ||
| 2490 | 			var custom = point.custom || {}; | ||
| 2491 | var options = chart.options.elements.point; | ||
| 2492 | var resolve = helpers.options.resolve; | ||
| 2493 | var data = dataset.data[index]; | ||
| 2494 | 			var values = {}; | ||
| 2495 | var i, ilen, key; | ||
| 2496 | |||
| 2497 | // Scriptable options | ||
| 2498 | 			var context = { | ||
| 2499 | chart: chart, | ||
| 2500 | dataIndex: index, | ||
| 2501 | dataset: dataset, | ||
| 2502 | datasetIndex: me.index | ||
| 2503 | }; | ||
| 2504 | |||
| 2505 | var keys = [ | ||
| 2506 | 'backgroundColor', | ||
| 2507 | 'borderColor', | ||
| 2508 | 'borderWidth', | ||
| 2509 | 'hoverBackgroundColor', | ||
| 2510 | 'hoverBorderColor', | ||
| 2511 | 'hoverBorderWidth', | ||
| 2512 | 'hoverRadius', | ||
| 2513 | 'hitRadius', | ||
| 2514 | 'pointStyle', | ||
| 2515 | 'rotation' | ||
| 2516 | ]; | ||
| 2517 | |||
| 2518 | 			for (i = 0, ilen = keys.length; i < ilen; ++i) { | ||
| 2519 | key = keys[i]; | ||
| 2520 | values[key] = resolve([ | ||
| 2521 | custom[key], | ||
| 2522 | dataset[key], | ||
| 2523 | options[key] | ||
| 2524 | ], context, index); | ||
| 2525 | } | ||
| 2526 | |||
| 2527 | // Custom radius resolution | ||
| 2528 | values.radius = resolve([ | ||
| 2529 | custom.radius, | ||
| 2530 | data ? data.r : undefined, | ||
| 2531 | dataset.radius, | ||
| 2532 | options.radius | ||
| 2533 | ], context, index); | ||
| 2534 | return values; | ||
| 2535 | } | ||
| 2536 | }); | ||
| 2537 | }; | ||
| 2538 | |||
| 2539 | },{"26":26,"41":41,"46":46}],17:[function(require,module,exports){ | ||
| 2540 | 'use strict'; | ||
| 2541 | |||
| 2542 | var defaults = require(26); | ||
| 2543 | var elements = require(41); | ||
| 2544 | var helpers = require(46); | ||
| 2545 | |||
| 2546 | defaults._set('doughnut', { | ||
| 2547 | 	animation: { | ||
| 2548 | // Boolean - Whether we animate the rotation of the Doughnut | ||
| 2549 | animateRotate: true, | ||
| 2550 | // Boolean - Whether we animate scaling the Doughnut from the centre | ||
| 2551 | animateScale: false | ||
| 2552 | }, | ||
| 2553 | 	hover: { | ||
| 2554 | mode: 'single' | ||
| 2555 | }, | ||
| 2556 | 	legendCallback: function(chart) { | ||
| 2557 | var text = []; | ||
| 2558 | 		text.push('<ul class="' + chart.id + '-legend">'); | ||
| 2559 | |||
| 2560 | var data = chart.data; | ||
| 2561 | var datasets = data.datasets; | ||
| 2562 | var labels = data.labels; | ||
| 2563 | |||
| 2564 | 		if (datasets.length) { | ||
| 2565 | 			for (var i = 0; i < datasets[0].data.length; ++i) { | ||
| 2566 | 				text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); | ||
| 2567 | 				if (labels[i]) { | ||
| 2568 | text.push(labels[i]); | ||
| 2569 | } | ||
| 2570 | 				text.push('</li>'); | ||
| 2571 | } | ||
| 2572 | } | ||
| 2573 | |||
| 2574 | 		text.push('</ul>'); | ||
| 2575 | 		return text.join(''); | ||
| 2576 | }, | ||
| 2577 | 	legend: { | ||
| 2578 | 		labels: { | ||
| 2579 | 			generateLabels: function(chart) { | ||
| 2580 | var data = chart.data; | ||
| 2581 | 				if (data.labels.length && data.datasets.length) { | ||
| 2582 | 					return data.labels.map(function(label, i) { | ||
| 2583 | var meta = chart.getDatasetMeta(0); | ||
| 2584 | var ds = data.datasets[0]; | ||
| 2585 | var arc = meta.data[i]; | ||
| 2586 | 						var custom = arc && arc.custom || {}; | ||
| 2587 | var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; | ||
| 2588 | var arcOpts = chart.options.elements.arc; | ||
| 2589 | var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); | ||
| 2590 | var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); | ||
| 2591 | var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); | ||
| 2592 | |||
| 2593 | 						return { | ||
| 2594 | text: label, | ||
| 2595 | fillStyle: fill, | ||
| 2596 | strokeStyle: stroke, | ||
| 2597 | lineWidth: bw, | ||
| 2598 | hidden: isNaN(ds.data[i]) || meta.data[i].hidden, | ||
| 2599 | |||
| 2600 | // Extra data used for toggling the correct item | ||
| 2601 | index: i | ||
| 2602 | }; | ||
| 2603 | }); | ||
| 2604 | } | ||
| 2605 | return []; | ||
| 2606 | } | ||
| 2607 | }, | ||
| 2608 | |||
| 2609 | 		onClick: function(e, legendItem) { | ||
| 2610 | var index = legendItem.index; | ||
| 2611 | var chart = this.chart; | ||
| 2612 | var i, ilen, meta; | ||
| 2613 | |||
| 2614 | 			for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { | ||
| 2615 | meta = chart.getDatasetMeta(i); | ||
| 2616 | // toggle visibility of index if exists | ||
| 2617 | 				if (meta.data[index]) { | ||
| 2618 | meta.data[index].hidden = !meta.data[index].hidden; | ||
| 2619 | } | ||
| 2620 | } | ||
| 2621 | |||
| 2622 | chart.update(); | ||
| 2623 | } | ||
| 2624 | }, | ||
| 2625 | |||
| 2626 | // The percentage of the chart that we cut out of the middle. | ||
| 2627 | cutoutPercentage: 50, | ||
| 2628 | |||
| 2629 | // The rotation of the chart, where the first data arc begins. | ||
| 2630 | rotation: Math.PI * -0.5, | ||
| 2631 | |||
| 2632 | // The total circumference of the chart. | ||
| 2633 | circumference: Math.PI * 2.0, | ||
| 2634 | |||
| 2635 | // Need to override these to give a nice default | ||
| 2636 | 	tooltips: { | ||
| 2637 | 		callbacks: { | ||
| 2638 | 			title: function() { | ||
| 2639 | return ''; | ||
| 2640 | }, | ||
| 2641 | 			label: function(tooltipItem, data) { | ||
| 2642 | var dataLabel = data.labels[tooltipItem.index]; | ||
| 2643 | var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; | ||
| 2644 | |||
| 2645 | 				if (helpers.isArray(dataLabel)) { | ||
| 2646 | // show value on first line of multiline label | ||
| 2647 | // need to clone because we are changing the value | ||
| 2648 | dataLabel = dataLabel.slice(); | ||
| 2649 | dataLabel[0] += value; | ||
| 2650 | 				} else { | ||
| 2651 | dataLabel += value; | ||
| 2652 | } | ||
| 2653 | |||
| 2654 | return dataLabel; | ||
| 2655 | } | ||
| 2656 | } | ||
| 2657 | } | ||
| 2658 | }); | ||
| 2659 | |||
| 2660 | defaults._set('pie', helpers.clone(defaults.doughnut)); | ||
| 2661 | defaults._set('pie', { | ||
| 2662 | cutoutPercentage: 0 | ||
| 2663 | }); | ||
| 2664 | |||
| 2665 | module.exports = function(Chart) { | ||
| 2666 | |||
| 2667 | 	Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ | ||
| 2668 | |||
| 2669 | dataElementType: elements.Arc, | ||
| 2670 | |||
| 2671 | linkScales: helpers.noop, | ||
| 2672 | |||
| 2673 | // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly | ||
| 2674 | 		getRingIndex: function(datasetIndex) { | ||
| 2675 | var ringIndex = 0; | ||
| 2676 | |||
| 2677 | 			for (var j = 0; j < datasetIndex; ++j) { | ||
| 2678 | 				if (this.chart.isDatasetVisible(j)) { | ||
| 2679 | ++ringIndex; | ||
| 2680 | } | ||
| 2681 | } | ||
| 2682 | |||
| 2683 | return ringIndex; | ||
| 2684 | }, | ||
| 2685 | |||
| 2686 | 		update: function(reset) { | ||
| 2687 | var me = this; | ||
| 2688 | var chart = me.chart; | ||
| 2689 | var chartArea = chart.chartArea; | ||
| 2690 | var opts = chart.options; | ||
| 2691 | var arcOpts = opts.elements.arc; | ||
| 2692 | var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth; | ||
| 2693 | var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth; | ||
| 2694 | var minSize = Math.min(availableWidth, availableHeight); | ||
| 2695 | 			var offset = {x: 0, y: 0}; | ||
| 2696 | var meta = me.getMeta(); | ||
| 2697 | var cutoutPercentage = opts.cutoutPercentage; | ||
| 2698 | var circumference = opts.circumference; | ||
| 2699 | |||
| 2700 | // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc | ||
| 2701 | 			if (circumference < Math.PI * 2.0) { | ||
| 2702 | var startAngle = opts.rotation % (Math.PI * 2.0); | ||
| 2703 | startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); | ||
| 2704 | var endAngle = startAngle + circumference; | ||
| 2705 | 				var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; | ||
| 2706 | 				var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; | ||
| 2707 | var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); | ||
| 2708 | var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); | ||
| 2709 | var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); | ||
| 2710 | var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); | ||
| 2711 | var cutout = cutoutPercentage / 100.0; | ||
| 2712 | 				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))}; | ||
| 2713 | 				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))}; | ||
| 2714 | 				var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; | ||
| 2715 | minSize = Math.min(availableWidth / size.width, availableHeight / size.height); | ||
| 2716 | 				offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; | ||
| 2717 | } | ||
| 2718 | |||
| 2719 | chart.borderWidth = me.getMaxBorderWidth(meta.data); | ||
| 2720 | chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); | ||
| 2721 | chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); | ||
| 2722 | chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); | ||
| 2723 | chart.offsetX = offset.x * chart.outerRadius; | ||
| 2724 | chart.offsetY = offset.y * chart.outerRadius; | ||
| 2725 | |||
| 2726 | meta.total = me.calculateTotal(); | ||
| 2727 | |||
| 2728 | me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); | ||
| 2729 | me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); | ||
| 2730 | |||
| 2731 | 			helpers.each(meta.data, function(arc, index) { | ||
| 2732 | me.updateElement(arc, index, reset); | ||
| 2733 | }); | ||
| 2734 | }, | ||
| 2735 | |||
| 2736 | 		updateElement: function(arc, index, reset) { | ||
| 2737 | var me = this; | ||
| 2738 | var chart = me.chart; | ||
| 2739 | var chartArea = chart.chartArea; | ||
| 2740 | var opts = chart.options; | ||
| 2741 | var animationOpts = opts.animation; | ||
| 2742 | var centerX = (chartArea.left + chartArea.right) / 2; | ||
| 2743 | var centerY = (chartArea.top + chartArea.bottom) / 2; | ||
| 2744 | var startAngle = opts.rotation; // non reset case handled later | ||
| 2745 | var endAngle = opts.rotation; // non reset case handled later | ||
| 2746 | var dataset = me.getDataset(); | ||
| 2747 | var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)); | ||
| 2748 | var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; | ||
| 2749 | var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; | ||
| 2750 | var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; | ||
| 2751 | |||
| 2752 | 			helpers.extend(arc, { | ||
| 2753 | // Utility | ||
| 2754 | _datasetIndex: me.index, | ||
| 2755 | _index: index, | ||
| 2756 | |||
| 2757 | // Desired view properties | ||
| 2758 | 				_model: { | ||
| 2759 | x: centerX + chart.offsetX, | ||
| 2760 | y: centerY + chart.offsetY, | ||
| 2761 | startAngle: startAngle, | ||
| 2762 | endAngle: endAngle, | ||
| 2763 | circumference: circumference, | ||
| 2764 | outerRadius: outerRadius, | ||
| 2765 | innerRadius: innerRadius, | ||
| 2766 | label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) | ||
| 2767 | } | ||
| 2768 | }); | ||
| 2769 | |||
| 2770 | var model = arc._model; | ||
| 2771 | |||
| 2772 | // Resets the visual styles | ||
| 2773 | 			var custom = arc.custom || {}; | ||
| 2774 | var valueOrDefault = helpers.valueAtIndexOrDefault; | ||
| 2775 | var elementOpts = this.chart.options.elements.arc; | ||
| 2776 | model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); | ||
| 2777 | model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); | ||
| 2778 | model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); | ||
| 2779 | |||
| 2780 | // Set correct angles if not resetting | ||
| 2781 | 			if (!reset || !animationOpts.animateRotate) { | ||
| 2782 | 				if (index === 0) { | ||
| 2783 | model.startAngle = opts.rotation; | ||
| 2784 | 				} else { | ||
| 2785 | model.startAngle = me.getMeta().data[index - 1]._model.endAngle; | ||
| 2786 | } | ||
| 2787 | |||
| 2788 | model.endAngle = model.startAngle + model.circumference; | ||
| 2789 | } | ||
| 2790 | |||
| 2791 | arc.pivot(); | ||
| 2792 | }, | ||
| 2793 | |||
| 2794 | 		calculateTotal: function() { | ||
| 2795 | var dataset = this.getDataset(); | ||
| 2796 | var meta = this.getMeta(); | ||
| 2797 | var total = 0; | ||
| 2798 | var value; | ||
| 2799 | |||
| 2800 | 			helpers.each(meta.data, function(element, index) { | ||
| 2801 | value = dataset.data[index]; | ||
| 2802 | 				if (!isNaN(value) && !element.hidden) { | ||
| 2803 | total += Math.abs(value); | ||
| 2804 | } | ||
| 2805 | }); | ||
| 2806 | |||
| 2807 | 			/* if (total === 0) { | ||
| 2808 | total = NaN; | ||
| 2809 | }*/ | ||
| 2810 | |||
| 2811 | return total; | ||
| 2812 | }, | ||
| 2813 | |||
| 2814 | 		calculateCircumference: function(value) { | ||
| 2815 | var total = this.getMeta().total; | ||
| 2816 | 			if (total > 0 && !isNaN(value)) { | ||
| 2817 | return (Math.PI * 2.0) * (Math.abs(value) / total); | ||
| 2818 | } | ||
| 2819 | return 0; | ||
| 2820 | }, | ||
| 2821 | |||
| 2822 | // gets the max border or hover width to properly scale pie charts | ||
| 2823 | 		getMaxBorderWidth: function(arcs) { | ||
| 2824 | var max = 0; | ||
| 2825 | var index = this.index; | ||
| 2826 | var length = arcs.length; | ||
| 2827 | var borderWidth; | ||
| 2828 | var hoverWidth; | ||
| 2829 | |||
| 2830 | 			for (var i = 0; i < length; i++) { | ||
| 2831 | borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0; | ||
| 2832 | hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; | ||
| 2833 | |||
| 2834 | max = borderWidth > max ? borderWidth : max; | ||
| 2835 | max = hoverWidth > max ? hoverWidth : max; | ||
| 2836 | } | ||
| 2837 | return max; | ||
| 2838 | } | ||
| 2839 | }); | ||
| 2840 | }; | ||
| 2841 | |||
| 2842 | },{"26":26,"41":41,"46":46}],18:[function(require,module,exports){ | ||
| 2843 | 'use strict'; | ||
| 2844 | |||
| 2845 | var defaults = require(26); | ||
| 2846 | var elements = require(41); | ||
| 2847 | var helpers = require(46); | ||
| 2848 | |||
| 2849 | defaults._set('line', { | ||
| 2850 | showLines: true, | ||
| 2851 | spanGaps: false, | ||
| 2852 | |||
| 2853 | 	hover: { | ||
| 2854 | mode: 'label' | ||
| 2855 | }, | ||
| 2856 | |||
| 2857 | 	scales: { | ||
| 2858 | 		xAxes: [{ | ||
| 2859 | type: 'category', | ||
| 2860 | id: 'x-axis-0' | ||
| 2861 | }], | ||
| 2862 | 		yAxes: [{ | ||
| 2863 | type: 'linear', | ||
| 2864 | id: 'y-axis-0' | ||
| 2865 | }] | ||
| 2866 | } | ||
| 2867 | }); | ||
| 2868 | |||
| 2869 | module.exports = function(Chart) { | ||
| 2870 | |||
| 2871 | 	function lineEnabled(dataset, options) { | ||
| 2872 | return helpers.valueOrDefault(dataset.showLine, options.showLines); | ||
| 2873 | } | ||
| 2874 | |||
| 2875 | 	Chart.controllers.line = Chart.DatasetController.extend({ | ||
| 2876 | |||
| 2877 | datasetElementType: elements.Line, | ||
| 2878 | |||
| 2879 | dataElementType: elements.Point, | ||
| 2880 | |||
| 2881 | 		update: function(reset) { | ||
| 2882 | var me = this; | ||
| 2883 | var meta = me.getMeta(); | ||
| 2884 | var line = meta.dataset; | ||
| 2885 | var points = meta.data || []; | ||
| 2886 | var options = me.chart.options; | ||
| 2887 | var lineElementOptions = options.elements.line; | ||
| 2888 | var scale = me.getScaleForId(meta.yAxisID); | ||
| 2889 | var i, ilen, custom; | ||
| 2890 | var dataset = me.getDataset(); | ||
| 2891 | var showLine = lineEnabled(dataset, options); | ||
| 2892 | |||
| 2893 | // Update Line | ||
| 2894 | 			if (showLine) { | ||
| 2895 | 				custom = line.custom || {}; | ||
| 2896 | |||
| 2897 | // Compatibility: If the properties are defined with only the old name, use those values | ||
| 2898 | 				if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { | ||
| 2899 | dataset.lineTension = dataset.tension; | ||
| 2900 | } | ||
| 2901 | |||
| 2902 | // Utility | ||
| 2903 | line._scale = scale; | ||
| 2904 | line._datasetIndex = me.index; | ||
| 2905 | // Data | ||
| 2906 | line._children = points; | ||
| 2907 | // Model | ||
| 2908 | 				line._model = { | ||
| 2909 | // Appearance | ||
| 2910 | // The default behavior of lines is to break at null values, according | ||
| 2911 | // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 | ||
| 2912 | // This option gives lines the ability to span gaps | ||
| 2913 | spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, | ||
| 2914 | tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), | ||
| 2915 | backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), | ||
| 2916 | borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), | ||
| 2917 | borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), | ||
| 2918 | borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), | ||
| 2919 | borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), | ||
| 2920 | borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), | ||
| 2921 | borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), | ||
| 2922 | fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), | ||
| 2923 | steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped), | ||
| 2924 | cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode), | ||
| 2925 | }; | ||
| 2926 | |||
| 2927 | line.pivot(); | ||
| 2928 | } | ||
| 2929 | |||
| 2930 | // Update Points | ||
| 2931 | 			for (i = 0, ilen = points.length; i < ilen; ++i) { | ||
| 2932 | me.updateElement(points[i], i, reset); | ||
| 2933 | } | ||
| 2934 | |||
| 2935 | 			if (showLine && line._model.tension !== 0) { | ||
| 2936 | me.updateBezierControlPoints(); | ||
| 2937 | } | ||
| 2938 | |||
| 2939 | // Now pivot the point for animation | ||
| 2940 | 			for (i = 0, ilen = points.length; i < ilen; ++i) { | ||
| 2941 | points[i].pivot(); | ||
| 2942 | } | ||
| 2943 | }, | ||
| 2944 | |||
| 2945 | 		getPointBackgroundColor: function(point, index) { | ||
| 2946 | var backgroundColor = this.chart.options.elements.point.backgroundColor; | ||
| 2947 | var dataset = this.getDataset(); | ||
| 2948 | 			var custom = point.custom || {}; | ||
| 2949 | |||
| 2950 | 			if (custom.backgroundColor) { | ||
| 2951 | backgroundColor = custom.backgroundColor; | ||
| 2952 | 			} else if (dataset.pointBackgroundColor) { | ||
| 2953 | backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); | ||
| 2954 | 			} else if (dataset.backgroundColor) { | ||
| 2955 | backgroundColor = dataset.backgroundColor; | ||
| 2956 | } | ||
| 2957 | |||
| 2958 | return backgroundColor; | ||
| 2959 | }, | ||
| 2960 | |||
| 2961 | 		getPointBorderColor: function(point, index) { | ||
| 2962 | var borderColor = this.chart.options.elements.point.borderColor; | ||
| 2963 | var dataset = this.getDataset(); | ||
| 2964 | 			var custom = point.custom || {}; | ||
| 2965 | |||
| 2966 | 			if (custom.borderColor) { | ||
| 2967 | borderColor = custom.borderColor; | ||
| 2968 | 			} else if (dataset.pointBorderColor) { | ||
| 2969 | borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); | ||
| 2970 | 			} else if (dataset.borderColor) { | ||
| 2971 | borderColor = dataset.borderColor; | ||
| 2972 | } | ||
| 2973 | |||
| 2974 | return borderColor; | ||
| 2975 | }, | ||
| 2976 | |||
| 2977 | 		getPointBorderWidth: function(point, index) { | ||
| 2978 | var borderWidth = this.chart.options.elements.point.borderWidth; | ||
| 2979 | var dataset = this.getDataset(); | ||
| 2980 | 			var custom = point.custom || {}; | ||
| 2981 | |||
| 2982 | 			if (!isNaN(custom.borderWidth)) { | ||
| 2983 | borderWidth = custom.borderWidth; | ||
| 2984 | 			} else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) { | ||
| 2985 | borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); | ||
| 2986 | 			} else if (!isNaN(dataset.borderWidth)) { | ||
| 2987 | borderWidth = dataset.borderWidth; | ||
| 2988 | } | ||
| 2989 | |||
| 2990 | return borderWidth; | ||
| 2991 | }, | ||
| 2992 | |||
| 2993 | 		getPointRotation: function(point, index) { | ||
| 2994 | var pointRotation = this.chart.options.elements.point.rotation; | ||
| 2995 | var dataset = this.getDataset(); | ||
| 2996 | 			var custom = point.custom || {}; | ||
| 2997 | |||
| 2998 | 			if (!isNaN(custom.rotation)) { | ||
| 2999 | pointRotation = custom.rotation; | ||
| 3000 | 			} else if (!isNaN(dataset.pointRotation) || helpers.isArray(dataset.pointRotation)) { | ||
| 3001 | pointRotation = helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointRotation); | ||
| 3002 | } | ||
| 3003 | return pointRotation; | ||
| 3004 | }, | ||
| 3005 | |||
| 3006 | 		updateElement: function(point, index, reset) { | ||
| 3007 | var me = this; | ||
| 3008 | var meta = me.getMeta(); | ||
| 3009 | 			var custom = point.custom || {}; | ||
| 3010 | var dataset = me.getDataset(); | ||
| 3011 | var datasetIndex = me.index; | ||
| 3012 | var value = dataset.data[index]; | ||
| 3013 | var yScale = me.getScaleForId(meta.yAxisID); | ||
| 3014 | var xScale = me.getScaleForId(meta.xAxisID); | ||
| 3015 | var pointOptions = me.chart.options.elements.point; | ||
| 3016 | var x, y; | ||
| 3017 | |||
| 3018 | // Compatibility: If the properties are defined with only the old name, use those values | ||
| 3019 | 			if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { | ||
| 3020 | dataset.pointRadius = dataset.radius; | ||
| 3021 | } | ||
| 3022 | 			if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { | ||
| 3023 | dataset.pointHitRadius = dataset.hitRadius; | ||
| 3024 | } | ||
| 3025 | |||
| 3026 | x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); | ||
| 3027 | y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); | ||
| 3028 | |||
| 3029 | // Utility | ||
| 3030 | point._xScale = xScale; | ||
| 3031 | point._yScale = yScale; | ||
| 3032 | point._datasetIndex = datasetIndex; | ||
| 3033 | point._index = index; | ||
| 3034 | |||
| 3035 | // Desired view properties | ||
| 3036 | 			point._model = { | ||
| 3037 | x: x, | ||
| 3038 | y: y, | ||
| 3039 | skip: custom.skip || isNaN(x) || isNaN(y), | ||
| 3040 | // Appearance | ||
| 3041 | radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), | ||
| 3042 | pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), | ||
| 3043 | rotation: me.getPointRotation(point, index), | ||
| 3044 | backgroundColor: me.getPointBackgroundColor(point, index), | ||
| 3045 | borderColor: me.getPointBorderColor(point, index), | ||
| 3046 | borderWidth: me.getPointBorderWidth(point, index), | ||
| 3047 | tension: meta.dataset._model ? meta.dataset._model.tension : 0, | ||
| 3048 | steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, | ||
| 3049 | // Tooltip | ||
| 3050 | hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) | ||
| 3051 | }; | ||
| 3052 | }, | ||
| 3053 | |||
| 3054 | 		calculatePointY: function(value, index, datasetIndex) { | ||
| 3055 | var me = this; | ||
| 3056 | var chart = me.chart; | ||
| 3057 | var meta = me.getMeta(); | ||
| 3058 | var yScale = me.getScaleForId(meta.yAxisID); | ||
| 3059 | var sumPos = 0; | ||
| 3060 | var sumNeg = 0; | ||
| 3061 | var i, ds, dsMeta; | ||
| 3062 | |||
| 3063 | 			if (yScale.options.stacked) { | ||
| 3064 | 				for (i = 0; i < datasetIndex; i++) { | ||
| 3065 | ds = chart.data.datasets[i]; | ||
| 3066 | dsMeta = chart.getDatasetMeta(i); | ||
| 3067 | 					if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { | ||
| 3068 | var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); | ||
| 3069 | 						if (stackedRightValue < 0) { | ||
| 3070 | sumNeg += stackedRightValue || 0; | ||
| 3071 | 						} else { | ||
| 3072 | sumPos += stackedRightValue || 0; | ||
| 3073 | } | ||
| 3074 | } | ||
| 3075 | } | ||
| 3076 | |||
| 3077 | var rightValue = Number(yScale.getRightValue(value)); | ||
| 3078 | 				if (rightValue < 0) { | ||
| 3079 | return yScale.getPixelForValue(sumNeg + rightValue); | ||
| 3080 | } | ||
| 3081 | return yScale.getPixelForValue(sumPos + rightValue); | ||
| 3082 | } | ||
| 3083 | |||
| 3084 | return yScale.getPixelForValue(value); | ||
| 3085 | }, | ||
| 3086 | |||
| 3087 | 		updateBezierControlPoints: function() { | ||
| 3088 | var me = this; | ||
| 3089 | var meta = me.getMeta(); | ||
| 3090 | var area = me.chart.chartArea; | ||
| 3091 | var points = (meta.data || []); | ||
| 3092 | var i, ilen, point, model, controlPoints; | ||
| 3093 | |||
| 3094 | // Only consider points that are drawn in case the spanGaps option is used | ||
| 3095 | 			if (meta.dataset._model.spanGaps) { | ||
| 3096 | 				points = points.filter(function(pt) { | ||
| 3097 | return !pt._model.skip; | ||
| 3098 | }); | ||
| 3099 | } | ||
| 3100 | |||
| 3101 | 			function capControlPoint(pt, min, max) { | ||
| 3102 | return Math.max(Math.min(pt, max), min); | ||
| 3103 | } | ||
| 3104 | |||
| 3105 | 			if (meta.dataset._model.cubicInterpolationMode === 'monotone') { | ||
| 3106 | helpers.splineCurveMonotone(points); | ||
| 3107 | 			} else { | ||
| 3108 | 				for (i = 0, ilen = points.length; i < ilen; ++i) { | ||
| 3109 | point = points[i]; | ||
| 3110 | model = point._model; | ||
| 3111 | controlPoints = helpers.splineCurve( | ||
| 3112 | helpers.previousItem(points, i)._model, | ||
| 3113 | model, | ||
| 3114 | helpers.nextItem(points, i)._model, | ||
| 3115 | meta.dataset._model.tension | ||
| 3116 | ); | ||
| 3117 | model.controlPointPreviousX = controlPoints.previous.x; | ||
| 3118 | model.controlPointPreviousY = controlPoints.previous.y; | ||
| 3119 | model.controlPointNextX = controlPoints.next.x; | ||
| 3120 | model.controlPointNextY = controlPoints.next.y; | ||
| 3121 | } | ||
| 3122 | } | ||
| 3123 | |||
| 3124 | 			if (me.chart.options.elements.line.capBezierPoints) { | ||
| 3125 | 				for (i = 0, ilen = points.length; i < ilen; ++i) { | ||
| 3126 | model = points[i]._model; | ||
| 3127 | model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); | ||
| 3128 | model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); | ||
| 3129 | model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); | ||
| 3130 | model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); | ||
| 3131 | } | ||
| 3132 | } | ||
| 3133 | }, | ||
| 3134 | |||
| 3135 | 		draw: function() { | ||
| 3136 | var me = this; | ||
| 3137 | var chart = me.chart; | ||
| 3138 | var meta = me.getMeta(); | ||
| 3139 | var points = meta.data || []; | ||
| 3140 | var area = chart.chartArea; | ||
| 3141 | var ilen = points.length; | ||
| 3142 | var halfBorderWidth; | ||
| 3143 | var i = 0; | ||
| 3144 | |||
| 3145 | 			if (lineEnabled(me.getDataset(), chart.options)) { | ||
| 3146 | halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2; | ||
| 3147 | |||
| 3148 | 				helpers.canvas.clipArea(chart.ctx, { | ||
| 3149 | left: area.left, | ||
| 3150 | right: area.right, | ||
| 3151 | top: area.top - halfBorderWidth, | ||
| 3152 | bottom: area.bottom + halfBorderWidth | ||
| 3153 | }); | ||
| 3154 | |||
| 3155 | meta.dataset.draw(); | ||
| 3156 | |||
| 3157 | helpers.canvas.unclipArea(chart.ctx); | ||
| 3158 | } | ||
| 3159 | |||
| 3160 | // Draw the points | ||
| 3161 | 			for (; i < ilen; ++i) { | ||
| 3162 | points[i].draw(area); | ||
| 3163 | } | ||
| 3164 | }, | ||
| 3165 | |||
| 3166 | 		setHoverStyle: function(element) { | ||
| 3167 | // Point | ||
| 3168 | var dataset = this.chart.data.datasets[element._datasetIndex]; | ||
| 3169 | var index = element._index; | ||
| 3170 | 			var custom = element.custom || {}; | ||
| 3171 | var model = element._model; | ||
| 3172 | |||
| 3173 | 			element.$previousStyle = { | ||
| 3174 | backgroundColor: model.backgroundColor, | ||
| 3175 | borderColor: model.borderColor, | ||
| 3176 | borderWidth: model.borderWidth, | ||
| 3177 | radius: model.radius | ||
| 3178 | }; | ||
| 3179 | |||
| 3180 | model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); | ||
| 3181 | model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); | ||
| 3182 | model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); | ||
| 3183 | model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); | ||
| 3184 | }, | ||
| 3185 | }); | ||
| 3186 | }; | ||
| 3187 | |||
| 3188 | },{"26":26,"41":41,"46":46}],19:[function(require,module,exports){ | ||
| 3189 | 'use strict'; | ||
| 3190 | |||
| 3191 | var defaults = require(26); | ||
| 3192 | var elements = require(41); | ||
| 3193 | var helpers = require(46); | ||
| 3194 | |||
| 3195 | defaults._set('polarArea', { | ||
| 3196 | 	scale: { | ||
| 3197 | type: 'radialLinear', | ||
| 3198 | 		angleLines: { | ||
| 3199 | display: false | ||
| 3200 | }, | ||
| 3201 | 		gridLines: { | ||
| 3202 | circular: true | ||
| 3203 | }, | ||
| 3204 | 		pointLabels: { | ||
| 3205 | display: false | ||
| 3206 | }, | ||
| 3207 | 		ticks: { | ||
| 3208 | beginAtZero: true | ||
| 3209 | } | ||
| 3210 | }, | ||
| 3211 | |||
| 3212 | // Boolean - Whether to animate the rotation of the chart | ||
| 3213 | 	animation: { | ||
| 3214 | animateRotate: true, | ||
| 3215 | animateScale: true | ||
| 3216 | }, | ||
| 3217 | |||
| 3218 | startAngle: -0.5 * Math.PI, | ||
| 3219 | 	legendCallback: function(chart) { | ||
| 3220 | var text = []; | ||
| 3221 | 		text.push('<ul class="' + chart.id + '-legend">'); | ||
| 3222 | |||
| 3223 | var data = chart.data; | ||
| 3224 | var datasets = data.datasets; | ||
| 3225 | var labels = data.labels; | ||
| 3226 | |||
| 3227 | 		if (datasets.length) { | ||
| 3228 | 			for (var i = 0; i < datasets[0].data.length; ++i) { | ||
| 3229 | 				text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); | ||
| 3230 | 				if (labels[i]) { | ||
| 3231 | text.push(labels[i]); | ||
| 3232 | } | ||
| 3233 | 				text.push('</li>'); | ||
| 3234 | } | ||
| 3235 | } | ||
| 3236 | |||
| 3237 | 		text.push('</ul>'); | ||
| 3238 | 		return text.join(''); | ||
| 3239 | }, | ||
| 3240 | 	legend: { | ||
| 3241 | 		labels: { | ||
| 3242 | 			generateLabels: function(chart) { | ||
| 3243 | var data = chart.data; | ||
| 3244 | 				if (data.labels.length && data.datasets.length) { | ||
| 3245 | 					return data.labels.map(function(label, i) { | ||
| 3246 | var meta = chart.getDatasetMeta(0); | ||
| 3247 | var ds = data.datasets[0]; | ||
| 3248 | var arc = meta.data[i]; | ||
| 3249 | 						var custom = arc.custom || {}; | ||
| 3250 | var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; | ||
| 3251 | var arcOpts = chart.options.elements.arc; | ||
| 3252 | var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); | ||
| 3253 | var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); | ||
| 3254 | var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); | ||
| 3255 | |||
| 3256 | 						return { | ||
| 3257 | text: label, | ||
| 3258 | fillStyle: fill, | ||
| 3259 | strokeStyle: stroke, | ||
| 3260 | lineWidth: bw, | ||
| 3261 | hidden: isNaN(ds.data[i]) || meta.data[i].hidden, | ||
| 3262 | |||
| 3263 | // Extra data used for toggling the correct item | ||
| 3264 | index: i | ||
| 3265 | }; | ||
| 3266 | }); | ||
| 3267 | } | ||
| 3268 | return []; | ||
| 3269 | } | ||
| 3270 | }, | ||
| 3271 | |||
| 3272 | 		onClick: function(e, legendItem) { | ||
| 3273 | var index = legendItem.index; | ||
| 3274 | var chart = this.chart; | ||
| 3275 | var i, ilen, meta; | ||
| 3276 | |||
| 3277 | 			for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { | ||
| 3278 | meta = chart.getDatasetMeta(i); | ||
| 3279 | meta.data[index].hidden = !meta.data[index].hidden; | ||
| 3280 | } | ||
| 3281 | |||
| 3282 | chart.update(); | ||
| 3283 | } | ||
| 3284 | }, | ||
| 3285 | |||
| 3286 | // Need to override these to give a nice default | ||
| 3287 | 	tooltips: { | ||
| 3288 | 		callbacks: { | ||
| 3289 | 			title: function() { | ||
| 3290 | return ''; | ||
| 3291 | }, | ||
| 3292 | 			label: function(item, data) { | ||
| 3293 | return data.labels[item.index] + ': ' + item.yLabel; | ||
| 3294 | } | ||
| 3295 | } | ||
| 3296 | } | ||
| 3297 | }); | ||
| 3298 | |||
| 3299 | module.exports = function(Chart) { | ||
| 3300 | |||
| 3301 | 	Chart.controllers.polarArea = Chart.DatasetController.extend({ | ||
| 3302 | |||
| 3303 | dataElementType: elements.Arc, | ||
| 3304 | |||
| 3305 | linkScales: helpers.noop, | ||
| 3306 | |||
| 3307 | 		update: function(reset) { | ||
| 3308 | var me = this; | ||
| 3309 | var dataset = me.getDataset(); | ||
| 3310 | var meta = me.getMeta(); | ||
| 3311 | var start = me.chart.options.startAngle || 0; | ||
| 3312 | var starts = me._starts = []; | ||
| 3313 | var angles = me._angles = []; | ||
| 3314 | var i, ilen, angle; | ||
| 3315 | |||
| 3316 | me._updateRadius(); | ||
| 3317 | |||
| 3318 | meta.count = me.countVisibleElements(); | ||
| 3319 | |||
| 3320 | 			for (i = 0, ilen = dataset.data.length; i < ilen; i++) { | ||
| 3321 | starts[i] = start; | ||
| 3322 | angle = me._computeAngle(i); | ||
| 3323 | angles[i] = angle; | ||
| 3324 | start += angle; | ||
| 3325 | } | ||
| 3326 | |||
| 3327 | 			helpers.each(meta.data, function(arc, index) { | ||
| 3328 | me.updateElement(arc, index, reset); | ||
| 3329 | }); | ||
| 3330 | }, | ||
| 3331 | |||
| 3332 | /** | ||
| 3333 | * @private | ||
| 3334 | */ | ||
| 3335 | 		_updateRadius: function() { | ||
| 3336 | var me = this; | ||
| 3337 | var chart = me.chart; | ||
| 3338 | var chartArea = chart.chartArea; | ||
| 3339 | var opts = chart.options; | ||
| 3340 | var arcOpts = opts.elements.arc; | ||
| 3341 | var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); | ||
| 3342 | |||
| 3343 | chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); | ||
| 3344 | chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); | ||
| 3345 | chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); | ||
| 3346 | |||
| 3347 | me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); | ||
| 3348 | me.innerRadius = me.outerRadius - chart.radiusLength; | ||
| 3349 | }, | ||
| 3350 | |||
| 3351 | 		updateElement: function(arc, index, reset) { | ||
| 3352 | var me = this; | ||
| 3353 | var chart = me.chart; | ||
| 3354 | var dataset = me.getDataset(); | ||
| 3355 | var opts = chart.options; | ||
| 3356 | var animationOpts = opts.animation; | ||
| 3357 | var scale = chart.scale; | ||
| 3358 | var labels = chart.data.labels; | ||
| 3359 | |||
| 3360 | var centerX = scale.xCenter; | ||
| 3361 | var centerY = scale.yCenter; | ||
| 3362 | |||
| 3363 | // var negHalfPI = -0.5 * Math.PI; | ||
| 3364 | var datasetStartAngle = opts.startAngle; | ||
| 3365 | var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); | ||
| 3366 | var startAngle = me._starts[index]; | ||
| 3367 | var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); | ||
| 3368 | |||
| 3369 | var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); | ||
| 3370 | |||
| 3371 | 			helpers.extend(arc, { | ||
| 3372 | // Utility | ||
| 3373 | _datasetIndex: me.index, | ||
| 3374 | _index: index, | ||
| 3375 | _scale: scale, | ||
| 3376 | |||
| 3377 | // Desired view properties | ||
| 3378 | 				_model: { | ||
| 3379 | x: centerX, | ||
| 3380 | y: centerY, | ||
| 3381 | innerRadius: 0, | ||
| 3382 | outerRadius: reset ? resetRadius : distance, | ||
| 3383 | startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, | ||
| 3384 | endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, | ||
| 3385 | label: helpers.valueAtIndexOrDefault(labels, index, labels[index]) | ||
| 3386 | } | ||
| 3387 | }); | ||
| 3388 | |||
| 3389 | // Apply border and fill style | ||
| 3390 | var elementOpts = this.chart.options.elements.arc; | ||
| 3391 | 			var custom = arc.custom || {}; | ||
| 3392 | var valueOrDefault = helpers.valueAtIndexOrDefault; | ||
| 3393 | var model = arc._model; | ||
| 3394 | |||
| 3395 | model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); | ||
| 3396 | model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); | ||
| 3397 | model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); | ||
| 3398 | |||
| 3399 | arc.pivot(); | ||
| 3400 | }, | ||
| 3401 | |||
| 3402 | 		countVisibleElements: function() { | ||
| 3403 | var dataset = this.getDataset(); | ||
| 3404 | var meta = this.getMeta(); | ||
| 3405 | var count = 0; | ||
| 3406 | |||
| 3407 | 			helpers.each(meta.data, function(element, index) { | ||
| 3408 | 				if (!isNaN(dataset.data[index]) && !element.hidden) { | ||
| 3409 | count++; | ||
| 3410 | } | ||
| 3411 | }); | ||
| 3412 | |||
| 3413 | return count; | ||
| 3414 | }, | ||
| 3415 | |||
| 3416 | /** | ||
| 3417 | * @private | ||
| 3418 | */ | ||
| 3419 | 		_computeAngle: function(index) { | ||
| 3420 | var me = this; | ||
| 3421 | var count = this.getMeta().count; | ||
| 3422 | var dataset = me.getDataset(); | ||
| 3423 | var meta = me.getMeta(); | ||
| 3424 | |||
| 3425 | 			if (isNaN(dataset.data[index]) || meta.data[index].hidden) { | ||
| 3426 | return 0; | ||
| 3427 | } | ||
| 3428 | |||
| 3429 | // Scriptable options | ||
| 3430 | 			var context = { | ||
| 3431 | chart: me.chart, | ||
| 3432 | dataIndex: index, | ||
| 3433 | dataset: dataset, | ||
| 3434 | datasetIndex: me.index | ||
| 3435 | }; | ||
| 3436 | |||
| 3437 | return helpers.options.resolve([ | ||
| 3438 | me.chart.options.elements.arc.angle, | ||
| 3439 | (2 * Math.PI) / count | ||
| 3440 | ], context, index); | ||
| 3441 | } | ||
| 3442 | }); | ||
| 3443 | }; | ||
| 3444 | |||
| 3445 | },{"26":26,"41":41,"46":46}],20:[function(require,module,exports){ | ||
| 3446 | 'use strict'; | ||
| 3447 | |||
| 3448 | var defaults = require(26); | ||
| 3449 | var elements = require(41); | ||
| 3450 | var helpers = require(46); | ||
| 3451 | |||
| 3452 | defaults._set('radar', { | ||
| 3453 | 	scale: { | ||
| 3454 | type: 'radialLinear' | ||
| 3455 | }, | ||
| 3456 | 	elements: { | ||
| 3457 | 		line: { | ||
| 3458 | tension: 0 // no bezier in radar | ||
| 3459 | } | ||
| 3460 | } | ||
| 3461 | }); | ||
| 3462 | |||
| 3463 | module.exports = function(Chart) { | ||
| 3464 | |||
| 3465 | 	Chart.controllers.radar = Chart.DatasetController.extend({ | ||
| 3466 | |||
| 3467 | datasetElementType: elements.Line, | ||
| 3468 | |||
| 3469 | dataElementType: elements.Point, | ||
| 3470 | |||
| 3471 | linkScales: helpers.noop, | ||
| 3472 | |||
| 3473 | 		update: function(reset) { | ||
| 3474 | var me = this; | ||
| 3475 | var meta = me.getMeta(); | ||
| 3476 | var line = meta.dataset; | ||
| 3477 | var points = meta.data; | ||
| 3478 | 			var custom = line.custom || {}; | ||
| 3479 | var dataset = me.getDataset(); | ||
| 3480 | var lineElementOptions = me.chart.options.elements.line; | ||
| 3481 | var scale = me.chart.scale; | ||
| 3482 | |||
| 3483 | // Compatibility: If the properties are defined with only the old name, use those values | ||
| 3484 | 			if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { | ||
| 3485 | dataset.lineTension = dataset.tension; | ||
| 3486 | } | ||
| 3487 | |||
| 3488 | 			helpers.extend(meta.dataset, { | ||
| 3489 | // Utility | ||
| 3490 | _datasetIndex: me.index, | ||
| 3491 | _scale: scale, | ||
| 3492 | // Data | ||
| 3493 | _children: points, | ||
| 3494 | _loop: true, | ||
| 3495 | // Model | ||
| 3496 | 				_model: { | ||
| 3497 | // Appearance | ||
| 3498 | tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), | ||
| 3499 | backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), | ||
| 3500 | borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), | ||
| 3501 | borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), | ||
| 3502 | fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), | ||
| 3503 | borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), | ||
| 3504 | borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), | ||
| 3505 | borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), | ||
| 3506 | borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), | ||
| 3507 | } | ||
| 3508 | }); | ||
| 3509 | |||
| 3510 | meta.dataset.pivot(); | ||
| 3511 | |||
| 3512 | // Update Points | ||
| 3513 | 			helpers.each(points, function(point, index) { | ||
| 3514 | me.updateElement(point, index, reset); | ||
| 3515 | }, me); | ||
| 3516 | |||
| 3517 | // Update bezier control points | ||
| 3518 | me.updateBezierControlPoints(); | ||
| 3519 | }, | ||
| 3520 | 		updateElement: function(point, index, reset) { | ||
| 3521 | var me = this; | ||
| 3522 | 			var custom = point.custom || {}; | ||
| 3523 | var dataset = me.getDataset(); | ||
| 3524 | var scale = me.chart.scale; | ||
| 3525 | var pointElementOptions = me.chart.options.elements.point; | ||
| 3526 | var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); | ||
| 3527 | |||
| 3528 | // Compatibility: If the properties are defined with only the old name, use those values | ||
| 3529 | 			if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { | ||
| 3530 | dataset.pointRadius = dataset.radius; | ||
| 3531 | } | ||
| 3532 | 			if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { | ||
| 3533 | dataset.pointHitRadius = dataset.hitRadius; | ||
| 3534 | } | ||
| 3535 | |||
| 3536 | 			helpers.extend(point, { | ||
| 3537 | // Utility | ||
| 3538 | _datasetIndex: me.index, | ||
| 3539 | _index: index, | ||
| 3540 | _scale: scale, | ||
| 3541 | |||
| 3542 | // Desired view properties | ||
| 3543 | 				_model: { | ||
| 3544 | x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales | ||
| 3545 | y: reset ? scale.yCenter : pointPosition.y, | ||
| 3546 | |||
| 3547 | // Appearance | ||
| 3548 | tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), | ||
| 3549 | radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), | ||
| 3550 | backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), | ||
| 3551 | borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), | ||
| 3552 | borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), | ||
| 3553 | pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), | ||
| 3554 | rotation: custom.rotation ? custom.rotation : helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointElementOptions.rotation), | ||
| 3555 | |||
| 3556 | // Tooltip | ||
| 3557 | hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) | ||
| 3558 | } | ||
| 3559 | }); | ||
| 3560 | |||
| 3561 | point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); | ||
| 3562 | }, | ||
| 3563 | 		updateBezierControlPoints: function() { | ||
| 3564 | var chartArea = this.chart.chartArea; | ||
| 3565 | var meta = this.getMeta(); | ||
| 3566 | |||
| 3567 | 			helpers.each(meta.data, function(point, index) { | ||
| 3568 | var model = point._model; | ||
| 3569 | var controlPoints = helpers.splineCurve( | ||
| 3570 | helpers.previousItem(meta.data, index, true)._model, | ||
| 3571 | model, | ||
| 3572 | helpers.nextItem(meta.data, index, true)._model, | ||
| 3573 | model.tension | ||
| 3574 | ); | ||
| 3575 | |||
| 3576 | // Prevent the bezier going outside of the bounds of the graph | ||
| 3577 | model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); | ||
| 3578 | model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); | ||
| 3579 | |||
| 3580 | model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); | ||
| 3581 | model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); | ||
| 3582 | |||
| 3583 | // Now pivot the point for animation | ||
| 3584 | point.pivot(); | ||
| 3585 | }); | ||
| 3586 | }, | ||
| 3587 | |||
| 3588 | 		setHoverStyle: function(point) { | ||
| 3589 | // Point | ||
| 3590 | var dataset = this.chart.data.datasets[point._datasetIndex]; | ||
| 3591 | 			var custom = point.custom || {}; | ||
| 3592 | var index = point._index; | ||
| 3593 | var model = point._model; | ||
| 3594 | |||
| 3595 | 			point.$previousStyle = { | ||
| 3596 | backgroundColor: model.backgroundColor, | ||
| 3597 | borderColor: model.borderColor, | ||
| 3598 | borderWidth: model.borderWidth, | ||
| 3599 | radius: model.radius | ||
| 3600 | }; | ||
| 3601 | |||
| 3602 | model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); | ||
| 3603 | model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); | ||
| 3604 | model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); | ||
| 3605 | model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); | ||
| 3606 | }, | ||
| 3607 | }); | ||
| 3608 | }; | ||
| 3609 | |||
| 3610 | },{"26":26,"41":41,"46":46}],21:[function(require,module,exports){ | ||
| 3611 | 'use strict'; | ||
| 3612 | |||
| 3613 | var defaults = require(26); | ||
| 3614 | |||
| 3615 | defaults._set('scatter', { | ||
| 3616 | 	hover: { | ||
| 3617 | mode: 'single' | ||
| 3618 | }, | ||
| 3619 | |||
| 3620 | 	scales: { | ||
| 3621 | 		xAxes: [{ | ||
| 3622 | id: 'x-axis-1', // need an ID so datasets can reference the scale | ||
| 3623 | type: 'linear', // scatter should not use a category axis | ||
| 3624 | position: 'bottom' | ||
| 3625 | }], | ||
| 3626 | 		yAxes: [{ | ||
| 3627 | id: 'y-axis-1', | ||
| 3628 | type: 'linear', | ||
| 3629 | position: 'left' | ||
| 3630 | }] | ||
| 3631 | }, | ||
| 3632 | |||
| 3633 | showLines: false, | ||
| 3634 | |||
| 3635 | 	tooltips: { | ||
| 3636 | 		callbacks: { | ||
| 3637 | 			title: function() { | ||
| 3638 | return ''; // doesn't make sense for scatter since data are formatted as a point | ||
| 3639 | }, | ||
| 3640 | 			label: function(item) { | ||
| 3641 | 				return '(' + item.xLabel + ', ' + item.yLabel + ')'; | ||
| 3642 | } | ||
| 3643 | } | ||
| 3644 | } | ||
| 3645 | }); | ||
| 3646 | |||
| 3647 | module.exports = function(Chart) { | ||
| 3648 | |||
| 3649 | // Scatter charts use line controllers | ||
| 3650 | Chart.controllers.scatter = Chart.controllers.line; | ||
| 3651 | |||
| 3652 | }; | ||
| 3653 | |||
| 3654 | },{"26":26}],22:[function(require,module,exports){ | ||
| 3655 | 'use strict'; | ||
| 3656 | |||
| 3657 | var Element = require(27); | ||
| 3658 | |||
| 3659 | var exports = module.exports = Element.extend({ | ||
| 3660 | chart: null, // the animation associated chart instance | ||
| 3661 | currentStep: 0, // the current animation step | ||
| 3662 | numSteps: 60, // default number of steps | ||
| 3663 | easing: '', // the easing to use for this animation | ||
| 3664 | render: null, // render function used by the animation service | ||
| 3665 | |||
| 3666 | onAnimationProgress: null, // user specified callback to fire on each step of the animation | ||
| 3667 | onAnimationComplete: null, // user specified callback to fire when the animation finishes | ||
| 3668 | }); | ||
| 3669 | |||
| 3670 | // DEPRECATIONS | ||
| 3671 | |||
| 3672 | /** | ||
| 3673 | * Provided for backward compatibility, use Chart.Animation instead | ||
| 3674 | * @prop Chart.Animation#animationObject | ||
| 3675 | * @deprecated since version 2.6.0 | ||
| 3676 | * @todo remove at version 3 | ||
| 3677 | */ | ||
| 3678 | Object.defineProperty(exports.prototype, 'animationObject', { | ||
| 3679 | 	get: function() { | ||
| 3680 | return this; | ||
| 3681 | } | ||
| 3682 | }); | ||
| 3683 | |||
| 3684 | /** | ||
| 3685 | * Provided for backward compatibility, use Chart.Animation#chart instead | ||
| 3686 | * @prop Chart.Animation#chartInstance | ||
| 3687 | * @deprecated since version 2.6.0 | ||
| 3688 | * @todo remove at version 3 | ||
| 3689 | */ | ||
| 3690 | Object.defineProperty(exports.prototype, 'chartInstance', { | ||
| 3691 | 	get: function() { | ||
| 3692 | return this.chart; | ||
| 3693 | }, | ||
| 3694 | 	set: function(value) { | ||
| 3695 | this.chart = value; | ||
| 3696 | } | ||
| 3697 | }); | ||
| 3698 | |||
| 3699 | },{"27":27}],23:[function(require,module,exports){ | ||
| 3700 | /* global window: false */ | ||
| 3701 | 'use strict'; | ||
| 3702 | |||
| 3703 | var defaults = require(26); | ||
| 3704 | var helpers = require(46); | ||
| 3705 | |||
| 3706 | defaults._set('global', { | ||
| 3707 | 	animation: { | ||
| 3708 | duration: 1000, | ||
| 3709 | easing: 'easeOutQuart', | ||
| 3710 | onProgress: helpers.noop, | ||
| 3711 | onComplete: helpers.noop | ||
| 3712 | } | ||
| 3713 | }); | ||
| 3714 | |||
| 3715 | module.exports = { | ||
| 3716 | frameDuration: 17, | ||
| 3717 | animations: [], | ||
| 3718 | dropFrames: 0, | ||
| 3719 | request: null, | ||
| 3720 | |||
| 3721 | /** | ||
| 3722 | 	 * @param {Chart} chart - The chart to animate. | ||
| 3723 | 	 * @param {Chart.Animation} animation - The animation that we will animate. | ||
| 3724 | 	 * @param {Number} duration - The animation duration in ms. | ||
| 3725 | 	 * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions | ||
| 3726 | */ | ||
| 3727 | 	addAnimation: function(chart, animation, duration, lazy) { | ||
| 3728 | var animations = this.animations; | ||
| 3729 | var i, ilen; | ||
| 3730 | |||
| 3731 | animation.chart = chart; | ||
| 3732 | |||
| 3733 | 		if (!lazy) { | ||
| 3734 | chart.animating = true; | ||
| 3735 | } | ||
| 3736 | |||
| 3737 | 		for (i = 0, ilen = animations.length; i < ilen; ++i) { | ||
| 3738 | 			if (animations[i].chart === chart) { | ||
| 3739 | animations[i] = animation; | ||
| 3740 | return; | ||
| 3741 | } | ||
| 3742 | } | ||
| 3743 | |||
| 3744 | animations.push(animation); | ||
| 3745 | |||
| 3746 | // If there are no animations queued, manually kickstart a digest, for lack of a better word | ||
| 3747 | 		if (animations.length === 1) { | ||
| 3748 | this.requestAnimationFrame(); | ||
| 3749 | } | ||
| 3750 | }, | ||
| 3751 | |||
| 3752 | 	cancelAnimation: function(chart) { | ||
| 3753 | 		var index = helpers.findIndex(this.animations, function(animation) { | ||
| 3754 | return animation.chart === chart; | ||
| 3755 | }); | ||
| 3756 | |||
| 3757 | 		if (index !== -1) { | ||
| 3758 | this.animations.splice(index, 1); | ||
| 3759 | chart.animating = false; | ||
| 3760 | } | ||
| 3761 | }, | ||
| 3762 | |||
| 3763 | 	requestAnimationFrame: function() { | ||
| 3764 | var me = this; | ||
| 3765 | 		if (me.request === null) { | ||
| 3766 | // Skip animation frame requests until the active one is executed. | ||
| 3767 | // This can happen when processing mouse events, e.g. 'mousemove' | ||
| 3768 | // and 'mouseout' events will trigger multiple renders. | ||
| 3769 | 			me.request = helpers.requestAnimFrame.call(window, function() { | ||
| 3770 | me.request = null; | ||
| 3771 | me.startDigest(); | ||
| 3772 | }); | ||
| 3773 | } | ||
| 3774 | }, | ||
| 3775 | |||
| 3776 | /** | ||
| 3777 | * @private | ||
| 3778 | */ | ||
| 3779 | 	startDigest: function() { | ||
| 3780 | var me = this; | ||
| 3781 | var startTime = Date.now(); | ||
| 3782 | var framesToDrop = 0; | ||
| 3783 | |||
| 3784 | 		if (me.dropFrames > 1) { | ||
| 3785 | framesToDrop = Math.floor(me.dropFrames); | ||
| 3786 | me.dropFrames = me.dropFrames % 1; | ||
| 3787 | } | ||
| 3788 | |||
| 3789 | me.advance(1 + framesToDrop); | ||
| 3790 | |||
| 3791 | var endTime = Date.now(); | ||
| 3792 | |||
| 3793 | me.dropFrames += (endTime - startTime) / me.frameDuration; | ||
| 3794 | |||
| 3795 | // Do we have more stuff to animate? | ||
| 3796 | 		if (me.animations.length > 0) { | ||
| 3797 | me.requestAnimationFrame(); | ||
| 3798 | } | ||
| 3799 | }, | ||
| 3800 | |||
| 3801 | /** | ||
| 3802 | * @private | ||
| 3803 | */ | ||
| 3804 | 	advance: function(count) { | ||
| 3805 | var animations = this.animations; | ||
| 3806 | var animation, chart; | ||
| 3807 | var i = 0; | ||
| 3808 | |||
| 3809 | 		while (i < animations.length) { | ||
| 3810 | animation = animations[i]; | ||
| 3811 | chart = animation.chart; | ||
| 3812 | |||
| 3813 | animation.currentStep = (animation.currentStep || 0) + count; | ||
| 3814 | animation.currentStep = Math.min(animation.currentStep, animation.numSteps); | ||
| 3815 | |||
| 3816 | helpers.callback(animation.render, [chart, animation], chart); | ||
| 3817 | helpers.callback(animation.onAnimationProgress, [animation], chart); | ||
| 3818 | |||
| 3819 | 			if (animation.currentStep >= animation.numSteps) { | ||
| 3820 | helpers.callback(animation.onAnimationComplete, [animation], chart); | ||
| 3821 | chart.animating = false; | ||
| 3822 | animations.splice(i, 1); | ||
| 3823 | 			} else { | ||
| 3824 | ++i; | ||
| 3825 | } | ||
| 3826 | } | ||
| 3827 | } | ||
| 3828 | }; | ||
| 3829 | |||
| 3830 | },{"26":26,"46":46}],24:[function(require,module,exports){ | ||
| 3831 | 'use strict'; | ||
| 3832 | |||
| 3833 | var Animation = require(22); | ||
| 3834 | var animations = require(23); | ||
| 3835 | var defaults = require(26); | ||
| 3836 | var helpers = require(46); | ||
| 3837 | var Interaction = require(29); | ||
| 3838 | var layouts = require(31); | ||
| 3839 | var platform = require(49); | ||
| 3840 | var plugins = require(32); | ||
| 3841 | var scaleService = require(34); | ||
| 3842 | var Tooltip = require(36); | ||
| 3843 | |||
| 3844 | module.exports = function(Chart) { | ||
| 3845 | |||
| 3846 | // Create a dictionary of chart types, to allow for extension of existing types | ||
| 3847 | 	Chart.types = {}; | ||
| 3848 | |||
| 3849 | // Store a reference to each instance - allowing us to globally resize chart instances on window resize. | ||
| 3850 | // Destroy method on the chart will remove the instance of the chart from this reference. | ||
| 3851 | 	Chart.instances = {}; | ||
| 3852 | |||
| 3853 | // Controllers available for dataset visualization eg. bar, line, slice, etc. | ||
| 3854 | 	Chart.controllers = {}; | ||
| 3855 | |||
| 3856 | /** | ||
| 3857 | * Initializes the given config with global and chart default values. | ||
| 3858 | */ | ||
| 3859 | 	function initConfig(config) { | ||
| 3860 | 		config = config || {}; | ||
| 3861 | |||
| 3862 | // Do NOT use configMerge() for the data object because this method merges arrays | ||
| 3863 | // and so would change references to labels and datasets, preventing data updates. | ||
| 3864 | 		var data = config.data = config.data || {}; | ||
| 3865 | data.datasets = data.datasets || []; | ||
| 3866 | data.labels = data.labels || []; | ||
| 3867 | |||
| 3868 | config.options = helpers.configMerge( | ||
| 3869 | defaults.global, | ||
| 3870 | defaults[config.type], | ||
| 3871 | 			config.options || {}); | ||
| 3872 | |||
| 3873 | return config; | ||
| 3874 | } | ||
| 3875 | |||
| 3876 | /** | ||
| 3877 | * Updates the config of the chart | ||
| 3878 | 	 * @param chart {Chart} chart to update the options for | ||
| 3879 | */ | ||
| 3880 | 	function updateConfig(chart) { | ||
| 3881 | var newOptions = chart.options; | ||
| 3882 | |||
| 3883 | 		helpers.each(chart.scales, function(scale) { | ||
| 3884 | layouts.removeBox(chart, scale); | ||
| 3885 | }); | ||
| 3886 | |||
| 3887 | newOptions = helpers.configMerge( | ||
| 3888 | Chart.defaults.global, | ||
| 3889 | Chart.defaults[chart.config.type], | ||
| 3890 | newOptions); | ||
| 3891 | |||
| 3892 | chart.options = chart.config.options = newOptions; | ||
| 3893 | chart.ensureScalesHaveIDs(); | ||
| 3894 | chart.buildOrUpdateScales(); | ||
| 3895 | // Tooltip | ||
| 3896 | chart.tooltip._options = newOptions.tooltips; | ||
| 3897 | chart.tooltip.initialize(); | ||
| 3898 | } | ||
| 3899 | |||
| 3900 | 	function positionIsHorizontal(position) { | ||
| 3901 | return position === 'top' || position === 'bottom'; | ||
| 3902 | } | ||
| 3903 | |||
| 3904 | 	helpers.extend(Chart.prototype, /** @lends Chart */ { | ||
| 3905 | /** | ||
| 3906 | * @private | ||
| 3907 | */ | ||
| 3908 | 		construct: function(item, config) { | ||
| 3909 | var me = this; | ||
| 3910 | |||
| 3911 | config = initConfig(config); | ||
| 3912 | |||
| 3913 | var context = platform.acquireContext(item, config); | ||
| 3914 | var canvas = context && context.canvas; | ||
| 3915 | var height = canvas && canvas.height; | ||
| 3916 | var width = canvas && canvas.width; | ||
| 3917 | |||
| 3918 | me.id = helpers.uid(); | ||
| 3919 | me.ctx = context; | ||
| 3920 | me.canvas = canvas; | ||
| 3921 | me.config = config; | ||
| 3922 | me.width = width; | ||
| 3923 | me.height = height; | ||
| 3924 | me.aspectRatio = height ? width / height : null; | ||
| 3925 | me.options = config.options; | ||
| 3926 | me._bufferedRender = false; | ||
| 3927 | |||
| 3928 | /** | ||
| 3929 | * Provided for backward compatibility, Chart and Chart.Controller have been merged, | ||
| 3930 | * the "instance" still need to be defined since it might be called from plugins. | ||
| 3931 | * @prop Chart#chart | ||
| 3932 | * @deprecated since version 2.6.0 | ||
| 3933 | * @todo remove at version 3 | ||
| 3934 | * @private | ||
| 3935 | */ | ||
| 3936 | me.chart = me; | ||
| 3937 | me.controller = me; // chart.chart.controller #inception | ||
| 3938 | |||
| 3939 | // Add the chart instance to the global namespace | ||
| 3940 | Chart.instances[me.id] = me; | ||
| 3941 | |||
| 3942 | // Define alias to the config data: `chart.data === chart.config.data` | ||
| 3943 | 			Object.defineProperty(me, 'data', { | ||
| 3944 | 				get: function() { | ||
| 3945 | return me.config.data; | ||
| 3946 | }, | ||
| 3947 | 				set: function(value) { | ||
| 3948 | me.config.data = value; | ||
| 3949 | } | ||
| 3950 | }); | ||
| 3951 | |||
| 3952 | 			if (!context || !canvas) { | ||
| 3953 | // The given item is not a compatible context2d element, let's return before finalizing | ||
| 3954 | // the chart initialization but after setting basic chart / controller properties that | ||
| 3955 | // can help to figure out that the chart is not valid (e.g chart.canvas !== null); | ||
| 3956 | // https://github.com/chartjs/Chart.js/issues/2807 | ||
| 3957 | 				console.error("Failed to create chart: can't acquire context from the given item"); | ||
| 3958 | return; | ||
| 3959 | } | ||
| 3960 | |||
| 3961 | me.initialize(); | ||
| 3962 | me.update(); | ||
| 3963 | }, | ||
| 3964 | |||
| 3965 | /** | ||
| 3966 | * @private | ||
| 3967 | */ | ||
| 3968 | 		initialize: function() { | ||
| 3969 | var me = this; | ||
| 3970 | |||
| 3971 | // Before init plugin notification | ||
| 3972 | plugins.notify(me, 'beforeInit'); | ||
| 3973 | |||
| 3974 | helpers.retinaScale(me, me.options.devicePixelRatio); | ||
| 3975 | |||
| 3976 | me.bindEvents(); | ||
| 3977 | |||
| 3978 | 			if (me.options.responsive) { | ||
| 3979 | // Initial resize before chart draws (must be silent to preserve initial animations). | ||
| 3980 | me.resize(true); | ||
| 3981 | } | ||
| 3982 | |||
| 3983 | // Make sure scales have IDs and are built before we build any controllers. | ||
| 3984 | me.ensureScalesHaveIDs(); | ||
| 3985 | me.buildOrUpdateScales(); | ||
| 3986 | me.initToolTip(); | ||
| 3987 | |||
| 3988 | // After init plugin notification | ||
| 3989 | plugins.notify(me, 'afterInit'); | ||
| 3990 | |||
| 3991 | return me; | ||
| 3992 | }, | ||
| 3993 | |||
| 3994 | 		clear: function() { | ||
| 3995 | helpers.canvas.clear(this); | ||
| 3996 | return this; | ||
| 3997 | }, | ||
| 3998 | |||
| 3999 | 		stop: function() { | ||
| 4000 | // Stops any current animation loop occurring | ||
| 4001 | animations.cancelAnimation(this); | ||
| 4002 | return this; | ||
| 4003 | }, | ||
| 4004 | |||
| 4005 | 		resize: function(silent) { | ||
| 4006 | var me = this; | ||
| 4007 | var options = me.options; | ||
| 4008 | var canvas = me.canvas; | ||
| 4009 | var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; | ||
| 4010 | |||
| 4011 | // the canvas render width and height will be casted to integers so make sure that | ||
| 4012 | // the canvas display style uses the same integer values to avoid blurring effect. | ||
| 4013 | |||
| 4014 | // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed | ||
| 4015 | var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas))); | ||
| 4016 | var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas))); | ||
| 4017 | |||
| 4018 | 			if (me.width === newWidth && me.height === newHeight) { | ||
| 4019 | return; | ||
| 4020 | } | ||
| 4021 | |||
| 4022 | canvas.width = me.width = newWidth; | ||
| 4023 | canvas.height = me.height = newHeight; | ||
| 4024 | canvas.style.width = newWidth + 'px'; | ||
| 4025 | canvas.style.height = newHeight + 'px'; | ||
| 4026 | |||
| 4027 | helpers.retinaScale(me, options.devicePixelRatio); | ||
| 4028 | |||
| 4029 | 			if (!silent) { | ||
| 4030 | // Notify any plugins about the resize | ||
| 4031 | 				var newSize = {width: newWidth, height: newHeight}; | ||
| 4032 | plugins.notify(me, 'resize', [newSize]); | ||
| 4033 | |||
| 4034 | // Notify of resize | ||
| 4035 | 				if (me.options.onResize) { | ||
| 4036 | me.options.onResize(me, newSize); | ||
| 4037 | } | ||
| 4038 | |||
| 4039 | me.stop(); | ||
| 4040 | 				me.update({ | ||
| 4041 | duration: me.options.responsiveAnimationDuration | ||
| 4042 | }); | ||
| 4043 | } | ||
| 4044 | }, | ||
| 4045 | |||
| 4046 | 		ensureScalesHaveIDs: function() { | ||
| 4047 | var options = this.options; | ||
| 4048 | 			var scalesOptions = options.scales || {}; | ||
| 4049 | var scaleOptions = options.scale; | ||
| 4050 | |||
| 4051 | 			helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { | ||
| 4052 | 				xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); | ||
| 4053 | }); | ||
| 4054 | |||
| 4055 | 			helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { | ||
| 4056 | 				yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); | ||
| 4057 | }); | ||
| 4058 | |||
| 4059 | 			if (scaleOptions) { | ||
| 4060 | scaleOptions.id = scaleOptions.id || 'scale'; | ||
| 4061 | } | ||
| 4062 | }, | ||
| 4063 | |||
| 4064 | /** | ||
| 4065 | * Builds a map of scale ID to scale object for future lookup. | ||
| 4066 | */ | ||
| 4067 | 		buildOrUpdateScales: function() { | ||
| 4068 | var me = this; | ||
| 4069 | var options = me.options; | ||
| 4070 | 			var scales = me.scales || {}; | ||
| 4071 | var items = []; | ||
| 4072 | 			var updated = Object.keys(scales).reduce(function(obj, id) { | ||
| 4073 | obj[id] = false; | ||
| 4074 | return obj; | ||
| 4075 | 			}, {}); | ||
| 4076 | |||
| 4077 | 			if (options.scales) { | ||
| 4078 | items = items.concat( | ||
| 4079 | 					(options.scales.xAxes || []).map(function(xAxisOptions) { | ||
| 4080 | 						return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; | ||
| 4081 | }), | ||
| 4082 | 					(options.scales.yAxes || []).map(function(yAxisOptions) { | ||
| 4083 | 						return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; | ||
| 4084 | }) | ||
| 4085 | ); | ||
| 4086 | } | ||
| 4087 | |||
| 4088 | 			if (options.scale) { | ||
| 4089 | 				items.push({ | ||
| 4090 | options: options.scale, | ||
| 4091 | dtype: 'radialLinear', | ||
| 4092 | isDefault: true, | ||
| 4093 | dposition: 'chartArea' | ||
| 4094 | }); | ||
| 4095 | } | ||
| 4096 | |||
| 4097 | 			helpers.each(items, function(item) { | ||
| 4098 | var scaleOptions = item.options; | ||
| 4099 | var id = scaleOptions.id; | ||
| 4100 | var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype); | ||
| 4101 | |||
| 4102 | 				if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { | ||
| 4103 | scaleOptions.position = item.dposition; | ||
| 4104 | } | ||
| 4105 | |||
| 4106 | updated[id] = true; | ||
| 4107 | var scale = null; | ||
| 4108 | 				if (id in scales && scales[id].type === scaleType) { | ||
| 4109 | scale = scales[id]; | ||
| 4110 | scale.options = scaleOptions; | ||
| 4111 | scale.ctx = me.ctx; | ||
| 4112 | scale.chart = me; | ||
| 4113 | 				} else { | ||
| 4114 | var scaleClass = scaleService.getScaleConstructor(scaleType); | ||
| 4115 | 					if (!scaleClass) { | ||
| 4116 | return; | ||
| 4117 | } | ||
| 4118 | 					scale = new scaleClass({ | ||
| 4119 | id: id, | ||
| 4120 | type: scaleType, | ||
| 4121 | options: scaleOptions, | ||
| 4122 | ctx: me.ctx, | ||
| 4123 | chart: me | ||
| 4124 | }); | ||
| 4125 | scales[scale.id] = scale; | ||
| 4126 | } | ||
| 4127 | |||
| 4128 | scale.mergeTicksOptions(); | ||
| 4129 | |||
| 4130 | // TODO(SB): I think we should be able to remove this custom case (options.scale) | ||
| 4131 | // and consider it as a regular scale part of the "scales"" map only! This would | ||
| 4132 | // make the logic easier and remove some useless? custom code. | ||
| 4133 | 				if (item.isDefault) { | ||
| 4134 | me.scale = scale; | ||
| 4135 | } | ||
| 4136 | }); | ||
| 4137 | // clear up discarded scales | ||
| 4138 | 			helpers.each(updated, function(hasUpdated, id) { | ||
| 4139 | 				if (!hasUpdated) { | ||
| 4140 | delete scales[id]; | ||
| 4141 | } | ||
| 4142 | }); | ||
| 4143 | |||
| 4144 | me.scales = scales; | ||
| 4145 | |||
| 4146 | scaleService.addScalesToLayout(this); | ||
| 4147 | }, | ||
| 4148 | |||
| 4149 | 		buildOrUpdateControllers: function() { | ||
| 4150 | var me = this; | ||
| 4151 | var types = []; | ||
| 4152 | var newControllers = []; | ||
| 4153 | |||
| 4154 | 			helpers.each(me.data.datasets, function(dataset, datasetIndex) { | ||
| 4155 | var meta = me.getDatasetMeta(datasetIndex); | ||
| 4156 | var type = dataset.type || me.config.type; | ||
| 4157 | |||
| 4158 | 				if (meta.type && meta.type !== type) { | ||
| 4159 | me.destroyDatasetMeta(datasetIndex); | ||
| 4160 | meta = me.getDatasetMeta(datasetIndex); | ||
| 4161 | } | ||
| 4162 | meta.type = type; | ||
| 4163 | |||
| 4164 | types.push(meta.type); | ||
| 4165 | |||
| 4166 | 				if (meta.controller) { | ||
| 4167 | meta.controller.updateIndex(datasetIndex); | ||
| 4168 | meta.controller.linkScales(); | ||
| 4169 | 				} else { | ||
| 4170 | var ControllerClass = Chart.controllers[meta.type]; | ||
| 4171 | 					if (ControllerClass === undefined) { | ||
| 4172 | 						throw new Error('"' + meta.type + '" is not a chart type.'); | ||
| 4173 | } | ||
| 4174 | |||
| 4175 | meta.controller = new ControllerClass(me, datasetIndex); | ||
| 4176 | newControllers.push(meta.controller); | ||
| 4177 | } | ||
| 4178 | }, me); | ||
| 4179 | |||
| 4180 | return newControllers; | ||
| 4181 | }, | ||
| 4182 | |||
| 4183 | /** | ||
| 4184 | * Reset the elements of all datasets | ||
| 4185 | * @private | ||
| 4186 | */ | ||
| 4187 | 		resetElements: function() { | ||
| 4188 | var me = this; | ||
| 4189 | 			helpers.each(me.data.datasets, function(dataset, datasetIndex) { | ||
| 4190 | me.getDatasetMeta(datasetIndex).controller.reset(); | ||
| 4191 | }, me); | ||
| 4192 | }, | ||
| 4193 | |||
| 4194 | /** | ||
| 4195 | * Resets the chart back to it's state before the initial animation | ||
| 4196 | */ | ||
| 4197 | 		reset: function() { | ||
| 4198 | this.resetElements(); | ||
| 4199 | this.tooltip.initialize(); | ||
| 4200 | }, | ||
| 4201 | |||
| 4202 | 		update: function(config) { | ||
| 4203 | var me = this; | ||
| 4204 | |||
| 4205 | 			if (!config || typeof config !== 'object') { | ||
| 4206 | // backwards compatibility | ||
| 4207 | 				config = { | ||
| 4208 | duration: config, | ||
| 4209 | lazy: arguments[1] | ||
| 4210 | }; | ||
| 4211 | } | ||
| 4212 | |||
| 4213 | updateConfig(me); | ||
| 4214 | |||
| 4215 | // plugins options references might have change, let's invalidate the cache | ||
| 4216 | // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 | ||
| 4217 | plugins._invalidate(me); | ||
| 4218 | |||
| 4219 | 			if (plugins.notify(me, 'beforeUpdate') === false) { | ||
| 4220 | return; | ||
| 4221 | } | ||
| 4222 | |||
| 4223 | // In case the entire data object changed | ||
| 4224 | me.tooltip._data = me.data; | ||
| 4225 | |||
| 4226 | // Make sure dataset controllers are updated and new controllers are reset | ||
| 4227 | var newControllers = me.buildOrUpdateControllers(); | ||
| 4228 | |||
| 4229 | // Make sure all dataset controllers have correct meta data counts | ||
| 4230 | 			helpers.each(me.data.datasets, function(dataset, datasetIndex) { | ||
| 4231 | me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); | ||
| 4232 | }, me); | ||
| 4233 | |||
| 4234 | me.updateLayout(); | ||
| 4235 | |||
| 4236 | // Can only reset the new controllers after the scales have been updated | ||
| 4237 | 			if (me.options.animation && me.options.animation.duration) { | ||
| 4238 | 				helpers.each(newControllers, function(controller) { | ||
| 4239 | controller.reset(); | ||
| 4240 | }); | ||
| 4241 | } | ||
| 4242 | |||
| 4243 | me.updateDatasets(); | ||
| 4244 | |||
| 4245 | // Need to reset tooltip in case it is displayed with elements that are removed | ||
| 4246 | // after update. | ||
| 4247 | me.tooltip.initialize(); | ||
| 4248 | |||
| 4249 | // Last active contains items that were previously in the tooltip. | ||
| 4250 | // When we reset the tooltip, we need to clear it | ||
| 4251 | me.lastActive = []; | ||
| 4252 | |||
| 4253 | // Do this before render so that any plugins that need final scale updates can use it | ||
| 4254 | plugins.notify(me, 'afterUpdate'); | ||
| 4255 | |||
| 4256 | 			if (me._bufferedRender) { | ||
| 4257 | 				me._bufferedRequest = { | ||
| 4258 | duration: config.duration, | ||
| 4259 | easing: config.easing, | ||
| 4260 | lazy: config.lazy | ||
| 4261 | }; | ||
| 4262 | 			} else { | ||
| 4263 | me.render(config); | ||
| 4264 | } | ||
| 4265 | }, | ||
| 4266 | |||
| 4267 | /** | ||
| 4268 | * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` | ||
| 4269 | * hook, in which case, plugins will not be called on `afterLayout`. | ||
| 4270 | * @private | ||
| 4271 | */ | ||
| 4272 | 		updateLayout: function() { | ||
| 4273 | var me = this; | ||
| 4274 | |||
| 4275 | 			if (plugins.notify(me, 'beforeLayout') === false) { | ||
| 4276 | return; | ||
| 4277 | } | ||
| 4278 | |||
| 4279 | layouts.update(this, this.width, this.height); | ||
| 4280 | |||
| 4281 | /** | ||
| 4282 | * Provided for backward compatibility, use `afterLayout` instead. | ||
| 4283 | * @method IPlugin#afterScaleUpdate | ||
| 4284 | * @deprecated since version 2.5.0 | ||
| 4285 | * @todo remove at version 3 | ||
| 4286 | * @private | ||
| 4287 | */ | ||
| 4288 | plugins.notify(me, 'afterScaleUpdate'); | ||
| 4289 | plugins.notify(me, 'afterLayout'); | ||
| 4290 | }, | ||
| 4291 | |||
| 4292 | /** | ||
| 4293 | * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` | ||
| 4294 | * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. | ||
| 4295 | * @private | ||
| 4296 | */ | ||
| 4297 | 		updateDatasets: function() { | ||
| 4298 | var me = this; | ||
| 4299 | |||
| 4300 | 			if (plugins.notify(me, 'beforeDatasetsUpdate') === false) { | ||
| 4301 | return; | ||
| 4302 | } | ||
| 4303 | |||
| 4304 | 			for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { | ||
| 4305 | me.updateDataset(i); | ||
| 4306 | } | ||
| 4307 | |||
| 4308 | plugins.notify(me, 'afterDatasetsUpdate'); | ||
| 4309 | }, | ||
| 4310 | |||
| 4311 | /** | ||
| 4312 | * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` | ||
| 4313 | * hook, in which case, plugins will not be called on `afterDatasetUpdate`. | ||
| 4314 | * @private | ||
| 4315 | */ | ||
| 4316 | 		updateDataset: function(index) { | ||
| 4317 | var me = this; | ||
| 4318 | var meta = me.getDatasetMeta(index); | ||
| 4319 | 			var args = { | ||
| 4320 | meta: meta, | ||
| 4321 | index: index | ||
| 4322 | }; | ||
| 4323 | |||
| 4324 | 			if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { | ||
| 4325 | return; | ||
| 4326 | } | ||
| 4327 | |||
| 4328 | meta.controller.update(); | ||
| 4329 | |||
| 4330 | plugins.notify(me, 'afterDatasetUpdate', [args]); | ||
| 4331 | }, | ||
| 4332 | |||
| 4333 | 		render: function(config) { | ||
| 4334 | var me = this; | ||
| 4335 | |||
| 4336 | 			if (!config || typeof config !== 'object') { | ||
| 4337 | // backwards compatibility | ||
| 4338 | 				config = { | ||
| 4339 | duration: config, | ||
| 4340 | lazy: arguments[1] | ||
| 4341 | }; | ||
| 4342 | } | ||
| 4343 | |||
| 4344 | var duration = config.duration; | ||
| 4345 | var lazy = config.lazy; | ||
| 4346 | |||
| 4347 | 			if (plugins.notify(me, 'beforeRender') === false) { | ||
| 4348 | return; | ||
| 4349 | } | ||
| 4350 | |||
| 4351 | var animationOptions = me.options.animation; | ||
| 4352 | 			var onComplete = function(animation) { | ||
| 4353 | plugins.notify(me, 'afterRender'); | ||
| 4354 | helpers.callback(animationOptions && animationOptions.onComplete, [animation], me); | ||
| 4355 | }; | ||
| 4356 | |||
| 4357 | 			if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { | ||
| 4358 | 				var animation = new Animation({ | ||
| 4359 | numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps | ||
| 4360 | easing: config.easing || animationOptions.easing, | ||
| 4361 | |||
| 4362 | 					render: function(chart, animationObject) { | ||
| 4363 | var easingFunction = helpers.easing.effects[animationObject.easing]; | ||
| 4364 | var currentStep = animationObject.currentStep; | ||
| 4365 | var stepDecimal = currentStep / animationObject.numSteps; | ||
| 4366 | |||
| 4367 | chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); | ||
| 4368 | }, | ||
| 4369 | |||
| 4370 | onAnimationProgress: animationOptions.onProgress, | ||
| 4371 | onAnimationComplete: onComplete | ||
| 4372 | }); | ||
| 4373 | |||
| 4374 | animations.addAnimation(me, animation, duration, lazy); | ||
| 4375 | 			} else { | ||
| 4376 | me.draw(); | ||
| 4377 | |||
| 4378 | // See https://github.com/chartjs/Chart.js/issues/3781 | ||
| 4379 | 				onComplete(new Animation({numSteps: 0, chart: me})); | ||
| 4380 | } | ||
| 4381 | |||
| 4382 | return me; | ||
| 4383 | }, | ||
| 4384 | |||
| 4385 | 		draw: function(easingValue) { | ||
| 4386 | var me = this; | ||
| 4387 | |||
| 4388 | me.clear(); | ||
| 4389 | |||
| 4390 | 			if (helpers.isNullOrUndef(easingValue)) { | ||
| 4391 | easingValue = 1; | ||
| 4392 | } | ||
| 4393 | |||
| 4394 | me.transition(easingValue); | ||
| 4395 | |||
| 4396 | 			if (me.width <= 0 || me.height <= 0) { | ||
| 4397 | return; | ||
| 4398 | } | ||
| 4399 | |||
| 4400 | 			if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) { | ||
| 4401 | return; | ||
| 4402 | } | ||
| 4403 | |||
| 4404 | // Draw all the scales | ||
| 4405 | 			helpers.each(me.boxes, function(box) { | ||
| 4406 | box.draw(me.chartArea); | ||
| 4407 | }, me); | ||
| 4408 | |||
| 4409 | 			if (me.scale) { | ||
| 4410 | me.scale.draw(); | ||
| 4411 | } | ||
| 4412 | |||
| 4413 | me.drawDatasets(easingValue); | ||
| 4414 | me._drawTooltip(easingValue); | ||
| 4415 | |||
| 4416 | plugins.notify(me, 'afterDraw', [easingValue]); | ||
| 4417 | }, | ||
| 4418 | |||
| 4419 | /** | ||
| 4420 | * @private | ||
| 4421 | */ | ||
| 4422 | 		transition: function(easingValue) { | ||
| 4423 | var me = this; | ||
| 4424 | |||
| 4425 | 			for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { | ||
| 4426 | 				if (me.isDatasetVisible(i)) { | ||
| 4427 | me.getDatasetMeta(i).controller.transition(easingValue); | ||
| 4428 | } | ||
| 4429 | } | ||
| 4430 | |||
| 4431 | me.tooltip.transition(easingValue); | ||
| 4432 | }, | ||
| 4433 | |||
| 4434 | /** | ||
| 4435 | * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` | ||
| 4436 | * hook, in which case, plugins will not be called on `afterDatasetsDraw`. | ||
| 4437 | * @private | ||
| 4438 | */ | ||
| 4439 | 		drawDatasets: function(easingValue) { | ||
| 4440 | var me = this; | ||
| 4441 | |||
| 4442 | 			if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { | ||
| 4443 | return; | ||
| 4444 | } | ||
| 4445 | |||
| 4446 | // Draw datasets reversed to support proper line stacking | ||
| 4447 | 			for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) { | ||
| 4448 | 				if (me.isDatasetVisible(i)) { | ||
| 4449 | me.drawDataset(i, easingValue); | ||
| 4450 | } | ||
| 4451 | } | ||
| 4452 | |||
| 4453 | plugins.notify(me, 'afterDatasetsDraw', [easingValue]); | ||
| 4454 | }, | ||
| 4455 | |||
| 4456 | /** | ||
| 4457 | * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` | ||
| 4458 | * hook, in which case, plugins will not be called on `afterDatasetDraw`. | ||
| 4459 | * @private | ||
| 4460 | */ | ||
| 4461 | 		drawDataset: function(index, easingValue) { | ||
| 4462 | var me = this; | ||
| 4463 | var meta = me.getDatasetMeta(index); | ||
| 4464 | 			var args = { | ||
| 4465 | meta: meta, | ||
| 4466 | index: index, | ||
| 4467 | easingValue: easingValue | ||
| 4468 | }; | ||
| 4469 | |||
| 4470 | 			if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { | ||
| 4471 | return; | ||
| 4472 | } | ||
| 4473 | |||
| 4474 | meta.controller.draw(easingValue); | ||
| 4475 | |||
| 4476 | plugins.notify(me, 'afterDatasetDraw', [args]); | ||
| 4477 | }, | ||
| 4478 | |||
| 4479 | /** | ||
| 4480 | * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` | ||
| 4481 | * hook, in which case, plugins will not be called on `afterTooltipDraw`. | ||
| 4482 | * @private | ||
| 4483 | */ | ||
| 4484 | 		_drawTooltip: function(easingValue) { | ||
| 4485 | var me = this; | ||
| 4486 | var tooltip = me.tooltip; | ||
| 4487 | 			var args = { | ||
| 4488 | tooltip: tooltip, | ||
| 4489 | easingValue: easingValue | ||
| 4490 | }; | ||
| 4491 | |||
| 4492 | 			if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { | ||
| 4493 | return; | ||
| 4494 | } | ||
| 4495 | |||
| 4496 | tooltip.draw(); | ||
| 4497 | |||
| 4498 | plugins.notify(me, 'afterTooltipDraw', [args]); | ||
| 4499 | }, | ||
| 4500 | |||
| 4501 | // Get the single element that was clicked on | ||
| 4502 | // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw | ||
| 4503 | 		getElementAtEvent: function(e) { | ||
| 4504 | return Interaction.modes.single(this, e); | ||
| 4505 | }, | ||
| 4506 | |||
| 4507 | 		getElementsAtEvent: function(e) { | ||
| 4508 | 			return Interaction.modes.label(this, e, {intersect: true}); | ||
| 4509 | }, | ||
| 4510 | |||
| 4511 | 		getElementsAtXAxis: function(e) { | ||
| 4512 | 			return Interaction.modes['x-axis'](this, e, {intersect: true}); | ||
| 4513 | }, | ||
| 4514 | |||
| 4515 | 		getElementsAtEventForMode: function(e, mode, options) { | ||
| 4516 | var method = Interaction.modes[mode]; | ||
| 4517 | 			if (typeof method === 'function') { | ||
| 4518 | return method(this, e, options); | ||
| 4519 | } | ||
| 4520 | |||
| 4521 | return []; | ||
| 4522 | }, | ||
| 4523 | |||
| 4524 | 		getDatasetAtEvent: function(e) { | ||
| 4525 | 			return Interaction.modes.dataset(this, e, {intersect: true}); | ||
| 4526 | }, | ||
| 4527 | |||
| 4528 | 		getDatasetMeta: function(datasetIndex) { | ||
| 4529 | var me = this; | ||
| 4530 | var dataset = me.data.datasets[datasetIndex]; | ||
| 4531 | 			if (!dataset._meta) { | ||
| 4532 | 				dataset._meta = {}; | ||
| 4533 | } | ||
| 4534 | |||
| 4535 | var meta = dataset._meta[me.id]; | ||
| 4536 | 			if (!meta) { | ||
| 4537 | 				meta = dataset._meta[me.id] = { | ||
| 4538 | type: null, | ||
| 4539 | data: [], | ||
| 4540 | dataset: null, | ||
| 4541 | controller: null, | ||
| 4542 | hidden: null, // See isDatasetVisible() comment | ||
| 4543 | xAxisID: null, | ||
| 4544 | yAxisID: null | ||
| 4545 | }; | ||
| 4546 | } | ||
| 4547 | |||
| 4548 | return meta; | ||
| 4549 | }, | ||
| 4550 | |||
| 4551 | 		getVisibleDatasetCount: function() { | ||
| 4552 | var count = 0; | ||
| 4553 | 			for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { | ||
| 4554 | 				if (this.isDatasetVisible(i)) { | ||
| 4555 | count++; | ||
| 4556 | } | ||
| 4557 | } | ||
| 4558 | return count; | ||
| 4559 | }, | ||
| 4560 | |||
| 4561 | 		isDatasetVisible: function(datasetIndex) { | ||
| 4562 | var meta = this.getDatasetMeta(datasetIndex); | ||
| 4563 | |||
| 4564 | // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, | ||
| 4565 | // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. | ||
| 4566 | return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; | ||
| 4567 | }, | ||
| 4568 | |||
| 4569 | 		generateLegend: function() { | ||
| 4570 | return this.options.legendCallback(this); | ||
| 4571 | }, | ||
| 4572 | |||
| 4573 | /** | ||
| 4574 | * @private | ||
| 4575 | */ | ||
| 4576 | 		destroyDatasetMeta: function(datasetIndex) { | ||
| 4577 | var id = this.id; | ||
| 4578 | var dataset = this.data.datasets[datasetIndex]; | ||
| 4579 | var meta = dataset._meta && dataset._meta[id]; | ||
| 4580 | |||
| 4581 | 			if (meta) { | ||
| 4582 | meta.controller.destroy(); | ||
| 4583 | delete dataset._meta[id]; | ||
| 4584 | } | ||
| 4585 | }, | ||
| 4586 | |||
| 4587 | 		destroy: function() { | ||
| 4588 | var me = this; | ||
| 4589 | var canvas = me.canvas; | ||
| 4590 | var i, ilen; | ||
| 4591 | |||
| 4592 | me.stop(); | ||
| 4593 | |||
| 4594 | // dataset controllers need to cleanup associated data | ||
| 4595 | 			for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { | ||
| 4596 | me.destroyDatasetMeta(i); | ||
| 4597 | } | ||
| 4598 | |||
| 4599 | 			if (canvas) { | ||
| 4600 | me.unbindEvents(); | ||
| 4601 | helpers.canvas.clear(me); | ||
| 4602 | platform.releaseContext(me.ctx); | ||
| 4603 | me.canvas = null; | ||
| 4604 | me.ctx = null; | ||
| 4605 | } | ||
| 4606 | |||
| 4607 | plugins.notify(me, 'destroy'); | ||
| 4608 | |||
| 4609 | delete Chart.instances[me.id]; | ||
| 4610 | }, | ||
| 4611 | |||
| 4612 | 		toBase64Image: function() { | ||
| 4613 | return this.canvas.toDataURL.apply(this.canvas, arguments); | ||
| 4614 | }, | ||
| 4615 | |||
| 4616 | 		initToolTip: function() { | ||
| 4617 | var me = this; | ||
| 4618 | 			me.tooltip = new Tooltip({ | ||
| 4619 | _chart: me, | ||
| 4620 | _chartInstance: me, // deprecated, backward compatibility | ||
| 4621 | _data: me.data, | ||
| 4622 | _options: me.options.tooltips | ||
| 4623 | }, me); | ||
| 4624 | }, | ||
| 4625 | |||
| 4626 | /** | ||
| 4627 | * @private | ||
| 4628 | */ | ||
| 4629 | 		bindEvents: function() { | ||
| 4630 | var me = this; | ||
| 4631 | 			var listeners = me._listeners = {}; | ||
| 4632 | 			var listener = function() { | ||
| 4633 | me.eventHandler.apply(me, arguments); | ||
| 4634 | }; | ||
| 4635 | |||
| 4636 | 			helpers.each(me.options.events, function(type) { | ||
| 4637 | platform.addEventListener(me, type, listener); | ||
| 4638 | listeners[type] = listener; | ||
| 4639 | }); | ||
| 4640 | |||
| 4641 | // Elements used to detect size change should not be injected for non responsive charts. | ||
| 4642 | // See https://github.com/chartjs/Chart.js/issues/2210 | ||
| 4643 | 			if (me.options.responsive) { | ||
| 4644 | 				listener = function() { | ||
| 4645 | me.resize(); | ||
| 4646 | }; | ||
| 4647 | |||
| 4648 | platform.addEventListener(me, 'resize', listener); | ||
| 4649 | listeners.resize = listener; | ||
| 4650 | } | ||
| 4651 | }, | ||
| 4652 | |||
| 4653 | /** | ||
| 4654 | * @private | ||
| 4655 | */ | ||
| 4656 | 		unbindEvents: function() { | ||
| 4657 | var me = this; | ||
| 4658 | var listeners = me._listeners; | ||
| 4659 | 			if (!listeners) { | ||
| 4660 | return; | ||
| 4661 | } | ||
| 4662 | |||
| 4663 | delete me._listeners; | ||
| 4664 | 			helpers.each(listeners, function(listener, type) { | ||
| 4665 | platform.removeEventListener(me, type, listener); | ||
| 4666 | }); | ||
| 4667 | }, | ||
| 4668 | |||
| 4669 | 		updateHoverStyle: function(elements, mode, enabled) { | ||
| 4670 | var method = enabled ? 'setHoverStyle' : 'removeHoverStyle'; | ||
| 4671 | var element, i, ilen; | ||
| 4672 | |||
| 4673 | 			for (i = 0, ilen = elements.length; i < ilen; ++i) { | ||
| 4674 | element = elements[i]; | ||
| 4675 | 				if (element) { | ||
| 4676 | this.getDatasetMeta(element._datasetIndex).controller[method](element); | ||
| 4677 | } | ||
| 4678 | } | ||
| 4679 | }, | ||
| 4680 | |||
| 4681 | /** | ||
| 4682 | * @private | ||
| 4683 | */ | ||
| 4684 | 		eventHandler: function(e) { | ||
| 4685 | var me = this; | ||
| 4686 | var tooltip = me.tooltip; | ||
| 4687 | |||
| 4688 | 			if (plugins.notify(me, 'beforeEvent', [e]) === false) { | ||
| 4689 | return; | ||
| 4690 | } | ||
| 4691 | |||
| 4692 | // Buffer any update calls so that renders do not occur | ||
| 4693 | me._bufferedRender = true; | ||
| 4694 | me._bufferedRequest = null; | ||
| 4695 | |||
| 4696 | var changed = me.handleEvent(e); | ||
| 4697 | // for smooth tooltip animations issue #4989 | ||
| 4698 | // the tooltip should be the source of change | ||
| 4699 | // Animation check workaround: | ||
| 4700 | // tooltip._start will be null when tooltip isn't animating | ||
| 4701 | 			if (tooltip) { | ||
| 4702 | changed = tooltip._start | ||
| 4703 | ? tooltip.handleEvent(e) | ||
| 4704 | : changed | tooltip.handleEvent(e); | ||
| 4705 | } | ||
| 4706 | |||
| 4707 | plugins.notify(me, 'afterEvent', [e]); | ||
| 4708 | |||
| 4709 | var bufferedRequest = me._bufferedRequest; | ||
| 4710 | 			if (bufferedRequest) { | ||
| 4711 | // If we have an update that was triggered, we need to do a normal render | ||
| 4712 | me.render(bufferedRequest); | ||
| 4713 | 			} else if (changed && !me.animating) { | ||
| 4714 | // If entering, leaving, or changing elements, animate the change via pivot | ||
| 4715 | me.stop(); | ||
| 4716 | |||
| 4717 | // We only need to render at this point. Updating will cause scales to be | ||
| 4718 | // recomputed generating flicker & using more memory than necessary. | ||
| 4719 | 				me.render({ | ||
| 4720 | duration: me.options.hover.animationDuration, | ||
| 4721 | lazy: true | ||
| 4722 | }); | ||
| 4723 | } | ||
| 4724 | |||
| 4725 | me._bufferedRender = false; | ||
| 4726 | me._bufferedRequest = null; | ||
| 4727 | |||
| 4728 | return me; | ||
| 4729 | }, | ||
| 4730 | |||
| 4731 | /** | ||
| 4732 | * Handle an event | ||
| 4733 | * @private | ||
| 4734 | 		 * @param {IEvent} event the event to handle | ||
| 4735 | 		 * @return {Boolean} true if the chart needs to re-render | ||
| 4736 | */ | ||
| 4737 | 		handleEvent: function(e) { | ||
| 4738 | var me = this; | ||
| 4739 | 			var options = me.options || {}; | ||
| 4740 | var hoverOptions = options.hover; | ||
| 4741 | var changed = false; | ||
| 4742 | |||
| 4743 | me.lastActive = me.lastActive || []; | ||
| 4744 | |||
| 4745 | // Find Active Elements for hover and tooltips | ||
| 4746 | 			if (e.type === 'mouseout') { | ||
| 4747 | me.active = []; | ||
| 4748 | 			} else { | ||
| 4749 | me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); | ||
| 4750 | } | ||
| 4751 | |||
| 4752 | // Invoke onHover hook | ||
| 4753 | // Need to call with native event here to not break backwards compatibility | ||
| 4754 | helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); | ||
| 4755 | |||
| 4756 | 			if (e.type === 'mouseup' || e.type === 'click') { | ||
| 4757 | 				if (options.onClick) { | ||
| 4758 | // Use e.native here for backwards compatibility | ||
| 4759 | options.onClick.call(me, e.native, me.active); | ||
| 4760 | } | ||
| 4761 | } | ||
| 4762 | |||
| 4763 | // Remove styling for last active (even if it may still be active) | ||
| 4764 | 			if (me.lastActive.length) { | ||
| 4765 | me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); | ||
| 4766 | } | ||
| 4767 | |||
| 4768 | // Built in hover styling | ||
| 4769 | 			if (me.active.length && hoverOptions.mode) { | ||
| 4770 | me.updateHoverStyle(me.active, hoverOptions.mode, true); | ||
| 4771 | } | ||
| 4772 | |||
| 4773 | changed = !helpers.arrayEquals(me.active, me.lastActive); | ||
| 4774 | |||
| 4775 | // Remember Last Actives | ||
| 4776 | me.lastActive = me.active; | ||
| 4777 | |||
| 4778 | return changed; | ||
| 4779 | } | ||
| 4780 | }); | ||
| 4781 | |||
| 4782 | /** | ||
| 4783 | * Provided for backward compatibility, use Chart instead. | ||
| 4784 | * @class Chart.Controller | ||
| 4785 | * @deprecated since version 2.6.0 | ||
| 4786 | * @todo remove at version 3 | ||
| 4787 | * @private | ||
| 4788 | */ | ||
| 4789 | Chart.Controller = Chart; | ||
| 4790 | }; | ||
| 4791 | |||
| 4792 | },{"22":22,"23":23,"26":26,"29":29,"31":31,"32":32,"34":34,"36":36,"46":46,"49":49}],25:[function(require,module,exports){ | ||
| 4793 | 'use strict'; | ||
| 4794 | |||
| 4795 | var helpers = require(46); | ||
| 4796 | |||
| 4797 | module.exports = function(Chart) { | ||
| 4798 | |||
| 4799 | var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; | ||
| 4800 | |||
| 4801 | /** | ||
| 4802 | 	 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', | ||
| 4803 | * 'unshift') and notify the listener AFTER the array has been altered. Listeners are | ||
| 4804 | * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. | ||
| 4805 | */ | ||
| 4806 | 	function listenArrayEvents(array, listener) { | ||
| 4807 | 		if (array._chartjs) { | ||
| 4808 | array._chartjs.listeners.push(listener); | ||
| 4809 | return; | ||
| 4810 | } | ||
| 4811 | |||
| 4812 | 		Object.defineProperty(array, '_chartjs', { | ||
| 4813 | configurable: true, | ||
| 4814 | enumerable: false, | ||
| 4815 | 			value: { | ||
| 4816 | listeners: [listener] | ||
| 4817 | } | ||
| 4818 | }); | ||
| 4819 | |||
| 4820 | 		arrayEvents.forEach(function(key) { | ||
| 4821 | var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); | ||
| 4822 | var base = array[key]; | ||
| 4823 | |||
| 4824 | 			Object.defineProperty(array, key, { | ||
| 4825 | configurable: true, | ||
| 4826 | enumerable: false, | ||
| 4827 | 				value: function() { | ||
| 4828 | var args = Array.prototype.slice.call(arguments); | ||
| 4829 | var res = base.apply(this, args); | ||
| 4830 | |||
| 4831 | 					helpers.each(array._chartjs.listeners, function(object) { | ||
| 4832 | 						if (typeof object[method] === 'function') { | ||
| 4833 | object[method].apply(object, args); | ||
| 4834 | } | ||
| 4835 | }); | ||
| 4836 | |||
| 4837 | return res; | ||
| 4838 | } | ||
| 4839 | }); | ||
| 4840 | }); | ||
| 4841 | } | ||
| 4842 | |||
| 4843 | /** | ||
| 4844 | * Removes the given array event listener and cleanup extra attached properties (such as | ||
| 4845 | * the _chartjs stub and overridden methods) if array doesn't have any more listeners. | ||
| 4846 | */ | ||
| 4847 | 	function unlistenArrayEvents(array, listener) { | ||
| 4848 | var stub = array._chartjs; | ||
| 4849 | 		if (!stub) { | ||
| 4850 | return; | ||
| 4851 | } | ||
| 4852 | |||
| 4853 | var listeners = stub.listeners; | ||
| 4854 | var index = listeners.indexOf(listener); | ||
| 4855 | 		if (index !== -1) { | ||
| 4856 | listeners.splice(index, 1); | ||
| 4857 | } | ||
| 4858 | |||
| 4859 | 		if (listeners.length > 0) { | ||
| 4860 | return; | ||
| 4861 | } | ||
| 4862 | |||
| 4863 | 		arrayEvents.forEach(function(key) { | ||
| 4864 | delete array[key]; | ||
| 4865 | }); | ||
| 4866 | |||
| 4867 | delete array._chartjs; | ||
| 4868 | } | ||
| 4869 | |||
| 4870 | // Base class for all dataset controllers (line, bar, etc) | ||
| 4871 | 	Chart.DatasetController = function(chart, datasetIndex) { | ||
| 4872 | this.initialize(chart, datasetIndex); | ||
| 4873 | }; | ||
| 4874 | |||
| 4875 | 	helpers.extend(Chart.DatasetController.prototype, { | ||
| 4876 | |||
| 4877 | /** | ||
| 4878 | * Element type used to generate a meta dataset (e.g. Chart.element.Line). | ||
| 4879 | 		 * @type {Chart.core.element} | ||
| 4880 | */ | ||
| 4881 | datasetElementType: null, | ||
| 4882 | |||
| 4883 | /** | ||
| 4884 | * Element type used to generate a meta data (e.g. Chart.element.Point). | ||
| 4885 | 		 * @type {Chart.core.element} | ||
| 4886 | */ | ||
| 4887 | dataElementType: null, | ||
| 4888 | |||
| 4889 | 		initialize: function(chart, datasetIndex) { | ||
| 4890 | var me = this; | ||
| 4891 | me.chart = chart; | ||
| 4892 | me.index = datasetIndex; | ||
| 4893 | me.linkScales(); | ||
| 4894 | me.addElements(); | ||
| 4895 | }, | ||
| 4896 | |||
| 4897 | 		updateIndex: function(datasetIndex) { | ||
| 4898 | this.index = datasetIndex; | ||
| 4899 | }, | ||
| 4900 | |||
| 4901 | 		linkScales: function() { | ||
| 4902 | var me = this; | ||
| 4903 | var meta = me.getMeta(); | ||
| 4904 | var dataset = me.getDataset(); | ||
| 4905 | |||
| 4906 | 			if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) { | ||
| 4907 | meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; | ||
| 4908 | } | ||
| 4909 | 			if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) { | ||
| 4910 | meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; | ||
| 4911 | } | ||
| 4912 | }, | ||
| 4913 | |||
| 4914 | 		getDataset: function() { | ||
| 4915 | return this.chart.data.datasets[this.index]; | ||
| 4916 | }, | ||
| 4917 | |||
| 4918 | 		getMeta: function() { | ||
| 4919 | return this.chart.getDatasetMeta(this.index); | ||
| 4920 | }, | ||
| 4921 | |||
| 4922 | 		getScaleForId: function(scaleID) { | ||
| 4923 | return this.chart.scales[scaleID]; | ||
| 4924 | }, | ||
| 4925 | |||
| 4926 | 		reset: function() { | ||
| 4927 | this.update(true); | ||
| 4928 | }, | ||
| 4929 | |||
| 4930 | /** | ||
| 4931 | * @private | ||
| 4932 | */ | ||
| 4933 | 		destroy: function() { | ||
| 4934 | 			if (this._data) { | ||
| 4935 | unlistenArrayEvents(this._data, this); | ||
| 4936 | } | ||
| 4937 | }, | ||
| 4938 | |||
| 4939 | 		createMetaDataset: function() { | ||
| 4940 | var me = this; | ||
| 4941 | var type = me.datasetElementType; | ||
| 4942 | 			return type && new type({ | ||
| 4943 | _chart: me.chart, | ||
| 4944 | _datasetIndex: me.index | ||
| 4945 | }); | ||
| 4946 | }, | ||
| 4947 | |||
| 4948 | 		createMetaData: function(index) { | ||
| 4949 | var me = this; | ||
| 4950 | var type = me.dataElementType; | ||
| 4951 | 			return type && new type({ | ||
| 4952 | _chart: me.chart, | ||
| 4953 | _datasetIndex: me.index, | ||
| 4954 | _index: index | ||
| 4955 | }); | ||
| 4956 | }, | ||
| 4957 | |||
| 4958 | 		addElements: function() { | ||
| 4959 | var me = this; | ||
| 4960 | var meta = me.getMeta(); | ||
| 4961 | var data = me.getDataset().data || []; | ||
| 4962 | var metaData = meta.data; | ||
| 4963 | var i, ilen; | ||
| 4964 | |||
| 4965 | 			for (i = 0, ilen = data.length; i < ilen; ++i) { | ||
| 4966 | metaData[i] = metaData[i] || me.createMetaData(i); | ||
| 4967 | } | ||
| 4968 | |||
| 4969 | meta.dataset = meta.dataset || me.createMetaDataset(); | ||
| 4970 | }, | ||
| 4971 | |||
| 4972 | 		addElementAndReset: function(index) { | ||
| 4973 | var element = this.createMetaData(index); | ||
| 4974 | this.getMeta().data.splice(index, 0, element); | ||
| 4975 | this.updateElement(element, index, true); | ||
| 4976 | }, | ||
| 4977 | |||
| 4978 | 		buildOrUpdateElements: function() { | ||
| 4979 | var me = this; | ||
| 4980 | var dataset = me.getDataset(); | ||
| 4981 | var data = dataset.data || (dataset.data = []); | ||
| 4982 | |||
| 4983 | // In order to correctly handle data addition/deletion animation (an thus simulate | ||
| 4984 | // real-time charts), we need to monitor these data modifications and synchronize | ||
| 4985 | // the internal meta data accordingly. | ||
| 4986 | 			if (me._data !== data) { | ||
| 4987 | 				if (me._data) { | ||
| 4988 | // This case happens when the user replaced the data array instance. | ||
| 4989 | unlistenArrayEvents(me._data, me); | ||
| 4990 | } | ||
| 4991 | |||
| 4992 | listenArrayEvents(data, me); | ||
| 4993 | me._data = data; | ||
| 4994 | } | ||
| 4995 | |||
| 4996 | // Re-sync meta data in case the user replaced the data array or if we missed | ||
| 4997 | // any updates and so make sure that we handle number of datapoints changing. | ||
| 4998 | me.resyncElements(); | ||
| 4999 | }, | ||
| 5000 | |||
| 5001 | update: helpers.noop, | ||
| 5002 | |||
| 5003 | 		transition: function(easingValue) { | ||
| 5004 | var meta = this.getMeta(); | ||
| 5005 | var elements = meta.data || []; | ||
| 5006 | var ilen = elements.length; | ||
| 5007 | var i = 0; | ||
| 5008 | |||
| 5009 | 			for (; i < ilen; ++i) { | ||
| 5010 | elements[i].transition(easingValue); | ||
| 5011 | } | ||
| 5012 | |||
| 5013 | 			if (meta.dataset) { | ||
| 5014 | meta.dataset.transition(easingValue); | ||
| 5015 | } | ||
| 5016 | }, | ||
| 5017 | |||
| 5018 | 		draw: function() { | ||
| 5019 | var meta = this.getMeta(); | ||
| 5020 | var elements = meta.data || []; | ||
| 5021 | var ilen = elements.length; | ||
| 5022 | var i = 0; | ||
| 5023 | |||
| 5024 | 			if (meta.dataset) { | ||
| 5025 | meta.dataset.draw(); | ||
| 5026 | } | ||
| 5027 | |||
| 5028 | 			for (; i < ilen; ++i) { | ||
| 5029 | elements[i].draw(); | ||
| 5030 | } | ||
| 5031 | }, | ||
| 5032 | |||
| 5033 | 		removeHoverStyle: function(element) { | ||
| 5034 | 			helpers.merge(element._model, element.$previousStyle || {}); | ||
| 5035 | delete element.$previousStyle; | ||
| 5036 | }, | ||
| 5037 | |||
| 5038 | 		setHoverStyle: function(element) { | ||
| 5039 | var dataset = this.chart.data.datasets[element._datasetIndex]; | ||
| 5040 | var index = element._index; | ||
| 5041 | 			var custom = element.custom || {}; | ||
| 5042 | var valueOrDefault = helpers.valueAtIndexOrDefault; | ||
| 5043 | var getHoverColor = helpers.getHoverColor; | ||
| 5044 | var model = element._model; | ||
| 5045 | |||
| 5046 | 			element.$previousStyle = { | ||
| 5047 | backgroundColor: model.backgroundColor, | ||
| 5048 | borderColor: model.borderColor, | ||
| 5049 | borderWidth: model.borderWidth | ||
| 5050 | }; | ||
| 5051 | |||
| 5052 | model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); | ||
| 5053 | model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); | ||
| 5054 | model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); | ||
| 5055 | }, | ||
| 5056 | |||
| 5057 | /** | ||
| 5058 | * @private | ||
| 5059 | */ | ||
| 5060 | 		resyncElements: function() { | ||
| 5061 | var me = this; | ||
| 5062 | var meta = me.getMeta(); | ||
| 5063 | var data = me.getDataset().data; | ||
| 5064 | var numMeta = meta.data.length; | ||
| 5065 | var numData = data.length; | ||
| 5066 | |||
| 5067 | 			if (numData < numMeta) { | ||
| 5068 | meta.data.splice(numData, numMeta - numData); | ||
| 5069 | 			} else if (numData > numMeta) { | ||
| 5070 | me.insertElements(numMeta, numData - numMeta); | ||
| 5071 | } | ||
| 5072 | }, | ||
| 5073 | |||
| 5074 | /** | ||
| 5075 | * @private | ||
| 5076 | */ | ||
| 5077 | 		insertElements: function(start, count) { | ||
| 5078 | 			for (var i = 0; i < count; ++i) { | ||
| 5079 | this.addElementAndReset(start + i); | ||
| 5080 | } | ||
| 5081 | }, | ||
| 5082 | |||
| 5083 | /** | ||
| 5084 | * @private | ||
| 5085 | */ | ||
| 5086 | 		onDataPush: function() { | ||
| 5087 | this.insertElements(this.getDataset().data.length - 1, arguments.length); | ||
| 5088 | }, | ||
| 5089 | |||
| 5090 | /** | ||
| 5091 | * @private | ||
| 5092 | */ | ||
| 5093 | 		onDataPop: function() { | ||
| 5094 | this.getMeta().data.pop(); | ||
| 5095 | }, | ||
| 5096 | |||
| 5097 | /** | ||
| 5098 | * @private | ||
| 5099 | */ | ||
| 5100 | 		onDataShift: function() { | ||
| 5101 | this.getMeta().data.shift(); | ||
| 5102 | }, | ||
| 5103 | |||
| 5104 | /** | ||
| 5105 | * @private | ||
| 5106 | */ | ||
| 5107 | 		onDataSplice: function(start, count) { | ||
| 5108 | this.getMeta().data.splice(start, count); | ||
| 5109 | this.insertElements(start, arguments.length - 2); | ||
| 5110 | }, | ||
| 5111 | |||
| 5112 | /** | ||
| 5113 | * @private | ||
| 5114 | */ | ||
| 5115 | 		onDataUnshift: function() { | ||
| 5116 | this.insertElements(0, arguments.length); | ||
| 5117 | } | ||
| 5118 | }); | ||
| 5119 | |||
| 5120 | Chart.DatasetController.extend = helpers.inherits; | ||
| 5121 | }; | ||
| 5122 | |||
| 5123 | },{"46":46}],26:[function(require,module,exports){ | ||
| 5124 | 'use strict'; | ||
| 5125 | |||
| 5126 | var helpers = require(46); | ||
| 5127 | |||
| 5128 | module.exports = { | ||
| 5129 | /** | ||
| 5130 | * @private | ||
| 5131 | */ | ||
| 5132 | 	_set: function(scope, values) { | ||
| 5133 | 		return helpers.merge(this[scope] || (this[scope] = {}), values); | ||
| 5134 | } | ||
| 5135 | }; | ||
| 5136 | |||
| 5137 | },{"46":46}],27:[function(require,module,exports){ | ||
| 5138 | 'use strict'; | ||
| 5139 | |||
| 5140 | var color = require(3); | ||
| 5141 | var helpers = require(46); | ||
| 5142 | |||
| 5143 | function interpolate(start, view, model, ease) { | ||
| 5144 | var keys = Object.keys(model); | ||
| 5145 | var i, ilen, key, actual, origin, target, type, c0, c1; | ||
| 5146 | |||
| 5147 | 	for (i = 0, ilen = keys.length; i < ilen; ++i) { | ||
| 5148 | key = keys[i]; | ||
| 5149 | |||
| 5150 | target = model[key]; | ||
| 5151 | |||
| 5152 | // if a value is added to the model after pivot() has been called, the view | ||
| 5153 | // doesn't contain it, so let's initialize the view to the target value. | ||
| 5154 | 		if (!view.hasOwnProperty(key)) { | ||
| 5155 | view[key] = target; | ||
| 5156 | } | ||
| 5157 | |||
| 5158 | actual = view[key]; | ||
| 5159 | |||
| 5160 | 		if (actual === target || key[0] === '_') { | ||
| 5161 | continue; | ||
| 5162 | } | ||
| 5163 | |||
| 5164 | 		if (!start.hasOwnProperty(key)) { | ||
| 5165 | start[key] = actual; | ||
| 5166 | } | ||
| 5167 | |||
| 5168 | origin = start[key]; | ||
| 5169 | |||
| 5170 | type = typeof target; | ||
| 5171 | |||
| 5172 | 		if (type === typeof origin) { | ||
| 5173 | 			if (type === 'string') { | ||
| 5174 | c0 = color(origin); | ||
| 5175 | 				if (c0.valid) { | ||
| 5176 | c1 = color(target); | ||
| 5177 | 					if (c1.valid) { | ||
| 5178 | view[key] = c1.mix(c0, ease).rgbString(); | ||
| 5179 | continue; | ||
| 5180 | } | ||
| 5181 | } | ||
| 5182 | 			} else if (type === 'number' && isFinite(origin) && isFinite(target)) { | ||
| 5183 | view[key] = origin + (target - origin) * ease; | ||
| 5184 | continue; | ||
| 5185 | } | ||
| 5186 | } | ||
| 5187 | |||
| 5188 | view[key] = target; | ||
| 5189 | } | ||
| 5190 | } | ||
| 5191 | |||
| 5192 | var Element = function(configuration) { | ||
| 5193 | helpers.extend(this, configuration); | ||
| 5194 | this.initialize.apply(this, arguments); | ||
| 5195 | }; | ||
| 5196 | |||
| 5197 | helpers.extend(Element.prototype, { | ||
| 5198 | |||
| 5199 | 	initialize: function() { | ||
| 5200 | this.hidden = false; | ||
| 5201 | }, | ||
| 5202 | |||
| 5203 | 	pivot: function() { | ||
| 5204 | var me = this; | ||
| 5205 | 		if (!me._view) { | ||
| 5206 | me._view = helpers.clone(me._model); | ||
| 5207 | } | ||
| 5208 | 		me._start = {}; | ||
| 5209 | return me; | ||
| 5210 | }, | ||
| 5211 | |||
| 5212 | 	transition: function(ease) { | ||
| 5213 | var me = this; | ||
| 5214 | var model = me._model; | ||
| 5215 | var start = me._start; | ||
| 5216 | var view = me._view; | ||
| 5217 | |||
| 5218 | // No animation -> No Transition | ||
| 5219 | 		if (!model || ease === 1) { | ||
| 5220 | me._view = model; | ||
| 5221 | me._start = null; | ||
| 5222 | return me; | ||
| 5223 | } | ||
| 5224 | |||
| 5225 | 		if (!view) { | ||
| 5226 | 			view = me._view = {}; | ||
| 5227 | } | ||
| 5228 | |||
| 5229 | 		if (!start) { | ||
| 5230 | 			start = me._start = {}; | ||
| 5231 | } | ||
| 5232 | |||
| 5233 | interpolate(start, view, model, ease); | ||
| 5234 | |||
| 5235 | return me; | ||
| 5236 | }, | ||
| 5237 | |||
| 5238 | 	tooltipPosition: function() { | ||
| 5239 | 		return { | ||
| 5240 | x: this._model.x, | ||
| 5241 | y: this._model.y | ||
| 5242 | }; | ||
| 5243 | }, | ||
| 5244 | |||
| 5245 | 	hasValue: function() { | ||
| 5246 | return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); | ||
| 5247 | } | ||
| 5248 | }); | ||
| 5249 | |||
| 5250 | Element.extend = helpers.inherits; | ||
| 5251 | |||
| 5252 | module.exports = Element; | ||
| 5253 | |||
| 5254 | },{"3":3,"46":46}],28:[function(require,module,exports){ | ||
| 5255 | /* global window: false */ | ||
| 5256 | /* global document: false */ | ||
| 5257 | 'use strict'; | ||
| 5258 | |||
| 5259 | var color = require(3); | ||
| 5260 | var defaults = require(26); | ||
| 5261 | var helpers = require(46); | ||
| 5262 | var scaleService = require(34); | ||
| 5263 | |||
| 5264 | module.exports = function() { | ||
| 5265 | |||
| 5266 | // -- Basic js utility methods | ||
| 5267 | |||
| 5268 | 	helpers.configMerge = function(/* objects ... */) { | ||
| 5269 | 		return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { | ||
| 5270 | 			merger: function(key, target, source, options) { | ||
| 5271 | 				var tval = target[key] || {}; | ||
| 5272 | var sval = source[key]; | ||
| 5273 | |||
| 5274 | 				if (key === 'scales') { | ||
| 5275 | // scale config merging is complex. Add our own function here for that | ||
| 5276 | target[key] = helpers.scaleMerge(tval, sval); | ||
| 5277 | 				} else if (key === 'scale') { | ||
| 5278 | // used in polar area & radar charts since there is only one scale | ||
| 5279 | target[key] = helpers.merge(tval, [scaleService.getScaleDefaults(sval.type), sval]); | ||
| 5280 | 				} else { | ||
| 5281 | helpers._merger(key, target, source, options); | ||
| 5282 | } | ||
| 5283 | } | ||
| 5284 | }); | ||
| 5285 | }; | ||
| 5286 | |||
| 5287 | 	helpers.scaleMerge = function(/* objects ... */) { | ||
| 5288 | 		return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { | ||
| 5289 | 			merger: function(key, target, source, options) { | ||
| 5290 | 				if (key === 'xAxes' || key === 'yAxes') { | ||
| 5291 | var slen = source[key].length; | ||
| 5292 | var i, type, scale; | ||
| 5293 | |||
| 5294 | 					if (!target[key]) { | ||
| 5295 | target[key] = []; | ||
| 5296 | } | ||
| 5297 | |||
| 5298 | 					for (i = 0; i < slen; ++i) { | ||
| 5299 | scale = source[key][i]; | ||
| 5300 | type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear'); | ||
| 5301 | |||
| 5302 | 						if (i >= target[key].length) { | ||
| 5303 | 							target[key].push({}); | ||
| 5304 | } | ||
| 5305 | |||
| 5306 | 						if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { | ||
| 5307 | // new/untyped scale or type changed: let's apply the new defaults | ||
| 5308 | // then merge source scale to correctly overwrite the defaults. | ||
| 5309 | helpers.merge(target[key][i], [scaleService.getScaleDefaults(type), scale]); | ||
| 5310 | 						} else { | ||
| 5311 | // scales type are the same | ||
| 5312 | helpers.merge(target[key][i], scale); | ||
| 5313 | } | ||
| 5314 | } | ||
| 5315 | 				} else { | ||
| 5316 | helpers._merger(key, target, source, options); | ||
| 5317 | } | ||
| 5318 | } | ||
| 5319 | }); | ||
| 5320 | }; | ||
| 5321 | |||
| 5322 | 	helpers.where = function(collection, filterCallback) { | ||
| 5323 | 		if (helpers.isArray(collection) && Array.prototype.filter) { | ||
| 5324 | return collection.filter(filterCallback); | ||
| 5325 | } | ||
| 5326 | var filtered = []; | ||
| 5327 | |||
| 5328 | 		helpers.each(collection, function(item) { | ||
| 5329 | 			if (filterCallback(item)) { | ||
| 5330 | filtered.push(item); | ||
| 5331 | } | ||
| 5332 | }); | ||
| 5333 | |||
| 5334 | return filtered; | ||
| 5335 | }; | ||
| 5336 | helpers.findIndex = Array.prototype.findIndex ? | ||
| 5337 | 		function(array, callback, scope) { | ||
| 5338 | return array.findIndex(callback, scope); | ||
| 5339 | } : | ||
| 5340 | 		function(array, callback, scope) { | ||
| 5341 | scope = scope === undefined ? array : scope; | ||
| 5342 | 			for (var i = 0, ilen = array.length; i < ilen; ++i) { | ||
| 5343 | 				if (callback.call(scope, array[i], i, array)) { | ||
| 5344 | return i; | ||
| 5345 | } | ||
| 5346 | } | ||
| 5347 | return -1; | ||
| 5348 | }; | ||
| 5349 | 	helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { | ||
| 5350 | // Default to start of the array | ||
| 5351 | 		if (helpers.isNullOrUndef(startIndex)) { | ||
| 5352 | startIndex = -1; | ||
| 5353 | } | ||
| 5354 | 		for (var i = startIndex + 1; i < arrayToSearch.length; i++) { | ||
| 5355 | var currentItem = arrayToSearch[i]; | ||
| 5356 | 			if (filterCallback(currentItem)) { | ||
| 5357 | return currentItem; | ||
| 5358 | } | ||
| 5359 | } | ||
| 5360 | }; | ||
| 5361 | 	helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { | ||
| 5362 | // Default to end of the array | ||
| 5363 | 		if (helpers.isNullOrUndef(startIndex)) { | ||
| 5364 | startIndex = arrayToSearch.length; | ||
| 5365 | } | ||
| 5366 | 		for (var i = startIndex - 1; i >= 0; i--) { | ||
| 5367 | var currentItem = arrayToSearch[i]; | ||
| 5368 | 			if (filterCallback(currentItem)) { | ||
| 5369 | return currentItem; | ||
| 5370 | } | ||
| 5371 | } | ||
| 5372 | }; | ||
| 5373 | |||
| 5374 | // -- Math methods | ||
| 5375 | 	helpers.isNumber = function(n) { | ||
| 5376 | return !isNaN(parseFloat(n)) && isFinite(n); | ||
| 5377 | }; | ||
| 5378 | 	helpers.almostEquals = function(x, y, epsilon) { | ||
| 5379 | return Math.abs(x - y) < epsilon; | ||
| 5380 | }; | ||
| 5381 | 	helpers.almostWhole = function(x, epsilon) { | ||
| 5382 | var rounded = Math.round(x); | ||
| 5383 | return (((rounded - epsilon) < x) && ((rounded + epsilon) > x)); | ||
| 5384 | }; | ||
| 5385 | 	helpers.max = function(array) { | ||
| 5386 | 		return array.reduce(function(max, value) { | ||
| 5387 | 			if (!isNaN(value)) { | ||
| 5388 | return Math.max(max, value); | ||
| 5389 | } | ||
| 5390 | return max; | ||
| 5391 | }, Number.NEGATIVE_INFINITY); | ||
| 5392 | }; | ||
| 5393 | 	helpers.min = function(array) { | ||
| 5394 | 		return array.reduce(function(min, value) { | ||
| 5395 | 			if (!isNaN(value)) { | ||
| 5396 | return Math.min(min, value); | ||
| 5397 | } | ||
| 5398 | return min; | ||
| 5399 | }, Number.POSITIVE_INFINITY); | ||
| 5400 | }; | ||
| 5401 | helpers.sign = Math.sign ? | ||
| 5402 | 		function(x) { | ||
| 5403 | return Math.sign(x); | ||
| 5404 | } : | ||
| 5405 | 		function(x) { | ||
| 5406 | x = +x; // convert to a number | ||
| 5407 | 			if (x === 0 || isNaN(x)) { | ||
| 5408 | return x; | ||
| 5409 | } | ||
| 5410 | return x > 0 ? 1 : -1; | ||
| 5411 | }; | ||
| 5412 | helpers.log10 = Math.log10 ? | ||
| 5413 | 		function(x) { | ||
| 5414 | return Math.log10(x); | ||
| 5415 | } : | ||
| 5416 | 		function(x) { | ||
| 5417 | var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. | ||
| 5418 | // Check for whole powers of 10, | ||
| 5419 | // which due to floating point rounding error should be corrected. | ||
| 5420 | var powerOf10 = Math.round(exponent); | ||
| 5421 | var isPowerOf10 = x === Math.pow(10, powerOf10); | ||
| 5422 | |||
| 5423 | return isPowerOf10 ? powerOf10 : exponent; | ||
| 5424 | }; | ||
| 5425 | 	helpers.toRadians = function(degrees) { | ||
| 5426 | return degrees * (Math.PI / 180); | ||
| 5427 | }; | ||
| 5428 | 	helpers.toDegrees = function(radians) { | ||
| 5429 | return radians * (180 / Math.PI); | ||
| 5430 | }; | ||
| 5431 | // Gets the angle from vertical upright to the point about a centre. | ||
| 5432 | 	helpers.getAngleFromPoint = function(centrePoint, anglePoint) { | ||
| 5433 | var distanceFromXCenter = anglePoint.x - centrePoint.x; | ||
| 5434 | var distanceFromYCenter = anglePoint.y - centrePoint.y; | ||
| 5435 | var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); | ||
| 5436 | |||
| 5437 | var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); | ||
| 5438 | |||
| 5439 | 		if (angle < (-0.5 * Math.PI)) { | ||
| 5440 | angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] | ||
| 5441 | } | ||
| 5442 | |||
| 5443 | 		return { | ||
| 5444 | angle: angle, | ||
| 5445 | distance: radialDistanceFromCenter | ||
| 5446 | }; | ||
| 5447 | }; | ||
| 5448 | 	helpers.distanceBetweenPoints = function(pt1, pt2) { | ||
| 5449 | return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); | ||
| 5450 | }; | ||
| 5451 | 	helpers.aliasPixel = function(pixelWidth) { | ||
| 5452 | return (pixelWidth % 2 === 0) ? 0 : 0.5; | ||
| 5453 | }; | ||
| 5454 | 	helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { | ||
| 5455 | // Props to Rob Spencer at scaled innovation for his post on splining between points | ||
| 5456 | // http://scaledinnovation.com/analytics/splines/aboutSplines.html | ||
| 5457 | |||
| 5458 | // This function must also respect "skipped" points | ||
| 5459 | |||
| 5460 | var previous = firstPoint.skip ? middlePoint : firstPoint; | ||
| 5461 | var current = middlePoint; | ||
| 5462 | var next = afterPoint.skip ? middlePoint : afterPoint; | ||
| 5463 | |||
| 5464 | var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); | ||
| 5465 | var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); | ||
| 5466 | |||
| 5467 | var s01 = d01 / (d01 + d12); | ||
| 5468 | var s12 = d12 / (d01 + d12); | ||
| 5469 | |||
| 5470 | // If all points are the same, s01 & s02 will be inf | ||
| 5471 | s01 = isNaN(s01) ? 0 : s01; | ||
| 5472 | s12 = isNaN(s12) ? 0 : s12; | ||
| 5473 | |||
| 5474 | var fa = t * s01; // scaling factor for triangle Ta | ||
| 5475 | var fb = t * s12; | ||
| 5476 | |||
| 5477 | 		return { | ||
| 5478 | 			previous: { | ||
| 5479 | x: current.x - fa * (next.x - previous.x), | ||
| 5480 | y: current.y - fa * (next.y - previous.y) | ||
| 5481 | }, | ||
| 5482 | 			next: { | ||
| 5483 | x: current.x + fb * (next.x - previous.x), | ||
| 5484 | y: current.y + fb * (next.y - previous.y) | ||
| 5485 | } | ||
| 5486 | }; | ||
| 5487 | }; | ||
| 5488 | helpers.EPSILON = Number.EPSILON || 1e-14; | ||
| 5489 | 	helpers.splineCurveMonotone = function(points) { | ||
| 5490 | // This function calculates Bézier control points in a similar way than |splineCurve|, | ||
| 5491 | // but preserves monotonicity of the provided data and ensures no local extremums are added | ||
| 5492 | // between the dataset discrete points due to the interpolation. | ||
| 5493 | // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation | ||
| 5494 | |||
| 5495 | 		var pointsWithTangents = (points || []).map(function(point) { | ||
| 5496 | 			return { | ||
| 5497 | model: point._model, | ||
| 5498 | deltaK: 0, | ||
| 5499 | mK: 0 | ||
| 5500 | }; | ||
| 5501 | }); | ||
| 5502 | |||
| 5503 | // Calculate slopes (deltaK) and initialize tangents (mK) | ||
| 5504 | var pointsLen = pointsWithTangents.length; | ||
| 5505 | var i, pointBefore, pointCurrent, pointAfter; | ||
| 5506 | 		for (i = 0; i < pointsLen; ++i) { | ||
| 5507 | pointCurrent = pointsWithTangents[i]; | ||
| 5508 | 			if (pointCurrent.model.skip) { | ||
| 5509 | continue; | ||
| 5510 | } | ||
| 5511 | |||
| 5512 | pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; | ||
| 5513 | pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; | ||
| 5514 | 			if (pointAfter && !pointAfter.model.skip) { | ||
| 5515 | var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); | ||
| 5516 | |||
| 5517 | // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 | ||
| 5518 | pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; | ||
| 5519 | } | ||
| 5520 | |||
| 5521 | 			if (!pointBefore || pointBefore.model.skip) { | ||
| 5522 | pointCurrent.mK = pointCurrent.deltaK; | ||
| 5523 | 			} else if (!pointAfter || pointAfter.model.skip) { | ||
| 5524 | pointCurrent.mK = pointBefore.deltaK; | ||
| 5525 | 			} else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { | ||
| 5526 | pointCurrent.mK = 0; | ||
| 5527 | 			} else { | ||
| 5528 | pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; | ||
| 5529 | } | ||
| 5530 | } | ||
| 5531 | |||
| 5532 | // Adjust tangents to ensure monotonic properties | ||
| 5533 | var alphaK, betaK, tauK, squaredMagnitude; | ||
| 5534 | 		for (i = 0; i < pointsLen - 1; ++i) { | ||
| 5535 | pointCurrent = pointsWithTangents[i]; | ||
| 5536 | pointAfter = pointsWithTangents[i + 1]; | ||
| 5537 | 			if (pointCurrent.model.skip || pointAfter.model.skip) { | ||
| 5538 | continue; | ||
| 5539 | } | ||
| 5540 | |||
| 5541 | 			if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { | ||
| 5542 | pointCurrent.mK = pointAfter.mK = 0; | ||
| 5543 | continue; | ||
| 5544 | } | ||
| 5545 | |||
| 5546 | alphaK = pointCurrent.mK / pointCurrent.deltaK; | ||
| 5547 | betaK = pointAfter.mK / pointCurrent.deltaK; | ||
| 5548 | squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); | ||
| 5549 | 			if (squaredMagnitude <= 9) { | ||
| 5550 | continue; | ||
| 5551 | } | ||
| 5552 | |||
| 5553 | tauK = 3 / Math.sqrt(squaredMagnitude); | ||
| 5554 | pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; | ||
| 5555 | pointAfter.mK = betaK * tauK * pointCurrent.deltaK; | ||
| 5556 | } | ||
| 5557 | |||
| 5558 | // Compute control points | ||
| 5559 | var deltaX; | ||
| 5560 | 		for (i = 0; i < pointsLen; ++i) { | ||
| 5561 | pointCurrent = pointsWithTangents[i]; | ||
| 5562 | 			if (pointCurrent.model.skip) { | ||
| 5563 | continue; | ||
| 5564 | } | ||
| 5565 | |||
| 5566 | pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; | ||
| 5567 | pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; | ||
| 5568 | 			if (pointBefore && !pointBefore.model.skip) { | ||
| 5569 | deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; | ||
| 5570 | pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; | ||
| 5571 | pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; | ||
| 5572 | } | ||
| 5573 | 			if (pointAfter && !pointAfter.model.skip) { | ||
| 5574 | deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; | ||
| 5575 | pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; | ||
| 5576 | pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; | ||
| 5577 | } | ||
| 5578 | } | ||
| 5579 | }; | ||
| 5580 | 	helpers.nextItem = function(collection, index, loop) { | ||
| 5581 | 		if (loop) { | ||
| 5582 | return index >= collection.length - 1 ? collection[0] : collection[index + 1]; | ||
| 5583 | } | ||
| 5584 | return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; | ||
| 5585 | }; | ||
| 5586 | 	helpers.previousItem = function(collection, index, loop) { | ||
| 5587 | 		if (loop) { | ||
| 5588 | return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; | ||
| 5589 | } | ||
| 5590 | return index <= 0 ? collection[0] : collection[index - 1]; | ||
| 5591 | }; | ||
| 5592 | // Implementation of the nice number algorithm used in determining where axis labels will go | ||
| 5593 | 	helpers.niceNum = function(range, round) { | ||
| 5594 | var exponent = Math.floor(helpers.log10(range)); | ||
| 5595 | var fraction = range / Math.pow(10, exponent); | ||
| 5596 | var niceFraction; | ||
| 5597 | |||
| 5598 | 		if (round) { | ||
| 5599 | 			if (fraction < 1.5) { | ||
| 5600 | niceFraction = 1; | ||
| 5601 | 			} else if (fraction < 3) { | ||
| 5602 | niceFraction = 2; | ||
| 5603 | 			} else if (fraction < 7) { | ||
| 5604 | niceFraction = 5; | ||
| 5605 | 			} else { | ||
| 5606 | niceFraction = 10; | ||
| 5607 | } | ||
| 5608 | 		} else if (fraction <= 1.0) { | ||
| 5609 | niceFraction = 1; | ||
| 5610 | 		} else if (fraction <= 2) { | ||
| 5611 | niceFraction = 2; | ||
| 5612 | 		} else if (fraction <= 5) { | ||
| 5613 | niceFraction = 5; | ||
| 5614 | 		} else { | ||
| 5615 | niceFraction = 10; | ||
| 5616 | } | ||
| 5617 | |||
| 5618 | return niceFraction * Math.pow(10, exponent); | ||
| 5619 | }; | ||
| 5620 | // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ | ||
| 5621 | 	helpers.requestAnimFrame = (function() { | ||
| 5622 | 		if (typeof window === 'undefined') { | ||
| 5623 | 			return function(callback) { | ||
| 5624 | callback(); | ||
| 5625 | }; | ||
| 5626 | } | ||
| 5627 | return window.requestAnimationFrame || | ||
| 5628 | window.webkitRequestAnimationFrame || | ||
| 5629 | window.mozRequestAnimationFrame || | ||
| 5630 | window.oRequestAnimationFrame || | ||
| 5631 | window.msRequestAnimationFrame || | ||
| 5632 | 			function(callback) { | ||
| 5633 | return window.setTimeout(callback, 1000 / 60); | ||
| 5634 | }; | ||
| 5635 | }()); | ||
| 5636 | // -- DOM methods | ||
| 5637 | 	helpers.getRelativePosition = function(evt, chart) { | ||
| 5638 | var mouseX, mouseY; | ||
| 5639 | var e = evt.originalEvent || evt; | ||
| 5640 | var canvas = evt.target || evt.srcElement; | ||
| 5641 | var boundingRect = canvas.getBoundingClientRect(); | ||
| 5642 | |||
| 5643 | var touches = e.touches; | ||
| 5644 | 		if (touches && touches.length > 0) { | ||
| 5645 | mouseX = touches[0].clientX; | ||
| 5646 | mouseY = touches[0].clientY; | ||
| 5647 | |||
| 5648 | 		} else { | ||
| 5649 | mouseX = e.clientX; | ||
| 5650 | mouseY = e.clientY; | ||
| 5651 | } | ||
| 5652 | |||
| 5653 | // Scale mouse coordinates into canvas coordinates | ||
| 5654 | // by following the pattern laid out by 'jerryj' in the comments of | ||
| 5655 | // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ | ||
| 5656 | var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); | ||
| 5657 | var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); | ||
| 5658 | var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); | ||
| 5659 | var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); | ||
| 5660 | var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; | ||
| 5661 | var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; | ||
| 5662 | |||
| 5663 | // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However | ||
| 5664 | // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here | ||
| 5665 | mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); | ||
| 5666 | mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); | ||
| 5667 | |||
| 5668 | 		return { | ||
| 5669 | x: mouseX, | ||
| 5670 | y: mouseY | ||
| 5671 | }; | ||
| 5672 | |||
| 5673 | }; | ||
| 5674 | |||
| 5675 | // Private helper function to convert max-width/max-height values that may be percentages into a number | ||
| 5676 | 	function parseMaxStyle(styleValue, node, parentProperty) { | ||
| 5677 | var valueInPixels; | ||
| 5678 | 		if (typeof styleValue === 'string') { | ||
| 5679 | valueInPixels = parseInt(styleValue, 10); | ||
| 5680 | |||
| 5681 | 			if (styleValue.indexOf('%') !== -1) { | ||
| 5682 | // percentage * size in dimension | ||
| 5683 | valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; | ||
| 5684 | } | ||
| 5685 | 		} else { | ||
| 5686 | valueInPixels = styleValue; | ||
| 5687 | } | ||
| 5688 | |||
| 5689 | return valueInPixels; | ||
| 5690 | } | ||
| 5691 | |||
| 5692 | /** | ||
| 5693 | * Returns if the given value contains an effective constraint. | ||
| 5694 | * @private | ||
| 5695 | */ | ||
| 5696 | 	function isConstrainedValue(value) { | ||
| 5697 | return value !== undefined && value !== null && value !== 'none'; | ||
| 5698 | } | ||
| 5699 | |||
| 5700 | // Private helper to get a constraint dimension | ||
| 5701 | // @param domNode : the node to check the constraint on | ||
| 5702 | // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) | ||
| 5703 | // @param percentageProperty : property of parent to use when calculating width as a percentage | ||
| 5704 | // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser | ||
| 5705 | 	function getConstraintDimension(domNode, maxStyle, percentageProperty) { | ||
| 5706 | var view = document.defaultView; | ||
| 5707 | var parentNode = helpers._getParentNode(domNode); | ||
| 5708 | var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; | ||
| 5709 | var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; | ||
| 5710 | var hasCNode = isConstrainedValue(constrainedNode); | ||
| 5711 | var hasCContainer = isConstrainedValue(constrainedContainer); | ||
| 5712 | var infinity = Number.POSITIVE_INFINITY; | ||
| 5713 | |||
| 5714 | 		if (hasCNode || hasCContainer) { | ||
| 5715 | return Math.min( | ||
| 5716 | hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, | ||
| 5717 | hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); | ||
| 5718 | } | ||
| 5719 | |||
| 5720 | return 'none'; | ||
| 5721 | } | ||
| 5722 | // returns Number or undefined if no constraint | ||
| 5723 | 	helpers.getConstraintWidth = function(domNode) { | ||
| 5724 | return getConstraintDimension(domNode, 'max-width', 'clientWidth'); | ||
| 5725 | }; | ||
| 5726 | // returns Number or undefined if no constraint | ||
| 5727 | 	helpers.getConstraintHeight = function(domNode) { | ||
| 5728 | return getConstraintDimension(domNode, 'max-height', 'clientHeight'); | ||
| 5729 | }; | ||
| 5730 | /** | ||
| 5731 | * @private | ||
| 5732 | */ | ||
| 5733 | 	helpers._calculatePadding = function(container, padding, parentDimension) { | ||
| 5734 | padding = helpers.getStyle(container, padding); | ||
| 5735 | |||
| 5736 | 		return padding.indexOf('%') > -1 ? parentDimension / parseInt(padding, 10) : parseInt(padding, 10); | ||
| 5737 | }; | ||
| 5738 | /** | ||
| 5739 | * @private | ||
| 5740 | */ | ||
| 5741 | 	helpers._getParentNode = function(domNode) { | ||
| 5742 | var parent = domNode.parentNode; | ||
| 5743 | 		if (parent && parent.host) { | ||
| 5744 | parent = parent.host; | ||
| 5745 | } | ||
| 5746 | return parent; | ||
| 5747 | }; | ||
| 5748 | 	helpers.getMaximumWidth = function(domNode) { | ||
| 5749 | var container = helpers._getParentNode(domNode); | ||
| 5750 | 		if (!container) { | ||
| 5751 | return domNode.clientWidth; | ||
| 5752 | } | ||
| 5753 | |||
| 5754 | var clientWidth = container.clientWidth; | ||
| 5755 | var paddingLeft = helpers._calculatePadding(container, 'padding-left', clientWidth); | ||
| 5756 | var paddingRight = helpers._calculatePadding(container, 'padding-right', clientWidth); | ||
| 5757 | |||
| 5758 | var w = clientWidth - paddingLeft - paddingRight; | ||
| 5759 | var cw = helpers.getConstraintWidth(domNode); | ||
| 5760 | return isNaN(cw) ? w : Math.min(w, cw); | ||
| 5761 | }; | ||
| 5762 | 	helpers.getMaximumHeight = function(domNode) { | ||
| 5763 | var container = helpers._getParentNode(domNode); | ||
| 5764 | 		if (!container) { | ||
| 5765 | return domNode.clientHeight; | ||
| 5766 | } | ||
| 5767 | |||
| 5768 | var clientHeight = container.clientHeight; | ||
| 5769 | var paddingTop = helpers._calculatePadding(container, 'padding-top', clientHeight); | ||
| 5770 | var paddingBottom = helpers._calculatePadding(container, 'padding-bottom', clientHeight); | ||
| 5771 | |||
| 5772 | var h = clientHeight - paddingTop - paddingBottom; | ||
| 5773 | var ch = helpers.getConstraintHeight(domNode); | ||
| 5774 | return isNaN(ch) ? h : Math.min(h, ch); | ||
| 5775 | }; | ||
| 5776 | 	helpers.getStyle = function(el, property) { | ||
| 5777 | return el.currentStyle ? | ||
| 5778 | el.currentStyle[property] : | ||
| 5779 | document.defaultView.getComputedStyle(el, null).getPropertyValue(property); | ||
| 5780 | }; | ||
| 5781 | 	helpers.retinaScale = function(chart, forceRatio) { | ||
| 5782 | var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; | ||
| 5783 | 		if (pixelRatio === 1) { | ||
| 5784 | return; | ||
| 5785 | } | ||
| 5786 | |||
| 5787 | var canvas = chart.canvas; | ||
| 5788 | var height = chart.height; | ||
| 5789 | var width = chart.width; | ||
| 5790 | |||
| 5791 | canvas.height = height * pixelRatio; | ||
| 5792 | canvas.width = width * pixelRatio; | ||
| 5793 | chart.ctx.scale(pixelRatio, pixelRatio); | ||
| 5794 | |||
| 5795 | // If no style has been set on the canvas, the render size is used as display size, | ||
| 5796 | // making the chart visually bigger, so let's enforce it to the "correct" values. | ||
| 5797 | // See https://github.com/chartjs/Chart.js/issues/3575 | ||
| 5798 | 		if (!canvas.style.height && !canvas.style.width) { | ||
| 5799 | canvas.style.height = height + 'px'; | ||
| 5800 | canvas.style.width = width + 'px'; | ||
| 5801 | } | ||
| 5802 | }; | ||
| 5803 | // -- Canvas methods | ||
| 5804 | 	helpers.fontString = function(pixelSize, fontStyle, fontFamily) { | ||
| 5805 | return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; | ||
| 5806 | }; | ||
| 5807 | 	helpers.longestText = function(ctx, font, arrayOfThings, cache) { | ||
| 5808 | 		cache = cache || {}; | ||
| 5809 | 		var data = cache.data = cache.data || {}; | ||
| 5810 | var gc = cache.garbageCollect = cache.garbageCollect || []; | ||
| 5811 | |||
| 5812 | 		if (cache.font !== font) { | ||
| 5813 | 			data = cache.data = {}; | ||
| 5814 | gc = cache.garbageCollect = []; | ||
| 5815 | cache.font = font; | ||
| 5816 | } | ||
| 5817 | |||
| 5818 | ctx.font = font; | ||
| 5819 | var longest = 0; | ||
| 5820 | 		helpers.each(arrayOfThings, function(thing) { | ||
| 5821 | // Undefined strings and arrays should not be measured | ||
| 5822 | 			if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { | ||
| 5823 | longest = helpers.measureText(ctx, data, gc, longest, thing); | ||
| 5824 | 			} else if (helpers.isArray(thing)) { | ||
| 5825 | // if it is an array lets measure each element | ||
| 5826 | // to do maybe simplify this function a bit so we can do this more recursively? | ||
| 5827 | 				helpers.each(thing, function(nestedThing) { | ||
| 5828 | // Undefined strings and arrays should not be measured | ||
| 5829 | 					if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { | ||
| 5830 | longest = helpers.measureText(ctx, data, gc, longest, nestedThing); | ||
| 5831 | } | ||
| 5832 | }); | ||
| 5833 | } | ||
| 5834 | }); | ||
| 5835 | |||
| 5836 | var gcLen = gc.length / 2; | ||
| 5837 | 		if (gcLen > arrayOfThings.length) { | ||
| 5838 | 			for (var i = 0; i < gcLen; i++) { | ||
| 5839 | delete data[gc[i]]; | ||
| 5840 | } | ||
| 5841 | gc.splice(0, gcLen); | ||
| 5842 | } | ||
| 5843 | return longest; | ||
| 5844 | }; | ||
| 5845 | 	helpers.measureText = function(ctx, data, gc, longest, string) { | ||
| 5846 | var textWidth = data[string]; | ||
| 5847 | 		if (!textWidth) { | ||
| 5848 | textWidth = data[string] = ctx.measureText(string).width; | ||
| 5849 | gc.push(string); | ||
| 5850 | } | ||
| 5851 | 		if (textWidth > longest) { | ||
| 5852 | longest = textWidth; | ||
| 5853 | } | ||
| 5854 | return longest; | ||
| 5855 | }; | ||
| 5856 | 	helpers.numberOfLabelLines = function(arrayOfThings) { | ||
| 5857 | var numberOfLines = 1; | ||
| 5858 | 		helpers.each(arrayOfThings, function(thing) { | ||
| 5859 | 			if (helpers.isArray(thing)) { | ||
| 5860 | 				if (thing.length > numberOfLines) { | ||
| 5861 | numberOfLines = thing.length; | ||
| 5862 | } | ||
| 5863 | } | ||
| 5864 | }); | ||
| 5865 | return numberOfLines; | ||
| 5866 | }; | ||
| 5867 | |||
| 5868 | helpers.color = !color ? | ||
| 5869 | 		function(value) { | ||
| 5870 | 			console.error('Color.js not found!'); | ||
| 5871 | return value; | ||
| 5872 | } : | ||
| 5873 | 		function(value) { | ||
| 5874 | /* global CanvasGradient */ | ||
| 5875 | 			if (value instanceof CanvasGradient) { | ||
| 5876 | value = defaults.global.defaultColor; | ||
| 5877 | } | ||
| 5878 | |||
| 5879 | return color(value); | ||
| 5880 | }; | ||
| 5881 | |||
| 5882 | 	helpers.getHoverColor = function(colorValue) { | ||
| 5883 | /* global CanvasPattern */ | ||
| 5884 | return (colorValue instanceof CanvasPattern) ? | ||
| 5885 | colorValue : | ||
| 5886 | helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString(); | ||
| 5887 | }; | ||
| 5888 | }; | ||
| 5889 | |||
| 5890 | },{"26":26,"3":3,"34":34,"46":46}],29:[function(require,module,exports){ | ||
| 5891 | 'use strict'; | ||
| 5892 | |||
| 5893 | var helpers = require(46); | ||
| 5894 | |||
| 5895 | /** | ||
| 5896 | * Helper function to get relative position for an event | ||
| 5897 |  * @param {Event|IEvent} event - The event to get the position for | ||
| 5898 |  * @param {Chart} chart - The chart | ||
| 5899 |  * @returns {Point} the event position | ||
| 5900 | */ | ||
| 5901 | function getRelativePosition(e, chart) { | ||
| 5902 | 	if (e.native) { | ||
| 5903 | 		return { | ||
| 5904 | x: e.x, | ||
| 5905 | y: e.y | ||
| 5906 | }; | ||
| 5907 | } | ||
| 5908 | |||
| 5909 | return helpers.getRelativePosition(e, chart); | ||
| 5910 | } | ||
| 5911 | |||
| 5912 | /** | ||
| 5913 | * Helper function to traverse all of the visible elements in the chart | ||
| 5914 |  * @param chart {chart} the chart | ||
| 5915 |  * @param handler {Function} the callback to execute for each visible item | ||
| 5916 | */ | ||
| 5917 | function parseVisibleItems(chart, handler) { | ||
| 5918 | var datasets = chart.data.datasets; | ||
| 5919 | var meta, i, j, ilen, jlen; | ||
| 5920 | |||
| 5921 | 	for (i = 0, ilen = datasets.length; i < ilen; ++i) { | ||
| 5922 | 		if (!chart.isDatasetVisible(i)) { | ||
| 5923 | continue; | ||
| 5924 | } | ||
| 5925 | |||
| 5926 | meta = chart.getDatasetMeta(i); | ||
| 5927 | 		for (j = 0, jlen = meta.data.length; j < jlen; ++j) { | ||
| 5928 | var element = meta.data[j]; | ||
| 5929 | 			if (!element._view.skip) { | ||
| 5930 | handler(element); | ||
| 5931 | } | ||
| 5932 | } | ||
| 5933 | } | ||
| 5934 | } | ||
| 5935 | |||
| 5936 | /** | ||
| 5937 | * Helper function to get the items that intersect the event position | ||
| 5938 |  * @param items {ChartElement[]} elements to filter | ||
| 5939 |  * @param position {Point} the point to be nearest to | ||
| 5940 |  * @return {ChartElement[]} the nearest items | ||
| 5941 | */ | ||
| 5942 | function getIntersectItems(chart, position) { | ||
| 5943 | var elements = []; | ||
| 5944 | |||
| 5945 | 	parseVisibleItems(chart, function(element) { | ||
| 5946 | 		if (element.inRange(position.x, position.y)) { | ||
| 5947 | elements.push(element); | ||
| 5948 | } | ||
| 5949 | }); | ||
| 5950 | |||
| 5951 | return elements; | ||
| 5952 | } | ||
| 5953 | |||
| 5954 | /** | ||
| 5955 | * Helper function to get the items nearest to the event position considering all visible items in teh chart | ||
| 5956 |  * @param chart {Chart} the chart to look at elements from | ||
| 5957 |  * @param position {Point} the point to be nearest to | ||
| 5958 |  * @param intersect {Boolean} if true, only consider items that intersect the position | ||
| 5959 |  * @param distanceMetric {Function} function to provide the distance between points | ||
| 5960 |  * @return {ChartElement[]} the nearest items | ||
| 5961 | */ | ||
| 5962 | function getNearestItems(chart, position, intersect, distanceMetric) { | ||
| 5963 | var minDistance = Number.POSITIVE_INFINITY; | ||
| 5964 | var nearestItems = []; | ||
| 5965 | |||
| 5966 | 	parseVisibleItems(chart, function(element) { | ||
| 5967 | 		if (intersect && !element.inRange(position.x, position.y)) { | ||
| 5968 | return; | ||
| 5969 | } | ||
| 5970 | |||
| 5971 | var center = element.getCenterPoint(); | ||
| 5972 | var distance = distanceMetric(position, center); | ||
| 5973 | |||
| 5974 | 		if (distance < minDistance) { | ||
| 5975 | nearestItems = [element]; | ||
| 5976 | minDistance = distance; | ||
| 5977 | 		} else if (distance === minDistance) { | ||
| 5978 | // Can have multiple items at the same distance in which case we sort by size | ||
| 5979 | nearestItems.push(element); | ||
| 5980 | } | ||
| 5981 | }); | ||
| 5982 | |||
| 5983 | return nearestItems; | ||
| 5984 | } | ||
| 5985 | |||
| 5986 | /** | ||
| 5987 | * Get a distance metric function for two points based on the | ||
| 5988 | * axis mode setting | ||
| 5989 |  * @param {String} axis the axis mode. x|y|xy | ||
| 5990 | */ | ||
| 5991 | function getDistanceMetricForAxis(axis) { | ||
| 5992 | 	var useX = axis.indexOf('x') !== -1; | ||
| 5993 | 	var useY = axis.indexOf('y') !== -1; | ||
| 5994 | |||
| 5995 | 	return function(pt1, pt2) { | ||
| 5996 | var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; | ||
| 5997 | var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; | ||
| 5998 | return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); | ||
| 5999 | }; | ||
| 6000 | } | ||
| 6001 | |||
| 6002 | function indexMode(chart, e, options) { | ||
| 6003 | var position = getRelativePosition(e, chart); | ||
| 6004 | // Default axis for index mode is 'x' to match old behaviour | ||
| 6005 | options.axis = options.axis || 'x'; | ||
| 6006 | var distanceMetric = getDistanceMetricForAxis(options.axis); | ||
| 6007 | var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); | ||
| 6008 | var elements = []; | ||
| 6009 | |||
| 6010 | 	if (!items.length) { | ||
| 6011 | return []; | ||
| 6012 | } | ||
| 6013 | |||
| 6014 | 	chart.data.datasets.forEach(function(dataset, datasetIndex) { | ||
| 6015 | 		if (chart.isDatasetVisible(datasetIndex)) { | ||
| 6016 | var meta = chart.getDatasetMeta(datasetIndex); | ||
| 6017 | var element = meta.data[items[0]._index]; | ||
| 6018 | |||
| 6019 | // don't count items that are skipped (null data) | ||
| 6020 | 			if (element && !element._view.skip) { | ||
| 6021 | elements.push(element); | ||
| 6022 | } | ||
| 6023 | } | ||
| 6024 | }); | ||
| 6025 | |||
| 6026 | return elements; | ||
| 6027 | } | ||
| 6028 | |||
| 6029 | /** | ||
| 6030 | * @interface IInteractionOptions | ||
| 6031 | */ | ||
| 6032 | /** | ||
| 6033 | * If true, only consider items that intersect the point | ||
| 6034 | * @name IInterfaceOptions#boolean | ||
| 6035 | * @type Boolean | ||
| 6036 | */ | ||
| 6037 | |||
| 6038 | /** | ||
| 6039 | * Contains interaction related functions | ||
| 6040 | * @namespace Chart.Interaction | ||
| 6041 | */ | ||
| 6042 | module.exports = { | ||
| 6043 | // Helper function for different modes | ||
| 6044 | 	modes: { | ||
| 6045 | 		single: function(chart, e) { | ||
| 6046 | var position = getRelativePosition(e, chart); | ||
| 6047 | var elements = []; | ||
| 6048 | |||
| 6049 | 			parseVisibleItems(chart, function(element) { | ||
| 6050 | 				if (element.inRange(position.x, position.y)) { | ||
| 6051 | elements.push(element); | ||
| 6052 | return elements; | ||
| 6053 | } | ||
| 6054 | }); | ||
| 6055 | |||
| 6056 | return elements.slice(0, 1); | ||
| 6057 | }, | ||
| 6058 | |||
| 6059 | /** | ||
| 6060 | * @function Chart.Interaction.modes.label | ||
| 6061 | * @deprecated since version 2.4.0 | ||
| 6062 | * @todo remove at version 3 | ||
| 6063 | * @private | ||
| 6064 | */ | ||
| 6065 | label: indexMode, | ||
| 6066 | |||
| 6067 | /** | ||
| 6068 | * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something | ||
| 6069 | * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item | ||
| 6070 | * @function Chart.Interaction.modes.index | ||
| 6071 | * @since v2.4.0 | ||
| 6072 | 		 * @param chart {chart} the chart we are returning items from | ||
| 6073 | 		 * @param e {Event} the event we are find things at | ||
| 6074 | 		 * @param options {IInteractionOptions} options to use during interaction | ||
| 6075 | 		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned | ||
| 6076 | */ | ||
| 6077 | index: indexMode, | ||
| 6078 | |||
| 6079 | /** | ||
| 6080 | * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something | ||
| 6081 | * If the options.intersect is false, we find the nearest item and return the items in that dataset | ||
| 6082 | * @function Chart.Interaction.modes.dataset | ||
| 6083 | 		 * @param chart {chart} the chart we are returning items from | ||
| 6084 | 		 * @param e {Event} the event we are find things at | ||
| 6085 | 		 * @param options {IInteractionOptions} options to use during interaction | ||
| 6086 | 		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned | ||
| 6087 | */ | ||
| 6088 | 		dataset: function(chart, e, options) { | ||
| 6089 | var position = getRelativePosition(e, chart); | ||
| 6090 | options.axis = options.axis || 'xy'; | ||
| 6091 | var distanceMetric = getDistanceMetricForAxis(options.axis); | ||
| 6092 | var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); | ||
| 6093 | |||
| 6094 | 			if (items.length > 0) { | ||
| 6095 | items = chart.getDatasetMeta(items[0]._datasetIndex).data; | ||
| 6096 | } | ||
| 6097 | |||
| 6098 | return items; | ||
| 6099 | }, | ||
| 6100 | |||
| 6101 | /** | ||
| 6102 | * @function Chart.Interaction.modes.x-axis | ||
| 6103 | * @deprecated since version 2.4.0. Use index mode and intersect == true | ||
| 6104 | * @todo remove at version 3 | ||
| 6105 | * @private | ||
| 6106 | */ | ||
| 6107 | 		'x-axis': function(chart, e) { | ||
| 6108 | 			return indexMode(chart, e, {intersect: false}); | ||
| 6109 | }, | ||
| 6110 | |||
| 6111 | /** | ||
| 6112 | * Point mode returns all elements that hit test based on the event position | ||
| 6113 | * of the event | ||
| 6114 | * @function Chart.Interaction.modes.intersect | ||
| 6115 | 		 * @param chart {chart} the chart we are returning items from | ||
| 6116 | 		 * @param e {Event} the event we are find things at | ||
| 6117 | 		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned | ||
| 6118 | */ | ||
| 6119 | 		point: function(chart, e) { | ||
| 6120 | var position = getRelativePosition(e, chart); | ||
| 6121 | return getIntersectItems(chart, position); | ||
| 6122 | }, | ||
| 6123 | |||
| 6124 | /** | ||
| 6125 | * nearest mode returns the element closest to the point | ||
| 6126 | * @function Chart.Interaction.modes.intersect | ||
| 6127 | 		 * @param chart {chart} the chart we are returning items from | ||
| 6128 | 		 * @param e {Event} the event we are find things at | ||
| 6129 | 		 * @param options {IInteractionOptions} options to use | ||
| 6130 | 		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned | ||
| 6131 | */ | ||
| 6132 | 		nearest: function(chart, e, options) { | ||
| 6133 | var position = getRelativePosition(e, chart); | ||
| 6134 | options.axis = options.axis || 'xy'; | ||
| 6135 | var distanceMetric = getDistanceMetricForAxis(options.axis); | ||
| 6136 | var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric); | ||
| 6137 | |||
| 6138 | // We have multiple items at the same distance from the event. Now sort by smallest | ||
| 6139 | 			if (nearestItems.length > 1) { | ||
| 6140 | 				nearestItems.sort(function(a, b) { | ||
| 6141 | var sizeA = a.getArea(); | ||
| 6142 | var sizeB = b.getArea(); | ||
| 6143 | var ret = sizeA - sizeB; | ||
| 6144 | |||
| 6145 | 					if (ret === 0) { | ||
| 6146 | // if equal sort by dataset index | ||
| 6147 | ret = a._datasetIndex - b._datasetIndex; | ||
| 6148 | } | ||
| 6149 | |||
| 6150 | return ret; | ||
| 6151 | }); | ||
| 6152 | } | ||
| 6153 | |||
| 6154 | // Return only 1 item | ||
| 6155 | return nearestItems.slice(0, 1); | ||
| 6156 | }, | ||
| 6157 | |||
| 6158 | /** | ||
| 6159 | * x mode returns the elements that hit-test at the current x coordinate | ||
| 6160 | * @function Chart.Interaction.modes.x | ||
| 6161 | 		 * @param chart {chart} the chart we are returning items from | ||
| 6162 | 		 * @param e {Event} the event we are find things at | ||
| 6163 | 		 * @param options {IInteractionOptions} options to use | ||
| 6164 | 		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned | ||
| 6165 | */ | ||
| 6166 | 		x: function(chart, e, options) { | ||
| 6167 | var position = getRelativePosition(e, chart); | ||
| 6168 | var items = []; | ||
| 6169 | var intersectsItem = false; | ||
| 6170 | |||
| 6171 | 			parseVisibleItems(chart, function(element) { | ||
| 6172 | 				if (element.inXRange(position.x)) { | ||
| 6173 | items.push(element); | ||
| 6174 | } | ||
| 6175 | |||
| 6176 | 				if (element.inRange(position.x, position.y)) { | ||
| 6177 | intersectsItem = true; | ||
| 6178 | } | ||
| 6179 | }); | ||
| 6180 | |||
| 6181 | // If we want to trigger on an intersect and we don't have any items | ||
| 6182 | // that intersect the position, return nothing | ||
| 6183 | 			if (options.intersect && !intersectsItem) { | ||
| 6184 | items = []; | ||
| 6185 | } | ||
| 6186 | return items; | ||
| 6187 | }, | ||
| 6188 | |||
| 6189 | /** | ||
| 6190 | * y mode returns the elements that hit-test at the current y coordinate | ||
| 6191 | * @function Chart.Interaction.modes.y | ||
| 6192 | 		 * @param chart {chart} the chart we are returning items from | ||
| 6193 | 		 * @param e {Event} the event we are find things at | ||
| 6194 | 		 * @param options {IInteractionOptions} options to use | ||
| 6195 | 		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned | ||
| 6196 | */ | ||
| 6197 | 		y: function(chart, e, options) { | ||
| 6198 | var position = getRelativePosition(e, chart); | ||
| 6199 | var items = []; | ||
| 6200 | var intersectsItem = false; | ||
| 6201 | |||
| 6202 | 			parseVisibleItems(chart, function(element) { | ||
| 6203 | 				if (element.inYRange(position.y)) { | ||
| 6204 | items.push(element); | ||
| 6205 | } | ||
| 6206 | |||
| 6207 | 				if (element.inRange(position.x, position.y)) { | ||
| 6208 | intersectsItem = true; | ||
| 6209 | } | ||
| 6210 | }); | ||
| 6211 | |||
| 6212 | // If we want to trigger on an intersect and we don't have any items | ||
| 6213 | // that intersect the position, return nothing | ||
| 6214 | 			if (options.intersect && !intersectsItem) { | ||
| 6215 | items = []; | ||
| 6216 | } | ||
| 6217 | return items; | ||
| 6218 | } | ||
| 6219 | } | ||
| 6220 | }; | ||
| 6221 | |||
| 6222 | },{"46":46}],30:[function(require,module,exports){ | ||
| 6223 | 'use strict'; | ||
| 6224 | |||
| 6225 | var defaults = require(26); | ||
| 6226 | |||
| 6227 | defaults._set('global', { | ||
| 6228 | responsive: true, | ||
| 6229 | responsiveAnimationDuration: 0, | ||
| 6230 | maintainAspectRatio: true, | ||
| 6231 | events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], | ||
| 6232 | 	hover: { | ||
| 6233 | onHover: null, | ||
| 6234 | mode: 'nearest', | ||
| 6235 | intersect: true, | ||
| 6236 | animationDuration: 400 | ||
| 6237 | }, | ||
| 6238 | onClick: null, | ||
| 6239 | defaultColor: 'rgba(0,0,0,0.1)', | ||
| 6240 | defaultFontColor: '#666', | ||
| 6241 | defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", | ||
| 6242 | defaultFontSize: 12, | ||
| 6243 | defaultFontStyle: 'normal', | ||
| 6244 | showLines: true, | ||
| 6245 | |||
| 6246 | // Element defaults defined in element extensions | ||
| 6247 | 	elements: {}, | ||
| 6248 | |||
| 6249 | // Layout options such as padding | ||
| 6250 | 	layout: { | ||
| 6251 | 		padding: { | ||
| 6252 | top: 0, | ||
| 6253 | right: 0, | ||
| 6254 | bottom: 0, | ||
| 6255 | left: 0 | ||
| 6256 | } | ||
| 6257 | } | ||
| 6258 | }); | ||
| 6259 | |||
| 6260 | module.exports = function() { | ||
| 6261 | |||
| 6262 | // Occupy the global variable of Chart, and create a simple base class | ||
| 6263 | 	var Chart = function(item, config) { | ||
| 6264 | this.construct(item, config); | ||
| 6265 | return this; | ||
| 6266 | }; | ||
| 6267 | |||
| 6268 | Chart.Chart = Chart; | ||
| 6269 | |||
| 6270 | return Chart; | ||
| 6271 | }; | ||
| 6272 | |||
| 6273 | },{"26":26}],31:[function(require,module,exports){ | ||
| 6274 | 'use strict'; | ||
| 6275 | |||
| 6276 | var helpers = require(46); | ||
| 6277 | |||
| 6278 | function filterByPosition(array, position) { | ||
| 6279 | 	return helpers.where(array, function(v) { | ||
| 6280 | return v.position === position; | ||
| 6281 | }); | ||
| 6282 | } | ||
| 6283 | |||
| 6284 | function sortByWeight(array, reverse) { | ||
| 6285 | 	array.forEach(function(v, i) { | ||
| 6286 | v._tmpIndex_ = i; | ||
| 6287 | return v; | ||
| 6288 | }); | ||
| 6289 | 	array.sort(function(a, b) { | ||
| 6290 | var v0 = reverse ? b : a; | ||
| 6291 | var v1 = reverse ? a : b; | ||
| 6292 | return v0.weight === v1.weight ? | ||
| 6293 | v0._tmpIndex_ - v1._tmpIndex_ : | ||
| 6294 | v0.weight - v1.weight; | ||
| 6295 | }); | ||
| 6296 | 	array.forEach(function(v) { | ||
| 6297 | delete v._tmpIndex_; | ||
| 6298 | }); | ||
| 6299 | } | ||
| 6300 | |||
| 6301 | /** | ||
| 6302 | * @interface ILayoutItem | ||
| 6303 |  * @prop {String} position - The position of the item in the chart layout. Possible values are | ||
| 6304 | * 'left', 'top', 'right', 'bottom', and 'chartArea' | ||
| 6305 |  * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area | ||
| 6306 |  * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down | ||
| 6307 |  * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) | ||
| 6308 |  * @prop {Function} update - Takes two parameters: width and height. Returns size of item | ||
| 6309 |  * @prop {Function} getPadding -  Returns an object with padding on the edges | ||
| 6310 |  * @prop {Number} width - Width of item. Must be valid after update() | ||
| 6311 |  * @prop {Number} height - Height of item. Must be valid after update() | ||
| 6312 |  * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update | ||
| 6313 |  * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update | ||
| 6314 |  * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update | ||
| 6315 |  * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update | ||
| 6316 | */ | ||
| 6317 | |||
| 6318 | // The layout service is very self explanatory. It's responsible for the layout within a chart. | ||
| 6319 | // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need | ||
| 6320 | // It is this service's responsibility of carrying out that layout. | ||
| 6321 | module.exports = { | ||
| 6322 | 	defaults: {}, | ||
| 6323 | |||
| 6324 | /** | ||
| 6325 | * Register a box to a chart. | ||
| 6326 | * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. | ||
| 6327 | 	 * @param {Chart} chart - the chart to use | ||
| 6328 | 	 * @param {ILayoutItem} item - the item to add to be layed out | ||
| 6329 | */ | ||
| 6330 | 	addBox: function(chart, item) { | ||
| 6331 | 		if (!chart.boxes) { | ||
| 6332 | chart.boxes = []; | ||
| 6333 | } | ||
| 6334 | |||
| 6335 | // initialize item with default values | ||
| 6336 | item.fullWidth = item.fullWidth || false; | ||
| 6337 | item.position = item.position || 'top'; | ||
| 6338 | item.weight = item.weight || 0; | ||
| 6339 | |||
| 6340 | chart.boxes.push(item); | ||
| 6341 | }, | ||
| 6342 | |||
| 6343 | /** | ||
| 6344 | * Remove a layoutItem from a chart | ||
| 6345 | 	 * @param {Chart} chart - the chart to remove the box from | ||
| 6346 | 	 * @param {Object} layoutItem - the item to remove from the layout | ||
| 6347 | */ | ||
| 6348 | 	removeBox: function(chart, layoutItem) { | ||
| 6349 | var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; | ||
| 6350 | 		if (index !== -1) { | ||
| 6351 | chart.boxes.splice(index, 1); | ||
| 6352 | } | ||
| 6353 | }, | ||
| 6354 | |||
| 6355 | /** | ||
| 6356 | * Sets (or updates) options on the given `item`. | ||
| 6357 | 	 * @param {Chart} chart - the chart in which the item lives (or will be added to) | ||
| 6358 | 	 * @param {Object} item - the item to configure with the given options | ||
| 6359 | 	 * @param {Object} options - the new item options. | ||
| 6360 | */ | ||
| 6361 | 	configure: function(chart, item, options) { | ||
| 6362 | var props = ['fullWidth', 'position', 'weight']; | ||
| 6363 | var ilen = props.length; | ||
| 6364 | var i = 0; | ||
| 6365 | var prop; | ||
| 6366 | |||
| 6367 | 		for (; i < ilen; ++i) { | ||
| 6368 | prop = props[i]; | ||
| 6369 | 			if (options.hasOwnProperty(prop)) { | ||
| 6370 | item[prop] = options[prop]; | ||
| 6371 | } | ||
| 6372 | } | ||
| 6373 | }, | ||
| 6374 | |||
| 6375 | /** | ||
| 6376 | * Fits boxes of the given chart into the given size by having each box measure itself | ||
| 6377 | * then running a fitting algorithm | ||
| 6378 | 	 * @param {Chart} chart - the chart | ||
| 6379 | 	 * @param {Number} width - the width to fit into | ||
| 6380 | 	 * @param {Number} height - the height to fit into | ||
| 6381 | */ | ||
| 6382 | 	update: function(chart, width, height) { | ||
| 6383 | 		if (!chart) { | ||
| 6384 | return; | ||
| 6385 | } | ||
| 6386 | |||
| 6387 | 		var layoutOptions = chart.options.layout || {}; | ||
| 6388 | var padding = helpers.options.toPadding(layoutOptions.padding); | ||
| 6389 | var leftPadding = padding.left; | ||
| 6390 | var rightPadding = padding.right; | ||
| 6391 | var topPadding = padding.top; | ||
| 6392 | var bottomPadding = padding.bottom; | ||
| 6393 | |||
| 6394 | var leftBoxes = filterByPosition(chart.boxes, 'left'); | ||
| 6395 | var rightBoxes = filterByPosition(chart.boxes, 'right'); | ||
| 6396 | var topBoxes = filterByPosition(chart.boxes, 'top'); | ||
| 6397 | var bottomBoxes = filterByPosition(chart.boxes, 'bottom'); | ||
| 6398 | var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea'); | ||
| 6399 | |||
| 6400 | // Sort boxes by weight. A higher weight is further away from the chart area | ||
| 6401 | sortByWeight(leftBoxes, true); | ||
| 6402 | sortByWeight(rightBoxes, false); | ||
| 6403 | sortByWeight(topBoxes, true); | ||
| 6404 | sortByWeight(bottomBoxes, false); | ||
| 6405 | |||
| 6406 | // Essentially we now have any number of boxes on each of the 4 sides. | ||
| 6407 | // Our canvas looks like the following. | ||
| 6408 | // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and | ||
| 6409 | // B1 is the bottom axis | ||
| 6410 | // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays | ||
| 6411 | // These locations are single-box locations only, when trying to register a chartArea location that is already taken, | ||
| 6412 | // an error will be thrown. | ||
| 6413 | // | ||
| 6414 | // |----------------------------------------------------| | ||
| 6415 | // | T1 (Full Width) | | ||
| 6416 | // |----------------------------------------------------| | ||
| 6417 | // | | | T2 | | | ||
| 6418 | // | |----|-------------------------------------|----| | ||
| 6419 | // | | | C1 | | C2 | | | ||
| 6420 | // | | |----| |----| | | ||
| 6421 | // | | | | | | ||
| 6422 | // | L1 | L2 | ChartArea (C0) | R1 | | ||
| 6423 | // | | | | | | ||
| 6424 | // | | |----| |----| | | ||
| 6425 | // | | | C3 | | C4 | | | ||
| 6426 | // | |----|-------------------------------------|----| | ||
| 6427 | // | | | B1 | | | ||
| 6428 | // |----------------------------------------------------| | ||
| 6429 | // | B2 (Full Width) | | ||
| 6430 | // |----------------------------------------------------| | ||
| 6431 | // | ||
| 6432 | // What we do to find the best sizing, we do the following | ||
| 6433 | // 1. Determine the minimum size of the chart area. | ||
| 6434 | // 2. Split the remaining width equally between each vertical axis | ||
| 6435 | // 3. Split the remaining height equally between each horizontal axis | ||
| 6436 | // 4. Give each layout the maximum size it can be. The layout will return it's minimum size | ||
| 6437 | // 5. Adjust the sizes of each axis based on it's minimum reported size. | ||
| 6438 | // 6. Refit each axis | ||
| 6439 | // 7. Position each axis in the final location | ||
| 6440 | // 8. Tell the chart the final location of the chart area | ||
| 6441 | // 9. Tell any axes that overlay the chart area the positions of the chart area | ||
| 6442 | |||
| 6443 | // Step 1 | ||
| 6444 | var chartWidth = width - leftPadding - rightPadding; | ||
| 6445 | var chartHeight = height - topPadding - bottomPadding; | ||
| 6446 | var chartAreaWidth = chartWidth / 2; // min 50% | ||
| 6447 | var chartAreaHeight = chartHeight / 2; // min 50% | ||
| 6448 | |||
| 6449 | // Step 2 | ||
| 6450 | var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); | ||
| 6451 | |||
| 6452 | // Step 3 | ||
| 6453 | var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); | ||
| 6454 | |||
| 6455 | // Step 4 | ||
| 6456 | var maxChartAreaWidth = chartWidth; | ||
| 6457 | var maxChartAreaHeight = chartHeight; | ||
| 6458 | var minBoxSizes = []; | ||
| 6459 | |||
| 6460 | 		function getMinimumBoxSize(box) { | ||
| 6461 | var minSize; | ||
| 6462 | var isHorizontal = box.isHorizontal(); | ||
| 6463 | |||
| 6464 | 			if (isHorizontal) { | ||
| 6465 | minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); | ||
| 6466 | maxChartAreaHeight -= minSize.height; | ||
| 6467 | 			} else { | ||
| 6468 | minSize = box.update(verticalBoxWidth, maxChartAreaHeight); | ||
| 6469 | maxChartAreaWidth -= minSize.width; | ||
| 6470 | } | ||
| 6471 | |||
| 6472 | 			minBoxSizes.push({ | ||
| 6473 | horizontal: isHorizontal, | ||
| 6474 | minSize: minSize, | ||
| 6475 | box: box, | ||
| 6476 | }); | ||
| 6477 | } | ||
| 6478 | |||
| 6479 | helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); | ||
| 6480 | |||
| 6481 | // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478) | ||
| 6482 | var maxHorizontalLeftPadding = 0; | ||
| 6483 | var maxHorizontalRightPadding = 0; | ||
| 6484 | var maxVerticalTopPadding = 0; | ||
| 6485 | var maxVerticalBottomPadding = 0; | ||
| 6486 | |||
| 6487 | 		helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) { | ||
| 6488 | 			if (horizontalBox.getPadding) { | ||
| 6489 | var boxPadding = horizontalBox.getPadding(); | ||
| 6490 | maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left); | ||
| 6491 | maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right); | ||
| 6492 | } | ||
| 6493 | }); | ||
| 6494 | |||
| 6495 | 		helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) { | ||
| 6496 | 			if (verticalBox.getPadding) { | ||
| 6497 | var boxPadding = verticalBox.getPadding(); | ||
| 6498 | maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top); | ||
| 6499 | maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom); | ||
| 6500 | } | ||
| 6501 | }); | ||
| 6502 | |||
| 6503 | // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could | ||
| 6504 | // be if the axes are drawn at their minimum sizes. | ||
| 6505 | // Steps 5 & 6 | ||
| 6506 | var totalLeftBoxesWidth = leftPadding; | ||
| 6507 | var totalRightBoxesWidth = rightPadding; | ||
| 6508 | var totalTopBoxesHeight = topPadding; | ||
| 6509 | var totalBottomBoxesHeight = bottomPadding; | ||
| 6510 | |||
| 6511 | // Function to fit a box | ||
| 6512 | 		function fitBox(box) { | ||
| 6513 | 			var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) { | ||
| 6514 | return minBox.box === box; | ||
| 6515 | }); | ||
| 6516 | |||
| 6517 | 			if (minBoxSize) { | ||
| 6518 | 				if (box.isHorizontal()) { | ||
| 6519 | 					var scaleMargin = { | ||
| 6520 | left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding), | ||
| 6521 | right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding), | ||
| 6522 | top: 0, | ||
| 6523 | bottom: 0 | ||
| 6524 | }; | ||
| 6525 | |||
| 6526 | // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends | ||
| 6527 | // on the margin. Sometimes they need to increase in size slightly | ||
| 6528 | box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); | ||
| 6529 | 				} else { | ||
| 6530 | box.update(minBoxSize.minSize.width, maxChartAreaHeight); | ||
| 6531 | } | ||
| 6532 | } | ||
| 6533 | } | ||
| 6534 | |||
| 6535 | // Update, and calculate the left and right margins for the horizontal boxes | ||
| 6536 | helpers.each(leftBoxes.concat(rightBoxes), fitBox); | ||
| 6537 | |||
| 6538 | 		helpers.each(leftBoxes, function(box) { | ||
| 6539 | totalLeftBoxesWidth += box.width; | ||
| 6540 | }); | ||
| 6541 | |||
| 6542 | 		helpers.each(rightBoxes, function(box) { | ||
| 6543 | totalRightBoxesWidth += box.width; | ||
| 6544 | }); | ||
| 6545 | |||
| 6546 | // Set the Left and Right margins for the horizontal boxes | ||
| 6547 | helpers.each(topBoxes.concat(bottomBoxes), fitBox); | ||
| 6548 | |||
| 6549 | // Figure out how much margin is on the top and bottom of the vertical boxes | ||
| 6550 | 		helpers.each(topBoxes, function(box) { | ||
| 6551 | totalTopBoxesHeight += box.height; | ||
| 6552 | }); | ||
| 6553 | |||
| 6554 | 		helpers.each(bottomBoxes, function(box) { | ||
| 6555 | totalBottomBoxesHeight += box.height; | ||
| 6556 | }); | ||
| 6557 | |||
| 6558 | 		function finalFitVerticalBox(box) { | ||
| 6559 | 			var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) { | ||
| 6560 | return minSize.box === box; | ||
| 6561 | }); | ||
| 6562 | |||
| 6563 | 			var scaleMargin = { | ||
| 6564 | left: 0, | ||
| 6565 | right: 0, | ||
| 6566 | top: totalTopBoxesHeight, | ||
| 6567 | bottom: totalBottomBoxesHeight | ||
| 6568 | }; | ||
| 6569 | |||
| 6570 | 			if (minBoxSize) { | ||
| 6571 | box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); | ||
| 6572 | } | ||
| 6573 | } | ||
| 6574 | |||
| 6575 | // Let the left layout know the final margin | ||
| 6576 | helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); | ||
| 6577 | |||
| 6578 | // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) | ||
| 6579 | totalLeftBoxesWidth = leftPadding; | ||
| 6580 | totalRightBoxesWidth = rightPadding; | ||
| 6581 | totalTopBoxesHeight = topPadding; | ||
| 6582 | totalBottomBoxesHeight = bottomPadding; | ||
| 6583 | |||
| 6584 | 		helpers.each(leftBoxes, function(box) { | ||
| 6585 | totalLeftBoxesWidth += box.width; | ||
| 6586 | }); | ||
| 6587 | |||
| 6588 | 		helpers.each(rightBoxes, function(box) { | ||
| 6589 | totalRightBoxesWidth += box.width; | ||
| 6590 | }); | ||
| 6591 | |||
| 6592 | 		helpers.each(topBoxes, function(box) { | ||
| 6593 | totalTopBoxesHeight += box.height; | ||
| 6594 | }); | ||
| 6595 | 		helpers.each(bottomBoxes, function(box) { | ||
| 6596 | totalBottomBoxesHeight += box.height; | ||
| 6597 | }); | ||
| 6598 | |||
| 6599 | // We may be adding some padding to account for rotated x axis labels | ||
| 6600 | var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0); | ||
| 6601 | totalLeftBoxesWidth += leftPaddingAddition; | ||
| 6602 | totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0); | ||
| 6603 | |||
| 6604 | var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0); | ||
| 6605 | totalTopBoxesHeight += topPaddingAddition; | ||
| 6606 | totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0); | ||
| 6607 | |||
| 6608 | // Figure out if our chart area changed. This would occur if the dataset layout label rotation | ||
| 6609 | // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do | ||
| 6610 | // without calling `fit` again | ||
| 6611 | var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; | ||
| 6612 | var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; | ||
| 6613 | |||
| 6614 | 		if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { | ||
| 6615 | 			helpers.each(leftBoxes, function(box) { | ||
| 6616 | box.height = newMaxChartAreaHeight; | ||
| 6617 | }); | ||
| 6618 | |||
| 6619 | 			helpers.each(rightBoxes, function(box) { | ||
| 6620 | box.height = newMaxChartAreaHeight; | ||
| 6621 | }); | ||
| 6622 | |||
| 6623 | 			helpers.each(topBoxes, function(box) { | ||
| 6624 | 				if (!box.fullWidth) { | ||
| 6625 | box.width = newMaxChartAreaWidth; | ||
| 6626 | } | ||
| 6627 | }); | ||
| 6628 | |||
| 6629 | 			helpers.each(bottomBoxes, function(box) { | ||
| 6630 | 				if (!box.fullWidth) { | ||
| 6631 | box.width = newMaxChartAreaWidth; | ||
| 6632 | } | ||
| 6633 | }); | ||
| 6634 | |||
| 6635 | maxChartAreaHeight = newMaxChartAreaHeight; | ||
| 6636 | maxChartAreaWidth = newMaxChartAreaWidth; | ||
| 6637 | } | ||
| 6638 | |||
| 6639 | // Step 7 - Position the boxes | ||
| 6640 | var left = leftPadding + leftPaddingAddition; | ||
| 6641 | var top = topPadding + topPaddingAddition; | ||
| 6642 | |||
| 6643 | 		function placeBox(box) { | ||
| 6644 | 			if (box.isHorizontal()) { | ||
| 6645 | box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth; | ||
| 6646 | box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth; | ||
| 6647 | box.top = top; | ||
| 6648 | box.bottom = top + box.height; | ||
| 6649 | |||
| 6650 | // Move to next point | ||
| 6651 | top = box.bottom; | ||
| 6652 | |||
| 6653 | 			} else { | ||
| 6654 | |||
| 6655 | box.left = left; | ||
| 6656 | box.right = left + box.width; | ||
| 6657 | box.top = totalTopBoxesHeight; | ||
| 6658 | box.bottom = totalTopBoxesHeight + maxChartAreaHeight; | ||
| 6659 | |||
| 6660 | // Move to next point | ||
| 6661 | left = box.right; | ||
| 6662 | } | ||
| 6663 | } | ||
| 6664 | |||
| 6665 | helpers.each(leftBoxes.concat(topBoxes), placeBox); | ||
| 6666 | |||
| 6667 | // Account for chart width and height | ||
| 6668 | left += maxChartAreaWidth; | ||
| 6669 | top += maxChartAreaHeight; | ||
| 6670 | |||
| 6671 | helpers.each(rightBoxes, placeBox); | ||
| 6672 | helpers.each(bottomBoxes, placeBox); | ||
| 6673 | |||
| 6674 | // Step 8 | ||
| 6675 | 		chart.chartArea = { | ||
| 6676 | left: totalLeftBoxesWidth, | ||
| 6677 | top: totalTopBoxesHeight, | ||
| 6678 | right: totalLeftBoxesWidth + maxChartAreaWidth, | ||
| 6679 | bottom: totalTopBoxesHeight + maxChartAreaHeight | ||
| 6680 | }; | ||
| 6681 | |||
| 6682 | // Step 9 | ||
| 6683 | 		helpers.each(chartAreaBoxes, function(box) { | ||
| 6684 | box.left = chart.chartArea.left; | ||
| 6685 | box.top = chart.chartArea.top; | ||
| 6686 | box.right = chart.chartArea.right; | ||
| 6687 | box.bottom = chart.chartArea.bottom; | ||
| 6688 | |||
| 6689 | box.update(maxChartAreaWidth, maxChartAreaHeight); | ||
| 6690 | }); | ||
| 6691 | } | ||
| 6692 | }; | ||
| 6693 | |||
| 6694 | },{"46":46}],32:[function(require,module,exports){ | ||
| 6695 | 'use strict'; | ||
| 6696 | |||
| 6697 | var defaults = require(26); | ||
| 6698 | var helpers = require(46); | ||
| 6699 | |||
| 6700 | defaults._set('global', { | ||
| 6701 | 	plugins: {} | ||
| 6702 | }); | ||
| 6703 | |||
| 6704 | /** | ||
| 6705 | * The plugin service singleton | ||
| 6706 | * @namespace Chart.plugins | ||
| 6707 | * @since 2.1.0 | ||
| 6708 | */ | ||
| 6709 | module.exports = { | ||
| 6710 | /** | ||
| 6711 | * Globally registered plugins. | ||
| 6712 | * @private | ||
| 6713 | */ | ||
| 6714 | _plugins: [], | ||
| 6715 | |||
| 6716 | /** | ||
| 6717 | * This identifier is used to invalidate the descriptors cache attached to each chart | ||
| 6718 | * when a global plugin is registered or unregistered. In this case, the cache ID is | ||
| 6719 | * incremented and descriptors are regenerated during following API calls. | ||
| 6720 | * @private | ||
| 6721 | */ | ||
| 6722 | _cacheId: 0, | ||
| 6723 | |||
| 6724 | /** | ||
| 6725 | * Registers the given plugin(s) if not already registered. | ||
| 6726 | 	 * @param {Array|Object} plugins plugin instance(s). | ||
| 6727 | */ | ||
| 6728 | 	register: function(plugins) { | ||
| 6729 | var p = this._plugins; | ||
| 6730 | 		([]).concat(plugins).forEach(function(plugin) { | ||
| 6731 | 			if (p.indexOf(plugin) === -1) { | ||
| 6732 | p.push(plugin); | ||
| 6733 | } | ||
| 6734 | }); | ||
| 6735 | |||
| 6736 | this._cacheId++; | ||
| 6737 | }, | ||
| 6738 | |||
| 6739 | /** | ||
| 6740 | * Unregisters the given plugin(s) only if registered. | ||
| 6741 | 	 * @param {Array|Object} plugins plugin instance(s). | ||
| 6742 | */ | ||
| 6743 | 	unregister: function(plugins) { | ||
| 6744 | var p = this._plugins; | ||
| 6745 | 		([]).concat(plugins).forEach(function(plugin) { | ||
| 6746 | var idx = p.indexOf(plugin); | ||
| 6747 | 			if (idx !== -1) { | ||
| 6748 | p.splice(idx, 1); | ||
| 6749 | } | ||
| 6750 | }); | ||
| 6751 | |||
| 6752 | this._cacheId++; | ||
| 6753 | }, | ||
| 6754 | |||
| 6755 | /** | ||
| 6756 | * Remove all registered plugins. | ||
| 6757 | * @since 2.1.5 | ||
| 6758 | */ | ||
| 6759 | 	clear: function() { | ||
| 6760 | this._plugins = []; | ||
| 6761 | this._cacheId++; | ||
| 6762 | }, | ||
| 6763 | |||
| 6764 | /** | ||
| 6765 | * Returns the number of registered plugins? | ||
| 6766 | 	 * @returns {Number} | ||
| 6767 | * @since 2.1.5 | ||
| 6768 | */ | ||
| 6769 | 	count: function() { | ||
| 6770 | return this._plugins.length; | ||
| 6771 | }, | ||
| 6772 | |||
| 6773 | /** | ||
| 6774 | * Returns all registered plugin instances. | ||
| 6775 | 	 * @returns {Array} array of plugin objects. | ||
| 6776 | * @since 2.1.5 | ||
| 6777 | */ | ||
| 6778 | 	getAll: function() { | ||
| 6779 | return this._plugins; | ||
| 6780 | }, | ||
| 6781 | |||
| 6782 | /** | ||
| 6783 | * Calls enabled plugins for `chart` on the specified hook and with the given args. | ||
| 6784 | * This method immediately returns as soon as a plugin explicitly returns false. The | ||
| 6785 | * returned value can be used, for instance, to interrupt the current action. | ||
| 6786 | 	 * @param {Object} chart - The chart instance for which plugins should be called. | ||
| 6787 | 	 * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). | ||
| 6788 | 	 * @param {Array} [args] - Extra arguments to apply to the hook call. | ||
| 6789 | 	 * @returns {Boolean} false if any of the plugins return false, else returns true. | ||
| 6790 | */ | ||
| 6791 | 	notify: function(chart, hook, args) { | ||
| 6792 | var descriptors = this.descriptors(chart); | ||
| 6793 | var ilen = descriptors.length; | ||
| 6794 | var i, descriptor, plugin, params, method; | ||
| 6795 | |||
| 6796 | 		for (i = 0; i < ilen; ++i) { | ||
| 6797 | descriptor = descriptors[i]; | ||
| 6798 | plugin = descriptor.plugin; | ||
| 6799 | method = plugin[hook]; | ||
| 6800 | 			if (typeof method === 'function') { | ||
| 6801 | params = [chart].concat(args || []); | ||
| 6802 | params.push(descriptor.options); | ||
| 6803 | 				if (method.apply(plugin, params) === false) { | ||
| 6804 | return false; | ||
| 6805 | } | ||
| 6806 | } | ||
| 6807 | } | ||
| 6808 | |||
| 6809 | return true; | ||
| 6810 | }, | ||
| 6811 | |||
| 6812 | /** | ||
| 6813 | * Returns descriptors of enabled plugins for the given chart. | ||
| 6814 | 	 * @returns {Array} [{ plugin, options }] | ||
| 6815 | * @private | ||
| 6816 | */ | ||
| 6817 | 	descriptors: function(chart) { | ||
| 6818 | 		var cache = chart.$plugins || (chart.$plugins = {}); | ||
| 6819 | 		if (cache.id === this._cacheId) { | ||
| 6820 | return cache.descriptors; | ||
| 6821 | } | ||
| 6822 | |||
| 6823 | var plugins = []; | ||
| 6824 | var descriptors = []; | ||
| 6825 | 		var config = (chart && chart.config) || {}; | ||
| 6826 | 		var options = (config.options && config.options.plugins) || {}; | ||
| 6827 | |||
| 6828 | 		this._plugins.concat(config.plugins || []).forEach(function(plugin) { | ||
| 6829 | var idx = plugins.indexOf(plugin); | ||
| 6830 | 			if (idx !== -1) { | ||
| 6831 | return; | ||
| 6832 | } | ||
| 6833 | |||
| 6834 | var id = plugin.id; | ||
| 6835 | var opts = options[id]; | ||
| 6836 | 			if (opts === false) { | ||
| 6837 | return; | ||
| 6838 | } | ||
| 6839 | |||
| 6840 | 			if (opts === true) { | ||
| 6841 | opts = helpers.clone(defaults.global.plugins[id]); | ||
| 6842 | } | ||
| 6843 | |||
| 6844 | plugins.push(plugin); | ||
| 6845 | 			descriptors.push({ | ||
| 6846 | plugin: plugin, | ||
| 6847 | 				options: opts || {} | ||
| 6848 | }); | ||
| 6849 | }); | ||
| 6850 | |||
| 6851 | cache.descriptors = descriptors; | ||
| 6852 | cache.id = this._cacheId; | ||
| 6853 | return descriptors; | ||
| 6854 | }, | ||
| 6855 | |||
| 6856 | /** | ||
| 6857 | * Invalidates cache for the given chart: descriptors hold a reference on plugin option, | ||
| 6858 | * but in some cases, this reference can be changed by the user when updating options. | ||
| 6859 | * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 | ||
| 6860 | * @private | ||
| 6861 | */ | ||
| 6862 | 	_invalidate: function(chart) { | ||
| 6863 | delete chart.$plugins; | ||
| 6864 | } | ||
| 6865 | }; | ||
| 6866 | |||
| 6867 | /** | ||
| 6868 | * Plugin extension hooks. | ||
| 6869 | * @interface IPlugin | ||
| 6870 | * @since 2.1.0 | ||
| 6871 | */ | ||
| 6872 | /** | ||
| 6873 | * @method IPlugin#beforeInit | ||
| 6874 | * @desc Called before initializing `chart`. | ||
| 6875 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6876 |  * @param {Object} options - The plugin options. | ||
| 6877 | */ | ||
| 6878 | /** | ||
| 6879 | * @method IPlugin#afterInit | ||
| 6880 | * @desc Called after `chart` has been initialized and before the first update. | ||
| 6881 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6882 |  * @param {Object} options - The plugin options. | ||
| 6883 | */ | ||
| 6884 | /** | ||
| 6885 | * @method IPlugin#beforeUpdate | ||
| 6886 | * @desc Called before updating `chart`. If any plugin returns `false`, the update | ||
| 6887 | * is cancelled (and thus subsequent render(s)) until another `update` is triggered. | ||
| 6888 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6889 |  * @param {Object} options - The plugin options. | ||
| 6890 |  * @returns {Boolean} `false` to cancel the chart update. | ||
| 6891 | */ | ||
| 6892 | /** | ||
| 6893 | * @method IPlugin#afterUpdate | ||
| 6894 | * @desc Called after `chart` has been updated and before rendering. Note that this | ||
| 6895 | * hook will not be called if the chart update has been previously cancelled. | ||
| 6896 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6897 |  * @param {Object} options - The plugin options. | ||
| 6898 | */ | ||
| 6899 | /** | ||
| 6900 | * @method IPlugin#beforeDatasetsUpdate | ||
| 6901 | * @desc Called before updating the `chart` datasets. If any plugin returns `false`, | ||
| 6902 | * the datasets update is cancelled until another `update` is triggered. | ||
| 6903 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6904 |  * @param {Object} options - The plugin options. | ||
| 6905 |  * @returns {Boolean} false to cancel the datasets update. | ||
| 6906 | * @since version 2.1.5 | ||
| 6907 | */ | ||
| 6908 | /** | ||
| 6909 | * @method IPlugin#afterDatasetsUpdate | ||
| 6910 | * @desc Called after the `chart` datasets have been updated. Note that this hook | ||
| 6911 | * will not be called if the datasets update has been previously cancelled. | ||
| 6912 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6913 |  * @param {Object} options - The plugin options. | ||
| 6914 | * @since version 2.1.5 | ||
| 6915 | */ | ||
| 6916 | /** | ||
| 6917 | * @method IPlugin#beforeDatasetUpdate | ||
| 6918 | * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin | ||
| 6919 | * returns `false`, the datasets update is cancelled until another `update` is triggered. | ||
| 6920 |  * @param {Chart} chart - The chart instance. | ||
| 6921 |  * @param {Object} args - The call arguments. | ||
| 6922 |  * @param {Number} args.index - The dataset index. | ||
| 6923 |  * @param {Object} args.meta - The dataset metadata. | ||
| 6924 |  * @param {Object} options - The plugin options. | ||
| 6925 |  * @returns {Boolean} `false` to cancel the chart datasets drawing. | ||
| 6926 | */ | ||
| 6927 | /** | ||
| 6928 | * @method IPlugin#afterDatasetUpdate | ||
| 6929 | * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note | ||
| 6930 | * that this hook will not be called if the datasets update has been previously cancelled. | ||
| 6931 |  * @param {Chart} chart - The chart instance. | ||
| 6932 |  * @param {Object} args - The call arguments. | ||
| 6933 |  * @param {Number} args.index - The dataset index. | ||
| 6934 |  * @param {Object} args.meta - The dataset metadata. | ||
| 6935 |  * @param {Object} options - The plugin options. | ||
| 6936 | */ | ||
| 6937 | /** | ||
| 6938 | * @method IPlugin#beforeLayout | ||
| 6939 | * @desc Called before laying out `chart`. If any plugin returns `false`, | ||
| 6940 | * the layout update is cancelled until another `update` is triggered. | ||
| 6941 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6942 |  * @param {Object} options - The plugin options. | ||
| 6943 |  * @returns {Boolean} `false` to cancel the chart layout. | ||
| 6944 | */ | ||
| 6945 | /** | ||
| 6946 | * @method IPlugin#afterLayout | ||
| 6947 | * @desc Called after the `chart` has been layed out. Note that this hook will not | ||
| 6948 | * be called if the layout update has been previously cancelled. | ||
| 6949 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6950 |  * @param {Object} options - The plugin options. | ||
| 6951 | */ | ||
| 6952 | /** | ||
| 6953 | * @method IPlugin#beforeRender | ||
| 6954 | * @desc Called before rendering `chart`. If any plugin returns `false`, | ||
| 6955 | * the rendering is cancelled until another `render` is triggered. | ||
| 6956 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6957 |  * @param {Object} options - The plugin options. | ||
| 6958 |  * @returns {Boolean} `false` to cancel the chart rendering. | ||
| 6959 | */ | ||
| 6960 | /** | ||
| 6961 | * @method IPlugin#afterRender | ||
| 6962 | * @desc Called after the `chart` has been fully rendered (and animation completed). Note | ||
| 6963 | * that this hook will not be called if the rendering has been previously cancelled. | ||
| 6964 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6965 |  * @param {Object} options - The plugin options. | ||
| 6966 | */ | ||
| 6967 | /** | ||
| 6968 | * @method IPlugin#beforeDraw | ||
| 6969 | * @desc Called before drawing `chart` at every animation frame specified by the given | ||
| 6970 | * easing value. If any plugin returns `false`, the frame drawing is cancelled until | ||
| 6971 | * another `render` is triggered. | ||
| 6972 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6973 |  * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. | ||
| 6974 |  * @param {Object} options - The plugin options. | ||
| 6975 |  * @returns {Boolean} `false` to cancel the chart drawing. | ||
| 6976 | */ | ||
| 6977 | /** | ||
| 6978 | * @method IPlugin#afterDraw | ||
| 6979 | * @desc Called after the `chart` has been drawn for the specific easing value. Note | ||
| 6980 | * that this hook will not be called if the drawing has been previously cancelled. | ||
| 6981 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6982 |  * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. | ||
| 6983 |  * @param {Object} options - The plugin options. | ||
| 6984 | */ | ||
| 6985 | /** | ||
| 6986 | * @method IPlugin#beforeDatasetsDraw | ||
| 6987 | * @desc Called before drawing the `chart` datasets. If any plugin returns `false`, | ||
| 6988 | * the datasets drawing is cancelled until another `render` is triggered. | ||
| 6989 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6990 |  * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. | ||
| 6991 |  * @param {Object} options - The plugin options. | ||
| 6992 |  * @returns {Boolean} `false` to cancel the chart datasets drawing. | ||
| 6993 | */ | ||
| 6994 | /** | ||
| 6995 | * @method IPlugin#afterDatasetsDraw | ||
| 6996 | * @desc Called after the `chart` datasets have been drawn. Note that this hook | ||
| 6997 | * will not be called if the datasets drawing has been previously cancelled. | ||
| 6998 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 6999 |  * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. | ||
| 7000 |  * @param {Object} options - The plugin options. | ||
| 7001 | */ | ||
| 7002 | /** | ||
| 7003 | * @method IPlugin#beforeDatasetDraw | ||
| 7004 | * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets | ||
| 7005 | * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing | ||
| 7006 | * is cancelled until another `render` is triggered. | ||
| 7007 |  * @param {Chart} chart - The chart instance. | ||
| 7008 |  * @param {Object} args - The call arguments. | ||
| 7009 |  * @param {Number} args.index - The dataset index. | ||
| 7010 |  * @param {Object} args.meta - The dataset metadata. | ||
| 7011 |  * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. | ||
| 7012 |  * @param {Object} options - The plugin options. | ||
| 7013 |  * @returns {Boolean} `false` to cancel the chart datasets drawing. | ||
| 7014 | */ | ||
| 7015 | /** | ||
| 7016 | * @method IPlugin#afterDatasetDraw | ||
| 7017 | * @desc Called after the `chart` datasets at the given `args.index` have been drawn | ||
| 7018 | * (datasets are drawn in the reverse order). Note that this hook will not be called | ||
| 7019 | * if the datasets drawing has been previously cancelled. | ||
| 7020 |  * @param {Chart} chart - The chart instance. | ||
| 7021 |  * @param {Object} args - The call arguments. | ||
| 7022 |  * @param {Number} args.index - The dataset index. | ||
| 7023 |  * @param {Object} args.meta - The dataset metadata. | ||
| 7024 |  * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. | ||
| 7025 |  * @param {Object} options - The plugin options. | ||
| 7026 | */ | ||
| 7027 | /** | ||
| 7028 | * @method IPlugin#beforeTooltipDraw | ||
| 7029 | * @desc Called before drawing the `tooltip`. If any plugin returns `false`, | ||
| 7030 | * the tooltip drawing is cancelled until another `render` is triggered. | ||
| 7031 |  * @param {Chart} chart - The chart instance. | ||
| 7032 |  * @param {Object} args - The call arguments. | ||
| 7033 |  * @param {Object} args.tooltip - The tooltip. | ||
| 7034 |  * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. | ||
| 7035 |  * @param {Object} options - The plugin options. | ||
| 7036 |  * @returns {Boolean} `false` to cancel the chart tooltip drawing. | ||
| 7037 | */ | ||
| 7038 | /** | ||
| 7039 | * @method IPlugin#afterTooltipDraw | ||
| 7040 | * @desc Called after drawing the `tooltip`. Note that this hook will not | ||
| 7041 | * be called if the tooltip drawing has been previously cancelled. | ||
| 7042 |  * @param {Chart} chart - The chart instance. | ||
| 7043 |  * @param {Object} args - The call arguments. | ||
| 7044 |  * @param {Object} args.tooltip - The tooltip. | ||
| 7045 |  * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. | ||
| 7046 |  * @param {Object} options - The plugin options. | ||
| 7047 | */ | ||
| 7048 | /** | ||
| 7049 | * @method IPlugin#beforeEvent | ||
| 7050 | * @desc Called before processing the specified `event`. If any plugin returns `false`, | ||
| 7051 | * the event will be discarded. | ||
| 7052 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 7053 |  * @param {IEvent} event - The event object. | ||
| 7054 |  * @param {Object} options - The plugin options. | ||
| 7055 | */ | ||
| 7056 | /** | ||
| 7057 | * @method IPlugin#afterEvent | ||
| 7058 | * @desc Called after the `event` has been consumed. Note that this hook | ||
| 7059 | * will not be called if the `event` has been previously discarded. | ||
| 7060 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 7061 |  * @param {IEvent} event - The event object. | ||
| 7062 |  * @param {Object} options - The plugin options. | ||
| 7063 | */ | ||
| 7064 | /** | ||
| 7065 | * @method IPlugin#resize | ||
| 7066 | * @desc Called after the chart as been resized. | ||
| 7067 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 7068 |  * @param {Number} size - The new canvas display size (eq. canvas.style width & height). | ||
| 7069 |  * @param {Object} options - The plugin options. | ||
| 7070 | */ | ||
| 7071 | /** | ||
| 7072 | * @method IPlugin#destroy | ||
| 7073 | * @desc Called after the chart as been destroyed. | ||
| 7074 |  * @param {Chart.Controller} chart - The chart instance. | ||
| 7075 |  * @param {Object} options - The plugin options. | ||
| 7076 | */ | ||
| 7077 | |||
| 7078 | },{"26":26,"46":46}],33:[function(require,module,exports){ | ||
| 7079 | 'use strict'; | ||
| 7080 | |||
| 7081 | var defaults = require(26); | ||
| 7082 | var Element = require(27); | ||
| 7083 | var helpers = require(46); | ||
| 7084 | var Ticks = require(35); | ||
| 7085 | |||
| 7086 | defaults._set('scale', { | ||
| 7087 | display: true, | ||
| 7088 | position: 'left', | ||
| 7089 | offset: false, | ||
| 7090 | |||
| 7091 | // grid line settings | ||
| 7092 | 	gridLines: { | ||
| 7093 | display: true, | ||
| 7094 | color: 'rgba(0, 0, 0, 0.1)', | ||
| 7095 | lineWidth: 1, | ||
| 7096 | drawBorder: true, | ||
| 7097 | drawOnChartArea: true, | ||
| 7098 | drawTicks: true, | ||
| 7099 | tickMarkLength: 10, | ||
| 7100 | zeroLineWidth: 1, | ||
| 7101 | zeroLineColor: 'rgba(0,0,0,0.25)', | ||
| 7102 | zeroLineBorderDash: [], | ||
| 7103 | zeroLineBorderDashOffset: 0.0, | ||
| 7104 | offsetGridLines: false, | ||
| 7105 | borderDash: [], | ||
| 7106 | borderDashOffset: 0.0 | ||
| 7107 | }, | ||
| 7108 | |||
| 7109 | // scale label | ||
| 7110 | 	scaleLabel: { | ||
| 7111 | // display property | ||
| 7112 | display: false, | ||
| 7113 | |||
| 7114 | // actual label | ||
| 7115 | labelString: '', | ||
| 7116 | |||
| 7117 | // line height | ||
| 7118 | lineHeight: 1.2, | ||
| 7119 | |||
| 7120 | // top/bottom padding | ||
| 7121 | 		padding: { | ||
| 7122 | top: 4, | ||
| 7123 | bottom: 4 | ||
| 7124 | } | ||
| 7125 | }, | ||
| 7126 | |||
| 7127 | // label settings | ||
| 7128 | 	ticks: { | ||
| 7129 | beginAtZero: false, | ||
| 7130 | minRotation: 0, | ||
| 7131 | maxRotation: 50, | ||
| 7132 | mirror: false, | ||
| 7133 | padding: 0, | ||
| 7134 | reverse: false, | ||
| 7135 | display: true, | ||
| 7136 | autoSkip: true, | ||
| 7137 | autoSkipPadding: 0, | ||
| 7138 | labelOffset: 0, | ||
| 7139 | // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. | ||
| 7140 | callback: Ticks.formatters.values, | ||
| 7141 | 		minor: {}, | ||
| 7142 | 		major: {} | ||
| 7143 | } | ||
| 7144 | }); | ||
| 7145 | |||
| 7146 | function labelsFromTicks(ticks) { | ||
| 7147 | var labels = []; | ||
| 7148 | var i, ilen; | ||
| 7149 | |||
| 7150 | 	for (i = 0, ilen = ticks.length; i < ilen; ++i) { | ||
| 7151 | labels.push(ticks[i].label); | ||
| 7152 | } | ||
| 7153 | |||
| 7154 | return labels; | ||
| 7155 | } | ||
| 7156 | |||
| 7157 | function getLineValue(scale, index, offsetGridLines) { | ||
| 7158 | var lineValue = scale.getPixelForTick(index); | ||
| 7159 | |||
| 7160 | 	if (offsetGridLines) { | ||
| 7161 | 		if (index === 0) { | ||
| 7162 | lineValue -= (scale.getPixelForTick(1) - lineValue) / 2; | ||
| 7163 | 		} else { | ||
| 7164 | lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2; | ||
| 7165 | } | ||
| 7166 | } | ||
| 7167 | return lineValue; | ||
| 7168 | } | ||
| 7169 | |||
| 7170 | function computeTextSize(context, tick, font) { | ||
| 7171 | return helpers.isArray(tick) ? | ||
| 7172 | helpers.longestText(context, font, tick) : | ||
| 7173 | context.measureText(tick).width; | ||
| 7174 | } | ||
| 7175 | |||
| 7176 | function parseFontOptions(options) { | ||
| 7177 | var valueOrDefault = helpers.valueOrDefault; | ||
| 7178 | var globalDefaults = defaults.global; | ||
| 7179 | var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); | ||
| 7180 | var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); | ||
| 7181 | var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); | ||
| 7182 | |||
| 7183 | 	return { | ||
| 7184 | size: size, | ||
| 7185 | style: style, | ||
| 7186 | family: family, | ||
| 7187 | font: helpers.fontString(size, style, family) | ||
| 7188 | }; | ||
| 7189 | } | ||
| 7190 | |||
| 7191 | function parseLineHeight(options) { | ||
| 7192 | return helpers.options.toLineHeight( | ||
| 7193 | helpers.valueOrDefault(options.lineHeight, 1.2), | ||
| 7194 | helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); | ||
| 7195 | } | ||
| 7196 | |||
| 7197 | module.exports = Element.extend({ | ||
| 7198 | /** | ||
| 7199 | * Get the padding needed for the scale | ||
| 7200 | * @method getPadding | ||
| 7201 | * @private | ||
| 7202 | 	 * @returns {Padding} the necessary padding | ||
| 7203 | */ | ||
| 7204 | 	getPadding: function() { | ||
| 7205 | var me = this; | ||
| 7206 | 		return { | ||
| 7207 | left: me.paddingLeft || 0, | ||
| 7208 | top: me.paddingTop || 0, | ||
| 7209 | right: me.paddingRight || 0, | ||
| 7210 | bottom: me.paddingBottom || 0 | ||
| 7211 | }; | ||
| 7212 | }, | ||
| 7213 | |||
| 7214 | /** | ||
| 7215 | 	 * Returns the scale tick objects ({label, major}) | ||
| 7216 | * @since 2.7 | ||
| 7217 | */ | ||
| 7218 | 	getTicks: function() { | ||
| 7219 | return this._ticks; | ||
| 7220 | }, | ||
| 7221 | |||
| 7222 | // These methods are ordered by lifecyle. Utilities then follow. | ||
| 7223 | // Any function defined here is inherited by all scale types. | ||
| 7224 | // Any function can be extended by the scale type | ||
| 7225 | |||
| 7226 | 	mergeTicksOptions: function() { | ||
| 7227 | var ticks = this.options.ticks; | ||
| 7228 | 		if (ticks.minor === false) { | ||
| 7229 | 			ticks.minor = { | ||
| 7230 | display: false | ||
| 7231 | }; | ||
| 7232 | } | ||
| 7233 | 		if (ticks.major === false) { | ||
| 7234 | 			ticks.major = { | ||
| 7235 | display: false | ||
| 7236 | }; | ||
| 7237 | } | ||
| 7238 | 		for (var key in ticks) { | ||
| 7239 | 			if (key !== 'major' && key !== 'minor') { | ||
| 7240 | 				if (typeof ticks.minor[key] === 'undefined') { | ||
| 7241 | ticks.minor[key] = ticks[key]; | ||
| 7242 | } | ||
| 7243 | 				if (typeof ticks.major[key] === 'undefined') { | ||
| 7244 | ticks.major[key] = ticks[key]; | ||
| 7245 | } | ||
| 7246 | } | ||
| 7247 | } | ||
| 7248 | }, | ||
| 7249 | 	beforeUpdate: function() { | ||
| 7250 | helpers.callback(this.options.beforeUpdate, [this]); | ||
| 7251 | }, | ||
| 7252 | |||
| 7253 | 	update: function(maxWidth, maxHeight, margins) { | ||
| 7254 | var me = this; | ||
| 7255 | var i, ilen, labels, label, ticks, tick; | ||
| 7256 | |||
| 7257 | // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) | ||
| 7258 | me.beforeUpdate(); | ||
| 7259 | |||
| 7260 | // Absorb the master measurements | ||
| 7261 | me.maxWidth = maxWidth; | ||
| 7262 | me.maxHeight = maxHeight; | ||
| 7263 | 		me.margins = helpers.extend({ | ||
| 7264 | left: 0, | ||
| 7265 | right: 0, | ||
| 7266 | top: 0, | ||
| 7267 | bottom: 0 | ||
| 7268 | }, margins); | ||
| 7269 | 		me.longestTextCache = me.longestTextCache || {}; | ||
| 7270 | |||
| 7271 | // Dimensions | ||
| 7272 | me.beforeSetDimensions(); | ||
| 7273 | me.setDimensions(); | ||
| 7274 | me.afterSetDimensions(); | ||
| 7275 | |||
| 7276 | // Data min/max | ||
| 7277 | me.beforeDataLimits(); | ||
| 7278 | me.determineDataLimits(); | ||
| 7279 | me.afterDataLimits(); | ||
| 7280 | |||
| 7281 | // Ticks - `this.ticks` is now DEPRECATED! | ||
| 7282 | // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member | ||
| 7283 | // and must not be accessed directly from outside this class. `this.ticks` being | ||
| 7284 | // around for long time and not marked as private, we can't change its structure | ||
| 7285 | // without unexpected breaking changes. If you need to access the scale ticks, | ||
| 7286 | // use scale.getTicks() instead. | ||
| 7287 | |||
| 7288 | me.beforeBuildTicks(); | ||
| 7289 | |||
| 7290 | // New implementations should return an array of objects but for BACKWARD COMPAT, | ||
| 7291 | // we still support no return (`this.ticks` internally set by calling this method). | ||
| 7292 | ticks = me.buildTicks() || []; | ||
| 7293 | |||
| 7294 | me.afterBuildTicks(); | ||
| 7295 | |||
| 7296 | me.beforeTickToLabelConversion(); | ||
| 7297 | |||
| 7298 | // New implementations should return the formatted tick labels but for BACKWARD | ||
| 7299 | // COMPAT, we still support no return (`this.ticks` internally changed by calling | ||
| 7300 | // this method and supposed to contain only string values). | ||
| 7301 | labels = me.convertTicksToLabels(ticks) || me.ticks; | ||
| 7302 | |||
| 7303 | me.afterTickToLabelConversion(); | ||
| 7304 | |||
| 7305 | me.ticks = labels; // BACKWARD COMPATIBILITY | ||
| 7306 | |||
| 7307 | // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change! | ||
| 7308 | |||
| 7309 | // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) | ||
| 7310 | 		for (i = 0, ilen = labels.length; i < ilen; ++i) { | ||
| 7311 | label = labels[i]; | ||
| 7312 | tick = ticks[i]; | ||
| 7313 | 			if (!tick) { | ||
| 7314 | 				ticks.push(tick = { | ||
| 7315 | label: label, | ||
| 7316 | major: false | ||
| 7317 | }); | ||
| 7318 | 			} else { | ||
| 7319 | tick.label = label; | ||
| 7320 | } | ||
| 7321 | } | ||
| 7322 | |||
| 7323 | me._ticks = ticks; | ||
| 7324 | |||
| 7325 | // Tick Rotation | ||
| 7326 | me.beforeCalculateTickRotation(); | ||
| 7327 | me.calculateTickRotation(); | ||
| 7328 | me.afterCalculateTickRotation(); | ||
| 7329 | // Fit | ||
| 7330 | me.beforeFit(); | ||
| 7331 | me.fit(); | ||
| 7332 | me.afterFit(); | ||
| 7333 | // | ||
| 7334 | me.afterUpdate(); | ||
| 7335 | |||
| 7336 | return me.minSize; | ||
| 7337 | |||
| 7338 | }, | ||
| 7339 | 	afterUpdate: function() { | ||
| 7340 | helpers.callback(this.options.afterUpdate, [this]); | ||
| 7341 | }, | ||
| 7342 | |||
| 7343 | // | ||
| 7344 | |||
| 7345 | 	beforeSetDimensions: function() { | ||
| 7346 | helpers.callback(this.options.beforeSetDimensions, [this]); | ||
| 7347 | }, | ||
| 7348 | 	setDimensions: function() { | ||
| 7349 | var me = this; | ||
| 7350 | // Set the unconstrained dimension before label rotation | ||
| 7351 | 		if (me.isHorizontal()) { | ||
| 7352 | // Reset position before calculating rotation | ||
| 7353 | me.width = me.maxWidth; | ||
| 7354 | me.left = 0; | ||
| 7355 | me.right = me.width; | ||
| 7356 | 		} else { | ||
| 7357 | me.height = me.maxHeight; | ||
| 7358 | |||
| 7359 | // Reset position before calculating rotation | ||
| 7360 | me.top = 0; | ||
| 7361 | me.bottom = me.height; | ||
| 7362 | } | ||
| 7363 | |||
| 7364 | // Reset padding | ||
| 7365 | me.paddingLeft = 0; | ||
| 7366 | me.paddingTop = 0; | ||
| 7367 | me.paddingRight = 0; | ||
| 7368 | me.paddingBottom = 0; | ||
| 7369 | }, | ||
| 7370 | 	afterSetDimensions: function() { | ||
| 7371 | helpers.callback(this.options.afterSetDimensions, [this]); | ||
| 7372 | }, | ||
| 7373 | |||
| 7374 | // Data limits | ||
| 7375 | 	beforeDataLimits: function() { | ||
| 7376 | helpers.callback(this.options.beforeDataLimits, [this]); | ||
| 7377 | }, | ||
| 7378 | determineDataLimits: helpers.noop, | ||
| 7379 | 	afterDataLimits: function() { | ||
| 7380 | helpers.callback(this.options.afterDataLimits, [this]); | ||
| 7381 | }, | ||
| 7382 | |||
| 7383 | // | ||
| 7384 | 	beforeBuildTicks: function() { | ||
| 7385 | helpers.callback(this.options.beforeBuildTicks, [this]); | ||
| 7386 | }, | ||
| 7387 | buildTicks: helpers.noop, | ||
| 7388 | 	afterBuildTicks: function() { | ||
| 7389 | helpers.callback(this.options.afterBuildTicks, [this]); | ||
| 7390 | }, | ||
| 7391 | |||
| 7392 | 	beforeTickToLabelConversion: function() { | ||
| 7393 | helpers.callback(this.options.beforeTickToLabelConversion, [this]); | ||
| 7394 | }, | ||
| 7395 | 	convertTicksToLabels: function() { | ||
| 7396 | var me = this; | ||
| 7397 | // Convert ticks to strings | ||
| 7398 | var tickOpts = me.options.ticks; | ||
| 7399 | me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); | ||
| 7400 | }, | ||
| 7401 | 	afterTickToLabelConversion: function() { | ||
| 7402 | helpers.callback(this.options.afterTickToLabelConversion, [this]); | ||
| 7403 | }, | ||
| 7404 | |||
| 7405 | // | ||
| 7406 | |||
| 7407 | 	beforeCalculateTickRotation: function() { | ||
| 7408 | helpers.callback(this.options.beforeCalculateTickRotation, [this]); | ||
| 7409 | }, | ||
| 7410 | 	calculateTickRotation: function() { | ||
| 7411 | var me = this; | ||
| 7412 | var context = me.ctx; | ||
| 7413 | var tickOpts = me.options.ticks; | ||
| 7414 | var labels = labelsFromTicks(me._ticks); | ||
| 7415 | |||
| 7416 | // Get the width of each grid by calculating the difference | ||
| 7417 | // between x offsets between 0 and 1. | ||
| 7418 | var tickFont = parseFontOptions(tickOpts); | ||
| 7419 | context.font = tickFont.font; | ||
| 7420 | |||
| 7421 | var labelRotation = tickOpts.minRotation || 0; | ||
| 7422 | |||
| 7423 | 		if (labels.length && me.options.display && me.isHorizontal()) { | ||
| 7424 | var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache); | ||
| 7425 | var labelWidth = originalLabelWidth; | ||
| 7426 | var cosRotation, sinRotation; | ||
| 7427 | |||
| 7428 | // Allow 3 pixels x2 padding either side for label readability | ||
| 7429 | var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; | ||
| 7430 | |||
| 7431 | // Max label rotation can be set or default to 90 - also act as a loop counter | ||
| 7432 | 			while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { | ||
| 7433 | var angleRadians = helpers.toRadians(labelRotation); | ||
| 7434 | cosRotation = Math.cos(angleRadians); | ||
| 7435 | sinRotation = Math.sin(angleRadians); | ||
| 7436 | |||
| 7437 | 				if (sinRotation * originalLabelWidth > me.maxHeight) { | ||
| 7438 | // go back one step | ||
| 7439 | labelRotation--; | ||
| 7440 | break; | ||
| 7441 | } | ||
| 7442 | |||
| 7443 | labelRotation++; | ||
| 7444 | labelWidth = cosRotation * originalLabelWidth; | ||
| 7445 | } | ||
| 7446 | } | ||
| 7447 | |||
| 7448 | me.labelRotation = labelRotation; | ||
| 7449 | }, | ||
| 7450 | 	afterCalculateTickRotation: function() { | ||
| 7451 | helpers.callback(this.options.afterCalculateTickRotation, [this]); | ||
| 7452 | }, | ||
| 7453 | |||
| 7454 | // | ||
| 7455 | |||
| 7456 | 	beforeFit: function() { | ||
| 7457 | helpers.callback(this.options.beforeFit, [this]); | ||
| 7458 | }, | ||
| 7459 | 	fit: function() { | ||
| 7460 | var me = this; | ||
| 7461 | // Reset | ||
| 7462 | 		var minSize = me.minSize = { | ||
| 7463 | width: 0, | ||
| 7464 | height: 0 | ||
| 7465 | }; | ||
| 7466 | |||
| 7467 | var labels = labelsFromTicks(me._ticks); | ||
| 7468 | |||
| 7469 | var opts = me.options; | ||
| 7470 | var tickOpts = opts.ticks; | ||
| 7471 | var scaleLabelOpts = opts.scaleLabel; | ||
| 7472 | var gridLineOpts = opts.gridLines; | ||
| 7473 | var display = opts.display; | ||
| 7474 | var isHorizontal = me.isHorizontal(); | ||
| 7475 | |||
| 7476 | var tickFont = parseFontOptions(tickOpts); | ||
| 7477 | var tickMarkLength = opts.gridLines.tickMarkLength; | ||
| 7478 | |||
| 7479 | // Width | ||
| 7480 | 		if (isHorizontal) { | ||
| 7481 | // subtract the margins to line up with the chartArea if we are a full width scale | ||
| 7482 | minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; | ||
| 7483 | 		} else { | ||
| 7484 | minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0; | ||
| 7485 | } | ||
| 7486 | |||
| 7487 | // height | ||
| 7488 | 		if (isHorizontal) { | ||
| 7489 | minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0; | ||
| 7490 | 		} else { | ||
| 7491 | minSize.height = me.maxHeight; // fill all the height | ||
| 7492 | } | ||
| 7493 | |||
| 7494 | // Are we showing a title for the scale? | ||
| 7495 | 		if (scaleLabelOpts.display && display) { | ||
| 7496 | var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); | ||
| 7497 | var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); | ||
| 7498 | var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height; | ||
| 7499 | |||
| 7500 | 			if (isHorizontal) { | ||
| 7501 | minSize.height += deltaHeight; | ||
| 7502 | 			} else { | ||
| 7503 | minSize.width += deltaHeight; | ||
| 7504 | } | ||
| 7505 | } | ||
| 7506 | |||
| 7507 | // Don't bother fitting the ticks if we are not showing them | ||
| 7508 | 		if (tickOpts.display && display) { | ||
| 7509 | var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache); | ||
| 7510 | var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels); | ||
| 7511 | var lineSpace = tickFont.size * 0.5; | ||
| 7512 | var tickPadding = me.options.ticks.padding; | ||
| 7513 | |||
| 7514 | 			if (isHorizontal) { | ||
| 7515 | // A horizontal axis is more constrained by the height. | ||
| 7516 | me.longestLabelWidth = largestTextWidth; | ||
| 7517 | |||
| 7518 | var angleRadians = helpers.toRadians(me.labelRotation); | ||
| 7519 | var cosRotation = Math.cos(angleRadians); | ||
| 7520 | var sinRotation = Math.sin(angleRadians); | ||
| 7521 | |||
| 7522 | // TODO - improve this calculation | ||
| 7523 | var labelHeight = (sinRotation * largestTextWidth) | ||
| 7524 | + (tickFont.size * tallestLabelHeightInLines) | ||
| 7525 | + (lineSpace * (tallestLabelHeightInLines - 1)) | ||
| 7526 | + lineSpace; // padding | ||
| 7527 | |||
| 7528 | minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); | ||
| 7529 | |||
| 7530 | me.ctx.font = tickFont.font; | ||
| 7531 | var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font); | ||
| 7532 | var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font); | ||
| 7533 | |||
| 7534 | // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned | ||
| 7535 | // which means that the right padding is dominated by the font height | ||
| 7536 | 				if (me.labelRotation !== 0) { | ||
| 7537 | me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges | ||
| 7538 | me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3; | ||
| 7539 | 				} else { | ||
| 7540 | me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges | ||
| 7541 | me.paddingRight = lastLabelWidth / 2 + 3; | ||
| 7542 | } | ||
| 7543 | 			} else { | ||
| 7544 | // A vertical axis is more constrained by the width. Labels are the | ||
| 7545 | // dominant factor here, so get that length first and account for padding | ||
| 7546 | 				if (tickOpts.mirror) { | ||
| 7547 | largestTextWidth = 0; | ||
| 7548 | 				} else { | ||
| 7549 | // use lineSpace for consistency with horizontal axis | ||
| 7550 | // tickPadding is not implemented for horizontal | ||
| 7551 | largestTextWidth += tickPadding + lineSpace; | ||
| 7552 | } | ||
| 7553 | |||
| 7554 | minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth); | ||
| 7555 | |||
| 7556 | me.paddingTop = tickFont.size / 2; | ||
| 7557 | me.paddingBottom = tickFont.size / 2; | ||
| 7558 | } | ||
| 7559 | } | ||
| 7560 | |||
| 7561 | me.handleMargins(); | ||
| 7562 | |||
| 7563 | me.width = minSize.width; | ||
| 7564 | me.height = minSize.height; | ||
| 7565 | }, | ||
| 7566 | |||
| 7567 | /** | ||
| 7568 | * Handle margins and padding interactions | ||
| 7569 | * @private | ||
| 7570 | */ | ||
| 7571 | 	handleMargins: function() { | ||
| 7572 | var me = this; | ||
| 7573 | 		if (me.margins) { | ||
| 7574 | me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); | ||
| 7575 | me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); | ||
| 7576 | me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); | ||
| 7577 | me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); | ||
| 7578 | } | ||
| 7579 | }, | ||
| 7580 | |||
| 7581 | 	afterFit: function() { | ||
| 7582 | helpers.callback(this.options.afterFit, [this]); | ||
| 7583 | }, | ||
| 7584 | |||
| 7585 | // Shared Methods | ||
| 7586 | 	isHorizontal: function() { | ||
| 7587 | return this.options.position === 'top' || this.options.position === 'bottom'; | ||
| 7588 | }, | ||
| 7589 | 	isFullWidth: function() { | ||
| 7590 | return (this.options.fullWidth); | ||
| 7591 | }, | ||
| 7592 | |||
| 7593 | // 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 | ||
| 7594 | 	getRightValue: function(rawValue) { | ||
| 7595 | // Null and undefined values first | ||
| 7596 | 		if (helpers.isNullOrUndef(rawValue)) { | ||
| 7597 | return NaN; | ||
| 7598 | } | ||
| 7599 | // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values | ||
| 7600 | 		if (typeof rawValue === 'number' && !isFinite(rawValue)) { | ||
| 7601 | return NaN; | ||
| 7602 | } | ||
| 7603 | // If it is in fact an object, dive in one more level | ||
| 7604 | 		if (rawValue) { | ||
| 7605 | 			if (this.isHorizontal()) { | ||
| 7606 | 				if (rawValue.x !== undefined) { | ||
| 7607 | return this.getRightValue(rawValue.x); | ||
| 7608 | } | ||
| 7609 | 			} else if (rawValue.y !== undefined) { | ||
| 7610 | return this.getRightValue(rawValue.y); | ||
| 7611 | } | ||
| 7612 | } | ||
| 7613 | |||
| 7614 | // Value is good, return it | ||
| 7615 | return rawValue; | ||
| 7616 | }, | ||
| 7617 | |||
| 7618 | /** | ||
| 7619 | * Used to get the value to display in the tooltip for the data at the given index | ||
| 7620 | * @param index | ||
| 7621 | * @param datasetIndex | ||
| 7622 | */ | ||
| 7623 | getLabelForIndex: helpers.noop, | ||
| 7624 | |||
| 7625 | /** | ||
| 7626 | * Returns the location of the given data point. Value can either be an index or a numerical value | ||
| 7627 | * The coordinate (0, 0) is at the upper-left corner of the canvas | ||
| 7628 | * @param value | ||
| 7629 | * @param index | ||
| 7630 | * @param datasetIndex | ||
| 7631 | */ | ||
| 7632 | getPixelForValue: helpers.noop, | ||
| 7633 | |||
| 7634 | /** | ||
| 7635 | * Used to get the data value from a given pixel. This is the inverse of getPixelForValue | ||
| 7636 | * The coordinate (0, 0) is at the upper-left corner of the canvas | ||
| 7637 | * @param pixel | ||
| 7638 | */ | ||
| 7639 | getValueForPixel: helpers.noop, | ||
| 7640 | |||
| 7641 | /** | ||
| 7642 | * Returns the location of the tick at the given index | ||
| 7643 | * The coordinate (0, 0) is at the upper-left corner of the canvas | ||
| 7644 | */ | ||
| 7645 | 	getPixelForTick: function(index) { | ||
| 7646 | var me = this; | ||
| 7647 | var offset = me.options.offset; | ||
| 7648 | 		if (me.isHorizontal()) { | ||
| 7649 | var innerWidth = me.width - (me.paddingLeft + me.paddingRight); | ||
| 7650 | var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1); | ||
| 7651 | var pixel = (tickWidth * index) + me.paddingLeft; | ||
| 7652 | |||
| 7653 | 			if (offset) { | ||
| 7654 | pixel += tickWidth / 2; | ||
| 7655 | } | ||
| 7656 | |||
| 7657 | var finalVal = me.left + Math.round(pixel); | ||
| 7658 | finalVal += me.isFullWidth() ? me.margins.left : 0; | ||
| 7659 | return finalVal; | ||
| 7660 | } | ||
| 7661 | var innerHeight = me.height - (me.paddingTop + me.paddingBottom); | ||
| 7662 | return me.top + (index * (innerHeight / (me._ticks.length - 1))); | ||
| 7663 | }, | ||
| 7664 | |||
| 7665 | /** | ||
| 7666 | * Utility for getting the pixel location of a percentage of scale | ||
| 7667 | * The coordinate (0, 0) is at the upper-left corner of the canvas | ||
| 7668 | */ | ||
| 7669 | 	getPixelForDecimal: function(decimal) { | ||
| 7670 | var me = this; | ||
| 7671 | 		if (me.isHorizontal()) { | ||
| 7672 | var innerWidth = me.width - (me.paddingLeft + me.paddingRight); | ||
| 7673 | var valueOffset = (innerWidth * decimal) + me.paddingLeft; | ||
| 7674 | |||
| 7675 | var finalVal = me.left + Math.round(valueOffset); | ||
| 7676 | finalVal += me.isFullWidth() ? me.margins.left : 0; | ||
| 7677 | return finalVal; | ||
| 7678 | } | ||
| 7679 | return me.top + (decimal * me.height); | ||
| 7680 | }, | ||
| 7681 | |||
| 7682 | /** | ||
| 7683 | * Returns the pixel for the minimum chart value | ||
| 7684 | * The coordinate (0, 0) is at the upper-left corner of the canvas | ||
| 7685 | */ | ||
| 7686 | 	getBasePixel: function() { | ||
| 7687 | return this.getPixelForValue(this.getBaseValue()); | ||
| 7688 | }, | ||
| 7689 | |||
| 7690 | 	getBaseValue: function() { | ||
| 7691 | var me = this; | ||
| 7692 | var min = me.min; | ||
| 7693 | var max = me.max; | ||
| 7694 | |||
| 7695 | return me.beginAtZero ? 0 : | ||
| 7696 | min < 0 && max < 0 ? max : | ||
| 7697 | min > 0 && max > 0 ? min : | ||
| 7698 | 0; | ||
| 7699 | }, | ||
| 7700 | |||
| 7701 | /** | ||
| 7702 | * Returns a subset of ticks to be plotted to avoid overlapping labels. | ||
| 7703 | * @private | ||
| 7704 | */ | ||
| 7705 | 	_autoSkip: function(ticks) { | ||
| 7706 | var skipRatio; | ||
| 7707 | var me = this; | ||
| 7708 | var isHorizontal = me.isHorizontal(); | ||
| 7709 | var optionTicks = me.options.ticks.minor; | ||
| 7710 | var tickCount = ticks.length; | ||
| 7711 | var labelRotationRadians = helpers.toRadians(me.labelRotation); | ||
| 7712 | var cosRotation = Math.cos(labelRotationRadians); | ||
| 7713 | var longestRotatedLabel = me.longestLabelWidth * cosRotation; | ||
| 7714 | var result = []; | ||
| 7715 | var i, tick, shouldSkip; | ||
| 7716 | |||
| 7717 | // figure out the maximum number of gridlines to show | ||
| 7718 | var maxTicks; | ||
| 7719 | 		if (optionTicks.maxTicksLimit) { | ||
| 7720 | maxTicks = optionTicks.maxTicksLimit; | ||
| 7721 | } | ||
| 7722 | |||
| 7723 | 		if (isHorizontal) { | ||
| 7724 | skipRatio = false; | ||
| 7725 | |||
| 7726 | 			if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) { | ||
| 7727 | skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight))); | ||
| 7728 | } | ||
| 7729 | |||
| 7730 | // if they defined a max number of optionTicks, | ||
| 7731 | // increase skipRatio until that number is met | ||
| 7732 | 			if (maxTicks && tickCount > maxTicks) { | ||
| 7733 | skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks)); | ||
| 7734 | } | ||
| 7735 | } | ||
| 7736 | |||
| 7737 | 		for (i = 0; i < tickCount; i++) { | ||
| 7738 | tick = ticks[i]; | ||
| 7739 | |||
| 7740 | // Since we always show the last tick,we need may need to hide the last shown one before | ||
| 7741 | shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount); | ||
| 7742 | 			if (shouldSkip && i !== tickCount - 1) { | ||
| 7743 | // leave tick in place but make sure it's not displayed (#4635) | ||
| 7744 | delete tick.label; | ||
| 7745 | } | ||
| 7746 | result.push(tick); | ||
| 7747 | } | ||
| 7748 | return result; | ||
| 7749 | }, | ||
| 7750 | |||
| 7751 | // Actually draw the scale on the canvas | ||
| 7752 | 	// @param {rectangle} chartArea : the area of the chart to draw full grid lines on | ||
| 7753 | 	draw: function(chartArea) { | ||
| 7754 | var me = this; | ||
| 7755 | var options = me.options; | ||
| 7756 | 		if (!options.display) { | ||
| 7757 | return; | ||
| 7758 | } | ||
| 7759 | |||
| 7760 | var context = me.ctx; | ||
| 7761 | var globalDefaults = defaults.global; | ||
| 7762 | var optionTicks = options.ticks.minor; | ||
| 7763 | var optionMajorTicks = options.ticks.major || optionTicks; | ||
| 7764 | var gridLines = options.gridLines; | ||
| 7765 | var scaleLabel = options.scaleLabel; | ||
| 7766 | |||
| 7767 | var isRotated = me.labelRotation !== 0; | ||
| 7768 | var isHorizontal = me.isHorizontal(); | ||
| 7769 | |||
| 7770 | var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); | ||
| 7771 | var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); | ||
| 7772 | var tickFont = parseFontOptions(optionTicks); | ||
| 7773 | var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); | ||
| 7774 | var majorTickFont = parseFontOptions(optionMajorTicks); | ||
| 7775 | |||
| 7776 | var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; | ||
| 7777 | |||
| 7778 | var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); | ||
| 7779 | var scaleLabelFont = parseFontOptions(scaleLabel); | ||
| 7780 | var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); | ||
| 7781 | var labelRotationRadians = helpers.toRadians(me.labelRotation); | ||
| 7782 | |||
| 7783 | var itemsToDraw = []; | ||
| 7784 | |||
| 7785 | var axisWidth = me.options.gridLines.lineWidth; | ||
| 7786 | var xTickStart = options.position === 'right' ? me.left : me.right - axisWidth - tl; | ||
| 7787 | var xTickEnd = options.position === 'right' ? me.left + tl : me.right; | ||
| 7788 | var yTickStart = options.position === 'bottom' ? me.top + axisWidth : me.bottom - tl - axisWidth; | ||
| 7789 | var yTickEnd = options.position === 'bottom' ? me.top + axisWidth + tl : me.bottom + axisWidth; | ||
| 7790 | |||
| 7791 | 		helpers.each(ticks, function(tick, index) { | ||
| 7792 | // autoskipper skipped this tick (#4635) | ||
| 7793 | 			if (helpers.isNullOrUndef(tick.label)) { | ||
| 7794 | return; | ||
| 7795 | } | ||
| 7796 | |||
| 7797 | var label = tick.label; | ||
| 7798 | var lineWidth, lineColor, borderDash, borderDashOffset; | ||
| 7799 | 			if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) { | ||
| 7800 | // Draw the first index specially | ||
| 7801 | lineWidth = gridLines.zeroLineWidth; | ||
| 7802 | lineColor = gridLines.zeroLineColor; | ||
| 7803 | borderDash = gridLines.zeroLineBorderDash; | ||
| 7804 | borderDashOffset = gridLines.zeroLineBorderDashOffset; | ||
| 7805 | 			} else { | ||
| 7806 | lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index); | ||
| 7807 | lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index); | ||
| 7808 | borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash); | ||
| 7809 | borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset); | ||
| 7810 | } | ||
| 7811 | |||
| 7812 | // Common properties | ||
| 7813 | var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; | ||
| 7814 | var textAlign = 'middle'; | ||
| 7815 | var textBaseline = 'middle'; | ||
| 7816 | var tickPadding = optionTicks.padding; | ||
| 7817 | |||
| 7818 | 			if (isHorizontal) { | ||
| 7819 | var labelYOffset = tl + tickPadding; | ||
| 7820 | |||
| 7821 | 				if (options.position === 'bottom') { | ||
| 7822 | // bottom | ||
| 7823 | textBaseline = !isRotated ? 'top' : 'middle'; | ||
| 7824 | textAlign = !isRotated ? 'center' : 'right'; | ||
| 7825 | labelY = me.top + labelYOffset; | ||
| 7826 | 				} else { | ||
| 7827 | // top | ||
| 7828 | textBaseline = !isRotated ? 'bottom' : 'middle'; | ||
| 7829 | textAlign = !isRotated ? 'center' : 'left'; | ||
| 7830 | labelY = me.bottom - labelYOffset; | ||
| 7831 | } | ||
| 7832 | |||
| 7833 | var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); | ||
| 7834 | 				if (xLineValue < me.left) { | ||
| 7835 | lineColor = 'rgba(0,0,0,0)'; | ||
| 7836 | } | ||
| 7837 | xLineValue += helpers.aliasPixel(lineWidth); | ||
| 7838 | |||
| 7839 | labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) | ||
| 7840 | |||
| 7841 | tx1 = tx2 = x1 = x2 = xLineValue; | ||
| 7842 | ty1 = yTickStart; | ||
| 7843 | ty2 = yTickEnd; | ||
| 7844 | y1 = chartArea.top; | ||
| 7845 | y2 = chartArea.bottom + axisWidth; | ||
| 7846 | 			} else { | ||
| 7847 | var isLeft = options.position === 'left'; | ||
| 7848 | var labelXOffset; | ||
| 7849 | |||
| 7850 | 				if (optionTicks.mirror) { | ||
| 7851 | textAlign = isLeft ? 'left' : 'right'; | ||
| 7852 | labelXOffset = tickPadding; | ||
| 7853 | 				} else { | ||
| 7854 | textAlign = isLeft ? 'right' : 'left'; | ||
| 7855 | labelXOffset = tl + tickPadding; | ||
| 7856 | } | ||
| 7857 | |||
| 7858 | labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset; | ||
| 7859 | |||
| 7860 | var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); | ||
| 7861 | 				if (yLineValue < me.top) { | ||
| 7862 | lineColor = 'rgba(0,0,0,0)'; | ||
| 7863 | } | ||
| 7864 | yLineValue += helpers.aliasPixel(lineWidth); | ||
| 7865 | |||
| 7866 | labelY = me.getPixelForTick(index) + optionTicks.labelOffset; | ||
| 7867 | |||
| 7868 | tx1 = xTickStart; | ||
| 7869 | tx2 = xTickEnd; | ||
| 7870 | x1 = chartArea.left; | ||
| 7871 | x2 = chartArea.right + axisWidth; | ||
| 7872 | ty1 = ty2 = y1 = y2 = yLineValue; | ||
| 7873 | } | ||
| 7874 | |||
| 7875 | 			itemsToDraw.push({ | ||
| 7876 | tx1: tx1, | ||
| 7877 | ty1: ty1, | ||
| 7878 | tx2: tx2, | ||
| 7879 | ty2: ty2, | ||
| 7880 | x1: x1, | ||
| 7881 | y1: y1, | ||
| 7882 | x2: x2, | ||
| 7883 | y2: y2, | ||
| 7884 | labelX: labelX, | ||
| 7885 | labelY: labelY, | ||
| 7886 | glWidth: lineWidth, | ||
| 7887 | glColor: lineColor, | ||
| 7888 | glBorderDash: borderDash, | ||
| 7889 | glBorderDashOffset: borderDashOffset, | ||
| 7890 | rotation: -1 * labelRotationRadians, | ||
| 7891 | label: label, | ||
| 7892 | major: tick.major, | ||
| 7893 | textBaseline: textBaseline, | ||
| 7894 | textAlign: textAlign | ||
| 7895 | }); | ||
| 7896 | }); | ||
| 7897 | |||
| 7898 | // Draw all of the tick labels, tick marks, and grid lines at the correct places | ||
| 7899 | 		helpers.each(itemsToDraw, function(itemToDraw) { | ||
| 7900 | 			if (gridLines.display) { | ||
| 7901 | context.save(); | ||
| 7902 | context.lineWidth = itemToDraw.glWidth; | ||
| 7903 | context.strokeStyle = itemToDraw.glColor; | ||
| 7904 | 				if (context.setLineDash) { | ||
| 7905 | context.setLineDash(itemToDraw.glBorderDash); | ||
| 7906 | context.lineDashOffset = itemToDraw.glBorderDashOffset; | ||
| 7907 | } | ||
| 7908 | |||
| 7909 | context.beginPath(); | ||
| 7910 | |||
| 7911 | 				if (gridLines.drawTicks) { | ||
| 7912 | context.moveTo(itemToDraw.tx1, itemToDraw.ty1); | ||
| 7913 | context.lineTo(itemToDraw.tx2, itemToDraw.ty2); | ||
| 7914 | } | ||
| 7915 | |||
| 7916 | 				if (gridLines.drawOnChartArea) { | ||
| 7917 | context.moveTo(itemToDraw.x1, itemToDraw.y1); | ||
| 7918 | context.lineTo(itemToDraw.x2, itemToDraw.y2); | ||
| 7919 | } | ||
| 7920 | |||
| 7921 | context.stroke(); | ||
| 7922 | context.restore(); | ||
| 7923 | } | ||
| 7924 | |||
| 7925 | 			if (optionTicks.display) { | ||
| 7926 | // Make sure we draw text in the correct color and font | ||
| 7927 | context.save(); | ||
| 7928 | context.translate(itemToDraw.labelX, itemToDraw.labelY); | ||
| 7929 | context.rotate(itemToDraw.rotation); | ||
| 7930 | context.font = itemToDraw.major ? majorTickFont.font : tickFont.font; | ||
| 7931 | context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; | ||
| 7932 | context.textBaseline = itemToDraw.textBaseline; | ||
| 7933 | context.textAlign = itemToDraw.textAlign; | ||
| 7934 | |||
| 7935 | var label = itemToDraw.label; | ||
| 7936 | 				if (helpers.isArray(label)) { | ||
| 7937 | var lineCount = label.length; | ||
| 7938 | var lineHeight = tickFont.size * 1.5; | ||
| 7939 | var y = me.isHorizontal() ? 0 : -lineHeight * (lineCount - 1) / 2; | ||
| 7940 | |||
| 7941 | 					for (var i = 0; i < lineCount; ++i) { | ||
| 7942 | // We just make sure the multiline element is a string here.. | ||
| 7943 | 						context.fillText('' + label[i], 0, y); | ||
| 7944 | // apply same lineSpacing as calculated @ L#320 | ||
| 7945 | y += lineHeight; | ||
| 7946 | } | ||
| 7947 | 				} else { | ||
| 7948 | context.fillText(label, 0, 0); | ||
| 7949 | } | ||
| 7950 | context.restore(); | ||
| 7951 | } | ||
| 7952 | }); | ||
| 7953 | |||
| 7954 | 		if (scaleLabel.display) { | ||
| 7955 | // Draw the scale label | ||
| 7956 | var scaleLabelX; | ||
| 7957 | var scaleLabelY; | ||
| 7958 | var rotation = 0; | ||
| 7959 | var halfLineHeight = parseLineHeight(scaleLabel) / 2; | ||
| 7960 | |||
| 7961 | 			if (isHorizontal) { | ||
| 7962 | scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width | ||
| 7963 | scaleLabelY = options.position === 'bottom' | ||
| 7964 | ? me.bottom - halfLineHeight - scaleLabelPadding.bottom | ||
| 7965 | : me.top + halfLineHeight + scaleLabelPadding.top; | ||
| 7966 | 			} else { | ||
| 7967 | var isLeft = options.position === 'left'; | ||
| 7968 | scaleLabelX = isLeft | ||
| 7969 | ? me.left + halfLineHeight + scaleLabelPadding.top | ||
| 7970 | : me.right - halfLineHeight - scaleLabelPadding.top; | ||
| 7971 | scaleLabelY = me.top + ((me.bottom - me.top) / 2); | ||
| 7972 | rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; | ||
| 7973 | } | ||
| 7974 | |||
| 7975 | context.save(); | ||
| 7976 | context.translate(scaleLabelX, scaleLabelY); | ||
| 7977 | context.rotate(rotation); | ||
| 7978 | context.textAlign = 'center'; | ||
| 7979 | context.textBaseline = 'middle'; | ||
| 7980 | context.fillStyle = scaleLabelFontColor; // render in correct colour | ||
| 7981 | context.font = scaleLabelFont.font; | ||
| 7982 | context.fillText(scaleLabel.labelString, 0, 0); | ||
| 7983 | context.restore(); | ||
| 7984 | } | ||
| 7985 | |||
| 7986 | 		if (gridLines.drawBorder) { | ||
| 7987 | // Draw the line at the edge of the axis | ||
| 7988 | context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0); | ||
| 7989 | context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0); | ||
| 7990 | var x1 = me.left; | ||
| 7991 | var x2 = me.right + axisWidth; | ||
| 7992 | var y1 = me.top; | ||
| 7993 | var y2 = me.bottom + axisWidth; | ||
| 7994 | |||
| 7995 | var aliasPixel = helpers.aliasPixel(context.lineWidth); | ||
| 7996 | 			if (isHorizontal) { | ||
| 7997 | y1 = y2 = options.position === 'top' ? me.bottom : me.top; | ||
| 7998 | y1 += aliasPixel; | ||
| 7999 | y2 += aliasPixel; | ||
| 8000 | 			} else { | ||
| 8001 | x1 = x2 = options.position === 'left' ? me.right : me.left; | ||
| 8002 | x1 += aliasPixel; | ||
| 8003 | x2 += aliasPixel; | ||
| 8004 | } | ||
| 8005 | |||
| 8006 | context.beginPath(); | ||
| 8007 | context.moveTo(x1, y1); | ||
| 8008 | context.lineTo(x2, y2); | ||
| 8009 | context.stroke(); | ||
| 8010 | } | ||
| 8011 | } | ||
| 8012 | }); | ||
| 8013 | |||
| 8014 | },{"26":26,"27":27,"35":35,"46":46}],34:[function(require,module,exports){ | ||
| 8015 | 'use strict'; | ||
| 8016 | |||
| 8017 | var defaults = require(26); | ||
| 8018 | var helpers = require(46); | ||
| 8019 | var layouts = require(31); | ||
| 8020 | |||
| 8021 | module.exports = { | ||
| 8022 | // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then | ||
| 8023 | // use the new chart options to grab the correct scale | ||
| 8024 | 	constructors: {}, | ||
| 8025 | // Use a registration function so that we can move to an ES6 map when we no longer need to support | ||
| 8026 | // old browsers | ||
| 8027 | |||
| 8028 | // Scale config defaults | ||
| 8029 | 	defaults: {}, | ||
| 8030 | 	registerScaleType: function(type, scaleConstructor, scaleDefaults) { | ||
| 8031 | this.constructors[type] = scaleConstructor; | ||
| 8032 | this.defaults[type] = helpers.clone(scaleDefaults); | ||
| 8033 | }, | ||
| 8034 | 	getScaleConstructor: function(type) { | ||
| 8035 | return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; | ||
| 8036 | }, | ||
| 8037 | 	getScaleDefaults: function(type) { | ||
| 8038 | // Return the scale defaults merged with the global settings so that we always use the latest ones | ||
| 8039 | 		return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {}; | ||
| 8040 | }, | ||
| 8041 | 	updateScaleDefaults: function(type, additions) { | ||
| 8042 | var me = this; | ||
| 8043 | 		if (me.defaults.hasOwnProperty(type)) { | ||
| 8044 | me.defaults[type] = helpers.extend(me.defaults[type], additions); | ||
| 8045 | } | ||
| 8046 | }, | ||
| 8047 | 	addScalesToLayout: function(chart) { | ||
| 8048 | // Adds each scale to the chart.boxes array to be sized accordingly | ||
| 8049 | 		helpers.each(chart.scales, function(scale) { | ||
| 8050 | // Set ILayoutItem parameters for backwards compatibility | ||
| 8051 | scale.fullWidth = scale.options.fullWidth; | ||
| 8052 | scale.position = scale.options.position; | ||
| 8053 | scale.weight = scale.options.weight; | ||
| 8054 | layouts.addBox(chart, scale); | ||
| 8055 | }); | ||
| 8056 | } | ||
| 8057 | }; | ||
| 8058 | |||
| 8059 | },{"26":26,"31":31,"46":46}],35:[function(require,module,exports){ | ||
| 8060 | 'use strict'; | ||
| 8061 | |||
| 8062 | var helpers = require(46); | ||
| 8063 | |||
| 8064 | /** | ||
| 8065 | * Namespace to hold static tick generation functions | ||
| 8066 | * @namespace Chart.Ticks | ||
| 8067 | */ | ||
| 8068 | module.exports = { | ||
| 8069 | /** | ||
| 8070 | * Namespace to hold formatters for different types of ticks | ||
| 8071 | * @namespace Chart.Ticks.formatters | ||
| 8072 | */ | ||
| 8073 | 	formatters: { | ||
| 8074 | /** | ||
| 8075 | * Formatter for value labels | ||
| 8076 | * @method Chart.Ticks.formatters.values | ||
| 8077 | * @param value the value to display | ||
| 8078 | 		 * @return {String|Array} the label to display | ||
| 8079 | */ | ||
| 8080 | 		values: function(value) { | ||
| 8081 | return helpers.isArray(value) ? value : '' + value; | ||
| 8082 | }, | ||
| 8083 | |||
| 8084 | /** | ||
| 8085 | * Formatter for linear numeric ticks | ||
| 8086 | * @method Chart.Ticks.formatters.linear | ||
| 8087 | 		 * @param tickValue {Number} the value to be formatted | ||
| 8088 | 		 * @param index {Number} the position of the tickValue parameter in the ticks array | ||
| 8089 | 		 * @param ticks {Array<Number>} the list of ticks being converted | ||
| 8090 | 		 * @return {String} string representation of the tickValue parameter | ||
| 8091 | */ | ||
| 8092 | 		linear: function(tickValue, index, ticks) { | ||
| 8093 | // If we have lots of ticks, don't use the ones | ||
| 8094 | var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; | ||
| 8095 | |||
| 8096 | // If we have a number like 2.5 as the delta, figure out how many decimal places we need | ||
| 8097 | 			if (Math.abs(delta) > 1) { | ||
| 8098 | 				if (tickValue !== Math.floor(tickValue)) { | ||
| 8099 | // not an integer | ||
| 8100 | delta = tickValue - Math.floor(tickValue); | ||
| 8101 | } | ||
| 8102 | } | ||
| 8103 | |||
| 8104 | var logDelta = helpers.log10(Math.abs(delta)); | ||
| 8105 | var tickString = ''; | ||
| 8106 | |||
| 8107 | 			if (tickValue !== 0) { | ||
| 8108 | var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1])); | ||
| 8109 | 				if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation | ||
| 8110 | var logTick = helpers.log10(Math.abs(tickValue)); | ||
| 8111 | tickString = tickValue.toExponential(Math.floor(logTick) - Math.floor(logDelta)); | ||
| 8112 | 				} else { | ||
| 8113 | var numDecimal = -1 * Math.floor(logDelta); | ||
| 8114 | numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places | ||
| 8115 | tickString = tickValue.toFixed(numDecimal); | ||
| 8116 | } | ||
| 8117 | 			} else { | ||
| 8118 | tickString = '0'; // never show decimal places for 0 | ||
| 8119 | } | ||
| 8120 | |||
| 8121 | return tickString; | ||
| 8122 | }, | ||
| 8123 | |||
| 8124 | 		logarithmic: function(tickValue, index, ticks) { | ||
| 8125 | var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); | ||
| 8126 | |||
| 8127 | 			if (tickValue === 0) { | ||
| 8128 | return '0'; | ||
| 8129 | 			} else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { | ||
| 8130 | return tickValue.toExponential(); | ||
| 8131 | } | ||
| 8132 | return ''; | ||
| 8133 | } | ||
| 8134 | } | ||
| 8135 | }; | ||
| 8136 | |||
| 8137 | },{"46":46}],36:[function(require,module,exports){ | ||
| 8138 | 'use strict'; | ||
| 8139 | |||
| 8140 | var defaults = require(26); | ||
| 8141 | var Element = require(27); | ||
| 8142 | var helpers = require(46); | ||
| 8143 | |||
| 8144 | defaults._set('global', { | ||
| 8145 | 	tooltips: { | ||
| 8146 | enabled: true, | ||
| 8147 | custom: null, | ||
| 8148 | mode: 'nearest', | ||
| 8149 | position: 'average', | ||
| 8150 | intersect: true, | ||
| 8151 | backgroundColor: 'rgba(0,0,0,0.8)', | ||
| 8152 | titleFontStyle: 'bold', | ||
| 8153 | titleSpacing: 2, | ||
| 8154 | titleMarginBottom: 6, | ||
| 8155 | titleFontColor: '#fff', | ||
| 8156 | titleAlign: 'left', | ||
| 8157 | bodySpacing: 2, | ||
| 8158 | bodyFontColor: '#fff', | ||
| 8159 | bodyAlign: 'left', | ||
| 8160 | footerFontStyle: 'bold', | ||
| 8161 | footerSpacing: 2, | ||
| 8162 | footerMarginTop: 6, | ||
| 8163 | footerFontColor: '#fff', | ||
| 8164 | footerAlign: 'left', | ||
| 8165 | yPadding: 6, | ||
| 8166 | xPadding: 6, | ||
| 8167 | caretPadding: 2, | ||
| 8168 | caretSize: 5, | ||
| 8169 | cornerRadius: 6, | ||
| 8170 | multiKeyBackground: '#fff', | ||
| 8171 | displayColors: true, | ||
| 8172 | borderColor: 'rgba(0,0,0,0)', | ||
| 8173 | borderWidth: 0, | ||
| 8174 | 		callbacks: { | ||
| 8175 | // Args are: (tooltipItems, data) | ||
| 8176 | beforeTitle: helpers.noop, | ||
| 8177 | 			title: function(tooltipItems, data) { | ||
| 8178 | // Pick first xLabel for now | ||
| 8179 | var title = ''; | ||
| 8180 | var labels = data.labels; | ||
| 8181 | var labelCount = labels ? labels.length : 0; | ||
| 8182 | |||
| 8183 | 				if (tooltipItems.length > 0) { | ||
| 8184 | var item = tooltipItems[0]; | ||
| 8185 | |||
| 8186 | 					if (item.xLabel) { | ||
| 8187 | title = item.xLabel; | ||
| 8188 | 					} else if (labelCount > 0 && item.index < labelCount) { | ||
| 8189 | title = labels[item.index]; | ||
| 8190 | } | ||
| 8191 | } | ||
| 8192 | |||
| 8193 | return title; | ||
| 8194 | }, | ||
| 8195 | afterTitle: helpers.noop, | ||
| 8196 | |||
| 8197 | // Args are: (tooltipItems, data) | ||
| 8198 | beforeBody: helpers.noop, | ||
| 8199 | |||
| 8200 | // Args are: (tooltipItem, data) | ||
| 8201 | beforeLabel: helpers.noop, | ||
| 8202 | 			label: function(tooltipItem, data) { | ||
| 8203 | var label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||
| 8204 | |||
| 8205 | 				if (label) { | ||
| 8206 | label += ': '; | ||
| 8207 | } | ||
| 8208 | label += tooltipItem.yLabel; | ||
| 8209 | return label; | ||
| 8210 | }, | ||
| 8211 | 			labelColor: function(tooltipItem, chart) { | ||
| 8212 | var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); | ||
| 8213 | var activeElement = meta.data[tooltipItem.index]; | ||
| 8214 | var view = activeElement._view; | ||
| 8215 | 				return { | ||
| 8216 | borderColor: view.borderColor, | ||
| 8217 | backgroundColor: view.backgroundColor | ||
| 8218 | }; | ||
| 8219 | }, | ||
| 8220 | 			labelTextColor: function() { | ||
| 8221 | return this._options.bodyFontColor; | ||
| 8222 | }, | ||
| 8223 | afterLabel: helpers.noop, | ||
| 8224 | |||
| 8225 | // Args are: (tooltipItems, data) | ||
| 8226 | afterBody: helpers.noop, | ||
| 8227 | |||
| 8228 | // Args are: (tooltipItems, data) | ||
| 8229 | beforeFooter: helpers.noop, | ||
| 8230 | footer: helpers.noop, | ||
| 8231 | afterFooter: helpers.noop | ||
| 8232 | } | ||
| 8233 | } | ||
| 8234 | }); | ||
| 8235 | |||
| 8236 | var positioners = { | ||
| 8237 | /** | ||
| 8238 | * Average mode places the tooltip at the average position of the elements shown | ||
| 8239 | * @function Chart.Tooltip.positioners.average | ||
| 8240 | 	 * @param elements {ChartElement[]} the elements being displayed in the tooltip | ||
| 8241 | 	 * @returns {Point} tooltip position | ||
| 8242 | */ | ||
| 8243 | 	average: function(elements) { | ||
| 8244 | 		if (!elements.length) { | ||
| 8245 | return false; | ||
| 8246 | } | ||
| 8247 | |||
| 8248 | var i, len; | ||
| 8249 | var x = 0; | ||
| 8250 | var y = 0; | ||
| 8251 | var count = 0; | ||
| 8252 | |||
| 8253 | 		for (i = 0, len = elements.length; i < len; ++i) { | ||
| 8254 | var el = elements[i]; | ||
| 8255 | 			if (el && el.hasValue()) { | ||
| 8256 | var pos = el.tooltipPosition(); | ||
| 8257 | x += pos.x; | ||
| 8258 | y += pos.y; | ||
| 8259 | ++count; | ||
| 8260 | } | ||
| 8261 | } | ||
| 8262 | |||
| 8263 | 		return { | ||
| 8264 | x: Math.round(x / count), | ||
| 8265 | y: Math.round(y / count) | ||
| 8266 | }; | ||
| 8267 | }, | ||
| 8268 | |||
| 8269 | /** | ||
| 8270 | * Gets the tooltip position nearest of the item nearest to the event position | ||
| 8271 | * @function Chart.Tooltip.positioners.nearest | ||
| 8272 | 	 * @param elements {Chart.Element[]} the tooltip elements | ||
| 8273 | 	 * @param eventPosition {Point} the position of the event in canvas coordinates | ||
| 8274 | 	 * @returns {Point} the tooltip position | ||
| 8275 | */ | ||
| 8276 | 	nearest: function(elements, eventPosition) { | ||
| 8277 | var x = eventPosition.x; | ||
| 8278 | var y = eventPosition.y; | ||
| 8279 | var minDistance = Number.POSITIVE_INFINITY; | ||
| 8280 | var i, len, nearestElement; | ||
| 8281 | |||
| 8282 | 		for (i = 0, len = elements.length; i < len; ++i) { | ||
| 8283 | var el = elements[i]; | ||
| 8284 | 			if (el && el.hasValue()) { | ||
| 8285 | var center = el.getCenterPoint(); | ||
| 8286 | var d = helpers.distanceBetweenPoints(eventPosition, center); | ||
| 8287 | |||
| 8288 | 				if (d < minDistance) { | ||
| 8289 | minDistance = d; | ||
| 8290 | nearestElement = el; | ||
| 8291 | } | ||
| 8292 | } | ||
| 8293 | } | ||
| 8294 | |||
| 8295 | 		if (nearestElement) { | ||
| 8296 | var tp = nearestElement.tooltipPosition(); | ||
| 8297 | x = tp.x; | ||
| 8298 | y = tp.y; | ||
| 8299 | } | ||
| 8300 | |||
| 8301 | 		return { | ||
| 8302 | x: x, | ||
| 8303 | y: y | ||
| 8304 | }; | ||
| 8305 | } | ||
| 8306 | }; | ||
| 8307 | |||
| 8308 | /** | ||
| 8309 | * Helper method to merge the opacity into a color | ||
| 8310 | */ | ||
| 8311 | function mergeOpacity(colorString, opacity) { | ||
| 8312 | var color = helpers.color(colorString); | ||
| 8313 | return color.alpha(opacity * color.alpha()).rgbaString(); | ||
| 8314 | } | ||
| 8315 | |||
| 8316 | // Helper to push or concat based on if the 2nd parameter is an array or not | ||
| 8317 | function pushOrConcat(base, toPush) { | ||
| 8318 | 	if (toPush) { | ||
| 8319 | 		if (helpers.isArray(toPush)) { | ||
| 8320 | // base = base.concat(toPush); | ||
| 8321 | Array.prototype.push.apply(base, toPush); | ||
| 8322 | 		} else { | ||
| 8323 | base.push(toPush); | ||
| 8324 | } | ||
| 8325 | } | ||
| 8326 | |||
| 8327 | return base; | ||
| 8328 | } | ||
| 8329 | |||
| 8330 | /** | ||
| 8331 | * Returns array of strings split by newline | ||
| 8332 |  * @param {String} value - The value to split by newline. | ||
| 8333 |  * @returns {Array} value if newline present - Returned from String split() method | ||
| 8334 | * @function | ||
| 8335 | */ | ||
| 8336 | function splitNewlines(str) { | ||
| 8337 | 	if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { | ||
| 8338 | 		return str.split('\n'); | ||
| 8339 | } | ||
| 8340 | return str; | ||
| 8341 | } | ||
| 8342 | |||
| 8343 | |||
| 8344 | // Private helper to create a tooltip item model | ||
| 8345 | // @param element : the chart element (point, arc, bar) to create the tooltip item for | ||
| 8346 | // @return : new tooltip item | ||
| 8347 | function createTooltipItem(element) { | ||
| 8348 | var xScale = element._xScale; | ||
| 8349 | var yScale = element._yScale || element._scale; // handle radar || polarArea charts | ||
| 8350 | var index = element._index; | ||
| 8351 | var datasetIndex = element._datasetIndex; | ||
| 8352 | |||
| 8353 | 	return { | ||
| 8354 | xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', | ||
| 8355 | yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', | ||
| 8356 | index: index, | ||
| 8357 | datasetIndex: datasetIndex, | ||
| 8358 | x: element._model.x, | ||
| 8359 | y: element._model.y | ||
| 8360 | }; | ||
| 8361 | } | ||
| 8362 | |||
| 8363 | /** | ||
| 8364 | * Helper to get the reset model for the tooltip | ||
| 8365 |  * @param tooltipOpts {Object} the tooltip options | ||
| 8366 | */ | ||
| 8367 | function getBaseModel(tooltipOpts) { | ||
| 8368 | var globalDefaults = defaults.global; | ||
| 8369 | var valueOrDefault = helpers.valueOrDefault; | ||
| 8370 | |||
| 8371 | 	return { | ||
| 8372 | // Positioning | ||
| 8373 | xPadding: tooltipOpts.xPadding, | ||
| 8374 | yPadding: tooltipOpts.yPadding, | ||
| 8375 | xAlign: tooltipOpts.xAlign, | ||
| 8376 | yAlign: tooltipOpts.yAlign, | ||
| 8377 | |||
| 8378 | // Body | ||
| 8379 | bodyFontColor: tooltipOpts.bodyFontColor, | ||
| 8380 | _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), | ||
| 8381 | _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), | ||
| 8382 | _bodyAlign: tooltipOpts.bodyAlign, | ||
| 8383 | bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), | ||
| 8384 | bodySpacing: tooltipOpts.bodySpacing, | ||
| 8385 | |||
| 8386 | // Title | ||
| 8387 | titleFontColor: tooltipOpts.titleFontColor, | ||
| 8388 | _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), | ||
| 8389 | _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), | ||
| 8390 | titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), | ||
| 8391 | _titleAlign: tooltipOpts.titleAlign, | ||
| 8392 | titleSpacing: tooltipOpts.titleSpacing, | ||
| 8393 | titleMarginBottom: tooltipOpts.titleMarginBottom, | ||
| 8394 | |||
| 8395 | // Footer | ||
| 8396 | footerFontColor: tooltipOpts.footerFontColor, | ||
| 8397 | _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), | ||
| 8398 | _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), | ||
| 8399 | footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), | ||
| 8400 | _footerAlign: tooltipOpts.footerAlign, | ||
| 8401 | footerSpacing: tooltipOpts.footerSpacing, | ||
| 8402 | footerMarginTop: tooltipOpts.footerMarginTop, | ||
| 8403 | |||
| 8404 | // Appearance | ||
| 8405 | caretSize: tooltipOpts.caretSize, | ||
| 8406 | cornerRadius: tooltipOpts.cornerRadius, | ||
| 8407 | backgroundColor: tooltipOpts.backgroundColor, | ||
| 8408 | opacity: 0, | ||
| 8409 | legendColorBackground: tooltipOpts.multiKeyBackground, | ||
| 8410 | displayColors: tooltipOpts.displayColors, | ||
| 8411 | borderColor: tooltipOpts.borderColor, | ||
| 8412 | borderWidth: tooltipOpts.borderWidth | ||
| 8413 | }; | ||
| 8414 | } | ||
| 8415 | |||
| 8416 | /** | ||
| 8417 | * Get the size of the tooltip | ||
| 8418 | */ | ||
| 8419 | function getTooltipSize(tooltip, model) { | ||
| 8420 | var ctx = tooltip._chart.ctx; | ||
| 8421 | |||
| 8422 | var height = model.yPadding * 2; // Tooltip Padding | ||
| 8423 | var width = 0; | ||
| 8424 | |||
| 8425 | // Count of all lines in the body | ||
| 8426 | var body = model.body; | ||
| 8427 | 	var combinedBodyLength = body.reduce(function(count, bodyItem) { | ||
| 8428 | return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; | ||
| 8429 | }, 0); | ||
| 8430 | combinedBodyLength += model.beforeBody.length + model.afterBody.length; | ||
| 8431 | |||
| 8432 | var titleLineCount = model.title.length; | ||
| 8433 | var footerLineCount = model.footer.length; | ||
| 8434 | var titleFontSize = model.titleFontSize; | ||
| 8435 | var bodyFontSize = model.bodyFontSize; | ||
| 8436 | var footerFontSize = model.footerFontSize; | ||
| 8437 | |||
| 8438 | height += titleLineCount * titleFontSize; // Title Lines | ||
| 8439 | height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing | ||
| 8440 | height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin | ||
| 8441 | height += combinedBodyLength * bodyFontSize; // Body Lines | ||
| 8442 | height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing | ||
| 8443 | height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin | ||
| 8444 | height += footerLineCount * (footerFontSize); // Footer Lines | ||
| 8445 | height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing | ||
| 8446 | |||
| 8447 | // Title width | ||
| 8448 | var widthPadding = 0; | ||
| 8449 | 	var maxLineWidth = function(line) { | ||
| 8450 | width = Math.max(width, ctx.measureText(line).width + widthPadding); | ||
| 8451 | }; | ||
| 8452 | |||
| 8453 | ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); | ||
| 8454 | helpers.each(model.title, maxLineWidth); | ||
| 8455 | |||
| 8456 | // Body width | ||
| 8457 | ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); | ||
| 8458 | helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth); | ||
| 8459 | |||
| 8460 | // Body lines may include some extra width due to the color box | ||
| 8461 | widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; | ||
| 8462 | 	helpers.each(body, function(bodyItem) { | ||
| 8463 | helpers.each(bodyItem.before, maxLineWidth); | ||
| 8464 | helpers.each(bodyItem.lines, maxLineWidth); | ||
| 8465 | helpers.each(bodyItem.after, maxLineWidth); | ||
| 8466 | }); | ||
| 8467 | |||
| 8468 | // Reset back to 0 | ||
| 8469 | widthPadding = 0; | ||
| 8470 | |||
| 8471 | // Footer width | ||
| 8472 | ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); | ||
| 8473 | helpers.each(model.footer, maxLineWidth); | ||
| 8474 | |||
| 8475 | // Add padding | ||
| 8476 | width += 2 * model.xPadding; | ||
| 8477 | |||
| 8478 | 	return { | ||
| 8479 | width: width, | ||
| 8480 | height: height | ||
| 8481 | }; | ||
| 8482 | } | ||
| 8483 | |||
| 8484 | /** | ||
| 8485 | * Helper to get the alignment of a tooltip given the size | ||
| 8486 | */ | ||
| 8487 | function determineAlignment(tooltip, size) { | ||
| 8488 | var model = tooltip._model; | ||
| 8489 | var chart = tooltip._chart; | ||
| 8490 | var chartArea = tooltip._chart.chartArea; | ||
| 8491 | var xAlign = 'center'; | ||
| 8492 | var yAlign = 'center'; | ||
| 8493 | |||
| 8494 | 	if (model.y < size.height) { | ||
| 8495 | yAlign = 'top'; | ||
| 8496 | 	} else if (model.y > (chart.height - size.height)) { | ||
| 8497 | yAlign = 'bottom'; | ||
| 8498 | } | ||
| 8499 | |||
| 8500 | var lf, rf; // functions to determine left, right alignment | ||
| 8501 | var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart | ||
| 8502 | var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges | ||
| 8503 | var midX = (chartArea.left + chartArea.right) / 2; | ||
| 8504 | var midY = (chartArea.top + chartArea.bottom) / 2; | ||
| 8505 | |||
| 8506 | 	if (yAlign === 'center') { | ||
| 8507 | 		lf = function(x) { | ||
| 8508 | return x <= midX; | ||
| 8509 | }; | ||
| 8510 | 		rf = function(x) { | ||
| 8511 | return x > midX; | ||
| 8512 | }; | ||
| 8513 | 	} else { | ||
| 8514 | 		lf = function(x) { | ||
| 8515 | return x <= (size.width / 2); | ||
| 8516 | }; | ||
| 8517 | 		rf = function(x) { | ||
| 8518 | return x >= (chart.width - (size.width / 2)); | ||
| 8519 | }; | ||
| 8520 | } | ||
| 8521 | |||
| 8522 | 	olf = function(x) { | ||
| 8523 | return x + size.width + model.caretSize + model.caretPadding > chart.width; | ||
| 8524 | }; | ||
| 8525 | 	orf = function(x) { | ||
| 8526 | return x - size.width - model.caretSize - model.caretPadding < 0; | ||
| 8527 | }; | ||
| 8528 | 	yf = function(y) { | ||
| 8529 | return y <= midY ? 'top' : 'bottom'; | ||
| 8530 | }; | ||
| 8531 | |||
| 8532 | 	if (lf(model.x)) { | ||
| 8533 | xAlign = 'left'; | ||
| 8534 | |||
| 8535 | // Is tooltip too wide and goes over the right side of the chart.? | ||
| 8536 | 		if (olf(model.x)) { | ||
| 8537 | xAlign = 'center'; | ||
| 8538 | yAlign = yf(model.y); | ||
| 8539 | } | ||
| 8540 | 	} else if (rf(model.x)) { | ||
| 8541 | xAlign = 'right'; | ||
| 8542 | |||
| 8543 | // Is tooltip too wide and goes outside left edge of canvas? | ||
| 8544 | 		if (orf(model.x)) { | ||
| 8545 | xAlign = 'center'; | ||
| 8546 | yAlign = yf(model.y); | ||
| 8547 | } | ||
| 8548 | } | ||
| 8549 | |||
| 8550 | var opts = tooltip._options; | ||
| 8551 | 	return { | ||
| 8552 | xAlign: opts.xAlign ? opts.xAlign : xAlign, | ||
| 8553 | yAlign: opts.yAlign ? opts.yAlign : yAlign | ||
| 8554 | }; | ||
| 8555 | } | ||
| 8556 | |||
| 8557 | /** | ||
| 8558 | * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment | ||
| 8559 | */ | ||
| 8560 | function getBackgroundPoint(vm, size, alignment, chart) { | ||
| 8561 | // Background Position | ||
| 8562 | var x = vm.x; | ||
| 8563 | var y = vm.y; | ||
| 8564 | |||
| 8565 | var caretSize = vm.caretSize; | ||
| 8566 | var caretPadding = vm.caretPadding; | ||
| 8567 | var cornerRadius = vm.cornerRadius; | ||
| 8568 | var xAlign = alignment.xAlign; | ||
| 8569 | var yAlign = alignment.yAlign; | ||
| 8570 | var paddingAndSize = caretSize + caretPadding; | ||
| 8571 | var radiusAndPadding = cornerRadius + caretPadding; | ||
| 8572 | |||
| 8573 | 	if (xAlign === 'right') { | ||
| 8574 | x -= size.width; | ||
| 8575 | 	} else if (xAlign === 'center') { | ||
| 8576 | x -= (size.width / 2); | ||
| 8577 | 		if (x + size.width > chart.width) { | ||
| 8578 | x = chart.width - size.width; | ||
| 8579 | } | ||
| 8580 | 		if (x < 0) { | ||
| 8581 | x = 0; | ||
| 8582 | } | ||
| 8583 | } | ||
| 8584 | |||
| 8585 | 	if (yAlign === 'top') { | ||
| 8586 | y += paddingAndSize; | ||
| 8587 | 	} else if (yAlign === 'bottom') { | ||
| 8588 | y -= size.height + paddingAndSize; | ||
| 8589 | 	} else { | ||
| 8590 | y -= (size.height / 2); | ||
| 8591 | } | ||
| 8592 | |||
| 8593 | 	if (yAlign === 'center') { | ||
| 8594 | 		if (xAlign === 'left') { | ||
| 8595 | x += paddingAndSize; | ||
| 8596 | 		} else if (xAlign === 'right') { | ||
| 8597 | x -= paddingAndSize; | ||
| 8598 | } | ||
| 8599 | 	} else if (xAlign === 'left') { | ||
| 8600 | x -= radiusAndPadding; | ||
| 8601 | 	} else if (xAlign === 'right') { | ||
| 8602 | x += radiusAndPadding; | ||
| 8603 | } | ||
| 8604 | |||
| 8605 | 	return { | ||
| 8606 | x: x, | ||
| 8607 | y: y | ||
| 8608 | }; | ||
| 8609 | } | ||
| 8610 | |||
| 8611 | /** | ||
| 8612 | * Helper to build before and after body lines | ||
| 8613 | */ | ||
| 8614 | function getBeforeAfterBodyLines(callback) { | ||
| 8615 | return pushOrConcat([], splitNewlines(callback)); | ||
| 8616 | } | ||
| 8617 | |||
| 8618 | var exports = module.exports = Element.extend({ | ||
| 8619 | 	initialize: function() { | ||
| 8620 | this._model = getBaseModel(this._options); | ||
| 8621 | this._lastActive = []; | ||
| 8622 | }, | ||
| 8623 | |||
| 8624 | // Get the title | ||
| 8625 | // Args are: (tooltipItem, data) | ||
| 8626 | 	getTitle: function() { | ||
| 8627 | var me = this; | ||
| 8628 | var opts = me._options; | ||
| 8629 | var callbacks = opts.callbacks; | ||
| 8630 | |||
| 8631 | var beforeTitle = callbacks.beforeTitle.apply(me, arguments); | ||
| 8632 | var title = callbacks.title.apply(me, arguments); | ||
| 8633 | var afterTitle = callbacks.afterTitle.apply(me, arguments); | ||
| 8634 | |||
| 8635 | var lines = []; | ||
| 8636 | lines = pushOrConcat(lines, splitNewlines(beforeTitle)); | ||
| 8637 | lines = pushOrConcat(lines, splitNewlines(title)); | ||
| 8638 | lines = pushOrConcat(lines, splitNewlines(afterTitle)); | ||
| 8639 | |||
| 8640 | return lines; | ||
| 8641 | }, | ||
| 8642 | |||
| 8643 | // Args are: (tooltipItem, data) | ||
| 8644 | 	getBeforeBody: function() { | ||
| 8645 | return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments)); | ||
| 8646 | }, | ||
| 8647 | |||
| 8648 | // Args are: (tooltipItem, data) | ||
| 8649 | 	getBody: function(tooltipItems, data) { | ||
| 8650 | var me = this; | ||
| 8651 | var callbacks = me._options.callbacks; | ||
| 8652 | var bodyItems = []; | ||
| 8653 | |||
| 8654 | 		helpers.each(tooltipItems, function(tooltipItem) { | ||
| 8655 | 			var bodyItem = { | ||
| 8656 | before: [], | ||
| 8657 | lines: [], | ||
| 8658 | after: [] | ||
| 8659 | }; | ||
| 8660 | pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data))); | ||
| 8661 | pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); | ||
| 8662 | pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data))); | ||
| 8663 | |||
| 8664 | bodyItems.push(bodyItem); | ||
| 8665 | }); | ||
| 8666 | |||
| 8667 | return bodyItems; | ||
| 8668 | }, | ||
| 8669 | |||
| 8670 | // Args are: (tooltipItem, data) | ||
| 8671 | 	getAfterBody: function() { | ||
| 8672 | return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments)); | ||
| 8673 | }, | ||
| 8674 | |||
| 8675 | // Get the footer and beforeFooter and afterFooter lines | ||
| 8676 | // Args are: (tooltipItem, data) | ||
| 8677 | 	getFooter: function() { | ||
| 8678 | var me = this; | ||
| 8679 | var callbacks = me._options.callbacks; | ||
| 8680 | |||
| 8681 | var beforeFooter = callbacks.beforeFooter.apply(me, arguments); | ||
| 8682 | var footer = callbacks.footer.apply(me, arguments); | ||
| 8683 | var afterFooter = callbacks.afterFooter.apply(me, arguments); | ||
| 8684 | |||
| 8685 | var lines = []; | ||
| 8686 | lines = pushOrConcat(lines, splitNewlines(beforeFooter)); | ||
| 8687 | lines = pushOrConcat(lines, splitNewlines(footer)); | ||
| 8688 | lines = pushOrConcat(lines, splitNewlines(afterFooter)); | ||
| 8689 | |||
| 8690 | return lines; | ||
| 8691 | }, | ||
| 8692 | |||
| 8693 | 	update: function(changed) { | ||
| 8694 | var me = this; | ||
| 8695 | var opts = me._options; | ||
| 8696 | |||
| 8697 | // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition | ||
| 8698 | // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time | ||
| 8699 | // which breaks any animations. | ||
| 8700 | var existingModel = me._model; | ||
| 8701 | var model = me._model = getBaseModel(opts); | ||
| 8702 | var active = me._active; | ||
| 8703 | |||
| 8704 | var data = me._data; | ||
| 8705 | |||
| 8706 | // In the case where active.length === 0 we need to keep these at existing values for good animations | ||
| 8707 | 		var alignment = { | ||
| 8708 | xAlign: existingModel.xAlign, | ||
| 8709 | yAlign: existingModel.yAlign | ||
| 8710 | }; | ||
| 8711 | 		var backgroundPoint = { | ||
| 8712 | x: existingModel.x, | ||
| 8713 | y: existingModel.y | ||
| 8714 | }; | ||
| 8715 | 		var tooltipSize = { | ||
| 8716 | width: existingModel.width, | ||
| 8717 | height: existingModel.height | ||
| 8718 | }; | ||
| 8719 | 		var tooltipPosition = { | ||
| 8720 | x: existingModel.caretX, | ||
| 8721 | y: existingModel.caretY | ||
| 8722 | }; | ||
| 8723 | |||
| 8724 | var i, len; | ||
| 8725 | |||
| 8726 | 		if (active.length) { | ||
| 8727 | model.opacity = 1; | ||
| 8728 | |||
| 8729 | var labelColors = []; | ||
| 8730 | var labelTextColors = []; | ||
| 8731 | tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition); | ||
| 8732 | |||
| 8733 | var tooltipItems = []; | ||
| 8734 | 			for (i = 0, len = active.length; i < len; ++i) { | ||
| 8735 | tooltipItems.push(createTooltipItem(active[i])); | ||
| 8736 | } | ||
| 8737 | |||
| 8738 | // If the user provided a filter function, use it to modify the tooltip items | ||
| 8739 | 			if (opts.filter) { | ||
| 8740 | 				tooltipItems = tooltipItems.filter(function(a) { | ||
| 8741 | return opts.filter(a, data); | ||
| 8742 | }); | ||
| 8743 | } | ||
| 8744 | |||
| 8745 | // If the user provided a sorting function, use it to modify the tooltip items | ||
| 8746 | 			if (opts.itemSort) { | ||
| 8747 | 				tooltipItems = tooltipItems.sort(function(a, b) { | ||
| 8748 | return opts.itemSort(a, b, data); | ||
| 8749 | }); | ||
| 8750 | } | ||
| 8751 | |||
| 8752 | // Determine colors for boxes | ||
| 8753 | 			helpers.each(tooltipItems, function(tooltipItem) { | ||
| 8754 | labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); | ||
| 8755 | labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); | ||
| 8756 | }); | ||
| 8757 | |||
| 8758 | |||
| 8759 | // Build the Text Lines | ||
| 8760 | model.title = me.getTitle(tooltipItems, data); | ||
| 8761 | model.beforeBody = me.getBeforeBody(tooltipItems, data); | ||
| 8762 | model.body = me.getBody(tooltipItems, data); | ||
| 8763 | model.afterBody = me.getAfterBody(tooltipItems, data); | ||
| 8764 | model.footer = me.getFooter(tooltipItems, data); | ||
| 8765 | |||
| 8766 | // Initial positioning and colors | ||
| 8767 | model.x = Math.round(tooltipPosition.x); | ||
| 8768 | model.y = Math.round(tooltipPosition.y); | ||
| 8769 | model.caretPadding = opts.caretPadding; | ||
| 8770 | model.labelColors = labelColors; | ||
| 8771 | model.labelTextColors = labelTextColors; | ||
| 8772 | |||
| 8773 | // data points | ||
| 8774 | model.dataPoints = tooltipItems; | ||
| 8775 | |||
| 8776 | // We need to determine alignment of the tooltip | ||
| 8777 | tooltipSize = getTooltipSize(this, model); | ||
| 8778 | alignment = determineAlignment(this, tooltipSize); | ||
| 8779 | // Final Size and Position | ||
| 8780 | backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); | ||
| 8781 | 		} else { | ||
| 8782 | model.opacity = 0; | ||
| 8783 | } | ||
| 8784 | |||
| 8785 | model.xAlign = alignment.xAlign; | ||
| 8786 | model.yAlign = alignment.yAlign; | ||
| 8787 | model.x = backgroundPoint.x; | ||
| 8788 | model.y = backgroundPoint.y; | ||
| 8789 | model.width = tooltipSize.width; | ||
| 8790 | model.height = tooltipSize.height; | ||
| 8791 | |||
| 8792 | // Point where the caret on the tooltip points to | ||
| 8793 | model.caretX = tooltipPosition.x; | ||
| 8794 | model.caretY = tooltipPosition.y; | ||
| 8795 | |||
| 8796 | me._model = model; | ||
| 8797 | |||
| 8798 | 		if (changed && opts.custom) { | ||
| 8799 | opts.custom.call(me, model); | ||
| 8800 | } | ||
| 8801 | |||
| 8802 | return me; | ||
| 8803 | }, | ||
| 8804 | |||
| 8805 | 	drawCaret: function(tooltipPoint, size) { | ||
| 8806 | var ctx = this._chart.ctx; | ||
| 8807 | var vm = this._view; | ||
| 8808 | var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); | ||
| 8809 | |||
| 8810 | ctx.lineTo(caretPosition.x1, caretPosition.y1); | ||
| 8811 | ctx.lineTo(caretPosition.x2, caretPosition.y2); | ||
| 8812 | ctx.lineTo(caretPosition.x3, caretPosition.y3); | ||
| 8813 | }, | ||
| 8814 | 	getCaretPosition: function(tooltipPoint, size, vm) { | ||
| 8815 | var x1, x2, x3, y1, y2, y3; | ||
| 8816 | var caretSize = vm.caretSize; | ||
| 8817 | var cornerRadius = vm.cornerRadius; | ||
| 8818 | var xAlign = vm.xAlign; | ||
| 8819 | var yAlign = vm.yAlign; | ||
| 8820 | var ptX = tooltipPoint.x; | ||
| 8821 | var ptY = tooltipPoint.y; | ||
| 8822 | var width = size.width; | ||
| 8823 | var height = size.height; | ||
| 8824 | |||
| 8825 | 		if (yAlign === 'center') { | ||
| 8826 | y2 = ptY + (height / 2); | ||
| 8827 | |||
| 8828 | 			if (xAlign === 'left') { | ||
| 8829 | x1 = ptX; | ||
| 8830 | x2 = x1 - caretSize; | ||
| 8831 | x3 = x1; | ||
| 8832 | |||
| 8833 | y1 = y2 + caretSize; | ||
| 8834 | y3 = y2 - caretSize; | ||
| 8835 | 			} else { | ||
| 8836 | x1 = ptX + width; | ||
| 8837 | x2 = x1 + caretSize; | ||
| 8838 | x3 = x1; | ||
| 8839 | |||
| 8840 | y1 = y2 - caretSize; | ||
| 8841 | y3 = y2 + caretSize; | ||
| 8842 | } | ||
| 8843 | 		} else { | ||
| 8844 | 			if (xAlign === 'left') { | ||
| 8845 | x2 = ptX + cornerRadius + (caretSize); | ||
| 8846 | x1 = x2 - caretSize; | ||
| 8847 | x3 = x2 + caretSize; | ||
| 8848 | 			} else if (xAlign === 'right') { | ||
| 8849 | x2 = ptX + width - cornerRadius - caretSize; | ||
| 8850 | x1 = x2 - caretSize; | ||
| 8851 | x3 = x2 + caretSize; | ||
| 8852 | 			} else { | ||
| 8853 | x2 = vm.caretX; | ||
| 8854 | x1 = x2 - caretSize; | ||
| 8855 | x3 = x2 + caretSize; | ||
| 8856 | } | ||
| 8857 | 			if (yAlign === 'top') { | ||
| 8858 | y1 = ptY; | ||
| 8859 | y2 = y1 - caretSize; | ||
| 8860 | y3 = y1; | ||
| 8861 | 			} else { | ||
| 8862 | y1 = ptY + height; | ||
| 8863 | y2 = y1 + caretSize; | ||
| 8864 | y3 = y1; | ||
| 8865 | // invert drawing order | ||
| 8866 | var tmp = x3; | ||
| 8867 | x3 = x1; | ||
| 8868 | x1 = tmp; | ||
| 8869 | } | ||
| 8870 | } | ||
| 8871 | 		return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; | ||
| 8872 | }, | ||
| 8873 | |||
| 8874 | 	drawTitle: function(pt, vm, ctx, opacity) { | ||
| 8875 | var title = vm.title; | ||
| 8876 | |||
| 8877 | 		if (title.length) { | ||
| 8878 | ctx.textAlign = vm._titleAlign; | ||
| 8879 | ctx.textBaseline = 'top'; | ||
| 8880 | |||
| 8881 | var titleFontSize = vm.titleFontSize; | ||
| 8882 | var titleSpacing = vm.titleSpacing; | ||
| 8883 | |||
| 8884 | ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); | ||
| 8885 | ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); | ||
| 8886 | |||
| 8887 | var i, len; | ||
| 8888 | 			for (i = 0, len = title.length; i < len; ++i) { | ||
| 8889 | ctx.fillText(title[i], pt.x, pt.y); | ||
| 8890 | pt.y += titleFontSize + titleSpacing; // Line Height and spacing | ||
| 8891 | |||
| 8892 | 				if (i + 1 === title.length) { | ||
| 8893 | pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing | ||
| 8894 | } | ||
| 8895 | } | ||
| 8896 | } | ||
| 8897 | }, | ||
| 8898 | |||
| 8899 | 	drawBody: function(pt, vm, ctx, opacity) { | ||
| 8900 | var bodyFontSize = vm.bodyFontSize; | ||
| 8901 | var bodySpacing = vm.bodySpacing; | ||
| 8902 | var body = vm.body; | ||
| 8903 | |||
| 8904 | ctx.textAlign = vm._bodyAlign; | ||
| 8905 | ctx.textBaseline = 'top'; | ||
| 8906 | ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); | ||
| 8907 | |||
| 8908 | // Before Body | ||
| 8909 | var xLinePadding = 0; | ||
| 8910 | 		var fillLineOfText = function(line) { | ||
| 8911 | ctx.fillText(line, pt.x + xLinePadding, pt.y); | ||
| 8912 | pt.y += bodyFontSize + bodySpacing; | ||
| 8913 | }; | ||
| 8914 | |||
| 8915 | // Before body lines | ||
| 8916 | ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity); | ||
| 8917 | helpers.each(vm.beforeBody, fillLineOfText); | ||
| 8918 | |||
| 8919 | var drawColorBoxes = vm.displayColors; | ||
| 8920 | xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; | ||
| 8921 | |||
| 8922 | // Draw body lines now | ||
| 8923 | 		helpers.each(body, function(bodyItem, i) { | ||
| 8924 | var textColor = mergeOpacity(vm.labelTextColors[i], opacity); | ||
| 8925 | ctx.fillStyle = textColor; | ||
| 8926 | helpers.each(bodyItem.before, fillLineOfText); | ||
| 8927 | |||
| 8928 | 			helpers.each(bodyItem.lines, function(line) { | ||
| 8929 | // Draw Legend-like boxes if needed | ||
| 8930 | 				if (drawColorBoxes) { | ||
| 8931 | // Fill a white rect so that colours merge nicely if the opacity is < 1 | ||
| 8932 | ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); | ||
| 8933 | ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); | ||
| 8934 | |||
| 8935 | // Border | ||
| 8936 | ctx.lineWidth = 1; | ||
| 8937 | ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); | ||
| 8938 | ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); | ||
| 8939 | |||
| 8940 | // Inner square | ||
| 8941 | ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); | ||
| 8942 | ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); | ||
| 8943 | ctx.fillStyle = textColor; | ||
| 8944 | } | ||
| 8945 | |||
| 8946 | fillLineOfText(line); | ||
| 8947 | }); | ||
| 8948 | |||
| 8949 | helpers.each(bodyItem.after, fillLineOfText); | ||
| 8950 | }); | ||
| 8951 | |||
| 8952 | // Reset back to 0 for after body | ||
| 8953 | xLinePadding = 0; | ||
| 8954 | |||
| 8955 | // After body lines | ||
| 8956 | helpers.each(vm.afterBody, fillLineOfText); | ||
| 8957 | pt.y -= bodySpacing; // Remove last body spacing | ||
| 8958 | }, | ||
| 8959 | |||
| 8960 | 	drawFooter: function(pt, vm, ctx, opacity) { | ||
| 8961 | var footer = vm.footer; | ||
| 8962 | |||
| 8963 | 		if (footer.length) { | ||
| 8964 | pt.y += vm.footerMarginTop; | ||
| 8965 | |||
| 8966 | ctx.textAlign = vm._footerAlign; | ||
| 8967 | ctx.textBaseline = 'top'; | ||
| 8968 | |||
| 8969 | ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); | ||
| 8970 | ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); | ||
| 8971 | |||
| 8972 | 			helpers.each(footer, function(line) { | ||
| 8973 | ctx.fillText(line, pt.x, pt.y); | ||
| 8974 | pt.y += vm.footerFontSize + vm.footerSpacing; | ||
| 8975 | }); | ||
| 8976 | } | ||
| 8977 | }, | ||
| 8978 | |||
| 8979 | 	drawBackground: function(pt, vm, ctx, tooltipSize, opacity) { | ||
| 8980 | ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); | ||
| 8981 | ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity); | ||
| 8982 | ctx.lineWidth = vm.borderWidth; | ||
| 8983 | var xAlign = vm.xAlign; | ||
| 8984 | var yAlign = vm.yAlign; | ||
| 8985 | var x = pt.x; | ||
| 8986 | var y = pt.y; | ||
| 8987 | var width = tooltipSize.width; | ||
| 8988 | var height = tooltipSize.height; | ||
| 8989 | var radius = vm.cornerRadius; | ||
| 8990 | |||
| 8991 | ctx.beginPath(); | ||
| 8992 | ctx.moveTo(x + radius, y); | ||
| 8993 | 		if (yAlign === 'top') { | ||
| 8994 | this.drawCaret(pt, tooltipSize); | ||
| 8995 | } | ||
| 8996 | ctx.lineTo(x + width - radius, y); | ||
| 8997 | ctx.quadraticCurveTo(x + width, y, x + width, y + radius); | ||
| 8998 | 		if (yAlign === 'center' && xAlign === 'right') { | ||
| 8999 | this.drawCaret(pt, tooltipSize); | ||
| 9000 | } | ||
| 9001 | ctx.lineTo(x + width, y + height - radius); | ||
| 9002 | ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); | ||
| 9003 | 		if (yAlign === 'bottom') { | ||
| 9004 | this.drawCaret(pt, tooltipSize); | ||
| 9005 | } | ||
| 9006 | ctx.lineTo(x + radius, y + height); | ||
| 9007 | ctx.quadraticCurveTo(x, y + height, x, y + height - radius); | ||
| 9008 | 		if (yAlign === 'center' && xAlign === 'left') { | ||
| 9009 | this.drawCaret(pt, tooltipSize); | ||
| 9010 | } | ||
| 9011 | ctx.lineTo(x, y + radius); | ||
| 9012 | ctx.quadraticCurveTo(x, y, x + radius, y); | ||
| 9013 | ctx.closePath(); | ||
| 9014 | |||
| 9015 | ctx.fill(); | ||
| 9016 | |||
| 9017 | 		if (vm.borderWidth > 0) { | ||
| 9018 | ctx.stroke(); | ||
| 9019 | } | ||
| 9020 | }, | ||
| 9021 | |||
| 9022 | 	draw: function() { | ||
| 9023 | var ctx = this._chart.ctx; | ||
| 9024 | var vm = this._view; | ||
| 9025 | |||
| 9026 | 		if (vm.opacity === 0) { | ||
| 9027 | return; | ||
| 9028 | } | ||
| 9029 | |||
| 9030 | 		var tooltipSize = { | ||
| 9031 | width: vm.width, | ||
| 9032 | height: vm.height | ||
| 9033 | }; | ||
| 9034 | 		var pt = { | ||
| 9035 | x: vm.x, | ||
| 9036 | y: vm.y | ||
| 9037 | }; | ||
| 9038 | |||
| 9039 | // IE11/Edge does not like very small opacities, so snap to 0 | ||
| 9040 | var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; | ||
| 9041 | |||
| 9042 | // Truthy/falsey value for empty tooltip | ||
| 9043 | var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; | ||
| 9044 | |||
| 9045 | 		if (this._options.enabled && hasTooltipContent) { | ||
| 9046 | // Draw Background | ||
| 9047 | this.drawBackground(pt, vm, ctx, tooltipSize, opacity); | ||
| 9048 | |||
| 9049 | // Draw Title, Body, and Footer | ||
| 9050 | pt.x += vm.xPadding; | ||
| 9051 | pt.y += vm.yPadding; | ||
| 9052 | |||
| 9053 | // Titles | ||
| 9054 | this.drawTitle(pt, vm, ctx, opacity); | ||
| 9055 | |||
| 9056 | // Body | ||
| 9057 | this.drawBody(pt, vm, ctx, opacity); | ||
| 9058 | |||
| 9059 | // Footer | ||
| 9060 | this.drawFooter(pt, vm, ctx, opacity); | ||
| 9061 | } | ||
| 9062 | }, | ||
| 9063 | |||
| 9064 | /** | ||
| 9065 | * Handle an event | ||
| 9066 | * @private | ||
| 9067 | 	 * @param {IEvent} event - The event to handle | ||
| 9068 | 	 * @returns {Boolean} true if the tooltip changed | ||
| 9069 | */ | ||
| 9070 | 	handleEvent: function(e) { | ||
| 9071 | var me = this; | ||
| 9072 | var options = me._options; | ||
| 9073 | var changed = false; | ||
| 9074 | |||
| 9075 | me._lastActive = me._lastActive || []; | ||
| 9076 | |||
| 9077 | // Find Active Elements for tooltips | ||
| 9078 | 		if (e.type === 'mouseout') { | ||
| 9079 | me._active = []; | ||
| 9080 | 		} else { | ||
| 9081 | me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); | ||
| 9082 | } | ||
| 9083 | |||
| 9084 | // Remember Last Actives | ||
| 9085 | changed = !helpers.arrayEquals(me._active, me._lastActive); | ||
| 9086 | |||
| 9087 | // Only handle target event on tooltip change | ||
| 9088 | 		if (changed) { | ||
| 9089 | me._lastActive = me._active; | ||
| 9090 | |||
| 9091 | 			if (options.enabled || options.custom) { | ||
| 9092 | 				me._eventPosition = { | ||
| 9093 | x: e.x, | ||
| 9094 | y: e.y | ||
| 9095 | }; | ||
| 9096 | |||
| 9097 | me.update(true); | ||
| 9098 | me.pivot(); | ||
| 9099 | } | ||
| 9100 | } | ||
| 9101 | |||
| 9102 | return changed; | ||
| 9103 | } | ||
| 9104 | }); | ||
| 9105 | |||
| 9106 | /** | ||
| 9107 | * @namespace Chart.Tooltip.positioners | ||
| 9108 | */ | ||
| 9109 | exports.positioners = positioners; | ||
| 9110 | |||
| 9111 | |||
| 9112 | },{"26":26,"27":27,"46":46}],37:[function(require,module,exports){ | ||
| 9113 | 'use strict'; | ||
| 9114 | |||
| 9115 | var defaults = require(26); | ||
| 9116 | var Element = require(27); | ||
| 9117 | var helpers = require(46); | ||
| 9118 | |||
| 9119 | defaults._set('global', { | ||
| 9120 | 	elements: { | ||
| 9121 | 		arc: { | ||
| 9122 | backgroundColor: defaults.global.defaultColor, | ||
| 9123 | borderColor: '#fff', | ||
| 9124 | borderWidth: 2 | ||
| 9125 | } | ||
| 9126 | } | ||
| 9127 | }); | ||
| 9128 | |||
| 9129 | module.exports = Element.extend({ | ||
| 9130 | 	inLabelRange: function(mouseX) { | ||
| 9131 | var vm = this._view; | ||
| 9132 | |||
| 9133 | 		if (vm) { | ||
| 9134 | return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); | ||
| 9135 | } | ||
| 9136 | return false; | ||
| 9137 | }, | ||
| 9138 | |||
| 9139 | 	inRange: function(chartX, chartY) { | ||
| 9140 | var vm = this._view; | ||
| 9141 | |||
| 9142 | 		if (vm) { | ||
| 9143 | 			var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY}); | ||
| 9144 | var angle = pointRelativePosition.angle; | ||
| 9145 | var distance = pointRelativePosition.distance; | ||
| 9146 | |||
| 9147 | // Sanitise angle range | ||
| 9148 | var startAngle = vm.startAngle; | ||
| 9149 | var endAngle = vm.endAngle; | ||
| 9150 | 			while (endAngle < startAngle) { | ||
| 9151 | endAngle += 2.0 * Math.PI; | ||
| 9152 | } | ||
| 9153 | 			while (angle > endAngle) { | ||
| 9154 | angle -= 2.0 * Math.PI; | ||
| 9155 | } | ||
| 9156 | 			while (angle < startAngle) { | ||
| 9157 | angle += 2.0 * Math.PI; | ||
| 9158 | } | ||
| 9159 | |||
| 9160 | // Check if within the range of the open/close angle | ||
| 9161 | var betweenAngles = (angle >= startAngle && angle <= endAngle); | ||
| 9162 | var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); | ||
| 9163 | |||
| 9164 | return (betweenAngles && withinRadius); | ||
| 9165 | } | ||
| 9166 | return false; | ||
| 9167 | }, | ||
| 9168 | |||
| 9169 | 	getCenterPoint: function() { | ||
| 9170 | var vm = this._view; | ||
| 9171 | var halfAngle = (vm.startAngle + vm.endAngle) / 2; | ||
| 9172 | var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; | ||
| 9173 | 		return { | ||
| 9174 | x: vm.x + Math.cos(halfAngle) * halfRadius, | ||
| 9175 | y: vm.y + Math.sin(halfAngle) * halfRadius | ||
| 9176 | }; | ||
| 9177 | }, | ||
| 9178 | |||
| 9179 | 	getArea: function() { | ||
| 9180 | var vm = this._view; | ||
| 9181 | return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); | ||
| 9182 | }, | ||
| 9183 | |||
| 9184 | 	tooltipPosition: function() { | ||
| 9185 | var vm = this._view; | ||
| 9186 | var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); | ||
| 9187 | var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; | ||
| 9188 | |||
| 9189 | 		return { | ||
| 9190 | x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), | ||
| 9191 | y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) | ||
| 9192 | }; | ||
| 9193 | }, | ||
| 9194 | |||
| 9195 | 	draw: function() { | ||
| 9196 | var ctx = this._chart.ctx; | ||
| 9197 | var vm = this._view; | ||
| 9198 | var sA = vm.startAngle; | ||
| 9199 | var eA = vm.endAngle; | ||
| 9200 | |||
| 9201 | ctx.beginPath(); | ||
| 9202 | |||
| 9203 | ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); | ||
| 9204 | ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); | ||
| 9205 | |||
| 9206 | ctx.closePath(); | ||
| 9207 | ctx.strokeStyle = vm.borderColor; | ||
| 9208 | ctx.lineWidth = vm.borderWidth; | ||
| 9209 | |||
| 9210 | ctx.fillStyle = vm.backgroundColor; | ||
| 9211 | |||
| 9212 | ctx.fill(); | ||
| 9213 | ctx.lineJoin = 'bevel'; | ||
| 9214 | |||
| 9215 | 		if (vm.borderWidth) { | ||
| 9216 | ctx.stroke(); | ||
| 9217 | } | ||
| 9218 | } | ||
| 9219 | }); | ||
| 9220 | |||
| 9221 | },{"26":26,"27":27,"46":46}],38:[function(require,module,exports){ | ||
| 9222 | 'use strict'; | ||
| 9223 | |||
| 9224 | var defaults = require(26); | ||
| 9225 | var Element = require(27); | ||
| 9226 | var helpers = require(46); | ||
| 9227 | |||
| 9228 | var globalDefaults = defaults.global; | ||
| 9229 | |||
| 9230 | defaults._set('global', { | ||
| 9231 | 	elements: { | ||
| 9232 | 		line: { | ||
| 9233 | tension: 0.4, | ||
| 9234 | backgroundColor: globalDefaults.defaultColor, | ||
| 9235 | borderWidth: 3, | ||
| 9236 | borderColor: globalDefaults.defaultColor, | ||
| 9237 | borderCapStyle: 'butt', | ||
| 9238 | borderDash: [], | ||
| 9239 | borderDashOffset: 0.0, | ||
| 9240 | borderJoinStyle: 'miter', | ||
| 9241 | capBezierPoints: true, | ||
| 9242 | fill: true, // do we fill in the area between the line and its base axis | ||
| 9243 | } | ||
| 9244 | } | ||
| 9245 | }); | ||
| 9246 | |||
| 9247 | module.exports = Element.extend({ | ||
| 9248 | 	draw: function() { | ||
| 9249 | var me = this; | ||
| 9250 | var vm = me._view; | ||
| 9251 | var ctx = me._chart.ctx; | ||
| 9252 | var spanGaps = vm.spanGaps; | ||
| 9253 | var points = me._children.slice(); // clone array | ||
| 9254 | var globalOptionLineElements = globalDefaults.elements.line; | ||
| 9255 | var lastDrawnIndex = -1; | ||
| 9256 | var index, current, previous, currentVM; | ||
| 9257 | |||
| 9258 | // If we are looping, adding the first point again | ||
| 9259 | 		if (me._loop && points.length) { | ||
| 9260 | points.push(points[0]); | ||
| 9261 | } | ||
| 9262 | |||
| 9263 | ctx.save(); | ||
| 9264 | |||
| 9265 | // Stroke Line Options | ||
| 9266 | ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; | ||
| 9267 | |||
| 9268 | // IE 9 and 10 do not support line dash | ||
| 9269 | 		if (ctx.setLineDash) { | ||
| 9270 | ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); | ||
| 9271 | } | ||
| 9272 | |||
| 9273 | ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; | ||
| 9274 | ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; | ||
| 9275 | ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; | ||
| 9276 | ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; | ||
| 9277 | |||
| 9278 | // Stroke Line | ||
| 9279 | ctx.beginPath(); | ||
| 9280 | lastDrawnIndex = -1; | ||
| 9281 | |||
| 9282 | 		for (index = 0; index < points.length; ++index) { | ||
| 9283 | current = points[index]; | ||
| 9284 | previous = helpers.previousItem(points, index); | ||
| 9285 | currentVM = current._view; | ||
| 9286 | |||
| 9287 | // First point moves to it's starting position no matter what | ||
| 9288 | 			if (index === 0) { | ||
| 9289 | 				if (!currentVM.skip) { | ||
| 9290 | ctx.moveTo(currentVM.x, currentVM.y); | ||
| 9291 | lastDrawnIndex = index; | ||
| 9292 | } | ||
| 9293 | 			} else { | ||
| 9294 | previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; | ||
| 9295 | |||
| 9296 | 				if (!currentVM.skip) { | ||
| 9297 | 					if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { | ||
| 9298 | // There was a gap and this is the first point after the gap | ||
| 9299 | ctx.moveTo(currentVM.x, currentVM.y); | ||
| 9300 | 					} else { | ||
| 9301 | // Line to next point | ||
| 9302 | helpers.canvas.lineTo(ctx, previous._view, current._view); | ||
| 9303 | } | ||
| 9304 | lastDrawnIndex = index; | ||
| 9305 | } | ||
| 9306 | } | ||
| 9307 | } | ||
| 9308 | |||
| 9309 | ctx.stroke(); | ||
| 9310 | ctx.restore(); | ||
| 9311 | } | ||
| 9312 | }); | ||
| 9313 | |||
| 9314 | },{"26":26,"27":27,"46":46}],39:[function(require,module,exports){ | ||
| 9315 | 'use strict'; | ||
| 9316 | |||
| 9317 | var defaults = require(26); | ||
| 9318 | var Element = require(27); | ||
| 9319 | var helpers = require(46); | ||
| 9320 | |||
| 9321 | var defaultColor = defaults.global.defaultColor; | ||
| 9322 | |||
| 9323 | defaults._set('global', { | ||
| 9324 | 	elements: { | ||
| 9325 | 		point: { | ||
| 9326 | radius: 3, | ||
| 9327 | pointStyle: 'circle', | ||
| 9328 | backgroundColor: defaultColor, | ||
| 9329 | borderColor: defaultColor, | ||
| 9330 | borderWidth: 1, | ||
| 9331 | // Hover | ||
| 9332 | hitRadius: 1, | ||
| 9333 | hoverRadius: 4, | ||
| 9334 | hoverBorderWidth: 1 | ||
| 9335 | } | ||
| 9336 | } | ||
| 9337 | }); | ||
| 9338 | |||
| 9339 | function xRange(mouseX) { | ||
| 9340 | var vm = this._view; | ||
| 9341 | return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; | ||
| 9342 | } | ||
| 9343 | |||
| 9344 | function yRange(mouseY) { | ||
| 9345 | var vm = this._view; | ||
| 9346 | return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; | ||
| 9347 | } | ||
| 9348 | |||
| 9349 | module.exports = Element.extend({ | ||
| 9350 | 	inRange: function(mouseX, mouseY) { | ||
| 9351 | var vm = this._view; | ||
| 9352 | return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; | ||
| 9353 | }, | ||
| 9354 | |||
| 9355 | inLabelRange: xRange, | ||
| 9356 | inXRange: xRange, | ||
| 9357 | inYRange: yRange, | ||
| 9358 | |||
| 9359 | 	getCenterPoint: function() { | ||
| 9360 | var vm = this._view; | ||
| 9361 | 		return { | ||
| 9362 | x: vm.x, | ||
| 9363 | y: vm.y | ||
| 9364 | }; | ||
| 9365 | }, | ||
| 9366 | |||
| 9367 | 	getArea: function() { | ||
| 9368 | return Math.PI * Math.pow(this._view.radius, 2); | ||
| 9369 | }, | ||
| 9370 | |||
| 9371 | 	tooltipPosition: function() { | ||
| 9372 | var vm = this._view; | ||
| 9373 | 		return { | ||
| 9374 | x: vm.x, | ||
| 9375 | y: vm.y, | ||
| 9376 | padding: vm.radius + vm.borderWidth | ||
| 9377 | }; | ||
| 9378 | }, | ||
| 9379 | |||
| 9380 | 	draw: function(chartArea) { | ||
| 9381 | var vm = this._view; | ||
| 9382 | var model = this._model; | ||
| 9383 | var ctx = this._chart.ctx; | ||
| 9384 | var pointStyle = vm.pointStyle; | ||
| 9385 | var rotation = vm.rotation; | ||
| 9386 | var radius = vm.radius; | ||
| 9387 | var x = vm.x; | ||
| 9388 | var y = vm.y; | ||
| 9389 | var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) | ||
| 9390 | |||
| 9391 | 		if (vm.skip) { | ||
| 9392 | return; | ||
| 9393 | } | ||
| 9394 | |||
| 9395 | // Clipping for Points. | ||
| 9396 | 		if (chartArea === undefined || (model.x >= chartArea.left && chartArea.right * errMargin >= model.x && model.y >= chartArea.top && chartArea.bottom * errMargin >= model.y)) { | ||
| 9397 | ctx.strokeStyle = vm.borderColor || defaultColor; | ||
| 9398 | ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); | ||
| 9399 | ctx.fillStyle = vm.backgroundColor || defaultColor; | ||
| 9400 | helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); | ||
| 9401 | } | ||
| 9402 | } | ||
| 9403 | }); | ||
| 9404 | |||
| 9405 | },{"26":26,"27":27,"46":46}],40:[function(require,module,exports){ | ||
| 9406 | 'use strict'; | ||
| 9407 | |||
| 9408 | var defaults = require(26); | ||
| 9409 | var Element = require(27); | ||
| 9410 | |||
| 9411 | defaults._set('global', { | ||
| 9412 | 	elements: { | ||
| 9413 | 		rectangle: { | ||
| 9414 | backgroundColor: defaults.global.defaultColor, | ||
| 9415 | borderColor: defaults.global.defaultColor, | ||
| 9416 | borderSkipped: 'bottom', | ||
| 9417 | borderWidth: 0 | ||
| 9418 | } | ||
| 9419 | } | ||
| 9420 | }); | ||
| 9421 | |||
| 9422 | function isVertical(bar) { | ||
| 9423 | return bar._view.width !== undefined; | ||
| 9424 | } | ||
| 9425 | |||
| 9426 | /** | ||
| 9427 | * Helper function to get the bounds of the bar regardless of the orientation | ||
| 9428 |  * @param bar {Chart.Element.Rectangle} the bar | ||
| 9429 |  * @return {Bounds} bounds of the bar | ||
| 9430 | * @private | ||
| 9431 | */ | ||
| 9432 | function getBarBounds(bar) { | ||
| 9433 | var vm = bar._view; | ||
| 9434 | var x1, x2, y1, y2; | ||
| 9435 | |||
| 9436 | 	if (isVertical(bar)) { | ||
| 9437 | // vertical | ||
| 9438 | var halfWidth = vm.width / 2; | ||
| 9439 | x1 = vm.x - halfWidth; | ||
| 9440 | x2 = vm.x + halfWidth; | ||
| 9441 | y1 = Math.min(vm.y, vm.base); | ||
| 9442 | y2 = Math.max(vm.y, vm.base); | ||
| 9443 | 	} else { | ||
| 9444 | // horizontal bar | ||
| 9445 | var halfHeight = vm.height / 2; | ||
| 9446 | x1 = Math.min(vm.x, vm.base); | ||
| 9447 | x2 = Math.max(vm.x, vm.base); | ||
| 9448 | y1 = vm.y - halfHeight; | ||
| 9449 | y2 = vm.y + halfHeight; | ||
| 9450 | } | ||
| 9451 | |||
| 9452 | 	return { | ||
| 9453 | left: x1, | ||
| 9454 | top: y1, | ||
| 9455 | right: x2, | ||
| 9456 | bottom: y2 | ||
| 9457 | }; | ||
| 9458 | } | ||
| 9459 | |||
| 9460 | module.exports = Element.extend({ | ||
| 9461 | 	draw: function() { | ||
| 9462 | var ctx = this._chart.ctx; | ||
| 9463 | var vm = this._view; | ||
| 9464 | var left, right, top, bottom, signX, signY, borderSkipped; | ||
| 9465 | var borderWidth = vm.borderWidth; | ||
| 9466 | |||
| 9467 | 		if (!vm.horizontal) { | ||
| 9468 | // bar | ||
| 9469 | left = vm.x - vm.width / 2; | ||
| 9470 | right = vm.x + vm.width / 2; | ||
| 9471 | top = vm.y; | ||
| 9472 | bottom = vm.base; | ||
| 9473 | signX = 1; | ||
| 9474 | signY = bottom > top ? 1 : -1; | ||
| 9475 | borderSkipped = vm.borderSkipped || 'bottom'; | ||
| 9476 | 		} else { | ||
| 9477 | // horizontal bar | ||
| 9478 | left = vm.base; | ||
| 9479 | right = vm.x; | ||
| 9480 | top = vm.y - vm.height / 2; | ||
| 9481 | bottom = vm.y + vm.height / 2; | ||
| 9482 | signX = right > left ? 1 : -1; | ||
| 9483 | signY = 1; | ||
| 9484 | borderSkipped = vm.borderSkipped || 'left'; | ||
| 9485 | } | ||
| 9486 | |||
| 9487 | // Canvas doesn't allow us to stroke inside the width so we can | ||
| 9488 | // adjust the sizes to fit if we're setting a stroke on the line | ||
| 9489 | 		if (borderWidth) { | ||
| 9490 | // borderWidth shold be less than bar width and bar height. | ||
| 9491 | var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); | ||
| 9492 | borderWidth = borderWidth > barSize ? barSize : borderWidth; | ||
| 9493 | var halfStroke = borderWidth / 2; | ||
| 9494 | // Adjust borderWidth when bar top position is near vm.base(zero). | ||
| 9495 | var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0); | ||
| 9496 | var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0); | ||
| 9497 | var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0); | ||
| 9498 | var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0); | ||
| 9499 | // not become a vertical line? | ||
| 9500 | 			if (borderLeft !== borderRight) { | ||
| 9501 | top = borderTop; | ||
| 9502 | bottom = borderBottom; | ||
| 9503 | } | ||
| 9504 | // not become a horizontal line? | ||
| 9505 | 			if (borderTop !== borderBottom) { | ||
| 9506 | left = borderLeft; | ||
| 9507 | right = borderRight; | ||
| 9508 | } | ||
| 9509 | } | ||
| 9510 | |||
| 9511 | ctx.beginPath(); | ||
| 9512 | ctx.fillStyle = vm.backgroundColor; | ||
| 9513 | ctx.strokeStyle = vm.borderColor; | ||
| 9514 | ctx.lineWidth = borderWidth; | ||
| 9515 | |||
| 9516 | // Corner points, from bottom-left to bottom-right clockwise | ||
| 9517 | // | 1 2 | | ||
| 9518 | // | 0 3 | | ||
| 9519 | var corners = [ | ||
| 9520 | [left, bottom], | ||
| 9521 | [left, top], | ||
| 9522 | [right, top], | ||
| 9523 | [right, bottom] | ||
| 9524 | ]; | ||
| 9525 | |||
| 9526 | // Find first (starting) corner with fallback to 'bottom' | ||
| 9527 | var borders = ['bottom', 'left', 'top', 'right']; | ||
| 9528 | var startCorner = borders.indexOf(borderSkipped, 0); | ||
| 9529 | 		if (startCorner === -1) { | ||
| 9530 | startCorner = 0; | ||
| 9531 | } | ||
| 9532 | |||
| 9533 | 		function cornerAt(index) { | ||
| 9534 | return corners[(startCorner + index) % 4]; | ||
| 9535 | } | ||
| 9536 | |||
| 9537 | // Draw rectangle from 'startCorner' | ||
| 9538 | var corner = cornerAt(0); | ||
| 9539 | ctx.moveTo(corner[0], corner[1]); | ||
| 9540 | |||
| 9541 | 		for (var i = 1; i < 4; i++) { | ||
| 9542 | corner = cornerAt(i); | ||
| 9543 | ctx.lineTo(corner[0], corner[1]); | ||
| 9544 | } | ||
| 9545 | |||
| 9546 | ctx.fill(); | ||
| 9547 | 		if (borderWidth) { | ||
| 9548 | ctx.stroke(); | ||
| 9549 | } | ||
| 9550 | }, | ||
| 9551 | |||
| 9552 | 	height: function() { | ||
| 9553 | var vm = this._view; | ||
| 9554 | return vm.base - vm.y; | ||
| 9555 | }, | ||
| 9556 | |||
| 9557 | 	inRange: function(mouseX, mouseY) { | ||
| 9558 | var inRange = false; | ||
| 9559 | |||
| 9560 | 		if (this._view) { | ||
| 9561 | var bounds = getBarBounds(this); | ||
| 9562 | inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; | ||
| 9563 | } | ||
| 9564 | |||
| 9565 | return inRange; | ||
| 9566 | }, | ||
| 9567 | |||
| 9568 | 	inLabelRange: function(mouseX, mouseY) { | ||
| 9569 | var me = this; | ||
| 9570 | 		if (!me._view) { | ||
| 9571 | return false; | ||
| 9572 | } | ||
| 9573 | |||
| 9574 | var inRange = false; | ||
| 9575 | var bounds = getBarBounds(me); | ||
| 9576 | |||
| 9577 | 		if (isVertical(me)) { | ||
| 9578 | inRange = mouseX >= bounds.left && mouseX <= bounds.right; | ||
| 9579 | 		} else { | ||
| 9580 | inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; | ||
| 9581 | } | ||
| 9582 | |||
| 9583 | return inRange; | ||
| 9584 | }, | ||
| 9585 | |||
| 9586 | 	inXRange: function(mouseX) { | ||
| 9587 | var bounds = getBarBounds(this); | ||
| 9588 | return mouseX >= bounds.left && mouseX <= bounds.right; | ||
| 9589 | }, | ||
| 9590 | |||
| 9591 | 	inYRange: function(mouseY) { | ||
| 9592 | var bounds = getBarBounds(this); | ||
| 9593 | return mouseY >= bounds.top && mouseY <= bounds.bottom; | ||
| 9594 | }, | ||
| 9595 | |||
| 9596 | 	getCenterPoint: function() { | ||
| 9597 | var vm = this._view; | ||
| 9598 | var x, y; | ||
| 9599 | 		if (isVertical(this)) { | ||
| 9600 | x = vm.x; | ||
| 9601 | y = (vm.y + vm.base) / 2; | ||
| 9602 | 		} else { | ||
| 9603 | x = (vm.x + vm.base) / 2; | ||
| 9604 | y = vm.y; | ||
| 9605 | } | ||
| 9606 | |||
| 9607 | 		return {x: x, y: y}; | ||
| 9608 | }, | ||
| 9609 | |||
| 9610 | 	getArea: function() { | ||
| 9611 | var vm = this._view; | ||
| 9612 | return vm.width * Math.abs(vm.y - vm.base); | ||
| 9613 | }, | ||
| 9614 | |||
| 9615 | 	tooltipPosition: function() { | ||
| 9616 | var vm = this._view; | ||
| 9617 | 		return { | ||
| 9618 | x: vm.x, | ||
| 9619 | y: vm.y | ||
| 9620 | }; | ||
| 9621 | } | ||
| 9622 | }); | ||
| 9623 | |||
| 9624 | },{"26":26,"27":27}],41:[function(require,module,exports){ | ||
| 9625 | 'use strict'; | ||
| 9626 | |||
| 9627 | module.exports = {}; | ||
| 9628 | module.exports.Arc = require(37); | ||
| 9629 | module.exports.Line = require(38); | ||
| 9630 | module.exports.Point = require(39); | ||
| 9631 | module.exports.Rectangle = require(40); | ||
| 9632 | |||
| 9633 | },{"37":37,"38":38,"39":39,"40":40}],42:[function(require,module,exports){ | ||
| 9634 | 'use strict'; | ||
| 9635 | |||
| 9636 | var helpers = require(43); | ||
| 9637 | |||
| 9638 | /** | ||
| 9639 | * @namespace Chart.helpers.canvas | ||
| 9640 | */ | ||
| 9641 | var exports = module.exports = { | ||
| 9642 | /** | ||
| 9643 | * Clears the entire canvas associated to the given `chart`. | ||
| 9644 | 	 * @param {Chart} chart - The chart for which to clear the canvas. | ||
| 9645 | */ | ||
| 9646 | 	clear: function(chart) { | ||
| 9647 | chart.ctx.clearRect(0, 0, chart.width, chart.height); | ||
| 9648 | }, | ||
| 9649 | |||
| 9650 | /** | ||
| 9651 | * Creates a "path" for a rectangle with rounded corners at position (x, y) with a | ||
| 9652 | * given size (width, height) and the same `radius` for all corners. | ||
| 9653 | 	 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. | ||
| 9654 | 	 * @param {Number} x - The x axis of the coordinate for the rectangle starting point. | ||
| 9655 | 	 * @param {Number} y - The y axis of the coordinate for the rectangle starting point. | ||
| 9656 | 	 * @param {Number} width - The rectangle's width. | ||
| 9657 | 	 * @param {Number} height - The rectangle's height. | ||
| 9658 | 	 * @param {Number} radius - The rounded amount (in pixels) for the four corners. | ||
| 9659 | * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? | ||
| 9660 | */ | ||
| 9661 | 	roundedRect: function(ctx, x, y, width, height, radius) { | ||
| 9662 | 		if (radius) { | ||
| 9663 | // NOTE(SB) `epsilon` helps to prevent minor artifacts appearing | ||
| 9664 | // on Chrome when `r` is exactly half the height or the width. | ||
| 9665 | var epsilon = 0.0000001; | ||
| 9666 | var r = Math.min(radius, (height / 2) - epsilon, (width / 2) - epsilon); | ||
| 9667 | |||
| 9668 | ctx.moveTo(x + r, y); | ||
| 9669 | ctx.lineTo(x + width - r, y); | ||
| 9670 | ctx.arcTo(x + width, y, x + width, y + r, r); | ||
| 9671 | ctx.lineTo(x + width, y + height - r); | ||
| 9672 | ctx.arcTo(x + width, y + height, x + width - r, y + height, r); | ||
| 9673 | ctx.lineTo(x + r, y + height); | ||
| 9674 | ctx.arcTo(x, y + height, x, y + height - r, r); | ||
| 9675 | ctx.lineTo(x, y + r); | ||
| 9676 | ctx.arcTo(x, y, x + r, y, r); | ||
| 9677 | ctx.closePath(); | ||
| 9678 | ctx.moveTo(x, y); | ||
| 9679 | 		} else { | ||
| 9680 | ctx.rect(x, y, width, height); | ||
| 9681 | } | ||
| 9682 | }, | ||
| 9683 | |||
| 9684 | 	drawPoint: function(ctx, style, radius, x, y, rotation) { | ||
| 9685 | var type, edgeLength, xOffset, yOffset, height, size; | ||
| 9686 | rotation = rotation || 0; | ||
| 9687 | |||
| 9688 | 		if (style && typeof style === 'object') { | ||
| 9689 | type = style.toString(); | ||
| 9690 | 			if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { | ||
| 9691 | ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height); | ||
| 9692 | return; | ||
| 9693 | } | ||
| 9694 | } | ||
| 9695 | |||
| 9696 | 		if (isNaN(radius) || radius <= 0) { | ||
| 9697 | return; | ||
| 9698 | } | ||
| 9699 | |||
| 9700 | ctx.save(); | ||
| 9701 | ctx.translate(x, y); | ||
| 9702 | ctx.rotate(rotation * Math.PI / 180); | ||
| 9703 | ctx.beginPath(); | ||
| 9704 | |||
| 9705 | 		switch (style) { | ||
| 9706 | // Default includes circle | ||
| 9707 | default: | ||
| 9708 | ctx.arc(0, 0, radius, 0, Math.PI * 2); | ||
| 9709 | ctx.closePath(); | ||
| 9710 | break; | ||
| 9711 | case 'triangle': | ||
| 9712 | edgeLength = 3 * radius / Math.sqrt(3); | ||
| 9713 | height = edgeLength * Math.sqrt(3) / 2; | ||
| 9714 | ctx.moveTo(-edgeLength / 2, height / 3); | ||
| 9715 | ctx.lineTo(edgeLength / 2, height / 3); | ||
| 9716 | ctx.lineTo(0, -2 * height / 3); | ||
| 9717 | ctx.closePath(); | ||
| 9718 | break; | ||
| 9719 | case 'rect': | ||
| 9720 | size = 1 / Math.SQRT2 * radius; | ||
| 9721 | ctx.rect(-size, -size, 2 * size, 2 * size); | ||
| 9722 | break; | ||
| 9723 | case 'rectRounded': | ||
| 9724 | var offset = radius / Math.SQRT2; | ||
| 9725 | var leftX = -offset; | ||
| 9726 | var topY = -offset; | ||
| 9727 | var sideSize = Math.SQRT2 * radius; | ||
| 9728 | |||
| 9729 | // NOTE(SB) the rounded rect implementation changed to use `arcTo` | ||
| 9730 | // instead of `quadraticCurveTo` since it generates better results | ||
| 9731 | // when rect is almost a circle. 0.425 (instead of 0.5) produces | ||
| 9732 | // results visually closer to the previous impl. | ||
| 9733 | this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius * 0.425); | ||
| 9734 | break; | ||
| 9735 | case 'rectRot': | ||
| 9736 | size = 1 / Math.SQRT2 * radius; | ||
| 9737 | ctx.moveTo(-size, 0); | ||
| 9738 | ctx.lineTo(0, size); | ||
| 9739 | ctx.lineTo(size, 0); | ||
| 9740 | ctx.lineTo(0, -size); | ||
| 9741 | ctx.closePath(); | ||
| 9742 | break; | ||
| 9743 | case 'cross': | ||
| 9744 | ctx.moveTo(0, radius); | ||
| 9745 | ctx.lineTo(0, -radius); | ||
| 9746 | ctx.moveTo(-radius, 0); | ||
| 9747 | ctx.lineTo(radius, 0); | ||
| 9748 | break; | ||
| 9749 | case 'crossRot': | ||
| 9750 | xOffset = Math.cos(Math.PI / 4) * radius; | ||
| 9751 | yOffset = Math.sin(Math.PI / 4) * radius; | ||
| 9752 | ctx.moveTo(-xOffset, -yOffset); | ||
| 9753 | ctx.lineTo(xOffset, yOffset); | ||
| 9754 | ctx.moveTo(-xOffset, yOffset); | ||
| 9755 | ctx.lineTo(xOffset, -yOffset); | ||
| 9756 | break; | ||
| 9757 | case 'star': | ||
| 9758 | ctx.moveTo(0, radius); | ||
| 9759 | ctx.lineTo(0, -radius); | ||
| 9760 | ctx.moveTo(-radius, 0); | ||
| 9761 | ctx.lineTo(radius, 0); | ||
| 9762 | xOffset = Math.cos(Math.PI / 4) * radius; | ||
| 9763 | yOffset = Math.sin(Math.PI / 4) * radius; | ||
| 9764 | ctx.moveTo(-xOffset, -yOffset); | ||
| 9765 | ctx.lineTo(xOffset, yOffset); | ||
| 9766 | ctx.moveTo(-xOffset, yOffset); | ||
| 9767 | ctx.lineTo(xOffset, -yOffset); | ||
| 9768 | break; | ||
| 9769 | case 'line': | ||
| 9770 | ctx.moveTo(-radius, 0); | ||
| 9771 | ctx.lineTo(radius, 0); | ||
| 9772 | break; | ||
| 9773 | case 'dash': | ||
| 9774 | ctx.moveTo(0, 0); | ||
| 9775 | ctx.lineTo(radius, 0); | ||
| 9776 | break; | ||
| 9777 | } | ||
| 9778 | |||
| 9779 | ctx.fill(); | ||
| 9780 | ctx.stroke(); | ||
| 9781 | ctx.restore(); | ||
| 9782 | }, | ||
| 9783 | |||
| 9784 | 	clipArea: function(ctx, area) { | ||
| 9785 | ctx.save(); | ||
| 9786 | ctx.beginPath(); | ||
| 9787 | ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); | ||
| 9788 | ctx.clip(); | ||
| 9789 | }, | ||
| 9790 | |||
| 9791 | 	unclipArea: function(ctx) { | ||
| 9792 | ctx.restore(); | ||
| 9793 | }, | ||
| 9794 | |||
| 9795 | 	lineTo: function(ctx, previous, target, flip) { | ||
| 9796 | 		if (target.steppedLine) { | ||
| 9797 | 			if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) { | ||
| 9798 | ctx.lineTo(previous.x, target.y); | ||
| 9799 | 			} else { | ||
| 9800 | ctx.lineTo(target.x, previous.y); | ||
| 9801 | } | ||
| 9802 | ctx.lineTo(target.x, target.y); | ||
| 9803 | return; | ||
| 9804 | } | ||
| 9805 | |||
| 9806 | 		if (!target.tension) { | ||
| 9807 | ctx.lineTo(target.x, target.y); | ||
| 9808 | return; | ||
| 9809 | } | ||
| 9810 | |||
| 9811 | ctx.bezierCurveTo( | ||
| 9812 | flip ? previous.controlPointPreviousX : previous.controlPointNextX, | ||
| 9813 | flip ? previous.controlPointPreviousY : previous.controlPointNextY, | ||
| 9814 | flip ? target.controlPointNextX : target.controlPointPreviousX, | ||
| 9815 | flip ? target.controlPointNextY : target.controlPointPreviousY, | ||
| 9816 | target.x, | ||
| 9817 | target.y); | ||
| 9818 | } | ||
| 9819 | }; | ||
| 9820 | |||
| 9821 | // DEPRECATIONS | ||
| 9822 | |||
| 9823 | /** | ||
| 9824 | * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. | ||
| 9825 | * @namespace Chart.helpers.clear | ||
| 9826 | * @deprecated since version 2.7.0 | ||
| 9827 | * @todo remove at version 3 | ||
| 9828 | * @private | ||
| 9829 | */ | ||
| 9830 | helpers.clear = exports.clear; | ||
| 9831 | |||
| 9832 | /** | ||
| 9833 | * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. | ||
| 9834 | * @namespace Chart.helpers.drawRoundedRectangle | ||
| 9835 | * @deprecated since version 2.7.0 | ||
| 9836 | * @todo remove at version 3 | ||
| 9837 | * @private | ||
| 9838 | */ | ||
| 9839 | helpers.drawRoundedRectangle = function(ctx) { | ||
| 9840 | ctx.beginPath(); | ||
| 9841 | exports.roundedRect.apply(exports, arguments); | ||
| 9842 | }; | ||
| 9843 | |||
| 9844 | },{"43":43}],43:[function(require,module,exports){ | ||
| 9845 | 'use strict'; | ||
| 9846 | |||
| 9847 | /** | ||
| 9848 | * @namespace Chart.helpers | ||
| 9849 | */ | ||
| 9850 | var helpers = { | ||
| 9851 | /** | ||
| 9852 | * An empty function that can be used, for example, for optional callback. | ||
| 9853 | */ | ||
| 9854 | 	noop: function() {}, | ||
| 9855 | |||
| 9856 | /** | ||
| 9857 | * Returns a unique id, sequentially generated from a global variable. | ||
| 9858 | 	 * @returns {Number} | ||
| 9859 | * @function | ||
| 9860 | */ | ||
| 9861 | 	uid: (function() { | ||
| 9862 | var id = 0; | ||
| 9863 | 		return function() { | ||
| 9864 | return id++; | ||
| 9865 | }; | ||
| 9866 | }()), | ||
| 9867 | |||
| 9868 | /** | ||
| 9869 | * Returns true if `value` is neither null nor undefined, else returns false. | ||
| 9870 | 	 * @param {*} value - The value to test. | ||
| 9871 | 	 * @returns {Boolean} | ||
| 9872 | * @since 2.7.0 | ||
| 9873 | */ | ||
| 9874 | 	isNullOrUndef: function(value) { | ||
| 9875 | return value === null || typeof value === 'undefined'; | ||
| 9876 | }, | ||
| 9877 | |||
| 9878 | /** | ||
| 9879 | * Returns true if `value` is an array, else returns false. | ||
| 9880 | 	 * @param {*} value - The value to test. | ||
| 9881 | 	 * @returns {Boolean} | ||
| 9882 | * @function | ||
| 9883 | */ | ||
| 9884 | 	isArray: Array.isArray ? Array.isArray : function(value) { | ||
| 9885 | return Object.prototype.toString.call(value) === '[object Array]'; | ||
| 9886 | }, | ||
| 9887 | |||
| 9888 | /** | ||
| 9889 | * Returns true if `value` is an object (excluding null), else returns false. | ||
| 9890 | 	 * @param {*} value - The value to test. | ||
| 9891 | 	 * @returns {Boolean} | ||
| 9892 | * @since 2.7.0 | ||
| 9893 | */ | ||
| 9894 | 	isObject: function(value) { | ||
| 9895 | return value !== null && Object.prototype.toString.call(value) === '[object Object]'; | ||
| 9896 | }, | ||
| 9897 | |||
| 9898 | /** | ||
| 9899 | * Returns `value` if defined, else returns `defaultValue`. | ||
| 9900 | 	 * @param {*} value - The value to return if defined. | ||
| 9901 | 	 * @param {*} defaultValue - The value to return if `value` is undefined. | ||
| 9902 | 	 * @returns {*} | ||
| 9903 | */ | ||
| 9904 | 	valueOrDefault: function(value, defaultValue) { | ||
| 9905 | return typeof value === 'undefined' ? defaultValue : value; | ||
| 9906 | }, | ||
| 9907 | |||
| 9908 | /** | ||
| 9909 | * Returns value at the given `index` in array if defined, else returns `defaultValue`. | ||
| 9910 | 	 * @param {Array} value - The array to lookup for value at `index`. | ||
| 9911 | 	 * @param {Number} index - The index in `value` to lookup for value. | ||
| 9912 | 	 * @param {*} defaultValue - The value to return if `value[index]` is undefined. | ||
| 9913 | 	 * @returns {*} | ||
| 9914 | */ | ||
| 9915 | 	valueAtIndexOrDefault: function(value, index, defaultValue) { | ||
| 9916 | return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); | ||
| 9917 | }, | ||
| 9918 | |||
| 9919 | /** | ||
| 9920 | * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the | ||
| 9921 | * value returned by `fn`. If `fn` is not a function, this method returns undefined. | ||
| 9922 | 	 * @param {Function} fn - The function to call. | ||
| 9923 | 	 * @param {Array|undefined|null} args - The arguments with which `fn` should be called. | ||
| 9924 | 	 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. | ||
| 9925 | 	 * @returns {*} | ||
| 9926 | */ | ||
| 9927 | 	callback: function(fn, args, thisArg) { | ||
| 9928 | 		if (fn && typeof fn.call === 'function') { | ||
| 9929 | return fn.apply(thisArg, args); | ||
| 9930 | } | ||
| 9931 | }, | ||
| 9932 | |||
| 9933 | /** | ||
| 9934 | * Note(SB) for performance sake, this method should only be used when loopable type | ||
| 9935 | * is unknown or in none intensive code (not called often and small loopable). Else | ||
| 9936 | * it's preferable to use a regular for() loop and save extra function calls. | ||
| 9937 | 	 * @param {Object|Array} loopable - The object or array to be iterated. | ||
| 9938 | 	 * @param {Function} fn - The function to call for each item. | ||
| 9939 | 	 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. | ||
| 9940 | 	 * @param {Boolean} [reverse] - If true, iterates backward on the loopable. | ||
| 9941 | */ | ||
| 9942 | 	each: function(loopable, fn, thisArg, reverse) { | ||
| 9943 | var i, len, keys; | ||
| 9944 | 		if (helpers.isArray(loopable)) { | ||
| 9945 | len = loopable.length; | ||
| 9946 | 			if (reverse) { | ||
| 9947 | 				for (i = len - 1; i >= 0; i--) { | ||
| 9948 | fn.call(thisArg, loopable[i], i); | ||
| 9949 | } | ||
| 9950 | 			} else { | ||
| 9951 | 				for (i = 0; i < len; i++) { | ||
| 9952 | fn.call(thisArg, loopable[i], i); | ||
| 9953 | } | ||
| 9954 | } | ||
| 9955 | 		} else if (helpers.isObject(loopable)) { | ||
| 9956 | keys = Object.keys(loopable); | ||
| 9957 | len = keys.length; | ||
| 9958 | 			for (i = 0; i < len; i++) { | ||
| 9959 | fn.call(thisArg, loopable[keys[i]], keys[i]); | ||
| 9960 | } | ||
| 9961 | } | ||
| 9962 | }, | ||
| 9963 | |||
| 9964 | /** | ||
| 9965 | * Returns true if the `a0` and `a1` arrays have the same content, else returns false. | ||
| 9966 | * @see http://stackoverflow.com/a/14853974 | ||
| 9967 | 	 * @param {Array} a0 - The array to compare | ||
| 9968 | 	 * @param {Array} a1 - The array to compare | ||
| 9969 | 	 * @returns {Boolean} | ||
| 9970 | */ | ||
| 9971 | 	arrayEquals: function(a0, a1) { | ||
| 9972 | var i, ilen, v0, v1; | ||
| 9973 | |||
| 9974 | 		if (!a0 || !a1 || a0.length !== a1.length) { | ||
| 9975 | return false; | ||
| 9976 | } | ||
| 9977 | |||
| 9978 | 		for (i = 0, ilen = a0.length; i < ilen; ++i) { | ||
| 9979 | v0 = a0[i]; | ||
| 9980 | v1 = a1[i]; | ||
| 9981 | |||
| 9982 | 			if (v0 instanceof Array && v1 instanceof Array) { | ||
| 9983 | 				if (!helpers.arrayEquals(v0, v1)) { | ||
| 9984 | return false; | ||
| 9985 | } | ||
| 9986 | 			} else if (v0 !== v1) { | ||
| 9987 | 				// NOTE: two different object instances will never be equal: {x:20} != {x:20} | ||
| 9988 | return false; | ||
| 9989 | } | ||
| 9990 | } | ||
| 9991 | |||
| 9992 | return true; | ||
| 9993 | }, | ||
| 9994 | |||
| 9995 | /** | ||
| 9996 | * Returns a deep copy of `source` without keeping references on objects and arrays. | ||
| 9997 | 	 * @param {*} source - The value to clone. | ||
| 9998 | 	 * @returns {*} | ||
| 9999 | */ | ||
| 10000 | 	clone: function(source) { | ||
| 10001 | 		if (helpers.isArray(source)) { | ||
| 10002 | return source.map(helpers.clone); | ||
| 10003 | } | ||
| 10004 | |||
| 10005 | 		if (helpers.isObject(source)) { | ||
| 10006 | 			var target = {}; | ||
| 10007 | var keys = Object.keys(source); | ||
| 10008 | var klen = keys.length; | ||
| 10009 | var k = 0; | ||
| 10010 | |||
| 10011 | 			for (; k < klen; ++k) { | ||
| 10012 | target[keys[k]] = helpers.clone(source[keys[k]]); | ||
| 10013 | } | ||
| 10014 | |||
| 10015 | return target; | ||
| 10016 | } | ||
| 10017 | |||
| 10018 | return source; | ||
| 10019 | }, | ||
| 10020 | |||
| 10021 | /** | ||
| 10022 | * The default merger when Chart.helpers.merge is called without merger option. | ||
| 10023 | * Note(SB): this method is also used by configMerge and scaleMerge as fallback. | ||
| 10024 | * @private | ||
| 10025 | */ | ||
| 10026 | 	_merger: function(key, target, source, options) { | ||
| 10027 | var tval = target[key]; | ||
| 10028 | var sval = source[key]; | ||
| 10029 | |||
| 10030 | 		if (helpers.isObject(tval) && helpers.isObject(sval)) { | ||
| 10031 | helpers.merge(tval, sval, options); | ||
| 10032 | 		} else { | ||
| 10033 | target[key] = helpers.clone(sval); | ||
| 10034 | } | ||
| 10035 | }, | ||
| 10036 | |||
| 10037 | /** | ||
| 10038 | * Merges source[key] in target[key] only if target[key] is undefined. | ||
| 10039 | * @private | ||
| 10040 | */ | ||
| 10041 | 	_mergerIf: function(key, target, source) { | ||
| 10042 | var tval = target[key]; | ||
| 10043 | var sval = source[key]; | ||
| 10044 | |||
| 10045 | 		if (helpers.isObject(tval) && helpers.isObject(sval)) { | ||
| 10046 | helpers.mergeIf(tval, sval); | ||
| 10047 | 		} else if (!target.hasOwnProperty(key)) { | ||
| 10048 | target[key] = helpers.clone(sval); | ||
| 10049 | } | ||
| 10050 | }, | ||
| 10051 | |||
| 10052 | /** | ||
| 10053 | * Recursively deep copies `source` properties into `target` with the given `options`. | ||
| 10054 | * IMPORTANT: `target` is not cloned and will be updated with `source` properties. | ||
| 10055 | 	 * @param {Object} target - The target object in which all sources are merged into. | ||
| 10056 | 	 * @param {Object|Array(Object)} source - Object(s) to merge into `target`. | ||
| 10057 | 	 * @param {Object} [options] - Merging options: | ||
| 10058 | 	 * @param {Function} [options.merger] - The merge method (key, target, source, options) | ||
| 10059 | 	 * @returns {Object} The `target` object. | ||
| 10060 | */ | ||
| 10061 | 	merge: function(target, source, options) { | ||
| 10062 | var sources = helpers.isArray(source) ? source : [source]; | ||
| 10063 | var ilen = sources.length; | ||
| 10064 | var merge, i, keys, klen, k; | ||
| 10065 | |||
| 10066 | 		if (!helpers.isObject(target)) { | ||
| 10067 | return target; | ||
| 10068 | } | ||
| 10069 | |||
| 10070 | 		options = options || {}; | ||
| 10071 | merge = options.merger || helpers._merger; | ||
| 10072 | |||
| 10073 | 		for (i = 0; i < ilen; ++i) { | ||
| 10074 | source = sources[i]; | ||
| 10075 | 			if (!helpers.isObject(source)) { | ||
| 10076 | continue; | ||
| 10077 | } | ||
| 10078 | |||
| 10079 | keys = Object.keys(source); | ||
| 10080 | 			for (k = 0, klen = keys.length; k < klen; ++k) { | ||
| 10081 | merge(keys[k], target, source, options); | ||
| 10082 | } | ||
| 10083 | } | ||
| 10084 | |||
| 10085 | return target; | ||
| 10086 | }, | ||
| 10087 | |||
| 10088 | /** | ||
| 10089 | * Recursively deep copies `source` properties into `target` *only* if not defined in target. | ||
| 10090 | * IMPORTANT: `target` is not cloned and will be updated with `source` properties. | ||
| 10091 | 	 * @param {Object} target - The target object in which all sources are merged into. | ||
| 10092 | 	 * @param {Object|Array(Object)} source - Object(s) to merge into `target`. | ||
| 10093 | 	 * @returns {Object} The `target` object. | ||
| 10094 | */ | ||
| 10095 | 	mergeIf: function(target, source) { | ||
| 10096 | 		return helpers.merge(target, source, {merger: helpers._mergerIf}); | ||
| 10097 | }, | ||
| 10098 | |||
| 10099 | /** | ||
| 10100 | * Applies the contents of two or more objects together into the first object. | ||
| 10101 | 	 * @param {Object} target - The target object in which all objects are merged into. | ||
| 10102 | 	 * @param {Object} arg1 - Object containing additional properties to merge in target. | ||
| 10103 | 	 * @param {Object} argN - Additional objects containing properties to merge in target. | ||
| 10104 | 	 * @returns {Object} The `target` object. | ||
| 10105 | */ | ||
| 10106 | 	extend: function(target) { | ||
| 10107 | 		var setFn = function(value, key) { | ||
| 10108 | target[key] = value; | ||
| 10109 | }; | ||
| 10110 | 		for (var i = 1, ilen = arguments.length; i < ilen; ++i) { | ||
| 10111 | helpers.each(arguments[i], setFn); | ||
| 10112 | } | ||
| 10113 | return target; | ||
| 10114 | }, | ||
| 10115 | |||
| 10116 | /** | ||
| 10117 | * Basic javascript inheritance based on the model created in Backbone.js | ||
| 10118 | */ | ||
| 10119 | 	inherits: function(extensions) { | ||
| 10120 | var me = this; | ||
| 10121 | 		var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { | ||
| 10122 | return me.apply(this, arguments); | ||
| 10123 | }; | ||
| 10124 | |||
| 10125 | 		var Surrogate = function() { | ||
| 10126 | this.constructor = ChartElement; | ||
| 10127 | }; | ||
| 10128 | |||
| 10129 | Surrogate.prototype = me.prototype; | ||
| 10130 | ChartElement.prototype = new Surrogate(); | ||
| 10131 | ChartElement.extend = helpers.inherits; | ||
| 10132 | |||
| 10133 | 		if (extensions) { | ||
| 10134 | helpers.extend(ChartElement.prototype, extensions); | ||
| 10135 | } | ||
| 10136 | |||
| 10137 | ChartElement.__super__ = me.prototype; | ||
| 10138 | return ChartElement; | ||
| 10139 | } | ||
| 10140 | }; | ||
| 10141 | |||
| 10142 | module.exports = helpers; | ||
| 10143 | |||
| 10144 | // DEPRECATIONS | ||
| 10145 | |||
| 10146 | /** | ||
| 10147 | * Provided for backward compatibility, use Chart.helpers.callback instead. | ||
| 10148 | * @function Chart.helpers.callCallback | ||
| 10149 | * @deprecated since version 2.6.0 | ||
| 10150 | * @todo remove at version 3 | ||
| 10151 | * @private | ||
| 10152 | */ | ||
| 10153 | helpers.callCallback = helpers.callback; | ||
| 10154 | |||
| 10155 | /** | ||
| 10156 | * Provided for backward compatibility, use Array.prototype.indexOf instead. | ||
| 10157 | * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ | ||
| 10158 | * @function Chart.helpers.indexOf | ||
| 10159 | * @deprecated since version 2.7.0 | ||
| 10160 | * @todo remove at version 3 | ||
| 10161 | * @private | ||
| 10162 | */ | ||
| 10163 | helpers.indexOf = function(array, item, fromIndex) { | ||
| 10164 | return Array.prototype.indexOf.call(array, item, fromIndex); | ||
| 10165 | }; | ||
| 10166 | |||
| 10167 | /** | ||
| 10168 | * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. | ||
| 10169 | * @function Chart.helpers.getValueOrDefault | ||
| 10170 | * @deprecated since version 2.7.0 | ||
| 10171 | * @todo remove at version 3 | ||
| 10172 | * @private | ||
| 10173 | */ | ||
| 10174 | helpers.getValueOrDefault = helpers.valueOrDefault; | ||
| 10175 | |||
| 10176 | /** | ||
| 10177 | * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. | ||
| 10178 | * @function Chart.helpers.getValueAtIndexOrDefault | ||
| 10179 | * @deprecated since version 2.7.0 | ||
| 10180 | * @todo remove at version 3 | ||
| 10181 | * @private | ||
| 10182 | */ | ||
| 10183 | helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; | ||
| 10184 | |||
| 10185 | },{}],44:[function(require,module,exports){ | ||
| 10186 | 'use strict'; | ||
| 10187 | |||
| 10188 | var helpers = require(43); | ||
| 10189 | |||
| 10190 | /** | ||
| 10191 | * Easing functions adapted from Robert Penner's easing equations. | ||
| 10192 | * @namespace Chart.helpers.easingEffects | ||
| 10193 | * @see http://www.robertpenner.com/easing/ | ||
| 10194 | */ | ||
| 10195 | var effects = { | ||
| 10196 | 	linear: function(t) { | ||
| 10197 | return t; | ||
| 10198 | }, | ||
| 10199 | |||
| 10200 | 	easeInQuad: function(t) { | ||
| 10201 | return t * t; | ||
| 10202 | }, | ||
| 10203 | |||
| 10204 | 	easeOutQuad: function(t) { | ||
| 10205 | return -t * (t - 2); | ||
| 10206 | }, | ||
| 10207 | |||
| 10208 | 	easeInOutQuad: function(t) { | ||
| 10209 | 		if ((t /= 0.5) < 1) { | ||
| 10210 | return 0.5 * t * t; | ||
| 10211 | } | ||
| 10212 | return -0.5 * ((--t) * (t - 2) - 1); | ||
| 10213 | }, | ||
| 10214 | |||
| 10215 | 	easeInCubic: function(t) { | ||
| 10216 | return t * t * t; | ||
| 10217 | }, | ||
| 10218 | |||
| 10219 | 	easeOutCubic: function(t) { | ||
| 10220 | return (t = t - 1) * t * t + 1; | ||
| 10221 | }, | ||
| 10222 | |||
| 10223 | 	easeInOutCubic: function(t) { | ||
| 10224 | 		if ((t /= 0.5) < 1) { | ||
| 10225 | return 0.5 * t * t * t; | ||
| 10226 | } | ||
| 10227 | return 0.5 * ((t -= 2) * t * t + 2); | ||
| 10228 | }, | ||
| 10229 | |||
| 10230 | 	easeInQuart: function(t) { | ||
| 10231 | return t * t * t * t; | ||
| 10232 | }, | ||
| 10233 | |||
| 10234 | 	easeOutQuart: function(t) { | ||
| 10235 | return -((t = t - 1) * t * t * t - 1); | ||
| 10236 | }, | ||
| 10237 | |||
| 10238 | 	easeInOutQuart: function(t) { | ||
| 10239 | 		if ((t /= 0.5) < 1) { | ||
| 10240 | return 0.5 * t * t * t * t; | ||
| 10241 | } | ||
| 10242 | return -0.5 * ((t -= 2) * t * t * t - 2); | ||
| 10243 | }, | ||
| 10244 | |||
| 10245 | 	easeInQuint: function(t) { | ||
| 10246 | return t * t * t * t * t; | ||
| 10247 | }, | ||
| 10248 | |||
| 10249 | 	easeOutQuint: function(t) { | ||
| 10250 | return (t = t - 1) * t * t * t * t + 1; | ||
| 10251 | }, | ||
| 10252 | |||
| 10253 | 	easeInOutQuint: function(t) { | ||
| 10254 | 		if ((t /= 0.5) < 1) { | ||
| 10255 | return 0.5 * t * t * t * t * t; | ||
| 10256 | } | ||
| 10257 | return 0.5 * ((t -= 2) * t * t * t * t + 2); | ||
| 10258 | }, | ||
| 10259 | |||
| 10260 | 	easeInSine: function(t) { | ||
| 10261 | return -Math.cos(t * (Math.PI / 2)) + 1; | ||
| 10262 | }, | ||
| 10263 | |||
| 10264 | 	easeOutSine: function(t) { | ||
| 10265 | return Math.sin(t * (Math.PI / 2)); | ||
| 10266 | }, | ||
| 10267 | |||
| 10268 | 	easeInOutSine: function(t) { | ||
| 10269 | return -0.5 * (Math.cos(Math.PI * t) - 1); | ||
| 10270 | }, | ||
| 10271 | |||
| 10272 | 	easeInExpo: function(t) { | ||
| 10273 | return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); | ||
| 10274 | }, | ||
| 10275 | |||
| 10276 | 	easeOutExpo: function(t) { | ||
| 10277 | return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; | ||
| 10278 | }, | ||
| 10279 | |||
| 10280 | 	easeInOutExpo: function(t) { | ||
| 10281 | 		if (t === 0) { | ||
| 10282 | return 0; | ||
| 10283 | } | ||
| 10284 | 		if (t === 1) { | ||
| 10285 | return 1; | ||
| 10286 | } | ||
| 10287 | 		if ((t /= 0.5) < 1) { | ||
| 10288 | return 0.5 * Math.pow(2, 10 * (t - 1)); | ||
| 10289 | } | ||
| 10290 | return 0.5 * (-Math.pow(2, -10 * --t) + 2); | ||
| 10291 | }, | ||
| 10292 | |||
| 10293 | 	easeInCirc: function(t) { | ||
| 10294 | 		if (t >= 1) { | ||
| 10295 | return t; | ||
| 10296 | } | ||
| 10297 | return -(Math.sqrt(1 - t * t) - 1); | ||
| 10298 | }, | ||
| 10299 | |||
| 10300 | 	easeOutCirc: function(t) { | ||
| 10301 | return Math.sqrt(1 - (t = t - 1) * t); | ||
| 10302 | }, | ||
| 10303 | |||
| 10304 | 	easeInOutCirc: function(t) { | ||
| 10305 | 		if ((t /= 0.5) < 1) { | ||
| 10306 | return -0.5 * (Math.sqrt(1 - t * t) - 1); | ||
| 10307 | } | ||
| 10308 | return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); | ||
| 10309 | }, | ||
| 10310 | |||
| 10311 | 	easeInElastic: function(t) { | ||
| 10312 | var s = 1.70158; | ||
| 10313 | var p = 0; | ||
| 10314 | var a = 1; | ||
| 10315 | 		if (t === 0) { | ||
| 10316 | return 0; | ||
| 10317 | } | ||
| 10318 | 		if (t === 1) { | ||
| 10319 | return 1; | ||
| 10320 | } | ||
| 10321 | 		if (!p) { | ||
| 10322 | p = 0.3; | ||
| 10323 | } | ||
| 10324 | 		if (a < 1) { | ||
| 10325 | a = 1; | ||
| 10326 | s = p / 4; | ||
| 10327 | 		} else { | ||
| 10328 | s = p / (2 * Math.PI) * Math.asin(1 / a); | ||
| 10329 | } | ||
| 10330 | return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); | ||
| 10331 | }, | ||
| 10332 | |||
| 10333 | 	easeOutElastic: function(t) { | ||
| 10334 | var s = 1.70158; | ||
| 10335 | var p = 0; | ||
| 10336 | var a = 1; | ||
| 10337 | 		if (t === 0) { | ||
| 10338 | return 0; | ||
| 10339 | } | ||
| 10340 | 		if (t === 1) { | ||
| 10341 | return 1; | ||
| 10342 | } | ||
| 10343 | 		if (!p) { | ||
| 10344 | p = 0.3; | ||
| 10345 | } | ||
| 10346 | 		if (a < 1) { | ||
| 10347 | a = 1; | ||
| 10348 | s = p / 4; | ||
| 10349 | 		} else { | ||
| 10350 | s = p / (2 * Math.PI) * Math.asin(1 / a); | ||
| 10351 | } | ||
| 10352 | return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; | ||
| 10353 | }, | ||
| 10354 | |||
| 10355 | 	easeInOutElastic: function(t) { | ||
| 10356 | var s = 1.70158; | ||
| 10357 | var p = 0; | ||
| 10358 | var a = 1; | ||
| 10359 | 		if (t === 0) { | ||
| 10360 | return 0; | ||
| 10361 | } | ||
| 10362 | 		if ((t /= 0.5) === 2) { | ||
| 10363 | return 1; | ||
| 10364 | } | ||
| 10365 | 		if (!p) { | ||
| 10366 | p = 0.45; | ||
| 10367 | } | ||
| 10368 | 		if (a < 1) { | ||
| 10369 | a = 1; | ||
| 10370 | s = p / 4; | ||
| 10371 | 		} else { | ||
| 10372 | s = p / (2 * Math.PI) * Math.asin(1 / a); | ||
| 10373 | } | ||
| 10374 | 		if (t < 1) { | ||
| 10375 | return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); | ||
| 10376 | } | ||
| 10377 | return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; | ||
| 10378 | }, | ||
| 10379 | 	easeInBack: function(t) { | ||
| 10380 | var s = 1.70158; | ||
| 10381 | return t * t * ((s + 1) * t - s); | ||
| 10382 | }, | ||
| 10383 | |||
| 10384 | 	easeOutBack: function(t) { | ||
| 10385 | var s = 1.70158; | ||
| 10386 | return (t = t - 1) * t * ((s + 1) * t + s) + 1; | ||
| 10387 | }, | ||
| 10388 | |||
| 10389 | 	easeInOutBack: function(t) { | ||
| 10390 | var s = 1.70158; | ||
| 10391 | 		if ((t /= 0.5) < 1) { | ||
| 10392 | return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); | ||
| 10393 | } | ||
| 10394 | return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); | ||
| 10395 | }, | ||
| 10396 | |||
| 10397 | 	easeInBounce: function(t) { | ||
| 10398 | return 1 - effects.easeOutBounce(1 - t); | ||
| 10399 | }, | ||
| 10400 | |||
| 10401 | 	easeOutBounce: function(t) { | ||
| 10402 | 		if (t < (1 / 2.75)) { | ||
| 10403 | return 7.5625 * t * t; | ||
| 10404 | } | ||
| 10405 | 		if (t < (2 / 2.75)) { | ||
| 10406 | return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; | ||
| 10407 | } | ||
| 10408 | 		if (t < (2.5 / 2.75)) { | ||
| 10409 | return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; | ||
| 10410 | } | ||
| 10411 | return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; | ||
| 10412 | }, | ||
| 10413 | |||
| 10414 | 	easeInOutBounce: function(t) { | ||
| 10415 | 		if (t < 0.5) { | ||
| 10416 | return effects.easeInBounce(t * 2) * 0.5; | ||
| 10417 | } | ||
| 10418 | return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; | ||
| 10419 | } | ||
| 10420 | }; | ||
| 10421 | |||
| 10422 | module.exports = { | ||
| 10423 | effects: effects | ||
| 10424 | }; | ||
| 10425 | |||
| 10426 | // DEPRECATIONS | ||
| 10427 | |||
| 10428 | /** | ||
| 10429 | * Provided for backward compatibility, use Chart.helpers.easing.effects instead. | ||
| 10430 | * @function Chart.helpers.easingEffects | ||
| 10431 | * @deprecated since version 2.7.0 | ||
| 10432 | * @todo remove at version 3 | ||
| 10433 | * @private | ||
| 10434 | */ | ||
| 10435 | helpers.easingEffects = effects; | ||
| 10436 | |||
| 10437 | },{"43":43}],45:[function(require,module,exports){ | ||
| 10438 | 'use strict'; | ||
| 10439 | |||
| 10440 | var helpers = require(43); | ||
| 10441 | |||
| 10442 | /** | ||
| 10443 | * @alias Chart.helpers.options | ||
| 10444 | * @namespace | ||
| 10445 | */ | ||
| 10446 | module.exports = { | ||
| 10447 | /** | ||
| 10448 | * Converts the given line height `value` in pixels for a specific font `size`. | ||
| 10449 | 	 * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). | ||
| 10450 | 	 * @param {Number} size - The font size (in pixels) used to resolve relative `value`. | ||
| 10451 | 	 * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid). | ||
| 10452 | * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height | ||
| 10453 | * @since 2.7.0 | ||
| 10454 | */ | ||
| 10455 | 	toLineHeight: function(value, size) { | ||
| 10456 | 		var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); | ||
| 10457 | 		if (!matches || matches[1] === 'normal') { | ||
| 10458 | return size * 1.2; | ||
| 10459 | } | ||
| 10460 | |||
| 10461 | value = +matches[2]; | ||
| 10462 | |||
| 10463 | 		switch (matches[3]) { | ||
| 10464 | case 'px': | ||
| 10465 | return value; | ||
| 10466 | case '%': | ||
| 10467 | value /= 100; | ||
| 10468 | break; | ||
| 10469 | default: | ||
| 10470 | break; | ||
| 10471 | } | ||
| 10472 | |||
| 10473 | return size * value; | ||
| 10474 | }, | ||
| 10475 | |||
| 10476 | /** | ||
| 10477 | * Converts the given value into a padding object with pre-computed width/height. | ||
| 10478 | 	 * @param {Number|Object} value - If a number, set the value to all TRBL component, | ||
| 10479 | * else, if and object, use defined properties and sets undefined ones to 0. | ||
| 10480 | 	 * @returns {Object} The padding values (top, right, bottom, left, width, height) | ||
| 10481 | * @since 2.7.0 | ||
| 10482 | */ | ||
| 10483 | 	toPadding: function(value) { | ||
| 10484 | var t, r, b, l; | ||
| 10485 | |||
| 10486 | 		if (helpers.isObject(value)) { | ||
| 10487 | t = +value.top || 0; | ||
| 10488 | r = +value.right || 0; | ||
| 10489 | b = +value.bottom || 0; | ||
| 10490 | l = +value.left || 0; | ||
| 10491 | 		} else { | ||
| 10492 | t = r = b = l = +value || 0; | ||
| 10493 | } | ||
| 10494 | |||
| 10495 | 		return { | ||
| 10496 | top: t, | ||
| 10497 | right: r, | ||
| 10498 | bottom: b, | ||
| 10499 | left: l, | ||
| 10500 | height: t + b, | ||
| 10501 | width: l + r | ||
| 10502 | }; | ||
| 10503 | }, | ||
| 10504 | |||
| 10505 | /** | ||
| 10506 | * Evaluates the given `inputs` sequentially and returns the first defined value. | ||
| 10507 | 	 * @param {Array[]} inputs - An array of values, falling back to the last value. | ||
| 10508 | 	 * @param {Object} [context] - If defined and the current value is a function, the value | ||
| 10509 | * is called with `context` as first argument and the result becomes the new input. | ||
| 10510 | 	 * @param {Number} [index] - If defined and the current value is an array, the value | ||
| 10511 | * at `index` become the new input. | ||
| 10512 | * @since 2.7.0 | ||
| 10513 | */ | ||
| 10514 | 	resolve: function(inputs, context, index) { | ||
| 10515 | var i, ilen, value; | ||
| 10516 | |||
| 10517 | 		for (i = 0, ilen = inputs.length; i < ilen; ++i) { | ||
| 10518 | value = inputs[i]; | ||
| 10519 | 			if (value === undefined) { | ||
| 10520 | continue; | ||
| 10521 | } | ||
| 10522 | 			if (context !== undefined && typeof value === 'function') { | ||
| 10523 | value = value(context); | ||
| 10524 | } | ||
| 10525 | 			if (index !== undefined && helpers.isArray(value)) { | ||
| 10526 | value = value[index]; | ||
| 10527 | } | ||
| 10528 | 			if (value !== undefined) { | ||
| 10529 | return value; | ||
| 10530 | } | ||
| 10531 | } | ||
| 10532 | } | ||
| 10533 | }; | ||
| 10534 | |||
| 10535 | },{"43":43}],46:[function(require,module,exports){ | ||
| 10536 | 'use strict'; | ||
| 10537 | |||
| 10538 | module.exports = require(43); | ||
| 10539 | module.exports.easing = require(44); | ||
| 10540 | module.exports.canvas = require(42); | ||
| 10541 | module.exports.options = require(45); | ||
| 10542 | |||
| 10543 | },{"42":42,"43":43,"44":44,"45":45}],47:[function(require,module,exports){ | ||
| 10544 | /** | ||
| 10545 | * Platform fallback implementation (minimal). | ||
| 10546 | * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 | ||
| 10547 | */ | ||
| 10548 | |||
| 10549 | module.exports = { | ||
| 10550 | 	acquireContext: function(item) { | ||
| 10551 | 		if (item && item.canvas) { | ||
| 10552 | // Support for any object associated to a canvas (including a context2d) | ||
| 10553 | item = item.canvas; | ||
| 10554 | } | ||
| 10555 | |||
| 10556 | 		return item && item.getContext('2d') || null; | ||
| 10557 | } | ||
| 10558 | }; | ||
| 10559 | |||
| 10560 | },{}],48:[function(require,module,exports){ | ||
| 10561 | /** | ||
| 10562 | * Chart.Platform implementation for targeting a web browser | ||
| 10563 | */ | ||
| 10564 | |||
| 10565 | 'use strict'; | ||
| 10566 | |||
| 10567 | var helpers = require(46); | ||
| 10568 | |||
| 10569 | var EXPANDO_KEY = '$chartjs'; | ||
| 10570 | var CSS_PREFIX = 'chartjs-'; | ||
| 10571 | var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; | ||
| 10572 | var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; | ||
| 10573 | var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; | ||
| 10574 | |||
| 10575 | /** | ||
| 10576 | * DOM event types -> Chart.js event types. | ||
| 10577 | * Note: only events with different types are mapped. | ||
| 10578 | * @see https://developer.mozilla.org/en-US/docs/Web/Events | ||
| 10579 | */ | ||
| 10580 | var EVENT_TYPES = { | ||
| 10581 | touchstart: 'mousedown', | ||
| 10582 | touchmove: 'mousemove', | ||
| 10583 | touchend: 'mouseup', | ||
| 10584 | pointerenter: 'mouseenter', | ||
| 10585 | pointerdown: 'mousedown', | ||
| 10586 | pointermove: 'mousemove', | ||
| 10587 | pointerup: 'mouseup', | ||
| 10588 | pointerleave: 'mouseout', | ||
| 10589 | pointerout: 'mouseout' | ||
| 10590 | }; | ||
| 10591 | |||
| 10592 | /** | ||
| 10593 | * The "used" size is the final value of a dimension property after all calculations have | ||
| 10594 | * been performed. This method uses the computed style of `element` but returns undefined | ||
| 10595 | * if the computed style is not expressed in pixels. That can happen in some cases where | ||
| 10596 | * `element` has a size relative to its parent and this last one is not yet displayed, | ||
| 10597 | * for example because of `display: none` on a parent node. | ||
| 10598 | * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value | ||
| 10599 |  * @returns {Number} Size in pixels or undefined if unknown. | ||
| 10600 | */ | ||
| 10601 | function readUsedSize(element, property) { | ||
| 10602 | var value = helpers.getStyle(element, property); | ||
| 10603 | var matches = value && value.match(/^(\d+)(\.\d+)?px$/); | ||
| 10604 | return matches ? Number(matches[1]) : undefined; | ||
| 10605 | } | ||
| 10606 | |||
| 10607 | /** | ||
| 10608 | * Initializes the canvas style and render size without modifying the canvas display size, | ||
| 10609 | * since responsiveness is handled by the controller.resize() method. The config is used | ||
| 10610 | * to determine the aspect ratio to apply in case no explicit height has been specified. | ||
| 10611 | */ | ||
| 10612 | function initCanvas(canvas, config) { | ||
| 10613 | var style = canvas.style; | ||
| 10614 | |||
| 10615 | 	// NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it | ||
| 10616 | // returns null or '' if no explicit value has been set to the canvas attribute. | ||
| 10617 | 	var renderHeight = canvas.getAttribute('height'); | ||
| 10618 | 	var renderWidth = canvas.getAttribute('width'); | ||
| 10619 | |||
| 10620 | // Chart.js modifies some canvas values that we want to restore on destroy | ||
| 10621 | 	canvas[EXPANDO_KEY] = { | ||
| 10622 | 		initial: { | ||
| 10623 | height: renderHeight, | ||
| 10624 | width: renderWidth, | ||
| 10625 | 			style: { | ||
| 10626 | display: style.display, | ||
| 10627 | height: style.height, | ||
| 10628 | width: style.width | ||
| 10629 | } | ||
| 10630 | } | ||
| 10631 | }; | ||
| 10632 | |||
| 10633 | // Force canvas to display as block to avoid extra space caused by inline | ||
| 10634 | // elements, which would interfere with the responsive resize process. | ||
| 10635 | // https://github.com/chartjs/Chart.js/issues/2538 | ||
| 10636 | style.display = style.display || 'block'; | ||
| 10637 | |||
| 10638 | 	if (renderWidth === null || renderWidth === '') { | ||
| 10639 | var displayWidth = readUsedSize(canvas, 'width'); | ||
| 10640 | 		if (displayWidth !== undefined) { | ||
| 10641 | canvas.width = displayWidth; | ||
| 10642 | } | ||
| 10643 | } | ||
| 10644 | |||
| 10645 | 	if (renderHeight === null || renderHeight === '') { | ||
| 10646 | 		if (canvas.style.height === '') { | ||
| 10647 | // If no explicit render height and style height, let's apply the aspect ratio, | ||
| 10648 | // which one can be specified by the user but also by charts as default option | ||
| 10649 | // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. | ||
| 10650 | canvas.height = canvas.width / (config.options.aspectRatio || 2); | ||
| 10651 | 		} else { | ||
| 10652 | var displayHeight = readUsedSize(canvas, 'height'); | ||
| 10653 | 			if (displayWidth !== undefined) { | ||
| 10654 | canvas.height = displayHeight; | ||
| 10655 | } | ||
| 10656 | } | ||
| 10657 | } | ||
| 10658 | |||
| 10659 | return canvas; | ||
| 10660 | } | ||
| 10661 | |||
| 10662 | /** | ||
| 10663 | * Detects support for options object argument in addEventListener. | ||
| 10664 | * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support | ||
| 10665 | * @private | ||
| 10666 | */ | ||
| 10667 | var supportsEventListenerOptions = (function() { | ||
| 10668 | var supports = false; | ||
| 10669 | 	try { | ||
| 10670 | 		var options = Object.defineProperty({}, 'passive', { | ||
| 10671 | 			get: function() { | ||
| 10672 | supports = true; | ||
| 10673 | } | ||
| 10674 | }); | ||
| 10675 | 		window.addEventListener('e', null, options); | ||
| 10676 | 	} catch (e) { | ||
| 10677 | // continue regardless of error | ||
| 10678 | } | ||
| 10679 | return supports; | ||
| 10680 | }()); | ||
| 10681 | |||
| 10682 | // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. | ||
| 10683 | // https://github.com/chartjs/Chart.js/issues/4287 | ||
| 10684 | var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; | ||
| 10685 | |||
| 10686 | function addEventListener(node, type, listener) { | ||
| 10687 | node.addEventListener(type, listener, eventListenerOptions); | ||
| 10688 | } | ||
| 10689 | |||
| 10690 | function removeEventListener(node, type, listener) { | ||
| 10691 | node.removeEventListener(type, listener, eventListenerOptions); | ||
| 10692 | } | ||
| 10693 | |||
| 10694 | function createEvent(type, chart, x, y, nativeEvent) { | ||
| 10695 | 	return { | ||
| 10696 | type: type, | ||
| 10697 | chart: chart, | ||
| 10698 | native: nativeEvent || null, | ||
| 10699 | x: x !== undefined ? x : null, | ||
| 10700 | y: y !== undefined ? y : null, | ||
| 10701 | }; | ||
| 10702 | } | ||
| 10703 | |||
| 10704 | function fromNativeEvent(event, chart) { | ||
| 10705 | var type = EVENT_TYPES[event.type] || event.type; | ||
| 10706 | var pos = helpers.getRelativePosition(event, chart); | ||
| 10707 | return createEvent(type, chart, pos.x, pos.y, event); | ||
| 10708 | } | ||
| 10709 | |||
| 10710 | function throttled(fn, thisArg) { | ||
| 10711 | var ticking = false; | ||
| 10712 | var args = []; | ||
| 10713 | |||
| 10714 | 	return function() { | ||
| 10715 | args = Array.prototype.slice.call(arguments); | ||
| 10716 | thisArg = thisArg || this; | ||
| 10717 | |||
| 10718 | 		if (!ticking) { | ||
| 10719 | ticking = true; | ||
| 10720 | 			helpers.requestAnimFrame.call(window, function() { | ||
| 10721 | ticking = false; | ||
| 10722 | fn.apply(thisArg, args); | ||
| 10723 | }); | ||
| 10724 | } | ||
| 10725 | }; | ||
| 10726 | } | ||
| 10727 | |||
| 10728 | // Implementation based on https://github.com/marcj/css-element-queries | ||
| 10729 | function createResizer(handler) { | ||
| 10730 | 	var resizer = document.createElement('div'); | ||
| 10731 | var cls = CSS_PREFIX + 'size-monitor'; | ||
| 10732 | var maxSize = 1000000; | ||
| 10733 | var style = | ||
| 10734 | 'position:absolute;' + | ||
| 10735 | 'left:0;' + | ||
| 10736 | 'top:0;' + | ||
| 10737 | 'right:0;' + | ||
| 10738 | 'bottom:0;' + | ||
| 10739 | 'overflow:hidden;' + | ||
| 10740 | 'pointer-events:none;' + | ||
| 10741 | 'visibility:hidden;' + | ||
| 10742 | 'z-index:-1;'; | ||
| 10743 | |||
| 10744 | resizer.style.cssText = style; | ||
| 10745 | resizer.className = cls; | ||
| 10746 | resizer.innerHTML = | ||
| 10747 | '<div class="' + cls + '-expand" style="' + style + '">' + | ||
| 10748 | '<div style="' + | ||
| 10749 | 'position:absolute;' + | ||
| 10750 | 'width:' + maxSize + 'px;' + | ||
| 10751 | 'height:' + maxSize + 'px;' + | ||
| 10752 | 'left:0;' + | ||
| 10753 | 'top:0">' + | ||
| 10754 | '</div>' + | ||
| 10755 | '</div>' + | ||
| 10756 | '<div class="' + cls + '-shrink" style="' + style + '">' + | ||
| 10757 | '<div style="' + | ||
| 10758 | 'position:absolute;' + | ||
| 10759 | 'width:200%;' + | ||
| 10760 | 'height:200%;' + | ||
| 10761 | 'left:0; ' + | ||
| 10762 | 'top:0">' + | ||
| 10763 | '</div>' + | ||
| 10764 | '</div>'; | ||
| 10765 | |||
| 10766 | var expand = resizer.childNodes[0]; | ||
| 10767 | var shrink = resizer.childNodes[1]; | ||
| 10768 | |||
| 10769 | 	resizer._reset = function() { | ||
| 10770 | expand.scrollLeft = maxSize; | ||
| 10771 | expand.scrollTop = maxSize; | ||
| 10772 | shrink.scrollLeft = maxSize; | ||
| 10773 | shrink.scrollTop = maxSize; | ||
| 10774 | }; | ||
| 10775 | 	var onScroll = function() { | ||
| 10776 | resizer._reset(); | ||
| 10777 | handler(); | ||
| 10778 | }; | ||
| 10779 | |||
| 10780 | addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand')); | ||
| 10781 | addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); | ||
| 10782 | |||
| 10783 | return resizer; | ||
| 10784 | } | ||
| 10785 | |||
| 10786 | // https://davidwalsh.name/detect-node-insertion | ||
| 10787 | function watchForRender(node, handler) { | ||
| 10788 | 	var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); | ||
| 10789 | 	var proxy = expando.renderProxy = function(e) { | ||
| 10790 | 		if (e.animationName === CSS_RENDER_ANIMATION) { | ||
| 10791 | handler(); | ||
| 10792 | } | ||
| 10793 | }; | ||
| 10794 | |||
| 10795 | 	helpers.each(ANIMATION_START_EVENTS, function(type) { | ||
| 10796 | addEventListener(node, type, proxy); | ||
| 10797 | }); | ||
| 10798 | |||
| 10799 | // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class | ||
| 10800 | // is removed then added back immediately (same animation frame?). Accessing the | ||
| 10801 | // `offsetParent` property will force a reflow and re-evaluate the CSS animation. | ||
| 10802 | // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics | ||
| 10803 | // https://github.com/chartjs/Chart.js/issues/4737 | ||
| 10804 | expando.reflow = !!node.offsetParent; | ||
| 10805 | |||
| 10806 | node.classList.add(CSS_RENDER_MONITOR); | ||
| 10807 | } | ||
| 10808 | |||
| 10809 | function unwatchForRender(node) { | ||
| 10810 | 	var expando = node[EXPANDO_KEY] || {}; | ||
| 10811 | var proxy = expando.renderProxy; | ||
| 10812 | |||
| 10813 | 	if (proxy) { | ||
| 10814 | 		helpers.each(ANIMATION_START_EVENTS, function(type) { | ||
| 10815 | removeEventListener(node, type, proxy); | ||
| 10816 | }); | ||
| 10817 | |||
| 10818 | delete expando.renderProxy; | ||
| 10819 | } | ||
| 10820 | |||
| 10821 | node.classList.remove(CSS_RENDER_MONITOR); | ||
| 10822 | } | ||
| 10823 | |||
| 10824 | function addResizeListener(node, listener, chart) { | ||
| 10825 | 	var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); | ||
| 10826 | |||
| 10827 | // Let's keep track of this added resizer and thus avoid DOM query when removing it. | ||
| 10828 | 	var resizer = expando.resizer = createResizer(throttled(function() { | ||
| 10829 | 		if (expando.resizer) { | ||
| 10830 | 			return listener(createEvent('resize', chart)); | ||
| 10831 | } | ||
| 10832 | })); | ||
| 10833 | |||
| 10834 | // The resizer needs to be attached to the node parent, so we first need to be | ||
| 10835 | // sure that `node` is attached to the DOM before injecting the resizer element. | ||
| 10836 | 	watchForRender(node, function() { | ||
| 10837 | 		if (expando.resizer) { | ||
| 10838 | var container = node.parentNode; | ||
| 10839 | 			if (container && container !== resizer.parentNode) { | ||
| 10840 | container.insertBefore(resizer, container.firstChild); | ||
| 10841 | } | ||
| 10842 | |||
| 10843 | // The container size might have changed, let's reset the resizer state. | ||
| 10844 | resizer._reset(); | ||
| 10845 | } | ||
| 10846 | }); | ||
| 10847 | } | ||
| 10848 | |||
| 10849 | function removeResizeListener(node) { | ||
| 10850 | 	var expando = node[EXPANDO_KEY] || {}; | ||
| 10851 | var resizer = expando.resizer; | ||
| 10852 | |||
| 10853 | delete expando.resizer; | ||
| 10854 | unwatchForRender(node); | ||
| 10855 | |||
| 10856 | 	if (resizer && resizer.parentNode) { | ||
| 10857 | resizer.parentNode.removeChild(resizer); | ||
| 10858 | } | ||
| 10859 | } | ||
| 10860 | |||
| 10861 | function injectCSS(platform, css) { | ||
| 10862 | // http://stackoverflow.com/q/3922139 | ||
| 10863 | 	var style = platform._style || document.createElement('style'); | ||
| 10864 | 	if (!platform._style) { | ||
| 10865 | platform._style = style; | ||
| 10866 | css = '/* Chart.js */\n' + css; | ||
| 10867 | 		style.setAttribute('type', 'text/css'); | ||
| 10868 | 		document.getElementsByTagName('head')[0].appendChild(style); | ||
| 10869 | } | ||
| 10870 | |||
| 10871 | style.appendChild(document.createTextNode(css)); | ||
| 10872 | } | ||
| 10873 | |||
| 10874 | module.exports = { | ||
| 10875 | /** | ||
| 10876 | * This property holds whether this platform is enabled for the current environment. | ||
| 10877 | * Currently used by platform.js to select the proper implementation. | ||
| 10878 | * @private | ||
| 10879 | */ | ||
| 10880 | _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', | ||
| 10881 | |||
| 10882 | 	initialize: function() { | ||
| 10883 | 		var keyframes = 'from{opacity:0.99}to{opacity:1}'; | ||
| 10884 | |||
| 10885 | injectCSS(this, | ||
| 10886 | // DOM rendering detection | ||
| 10887 | // https://davidwalsh.name/detect-node-insertion | ||
| 10888 | 			'@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + | ||
| 10889 | 			'@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + | ||
| 10890 | 			'.' + CSS_RENDER_MONITOR + '{' + | ||
| 10891 | '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + | ||
| 10892 | 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + | ||
| 10893 | '}' | ||
| 10894 | ); | ||
| 10895 | }, | ||
| 10896 | |||
| 10897 | 	acquireContext: function(item, config) { | ||
| 10898 | 		if (typeof item === 'string') { | ||
| 10899 | item = document.getElementById(item); | ||
| 10900 | 		} else if (item.length) { | ||
| 10901 | // Support for array based queries (such as jQuery) | ||
| 10902 | item = item[0]; | ||
| 10903 | } | ||
| 10904 | |||
| 10905 | 		if (item && item.canvas) { | ||
| 10906 | // Support for any object associated to a canvas (including a context2d) | ||
| 10907 | item = item.canvas; | ||
| 10908 | } | ||
| 10909 | |||
| 10910 | // To prevent canvas fingerprinting, some add-ons undefine the getContext | ||
| 10911 | // method, for example: https://github.com/kkapsner/CanvasBlocker | ||
| 10912 | // https://github.com/chartjs/Chart.js/issues/2807 | ||
| 10913 | 		var context = item && item.getContext && item.getContext('2d'); | ||
| 10914 | |||
| 10915 | // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is | ||
| 10916 | // inside an iframe or when running in a protected environment. We could guess the | ||
| 10917 | // types from their toString() value but let's keep things flexible and assume it's | ||
| 10918 | // a sufficient condition if the item has a context2D which has item as `canvas`. | ||
| 10919 | // https://github.com/chartjs/Chart.js/issues/3887 | ||
| 10920 | // https://github.com/chartjs/Chart.js/issues/4102 | ||
| 10921 | // https://github.com/chartjs/Chart.js/issues/4152 | ||
| 10922 | 		if (context && context.canvas === item) { | ||
| 10923 | initCanvas(item, config); | ||
| 10924 | return context; | ||
| 10925 | } | ||
| 10926 | |||
| 10927 | return null; | ||
| 10928 | }, | ||
| 10929 | |||
| 10930 | 	releaseContext: function(context) { | ||
| 10931 | var canvas = context.canvas; | ||
| 10932 | 		if (!canvas[EXPANDO_KEY]) { | ||
| 10933 | return; | ||
| 10934 | } | ||
| 10935 | |||
| 10936 | var initial = canvas[EXPANDO_KEY].initial; | ||
| 10937 | 		['height', 'width'].forEach(function(prop) { | ||
| 10938 | var value = initial[prop]; | ||
| 10939 | 			if (helpers.isNullOrUndef(value)) { | ||
| 10940 | canvas.removeAttribute(prop); | ||
| 10941 | 			} else { | ||
| 10942 | canvas.setAttribute(prop, value); | ||
| 10943 | } | ||
| 10944 | }); | ||
| 10945 | |||
| 10946 | 		helpers.each(initial.style || {}, function(value, key) { | ||
| 10947 | canvas.style[key] = value; | ||
| 10948 | }); | ||
| 10949 | |||
| 10950 | // The canvas render size might have been changed (and thus the state stack discarded), | ||
| 10951 | // we can't use save() and restore() to restore the initial state. So make sure that at | ||
| 10952 | // least the canvas context is reset to the default state by setting the canvas width. | ||
| 10953 | // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html | ||
| 10954 | canvas.width = canvas.width; | ||
| 10955 | |||
| 10956 | delete canvas[EXPANDO_KEY]; | ||
| 10957 | }, | ||
| 10958 | |||
| 10959 | 	addEventListener: function(chart, type, listener) { | ||
| 10960 | var canvas = chart.canvas; | ||
| 10961 | 		if (type === 'resize') { | ||
| 10962 | // Note: the resize event is not supported on all browsers. | ||
| 10963 | addResizeListener(canvas, listener, chart); | ||
| 10964 | return; | ||
| 10965 | } | ||
| 10966 | |||
| 10967 | 		var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); | ||
| 10968 | 		var proxies = expando.proxies || (expando.proxies = {}); | ||
| 10969 | 		var proxy = proxies[chart.id + '_' + type] = function(event) { | ||
| 10970 | listener(fromNativeEvent(event, chart)); | ||
| 10971 | }; | ||
| 10972 | |||
| 10973 | addEventListener(canvas, type, proxy); | ||
| 10974 | }, | ||
| 10975 | |||
| 10976 | 	removeEventListener: function(chart, type, listener) { | ||
| 10977 | var canvas = chart.canvas; | ||
| 10978 | 		if (type === 'resize') { | ||
| 10979 | // Note: the resize event is not supported on all browsers. | ||
| 10980 | removeResizeListener(canvas, listener); | ||
| 10981 | return; | ||
| 10982 | } | ||
| 10983 | |||
| 10984 | 		var expando = listener[EXPANDO_KEY] || {}; | ||
| 10985 | 		var proxies = expando.proxies || {}; | ||
| 10986 | var proxy = proxies[chart.id + '_' + type]; | ||
| 10987 | 		if (!proxy) { | ||
| 10988 | return; | ||
| 10989 | } | ||
| 10990 | |||
| 10991 | removeEventListener(canvas, type, proxy); | ||
| 10992 | } | ||
| 10993 | }; | ||
| 10994 | |||
| 10995 | // DEPRECATIONS | ||
| 10996 | |||
| 10997 | /** | ||
| 10998 | * Provided for backward compatibility, use EventTarget.addEventListener instead. | ||
| 10999 | * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ | ||
| 11000 | * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener | ||
| 11001 | * @function Chart.helpers.addEvent | ||
| 11002 | * @deprecated since version 2.7.0 | ||
| 11003 | * @todo remove at version 3 | ||
| 11004 | * @private | ||
| 11005 | */ | ||
| 11006 | helpers.addEvent = addEventListener; | ||
| 11007 | |||
| 11008 | /** | ||
| 11009 | * Provided for backward compatibility, use EventTarget.removeEventListener instead. | ||
| 11010 | * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ | ||
| 11011 | * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener | ||
| 11012 | * @function Chart.helpers.removeEvent | ||
| 11013 | * @deprecated since version 2.7.0 | ||
| 11014 | * @todo remove at version 3 | ||
| 11015 | * @private | ||
| 11016 | */ | ||
| 11017 | helpers.removeEvent = removeEventListener; | ||
| 11018 | |||
| 11019 | },{"46":46}],49:[function(require,module,exports){ | ||
| 11020 | 'use strict'; | ||
| 11021 | |||
| 11022 | var helpers = require(46); | ||
| 11023 | var basic = require(47); | ||
| 11024 | var dom = require(48); | ||
| 11025 | |||
| 11026 | // @TODO Make possible to select another platform at build time. | ||
| 11027 | var implementation = dom._enabled ? dom : basic; | ||
| 11028 | |||
| 11029 | /** | ||
| 11030 | * @namespace Chart.platform | ||
| 11031 | * @see https://chartjs.gitbooks.io/proposals/content/Platform.html | ||
| 11032 | * @since 2.4.0 | ||
| 11033 | */ | ||
| 11034 | module.exports = helpers.extend({ | ||
| 11035 | /** | ||
| 11036 | * @since 2.7.0 | ||
| 11037 | */ | ||
| 11038 | 	initialize: function() {}, | ||
| 11039 | |||
| 11040 | /** | ||
| 11041 | * Called at chart construction time, returns a context2d instance implementing | ||
| 11042 | 	 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. | ||
| 11043 | 	 * @param {*} item - The native item from which to acquire context (platform specific) | ||
| 11044 | 	 * @param {Object} options - The chart options | ||
| 11045 | 	 * @returns {CanvasRenderingContext2D} context2d instance | ||
| 11046 | */ | ||
| 11047 | 	acquireContext: function() {}, | ||
| 11048 | |||
| 11049 | /** | ||
| 11050 | * Called at chart destruction time, releases any resources associated to the context | ||
| 11051 | * previously returned by the acquireContext() method. | ||
| 11052 | 	 * @param {CanvasRenderingContext2D} context - The context2d instance | ||
| 11053 | 	 * @returns {Boolean} true if the method succeeded, else false | ||
| 11054 | */ | ||
| 11055 | 	releaseContext: function() {}, | ||
| 11056 | |||
| 11057 | /** | ||
| 11058 | * Registers the specified listener on the given chart. | ||
| 11059 | 	 * @param {Chart} chart - Chart from which to listen for event | ||
| 11060 | 	 * @param {String} type - The ({@link IEvent}) type to listen for | ||
| 11061 | 	 * @param {Function} listener - Receives a notification (an object that implements | ||
| 11062 | 	 * the {@link IEvent} interface) when an event of the specified type occurs. | ||
| 11063 | */ | ||
| 11064 | 	addEventListener: function() {}, | ||
| 11065 | |||
| 11066 | /** | ||
| 11067 | * Removes the specified listener previously registered with addEventListener. | ||
| 11068 | 	 * @param {Chart} chart -Chart from which to remove the listener | ||
| 11069 | 	 * @param {String} type - The ({@link IEvent}) type to remove | ||
| 11070 | 	 * @param {Function} listener - The listener function to remove from the event target. | ||
| 11071 | */ | ||
| 11072 | 	removeEventListener: function() {} | ||
| 11073 | |||
| 11074 | }, implementation); | ||
| 11075 | |||
| 11076 | /** | ||
| 11077 | * @interface IPlatform | ||
| 11078 | * Allows abstracting platform dependencies away from the chart | ||
| 11079 | * @borrows Chart.platform.acquireContext as acquireContext | ||
| 11080 | * @borrows Chart.platform.releaseContext as releaseContext | ||
| 11081 | * @borrows Chart.platform.addEventListener as addEventListener | ||
| 11082 | * @borrows Chart.platform.removeEventListener as removeEventListener | ||
| 11083 | */ | ||
| 11084 | |||
| 11085 | /** | ||
| 11086 | * @interface IEvent | ||
| 11087 |  * @prop {String} type - The event type name, possible values are: | ||
| 11088 | * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout', | ||
| 11089 | * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize' | ||
| 11090 |  * @prop {*} native - The original native event (null for emulated events, e.g. 'resize') | ||
| 11091 |  * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events) | ||
| 11092 |  * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) | ||
| 11093 | */ | ||
| 11094 | |||
| 11095 | },{"46":46,"47":47,"48":48}],50:[function(require,module,exports){ | ||
| 11096 | 'use strict'; | ||
| 11097 | |||
| 11098 | module.exports = {}; | ||
| 11099 | module.exports.filler = require(51); | ||
| 11100 | module.exports.legend = require(52); | ||
| 11101 | module.exports.title = require(53); | ||
| 11102 | |||
| 11103 | },{"51":51,"52":52,"53":53}],51:[function(require,module,exports){ | ||
| 11104 | /** | ||
| 11105 | * Plugin based on discussion from the following Chart.js issues: | ||
| 11106 | * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569 | ||
| 11107 | * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897 | ||
| 11108 | */ | ||
| 11109 | |||
| 11110 | 'use strict'; | ||
| 11111 | |||
| 11112 | var defaults = require(26); | ||
| 11113 | var elements = require(41); | ||
| 11114 | var helpers = require(46); | ||
| 11115 | |||
| 11116 | defaults._set('global', { | ||
| 11117 | 	plugins: { | ||
| 11118 | 		filler: { | ||
| 11119 | propagate: true | ||
| 11120 | } | ||
| 11121 | } | ||
| 11122 | }); | ||
| 11123 | |||
| 11124 | var mappers = { | ||
| 11125 | 	dataset: function(source) { | ||
| 11126 | var index = source.fill; | ||
| 11127 | var chart = source.chart; | ||
| 11128 | var meta = chart.getDatasetMeta(index); | ||
| 11129 | var visible = meta && chart.isDatasetVisible(index); | ||
| 11130 | var points = (visible && meta.dataset._children) || []; | ||
| 11131 | var length = points.length || 0; | ||
| 11132 | |||
| 11133 | 		return !length ? null : function(point, i) { | ||
| 11134 | return (i < length && points[i]._view) || null; | ||
| 11135 | }; | ||
| 11136 | }, | ||
| 11137 | |||
| 11138 | 	boundary: function(source) { | ||
| 11139 | var boundary = source.boundary; | ||
| 11140 | var x = boundary ? boundary.x : null; | ||
| 11141 | var y = boundary ? boundary.y : null; | ||
| 11142 | |||
| 11143 | 		return function(point) { | ||
| 11144 | 			return { | ||
| 11145 | x: x === null ? point.x : x, | ||
| 11146 | y: y === null ? point.y : y, | ||
| 11147 | }; | ||
| 11148 | }; | ||
| 11149 | } | ||
| 11150 | }; | ||
| 11151 | |||
| 11152 | // @todo if (fill[0] === '#') | ||
| 11153 | function decodeFill(el, index, count) { | ||
| 11154 | 	var model = el._model || {}; | ||
| 11155 | var fill = model.fill; | ||
| 11156 | var target; | ||
| 11157 | |||
| 11158 | 	if (fill === undefined) { | ||
| 11159 | fill = !!model.backgroundColor; | ||
| 11160 | } | ||
| 11161 | |||
| 11162 | 	if (fill === false || fill === null) { | ||
| 11163 | return false; | ||
| 11164 | } | ||
| 11165 | |||
| 11166 | 	if (fill === true) { | ||
| 11167 | return 'origin'; | ||
| 11168 | } | ||
| 11169 | |||
| 11170 | target = parseFloat(fill, 10); | ||
| 11171 | 	if (isFinite(target) && Math.floor(target) === target) { | ||
| 11172 | 		if (fill[0] === '-' || fill[0] === '+') { | ||
| 11173 | target = index + target; | ||
| 11174 | } | ||
| 11175 | |||
| 11176 | 		if (target === index || target < 0 || target >= count) { | ||
| 11177 | return false; | ||
| 11178 | } | ||
| 11179 | |||
| 11180 | return target; | ||
| 11181 | } | ||
| 11182 | |||
| 11183 | 	switch (fill) { | ||
| 11184 | // compatibility | ||
| 11185 | case 'bottom': | ||
| 11186 | return 'start'; | ||
| 11187 | case 'top': | ||
| 11188 | return 'end'; | ||
| 11189 | case 'zero': | ||
| 11190 | return 'origin'; | ||
| 11191 | // supported boundaries | ||
| 11192 | case 'origin': | ||
| 11193 | case 'start': | ||
| 11194 | case 'end': | ||
| 11195 | return fill; | ||
| 11196 | // invalid fill values | ||
| 11197 | default: | ||
| 11198 | return false; | ||
| 11199 | } | ||
| 11200 | } | ||
| 11201 | |||
| 11202 | function computeBoundary(source) { | ||
| 11203 | 	var model = source.el._model || {}; | ||
| 11204 | 	var scale = source.el._scale || {}; | ||
| 11205 | var fill = source.fill; | ||
| 11206 | var target = null; | ||
| 11207 | var horizontal; | ||
| 11208 | |||
| 11209 | 	if (isFinite(fill)) { | ||
| 11210 | return null; | ||
| 11211 | } | ||
| 11212 | |||
| 11213 | // Backward compatibility: until v3, we still need to support boundary values set on | ||
| 11214 | // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and | ||
| 11215 | // controllers might still use it (e.g. the Smith chart). | ||
| 11216 | |||
| 11217 | 	if (fill === 'start') { | ||
| 11218 | target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; | ||
| 11219 | 	} else if (fill === 'end') { | ||
| 11220 | target = model.scaleTop === undefined ? scale.top : model.scaleTop; | ||
| 11221 | 	} else if (model.scaleZero !== undefined) { | ||
| 11222 | target = model.scaleZero; | ||
| 11223 | 	} else if (scale.getBasePosition) { | ||
| 11224 | target = scale.getBasePosition(); | ||
| 11225 | 	} else if (scale.getBasePixel) { | ||
| 11226 | target = scale.getBasePixel(); | ||
| 11227 | } | ||
| 11228 | |||
| 11229 | 	if (target !== undefined && target !== null) { | ||
| 11230 | 		if (target.x !== undefined && target.y !== undefined) { | ||
| 11231 | return target; | ||
| 11232 | } | ||
| 11233 | |||
| 11234 | 		if (typeof target === 'number' && isFinite(target)) { | ||
| 11235 | horizontal = scale.isHorizontal(); | ||
| 11236 | 			return { | ||
| 11237 | x: horizontal ? target : null, | ||
| 11238 | y: horizontal ? null : target | ||
| 11239 | }; | ||
| 11240 | } | ||
| 11241 | } | ||
| 11242 | |||
| 11243 | return null; | ||
| 11244 | } | ||
| 11245 | |||
| 11246 | function resolveTarget(sources, index, propagate) { | ||
| 11247 | var source = sources[index]; | ||
| 11248 | var fill = source.fill; | ||
| 11249 | var visited = [index]; | ||
| 11250 | var target; | ||
| 11251 | |||
| 11252 | 	if (!propagate) { | ||
| 11253 | return fill; | ||
| 11254 | } | ||
| 11255 | |||
| 11256 | 	while (fill !== false && visited.indexOf(fill) === -1) { | ||
| 11257 | 		if (!isFinite(fill)) { | ||
| 11258 | return fill; | ||
| 11259 | } | ||
| 11260 | |||
| 11261 | target = sources[fill]; | ||
| 11262 | 		if (!target) { | ||
| 11263 | return false; | ||
| 11264 | } | ||
| 11265 | |||
| 11266 | 		if (target.visible) { | ||
| 11267 | return fill; | ||
| 11268 | } | ||
| 11269 | |||
| 11270 | visited.push(fill); | ||
| 11271 | fill = target.fill; | ||
| 11272 | } | ||
| 11273 | |||
| 11274 | return false; | ||
| 11275 | } | ||
| 11276 | |||
| 11277 | function createMapper(source) { | ||
| 11278 | var fill = source.fill; | ||
| 11279 | var type = 'dataset'; | ||
| 11280 | |||
| 11281 | 	if (fill === false) { | ||
| 11282 | return null; | ||
| 11283 | } | ||
| 11284 | |||
| 11285 | 	if (!isFinite(fill)) { | ||
| 11286 | type = 'boundary'; | ||
| 11287 | } | ||
| 11288 | |||
| 11289 | return mappers[type](source); | ||
| 11290 | } | ||
| 11291 | |||
| 11292 | function isDrawable(point) { | ||
| 11293 | return point && !point.skip; | ||
| 11294 | } | ||
| 11295 | |||
| 11296 | function drawArea(ctx, curve0, curve1, len0, len1) { | ||
| 11297 | var i; | ||
| 11298 | |||
| 11299 | 	if (!len0 || !len1) { | ||
| 11300 | return; | ||
| 11301 | } | ||
| 11302 | |||
| 11303 | // building first area curve (normal) | ||
| 11304 | ctx.moveTo(curve0[0].x, curve0[0].y); | ||
| 11305 | 	for (i = 1; i < len0; ++i) { | ||
| 11306 | helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); | ||
| 11307 | } | ||
| 11308 | |||
| 11309 | // joining the two area curves | ||
| 11310 | ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); | ||
| 11311 | |||
| 11312 | // building opposite area curve (reverse) | ||
| 11313 | 	for (i = len1 - 1; i > 0; --i) { | ||
| 11314 | helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); | ||
| 11315 | } | ||
| 11316 | } | ||
| 11317 | |||
| 11318 | function doFill(ctx, points, mapper, view, color, loop) { | ||
| 11319 | var count = points.length; | ||
| 11320 | var span = view.spanGaps; | ||
| 11321 | var curve0 = []; | ||
| 11322 | var curve1 = []; | ||
| 11323 | var len0 = 0; | ||
| 11324 | var len1 = 0; | ||
| 11325 | var i, ilen, index, p0, p1, d0, d1; | ||
| 11326 | |||
| 11327 | ctx.beginPath(); | ||
| 11328 | |||
| 11329 | 	for (i = 0, ilen = (count + !!loop); i < ilen; ++i) { | ||
| 11330 | index = i % count; | ||
| 11331 | p0 = points[index]._view; | ||
| 11332 | p1 = mapper(p0, index, view); | ||
| 11333 | d0 = isDrawable(p0); | ||
| 11334 | d1 = isDrawable(p1); | ||
| 11335 | |||
| 11336 | 		if (d0 && d1) { | ||
| 11337 | len0 = curve0.push(p0); | ||
| 11338 | len1 = curve1.push(p1); | ||
| 11339 | 		} else if (len0 && len1) { | ||
| 11340 | 			if (!span) { | ||
| 11341 | drawArea(ctx, curve0, curve1, len0, len1); | ||
| 11342 | len0 = len1 = 0; | ||
| 11343 | curve0 = []; | ||
| 11344 | curve1 = []; | ||
| 11345 | 			} else { | ||
| 11346 | 				if (d0) { | ||
| 11347 | curve0.push(p0); | ||
| 11348 | } | ||
| 11349 | 				if (d1) { | ||
| 11350 | curve1.push(p1); | ||
| 11351 | } | ||
| 11352 | } | ||
| 11353 | } | ||
| 11354 | } | ||
| 11355 | |||
| 11356 | drawArea(ctx, curve0, curve1, len0, len1); | ||
| 11357 | |||
| 11358 | ctx.closePath(); | ||
| 11359 | ctx.fillStyle = color; | ||
| 11360 | ctx.fill(); | ||
| 11361 | } | ||
| 11362 | |||
| 11363 | module.exports = { | ||
| 11364 | id: 'filler', | ||
| 11365 | |||
| 11366 | 	afterDatasetsUpdate: function(chart, options) { | ||
| 11367 | var count = (chart.data.datasets || []).length; | ||
| 11368 | var propagate = options.propagate; | ||
| 11369 | var sources = []; | ||
| 11370 | var meta, i, el, source; | ||
| 11371 | |||
| 11372 | 		for (i = 0; i < count; ++i) { | ||
| 11373 | meta = chart.getDatasetMeta(i); | ||
| 11374 | el = meta.dataset; | ||
| 11375 | source = null; | ||
| 11376 | |||
| 11377 | 			if (el && el._model && el instanceof elements.Line) { | ||
| 11378 | 				source = { | ||
| 11379 | visible: chart.isDatasetVisible(i), | ||
| 11380 | fill: decodeFill(el, i, count), | ||
| 11381 | chart: chart, | ||
| 11382 | el: el | ||
| 11383 | }; | ||
| 11384 | } | ||
| 11385 | |||
| 11386 | meta.$filler = source; | ||
| 11387 | sources.push(source); | ||
| 11388 | } | ||
| 11389 | |||
| 11390 | 		for (i = 0; i < count; ++i) { | ||
| 11391 | source = sources[i]; | ||
| 11392 | 			if (!source) { | ||
| 11393 | continue; | ||
| 11394 | } | ||
| 11395 | |||
| 11396 | source.fill = resolveTarget(sources, i, propagate); | ||
| 11397 | source.boundary = computeBoundary(source); | ||
| 11398 | source.mapper = createMapper(source); | ||
| 11399 | } | ||
| 11400 | }, | ||
| 11401 | |||
| 11402 | 	beforeDatasetDraw: function(chart, args) { | ||
| 11403 | var meta = args.meta.$filler; | ||
| 11404 | 		if (!meta) { | ||
| 11405 | return; | ||
| 11406 | } | ||
| 11407 | |||
| 11408 | var ctx = chart.ctx; | ||
| 11409 | var el = meta.el; | ||
| 11410 | var view = el._view; | ||
| 11411 | var points = el._children || []; | ||
| 11412 | var mapper = meta.mapper; | ||
| 11413 | var color = view.backgroundColor || defaults.global.defaultColor; | ||
| 11414 | |||
| 11415 | 		if (mapper && color && points.length) { | ||
| 11416 | helpers.canvas.clipArea(ctx, chart.chartArea); | ||
| 11417 | doFill(ctx, points, mapper, view, color, el._loop); | ||
| 11418 | helpers.canvas.unclipArea(ctx); | ||
| 11419 | } | ||
| 11420 | } | ||
| 11421 | }; | ||
| 11422 | |||
| 11423 | },{"26":26,"41":41,"46":46}],52:[function(require,module,exports){ | ||
| 11424 | 'use strict'; | ||
| 11425 | |||
| 11426 | var defaults = require(26); | ||
| 11427 | var Element = require(27); | ||
| 11428 | var helpers = require(46); | ||
| 11429 | var layouts = require(31); | ||
| 11430 | |||
| 11431 | var noop = helpers.noop; | ||
| 11432 | |||
| 11433 | defaults._set('global', { | ||
| 11434 | 	legend: { | ||
| 11435 | display: true, | ||
| 11436 | position: 'top', | ||
| 11437 | fullWidth: true, | ||
| 11438 | reverse: false, | ||
| 11439 | weight: 1000, | ||
| 11440 | |||
| 11441 | // a callback that will handle | ||
| 11442 | 		onClick: function(e, legendItem) { | ||
| 11443 | var index = legendItem.datasetIndex; | ||
| 11444 | var ci = this.chart; | ||
| 11445 | var meta = ci.getDatasetMeta(index); | ||
| 11446 | |||
| 11447 | // See controller.isDatasetVisible comment | ||
| 11448 | meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; | ||
| 11449 | |||
| 11450 | // We hid a dataset ... rerender the chart | ||
| 11451 | ci.update(); | ||
| 11452 | }, | ||
| 11453 | |||
| 11454 | onHover: null, | ||
| 11455 | |||
| 11456 | 		labels: { | ||
| 11457 | boxWidth: 40, | ||
| 11458 | padding: 10, | ||
| 11459 | // Generates labels shown in the legend | ||
| 11460 | // Valid properties to return: | ||
| 11461 | // text : text to display | ||
| 11462 | // fillStyle : fill of coloured box | ||
| 11463 | // strokeStyle: stroke of coloured box | ||
| 11464 | // hidden : if this legend item refers to a hidden item | ||
| 11465 | // lineCap : cap style for line | ||
| 11466 | // lineDash | ||
| 11467 | // lineDashOffset : | ||
| 11468 | // lineJoin : | ||
| 11469 | // lineWidth : | ||
| 11470 | 			generateLabels: function(chart) { | ||
| 11471 | var data = chart.data; | ||
| 11472 | 				return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { | ||
| 11473 | 					return { | ||
| 11474 | text: dataset.label, | ||
| 11475 | fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), | ||
| 11476 | hidden: !chart.isDatasetVisible(i), | ||
| 11477 | lineCap: dataset.borderCapStyle, | ||
| 11478 | lineDash: dataset.borderDash, | ||
| 11479 | lineDashOffset: dataset.borderDashOffset, | ||
| 11480 | lineJoin: dataset.borderJoinStyle, | ||
| 11481 | lineWidth: dataset.borderWidth, | ||
| 11482 | strokeStyle: dataset.borderColor, | ||
| 11483 | pointStyle: dataset.pointStyle, | ||
| 11484 | |||
| 11485 | // Below is extra data used for toggling the datasets | ||
| 11486 | datasetIndex: i | ||
| 11487 | }; | ||
| 11488 | }, this) : []; | ||
| 11489 | } | ||
| 11490 | } | ||
| 11491 | }, | ||
| 11492 | |||
| 11493 | 	legendCallback: function(chart) { | ||
| 11494 | var text = []; | ||
| 11495 | 		text.push('<ul class="' + chart.id + '-legend">'); | ||
| 11496 | 		for (var i = 0; i < chart.data.datasets.length; i++) { | ||
| 11497 | 			text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>'); | ||
| 11498 | 			if (chart.data.datasets[i].label) { | ||
| 11499 | text.push(chart.data.datasets[i].label); | ||
| 11500 | } | ||
| 11501 | 			text.push('</li>'); | ||
| 11502 | } | ||
| 11503 | 		text.push('</ul>'); | ||
| 11504 | 		return text.join(''); | ||
| 11505 | } | ||
| 11506 | }); | ||
| 11507 | |||
| 11508 | /** | ||
| 11509 | * Helper function to get the box width based on the usePointStyle option | ||
| 11510 |  * @param labelopts {Object} the label options on the legend | ||
| 11511 |  * @param fontSize {Number} the label font size | ||
| 11512 |  * @return {Number} width of the color box area | ||
| 11513 | */ | ||
| 11514 | function getBoxWidth(labelOpts, fontSize) { | ||
| 11515 | return labelOpts.usePointStyle ? | ||
| 11516 | fontSize * Math.SQRT2 : | ||
| 11517 | labelOpts.boxWidth; | ||
| 11518 | } | ||
| 11519 | |||
| 11520 | /** | ||
| 11521 | * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! | ||
| 11522 | */ | ||
| 11523 | var Legend = Element.extend({ | ||
| 11524 | |||
| 11525 | 	initialize: function(config) { | ||
| 11526 | helpers.extend(this, config); | ||
| 11527 | |||
| 11528 | // Contains hit boxes for each dataset (in dataset order) | ||
| 11529 | this.legendHitBoxes = []; | ||
| 11530 | |||
| 11531 | // Are we in doughnut mode which has a different data type | ||
| 11532 | this.doughnutMode = false; | ||
| 11533 | }, | ||
| 11534 | |||
| 11535 | // These methods are ordered by lifecycle. Utilities then follow. | ||
| 11536 | // Any function defined here is inherited by all legend types. | ||
| 11537 | // Any function can be extended by the legend type | ||
| 11538 | |||
| 11539 | beforeUpdate: noop, | ||
| 11540 | 	update: function(maxWidth, maxHeight, margins) { | ||
| 11541 | var me = this; | ||
| 11542 | |||
| 11543 | // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) | ||
| 11544 | me.beforeUpdate(); | ||
| 11545 | |||
| 11546 | // Absorb the master measurements | ||
| 11547 | me.maxWidth = maxWidth; | ||
| 11548 | me.maxHeight = maxHeight; | ||
| 11549 | me.margins = margins; | ||
| 11550 | |||
| 11551 | // Dimensions | ||
| 11552 | me.beforeSetDimensions(); | ||
| 11553 | me.setDimensions(); | ||
| 11554 | me.afterSetDimensions(); | ||
| 11555 | // Labels | ||
| 11556 | me.beforeBuildLabels(); | ||
| 11557 | me.buildLabels(); | ||
| 11558 | me.afterBuildLabels(); | ||
| 11559 | |||
| 11560 | // Fit | ||
| 11561 | me.beforeFit(); | ||
| 11562 | me.fit(); | ||
| 11563 | me.afterFit(); | ||
| 11564 | // | ||
| 11565 | me.afterUpdate(); | ||
| 11566 | |||
| 11567 | return me.minSize; | ||
| 11568 | }, | ||
| 11569 | afterUpdate: noop, | ||
| 11570 | |||
| 11571 | // | ||
| 11572 | |||
| 11573 | beforeSetDimensions: noop, | ||
| 11574 | 	setDimensions: function() { | ||
| 11575 | var me = this; | ||
| 11576 | // Set the unconstrained dimension before label rotation | ||
| 11577 | 		if (me.isHorizontal()) { | ||
| 11578 | // Reset position before calculating rotation | ||
| 11579 | me.width = me.maxWidth; | ||
| 11580 | me.left = 0; | ||
| 11581 | me.right = me.width; | ||
| 11582 | 		} else { | ||
| 11583 | me.height = me.maxHeight; | ||
| 11584 | |||
| 11585 | // Reset position before calculating rotation | ||
| 11586 | me.top = 0; | ||
| 11587 | me.bottom = me.height; | ||
| 11588 | } | ||
| 11589 | |||
| 11590 | // Reset padding | ||
| 11591 | me.paddingLeft = 0; | ||
| 11592 | me.paddingTop = 0; | ||
| 11593 | me.paddingRight = 0; | ||
| 11594 | me.paddingBottom = 0; | ||
| 11595 | |||
| 11596 | // Reset minSize | ||
| 11597 | 		me.minSize = { | ||
| 11598 | width: 0, | ||
| 11599 | height: 0 | ||
| 11600 | }; | ||
| 11601 | }, | ||
| 11602 | afterSetDimensions: noop, | ||
| 11603 | |||
| 11604 | // | ||
| 11605 | |||
| 11606 | beforeBuildLabels: noop, | ||
| 11607 | 	buildLabels: function() { | ||
| 11608 | var me = this; | ||
| 11609 | 		var labelOpts = me.options.labels || {}; | ||
| 11610 | var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || []; | ||
| 11611 | |||
| 11612 | 		if (labelOpts.filter) { | ||
| 11613 | 			legendItems = legendItems.filter(function(item) { | ||
| 11614 | return labelOpts.filter(item, me.chart.data); | ||
| 11615 | }); | ||
| 11616 | } | ||
| 11617 | |||
| 11618 | 		if (me.options.reverse) { | ||
| 11619 | legendItems.reverse(); | ||
| 11620 | } | ||
| 11621 | |||
| 11622 | me.legendItems = legendItems; | ||
| 11623 | }, | ||
| 11624 | afterBuildLabels: noop, | ||
| 11625 | |||
| 11626 | // | ||
| 11627 | |||
| 11628 | beforeFit: noop, | ||
| 11629 | 	fit: function() { | ||
| 11630 | var me = this; | ||
| 11631 | var opts = me.options; | ||
| 11632 | var labelOpts = opts.labels; | ||
| 11633 | var display = opts.display; | ||
| 11634 | |||
| 11635 | var ctx = me.ctx; | ||
| 11636 | |||
| 11637 | var globalDefault = defaults.global; | ||
| 11638 | var valueOrDefault = helpers.valueOrDefault; | ||
| 11639 | var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); | ||
| 11640 | var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); | ||
| 11641 | var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); | ||
| 11642 | var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); | ||
| 11643 | |||
| 11644 | // Reset hit boxes | ||
| 11645 | var hitboxes = me.legendHitBoxes = []; | ||
| 11646 | |||
| 11647 | var minSize = me.minSize; | ||
| 11648 | var isHorizontal = me.isHorizontal(); | ||
| 11649 | |||
| 11650 | 		if (isHorizontal) { | ||
| 11651 | minSize.width = me.maxWidth; // fill all the width | ||
| 11652 | minSize.height = display ? 10 : 0; | ||
| 11653 | 		} else { | ||
| 11654 | minSize.width = display ? 10 : 0; | ||
| 11655 | minSize.height = me.maxHeight; // fill all the height | ||
| 11656 | } | ||
| 11657 | |||
| 11658 | // Increase sizes here | ||
| 11659 | 		if (display) { | ||
| 11660 | ctx.font = labelFont; | ||
| 11661 | |||
| 11662 | 			if (isHorizontal) { | ||
| 11663 | // Labels | ||
| 11664 | |||
| 11665 | // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one | ||
| 11666 | var lineWidths = me.lineWidths = [0]; | ||
| 11667 | var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; | ||
| 11668 | |||
| 11669 | ctx.textAlign = 'left'; | ||
| 11670 | ctx.textBaseline = 'top'; | ||
| 11671 | |||
| 11672 | 				helpers.each(me.legendItems, function(legendItem, i) { | ||
| 11673 | var boxWidth = getBoxWidth(labelOpts, fontSize); | ||
| 11674 | var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; | ||
| 11675 | |||
| 11676 | 					if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { | ||
| 11677 | totalHeight += fontSize + (labelOpts.padding); | ||
| 11678 | lineWidths[lineWidths.length] = me.left; | ||
| 11679 | } | ||
| 11680 | |||
| 11681 | // Store the hitbox width and height here. Final position will be updated in `draw` | ||
| 11682 | 					hitboxes[i] = { | ||
| 11683 | left: 0, | ||
| 11684 | top: 0, | ||
| 11685 | width: width, | ||
| 11686 | height: fontSize | ||
| 11687 | }; | ||
| 11688 | |||
| 11689 | lineWidths[lineWidths.length - 1] += width + labelOpts.padding; | ||
| 11690 | }); | ||
| 11691 | |||
| 11692 | minSize.height += totalHeight; | ||
| 11693 | |||
| 11694 | 			} else { | ||
| 11695 | var vPadding = labelOpts.padding; | ||
| 11696 | var columnWidths = me.columnWidths = []; | ||
| 11697 | var totalWidth = labelOpts.padding; | ||
| 11698 | var currentColWidth = 0; | ||
| 11699 | var currentColHeight = 0; | ||
| 11700 | var itemHeight = fontSize + vPadding; | ||
| 11701 | |||
| 11702 | 				helpers.each(me.legendItems, function(legendItem, i) { | ||
| 11703 | var boxWidth = getBoxWidth(labelOpts, fontSize); | ||
| 11704 | var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; | ||
| 11705 | |||
| 11706 | // If too tall, go to new column | ||
| 11707 | 					if (currentColHeight + itemHeight > minSize.height) { | ||
| 11708 | totalWidth += currentColWidth + labelOpts.padding; | ||
| 11709 | columnWidths.push(currentColWidth); // previous column width | ||
| 11710 | |||
| 11711 | currentColWidth = 0; | ||
| 11712 | currentColHeight = 0; | ||
| 11713 | } | ||
| 11714 | |||
| 11715 | // Get max width | ||
| 11716 | currentColWidth = Math.max(currentColWidth, itemWidth); | ||
| 11717 | currentColHeight += itemHeight; | ||
| 11718 | |||
| 11719 | // Store the hitbox width and height here. Final position will be updated in `draw` | ||
| 11720 | 					hitboxes[i] = { | ||
| 11721 | left: 0, | ||
| 11722 | top: 0, | ||
| 11723 | width: itemWidth, | ||
| 11724 | height: fontSize | ||
| 11725 | }; | ||
| 11726 | }); | ||
| 11727 | |||
| 11728 | totalWidth += currentColWidth; | ||
| 11729 | columnWidths.push(currentColWidth); | ||
| 11730 | minSize.width += totalWidth; | ||
| 11731 | } | ||
| 11732 | } | ||
| 11733 | |||
| 11734 | me.width = minSize.width; | ||
| 11735 | me.height = minSize.height; | ||
| 11736 | }, | ||
| 11737 | afterFit: noop, | ||
| 11738 | |||
| 11739 | // Shared Methods | ||
| 11740 | 	isHorizontal: function() { | ||
| 11741 | return this.options.position === 'top' || this.options.position === 'bottom'; | ||
| 11742 | }, | ||
| 11743 | |||
| 11744 | // Actually draw the legend on the canvas | ||
| 11745 | 	draw: function() { | ||
| 11746 | var me = this; | ||
| 11747 | var opts = me.options; | ||
| 11748 | var labelOpts = opts.labels; | ||
| 11749 | var globalDefault = defaults.global; | ||
| 11750 | var lineDefault = globalDefault.elements.line; | ||
| 11751 | var legendWidth = me.width; | ||
| 11752 | var lineWidths = me.lineWidths; | ||
| 11753 | |||
| 11754 | 		if (opts.display) { | ||
| 11755 | var ctx = me.ctx; | ||
| 11756 | var valueOrDefault = helpers.valueOrDefault; | ||
| 11757 | var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor); | ||
| 11758 | var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); | ||
| 11759 | var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); | ||
| 11760 | var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); | ||
| 11761 | var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); | ||
| 11762 | var cursor; | ||
| 11763 | |||
| 11764 | // Canvas setup | ||
| 11765 | ctx.textAlign = 'left'; | ||
| 11766 | ctx.textBaseline = 'middle'; | ||
| 11767 | ctx.lineWidth = 0.5; | ||
| 11768 | ctx.strokeStyle = fontColor; // for strikethrough effect | ||
| 11769 | ctx.fillStyle = fontColor; // render in correct colour | ||
| 11770 | ctx.font = labelFont; | ||
| 11771 | |||
| 11772 | var boxWidth = getBoxWidth(labelOpts, fontSize); | ||
| 11773 | var hitboxes = me.legendHitBoxes; | ||
| 11774 | |||
| 11775 | // current position | ||
| 11776 | 			var drawLegendBox = function(x, y, legendItem) { | ||
| 11777 | 				if (isNaN(boxWidth) || boxWidth <= 0) { | ||
| 11778 | return; | ||
| 11779 | } | ||
| 11780 | |||
| 11781 | // Set the ctx for the box | ||
| 11782 | ctx.save(); | ||
| 11783 | |||
| 11784 | ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor); | ||
| 11785 | ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); | ||
| 11786 | ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); | ||
| 11787 | ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); | ||
| 11788 | ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth); | ||
| 11789 | ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); | ||
| 11790 | var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); | ||
| 11791 | |||
| 11792 | 				if (ctx.setLineDash) { | ||
| 11793 | // IE 9 and 10 do not support line dash | ||
| 11794 | ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash)); | ||
| 11795 | } | ||
| 11796 | |||
| 11797 | 				if (opts.labels && opts.labels.usePointStyle) { | ||
| 11798 | // Recalculate x and y for drawPoint() because its expecting | ||
| 11799 | // x and y to be center of figure (instead of top left) | ||
| 11800 | var radius = fontSize * Math.SQRT2 / 2; | ||
| 11801 | var offSet = radius / Math.SQRT2; | ||
| 11802 | var centerX = x + offSet; | ||
| 11803 | var centerY = y + offSet; | ||
| 11804 | |||
| 11805 | // Draw pointStyle as legend symbol | ||
| 11806 | helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); | ||
| 11807 | 				} else { | ||
| 11808 | // Draw box as legend symbol | ||
| 11809 | 					if (!isLineWidthZero) { | ||
| 11810 | ctx.strokeRect(x, y, boxWidth, fontSize); | ||
| 11811 | } | ||
| 11812 | ctx.fillRect(x, y, boxWidth, fontSize); | ||
| 11813 | } | ||
| 11814 | |||
| 11815 | ctx.restore(); | ||
| 11816 | }; | ||
| 11817 | 			var fillText = function(x, y, legendItem, textWidth) { | ||
| 11818 | var halfFontSize = fontSize / 2; | ||
| 11819 | var xLeft = boxWidth + halfFontSize + x; | ||
| 11820 | var yMiddle = y + halfFontSize; | ||
| 11821 | |||
| 11822 | ctx.fillText(legendItem.text, xLeft, yMiddle); | ||
| 11823 | |||
| 11824 | 				if (legendItem.hidden) { | ||
| 11825 | // Strikethrough the text if hidden | ||
| 11826 | ctx.beginPath(); | ||
| 11827 | ctx.lineWidth = 2; | ||
| 11828 | ctx.moveTo(xLeft, yMiddle); | ||
| 11829 | ctx.lineTo(xLeft + textWidth, yMiddle); | ||
| 11830 | ctx.stroke(); | ||
| 11831 | } | ||
| 11832 | }; | ||
| 11833 | |||
| 11834 | // Horizontal | ||
| 11835 | var isHorizontal = me.isHorizontal(); | ||
| 11836 | 			if (isHorizontal) { | ||
| 11837 | 				cursor = { | ||
| 11838 | x: me.left + ((legendWidth - lineWidths[0]) / 2), | ||
| 11839 | y: me.top + labelOpts.padding, | ||
| 11840 | line: 0 | ||
| 11841 | }; | ||
| 11842 | 			} else { | ||
| 11843 | 				cursor = { | ||
| 11844 | x: me.left + labelOpts.padding, | ||
| 11845 | y: me.top + labelOpts.padding, | ||
| 11846 | line: 0 | ||
| 11847 | }; | ||
| 11848 | } | ||
| 11849 | |||
| 11850 | var itemHeight = fontSize + labelOpts.padding; | ||
| 11851 | 			helpers.each(me.legendItems, function(legendItem, i) { | ||
| 11852 | var textWidth = ctx.measureText(legendItem.text).width; | ||
| 11853 | var width = boxWidth + (fontSize / 2) + textWidth; | ||
| 11854 | var x = cursor.x; | ||
| 11855 | var y = cursor.y; | ||
| 11856 | |||
| 11857 | 				if (isHorizontal) { | ||
| 11858 | 					if (x + width >= legendWidth) { | ||
| 11859 | y = cursor.y += itemHeight; | ||
| 11860 | cursor.line++; | ||
| 11861 | x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); | ||
| 11862 | } | ||
| 11863 | 				} else if (y + itemHeight > me.bottom) { | ||
| 11864 | x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; | ||
| 11865 | y = cursor.y = me.top + labelOpts.padding; | ||
| 11866 | cursor.line++; | ||
| 11867 | } | ||
| 11868 | |||
| 11869 | drawLegendBox(x, y, legendItem); | ||
| 11870 | |||
| 11871 | hitboxes[i].left = x; | ||
| 11872 | hitboxes[i].top = y; | ||
| 11873 | |||
| 11874 | // Fill the actual label | ||
| 11875 | fillText(x, y, legendItem, textWidth); | ||
| 11876 | |||
| 11877 | 				if (isHorizontal) { | ||
| 11878 | cursor.x += width + (labelOpts.padding); | ||
| 11879 | 				} else { | ||
| 11880 | cursor.y += itemHeight; | ||
| 11881 | } | ||
| 11882 | |||
| 11883 | }); | ||
| 11884 | } | ||
| 11885 | }, | ||
| 11886 | |||
| 11887 | /** | ||
| 11888 | * Handle an event | ||
| 11889 | * @private | ||
| 11890 | 	 * @param {IEvent} event - The event to handle | ||
| 11891 | 	 * @return {Boolean} true if a change occured | ||
| 11892 | */ | ||
| 11893 | 	handleEvent: function(e) { | ||
| 11894 | var me = this; | ||
| 11895 | var opts = me.options; | ||
| 11896 | var type = e.type === 'mouseup' ? 'click' : e.type; | ||
| 11897 | var changed = false; | ||
| 11898 | |||
| 11899 | 		if (type === 'mousemove') { | ||
| 11900 | 			if (!opts.onHover) { | ||
| 11901 | return; | ||
| 11902 | } | ||
| 11903 | 		} else if (type === 'click') { | ||
| 11904 | 			if (!opts.onClick) { | ||
| 11905 | return; | ||
| 11906 | } | ||
| 11907 | 		} else { | ||
| 11908 | return; | ||
| 11909 | } | ||
| 11910 | |||
| 11911 | // Chart event already has relative position in it | ||
| 11912 | var x = e.x; | ||
| 11913 | var y = e.y; | ||
| 11914 | |||
| 11915 | 		if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { | ||
| 11916 | // See if we are touching one of the dataset boxes | ||
| 11917 | var lh = me.legendHitBoxes; | ||
| 11918 | 			for (var i = 0; i < lh.length; ++i) { | ||
| 11919 | var hitBox = lh[i]; | ||
| 11920 | |||
| 11921 | 				if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { | ||
| 11922 | // Touching an element | ||
| 11923 | 					if (type === 'click') { | ||
| 11924 | // use e.native for backwards compatibility | ||
| 11925 | opts.onClick.call(me, e.native, me.legendItems[i]); | ||
| 11926 | changed = true; | ||
| 11927 | break; | ||
| 11928 | 					} else if (type === 'mousemove') { | ||
| 11929 | // use e.native for backwards compatibility | ||
| 11930 | opts.onHover.call(me, e.native, me.legendItems[i]); | ||
| 11931 | changed = true; | ||
| 11932 | break; | ||
| 11933 | } | ||
| 11934 | } | ||
| 11935 | } | ||
| 11936 | } | ||
| 11937 | |||
| 11938 | return changed; | ||
| 11939 | } | ||
| 11940 | }); | ||
| 11941 | |||
| 11942 | function createNewLegendAndAttach(chart, legendOpts) { | ||
| 11943 | 	var legend = new Legend({ | ||
| 11944 | ctx: chart.ctx, | ||
| 11945 | options: legendOpts, | ||
| 11946 | chart: chart | ||
| 11947 | }); | ||
| 11948 | |||
| 11949 | layouts.configure(chart, legend, legendOpts); | ||
| 11950 | layouts.addBox(chart, legend); | ||
| 11951 | chart.legend = legend; | ||
| 11952 | } | ||
| 11953 | |||
| 11954 | module.exports = { | ||
| 11955 | id: 'legend', | ||
| 11956 | |||
| 11957 | /** | ||
| 11958 | * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making | ||
| 11959 | * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of | ||
| 11960 | * the plugin, which one will be re-exposed in the chart.js file. | ||
| 11961 | * https://github.com/chartjs/Chart.js/pull/2640 | ||
| 11962 | * @private | ||
| 11963 | */ | ||
| 11964 | _element: Legend, | ||
| 11965 | |||
| 11966 | 	beforeInit: function(chart) { | ||
| 11967 | var legendOpts = chart.options.legend; | ||
| 11968 | |||
| 11969 | 		if (legendOpts) { | ||
| 11970 | createNewLegendAndAttach(chart, legendOpts); | ||
| 11971 | } | ||
| 11972 | }, | ||
| 11973 | |||
| 11974 | 	beforeUpdate: function(chart) { | ||
| 11975 | var legendOpts = chart.options.legend; | ||
| 11976 | var legend = chart.legend; | ||
| 11977 | |||
| 11978 | 		if (legendOpts) { | ||
| 11979 | helpers.mergeIf(legendOpts, defaults.global.legend); | ||
| 11980 | |||
| 11981 | 			if (legend) { | ||
| 11982 | layouts.configure(chart, legend, legendOpts); | ||
| 11983 | legend.options = legendOpts; | ||
| 11984 | 			} else { | ||
| 11985 | createNewLegendAndAttach(chart, legendOpts); | ||
| 11986 | } | ||
| 11987 | 		} else if (legend) { | ||
| 11988 | layouts.removeBox(chart, legend); | ||
| 11989 | delete chart.legend; | ||
| 11990 | } | ||
| 11991 | }, | ||
| 11992 | |||
| 11993 | 	afterEvent: function(chart, e) { | ||
| 11994 | var legend = chart.legend; | ||
| 11995 | 		if (legend) { | ||
| 11996 | legend.handleEvent(e); | ||
| 11997 | } | ||
| 11998 | } | ||
| 11999 | }; | ||
| 12000 | |||
| 12001 | },{"26":26,"27":27,"31":31,"46":46}],53:[function(require,module,exports){ | ||
| 12002 | 'use strict'; | ||
| 12003 | |||
| 12004 | var defaults = require(26); | ||
| 12005 | var Element = require(27); | ||
| 12006 | var helpers = require(46); | ||
| 12007 | var layouts = require(31); | ||
| 12008 | |||
| 12009 | var noop = helpers.noop; | ||
| 12010 | |||
| 12011 | defaults._set('global', { | ||
| 12012 | 	title: { | ||
| 12013 | display: false, | ||
| 12014 | fontStyle: 'bold', | ||
| 12015 | fullWidth: true, | ||
| 12016 | lineHeight: 1.2, | ||
| 12017 | padding: 10, | ||
| 12018 | position: 'top', | ||
| 12019 | text: '', | ||
| 12020 | weight: 2000 // by default greater than legend (1000) to be above | ||
| 12021 | } | ||
| 12022 | }); | ||
| 12023 | |||
| 12024 | /** | ||
| 12025 | * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! | ||
| 12026 | */ | ||
| 12027 | var Title = Element.extend({ | ||
| 12028 | 	initialize: function(config) { | ||
| 12029 | var me = this; | ||
| 12030 | helpers.extend(me, config); | ||
| 12031 | |||
| 12032 | // Contains hit boxes for each dataset (in dataset order) | ||
| 12033 | me.legendHitBoxes = []; | ||
| 12034 | }, | ||
| 12035 | |||
| 12036 | // These methods are ordered by lifecycle. Utilities then follow. | ||
| 12037 | |||
| 12038 | beforeUpdate: noop, | ||
| 12039 | 	update: function(maxWidth, maxHeight, margins) { | ||
| 12040 | var me = this; | ||
| 12041 | |||
| 12042 | // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) | ||
| 12043 | me.beforeUpdate(); | ||
| 12044 | |||
| 12045 | // Absorb the master measurements | ||
| 12046 | me.maxWidth = maxWidth; | ||
| 12047 | me.maxHeight = maxHeight; | ||
| 12048 | me.margins = margins; | ||
| 12049 | |||
| 12050 | // Dimensions | ||
| 12051 | me.beforeSetDimensions(); | ||
| 12052 | me.setDimensions(); | ||
| 12053 | me.afterSetDimensions(); | ||
| 12054 | // Labels | ||
| 12055 | me.beforeBuildLabels(); | ||
| 12056 | me.buildLabels(); | ||
| 12057 | me.afterBuildLabels(); | ||
| 12058 | |||
| 12059 | // Fit | ||
| 12060 | me.beforeFit(); | ||
| 12061 | me.fit(); | ||
| 12062 | me.afterFit(); | ||
| 12063 | // | ||
| 12064 | me.afterUpdate(); | ||
| 12065 | |||
| 12066 | return me.minSize; | ||
| 12067 | |||
| 12068 | }, | ||
| 12069 | afterUpdate: noop, | ||
| 12070 | |||
| 12071 | // | ||
| 12072 | |||
| 12073 | beforeSetDimensions: noop, | ||
| 12074 | 	setDimensions: function() { | ||
| 12075 | var me = this; | ||
| 12076 | // Set the unconstrained dimension before label rotation | ||
| 12077 | 		if (me.isHorizontal()) { | ||
| 12078 | // Reset position before calculating rotation | ||
| 12079 | me.width = me.maxWidth; | ||
| 12080 | me.left = 0; | ||
| 12081 | me.right = me.width; | ||
| 12082 | 		} else { | ||
| 12083 | me.height = me.maxHeight; | ||
| 12084 | |||
| 12085 | // Reset position before calculating rotation | ||
| 12086 | me.top = 0; | ||
| 12087 | me.bottom = me.height; | ||
| 12088 | } | ||
| 12089 | |||
| 12090 | // Reset padding | ||
| 12091 | me.paddingLeft = 0; | ||
| 12092 | me.paddingTop = 0; | ||
| 12093 | me.paddingRight = 0; | ||
| 12094 | me.paddingBottom = 0; | ||
| 12095 | |||
| 12096 | // Reset minSize | ||
| 12097 | 		me.minSize = { | ||
| 12098 | width: 0, | ||
| 12099 | height: 0 | ||
| 12100 | }; | ||
| 12101 | }, | ||
| 12102 | afterSetDimensions: noop, | ||
| 12103 | |||
| 12104 | // | ||
| 12105 | |||
| 12106 | beforeBuildLabels: noop, | ||
| 12107 | buildLabels: noop, | ||
| 12108 | afterBuildLabels: noop, | ||
| 12109 | |||
| 12110 | // | ||
| 12111 | |||
| 12112 | beforeFit: noop, | ||
| 12113 | 	fit: function() { | ||
| 12114 | var me = this; | ||
| 12115 | var valueOrDefault = helpers.valueOrDefault; | ||
| 12116 | var opts = me.options; | ||
| 12117 | var display = opts.display; | ||
| 12118 | var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize); | ||
| 12119 | var minSize = me.minSize; | ||
| 12120 | var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1; | ||
| 12121 | var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); | ||
| 12122 | var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0; | ||
| 12123 | |||
| 12124 | 		if (me.isHorizontal()) { | ||
| 12125 | minSize.width = me.maxWidth; // fill all the width | ||
| 12126 | minSize.height = textSize; | ||
| 12127 | 		} else { | ||
| 12128 | minSize.width = textSize; | ||
| 12129 | minSize.height = me.maxHeight; // fill all the height | ||
| 12130 | } | ||
| 12131 | |||
| 12132 | me.width = minSize.width; | ||
| 12133 | me.height = minSize.height; | ||
| 12134 | |||
| 12135 | }, | ||
| 12136 | afterFit: noop, | ||
| 12137 | |||
| 12138 | // Shared Methods | ||
| 12139 | 	isHorizontal: function() { | ||
| 12140 | var pos = this.options.position; | ||
| 12141 | return pos === 'top' || pos === 'bottom'; | ||
| 12142 | }, | ||
| 12143 | |||
| 12144 | // Actually draw the title block on the canvas | ||
| 12145 | 	draw: function() { | ||
| 12146 | var me = this; | ||
| 12147 | var ctx = me.ctx; | ||
| 12148 | var valueOrDefault = helpers.valueOrDefault; | ||
| 12149 | var opts = me.options; | ||
| 12150 | var globalDefaults = defaults.global; | ||
| 12151 | |||
| 12152 | 		if (opts.display) { | ||
| 12153 | var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize); | ||
| 12154 | var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle); | ||
| 12155 | var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily); | ||
| 12156 | var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); | ||
| 12157 | var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); | ||
| 12158 | var offset = lineHeight / 2 + opts.padding; | ||
| 12159 | var rotation = 0; | ||
| 12160 | var top = me.top; | ||
| 12161 | var left = me.left; | ||
| 12162 | var bottom = me.bottom; | ||
| 12163 | var right = me.right; | ||
| 12164 | var maxWidth, titleX, titleY; | ||
| 12165 | |||
| 12166 | ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour | ||
| 12167 | ctx.font = titleFont; | ||
| 12168 | |||
| 12169 | // Horizontal | ||
| 12170 | 			if (me.isHorizontal()) { | ||
| 12171 | titleX = left + ((right - left) / 2); // midpoint of the width | ||
| 12172 | titleY = top + offset; | ||
| 12173 | maxWidth = right - left; | ||
| 12174 | 			} else { | ||
| 12175 | titleX = opts.position === 'left' ? left + offset : right - offset; | ||
| 12176 | titleY = top + ((bottom - top) / 2); | ||
| 12177 | maxWidth = bottom - top; | ||
| 12178 | rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); | ||
| 12179 | } | ||
| 12180 | |||
| 12181 | ctx.save(); | ||
| 12182 | ctx.translate(titleX, titleY); | ||
| 12183 | ctx.rotate(rotation); | ||
| 12184 | ctx.textAlign = 'center'; | ||
| 12185 | ctx.textBaseline = 'middle'; | ||
| 12186 | |||
| 12187 | var text = opts.text; | ||
| 12188 | 			if (helpers.isArray(text)) { | ||
| 12189 | var y = 0; | ||
| 12190 | 				for (var i = 0; i < text.length; ++i) { | ||
| 12191 | ctx.fillText(text[i], 0, y, maxWidth); | ||
| 12192 | y += lineHeight; | ||
| 12193 | } | ||
| 12194 | 			} else { | ||
| 12195 | ctx.fillText(text, 0, 0, maxWidth); | ||
| 12196 | } | ||
| 12197 | |||
| 12198 | ctx.restore(); | ||
| 12199 | } | ||
| 12200 | } | ||
| 12201 | }); | ||
| 12202 | |||
| 12203 | function createNewTitleBlockAndAttach(chart, titleOpts) { | ||
| 12204 | 	var title = new Title({ | ||
| 12205 | ctx: chart.ctx, | ||
| 12206 | options: titleOpts, | ||
| 12207 | chart: chart | ||
| 12208 | }); | ||
| 12209 | |||
| 12210 | layouts.configure(chart, title, titleOpts); | ||
| 12211 | layouts.addBox(chart, title); | ||
| 12212 | chart.titleBlock = title; | ||
| 12213 | } | ||
| 12214 | |||
| 12215 | module.exports = { | ||
| 12216 | id: 'title', | ||
| 12217 | |||
| 12218 | /** | ||
| 12219 | * Backward compatibility: since 2.1.5, the title is registered as a plugin, making | ||
| 12220 | * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of | ||
| 12221 | * the plugin, which one will be re-exposed in the chart.js file. | ||
| 12222 | * https://github.com/chartjs/Chart.js/pull/2640 | ||
| 12223 | * @private | ||
| 12224 | */ | ||
| 12225 | _element: Title, | ||
| 12226 | |||
| 12227 | 	beforeInit: function(chart) { | ||
| 12228 | var titleOpts = chart.options.title; | ||
| 12229 | |||
| 12230 | 		if (titleOpts) { | ||
| 12231 | createNewTitleBlockAndAttach(chart, titleOpts); | ||
| 12232 | } | ||
| 12233 | }, | ||
| 12234 | |||
| 12235 | 	beforeUpdate: function(chart) { | ||
| 12236 | var titleOpts = chart.options.title; | ||
| 12237 | var titleBlock = chart.titleBlock; | ||
| 12238 | |||
| 12239 | 		if (titleOpts) { | ||
| 12240 | helpers.mergeIf(titleOpts, defaults.global.title); | ||
| 12241 | |||
| 12242 | 			if (titleBlock) { | ||
| 12243 | layouts.configure(chart, titleBlock, titleOpts); | ||
| 12244 | titleBlock.options = titleOpts; | ||
| 12245 | 			} else { | ||
| 12246 | createNewTitleBlockAndAttach(chart, titleOpts); | ||
| 12247 | } | ||
| 12248 | 		} else if (titleBlock) { | ||
| 12249 | layouts.removeBox(chart, titleBlock); | ||
| 12250 | delete chart.titleBlock; | ||
| 12251 | } | ||
| 12252 | } | ||
| 12253 | }; | ||
| 12254 | |||
| 12255 | },{"26":26,"27":27,"31":31,"46":46}],54:[function(require,module,exports){ | ||
| 12256 | 'use strict'; | ||
| 12257 | |||
| 12258 | var Scale = require(33); | ||
| 12259 | var scaleService = require(34); | ||
| 12260 | |||
| 12261 | module.exports = function() { | ||
| 12262 | |||
| 12263 | // Default config for a category scale | ||
| 12264 | 	var defaultConfig = { | ||
| 12265 | position: 'bottom' | ||
| 12266 | }; | ||
| 12267 | |||
| 12268 | 	var DatasetScale = Scale.extend({ | ||
| 12269 | /** | ||
| 12270 | * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those | ||
| 12271 | * else fall back to data.labels | ||
| 12272 | * @private | ||
| 12273 | */ | ||
| 12274 | 		getLabels: function() { | ||
| 12275 | var data = this.chart.data; | ||
| 12276 | return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; | ||
| 12277 | }, | ||
| 12278 | |||
| 12279 | 		determineDataLimits: function() { | ||
| 12280 | var me = this; | ||
| 12281 | var labels = me.getLabels(); | ||
| 12282 | me.minIndex = 0; | ||
| 12283 | me.maxIndex = labels.length - 1; | ||
| 12284 | var findIndex; | ||
| 12285 | |||
| 12286 | 			if (me.options.ticks.min !== undefined) { | ||
| 12287 | // user specified min value | ||
| 12288 | findIndex = labels.indexOf(me.options.ticks.min); | ||
| 12289 | me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; | ||
| 12290 | } | ||
| 12291 | |||
| 12292 | 			if (me.options.ticks.max !== undefined) { | ||
| 12293 | // user specified max value | ||
| 12294 | findIndex = labels.indexOf(me.options.ticks.max); | ||
| 12295 | me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; | ||
| 12296 | } | ||
| 12297 | |||
| 12298 | me.min = labels[me.minIndex]; | ||
| 12299 | me.max = labels[me.maxIndex]; | ||
| 12300 | }, | ||
| 12301 | |||
| 12302 | 		buildTicks: function() { | ||
| 12303 | var me = this; | ||
| 12304 | var labels = me.getLabels(); | ||
| 12305 | // If we are viewing some subset of labels, slice the original array | ||
| 12306 | me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); | ||
| 12307 | }, | ||
| 12308 | |||
| 12309 | 		getLabelForIndex: function(index, datasetIndex) { | ||
| 12310 | var me = this; | ||
| 12311 | var data = me.chart.data; | ||
| 12312 | var isHorizontal = me.isHorizontal(); | ||
| 12313 | |||
| 12314 | 			if (data.yLabels && !isHorizontal) { | ||
| 12315 | return me.getRightValue(data.datasets[datasetIndex].data[index]); | ||
| 12316 | } | ||
| 12317 | return me.ticks[index - me.minIndex]; | ||
| 12318 | }, | ||
| 12319 | |||
| 12320 | // Used to get data value locations. Value can either be an index or a numerical value | ||
| 12321 | 		getPixelForValue: function(value, index) { | ||
| 12322 | var me = this; | ||
| 12323 | var offset = me.options.offset; | ||
| 12324 | // 1 is added because we need the length but we have the indexes | ||
| 12325 | var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1); | ||
| 12326 | |||
| 12327 | // If value is a data object, then index is the index in the data array, | ||
| 12328 | // not the index of the scale. We need to change that. | ||
| 12329 | var valueCategory; | ||
| 12330 | 			if (value !== undefined && value !== null) { | ||
| 12331 | valueCategory = me.isHorizontal() ? value.x : value.y; | ||
| 12332 | } | ||
| 12333 | 			if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { | ||
| 12334 | var labels = me.getLabels(); | ||
| 12335 | value = valueCategory || value; | ||
| 12336 | var idx = labels.indexOf(value); | ||
| 12337 | index = idx !== -1 ? idx : index; | ||
| 12338 | } | ||
| 12339 | |||
| 12340 | 			if (me.isHorizontal()) { | ||
| 12341 | var valueWidth = me.width / offsetAmt; | ||
| 12342 | var widthOffset = (valueWidth * (index - me.minIndex)); | ||
| 12343 | |||
| 12344 | 				if (offset) { | ||
| 12345 | widthOffset += (valueWidth / 2); | ||
| 12346 | } | ||
| 12347 | |||
| 12348 | return me.left + Math.round(widthOffset); | ||
| 12349 | } | ||
| 12350 | var valueHeight = me.height / offsetAmt; | ||
| 12351 | var heightOffset = (valueHeight * (index - me.minIndex)); | ||
| 12352 | |||
| 12353 | 			if (offset) { | ||
| 12354 | heightOffset += (valueHeight / 2); | ||
| 12355 | } | ||
| 12356 | |||
| 12357 | return me.top + Math.round(heightOffset); | ||
| 12358 | }, | ||
| 12359 | 		getPixelForTick: function(index) { | ||
| 12360 | return this.getPixelForValue(this.ticks[index], index + this.minIndex, null); | ||
| 12361 | }, | ||
| 12362 | 		getValueForPixel: function(pixel) { | ||
| 12363 | var me = this; | ||
| 12364 | var offset = me.options.offset; | ||
| 12365 | var value; | ||
| 12366 | var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1); | ||
| 12367 | var horz = me.isHorizontal(); | ||
| 12368 | var valueDimension = (horz ? me.width : me.height) / offsetAmt; | ||
| 12369 | |||
| 12370 | pixel -= horz ? me.left : me.top; | ||
| 12371 | |||
| 12372 | 			if (offset) { | ||
| 12373 | pixel -= (valueDimension / 2); | ||
| 12374 | } | ||
| 12375 | |||
| 12376 | 			if (pixel <= 0) { | ||
| 12377 | value = 0; | ||
| 12378 | 			} else { | ||
| 12379 | value = Math.round(pixel / valueDimension); | ||
| 12380 | } | ||
| 12381 | |||
| 12382 | return value + me.minIndex; | ||
| 12383 | }, | ||
| 12384 | 		getBasePixel: function() { | ||
| 12385 | return this.bottom; | ||
| 12386 | } | ||
| 12387 | }); | ||
| 12388 | |||
| 12389 | 	scaleService.registerScaleType('category', DatasetScale, defaultConfig); | ||
| 12390 | }; | ||
| 12391 | |||
| 12392 | },{"33":33,"34":34}],55:[function(require,module,exports){ | ||
| 12393 | 'use strict'; | ||
| 12394 | |||
| 12395 | var defaults = require(26); | ||
| 12396 | var helpers = require(46); | ||
| 12397 | var scaleService = require(34); | ||
| 12398 | var Ticks = require(35); | ||
| 12399 | |||
| 12400 | module.exports = function(Chart) { | ||
| 12401 | |||
| 12402 | 	var defaultConfig = { | ||
| 12403 | position: 'left', | ||
| 12404 | 		ticks: { | ||
| 12405 | callback: Ticks.formatters.linear | ||
| 12406 | } | ||
| 12407 | }; | ||
| 12408 | |||
| 12409 | 	var LinearScale = Chart.LinearScaleBase.extend({ | ||
| 12410 | |||
| 12411 | 		determineDataLimits: function() { | ||
| 12412 | var me = this; | ||
| 12413 | var opts = me.options; | ||
| 12414 | var chart = me.chart; | ||
| 12415 | var data = chart.data; | ||
| 12416 | var datasets = data.datasets; | ||
| 12417 | var isHorizontal = me.isHorizontal(); | ||
| 12418 | var DEFAULT_MIN = 0; | ||
| 12419 | var DEFAULT_MAX = 1; | ||
| 12420 | |||
| 12421 | 			function IDMatches(meta) { | ||
| 12422 | return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; | ||
| 12423 | } | ||
| 12424 | |||
| 12425 | // First Calculate the range | ||
| 12426 | me.min = null; | ||
| 12427 | me.max = null; | ||
| 12428 | |||
| 12429 | var hasStacks = opts.stacked; | ||
| 12430 | 			if (hasStacks === undefined) { | ||
| 12431 | 				helpers.each(datasets, function(dataset, datasetIndex) { | ||
| 12432 | 					if (hasStacks) { | ||
| 12433 | return; | ||
| 12434 | } | ||
| 12435 | |||
| 12436 | var meta = chart.getDatasetMeta(datasetIndex); | ||
| 12437 | if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && | ||
| 12438 | 						meta.stack !== undefined) { | ||
| 12439 | hasStacks = true; | ||
| 12440 | } | ||
| 12441 | }); | ||
| 12442 | } | ||
| 12443 | |||
| 12444 | 			if (opts.stacked || hasStacks) { | ||
| 12445 | 				var valuesPerStack = {}; | ||
| 12446 | |||
| 12447 | 				helpers.each(datasets, function(dataset, datasetIndex) { | ||
| 12448 | var meta = chart.getDatasetMeta(datasetIndex); | ||
| 12449 | var key = [ | ||
| 12450 | meta.type, | ||
| 12451 | // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined | ||
| 12452 | ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), | ||
| 12453 | meta.stack | ||
| 12454 | 					].join('.'); | ||
| 12455 | |||
| 12456 | 					if (valuesPerStack[key] === undefined) { | ||
| 12457 | 						valuesPerStack[key] = { | ||
| 12458 | positiveValues: [], | ||
| 12459 | negativeValues: [] | ||
| 12460 | }; | ||
| 12461 | } | ||
| 12462 | |||
| 12463 | // Store these per type | ||
| 12464 | var positiveValues = valuesPerStack[key].positiveValues; | ||
| 12465 | var negativeValues = valuesPerStack[key].negativeValues; | ||
| 12466 | |||
| 12467 | 					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { | ||
| 12468 | 						helpers.each(dataset.data, function(rawValue, index) { | ||
| 12469 | var value = +me.getRightValue(rawValue); | ||
| 12470 | 							if (isNaN(value) || meta.data[index].hidden) { | ||
| 12471 | return; | ||
| 12472 | } | ||
| 12473 | |||
| 12474 | positiveValues[index] = positiveValues[index] || 0; | ||
| 12475 | negativeValues[index] = negativeValues[index] || 0; | ||
| 12476 | |||
| 12477 | 							if (opts.relativePoints) { | ||
| 12478 | positiveValues[index] = 100; | ||
| 12479 | 							} else if (value < 0) { | ||
| 12480 | negativeValues[index] += value; | ||
| 12481 | 							} else { | ||
| 12482 | positiveValues[index] += value; | ||
| 12483 | } | ||
| 12484 | }); | ||
| 12485 | } | ||
| 12486 | }); | ||
| 12487 | |||
| 12488 | 				helpers.each(valuesPerStack, function(valuesForType) { | ||
| 12489 | var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); | ||
| 12490 | var minVal = helpers.min(values); | ||
| 12491 | var maxVal = helpers.max(values); | ||
| 12492 | me.min = me.min === null ? minVal : Math.min(me.min, minVal); | ||
| 12493 | me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); | ||
| 12494 | }); | ||
| 12495 | |||
| 12496 | 			} else { | ||
| 12497 | 				helpers.each(datasets, function(dataset, datasetIndex) { | ||
| 12498 | var meta = chart.getDatasetMeta(datasetIndex); | ||
| 12499 | 					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { | ||
| 12500 | 						helpers.each(dataset.data, function(rawValue, index) { | ||
| 12501 | var value = +me.getRightValue(rawValue); | ||
| 12502 | 							if (isNaN(value) || meta.data[index].hidden) { | ||
| 12503 | return; | ||
| 12504 | } | ||
| 12505 | |||
| 12506 | 							if (me.min === null) { | ||
| 12507 | me.min = value; | ||
| 12508 | 							} else if (value < me.min) { | ||
| 12509 | me.min = value; | ||
| 12510 | } | ||
| 12511 | |||
| 12512 | 							if (me.max === null) { | ||
| 12513 | me.max = value; | ||
| 12514 | 							} else if (value > me.max) { | ||
| 12515 | me.max = value; | ||
| 12516 | } | ||
| 12517 | }); | ||
| 12518 | } | ||
| 12519 | }); | ||
| 12520 | } | ||
| 12521 | |||
| 12522 | me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; | ||
| 12523 | me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; | ||
| 12524 | |||
| 12525 | // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero | ||
| 12526 | this.handleTickRangeOptions(); | ||
| 12527 | }, | ||
| 12528 | 		getTickLimit: function() { | ||
| 12529 | var maxTicks; | ||
| 12530 | var me = this; | ||
| 12531 | var tickOpts = me.options.ticks; | ||
| 12532 | |||
| 12533 | 			if (me.isHorizontal()) { | ||
| 12534 | maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); | ||
| 12535 | 			} else { | ||
| 12536 | // The factor of 2 used to scale the font size has been experimentally determined. | ||
| 12537 | var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); | ||
| 12538 | maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); | ||
| 12539 | } | ||
| 12540 | |||
| 12541 | return maxTicks; | ||
| 12542 | }, | ||
| 12543 | // Called after the ticks are built. We need | ||
| 12544 | 		handleDirectionalChanges: function() { | ||
| 12545 | 			if (!this.isHorizontal()) { | ||
| 12546 | // We are in a vertical orientation. The top value is the highest. So reverse the array | ||
| 12547 | this.ticks.reverse(); | ||
| 12548 | } | ||
| 12549 | }, | ||
| 12550 | 		getLabelForIndex: function(index, datasetIndex) { | ||
| 12551 | return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); | ||
| 12552 | }, | ||
| 12553 | // Utils | ||
| 12554 | 		getPixelForValue: function(value) { | ||
| 12555 | // This must be called after fit has been run so that | ||
| 12556 | // this.left, this.top, this.right, and this.bottom have been defined | ||
| 12557 | var me = this; | ||
| 12558 | var start = me.start; | ||
| 12559 | |||
| 12560 | var rightValue = +me.getRightValue(value); | ||
| 12561 | var pixel; | ||
| 12562 | var range = me.end - start; | ||
| 12563 | |||
| 12564 | 			if (me.isHorizontal()) { | ||
| 12565 | pixel = me.left + (me.width / range * (rightValue - start)); | ||
| 12566 | 			} else { | ||
| 12567 | pixel = me.bottom - (me.height / range * (rightValue - start)); | ||
| 12568 | } | ||
| 12569 | return pixel; | ||
| 12570 | }, | ||
| 12571 | 		getValueForPixel: function(pixel) { | ||
| 12572 | var me = this; | ||
| 12573 | var isHorizontal = me.isHorizontal(); | ||
| 12574 | var innerDimension = isHorizontal ? me.width : me.height; | ||
| 12575 | var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; | ||
| 12576 | return me.start + ((me.end - me.start) * offset); | ||
| 12577 | }, | ||
| 12578 | 		getPixelForTick: function(index) { | ||
| 12579 | return this.getPixelForValue(this.ticksAsNumbers[index]); | ||
| 12580 | } | ||
| 12581 | }); | ||
| 12582 | |||
| 12583 | 	scaleService.registerScaleType('linear', LinearScale, defaultConfig); | ||
| 12584 | }; | ||
| 12585 | |||
| 12586 | },{"26":26,"34":34,"35":35,"46":46}],56:[function(require,module,exports){ | ||
| 12587 | 'use strict'; | ||
| 12588 | |||
| 12589 | var helpers = require(46); | ||
| 12590 | var Scale = require(33); | ||
| 12591 | |||
| 12592 | /** | ||
| 12593 | * Generate a set of linear ticks | ||
| 12594 | * @param generationOptions the options used to generate the ticks | ||
| 12595 | * @param dataRange the range of the data | ||
| 12596 |  * @returns {Array<Number>} array of tick values | ||
| 12597 | */ | ||
| 12598 | function generateTicks(generationOptions, dataRange) { | ||
| 12599 | var ticks = []; | ||
| 12600 | // To get a "nice" value for the tick spacing, we will use the appropriately named | ||
| 12601 | // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks | ||
| 12602 | // for details. | ||
| 12603 | |||
| 12604 | var factor; | ||
| 12605 | var precision; | ||
| 12606 | var spacing; | ||
| 12607 | |||
| 12608 | 	if (generationOptions.stepSize && generationOptions.stepSize > 0) { | ||
| 12609 | spacing = generationOptions.stepSize; | ||
| 12610 | 	} else { | ||
| 12611 | var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); | ||
| 12612 | spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true); | ||
| 12613 | |||
| 12614 | precision = generationOptions.precision; | ||
| 12615 | 		if (precision !== undefined) { | ||
| 12616 | // If the user specified a precision, round to that number of decimal places | ||
| 12617 | factor = Math.pow(10, precision); | ||
| 12618 | spacing = Math.ceil(spacing * factor) / factor; | ||
| 12619 | } | ||
| 12620 | } | ||
| 12621 | var niceMin = Math.floor(dataRange.min / spacing) * spacing; | ||
| 12622 | var niceMax = Math.ceil(dataRange.max / spacing) * spacing; | ||
| 12623 | |||
| 12624 | // If min, max and stepSize is set and they make an evenly spaced scale use it. | ||
| 12625 | 	if (!helpers.isNullOrUndef(generationOptions.min) && !helpers.isNullOrUndef(generationOptions.max) && generationOptions.stepSize) { | ||
| 12626 | // If very close to our whole number, use it. | ||
| 12627 | 		if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) { | ||
| 12628 | niceMin = generationOptions.min; | ||
| 12629 | niceMax = generationOptions.max; | ||
| 12630 | } | ||
| 12631 | } | ||
| 12632 | |||
| 12633 | var numSpaces = (niceMax - niceMin) / spacing; | ||
| 12634 | // If very close to our rounded value, use it. | ||
| 12635 | 	if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { | ||
| 12636 | numSpaces = Math.round(numSpaces); | ||
| 12637 | 	} else { | ||
| 12638 | numSpaces = Math.ceil(numSpaces); | ||
| 12639 | } | ||
| 12640 | |||
| 12641 | precision = 1; | ||
| 12642 | 	if (spacing < 1) { | ||
| 12643 | precision = Math.pow(10, 1 - Math.floor(helpers.log10(spacing))); | ||
| 12644 | niceMin = Math.round(niceMin * precision) / precision; | ||
| 12645 | niceMax = Math.round(niceMax * precision) / precision; | ||
| 12646 | } | ||
| 12647 | ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin); | ||
| 12648 | 	for (var j = 1; j < numSpaces; ++j) { | ||
| 12649 | ticks.push(Math.round((niceMin + j * spacing) * precision) / precision); | ||
| 12650 | } | ||
| 12651 | ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax); | ||
| 12652 | |||
| 12653 | return ticks; | ||
| 12654 | } | ||
| 12655 | |||
| 12656 | module.exports = function(Chart) { | ||
| 12657 | |||
| 12658 | var noop = helpers.noop; | ||
| 12659 | |||
| 12660 | 	Chart.LinearScaleBase = Scale.extend({ | ||
| 12661 | 		getRightValue: function(value) { | ||
| 12662 | 			if (typeof value === 'string') { | ||
| 12663 | return +value; | ||
| 12664 | } | ||
| 12665 | return Scale.prototype.getRightValue.call(this, value); | ||
| 12666 | }, | ||
| 12667 | |||
| 12668 | 		handleTickRangeOptions: function() { | ||
| 12669 | var me = this; | ||
| 12670 | var opts = me.options; | ||
| 12671 | var tickOpts = opts.ticks; | ||
| 12672 | |||
| 12673 | // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, | ||
| 12674 | // do nothing since that would make the chart weird. If the user really wants a weird chart | ||
| 12675 | // axis, they can manually override it | ||
| 12676 | 			if (tickOpts.beginAtZero) { | ||
| 12677 | var minSign = helpers.sign(me.min); | ||
| 12678 | var maxSign = helpers.sign(me.max); | ||
| 12679 | |||
| 12680 | 				if (minSign < 0 && maxSign < 0) { | ||
| 12681 | // move the top up to 0 | ||
| 12682 | me.max = 0; | ||
| 12683 | 				} else if (minSign > 0 && maxSign > 0) { | ||
| 12684 | // move the bottom down to 0 | ||
| 12685 | me.min = 0; | ||
| 12686 | } | ||
| 12687 | } | ||
| 12688 | |||
| 12689 | var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; | ||
| 12690 | var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; | ||
| 12691 | |||
| 12692 | 			if (tickOpts.min !== undefined) { | ||
| 12693 | me.min = tickOpts.min; | ||
| 12694 | 			} else if (tickOpts.suggestedMin !== undefined) { | ||
| 12695 | 				if (me.min === null) { | ||
| 12696 | me.min = tickOpts.suggestedMin; | ||
| 12697 | 				} else { | ||
| 12698 | me.min = Math.min(me.min, tickOpts.suggestedMin); | ||
| 12699 | } | ||
| 12700 | } | ||
| 12701 | |||
| 12702 | 			if (tickOpts.max !== undefined) { | ||
| 12703 | me.max = tickOpts.max; | ||
| 12704 | 			} else if (tickOpts.suggestedMax !== undefined) { | ||
| 12705 | 				if (me.max === null) { | ||
| 12706 | me.max = tickOpts.suggestedMax; | ||
| 12707 | 				} else { | ||
| 12708 | me.max = Math.max(me.max, tickOpts.suggestedMax); | ||
| 12709 | } | ||
| 12710 | } | ||
| 12711 | |||
| 12712 | 			if (setMin !== setMax) { | ||
| 12713 | // We set the min or the max but not both. | ||
| 12714 | // So ensure that our range is good | ||
| 12715 | // Inverted or 0 length range can happen when | ||
| 12716 | // ticks.min is set, and no datasets are visible | ||
| 12717 | 				if (me.min >= me.max) { | ||
| 12718 | 					if (setMin) { | ||
| 12719 | me.max = me.min + 1; | ||
| 12720 | 					} else { | ||
| 12721 | me.min = me.max - 1; | ||
| 12722 | } | ||
| 12723 | } | ||
| 12724 | } | ||
| 12725 | |||
| 12726 | 			if (me.min === me.max) { | ||
| 12727 | me.max++; | ||
| 12728 | |||
| 12729 | 				if (!tickOpts.beginAtZero) { | ||
| 12730 | me.min--; | ||
| 12731 | } | ||
| 12732 | } | ||
| 12733 | }, | ||
| 12734 | getTickLimit: noop, | ||
| 12735 | handleDirectionalChanges: noop, | ||
| 12736 | |||
| 12737 | 		buildTicks: function() { | ||
| 12738 | var me = this; | ||
| 12739 | var opts = me.options; | ||
| 12740 | var tickOpts = opts.ticks; | ||
| 12741 | |||
| 12742 | // Figure out what the max number of ticks we can support it is based on the size of | ||
| 12743 | // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 | ||
| 12744 | // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on | ||
| 12745 | // the graph. Make sure we always have at least 2 ticks | ||
| 12746 | var maxTicks = me.getTickLimit(); | ||
| 12747 | maxTicks = Math.max(2, maxTicks); | ||
| 12748 | |||
| 12749 | 			var numericGeneratorOptions = { | ||
| 12750 | maxTicks: maxTicks, | ||
| 12751 | min: tickOpts.min, | ||
| 12752 | max: tickOpts.max, | ||
| 12753 | precision: tickOpts.precision, | ||
| 12754 | stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) | ||
| 12755 | }; | ||
| 12756 | var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); | ||
| 12757 | |||
| 12758 | me.handleDirectionalChanges(); | ||
| 12759 | |||
| 12760 | // At this point, we need to update our max and min given the tick values since we have expanded the | ||
| 12761 | // range of the scale | ||
| 12762 | me.max = helpers.max(ticks); | ||
| 12763 | me.min = helpers.min(ticks); | ||
| 12764 | |||
| 12765 | 			if (tickOpts.reverse) { | ||
| 12766 | ticks.reverse(); | ||
| 12767 | |||
| 12768 | me.start = me.max; | ||
| 12769 | me.end = me.min; | ||
| 12770 | 			} else { | ||
| 12771 | me.start = me.min; | ||
| 12772 | me.end = me.max; | ||
| 12773 | } | ||
| 12774 | }, | ||
| 12775 | 		convertTicksToLabels: function() { | ||
| 12776 | var me = this; | ||
| 12777 | me.ticksAsNumbers = me.ticks.slice(); | ||
| 12778 | me.zeroLineIndex = me.ticks.indexOf(0); | ||
| 12779 | |||
| 12780 | Scale.prototype.convertTicksToLabels.call(me); | ||
| 12781 | } | ||
| 12782 | }); | ||
| 12783 | }; | ||
| 12784 | |||
| 12785 | },{"33":33,"46":46}],57:[function(require,module,exports){ | ||
| 12786 | 'use strict'; | ||
| 12787 | |||
| 12788 | var helpers = require(46); | ||
| 12789 | var Scale = require(33); | ||
| 12790 | var scaleService = require(34); | ||
| 12791 | var Ticks = require(35); | ||
| 12792 | |||
| 12793 | /** | ||
| 12794 | * Generate a set of logarithmic ticks | ||
| 12795 | * @param generationOptions the options used to generate the ticks | ||
| 12796 | * @param dataRange the range of the data | ||
| 12797 |  * @returns {Array<Number>} array of tick values | ||
| 12798 | */ | ||
| 12799 | function generateTicks(generationOptions, dataRange) { | ||
| 12800 | var ticks = []; | ||
| 12801 | var valueOrDefault = helpers.valueOrDefault; | ||
| 12802 | |||
| 12803 | // Figure out what the max number of ticks we can support it is based on the size of | ||
| 12804 | // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 | ||
| 12805 | // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on | ||
| 12806 | // the graph | ||
| 12807 | var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min)))); | ||
| 12808 | |||
| 12809 | var endExp = Math.floor(helpers.log10(dataRange.max)); | ||
| 12810 | var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); | ||
| 12811 | var exp, significand; | ||
| 12812 | |||
| 12813 | 	if (tickVal === 0) { | ||
| 12814 | exp = Math.floor(helpers.log10(dataRange.minNotZero)); | ||
| 12815 | significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); | ||
| 12816 | |||
| 12817 | ticks.push(tickVal); | ||
| 12818 | tickVal = significand * Math.pow(10, exp); | ||
| 12819 | 	} else { | ||
| 12820 | exp = Math.floor(helpers.log10(tickVal)); | ||
| 12821 | significand = Math.floor(tickVal / Math.pow(10, exp)); | ||
| 12822 | } | ||
| 12823 | var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; | ||
| 12824 | |||
| 12825 | 	do { | ||
| 12826 | ticks.push(tickVal); | ||
| 12827 | |||
| 12828 | ++significand; | ||
| 12829 | 		if (significand === 10) { | ||
| 12830 | significand = 1; | ||
| 12831 | ++exp; | ||
| 12832 | precision = exp >= 0 ? 1 : precision; | ||
| 12833 | } | ||
| 12834 | |||
| 12835 | tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; | ||
| 12836 | } while (exp < endExp || (exp === endExp && significand < endSignificand)); | ||
| 12837 | |||
| 12838 | var lastTick = valueOrDefault(generationOptions.max, tickVal); | ||
| 12839 | ticks.push(lastTick); | ||
| 12840 | |||
| 12841 | return ticks; | ||
| 12842 | } | ||
| 12843 | |||
| 12844 | |||
| 12845 | module.exports = function(Chart) { | ||
| 12846 | |||
| 12847 | 	var defaultConfig = { | ||
| 12848 | position: 'left', | ||
| 12849 | |||
| 12850 | // label settings | ||
| 12851 | 		ticks: { | ||
| 12852 | callback: Ticks.formatters.logarithmic | ||
| 12853 | } | ||
| 12854 | }; | ||
| 12855 | |||
| 12856 | 	var LogarithmicScale = Scale.extend({ | ||
| 12857 | 		determineDataLimits: function() { | ||
| 12858 | var me = this; | ||
| 12859 | var opts = me.options; | ||
| 12860 | var chart = me.chart; | ||
| 12861 | var data = chart.data; | ||
| 12862 | var datasets = data.datasets; | ||
| 12863 | var isHorizontal = me.isHorizontal(); | ||
| 12864 | 			function IDMatches(meta) { | ||
| 12865 | return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; | ||
| 12866 | } | ||
| 12867 | |||
| 12868 | // Calculate Range | ||
| 12869 | me.min = null; | ||
| 12870 | me.max = null; | ||
| 12871 | me.minNotZero = null; | ||
| 12872 | |||
| 12873 | var hasStacks = opts.stacked; | ||
| 12874 | 			if (hasStacks === undefined) { | ||
| 12875 | 				helpers.each(datasets, function(dataset, datasetIndex) { | ||
| 12876 | 					if (hasStacks) { | ||
| 12877 | return; | ||
| 12878 | } | ||
| 12879 | |||
| 12880 | var meta = chart.getDatasetMeta(datasetIndex); | ||
| 12881 | if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && | ||
| 12882 | 						meta.stack !== undefined) { | ||
| 12883 | hasStacks = true; | ||
| 12884 | } | ||
| 12885 | }); | ||
| 12886 | } | ||
| 12887 | |||
| 12888 | 			if (opts.stacked || hasStacks) { | ||
| 12889 | 				var valuesPerStack = {}; | ||
| 12890 | |||
| 12891 | 				helpers.each(datasets, function(dataset, datasetIndex) { | ||
| 12892 | var meta = chart.getDatasetMeta(datasetIndex); | ||
| 12893 | var key = [ | ||
| 12894 | meta.type, | ||
| 12895 | // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined | ||
| 12896 | ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), | ||
| 12897 | meta.stack | ||
| 12898 | 					].join('.'); | ||
| 12899 | |||
| 12900 | 					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { | ||
| 12901 | 						if (valuesPerStack[key] === undefined) { | ||
| 12902 | valuesPerStack[key] = []; | ||
| 12903 | } | ||
| 12904 | |||
| 12905 | 						helpers.each(dataset.data, function(rawValue, index) { | ||
| 12906 | var values = valuesPerStack[key]; | ||
| 12907 | var value = +me.getRightValue(rawValue); | ||
| 12908 | // invalid, hidden and negative values are ignored | ||
| 12909 | 							if (isNaN(value) || meta.data[index].hidden || value < 0) { | ||
| 12910 | return; | ||
| 12911 | } | ||
| 12912 | values[index] = values[index] || 0; | ||
| 12913 | values[index] += value; | ||
| 12914 | }); | ||
| 12915 | } | ||
| 12916 | }); | ||
| 12917 | |||
| 12918 | 				helpers.each(valuesPerStack, function(valuesForType) { | ||
| 12919 | 					if (valuesForType.length > 0) { | ||
| 12920 | var minVal = helpers.min(valuesForType); | ||
| 12921 | var maxVal = helpers.max(valuesForType); | ||
| 12922 | me.min = me.min === null ? minVal : Math.min(me.min, minVal); | ||
| 12923 | me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); | ||
| 12924 | } | ||
| 12925 | }); | ||
| 12926 | |||
| 12927 | 			} else { | ||
| 12928 | 				helpers.each(datasets, function(dataset, datasetIndex) { | ||
| 12929 | var meta = chart.getDatasetMeta(datasetIndex); | ||
| 12930 | 					if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { | ||
| 12931 | 						helpers.each(dataset.data, function(rawValue, index) { | ||
| 12932 | var value = +me.getRightValue(rawValue); | ||
| 12933 | // invalid, hidden and negative values are ignored | ||
| 12934 | 							if (isNaN(value) || meta.data[index].hidden || value < 0) { | ||
| 12935 | return; | ||
| 12936 | } | ||
| 12937 | |||
| 12938 | 							if (me.min === null) { | ||
| 12939 | me.min = value; | ||
| 12940 | 							} else if (value < me.min) { | ||
| 12941 | me.min = value; | ||
| 12942 | } | ||
| 12943 | |||
| 12944 | 							if (me.max === null) { | ||
| 12945 | me.max = value; | ||
| 12946 | 							} else if (value > me.max) { | ||
| 12947 | me.max = value; | ||
| 12948 | } | ||
| 12949 | |||
| 12950 | 							if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { | ||
| 12951 | me.minNotZero = value; | ||
| 12952 | } | ||
| 12953 | }); | ||
| 12954 | } | ||
| 12955 | }); | ||
| 12956 | } | ||
| 12957 | |||
| 12958 | // Common base implementation to handle ticks.min, ticks.max | ||
| 12959 | this.handleTickRangeOptions(); | ||
| 12960 | }, | ||
| 12961 | 		handleTickRangeOptions: function() { | ||
| 12962 | var me = this; | ||
| 12963 | var opts = me.options; | ||
| 12964 | var tickOpts = opts.ticks; | ||
| 12965 | var valueOrDefault = helpers.valueOrDefault; | ||
| 12966 | var DEFAULT_MIN = 1; | ||
| 12967 | var DEFAULT_MAX = 10; | ||
| 12968 | |||
| 12969 | me.min = valueOrDefault(tickOpts.min, me.min); | ||
| 12970 | me.max = valueOrDefault(tickOpts.max, me.max); | ||
| 12971 | |||
| 12972 | 			if (me.min === me.max) { | ||
| 12973 | 				if (me.min !== 0 && me.min !== null) { | ||
| 12974 | me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); | ||
| 12975 | me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); | ||
| 12976 | 				} else { | ||
| 12977 | me.min = DEFAULT_MIN; | ||
| 12978 | me.max = DEFAULT_MAX; | ||
| 12979 | } | ||
| 12980 | } | ||
| 12981 | 			if (me.min === null) { | ||
| 12982 | me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1); | ||
| 12983 | } | ||
| 12984 | 			if (me.max === null) { | ||
| 12985 | me.max = me.min !== 0 | ||
| 12986 | ? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1) | ||
| 12987 | : DEFAULT_MAX; | ||
| 12988 | } | ||
| 12989 | 			if (me.minNotZero === null) { | ||
| 12990 | 				if (me.min > 0) { | ||
| 12991 | me.minNotZero = me.min; | ||
| 12992 | 				} else if (me.max < 1) { | ||
| 12993 | me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max))); | ||
| 12994 | 				} else { | ||
| 12995 | me.minNotZero = DEFAULT_MIN; | ||
| 12996 | } | ||
| 12997 | } | ||
| 12998 | }, | ||
| 12999 | 		buildTicks: function() { | ||
| 13000 | var me = this; | ||
| 13001 | var opts = me.options; | ||
| 13002 | var tickOpts = opts.ticks; | ||
| 13003 | var reverse = !me.isHorizontal(); | ||
| 13004 | |||
| 13005 | 			var generationOptions = { | ||
| 13006 | min: tickOpts.min, | ||
| 13007 | max: tickOpts.max | ||
| 13008 | }; | ||
| 13009 | var ticks = me.ticks = generateTicks(generationOptions, me); | ||
| 13010 | |||
| 13011 | // At this point, we need to update our max and min given the tick values since we have expanded the | ||
| 13012 | // range of the scale | ||
| 13013 | me.max = helpers.max(ticks); | ||
| 13014 | me.min = helpers.min(ticks); | ||
| 13015 | |||
| 13016 | 			if (tickOpts.reverse) { | ||
| 13017 | reverse = !reverse; | ||
| 13018 | me.start = me.max; | ||
| 13019 | me.end = me.min; | ||
| 13020 | 			} else { | ||
| 13021 | me.start = me.min; | ||
| 13022 | me.end = me.max; | ||
| 13023 | } | ||
| 13024 | 			if (reverse) { | ||
| 13025 | ticks.reverse(); | ||
| 13026 | } | ||
| 13027 | }, | ||
| 13028 | 		convertTicksToLabels: function() { | ||
| 13029 | this.tickValues = this.ticks.slice(); | ||
| 13030 | |||
| 13031 | Scale.prototype.convertTicksToLabels.call(this); | ||
| 13032 | }, | ||
| 13033 | // Get the correct tooltip label | ||
| 13034 | 		getLabelForIndex: function(index, datasetIndex) { | ||
| 13035 | return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); | ||
| 13036 | }, | ||
| 13037 | 		getPixelForTick: function(index) { | ||
| 13038 | return this.getPixelForValue(this.tickValues[index]); | ||
| 13039 | }, | ||
| 13040 | /** | ||
| 13041 | * Returns the value of the first tick. | ||
| 13042 | 		 * @param {Number} value - The minimum not zero value. | ||
| 13043 | 		 * @return {Number} The first tick value. | ||
| 13044 | * @private | ||
| 13045 | */ | ||
| 13046 | 		_getFirstTickValue: function(value) { | ||
| 13047 | var exp = Math.floor(helpers.log10(value)); | ||
| 13048 | var significand = Math.floor(value / Math.pow(10, exp)); | ||
| 13049 | |||
| 13050 | return significand * Math.pow(10, exp); | ||
| 13051 | }, | ||
| 13052 | 		getPixelForValue: function(value) { | ||
| 13053 | var me = this; | ||
| 13054 | var reverse = me.options.ticks.reverse; | ||
| 13055 | var log10 = helpers.log10; | ||
| 13056 | var firstTickValue = me._getFirstTickValue(me.minNotZero); | ||
| 13057 | var offset = 0; | ||
| 13058 | var innerDimension, pixel, start, end, sign; | ||
| 13059 | |||
| 13060 | value = +me.getRightValue(value); | ||
| 13061 | 			if (reverse) { | ||
| 13062 | start = me.end; | ||
| 13063 | end = me.start; | ||
| 13064 | sign = -1; | ||
| 13065 | 			} else { | ||
| 13066 | start = me.start; | ||
| 13067 | end = me.end; | ||
| 13068 | sign = 1; | ||
| 13069 | } | ||
| 13070 | 			if (me.isHorizontal()) { | ||
| 13071 | innerDimension = me.width; | ||
| 13072 | pixel = reverse ? me.right : me.left; | ||
| 13073 | 			} else { | ||
| 13074 | innerDimension = me.height; | ||
| 13075 | sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0) | ||
| 13076 | pixel = reverse ? me.top : me.bottom; | ||
| 13077 | } | ||
| 13078 | 			if (value !== start) { | ||
| 13079 | 				if (start === 0) { // include zero tick | ||
| 13080 | offset = helpers.getValueOrDefault( | ||
| 13081 | me.options.ticks.fontSize, | ||
| 13082 | Chart.defaults.global.defaultFontSize | ||
| 13083 | ); | ||
| 13084 | innerDimension -= offset; | ||
| 13085 | start = firstTickValue; | ||
| 13086 | } | ||
| 13087 | 				if (value !== 0) { | ||
| 13088 | offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start)); | ||
| 13089 | } | ||
| 13090 | pixel += sign * offset; | ||
| 13091 | } | ||
| 13092 | return pixel; | ||
| 13093 | }, | ||
| 13094 | 		getValueForPixel: function(pixel) { | ||
| 13095 | var me = this; | ||
| 13096 | var reverse = me.options.ticks.reverse; | ||
| 13097 | var log10 = helpers.log10; | ||
| 13098 | var firstTickValue = me._getFirstTickValue(me.minNotZero); | ||
| 13099 | var innerDimension, start, end, value; | ||
| 13100 | |||
| 13101 | 			if (reverse) { | ||
| 13102 | start = me.end; | ||
| 13103 | end = me.start; | ||
| 13104 | 			} else { | ||
| 13105 | start = me.start; | ||
| 13106 | end = me.end; | ||
| 13107 | } | ||
| 13108 | 			if (me.isHorizontal()) { | ||
| 13109 | innerDimension = me.width; | ||
| 13110 | value = reverse ? me.right - pixel : pixel - me.left; | ||
| 13111 | 			} else { | ||
| 13112 | innerDimension = me.height; | ||
| 13113 | value = reverse ? pixel - me.top : me.bottom - pixel; | ||
| 13114 | } | ||
| 13115 | 			if (value !== start) { | ||
| 13116 | 				if (start === 0) { // include zero tick | ||
| 13117 | var offset = helpers.getValueOrDefault( | ||
| 13118 | me.options.ticks.fontSize, | ||
| 13119 | Chart.defaults.global.defaultFontSize | ||
| 13120 | ); | ||
| 13121 | value -= offset; | ||
| 13122 | innerDimension -= offset; | ||
| 13123 | start = firstTickValue; | ||
| 13124 | } | ||
| 13125 | value *= log10(end) - log10(start); | ||
| 13126 | value /= innerDimension; | ||
| 13127 | value = Math.pow(10, log10(start) + value); | ||
| 13128 | } | ||
| 13129 | return value; | ||
| 13130 | } | ||
| 13131 | }); | ||
| 13132 | |||
| 13133 | 	scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig); | ||
| 13134 | }; | ||
| 13135 | |||
| 13136 | },{"33":33,"34":34,"35":35,"46":46}],58:[function(require,module,exports){ | ||
| 13137 | 'use strict'; | ||
| 13138 | |||
| 13139 | var defaults = require(26); | ||
| 13140 | var helpers = require(46); | ||
| 13141 | var scaleService = require(34); | ||
| 13142 | var Ticks = require(35); | ||
| 13143 | |||
| 13144 | module.exports = function(Chart) { | ||
| 13145 | |||
| 13146 | var globalDefaults = defaults.global; | ||
| 13147 | |||
| 13148 | 	var defaultConfig = { | ||
| 13149 | display: true, | ||
| 13150 | |||
| 13151 | // Boolean - Whether to animate scaling the chart from the centre | ||
| 13152 | animate: true, | ||
| 13153 | position: 'chartArea', | ||
| 13154 | |||
| 13155 | 		angleLines: { | ||
| 13156 | display: true, | ||
| 13157 | color: 'rgba(0, 0, 0, 0.1)', | ||
| 13158 | lineWidth: 1 | ||
| 13159 | }, | ||
| 13160 | |||
| 13161 | 		gridLines: { | ||
| 13162 | circular: false | ||
| 13163 | }, | ||
| 13164 | |||
| 13165 | // label settings | ||
| 13166 | 		ticks: { | ||
| 13167 | // Boolean - Show a backdrop to the scale label | ||
| 13168 | showLabelBackdrop: true, | ||
| 13169 | |||
| 13170 | // String - The colour of the label backdrop | ||
| 13171 | backdropColor: 'rgba(255,255,255,0.75)', | ||
| 13172 | |||
| 13173 | // Number - The backdrop padding above & below the label in pixels | ||
| 13174 | backdropPaddingY: 2, | ||
| 13175 | |||
| 13176 | // Number - The backdrop padding to the side of the label in pixels | ||
| 13177 | backdropPaddingX: 2, | ||
| 13178 | |||
| 13179 | callback: Ticks.formatters.linear | ||
| 13180 | }, | ||
| 13181 | |||
| 13182 | 		pointLabels: { | ||
| 13183 | // Boolean - if true, show point labels | ||
| 13184 | display: true, | ||
| 13185 | |||
| 13186 | // Number - Point label font size in pixels | ||
| 13187 | fontSize: 10, | ||
| 13188 | |||
| 13189 | // Function - Used to convert point labels | ||
| 13190 | 			callback: function(label) { | ||
| 13191 | return label; | ||
| 13192 | } | ||
| 13193 | } | ||
| 13194 | }; | ||
| 13195 | |||
| 13196 | 	function getValueCount(scale) { | ||
| 13197 | var opts = scale.options; | ||
| 13198 | return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; | ||
| 13199 | } | ||
| 13200 | |||
| 13201 | 	function getPointLabelFontOptions(scale) { | ||
| 13202 | var pointLabelOptions = scale.options.pointLabels; | ||
| 13203 | var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize); | ||
| 13204 | var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle); | ||
| 13205 | var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily); | ||
| 13206 | var font = helpers.fontString(fontSize, fontStyle, fontFamily); | ||
| 13207 | |||
| 13208 | 		return { | ||
| 13209 | size: fontSize, | ||
| 13210 | style: fontStyle, | ||
| 13211 | family: fontFamily, | ||
| 13212 | font: font | ||
| 13213 | }; | ||
| 13214 | } | ||
| 13215 | |||
| 13216 | 	function measureLabelSize(ctx, fontSize, label) { | ||
| 13217 | 		if (helpers.isArray(label)) { | ||
| 13218 | 			return { | ||
| 13219 | w: helpers.longestText(ctx, ctx.font, label), | ||
| 13220 | h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize) | ||
| 13221 | }; | ||
| 13222 | } | ||
| 13223 | |||
| 13224 | 		return { | ||
| 13225 | w: ctx.measureText(label).width, | ||
| 13226 | h: fontSize | ||
| 13227 | }; | ||
| 13228 | } | ||
| 13229 | |||
| 13230 | 	function determineLimits(angle, pos, size, min, max) { | ||
| 13231 | 		if (angle === min || angle === max) { | ||
| 13232 | 			return { | ||
| 13233 | start: pos - (size / 2), | ||
| 13234 | end: pos + (size / 2) | ||
| 13235 | }; | ||
| 13236 | 		} else if (angle < min || angle > max) { | ||
| 13237 | 			return { | ||
| 13238 | start: pos - size - 5, | ||
| 13239 | end: pos | ||
| 13240 | }; | ||
| 13241 | } | ||
| 13242 | |||
| 13243 | 		return { | ||
| 13244 | start: pos, | ||
| 13245 | end: pos + size + 5 | ||
| 13246 | }; | ||
| 13247 | } | ||
| 13248 | |||
| 13249 | /** | ||
| 13250 | * Helper function to fit a radial linear scale with point labels | ||
| 13251 | */ | ||
| 13252 | 	function fitWithPointLabels(scale) { | ||
| 13253 | /* | ||
| 13254 | * Right, this is really confusing and there is a lot of maths going on here | ||
| 13255 | * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 | ||
| 13256 | * | ||
| 13257 | * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif | ||
| 13258 | * | ||
| 13259 | * Solution: | ||
| 13260 | * | ||
| 13261 | * We assume the radius of the polygon is half the size of the canvas at first | ||
| 13262 | * at each index we check if the text overlaps. | ||
| 13263 | * | ||
| 13264 | * Where it does, we store that angle and that index. | ||
| 13265 | * | ||
| 13266 | * After finding the largest index and angle we calculate how much we need to remove | ||
| 13267 | * from the shape radius to move the point inwards by that x. | ||
| 13268 | * | ||
| 13269 | * We average the left and right distances to get the maximum shape radius that can fit in the box | ||
| 13270 | * along with labels. | ||
| 13271 | * | ||
| 13272 | * Once we have that, we can find the centre point for the chart, by taking the x text protrusion | ||
| 13273 | * on each side, removing that from the size, halving it and adding the left x protrusion width. | ||
| 13274 | * | ||
| 13275 | * This will mean we have a shape fitted to the canvas, as large as it can be with the labels | ||
| 13276 | * and position it in the most space efficient manner | ||
| 13277 | * | ||
| 13278 | * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif | ||
| 13279 | */ | ||
| 13280 | |||
| 13281 | var plFont = getPointLabelFontOptions(scale); | ||
| 13282 | |||
| 13283 | // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. | ||
| 13284 | // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points | ||
| 13285 | var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); | ||
| 13286 | 		var furthestLimits = { | ||
| 13287 | r: scale.width, | ||
| 13288 | l: 0, | ||
| 13289 | t: scale.height, | ||
| 13290 | b: 0 | ||
| 13291 | }; | ||
| 13292 | 		var furthestAngles = {}; | ||
| 13293 | var i, textSize, pointPosition; | ||
| 13294 | |||
| 13295 | scale.ctx.font = plFont.font; | ||
| 13296 | scale._pointLabelSizes = []; | ||
| 13297 | |||
| 13298 | var valueCount = getValueCount(scale); | ||
| 13299 | 		for (i = 0; i < valueCount; i++) { | ||
| 13300 | pointPosition = scale.getPointPosition(i, largestPossibleRadius); | ||
| 13301 | textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || ''); | ||
| 13302 | scale._pointLabelSizes[i] = textSize; | ||
| 13303 | |||
| 13304 | // Add quarter circle to make degree 0 mean top of circle | ||
| 13305 | var angleRadians = scale.getIndexAngle(i); | ||
| 13306 | var angle = helpers.toDegrees(angleRadians) % 360; | ||
| 13307 | var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); | ||
| 13308 | var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); | ||
| 13309 | |||
| 13310 | 			if (hLimits.start < furthestLimits.l) { | ||
| 13311 | furthestLimits.l = hLimits.start; | ||
| 13312 | furthestAngles.l = angleRadians; | ||
| 13313 | } | ||
| 13314 | |||
| 13315 | 			if (hLimits.end > furthestLimits.r) { | ||
| 13316 | furthestLimits.r = hLimits.end; | ||
| 13317 | furthestAngles.r = angleRadians; | ||
| 13318 | } | ||
| 13319 | |||
| 13320 | 			if (vLimits.start < furthestLimits.t) { | ||
| 13321 | furthestLimits.t = vLimits.start; | ||
| 13322 | furthestAngles.t = angleRadians; | ||
| 13323 | } | ||
| 13324 | |||
| 13325 | 			if (vLimits.end > furthestLimits.b) { | ||
| 13326 | furthestLimits.b = vLimits.end; | ||
| 13327 | furthestAngles.b = angleRadians; | ||
| 13328 | } | ||
| 13329 | } | ||
| 13330 | |||
| 13331 | scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles); | ||
| 13332 | } | ||
| 13333 | |||
| 13334 | /** | ||
| 13335 | * Helper function to fit a radial linear scale with no point labels | ||
| 13336 | */ | ||
| 13337 | 	function fit(scale) { | ||
| 13338 | var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); | ||
| 13339 | scale.drawingArea = Math.round(largestPossibleRadius); | ||
| 13340 | scale.setCenterPoint(0, 0, 0, 0); | ||
| 13341 | } | ||
| 13342 | |||
| 13343 | 	function getTextAlignForAngle(angle) { | ||
| 13344 | 		if (angle === 0 || angle === 180) { | ||
| 13345 | return 'center'; | ||
| 13346 | 		} else if (angle < 180) { | ||
| 13347 | return 'left'; | ||
| 13348 | } | ||
| 13349 | |||
| 13350 | return 'right'; | ||
| 13351 | } | ||
| 13352 | |||
| 13353 | 	function fillText(ctx, text, position, fontSize) { | ||
| 13354 | 		if (helpers.isArray(text)) { | ||
| 13355 | var y = position.y; | ||
| 13356 | var spacing = 1.5 * fontSize; | ||
| 13357 | |||
| 13358 | 			for (var i = 0; i < text.length; ++i) { | ||
| 13359 | ctx.fillText(text[i], position.x, y); | ||
| 13360 | y += spacing; | ||
| 13361 | } | ||
| 13362 | 		} else { | ||
| 13363 | ctx.fillText(text, position.x, position.y); | ||
| 13364 | } | ||
| 13365 | } | ||
| 13366 | |||
| 13367 | 	function adjustPointPositionForLabelHeight(angle, textSize, position) { | ||
| 13368 | 		if (angle === 90 || angle === 270) { | ||
| 13369 | position.y -= (textSize.h / 2); | ||
| 13370 | 		} else if (angle > 270 || angle < 90) { | ||
| 13371 | position.y -= textSize.h; | ||
| 13372 | } | ||
| 13373 | } | ||
| 13374 | |||
| 13375 | 	function drawPointLabels(scale) { | ||
| 13376 | var ctx = scale.ctx; | ||
| 13377 | var opts = scale.options; | ||
| 13378 | var angleLineOpts = opts.angleLines; | ||
| 13379 | var pointLabelOpts = opts.pointLabels; | ||
| 13380 | |||
| 13381 | ctx.lineWidth = angleLineOpts.lineWidth; | ||
| 13382 | ctx.strokeStyle = angleLineOpts.color; | ||
| 13383 | |||
| 13384 | var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); | ||
| 13385 | |||
| 13386 | // Point Label Font | ||
| 13387 | var plFont = getPointLabelFontOptions(scale); | ||
| 13388 | |||
| 13389 | ctx.textBaseline = 'top'; | ||
| 13390 | |||
| 13391 | 		for (var i = getValueCount(scale) - 1; i >= 0; i--) { | ||
| 13392 | 			if (angleLineOpts.display) { | ||
| 13393 | var outerPosition = scale.getPointPosition(i, outerDistance); | ||
| 13394 | ctx.beginPath(); | ||
| 13395 | ctx.moveTo(scale.xCenter, scale.yCenter); | ||
| 13396 | ctx.lineTo(outerPosition.x, outerPosition.y); | ||
| 13397 | ctx.stroke(); | ||
| 13398 | ctx.closePath(); | ||
| 13399 | } | ||
| 13400 | |||
| 13401 | 			if (pointLabelOpts.display) { | ||
| 13402 | // Extra 3px out for some label spacing | ||
| 13403 | var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5); | ||
| 13404 | |||
| 13405 | // Keep this in loop since we may support array properties here | ||
| 13406 | var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, globalDefaults.defaultFontColor); | ||
| 13407 | ctx.font = plFont.font; | ||
| 13408 | ctx.fillStyle = pointLabelFontColor; | ||
| 13409 | |||
| 13410 | var angleRadians = scale.getIndexAngle(i); | ||
| 13411 | var angle = helpers.toDegrees(angleRadians); | ||
| 13412 | ctx.textAlign = getTextAlignForAngle(angle); | ||
| 13413 | adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); | ||
| 13414 | fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size); | ||
| 13415 | } | ||
| 13416 | } | ||
| 13417 | } | ||
| 13418 | |||
| 13419 | 	function drawRadiusLine(scale, gridLineOpts, radius, index) { | ||
| 13420 | var ctx = scale.ctx; | ||
| 13421 | ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); | ||
| 13422 | ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); | ||
| 13423 | |||
| 13424 | 		if (scale.options.gridLines.circular) { | ||
| 13425 | // Draw circular arcs between the points | ||
| 13426 | ctx.beginPath(); | ||
| 13427 | ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); | ||
| 13428 | ctx.closePath(); | ||
| 13429 | ctx.stroke(); | ||
| 13430 | 		} else { | ||
| 13431 | // Draw straight lines connecting each index | ||
| 13432 | var valueCount = getValueCount(scale); | ||
| 13433 | |||
| 13434 | 			if (valueCount === 0) { | ||
| 13435 | return; | ||
| 13436 | } | ||
| 13437 | |||
| 13438 | ctx.beginPath(); | ||
| 13439 | var pointPosition = scale.getPointPosition(0, radius); | ||
| 13440 | ctx.moveTo(pointPosition.x, pointPosition.y); | ||
| 13441 | |||
| 13442 | 			for (var i = 1; i < valueCount; i++) { | ||
| 13443 | pointPosition = scale.getPointPosition(i, radius); | ||
| 13444 | ctx.lineTo(pointPosition.x, pointPosition.y); | ||
| 13445 | } | ||
| 13446 | |||
| 13447 | ctx.closePath(); | ||
| 13448 | ctx.stroke(); | ||
| 13449 | } | ||
| 13450 | } | ||
| 13451 | |||
| 13452 | 	function numberOrZero(param) { | ||
| 13453 | return helpers.isNumber(param) ? param : 0; | ||
| 13454 | } | ||
| 13455 | |||
| 13456 | 	var LinearRadialScale = Chart.LinearScaleBase.extend({ | ||
| 13457 | 		setDimensions: function() { | ||
| 13458 | var me = this; | ||
| 13459 | var opts = me.options; | ||
| 13460 | var tickOpts = opts.ticks; | ||
| 13461 | // Set the unconstrained dimension before label rotation | ||
| 13462 | me.width = me.maxWidth; | ||
| 13463 | me.height = me.maxHeight; | ||
| 13464 | me.xCenter = Math.round(me.width / 2); | ||
| 13465 | me.yCenter = Math.round(me.height / 2); | ||
| 13466 | |||
| 13467 | var minSize = helpers.min([me.height, me.width]); | ||
| 13468 | var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); | ||
| 13469 | me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); | ||
| 13470 | }, | ||
| 13471 | 		determineDataLimits: function() { | ||
| 13472 | var me = this; | ||
| 13473 | var chart = me.chart; | ||
| 13474 | var min = Number.POSITIVE_INFINITY; | ||
| 13475 | var max = Number.NEGATIVE_INFINITY; | ||
| 13476 | |||
| 13477 | 			helpers.each(chart.data.datasets, function(dataset, datasetIndex) { | ||
| 13478 | 				if (chart.isDatasetVisible(datasetIndex)) { | ||
| 13479 | var meta = chart.getDatasetMeta(datasetIndex); | ||
| 13480 | |||
| 13481 | 					helpers.each(dataset.data, function(rawValue, index) { | ||
| 13482 | var value = +me.getRightValue(rawValue); | ||
| 13483 | 						if (isNaN(value) || meta.data[index].hidden) { | ||
| 13484 | return; | ||
| 13485 | } | ||
| 13486 | |||
| 13487 | min = Math.min(value, min); | ||
| 13488 | max = Math.max(value, max); | ||
| 13489 | }); | ||
| 13490 | } | ||
| 13491 | }); | ||
| 13492 | |||
| 13493 | me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); | ||
| 13494 | me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); | ||
| 13495 | |||
| 13496 | // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero | ||
| 13497 | me.handleTickRangeOptions(); | ||
| 13498 | }, | ||
| 13499 | 		getTickLimit: function() { | ||
| 13500 | var tickOpts = this.options.ticks; | ||
| 13501 | var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); | ||
| 13502 | return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); | ||
| 13503 | }, | ||
| 13504 | 		convertTicksToLabels: function() { | ||
| 13505 | var me = this; | ||
| 13506 | |||
| 13507 | Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); | ||
| 13508 | |||
| 13509 | // Point labels | ||
| 13510 | me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); | ||
| 13511 | }, | ||
| 13512 | 		getLabelForIndex: function(index, datasetIndex) { | ||
| 13513 | return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); | ||
| 13514 | }, | ||
| 13515 | 		fit: function() { | ||
| 13516 | 			if (this.options.pointLabels.display) { | ||
| 13517 | fitWithPointLabels(this); | ||
| 13518 | 			} else { | ||
| 13519 | fit(this); | ||
| 13520 | } | ||
| 13521 | }, | ||
| 13522 | /** | ||
| 13523 | * Set radius reductions and determine new radius and center point | ||
| 13524 | * @private | ||
| 13525 | */ | ||
| 13526 | 		setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { | ||
| 13527 | var me = this; | ||
| 13528 | var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); | ||
| 13529 | var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); | ||
| 13530 | var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); | ||
| 13531 | var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b); | ||
| 13532 | |||
| 13533 | radiusReductionLeft = numberOrZero(radiusReductionLeft); | ||
| 13534 | radiusReductionRight = numberOrZero(radiusReductionRight); | ||
| 13535 | radiusReductionTop = numberOrZero(radiusReductionTop); | ||
| 13536 | radiusReductionBottom = numberOrZero(radiusReductionBottom); | ||
| 13537 | |||
| 13538 | me.drawingArea = Math.min( | ||
| 13539 | Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), | ||
| 13540 | Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); | ||
| 13541 | me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); | ||
| 13542 | }, | ||
| 13543 | 		setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { | ||
| 13544 | var me = this; | ||
| 13545 | var maxRight = me.width - rightMovement - me.drawingArea; | ||
| 13546 | var maxLeft = leftMovement + me.drawingArea; | ||
| 13547 | var maxTop = topMovement + me.drawingArea; | ||
| 13548 | var maxBottom = me.height - bottomMovement - me.drawingArea; | ||
| 13549 | |||
| 13550 | me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); | ||
| 13551 | me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top); | ||
| 13552 | }, | ||
| 13553 | |||
| 13554 | 		getIndexAngle: function(index) { | ||
| 13555 | var angleMultiplier = (Math.PI * 2) / getValueCount(this); | ||
| 13556 | var startAngle = this.chart.options && this.chart.options.startAngle ? | ||
| 13557 | this.chart.options.startAngle : | ||
| 13558 | 0; | ||
| 13559 | |||
| 13560 | var startAngleRadians = startAngle * Math.PI * 2 / 360; | ||
| 13561 | |||
| 13562 | // Start from the top instead of right, so remove a quarter of the circle | ||
| 13563 | return index * angleMultiplier + startAngleRadians; | ||
| 13564 | }, | ||
| 13565 | 		getDistanceFromCenterForValue: function(value) { | ||
| 13566 | var me = this; | ||
| 13567 | |||
| 13568 | 			if (value === null) { | ||
| 13569 | return 0; // null always in center | ||
| 13570 | } | ||
| 13571 | |||
| 13572 | // Take into account half font size + the yPadding of the top value | ||
| 13573 | var scalingFactor = me.drawingArea / (me.max - me.min); | ||
| 13574 | 			if (me.options.ticks.reverse) { | ||
| 13575 | return (me.max - value) * scalingFactor; | ||
| 13576 | } | ||
| 13577 | return (value - me.min) * scalingFactor; | ||
| 13578 | }, | ||
| 13579 | 		getPointPosition: function(index, distanceFromCenter) { | ||
| 13580 | var me = this; | ||
| 13581 | var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); | ||
| 13582 | 			return { | ||
| 13583 | x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, | ||
| 13584 | y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter | ||
| 13585 | }; | ||
| 13586 | }, | ||
| 13587 | 		getPointPositionForValue: function(index, value) { | ||
| 13588 | return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); | ||
| 13589 | }, | ||
| 13590 | |||
| 13591 | 		getBasePosition: function() { | ||
| 13592 | var me = this; | ||
| 13593 | var min = me.min; | ||
| 13594 | var max = me.max; | ||
| 13595 | |||
| 13596 | return me.getPointPositionForValue(0, | ||
| 13597 | me.beginAtZero ? 0 : | ||
| 13598 | min < 0 && max < 0 ? max : | ||
| 13599 | min > 0 && max > 0 ? min : | ||
| 13600 | 0); | ||
| 13601 | }, | ||
| 13602 | |||
| 13603 | 		draw: function() { | ||
| 13604 | var me = this; | ||
| 13605 | var opts = me.options; | ||
| 13606 | var gridLineOpts = opts.gridLines; | ||
| 13607 | var tickOpts = opts.ticks; | ||
| 13608 | var valueOrDefault = helpers.valueOrDefault; | ||
| 13609 | |||
| 13610 | 			if (opts.display) { | ||
| 13611 | var ctx = me.ctx; | ||
| 13612 | var startAngle = this.getIndexAngle(0); | ||
| 13613 | |||
| 13614 | // Tick Font | ||
| 13615 | var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); | ||
| 13616 | var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); | ||
| 13617 | var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); | ||
| 13618 | var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); | ||
| 13619 | |||
| 13620 | 				helpers.each(me.ticks, function(label, index) { | ||
| 13621 | // Don't draw a centre value (if it is minimum) | ||
| 13622 | 					if (index > 0 || tickOpts.reverse) { | ||
| 13623 | var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); | ||
| 13624 | |||
| 13625 | // Draw circular lines around the scale | ||
| 13626 | 						if (gridLineOpts.display && index !== 0) { | ||
| 13627 | drawRadiusLine(me, gridLineOpts, yCenterOffset, index); | ||
| 13628 | } | ||
| 13629 | |||
| 13630 | 						if (tickOpts.display) { | ||
| 13631 | var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); | ||
| 13632 | ctx.font = tickLabelFont; | ||
| 13633 | |||
| 13634 | ctx.save(); | ||
| 13635 | ctx.translate(me.xCenter, me.yCenter); | ||
| 13636 | ctx.rotate(startAngle); | ||
| 13637 | |||
| 13638 | 							if (tickOpts.showLabelBackdrop) { | ||
| 13639 | var labelWidth = ctx.measureText(label).width; | ||
| 13640 | ctx.fillStyle = tickOpts.backdropColor; | ||
| 13641 | ctx.fillRect( | ||
| 13642 | -labelWidth / 2 - tickOpts.backdropPaddingX, | ||
| 13643 | -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY, | ||
| 13644 | labelWidth + tickOpts.backdropPaddingX * 2, | ||
| 13645 | tickFontSize + tickOpts.backdropPaddingY * 2 | ||
| 13646 | ); | ||
| 13647 | } | ||
| 13648 | |||
| 13649 | ctx.textAlign = 'center'; | ||
| 13650 | ctx.textBaseline = 'middle'; | ||
| 13651 | ctx.fillStyle = tickFontColor; | ||
| 13652 | ctx.fillText(label, 0, -yCenterOffset); | ||
| 13653 | ctx.restore(); | ||
| 13654 | } | ||
| 13655 | } | ||
| 13656 | }); | ||
| 13657 | |||
| 13658 | 				if (opts.angleLines.display || opts.pointLabels.display) { | ||
| 13659 | drawPointLabels(me); | ||
| 13660 | } | ||
| 13661 | } | ||
| 13662 | } | ||
| 13663 | }); | ||
| 13664 | |||
| 13665 | 	scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig); | ||
| 13666 | }; | ||
| 13667 | |||
| 13668 | },{"26":26,"34":34,"35":35,"46":46}],59:[function(require,module,exports){ | ||
| 13669 | /* global window: false */ | ||
| 13670 | 'use strict'; | ||
| 13671 | |||
| 13672 | var moment = require(1); | ||
| 13673 | moment = typeof moment === 'function' ? moment : window.moment; | ||
| 13674 | |||
| 13675 | var defaults = require(26); | ||
| 13676 | var helpers = require(46); | ||
| 13677 | var Scale = require(33); | ||
| 13678 | var scaleService = require(34); | ||
| 13679 | |||
| 13680 | // Integer constants are from the ES6 spec. | ||
| 13681 | var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; | ||
| 13682 | var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; | ||
| 13683 | |||
| 13684 | var INTERVALS = { | ||
| 13685 | 	millisecond: { | ||
| 13686 | common: true, | ||
| 13687 | size: 1, | ||
| 13688 | steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] | ||
| 13689 | }, | ||
| 13690 | 	second: { | ||
| 13691 | common: true, | ||
| 13692 | size: 1000, | ||
| 13693 | steps: [1, 2, 5, 10, 15, 30] | ||
| 13694 | }, | ||
| 13695 | 	minute: { | ||
| 13696 | common: true, | ||
| 13697 | size: 60000, | ||
| 13698 | steps: [1, 2, 5, 10, 15, 30] | ||
| 13699 | }, | ||
| 13700 | 	hour: { | ||
| 13701 | common: true, | ||
| 13702 | size: 3600000, | ||
| 13703 | steps: [1, 2, 3, 6, 12] | ||
| 13704 | }, | ||
| 13705 | 	day: { | ||
| 13706 | common: true, | ||
| 13707 | size: 86400000, | ||
| 13708 | steps: [1, 2, 5] | ||
| 13709 | }, | ||
| 13710 | 	week: { | ||
| 13711 | common: false, | ||
| 13712 | size: 604800000, | ||
| 13713 | steps: [1, 2, 3, 4] | ||
| 13714 | }, | ||
| 13715 | 	month: { | ||
| 13716 | common: true, | ||
| 13717 | size: 2.628e9, | ||
| 13718 | steps: [1, 2, 3] | ||
| 13719 | }, | ||
| 13720 | 	quarter: { | ||
| 13721 | common: false, | ||
| 13722 | size: 7.884e9, | ||
| 13723 | steps: [1, 2, 3, 4] | ||
| 13724 | }, | ||
| 13725 | 	year: { | ||
| 13726 | common: true, | ||
| 13727 | size: 3.154e10 | ||
| 13728 | } | ||
| 13729 | }; | ||
| 13730 | |||
| 13731 | var UNITS = Object.keys(INTERVALS); | ||
| 13732 | |||
| 13733 | function sorter(a, b) { | ||
| 13734 | return a - b; | ||
| 13735 | } | ||
| 13736 | |||
| 13737 | function arrayUnique(items) { | ||
| 13738 | 	var hash = {}; | ||
| 13739 | var out = []; | ||
| 13740 | var i, ilen, item; | ||
| 13741 | |||
| 13742 | 	for (i = 0, ilen = items.length; i < ilen; ++i) { | ||
| 13743 | item = items[i]; | ||
| 13744 | 		if (!hash[item]) { | ||
| 13745 | hash[item] = true; | ||
| 13746 | out.push(item); | ||
| 13747 | } | ||
| 13748 | } | ||
| 13749 | |||
| 13750 | return out; | ||
| 13751 | } | ||
| 13752 | |||
| 13753 | /** | ||
| 13754 |  * Returns an array of {time, pos} objects used to interpolate a specific `time` or position | ||
| 13755 | * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is | ||
| 13756 | * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other | ||
| 13757 | * extremity (left + width or top + height). Note that it would be more optimized to directly | ||
| 13758 | * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need | ||
| 13759 | * to create the lookup table. The table ALWAYS contains at least two items: min and max. | ||
| 13760 | * | ||
| 13761 |  * @param {Number[]} timestamps - timestamps sorted from lowest to highest. | ||
| 13762 |  * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min | ||
| 13763 |  * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. | ||
| 13764 | * If 'series', timestamps will be positioned at the same distance from each other. In this | ||
| 13765 | * case, only timestamps that break the time linearity are registered, meaning that in the | ||
| 13766 | * best case, all timestamps are linear, the table contains only min and max. | ||
| 13767 | */ | ||
| 13768 | function buildLookupTable(timestamps, min, max, distribution) { | ||
| 13769 | 	if (distribution === 'linear' || !timestamps.length) { | ||
| 13770 | return [ | ||
| 13771 | 			{time: min, pos: 0}, | ||
| 13772 | 			{time: max, pos: 1} | ||
| 13773 | ]; | ||
| 13774 | } | ||
| 13775 | |||
| 13776 | var table = []; | ||
| 13777 | var items = [min]; | ||
| 13778 | var i, ilen, prev, curr, next; | ||
| 13779 | |||
| 13780 | 	for (i = 0, ilen = timestamps.length; i < ilen; ++i) { | ||
| 13781 | curr = timestamps[i]; | ||
| 13782 | 		if (curr > min && curr < max) { | ||
| 13783 | items.push(curr); | ||
| 13784 | } | ||
| 13785 | } | ||
| 13786 | |||
| 13787 | items.push(max); | ||
| 13788 | |||
| 13789 | 	for (i = 0, ilen = items.length; i < ilen; ++i) { | ||
| 13790 | next = items[i + 1]; | ||
| 13791 | prev = items[i - 1]; | ||
| 13792 | curr = items[i]; | ||
| 13793 | |||
| 13794 | // only add points that breaks the scale linearity | ||
| 13795 | 		if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { | ||
| 13796 | 			table.push({time: curr, pos: i / (ilen - 1)}); | ||
| 13797 | } | ||
| 13798 | } | ||
| 13799 | |||
| 13800 | return table; | ||
| 13801 | } | ||
| 13802 | |||
| 13803 | // @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ | ||
| 13804 | function lookup(table, key, value) { | ||
| 13805 | var lo = 0; | ||
| 13806 | var hi = table.length - 1; | ||
| 13807 | var mid, i0, i1; | ||
| 13808 | |||
| 13809 | 	while (lo >= 0 && lo <= hi) { | ||
| 13810 | mid = (lo + hi) >> 1; | ||
| 13811 | i0 = table[mid - 1] || null; | ||
| 13812 | i1 = table[mid]; | ||
| 13813 | |||
| 13814 | 		if (!i0) { | ||
| 13815 | // given value is outside table (before first item) | ||
| 13816 | 			return {lo: null, hi: i1}; | ||
| 13817 | 		} else if (i1[key] < value) { | ||
| 13818 | lo = mid + 1; | ||
| 13819 | 		} else if (i0[key] > value) { | ||
| 13820 | hi = mid - 1; | ||
| 13821 | 		} else { | ||
| 13822 | 			return {lo: i0, hi: i1}; | ||
| 13823 | } | ||
| 13824 | } | ||
| 13825 | |||
| 13826 | // given value is outside table (after last item) | ||
| 13827 | 	return {lo: i1, hi: null}; | ||
| 13828 | } | ||
| 13829 | |||
| 13830 | /** | ||
| 13831 | * Linearly interpolates the given source `value` using the table items `skey` values and | ||
| 13832 | * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') | ||
| 13833 | * returns the position for a timestamp equal to 42. If value is out of bounds, values at | ||
| 13834 | * index [0, 1] or [n - 1, n] are used for the interpolation. | ||
| 13835 | */ | ||
| 13836 | function interpolate(table, skey, sval, tkey) { | ||
| 13837 | var range = lookup(table, skey, sval); | ||
| 13838 | |||
| 13839 | // Note: the lookup table ALWAYS contains at least 2 items (min and max) | ||
| 13840 | var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; | ||
| 13841 | var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; | ||
| 13842 | |||
| 13843 | var span = next[skey] - prev[skey]; | ||
| 13844 | var ratio = span ? (sval - prev[skey]) / span : 0; | ||
| 13845 | var offset = (next[tkey] - prev[tkey]) * ratio; | ||
| 13846 | |||
| 13847 | return prev[tkey] + offset; | ||
| 13848 | } | ||
| 13849 | |||
| 13850 | /** | ||
| 13851 | * Convert the given value to a moment object using the given time options. | ||
| 13852 | * @see http://momentjs.com/docs/#/parsing/ | ||
| 13853 | */ | ||
| 13854 | function momentify(value, options) { | ||
| 13855 | var parser = options.parser; | ||
| 13856 | var format = options.parser || options.format; | ||
| 13857 | |||
| 13858 | 	if (typeof parser === 'function') { | ||
| 13859 | return parser(value); | ||
| 13860 | } | ||
| 13861 | |||
| 13862 | 	if (typeof value === 'string' && typeof format === 'string') { | ||
| 13863 | return moment(value, format); | ||
| 13864 | } | ||
| 13865 | |||
| 13866 | 	if (!(value instanceof moment)) { | ||
| 13867 | value = moment(value); | ||
| 13868 | } | ||
| 13869 | |||
| 13870 | 	if (value.isValid()) { | ||
| 13871 | return value; | ||
| 13872 | } | ||
| 13873 | |||
| 13874 | // Labels are in an incompatible moment format and no `parser` has been provided. | ||
| 13875 | // The user might still use the deprecated `format` option to convert his inputs. | ||
| 13876 | 	if (typeof format === 'function') { | ||
| 13877 | return format(value); | ||
| 13878 | } | ||
| 13879 | |||
| 13880 | return value; | ||
| 13881 | } | ||
| 13882 | |||
| 13883 | function parse(input, scale) { | ||
| 13884 | 	if (helpers.isNullOrUndef(input)) { | ||
| 13885 | return null; | ||
| 13886 | } | ||
| 13887 | |||
| 13888 | var options = scale.options.time; | ||
| 13889 | var value = momentify(scale.getRightValue(input), options); | ||
| 13890 | 	if (!value.isValid()) { | ||
| 13891 | return null; | ||
| 13892 | } | ||
| 13893 | |||
| 13894 | 	if (options.round) { | ||
| 13895 | value.startOf(options.round); | ||
| 13896 | } | ||
| 13897 | |||
| 13898 | return value.valueOf(); | ||
| 13899 | } | ||
| 13900 | |||
| 13901 | /** | ||
| 13902 | * Returns the number of unit to skip to be able to display up to `capacity` number of ticks | ||
| 13903 | * in `unit` for the given `min` / `max` range and respecting the interval steps constraints. | ||
| 13904 | */ | ||
| 13905 | function determineStepSize(min, max, unit, capacity) { | ||
| 13906 | var range = max - min; | ||
| 13907 | var interval = INTERVALS[unit]; | ||
| 13908 | var milliseconds = interval.size; | ||
| 13909 | var steps = interval.steps; | ||
| 13910 | var i, ilen, factor; | ||
| 13911 | |||
| 13912 | 	if (!steps) { | ||
| 13913 | return Math.ceil(range / (capacity * milliseconds)); | ||
| 13914 | } | ||
| 13915 | |||
| 13916 | 	for (i = 0, ilen = steps.length; i < ilen; ++i) { | ||
| 13917 | factor = steps[i]; | ||
| 13918 | 		if (Math.ceil(range / (milliseconds * factor)) <= capacity) { | ||
| 13919 | break; | ||
| 13920 | } | ||
| 13921 | } | ||
| 13922 | |||
| 13923 | return factor; | ||
| 13924 | } | ||
| 13925 | |||
| 13926 | /** | ||
| 13927 | * Figures out what unit results in an appropriate number of auto-generated ticks | ||
| 13928 | */ | ||
| 13929 | function determineUnitForAutoTicks(minUnit, min, max, capacity) { | ||
| 13930 | var ilen = UNITS.length; | ||
| 13931 | var i, interval, factor; | ||
| 13932 | |||
| 13933 | 	for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { | ||
| 13934 | interval = INTERVALS[UNITS[i]]; | ||
| 13935 | factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER; | ||
| 13936 | |||
| 13937 | 		if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { | ||
| 13938 | return UNITS[i]; | ||
| 13939 | } | ||
| 13940 | } | ||
| 13941 | |||
| 13942 | return UNITS[ilen - 1]; | ||
| 13943 | } | ||
| 13944 | |||
| 13945 | /** | ||
| 13946 | * Figures out what unit to format a set of ticks with | ||
| 13947 | */ | ||
| 13948 | function determineUnitForFormatting(ticks, minUnit, min, max) { | ||
| 13949 | var duration = moment.duration(moment(max).diff(moment(min))); | ||
| 13950 | var ilen = UNITS.length; | ||
| 13951 | var i, unit; | ||
| 13952 | |||
| 13953 | 	for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) { | ||
| 13954 | unit = UNITS[i]; | ||
| 13955 | 		if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) { | ||
| 13956 | return unit; | ||
| 13957 | } | ||
| 13958 | } | ||
| 13959 | |||
| 13960 | return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; | ||
| 13961 | } | ||
| 13962 | |||
| 13963 | function determineMajorUnit(unit) { | ||
| 13964 | 	for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { | ||
| 13965 | 		if (INTERVALS[UNITS[i]].common) { | ||
| 13966 | return UNITS[i]; | ||
| 13967 | } | ||
| 13968 | } | ||
| 13969 | } | ||
| 13970 | |||
| 13971 | /** | ||
| 13972 | * Generates a maximum of `capacity` timestamps between min and max, rounded to the | ||
| 13973 | * `minor` unit, aligned on the `major` unit and using the given scale time `options`. | ||
| 13974 | * Important: this method can return ticks outside the min and max range, it's the | ||
| 13975 | * responsibility of the calling code to clamp values if needed. | ||
| 13976 | */ | ||
| 13977 | function generate(min, max, capacity, options) { | ||
| 13978 | var timeOpts = options.time; | ||
| 13979 | var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); | ||
| 13980 | var major = determineMajorUnit(minor); | ||
| 13981 | var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize); | ||
| 13982 | var weekday = minor === 'week' ? timeOpts.isoWeekday : false; | ||
| 13983 | var majorTicksEnabled = options.ticks.major.enabled; | ||
| 13984 | var interval = INTERVALS[minor]; | ||
| 13985 | var first = moment(min); | ||
| 13986 | var last = moment(max); | ||
| 13987 | var ticks = []; | ||
| 13988 | var time; | ||
| 13989 | |||
| 13990 | 	if (!stepSize) { | ||
| 13991 | stepSize = determineStepSize(min, max, minor, capacity); | ||
| 13992 | } | ||
| 13993 | |||
| 13994 | // For 'week' unit, handle the first day of week option | ||
| 13995 | 	if (weekday) { | ||
| 13996 | first = first.isoWeekday(weekday); | ||
| 13997 | last = last.isoWeekday(weekday); | ||
| 13998 | } | ||
| 13999 | |||
| 14000 | // Align first/last ticks on unit | ||
| 14001 | first = first.startOf(weekday ? 'day' : minor); | ||
| 14002 | last = last.startOf(weekday ? 'day' : minor); | ||
| 14003 | |||
| 14004 | // Make sure that the last tick include max | ||
| 14005 | 	if (last < max) { | ||
| 14006 | last.add(1, minor); | ||
| 14007 | } | ||
| 14008 | |||
| 14009 | time = moment(first); | ||
| 14010 | |||
| 14011 | 	if (majorTicksEnabled && major && !weekday && !timeOpts.round) { | ||
| 14012 | // Align the first tick on the previous `minor` unit aligned on the `major` unit: | ||
| 14013 | // we first aligned time on the previous `major` unit then add the number of full | ||
| 14014 | // stepSize there is between first and the previous major time. | ||
| 14015 | time.startOf(major); | ||
| 14016 | time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor); | ||
| 14017 | } | ||
| 14018 | |||
| 14019 | 	for (; time < last; time.add(stepSize, minor)) { | ||
| 14020 | ticks.push(+time); | ||
| 14021 | } | ||
| 14022 | |||
| 14023 | ticks.push(+time); | ||
| 14024 | |||
| 14025 | return ticks; | ||
| 14026 | } | ||
| 14027 | |||
| 14028 | /** | ||
| 14029 |  * Returns the right and left offsets from edges in the form of {left, right}. | ||
| 14030 | * Offsets are added when the `offset` option is true. | ||
| 14031 | */ | ||
| 14032 | function computeOffsets(table, ticks, min, max, options) { | ||
| 14033 | var left = 0; | ||
| 14034 | var right = 0; | ||
| 14035 | var upper, lower; | ||
| 14036 | |||
| 14037 | 	if (options.offset && ticks.length) { | ||
| 14038 | 		if (!options.time.min) { | ||
| 14039 | upper = ticks.length > 1 ? ticks[1] : max; | ||
| 14040 | lower = ticks[0]; | ||
| 14041 | left = ( | ||
| 14042 | interpolate(table, 'time', upper, 'pos') - | ||
| 14043 | interpolate(table, 'time', lower, 'pos') | ||
| 14044 | ) / 2; | ||
| 14045 | } | ||
| 14046 | 		if (!options.time.max) { | ||
| 14047 | upper = ticks[ticks.length - 1]; | ||
| 14048 | lower = ticks.length > 1 ? ticks[ticks.length - 2] : min; | ||
| 14049 | right = ( | ||
| 14050 | interpolate(table, 'time', upper, 'pos') - | ||
| 14051 | interpolate(table, 'time', lower, 'pos') | ||
| 14052 | ) / 2; | ||
| 14053 | } | ||
| 14054 | } | ||
| 14055 | |||
| 14056 | 	return {left: left, right: right}; | ||
| 14057 | } | ||
| 14058 | |||
| 14059 | function ticksFromTimestamps(values, majorUnit) { | ||
| 14060 | var ticks = []; | ||
| 14061 | var i, ilen, value, major; | ||
| 14062 | |||
| 14063 | 	for (i = 0, ilen = values.length; i < ilen; ++i) { | ||
| 14064 | value = values[i]; | ||
| 14065 | major = majorUnit ? value === +moment(value).startOf(majorUnit) : false; | ||
| 14066 | |||
| 14067 | 		ticks.push({ | ||
| 14068 | value: value, | ||
| 14069 | major: major | ||
| 14070 | }); | ||
| 14071 | } | ||
| 14072 | |||
| 14073 | return ticks; | ||
| 14074 | } | ||
| 14075 | |||
| 14076 | function determineLabelFormat(data, timeOpts) { | ||
| 14077 | var i, momentDate, hasTime; | ||
| 14078 | var ilen = data.length; | ||
| 14079 | |||
| 14080 | // find the label with the most parts (milliseconds, minutes, etc.) | ||
| 14081 | // format all labels with the same level of detail as the most specific label | ||
| 14082 | 	for (i = 0; i < ilen; i++) { | ||
| 14083 | momentDate = momentify(data[i], timeOpts); | ||
| 14084 | 		if (momentDate.millisecond() !== 0) { | ||
| 14085 | return 'MMM D, YYYY h:mm:ss.SSS a'; | ||
| 14086 | } | ||
| 14087 | 		if (momentDate.second() !== 0 || momentDate.minute() !== 0 || momentDate.hour() !== 0) { | ||
| 14088 | hasTime = true; | ||
| 14089 | } | ||
| 14090 | } | ||
| 14091 | 	if (hasTime) { | ||
| 14092 | return 'MMM D, YYYY h:mm:ss a'; | ||
| 14093 | } | ||
| 14094 | return 'MMM D, YYYY'; | ||
| 14095 | } | ||
| 14096 | |||
| 14097 | module.exports = function() { | ||
| 14098 | |||
| 14099 | 	var defaultConfig = { | ||
| 14100 | position: 'bottom', | ||
| 14101 | |||
| 14102 | /** | ||
| 14103 | * Data distribution along the scale: | ||
| 14104 | * - 'linear': data are spread according to their time (distances can vary), | ||
| 14105 | * - 'series': data are spread at the same distance from each other. | ||
| 14106 | * @see https://github.com/chartjs/Chart.js/pull/4507 | ||
| 14107 | * @since 2.7.0 | ||
| 14108 | */ | ||
| 14109 | distribution: 'linear', | ||
| 14110 | |||
| 14111 | /** | ||
| 14112 | * Scale boundary strategy (bypassed by min/max time options) | ||
| 14113 | * - `data`: make sure data are fully visible, ticks outside are removed | ||
| 14114 | * - `ticks`: make sure ticks are fully visible, data outside are truncated | ||
| 14115 | * @see https://github.com/chartjs/Chart.js/pull/4556 | ||
| 14116 | * @since 2.7.0 | ||
| 14117 | */ | ||
| 14118 | bounds: 'data', | ||
| 14119 | |||
| 14120 | 		time: { | ||
| 14121 | 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 | ||
| 14122 | format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ | ||
| 14123 | unit: false, // false == automatic or override with week, month, year, etc. | ||
| 14124 | round: false, // none, or override with week, month, year, etc. | ||
| 14125 | displayFormat: false, // DEPRECATED | ||
| 14126 | isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ | ||
| 14127 | minUnit: 'millisecond', | ||
| 14128 | |||
| 14129 | // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ | ||
| 14130 | 			displayFormats: { | ||
| 14131 | millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, | ||
| 14132 | second: 'h:mm:ss a', // 11:20:01 AM | ||
| 14133 | minute: 'h:mm a', // 11:20 AM | ||
| 14134 | hour: 'hA', // 5PM | ||
| 14135 | day: 'MMM D', // Sep 4 | ||
| 14136 | week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? | ||
| 14137 | month: 'MMM YYYY', // Sept 2015 | ||
| 14138 | quarter: '[Q]Q - YYYY', // Q3 | ||
| 14139 | year: 'YYYY' // 2015 | ||
| 14140 | }, | ||
| 14141 | }, | ||
| 14142 | 		ticks: { | ||
| 14143 | autoSkip: false, | ||
| 14144 | |||
| 14145 | /** | ||
| 14146 | * Ticks generation input values: | ||
| 14147 | * - 'auto': generates "optimal" ticks based on scale size and time options. | ||
| 14148 | 			 * - 'data': generates ticks from data (including labels from data {t|x|y} objects). | ||
| 14149 | * - 'labels': generates ticks from user given `data.labels` values ONLY. | ||
| 14150 | * @see https://github.com/chartjs/Chart.js/pull/4507 | ||
| 14151 | * @since 2.7.0 | ||
| 14152 | */ | ||
| 14153 | source: 'auto', | ||
| 14154 | |||
| 14155 | 			major: { | ||
| 14156 | enabled: false | ||
| 14157 | } | ||
| 14158 | } | ||
| 14159 | }; | ||
| 14160 | |||
| 14161 | 	var TimeScale = Scale.extend({ | ||
| 14162 | 		initialize: function() { | ||
| 14163 | 			if (!moment) { | ||
| 14164 | 				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'); | ||
| 14165 | } | ||
| 14166 | |||
| 14167 | this.mergeTicksOptions(); | ||
| 14168 | |||
| 14169 | Scale.prototype.initialize.call(this); | ||
| 14170 | }, | ||
| 14171 | |||
| 14172 | 		update: function() { | ||
| 14173 | var me = this; | ||
| 14174 | var options = me.options; | ||
| 14175 | |||
| 14176 | // DEPRECATIONS: output a message only one time per update | ||
| 14177 | 			if (options.time && options.time.format) { | ||
| 14178 | 				console.warn('options.time.format is deprecated and replaced by options.time.parser.'); | ||
| 14179 | } | ||
| 14180 | |||
| 14181 | return Scale.prototype.update.apply(me, arguments); | ||
| 14182 | }, | ||
| 14183 | |||
| 14184 | /** | ||
| 14185 | * Allows data to be referenced via 't' attribute | ||
| 14186 | */ | ||
| 14187 | 		getRightValue: function(rawValue) { | ||
| 14188 | 			if (rawValue && rawValue.t !== undefined) { | ||
| 14189 | rawValue = rawValue.t; | ||
| 14190 | } | ||
| 14191 | return Scale.prototype.getRightValue.call(this, rawValue); | ||
| 14192 | }, | ||
| 14193 | |||
| 14194 | 		determineDataLimits: function() { | ||
| 14195 | var me = this; | ||
| 14196 | var chart = me.chart; | ||
| 14197 | var timeOpts = me.options.time; | ||
| 14198 | var unit = timeOpts.unit || 'day'; | ||
| 14199 | var min = MAX_INTEGER; | ||
| 14200 | var max = MIN_INTEGER; | ||
| 14201 | var timestamps = []; | ||
| 14202 | var datasets = []; | ||
| 14203 | var labels = []; | ||
| 14204 | var i, j, ilen, jlen, data, timestamp; | ||
| 14205 | |||
| 14206 | // Convert labels to timestamps | ||
| 14207 | 			for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) { | ||
| 14208 | labels.push(parse(chart.data.labels[i], me)); | ||
| 14209 | } | ||
| 14210 | |||
| 14211 | // Convert data to timestamps | ||
| 14212 | 			for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { | ||
| 14213 | 				if (chart.isDatasetVisible(i)) { | ||
| 14214 | data = chart.data.datasets[i].data; | ||
| 14215 | |||
| 14216 | // Let's consider that all data have the same format. | ||
| 14217 | 					if (helpers.isObject(data[0])) { | ||
| 14218 | datasets[i] = []; | ||
| 14219 | |||
| 14220 | 						for (j = 0, jlen = data.length; j < jlen; ++j) { | ||
| 14221 | timestamp = parse(data[j], me); | ||
| 14222 | timestamps.push(timestamp); | ||
| 14223 | datasets[i][j] = timestamp; | ||
| 14224 | } | ||
| 14225 | 					} else { | ||
| 14226 | timestamps.push.apply(timestamps, labels); | ||
| 14227 | datasets[i] = labels.slice(0); | ||
| 14228 | } | ||
| 14229 | 				} else { | ||
| 14230 | datasets[i] = []; | ||
| 14231 | } | ||
| 14232 | } | ||
| 14233 | |||
| 14234 | 			if (labels.length) { | ||
| 14235 | // Sort labels **after** data have been converted | ||
| 14236 | labels = arrayUnique(labels).sort(sorter); | ||
| 14237 | min = Math.min(min, labels[0]); | ||
| 14238 | max = Math.max(max, labels[labels.length - 1]); | ||
| 14239 | } | ||
| 14240 | |||
| 14241 | 			if (timestamps.length) { | ||
| 14242 | timestamps = arrayUnique(timestamps).sort(sorter); | ||
| 14243 | min = Math.min(min, timestamps[0]); | ||
| 14244 | max = Math.max(max, timestamps[timestamps.length - 1]); | ||
| 14245 | } | ||
| 14246 | |||
| 14247 | min = parse(timeOpts.min, me) || min; | ||
| 14248 | max = parse(timeOpts.max, me) || max; | ||
| 14249 | |||
| 14250 | // In case there is no valid min/max, set limits based on unit time option | ||
| 14251 | min = min === MAX_INTEGER ? +moment().startOf(unit) : min; | ||
| 14252 | max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max; | ||
| 14253 | |||
| 14254 | // Make sure that max is strictly higher than min (required by the lookup table) | ||
| 14255 | me.min = Math.min(min, max); | ||
| 14256 | me.max = Math.max(min + 1, max); | ||
| 14257 | |||
| 14258 | // PRIVATE | ||
| 14259 | me._horizontal = me.isHorizontal(); | ||
| 14260 | me._table = []; | ||
| 14261 | 			me._timestamps = { | ||
| 14262 | data: timestamps, | ||
| 14263 | datasets: datasets, | ||
| 14264 | labels: labels | ||
| 14265 | }; | ||
| 14266 | }, | ||
| 14267 | |||
| 14268 | 		buildTicks: function() { | ||
| 14269 | var me = this; | ||
| 14270 | var min = me.min; | ||
| 14271 | var max = me.max; | ||
| 14272 | var options = me.options; | ||
| 14273 | var timeOpts = options.time; | ||
| 14274 | var timestamps = []; | ||
| 14275 | var ticks = []; | ||
| 14276 | var i, ilen, timestamp; | ||
| 14277 | |||
| 14278 | 			switch (options.ticks.source) { | ||
| 14279 | case 'data': | ||
| 14280 | timestamps = me._timestamps.data; | ||
| 14281 | break; | ||
| 14282 | case 'labels': | ||
| 14283 | timestamps = me._timestamps.labels; | ||
| 14284 | break; | ||
| 14285 | case 'auto': | ||
| 14286 | default: | ||
| 14287 | timestamps = generate(min, max, me.getLabelCapacity(min), options); | ||
| 14288 | } | ||
| 14289 | |||
| 14290 | 			if (options.bounds === 'ticks' && timestamps.length) { | ||
| 14291 | min = timestamps[0]; | ||
| 14292 | max = timestamps[timestamps.length - 1]; | ||
| 14293 | } | ||
| 14294 | |||
| 14295 | // Enforce limits with user min/max options | ||
| 14296 | min = parse(timeOpts.min, me) || min; | ||
| 14297 | max = parse(timeOpts.max, me) || max; | ||
| 14298 | |||
| 14299 | // Remove ticks outside the min/max range | ||
| 14300 | 			for (i = 0, ilen = timestamps.length; i < ilen; ++i) { | ||
| 14301 | timestamp = timestamps[i]; | ||
| 14302 | 				if (timestamp >= min && timestamp <= max) { | ||
| 14303 | ticks.push(timestamp); | ||
| 14304 | } | ||
| 14305 | } | ||
| 14306 | |||
| 14307 | me.min = min; | ||
| 14308 | me.max = max; | ||
| 14309 | |||
| 14310 | // PRIVATE | ||
| 14311 | me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); | ||
| 14312 | me._majorUnit = determineMajorUnit(me._unit); | ||
| 14313 | me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); | ||
| 14314 | me._offsets = computeOffsets(me._table, ticks, min, max, options); | ||
| 14315 | me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts); | ||
| 14316 | |||
| 14317 | return ticksFromTimestamps(ticks, me._majorUnit); | ||
| 14318 | }, | ||
| 14319 | |||
| 14320 | 		getLabelForIndex: function(index, datasetIndex) { | ||
| 14321 | var me = this; | ||
| 14322 | var data = me.chart.data; | ||
| 14323 | var timeOpts = me.options.time; | ||
| 14324 | var label = data.labels && index < data.labels.length ? data.labels[index] : ''; | ||
| 14325 | var value = data.datasets[datasetIndex].data[index]; | ||
| 14326 | |||
| 14327 | 			if (helpers.isObject(value)) { | ||
| 14328 | label = me.getRightValue(value); | ||
| 14329 | } | ||
| 14330 | 			if (timeOpts.tooltipFormat) { | ||
| 14331 | return momentify(label, timeOpts).format(timeOpts.tooltipFormat); | ||
| 14332 | } | ||
| 14333 | 			if (typeof label === 'string') { | ||
| 14334 | return label; | ||
| 14335 | } | ||
| 14336 | |||
| 14337 | return momentify(label, timeOpts).format(me._labelFormat); | ||
| 14338 | }, | ||
| 14339 | |||
| 14340 | /** | ||
| 14341 | * Function to format an individual tick mark | ||
| 14342 | * @private | ||
| 14343 | */ | ||
| 14344 | 		tickFormatFunction: function(tick, index, ticks, formatOverride) { | ||
| 14345 | var me = this; | ||
| 14346 | var options = me.options; | ||
| 14347 | var time = tick.valueOf(); | ||
| 14348 | var formats = options.time.displayFormats; | ||
| 14349 | var minorFormat = formats[me._unit]; | ||
| 14350 | var majorUnit = me._majorUnit; | ||
| 14351 | var majorFormat = formats[majorUnit]; | ||
| 14352 | var majorTime = tick.clone().startOf(majorUnit).valueOf(); | ||
| 14353 | var majorTickOpts = options.ticks.major; | ||
| 14354 | var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; | ||
| 14355 | var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); | ||
| 14356 | var tickOpts = major ? majorTickOpts : options.ticks.minor; | ||
| 14357 | var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); | ||
| 14358 | |||
| 14359 | return formatter ? formatter(label, index, ticks) : label; | ||
| 14360 | }, | ||
| 14361 | |||
| 14362 | 		convertTicksToLabels: function(ticks) { | ||
| 14363 | var labels = []; | ||
| 14364 | var i, ilen; | ||
| 14365 | |||
| 14366 | 			for (i = 0, ilen = ticks.length; i < ilen; ++i) { | ||
| 14367 | labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks)); | ||
| 14368 | } | ||
| 14369 | |||
| 14370 | return labels; | ||
| 14371 | }, | ||
| 14372 | |||
| 14373 | /** | ||
| 14374 | * @private | ||
| 14375 | */ | ||
| 14376 | 		getPixelForOffset: function(time) { | ||
| 14377 | var me = this; | ||
| 14378 | var size = me._horizontal ? me.width : me.height; | ||
| 14379 | var start = me._horizontal ? me.left : me.top; | ||
| 14380 | var pos = interpolate(me._table, 'time', time, 'pos'); | ||
| 14381 | |||
| 14382 | return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right); | ||
| 14383 | }, | ||
| 14384 | |||
| 14385 | 		getPixelForValue: function(value, index, datasetIndex) { | ||
| 14386 | var me = this; | ||
| 14387 | var time = null; | ||
| 14388 | |||
| 14389 | 			if (index !== undefined && datasetIndex !== undefined) { | ||
| 14390 | time = me._timestamps.datasets[datasetIndex][index]; | ||
| 14391 | } | ||
| 14392 | |||
| 14393 | 			if (time === null) { | ||
| 14394 | time = parse(value, me); | ||
| 14395 | } | ||
| 14396 | |||
| 14397 | 			if (time !== null) { | ||
| 14398 | return me.getPixelForOffset(time); | ||
| 14399 | } | ||
| 14400 | }, | ||
| 14401 | |||
| 14402 | 		getPixelForTick: function(index) { | ||
| 14403 | var ticks = this.getTicks(); | ||
| 14404 | return index >= 0 && index < ticks.length ? | ||
| 14405 | this.getPixelForOffset(ticks[index].value) : | ||
| 14406 | null; | ||
| 14407 | }, | ||
| 14408 | |||
| 14409 | 		getValueForPixel: function(pixel) { | ||
| 14410 | var me = this; | ||
| 14411 | var size = me._horizontal ? me.width : me.height; | ||
| 14412 | var start = me._horizontal ? me.left : me.top; | ||
| 14413 | var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right; | ||
| 14414 | var time = interpolate(me._table, 'pos', pos, 'time'); | ||
| 14415 | |||
| 14416 | return moment(time); | ||
| 14417 | }, | ||
| 14418 | |||
| 14419 | /** | ||
| 14420 | * Crude approximation of what the label width might be | ||
| 14421 | * @private | ||
| 14422 | */ | ||
| 14423 | 		getLabelWidth: function(label) { | ||
| 14424 | var me = this; | ||
| 14425 | var ticksOpts = me.options.ticks; | ||
| 14426 | var tickLabelWidth = me.ctx.measureText(label).width; | ||
| 14427 | var angle = helpers.toRadians(ticksOpts.maxRotation); | ||
| 14428 | var cosRotation = Math.cos(angle); | ||
| 14429 | var sinRotation = Math.sin(angle); | ||
| 14430 | var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); | ||
| 14431 | |||
| 14432 | return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); | ||
| 14433 | }, | ||
| 14434 | |||
| 14435 | /** | ||
| 14436 | * @private | ||
| 14437 | */ | ||
| 14438 | 		getLabelCapacity: function(exampleTime) { | ||
| 14439 | var me = this; | ||
| 14440 | |||
| 14441 | var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation | ||
| 14442 | |||
| 14443 | var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride); | ||
| 14444 | var tickLabelWidth = me.getLabelWidth(exampleLabel); | ||
| 14445 | var innerWidth = me.isHorizontal() ? me.width : me.height; | ||
| 14446 | |||
| 14447 | var capacity = Math.floor(innerWidth / tickLabelWidth); | ||
| 14448 | return capacity > 0 ? capacity : 1; | ||
| 14449 | } | ||
| 14450 | }); | ||
| 14451 | |||
| 14452 | 	scaleService.registerScaleType('time', TimeScale, defaultConfig); | ||
| 14453 | }; | ||
| 14454 | |||
| 14455 | },{"1":1,"26":26,"33":33,"34":34,"46":46}]},{},[7])(7) | ||
| 14456 | }); | ||
| 14457 |