| 1 |
/*! |
| 2 |
* Chart.js |
| 3 |
* http://chartjs.org/ |
| 4 |
* Version: 2.7.1 |
| 5 |
* |
| 6 |
* Copyright 2017 Nick Downie |
| 7 |
* Released under the MIT license |
| 8 |
* https://github.com/chartjs/Chart.js/blob/master/LICENSE.md |
| 9 |
*/ |
| 10 |
window.imagify = window.imagify || {}; |
| 11 |
(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.imagify}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ |
| 12 |
|
| 13 |
},{}],2:[function(require,module,exports){ |
| 14 |
/* MIT license */ |
| 15 |
var colorNames = require(6); |
| 16 |
|
| 17 |
module.exports = { |
| 18 |
getRgba: getRgba, |
| 19 |
getHsla: getHsla, |
| 20 |
getRgb: getRgb, |
| 21 |
getHsl: getHsl, |
| 22 |
getHwb: getHwb, |
| 23 |
getAlpha: getAlpha, |
| 24 |
|
| 25 |
hexString: hexString, |
| 26 |
rgbString: rgbString, |
| 27 |
rgbaString: rgbaString, |
| 28 |
percentString: percentString, |
| 29 |
percentaString: percentaString, |
| 30 |
hslString: hslString, |
| 31 |
hslaString: hslaString, |
| 32 |
hwbString: hwbString, |
| 33 |
keyword: keyword |
| 34 |
} |
| 35 |
|
| 36 |
function getRgba(string) { |
| 37 |
if (!string) { |
| 38 |
return; |
| 39 |
} |
| 40 |
var abbr = /^#([a-fA-F0-9]{3})$/i, |
| 41 |
hex = /^#([a-fA-F0-9]{6})$/i, |
| 42 |
rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, |
| 43 |
per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, |
| 44 |
keyword = /(\w+)/; |
| 45 |
|
| 46 |
var rgb = [0, 0, 0], |
| 47 |
a = 1, |
| 48 |
match = string.match(abbr); |
| 49 |
if (match) { |
| 50 |
match = match[1]; |
| 51 |
for (var i = 0; i < rgb.length; i++) { |
| 52 |
rgb[i] = parseInt(match[i] + match[i], 16); |
| 53 |
} |
| 54 |
} |
| 55 |
else if (match = string.match(hex)) { |
| 56 |
match = match[1]; |
| 57 |
for (var i = 0; i < rgb.length; i++) { |
| 58 |
rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); |
| 59 |
} |
| 60 |
} |
| 61 |
else if (match = string.match(rgba)) { |
| 62 |
for (var i = 0; i < rgb.length; i++) { |
| 63 |
rgb[i] = parseInt(match[i + 1]); |
| 64 |
} |
| 65 |
a = parseFloat(match[4]); |
| 66 |
} |
| 67 |
else if (match = string.match(per)) { |
| 68 |
for (var i = 0; i < rgb.length; i++) { |
| 69 |
rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); |
| 70 |
} |
| 71 |
a = parseFloat(match[4]); |
| 72 |
} |
| 73 |
else if (match = string.match(keyword)) { |
| 74 |
if (match[1] == "transparent") { |
| 75 |
return [0, 0, 0, 0]; |
| 76 |
} |
| 77 |
rgb = colorNames[match[1]]; |
| 78 |
if (!rgb) { |
| 79 |
return; |
| 80 |
} |
| 81 |
} |
| 82 |
|
| 83 |
for (var i = 0; i < rgb.length; i++) { |
| 84 |
rgb[i] = scale(rgb[i], 0, 255); |
| 85 |
} |
| 86 |
if (!a && a != 0) { |
| 87 |
a = 1; |
| 88 |
} |
| 89 |
else { |
| 90 |
a = scale(a, 0, 1); |
| 91 |
} |
| 92 |
rgb[3] = a; |
| 93 |
return rgb; |
| 94 |
} |
| 95 |
|
| 96 |
function getHsla(string) { |
| 97 |
if (!string) { |
| 98 |
return; |
| 99 |
} |
| 100 |
var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; |
| 101 |
var match = string.match(hsl); |
| 102 |
if (match) { |
| 103 |
var alpha = parseFloat(match[4]); |
| 104 |
var h = scale(parseInt(match[1]), 0, 360), |
| 105 |
s = scale(parseFloat(match[2]), 0, 100), |
| 106 |
l = scale(parseFloat(match[3]), 0, 100), |
| 107 |
a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); |
| 108 |
return [h, s, l, a]; |
| 109 |
} |
| 110 |
} |
| 111 |
|
| 112 |
function getHwb(string) { |
| 113 |
if (!string) { |
| 114 |
return; |
| 115 |
} |
| 116 |
var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; |
| 117 |
var match = string.match(hwb); |
| 118 |
if (match) { |
| 119 |
var alpha = parseFloat(match[4]); |
| 120 |
var h = scale(parseInt(match[1]), 0, 360), |
| 121 |
w = scale(parseFloat(match[2]), 0, 100), |
| 122 |
b = scale(parseFloat(match[3]), 0, 100), |
| 123 |
a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); |
| 124 |
return [h, w, b, a]; |
| 125 |
} |
| 126 |
} |
| 127 |
|
| 128 |
function getRgb(string) { |
| 129 |
var rgba = getRgba(string); |
| 130 |
return rgba && rgba.slice(0, 3); |
| 131 |
} |
| 132 |
|
| 133 |
function getHsl(string) { |
| 134 |
var hsla = getHsla(string); |
| 135 |
return hsla && hsla.slice(0, 3); |
| 136 |
} |
| 137 |
|
| 138 |
function getAlpha(string) { |
| 139 |
var vals = getRgba(string); |
| 140 |
if (vals) { |
| 141 |
return vals[3]; |
| 142 |
} |
| 143 |
else if (vals = getHsla(string)) { |
| 144 |
return vals[3]; |
| 145 |
} |
| 146 |
else if (vals = getHwb(string)) { |
| 147 |
return vals[3]; |
| 148 |
} |
| 149 |
} |
| 150 |
|
| 151 |
// generators |
| 152 |
function hexString(rgb) { |
| 153 |
return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1]) |
| 154 |
+ hexDouble(rgb[2]); |
| 155 |
} |
| 156 |
|
| 157 |
function rgbString(rgba, alpha) { |
| 158 |
if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { |
| 159 |
return rgbaString(rgba, alpha); |
| 160 |
} |
| 161 |
return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; |
| 162 |
} |
| 163 |
|
| 164 |
function rgbaString(rgba, alpha) { |
| 165 |
if (alpha === undefined) { |
| 166 |
alpha = (rgba[3] !== undefined ? rgba[3] : 1); |
| 167 |
} |
| 168 |
return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] |
| 169 |
+ ", " + alpha + ")"; |
| 170 |
} |
| 171 |
|
| 172 |
function percentString(rgba, alpha) { |
| 173 |
if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { |
| 174 |
return percentaString(rgba, alpha); |
| 175 |
} |
| 176 |
var r = Math.round(rgba[0]/255 * 100), |
| 177 |
g = Math.round(rgba[1]/255 * 100), |
| 178 |
b = Math.round(rgba[2]/255 * 100); |
| 179 |
|
| 180 |
return "rgb(" + r + "%, " + g + "%, " + b + "%)"; |
| 181 |
} |
| 182 |
|
| 183 |
function percentaString(rgba, alpha) { |
| 184 |
var r = Math.round(rgba[0]/255 * 100), |
| 185 |
g = Math.round(rgba[1]/255 * 100), |
| 186 |
b = Math.round(rgba[2]/255 * 100); |
| 187 |
return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; |
| 188 |
} |
| 189 |
|
| 190 |
function hslString(hsla, alpha) { |
| 191 |
if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { |
| 192 |
return hslaString(hsla, alpha); |
| 193 |
} |
| 194 |
return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; |
| 195 |
} |
| 196 |
|
| 197 |
function hslaString(hsla, alpha) { |
| 198 |
if (alpha === undefined) { |
| 199 |
alpha = (hsla[3] !== undefined ? hsla[3] : 1); |
| 200 |
} |
| 201 |
return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " |
| 202 |
+ alpha + ")"; |
| 203 |
} |
| 204 |
|
| 205 |
// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax |
| 206 |
// (hwb have alpha optional & 1 is default value) |
| 207 |
function hwbString(hwb, alpha) { |
| 208 |
if (alpha === undefined) { |
| 209 |
alpha = (hwb[3] !== undefined ? hwb[3] : 1); |
| 210 |
} |
| 211 |
return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" |
| 212 |
+ (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; |
| 213 |
} |
| 214 |
|
| 215 |
function keyword(rgb) { |
| 216 |
return reverseNames[rgb.slice(0, 3)]; |
| 217 |
} |
| 218 |
|
| 219 |
// helpers |
| 220 |
function scale(num, min, max) { |
| 221 |
return Math.min(Math.max(min, num), max); |
| 222 |
} |
| 223 |
|
| 224 |
function hexDouble(num) { |
| 225 |
var str = num.toString(16).toUpperCase(); |
| 226 |
return (str.length < 2) ? "0" + str : str; |
| 227 |
} |
| 228 |
|
| 229 |
|
| 230 |
//create a list of reverse color names |
| 231 |
var reverseNames = {}; |
| 232 |
for (var name in colorNames) { |
| 233 |
reverseNames[colorNames[name]] = name; |
| 234 |
} |
| 235 |
|
| 236 |
},{"6":6}],3:[function(require,module,exports){ |
| 237 |
/* MIT license */ |
| 238 |
var convert = require(5); |
| 239 |
var string = require(2); |
| 240 |
|
| 241 |
var Color = function (obj) { |
| 242 |
if (obj instanceof Color) { |
| 243 |
return obj; |
| 244 |
} |
| 245 |
if (!(this instanceof Color)) { |
| 246 |
return new Color(obj); |
| 247 |
} |
| 248 |
|
| 249 |
this.valid = false; |
| 250 |
this.values = { |
| 251 |
rgb: [0, 0, 0], |
| 252 |
hsl: [0, 0, 0], |
| 253 |
hsv: [0, 0, 0], |
| 254 |
hwb: [0, 0, 0], |
| 255 |
cmyk: [0, 0, 0, 0], |
| 256 |
alpha: 1 |
| 257 |
}; |
| 258 |
|
| 259 |
// parse Color() argument |
| 260 |
var vals; |
| 261 |
if (typeof obj === 'string') { |
| 262 |
vals = string.getRgba(obj); |
| 263 |
if (vals) { |
| 264 |
this.setValues('rgb', vals); |
| 265 |
} else if (vals = string.getHsla(obj)) { |
| 266 |
this.setValues('hsl', vals); |
| 267 |
} else if (vals = string.getHwb(obj)) { |
| 268 |
this.setValues('hwb', vals); |
| 269 |
} |
| 270 |
} else if (typeof obj === 'object') { |
| 271 |
vals = obj; |
| 272 |
if (vals.r !== undefined || vals.red !== undefined) { |
| 273 |
this.setValues('rgb', vals); |
| 274 |
} else if (vals.l !== undefined || vals.lightness !== undefined) { |
| 275 |
this.setValues('hsl', vals); |
| 276 |
} else if (vals.v !== undefined || vals.value !== undefined) { |
| 277 |
this.setValues('hsv', vals); |
| 278 |
} else if (vals.w !== undefined || vals.whiteness !== undefined) { |
| 279 |
this.setValues('hwb', vals); |
| 280 |
} else if (vals.c !== undefined || vals.cyan !== undefined) { |
| 281 |
this.setValues('cmyk', vals); |
| 282 |
} |
| 283 |
} |
| 284 |
}; |
| 285 |
|
| 286 |
Color.prototype = { |
| 287 |
isValid: function () { |
| 288 |
return this.valid; |
| 289 |
}, |
| 290 |
rgb: function () { |
| 291 |
return this.setSpace('rgb', arguments); |
| 292 |
}, |
| 293 |
hsl: function () { |
| 294 |
return this.setSpace('hsl', arguments); |
| 295 |
}, |
| 296 |
hsv: function () { |
| 297 |
return this.setSpace('hsv', arguments); |
| 298 |
}, |
| 299 |
hwb: function () { |
| 300 |
return this.setSpace('hwb', arguments); |
| 301 |
}, |
| 302 |
cmyk: function () { |
| 303 |
return this.setSpace('cmyk', arguments); |
| 304 |
}, |
| 305 |
|
| 306 |
rgbArray: function () { |
| 307 |
return this.values.rgb; |
| 308 |
}, |
| 309 |
hslArray: function () { |
| 310 |
return this.values.hsl; |
| 311 |
}, |
| 312 |
hsvArray: function () { |
| 313 |
return this.values.hsv; |
| 314 |
}, |
| 315 |
hwbArray: function () { |
| 316 |
var values = this.values; |
| 317 |
if (values.alpha !== 1) { |
| 318 |
return values.hwb.concat([values.alpha]); |
| 319 |
} |
| 320 |
return values.hwb; |
| 321 |
}, |
| 322 |
cmykArray: function () { |
| 323 |
return this.values.cmyk; |
| 324 |
}, |
| 325 |
rgbaArray: function () { |
| 326 |
var values = this.values; |
| 327 |
return values.rgb.concat([values.alpha]); |
| 328 |
}, |
| 329 |
hslaArray: function () { |
| 330 |
var values = this.values; |
| 331 |
return values.hsl.concat([values.alpha]); |
| 332 |
}, |
| 333 |
alpha: function (val) { |
| 334 |
if (val === undefined) { |
| 335 |
return this.values.alpha; |
| 336 |
} |
| 337 |
this.setValues('alpha', val); |
| 338 |
return this; |
| 339 |
}, |
| 340 |
|
| 341 |
red: function (val) { |
| 342 |
return this.setChannel('rgb', 0, val); |
| 343 |
}, |
| 344 |
green: function (val) { |
| 345 |
return this.setChannel('rgb', 1, val); |
| 346 |
}, |
| 347 |
blue: function (val) { |
| 348 |
return this.setChannel('rgb', 2, val); |
| 349 |
}, |
| 350 |
hue: function (val) { |
| 351 |
if (val) { |
| 352 |
val %= 360; |
| 353 |
val = val < 0 ? 360 + val : val; |
| 354 |
} |
| 355 |
return this.setChannel('hsl', 0, val); |
| 356 |
}, |
| 357 |
saturation: function (val) { |
| 358 |
return this.setChannel('hsl', 1, val); |
| 359 |
}, |
| 360 |
lightness: function (val) { |
| 361 |
return this.setChannel('hsl', 2, val); |
| 362 |
}, |
| 363 |
saturationv: function (val) { |
| 364 |
return this.setChannel('hsv', 1, val); |
| 365 |
}, |
| 366 |
whiteness: function (val) { |
| 367 |
return this.setChannel('hwb', 1, val); |
| 368 |
}, |
| 369 |
blackness: function (val) { |
| 370 |
return this.setChannel('hwb', 2, val); |
| 371 |
}, |
| 372 |
value: function (val) { |
| 373 |
return this.setChannel('hsv', 2, val); |
| 374 |
}, |
| 375 |
cyan: function (val) { |
| 376 |
return this.setChannel('cmyk', 0, val); |
| 377 |
}, |
| 378 |
magenta: function (val) { |
| 379 |
return this.setChannel('cmyk', 1, val); |
| 380 |
}, |
| 381 |
yellow: function (val) { |
| 382 |
return this.setChannel('cmyk', 2, val); |
| 383 |
}, |
| 384 |
black: function (val) { |
| 385 |
return this.setChannel('cmyk', 3, val); |
| 386 |
}, |
| 387 |
|
| 388 |
hexString: function () { |
| 389 |
return string.hexString(this.values.rgb); |
| 390 |
}, |
| 391 |
rgbString: function () { |
| 392 |
return string.rgbString(this.values.rgb, this.values.alpha); |
| 393 |
}, |
| 394 |
rgbaString: function () { |
| 395 |
return string.rgbaString(this.values.rgb, this.values.alpha); |
| 396 |
}, |
| 397 |
percentString: function () { |
| 398 |
return string.percentString(this.values.rgb, this.values.alpha); |
| 399 |
}, |
| 400 |
hslString: function () { |
| 401 |
return string.hslString(this.values.hsl, this.values.alpha); |
| 402 |
}, |
| 403 |
hslaString: function () { |
| 404 |
return string.hslaString(this.values.hsl, this.values.alpha); |
| 405 |
}, |
| 406 |
hwbString: function () { |
| 407 |
return string.hwbString(this.values.hwb, this.values.alpha); |
| 408 |
}, |
| 409 |
keyword: function () { |
| 410 |
return string.keyword(this.values.rgb, this.values.alpha); |
| 411 |
}, |
| 412 |
|
| 413 |
rgbNumber: function () { |
| 414 |
var rgb = this.values.rgb; |
| 415 |
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; |
| 416 |
}, |
| 417 |
|
| 418 |
luminosity: function () { |
| 419 |
// http://www.w3.org/TR/WCAG20/#relativeluminancedef |
| 420 |
var rgb = this.values.rgb; |
| 421 |
var lum = []; |
| 422 |
for (var i = 0; i < rgb.length; i++) { |
| 423 |
var chan = rgb[i] / 255; |
| 424 |
lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); |
| 425 |
} |
| 426 |
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; |
| 427 |
}, |
| 428 |
|
| 429 |
contrast: function (color2) { |
| 430 |
// http://www.w3.org/TR/WCAG20/#contrast-ratiodef |
| 431 |
var lum1 = this.luminosity(); |
| 432 |
var lum2 = color2.luminosity(); |
| 433 |
if (lum1 > lum2) { |
| 434 |
return (lum1 + 0.05) / (lum2 + 0.05); |
| 435 |
} |
| 436 |
return (lum2 + 0.05) / (lum1 + 0.05); |
| 437 |
}, |
| 438 |
|
| 439 |
level: function (color2) { |
| 440 |
var contrastRatio = this.contrast(color2); |
| 441 |
if (contrastRatio >= 7.1) { |
| 442 |
return 'AAA'; |
| 443 |
} |
| 444 |
|
| 445 |
return (contrastRatio >= 4.5) ? 'AA' : ''; |
| 446 |
}, |
| 447 |
|
| 448 |
dark: function () { |
| 449 |
// YIQ equation from http://24ways.org/2010/calculating-color-contrast |
| 450 |
var rgb = this.values.rgb; |
| 451 |
var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; |
| 452 |
return yiq < 128; |
| 453 |
}, |
| 454 |
|
| 455 |
light: function () { |
| 456 |
return !this.dark(); |
| 457 |
}, |
| 458 |
|
| 459 |
negate: function () { |
| 460 |
var rgb = []; |
| 461 |
for (var i = 0; i < 3; i++) { |
| 462 |
rgb[i] = 255 - this.values.rgb[i]; |
| 463 |
} |
| 464 |
this.setValues('rgb', rgb); |
| 465 |
return this; |
| 466 |
}, |
| 467 |
|
| 468 |
lighten: function (ratio) { |
| 469 |
var hsl = this.values.hsl; |
| 470 |
hsl[2] += hsl[2] * ratio; |
| 471 |
this.setValues('hsl', hsl); |
| 472 |
return this; |
| 473 |
}, |
| 474 |
|
| 475 |
darken: function (ratio) { |
| 476 |
var hsl = this.values.hsl; |
| 477 |
hsl[2] -= hsl[2] * ratio; |
| 478 |
this.setValues('hsl', hsl); |
| 479 |
return this; |
| 480 |
}, |
| 481 |
|
| 482 |
saturate: function (ratio) { |
| 483 |
var hsl = this.values.hsl; |
| 484 |
hsl[1] += hsl[1] * ratio; |
| 485 |
this.setValues('hsl', hsl); |
| 486 |
return this; |
| 487 |
}, |
| 488 |
|
| 489 |
desaturate: function (ratio) { |
| 490 |
var hsl = this.values.hsl; |
| 491 |
hsl[1] -= hsl[1] * ratio; |
| 492 |
this.setValues('hsl', hsl); |
| 493 |
return this; |
| 494 |
}, |
| 495 |
|
| 496 |
whiten: function (ratio) { |
| 497 |
var hwb = this.values.hwb; |
| 498 |
hwb[1] += hwb[1] * ratio; |
| 499 |
this.setValues('hwb', hwb); |
| 500 |
return this; |
| 501 |
}, |
| 502 |
|
| 503 |
blacken: function (ratio) { |
| 504 |
var hwb = this.values.hwb; |
| 505 |
hwb[2] += hwb[2] * ratio; |
| 506 |
this.setValues('hwb', hwb); |
| 507 |
return this; |
| 508 |
}, |
| 509 |
|
| 510 |
greyscale: function () { |
| 511 |
var rgb = this.values.rgb; |
| 512 |
// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale |
| 513 |
var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; |
| 514 |
this.setValues('rgb', [val, val, val]); |
| 515 |
return this; |
| 516 |
}, |
| 517 |
|
| 518 |
clearer: function (ratio) { |
| 519 |
var alpha = this.values.alpha; |
| 520 |
this.setValues('alpha', alpha - (alpha * ratio)); |
| 521 |
return this; |
| 522 |
}, |
| 523 |
|
| 524 |
opaquer: function (ratio) { |
| 525 |
var alpha = this.values.alpha; |
| 526 |
this.setValues('alpha', alpha + (alpha * ratio)); |
| 527 |
return this; |
| 528 |
}, |
| 529 |
|
| 530 |
rotate: function (degrees) { |
| 531 |
var hsl = this.values.hsl; |
| 532 |
var hue = (hsl[0] + degrees) % 360; |
| 533 |
hsl[0] = hue < 0 ? 360 + hue : hue; |
| 534 |
this.setValues('hsl', hsl); |
| 535 |
return this; |
| 536 |
}, |
| 537 |
|
| 538 |
/** |
| 539 |
* Ported from sass implementation in C |
| 540 |
* https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 |
| 541 |
*/ |
| 542 |
mix: function (mixinColor, weight) { |
| 543 |
var color1 = this; |
| 544 |
var color2 = mixinColor; |
| 545 |
var p = weight === undefined ? 0.5 : weight; |
| 546 |
|
| 547 |
var w = 2 * p - 1; |
| 548 |
var a = color1.alpha() - color2.alpha(); |
| 549 |
|
| 550 |
var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; |
| 551 |
var w2 = 1 - w1; |
| 552 |
|
| 553 |
return this |
| 554 |
.rgb( |
| 555 |
w1 * color1.red() + w2 * color2.red(), |
| 556 |
w1 * color1.green() + w2 * color2.green(), |
| 557 |
w1 * color1.blue() + w2 * color2.blue() |
| 558 |
) |
| 559 |
.alpha(color1.alpha() * p + color2.alpha() * (1 - p)); |
| 560 |
}, |
| 561 |
|
| 562 |
toJSON: function () { |
| 563 |
return this.rgb(); |
| 564 |
}, |
| 565 |
|
| 566 |
clone: function () { |
| 567 |
// NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, |
| 568 |
// making the final build way to big to embed in Chart.js. So let's do it manually, |
| 569 |
// assuming that values to clone are 1 dimension arrays containing only numbers, |
| 570 |
// except 'alpha' which is a number. |
| 571 |
var result = new Color(); |
| 572 |
var source = this.values; |
| 573 |
var target = result.values; |
| 574 |
var value, type; |
| 575 |
|
| 576 |
for (var prop in source) { |
| 577 |
if (source.hasOwnProperty(prop)) { |
| 578 |
value = source[prop]; |
| 579 |
type = ({}).toString.call(value); |
| 580 |
if (type === '[object Array]') { |
| 581 |
target[prop] = value.slice(0); |
| 582 |
} else if (type === '[object Number]') { |
| 583 |
target[prop] = value; |
| 584 |
} else { |
| 585 |
console.error('unexpected color value:', value); |
| 586 |
} |
| 587 |
} |
| 588 |
} |
| 589 |
|
| 590 |
return result; |
| 591 |
} |
| 592 |
}; |
| 593 |
|
| 594 |
Color.prototype.spaces = { |
| 595 |
rgb: ['red', 'green', 'blue'], |
| 596 |
hsl: ['hue', 'saturation', 'lightness'], |
| 597 |
hsv: ['hue', 'saturation', 'value'], |
| 598 |
hwb: ['hue', 'whiteness', 'blackness'], |
| 599 |
cmyk: ['cyan', 'magenta', 'yellow', 'black'] |
| 600 |
}; |
| 601 |
|
| 602 |
Color.prototype.maxes = { |
| 603 |
rgb: [255, 255, 255], |
| 604 |
hsl: [360, 100, 100], |
| 605 |
hsv: [360, 100, 100], |
| 606 |
hwb: [360, 100, 100], |
| 607 |
cmyk: [100, 100, 100, 100] |
| 608 |
}; |
| 609 |
|
| 610 |
Color.prototype.getValues = function (space) { |
| 611 |
var values = this.values; |
| 612 |
var vals = {}; |
| 613 |
|
| 614 |
for (var i = 0; i < space.length; i++) { |
| 615 |
vals[space.charAt(i)] = values[space][i]; |
| 616 |
} |
| 617 |
|
| 618 |
if (values.alpha !== 1) { |
| 619 |
vals.a = values.alpha; |
| 620 |
} |
| 621 |
|
| 622 |
// {r: 255, g: 255, b: 255, a: 0.4} |
| 623 |
return vals; |
| 624 |
}; |
| 625 |
|
| 626 |
Color.prototype.setValues = function (space, vals) { |
| 627 |
var values = this.values; |
| 628 |
var spaces = this.spaces; |
| 629 |
var maxes = this.maxes; |
| 630 |
var alpha = 1; |
| 631 |
var i; |
| 632 |
|
| 633 |
this.valid = true; |
| 634 |
|
| 635 |
if (space === 'alpha') { |
| 636 |
alpha = vals; |
| 637 |
} else if (vals.length) { |
| 638 |
// [10, 10, 10] |
| 639 |
values[space] = vals.slice(0, space.length); |
| 640 |
alpha = vals[space.length]; |
| 641 |
} else if (vals[space.charAt(0)] !== undefined) { |
| 642 |
// {r: 10, g: 10, b: 10} |
| 643 |
for (i = 0; i < space.length; i++) { |
| 644 |
values[space][i] = vals[space.charAt(i)]; |
| 645 |
} |
| 646 |
|
| 647 |
alpha = vals.a; |
| 648 |
} else if (vals[spaces[space][0]] !== undefined) { |
| 649 |
// {red: 10, green: 10, blue: 10} |
| 650 |
var chans = spaces[space]; |
| 651 |
|
| 652 |
for (i = 0; i < space.length; i++) { |
| 653 |
values[space][i] = vals[chans[i]]; |
| 654 |
} |
| 655 |
|
| 656 |
alpha = vals.alpha; |
| 657 |
} |
| 658 |
|
| 659 |
values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); |
| 660 |
|
| 661 |
if (space === 'alpha') { |
| 662 |
return false; |
| 663 |
} |
| 664 |
|
| 665 |
var capped; |
| 666 |
|
| 667 |
// cap values of the space prior converting all values |
| 668 |
for (i = 0; i < space.length; i++) { |
| 669 |
capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); |
| 670 |
values[space][i] = Math.round(capped); |
| 671 |
} |
| 672 |
|
| 673 |
// convert to all the other color spaces |
| 674 |
for (var sname in spaces) { |
| 675 |
if (sname !== space) { |
| 676 |
values[sname] = convert[space][sname](values[space]); |
| 677 |
} |
| 678 |
} |
| 679 |
|
| 680 |
return true; |
| 681 |
}; |
| 682 |
|
| 683 |
Color.prototype.setSpace = function (space, args) { |
| 684 |
var vals = args[0]; |
| 685 |
|
| 686 |
if (vals === undefined) { |
| 687 |
// color.rgb() |
| 688 |
return this.getValues(space); |
| 689 |
} |
| 690 |
|
| 691 |
// color.rgb(10, 10, 10) |
| 692 |
if (typeof vals === 'number') { |
| 693 |
vals = Array.prototype.slice.call(args); |
| 694 |
} |
| 695 |
|
| 696 |
this.setValues(space, vals); |
| 697 |
return this; |
| 698 |
}; |
| 699 |
|
| 700 |
Color.prototype.setChannel = function (space, index, val) { |
| 701 |
var svalues = this.values[space]; |
| 702 |
if (val === undefined) { |
| 703 |
// color.red() |
| 704 |
return svalues[index]; |
| 705 |
} else if (val === svalues[index]) { |
| 706 |
// color.red(color.red()) |
| 707 |
return this; |
| 708 |
} |
| 709 |
|
| 710 |
// color.red(100) |
| 711 |
svalues[index] = val; |
| 712 |
this.setValues(space, svalues); |
| 713 |
|
| 714 |
return this; |
| 715 |
}; |
| 716 |
|
| 717 |
if (typeof window !== 'undefined') { |
| 718 |
window.imagify.Color = Color; |
| 719 |
} |
| 720 |
|
| 721 |
module.exports = Color; |
| 722 |
|
| 723 |
},{"2":2,"5":5}],4:[function(require,module,exports){ |
| 724 |
/* MIT license */ |
| 725 |
|
| 726 |
module.exports = { |
| 727 |
rgb2hsl: rgb2hsl, |
| 728 |
rgb2hsv: rgb2hsv, |
| 729 |
rgb2hwb: rgb2hwb, |
| 730 |
rgb2cmyk: rgb2cmyk, |
| 731 |
rgb2keyword: rgb2keyword, |
| 732 |
rgb2xyz: rgb2xyz, |
| 733 |
rgb2lab: rgb2lab, |
| 734 |
rgb2lch: rgb2lch, |
| 735 |
|
| 736 |
hsl2rgb: hsl2rgb, |
| 737 |
hsl2hsv: hsl2hsv, |
| 738 |
hsl2hwb: hsl2hwb, |
| 739 |
hsl2cmyk: hsl2cmyk, |
| 740 |
hsl2keyword: hsl2keyword, |
| 741 |
|
| 742 |
hsv2rgb: hsv2rgb, |
| 743 |
hsv2hsl: hsv2hsl, |
| 744 |
hsv2hwb: hsv2hwb, |
| 745 |
hsv2cmyk: hsv2cmyk, |
| 746 |
hsv2keyword: hsv2keyword, |
| 747 |
|
| 748 |
hwb2rgb: hwb2rgb, |
| 749 |
hwb2hsl: hwb2hsl, |
| 750 |
hwb2hsv: hwb2hsv, |
| 751 |
hwb2cmyk: hwb2cmyk, |
| 752 |
hwb2keyword: hwb2keyword, |
| 753 |
|
| 754 |
cmyk2rgb: cmyk2rgb, |
| 755 |
cmyk2hsl: cmyk2hsl, |
| 756 |
cmyk2hsv: cmyk2hsv, |
| 757 |
cmyk2hwb: cmyk2hwb, |
| 758 |
cmyk2keyword: cmyk2keyword, |
| 759 |
|
| 760 |
keyword2rgb: keyword2rgb, |
| 761 |
keyword2hsl: keyword2hsl, |
| 762 |
keyword2hsv: keyword2hsv, |
| 763 |
keyword2hwb: keyword2hwb, |
| 764 |
keyword2cmyk: keyword2cmyk, |
| 765 |
keyword2lab: keyword2lab, |
| 766 |
keyword2xyz: keyword2xyz, |
| 767 |
|
| 768 |
xyz2rgb: xyz2rgb, |
| 769 |
xyz2lab: xyz2lab, |
| 770 |
xyz2lch: xyz2lch, |
| 771 |
|
| 772 |
lab2xyz: lab2xyz, |
| 773 |
lab2rgb: lab2rgb, |
| 774 |
lab2lch: lab2lch, |
| 775 |
|
| 776 |
lch2lab: lch2lab, |
| 777 |
lch2xyz: lch2xyz, |
| 778 |
lch2rgb: lch2rgb |
| 779 |
} |
| 780 |
|
| 781 |
|
| 782 |
function rgb2hsl(rgb) { |
| 783 |
var r = rgb[0]/255, |
| 784 |
g = rgb[1]/255, |
| 785 |
b = rgb[2]/255, |
| 786 |
min = Math.min(r, g, b), |
| 787 |
max = Math.max(r, g, b), |
| 788 |
delta = max - min, |
| 789 |
h, s, l; |
| 790 |
|
| 791 |
if (max == min) |
| 792 |
h = 0; |
| 793 |
else if (r == max) |
| 794 |
h = (g - b) / delta; |
| 795 |
else if (g == max) |
| 796 |
h = 2 + (b - r) / delta; |
| 797 |
else if (b == max) |
| 798 |
h = 4 + (r - g)/ delta; |
| 799 |
|
| 800 |
h = Math.min(h * 60, 360); |
| 801 |
|
| 802 |
if (h < 0) |
| 803 |
h += 360; |
| 804 |
|
| 805 |
l = (min + max) / 2; |
| 806 |
|
| 807 |
if (max == min) |
| 808 |
s = 0; |
| 809 |
else if (l <= 0.5) |
| 810 |
s = delta / (max + min); |
| 811 |
else |
| 812 |
s = delta / (2 - max - min); |
| 813 |
|
| 814 |
return [h, s * 100, l * 100]; |
| 815 |
} |
| 816 |
|
| 817 |
function rgb2hsv(rgb) { |
| 818 |
var r = rgb[0], |
| 819 |
g = rgb[1], |
| 820 |
b = rgb[2], |
| 821 |
min = Math.min(r, g, b), |
| 822 |
max = Math.max(r, g, b), |
| 823 |
delta = max - min, |
| 824 |
h, s, v; |
| 825 |
|
| 826 |
if (max == 0) |
| 827 |
s = 0; |
| 828 |
else |
| 829 |
s = (delta/max * 1000)/10; |
| 830 |
|
| 831 |
if (max == min) |
| 832 |
h = 0; |
| 833 |
else if (r == max) |
| 834 |
h = (g - b) / delta; |
| 835 |
else if (g == max) |
| 836 |
h = 2 + (b - r) / delta; |
| 837 |
else if (b == max) |
| 838 |
h = 4 + (r - g) / delta; |
| 839 |
|
| 840 |
h = Math.min(h * 60, 360); |
| 841 |
|
| 842 |
if (h < 0) |
| 843 |
h += 360; |
| 844 |
|
| 845 |
v = ((max / 255) * 1000) / 10; |
| 846 |
|
| 847 |
return [h, s, v]; |
| 848 |
} |
| 849 |
|
| 850 |
function rgb2hwb(rgb) { |
| 851 |
var r = rgb[0], |
| 852 |
g = rgb[1], |
| 853 |
b = rgb[2], |
| 854 |
h = rgb2hsl(rgb)[0], |
| 855 |
w = 1/255 * Math.min(r, Math.min(g, b)), |
| 856 |
b = 1 - 1/255 * Math.max(r, Math.max(g, b)); |
| 857 |
|
| 858 |
return [h, w * 100, b * 100]; |
| 859 |
} |
| 860 |
|
| 861 |
function rgb2cmyk(rgb) { |
| 862 |
var r = rgb[0] / 255, |
| 863 |
g = rgb[1] / 255, |
| 864 |
b = rgb[2] / 255, |
| 865 |
c, m, y, k; |
| 866 |
|
| 867 |
k = Math.min(1 - r, 1 - g, 1 - b); |
| 868 |
c = (1 - r - k) / (1 - k) || 0; |
| 869 |
m = (1 - g - k) / (1 - k) || 0; |
| 870 |
y = (1 - b - k) / (1 - k) || 0; |
| 871 |
return [c * 100, m * 100, y * 100, k * 100]; |
| 872 |
} |
| 873 |
|
| 874 |
function rgb2keyword(rgb) { |
| 875 |
return reverseKeywords[JSON.stringify(rgb)]; |
| 876 |
} |
| 877 |
|
| 878 |
function rgb2xyz(rgb) { |
| 879 |
var r = rgb[0] / 255, |
| 880 |
g = rgb[1] / 255, |
| 881 |
b = rgb[2] / 255; |
| 882 |
|
| 883 |
// assume sRGB |
| 884 |
r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); |
| 885 |
g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); |
| 886 |
b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); |
| 887 |
|
| 888 |
var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); |
| 889 |
var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); |
| 890 |
var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); |
| 891 |
|
| 892 |
return [x * 100, y *100, z * 100]; |
| 893 |
} |
| 894 |
|
| 895 |
function rgb2lab(rgb) { |
| 896 |
var xyz = rgb2xyz(rgb), |
| 897 |
x = xyz[0], |
| 898 |
y = xyz[1], |
| 899 |
z = xyz[2], |
| 900 |
l, a, b; |
| 901 |
|
| 902 |
x /= 95.047; |
| 903 |
y /= 100; |
| 904 |
z /= 108.883; |
| 905 |
|
| 906 |
x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); |
| 907 |
y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); |
| 908 |
z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); |
| 909 |
|
| 910 |
l = (116 * y) - 16; |
| 911 |
a = 500 * (x - y); |
| 912 |
b = 200 * (y - z); |
| 913 |
|
| 914 |
return [l, a, b]; |
| 915 |
} |
| 916 |
|
| 917 |
function rgb2lch(args) { |
| 918 |
return lab2lch(rgb2lab(args)); |
| 919 |
} |
| 920 |
|
| 921 |
function hsl2rgb(hsl) { |
| 922 |
var h = hsl[0] / 360, |
| 923 |
s = hsl[1] / 100, |
| 924 |
l = hsl[2] / 100, |
| 925 |
t1, t2, t3, rgb, val; |
| 926 |
|
| 927 |
if (s == 0) { |
| 928 |
val = l * 255; |
| 929 |
return [val, val, val]; |
| 930 |
} |
| 931 |
|
| 932 |
if (l < 0.5) |
| 933 |
t2 = l * (1 + s); |
| 934 |
else |
| 935 |
t2 = l + s - l * s; |
| 936 |
t1 = 2 * l - t2; |
| 937 |
|
| 938 |
rgb = [0, 0, 0]; |
| 939 |
for (var i = 0; i < 3; i++) { |
| 940 |
t3 = h + 1 / 3 * - (i - 1); |
| 941 |
t3 < 0 && t3++; |
| 942 |
t3 > 1 && t3--; |
| 943 |
|
| 944 |
if (6 * t3 < 1) |
| 945 |
val = t1 + (t2 - t1) * 6 * t3; |
| 946 |
else if (2 * t3 < 1) |
| 947 |
val = t2; |
| 948 |
else if (3 * t3 < 2) |
| 949 |
val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; |
| 950 |
else |
| 951 |
val = t1; |
| 952 |
|
| 953 |
rgb[i] = val * 255; |
| 954 |
} |
| 955 |
|
| 956 |
return rgb; |
| 957 |
} |
| 958 |
|
| 959 |
function hsl2hsv(hsl) { |
| 960 |
var h = hsl[0], |
| 961 |
s = hsl[1] / 100, |
| 962 |
l = hsl[2] / 100, |
| 963 |
sv, v; |
| 964 |
|
| 965 |
if(l === 0) { |
| 966 |
// no need to do calc on black |
| 967 |
// also avoids divide by 0 error |
| 968 |
return [0, 0, 0]; |
| 969 |
} |
| 970 |
|
| 971 |
l *= 2; |
| 972 |
s *= (l <= 1) ? l : 2 - l; |
| 973 |
v = (l + s) / 2; |
| 974 |
sv = (2 * s) / (l + s); |
| 975 |
return [h, sv * 100, v * 100]; |
| 976 |
} |
| 977 |
|
| 978 |
function hsl2hwb(args) { |
| 979 |
return rgb2hwb(hsl2rgb(args)); |
| 980 |
} |
| 981 |
|
| 982 |
function hsl2cmyk(args) { |
| 983 |
return rgb2cmyk(hsl2rgb(args)); |
| 984 |
} |
| 985 |
|
| 986 |
function hsl2keyword(args) { |
| 987 |
return rgb2keyword(hsl2rgb(args)); |
| 988 |
} |
| 989 |
|
| 990 |
|
| 991 |
function hsv2rgb(hsv) { |
| 992 |
var h = hsv[0] / 60, |
| 993 |
s = hsv[1] / 100, |
| 994 |
v = hsv[2] / 100, |
| 995 |
hi = Math.floor(h) % 6; |
| 996 |
|
| 997 |
var f = h - Math.floor(h), |
| 998 |
p = 255 * v * (1 - s), |
| 999 |
q = 255 * v * (1 - (s * f)), |
| 1000 |
t = 255 * v * (1 - (s * (1 - f))), |
| 1001 |
v = 255 * v; |
| 1002 |
|
| 1003 |
switch(hi) { |
| 1004 |
case 0: |
| 1005 |
return [v, t, p]; |
| 1006 |
case 1: |
| 1007 |
return [q, v, p]; |
| 1008 |
case 2: |
| 1009 |
return [p, v, t]; |
| 1010 |
case 3: |
| 1011 |
return [p, q, v]; |
| 1012 |
case 4: |
| 1013 |
return [t, p, v]; |
| 1014 |
case 5: |
| 1015 |
return [v, p, q]; |
| 1016 |
} |
| 1017 |
} |
| 1018 |
|
| 1019 |
function hsv2hsl(hsv) { |
| 1020 |
var h = hsv[0], |
| 1021 |
s = hsv[1] / 100, |
| 1022 |
v = hsv[2] / 100, |
| 1023 |
sl, l; |
| 1024 |
|
| 1025 |
l = (2 - s) * v; |
| 1026 |
sl = s * v; |
| 1027 |
sl /= (l <= 1) ? l : 2 - l; |
| 1028 |
sl = sl || 0; |
| 1029 |
l /= 2; |
| 1030 |
return [h, sl * 100, l * 100]; |
| 1031 |
} |
| 1032 |
|
| 1033 |
function hsv2hwb(args) { |
| 1034 |
return rgb2hwb(hsv2rgb(args)) |
| 1035 |
} |
| 1036 |
|
| 1037 |
function hsv2cmyk(args) { |
| 1038 |
return rgb2cmyk(hsv2rgb(args)); |
| 1039 |
} |
| 1040 |
|
| 1041 |
function hsv2keyword(args) { |
| 1042 |
return rgb2keyword(hsv2rgb(args)); |
| 1043 |
} |
| 1044 |
|
| 1045 |
// http://dev.w3.org/csswg/css-color/#hwb-to-rgb |
| 1046 |
function hwb2rgb(hwb) { |
| 1047 |
var h = hwb[0] / 360, |
| 1048 |
wh = hwb[1] / 100, |
| 1049 |
bl = hwb[2] / 100, |
| 1050 |
ratio = wh + bl, |
| 1051 |
i, v, f, n; |
| 1052 |
|
| 1053 |
// wh + bl cant be > 1 |
| 1054 |
if (ratio > 1) { |
| 1055 |
wh /= ratio; |
| 1056 |
bl /= ratio; |
| 1057 |
} |
| 1058 |
|
| 1059 |
i = Math.floor(6 * h); |
| 1060 |
v = 1 - bl; |
| 1061 |
f = 6 * h - i; |
| 1062 |
if ((i & 0x01) != 0) { |
| 1063 |
f = 1 - f; |
| 1064 |
} |
| 1065 |
n = wh + f * (v - wh); // linear interpolation |
| 1066 |
|
| 1067 |
switch (i) { |
| 1068 |
default: |
| 1069 |
case 6: |
| 1070 |
case 0: r = v; g = n; b = wh; break; |
| 1071 |
case 1: r = n; g = v; b = wh; break; |
| 1072 |
case 2: r = wh; g = v; b = n; break; |
| 1073 |
case 3: r = wh; g = n; b = v; break; |
| 1074 |
case 4: r = n; g = wh; b = v; break; |
| 1075 |
case 5: r = v; g = wh; b = n; break; |
| 1076 |
} |
| 1077 |
|
| 1078 |
return [r * 255, g * 255, b * 255]; |
| 1079 |
} |
| 1080 |
|
| 1081 |
function hwb2hsl(args) { |
| 1082 |
return rgb2hsl(hwb2rgb(args)); |
| 1083 |
} |
| 1084 |
|
| 1085 |
function hwb2hsv(args) { |
| 1086 |
return rgb2hsv(hwb2rgb(args)); |
| 1087 |
} |
| 1088 |
|
| 1089 |
function hwb2cmyk(args) { |
| 1090 |
return rgb2cmyk(hwb2rgb(args)); |
| 1091 |
} |
| 1092 |
|
| 1093 |
function hwb2keyword(args) { |
| 1094 |
return rgb2keyword(hwb2rgb(args)); |
| 1095 |
} |
| 1096 |
|
| 1097 |
function cmyk2rgb(cmyk) { |
| 1098 |
var c = cmyk[0] / 100, |
| 1099 |
m = cmyk[1] / 100, |
| 1100 |
y = cmyk[2] / 100, |
| 1101 |
k = cmyk[3] / 100, |
| 1102 |
r, g, b; |
| 1103 |
|
| 1104 |
r = 1 - Math.min(1, c * (1 - k) + k); |
| 1105 |
g = 1 - Math.min(1, m * (1 - k) + k); |
| 1106 |
b = 1 - Math.min(1, y * (1 - k) + k); |
| 1107 |
return [r * 255, g * 255, b * 255]; |
| 1108 |
} |
| 1109 |
|
| 1110 |
function cmyk2hsl(args) { |
| 1111 |
return rgb2hsl(cmyk2rgb(args)); |
| 1112 |
} |
| 1113 |
|
| 1114 |
function cmyk2hsv(args) { |
| 1115 |
return rgb2hsv(cmyk2rgb(args)); |
| 1116 |
} |
| 1117 |
|
| 1118 |
function cmyk2hwb(args) { |
| 1119 |
return rgb2hwb(cmyk2rgb(args)); |
| 1120 |
} |
| 1121 |
|
| 1122 |
function cmyk2keyword(args) { |
| 1123 |
return rgb2keyword(cmyk2rgb(args)); |
| 1124 |
} |
| 1125 |
|
| 1126 |
|
| 1127 |
function xyz2rgb(xyz) { |
| 1128 |
var x = xyz[0] / 100, |
| 1129 |
y = xyz[1] / 100, |
| 1130 |
z = xyz[2] / 100, |
| 1131 |
r, g, b; |
| 1132 |
|
| 1133 |
r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); |
| 1134 |
g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); |
| 1135 |
b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); |
| 1136 |
|
| 1137 |
// assume sRGB |
| 1138 |
r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) |
| 1139 |
: r = (r * 12.92); |
| 1140 |
|
| 1141 |
g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) |
| 1142 |
: g = (g * 12.92); |
| 1143 |
|
| 1144 |
b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) |
| 1145 |
: b = (b * 12.92); |
| 1146 |
|
| 1147 |
r = Math.min(Math.max(0, r), 1); |
| 1148 |
g = Math.min(Math.max(0, g), 1); |
| 1149 |
b = Math.min(Math.max(0, b), 1); |
| 1150 |
|
| 1151 |
return [r * 255, g * 255, b * 255]; |
| 1152 |
} |
| 1153 |
|
| 1154 |
function xyz2lab(xyz) { |
| 1155 |
var x = xyz[0], |
| 1156 |
y = xyz[1], |
| 1157 |
z = xyz[2], |
| 1158 |
l, a, b; |
| 1159 |
|
| 1160 |
x /= 95.047; |
| 1161 |
y /= 100; |
| 1162 |
z /= 108.883; |
| 1163 |
|
| 1164 |
x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); |
| 1165 |
y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); |
| 1166 |
z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); |
| 1167 |
|
| 1168 |
l = (116 * y) - 16; |
| 1169 |
a = 500 * (x - y); |
| 1170 |
b = 200 * (y - z); |
| 1171 |
|
| 1172 |
return [l, a, b]; |
| 1173 |
} |
| 1174 |
|
| 1175 |
function xyz2lch(args) { |
| 1176 |
return lab2lch(xyz2lab(args)); |
| 1177 |
} |
| 1178 |
|
| 1179 |
function lab2xyz(lab) { |
| 1180 |
var l = lab[0], |
| 1181 |
a = lab[1], |
| 1182 |
b = lab[2], |
| 1183 |
x, y, z, y2; |
| 1184 |
|
| 1185 |
if (l <= 8) { |
| 1186 |
y = (l * 100) / 903.3; |
| 1187 |
y2 = (7.787 * (y / 100)) + (16 / 116); |
| 1188 |
} else { |
| 1189 |
y = 100 * Math.pow((l + 16) / 116, 3); |
| 1190 |
y2 = Math.pow(y / 100, 1/3); |
| 1191 |
} |
| 1192 |
|
| 1193 |
x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); |
| 1194 |
|
| 1195 |
z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); |
| 1196 |
|
| 1197 |
return [x, y, z]; |
| 1198 |
} |
| 1199 |
|
| 1200 |
function lab2lch(lab) { |
| 1201 |
var l = lab[0], |
| 1202 |
a = lab[1], |
| 1203 |
b = lab[2], |
| 1204 |
hr, h, c; |
| 1205 |
|
| 1206 |
hr = Math.atan2(b, a); |
| 1207 |
h = hr * 360 / 2 / Math.PI; |
| 1208 |
if (h < 0) { |
| 1209 |
h += 360; |
| 1210 |
} |
| 1211 |
c = Math.sqrt(a * a + b * b); |
| 1212 |
return [l, c, h]; |
| 1213 |
} |
| 1214 |
|
| 1215 |
function lab2rgb(args) { |
| 1216 |
return xyz2rgb(lab2xyz(args)); |
| 1217 |
} |
| 1218 |
|
| 1219 |
function lch2lab(lch) { |
| 1220 |
var l = lch[0], |
| 1221 |
c = lch[1], |
| 1222 |
h = lch[2], |
| 1223 |
a, b, hr; |
| 1224 |
|
| 1225 |
hr = h / 360 * 2 * Math.PI; |
| 1226 |
a = c * Math.cos(hr); |
| 1227 |
b = c * Math.sin(hr); |
| 1228 |
return [l, a, b]; |
| 1229 |
} |
| 1230 |
|
| 1231 |
function lch2xyz(args) { |
| 1232 |
return lab2xyz(lch2lab(args)); |
| 1233 |
} |
| 1234 |
|
| 1235 |
function lch2rgb(args) { |
| 1236 |
return lab2rgb(lch2lab(args)); |
| 1237 |
} |
| 1238 |
|
| 1239 |
function keyword2rgb(keyword) { |
| 1240 |
return cssKeywords[keyword]; |
| 1241 |
} |
| 1242 |
|
| 1243 |
function keyword2hsl(args) { |
| 1244 |
return rgb2hsl(keyword2rgb(args)); |
| 1245 |
} |
| 1246 |
|
| 1247 |
function keyword2hsv(args) { |
| 1248 |
return rgb2hsv(keyword2rgb(args)); |
| 1249 |
} |
| 1250 |
|
| 1251 |
function keyword2hwb(args) { |
| 1252 |
return rgb2hwb(keyword2rgb(args)); |
| 1253 |
} |
| 1254 |
|
| 1255 |
function keyword2cmyk(args) { |
| 1256 |
return rgb2cmyk(keyword2rgb(args)); |
| 1257 |
} |
| 1258 |
|
| 1259 |
function keyword2lab(args) { |
| 1260 |
return rgb2lab(keyword2rgb(args)); |
| 1261 |
} |
| 1262 |
|
| 1263 |
function keyword2xyz(args) { |
| 1264 |
return rgb2xyz(keyword2rgb(args)); |
| 1265 |
} |
| 1266 |
|
| 1267 |
var cssKeywords = { |
| 1268 |
aliceblue: [240,248,255], |
| 1269 |
antiquewhite: [250,235,215], |
| 1270 |
aqua: [0,255,255], |
| 1271 |
aquamarine: [127,255,212], |
| 1272 |
azure: [240,255,255], |
| 1273 |
beige: [245,245,220], |
| 1274 |
bisque: [255,228,196], |
| 1275 |
black: [0,0,0], |
| 1276 |
blanchedalmond: [255,235,205], |
| 1277 |
blue: [0,0,255], |
| 1278 |
blueviolet: [138,43,226], |
| 1279 |
brown: [165,42,42], |
| 1280 |
burlywood: [222,184,135], |
| 1281 |
cadetblue: [95,158,160], |
| 1282 |
chartreuse: [127,255,0], |
| 1283 |
chocolate: [210,105,30], |
| 1284 |
coral: [255,127,80], |
| 1285 |
cornflowerblue: [100,149,237], |
| 1286 |
cornsilk: [255,248,220], |
| 1287 |
crimson: [220,20,60], |
| 1288 |
cyan: [0,255,255], |
| 1289 |
darkblue: [0,0,139], |
| 1290 |
darkcyan: [0,139,139], |
| 1291 |
darkgoldenrod: [184,134,11], |
| 1292 |
darkgray: [169,169,169], |
| 1293 |
darkgreen: [0,100,0], |
| 1294 |
darkgrey: [169,169,169], |
| 1295 |
darkkhaki: [189,183,107], |
| 1296 |
darkmagenta: [139,0,139], |
| 1297 |
darkolivegreen: [85,107,47], |
| 1298 |
darkorange: [255,140,0], |
| 1299 |
darkorchid: [153,50,204], |
| 1300 |
darkred: [139,0,0], |
| 1301 |
darksalmon: [233,150,122], |
| 1302 |
darkseagreen: [143,188,143], |
| 1303 |
darkslateblue: [72,61,139], |
| 1304 |
darkslategray: [47,79,79], |
| 1305 |
darkslategrey: [47,79,79], |
| 1306 |
darkturquoise: [0,206,209], |
| 1307 |
darkviolet: [148,0,211], |
| 1308 |
deeppink: [255,20,147], |
| 1309 |
deepskyblue: [0,191,255], |
| 1310 |
dimgray: [105,105,105], |
| 1311 |
dimgrey: [105,105,105], |
| 1312 |
dodgerblue: [30,144,255], |
| 1313 |
firebrick: [178,34,34], |
| 1314 |
floralwhite: [255,250,240], |
| 1315 |
forestgreen: [34,139,34], |
| 1316 |
fuchsia: [255,0,255], |
| 1317 |
gainsboro: [220,220,220], |
| 1318 |
ghostwhite: [248,248,255], |
| 1319 |
gold: [255,215,0], |
| 1320 |
goldenrod: [218,165,32], |
| 1321 |
gray: [128,128,128], |
| 1322 |
green: [0,128,0], |
| 1323 |
greenyellow: [173,255,47], |
| 1324 |
grey: [128,128,128], |
| 1325 |
honeydew: [240,255,240], |
| 1326 |
hotpink: [255,105,180], |
| 1327 |
indianred: [205,92,92], |
| 1328 |
indigo: [75,0,130], |
| 1329 |
ivory: [255,255,240], |
| 1330 |
khaki: [240,230,140], |
| 1331 |
lavender: [230,230,250], |
| 1332 |
lavenderblush: [255,240,245], |
| 1333 |
lawngreen: [124,252,0], |
| 1334 |
lemonchiffon: [255,250,205], |
| 1335 |
lightblue: [173,216,230], |
| 1336 |
lightcoral: [240,128,128], |
| 1337 |
lightcyan: [224,255,255], |
| 1338 |
lightgoldenrodyellow: [250,250,210], |
| 1339 |
lightgray: [211,211,211], |
| 1340 |
lightgreen: [144,238,144], |
| 1341 |
lightgrey: [211,211,211], |
| 1342 |
lightpink: [255,182,193], |
| 1343 |
lightsalmon: [255,160,122], |
| 1344 |
lightseagreen: [32,178,170], |
| 1345 |
lightskyblue: [135,206,250], |
| 1346 |
lightslategray: [119,136,153], |
| 1347 |
lightslategrey: [119,136,153], |
| 1348 |
lightsteelblue: [176,196,222], |
| 1349 |
lightyellow: [255,255,224], |
| 1350 |
lime: [0,255,0], |
| 1351 |
limegreen: [50,205,50], |
| 1352 |
linen: [250,240,230], |
| 1353 |
magenta: [255,0,255], |
| 1354 |
maroon: [128,0,0], |
| 1355 |
mediumaquamarine: [102,205,170], |
| 1356 |
mediumblue: [0,0,205], |
| 1357 |
mediumorchid: [186,85,211], |
| 1358 |
mediumpurple: [147,112,219], |
| 1359 |
mediumseagreen: [60,179,113], |
| 1360 |
mediumslateblue: [123,104,238], |
| 1361 |
mediumspringgreen: [0,250,154], |
| 1362 |
mediumturquoise: [72,209,204], |
| 1363 |
mediumvioletred: [199,21,133], |
| 1364 |
midnightblue: [25,25,112], |
| 1365 |
mintcream: [245,255,250], |
| 1366 |
mistyrose: [255,228,225], |
| 1367 |
moccasin: [255,228,181], |
| 1368 |
navajowhite: [255,222,173], |
| 1369 |
navy: [0,0,128], |
| 1370 |
oldlace: [253,245,230], |
| 1371 |
olive: [128,128,0], |
| 1372 |
olivedrab: [107,142,35], |
| 1373 |
orange: [255,165,0], |
| 1374 |
orangered: [255,69,0], |
| 1375 |
orchid: [218,112,214], |
| 1376 |
palegoldenrod: [238,232,170], |
| 1377 |
palegreen: [152,251,152], |
| 1378 |
paleturquoise: [175,238,238], |
| 1379 |
palevioletred: [219,112,147], |
| 1380 |
papayawhip: [255,239,213], |
| 1381 |
peachpuff: [255,218,185], |
| 1382 |
peru: [205,133,63], |
| 1383 |
pink: [255,192,203], |
| 1384 |
plum: [221,160,221], |
| 1385 |
powderblue: [176,224,230], |
| 1386 |
purple: [128,0,128], |
| 1387 |
rebeccapurple: [102, 51, 153], |
| 1388 |
red: [255,0,0], |
| 1389 |
rosybrown: [188,143,143], |
| 1390 |
royalblue: [65,105,225], |
| 1391 |
saddlebrown: [139,69,19], |
| 1392 |
salmon: [250,128,114], |
| 1393 |
sandybrown: [244,164,96], |
| 1394 |
seagreen: [46,139,87], |
| 1395 |
seashell: [255,245,238], |
| 1396 |
sienna: [160,82,45], |
| 1397 |
silver: [192,192,192], |
| 1398 |
skyblue: [135,206,235], |
| 1399 |
slateblue: [106,90,205], |
| 1400 |
slategray: [112,128,144], |
| 1401 |
slategrey: [112,128,144], |
| 1402 |
snow: [255,250,250], |
| 1403 |
springgreen: [0,255,127], |
| 1404 |
steelblue: [70,130,180], |
| 1405 |
tan: [210,180,140], |
| 1406 |
teal: [0,128,128], |
| 1407 |
thistle: [216,191,216], |
| 1408 |
tomato: [255,99,71], |
| 1409 |
turquoise: [64,224,208], |
| 1410 |
violet: [238,130,238], |
| 1411 |
wheat: [245,222,179], |
| 1412 |
white: [255,255,255], |
| 1413 |
whitesmoke: [245,245,245], |
| 1414 |
yellow: [255,255,0], |
| 1415 |
yellowgreen: [154,205,50] |
| 1416 |
}; |
| 1417 |
|
| 1418 |
var reverseKeywords = {}; |
| 1419 |
for (var key in cssKeywords) { |
| 1420 |
reverseKeywords[JSON.stringify(cssKeywords[key])] = key; |
| 1421 |
} |
| 1422 |
|
| 1423 |
},{}],5:[function(require,module,exports){ |
| 1424 |
var conversions = require(4); |
| 1425 |
|
| 1426 |
var convert = function() { |
| 1427 |
return new Converter(); |
| 1428 |
} |
| 1429 |
|
| 1430 |
for (var func in conversions) { |
| 1431 |
// export Raw versions |
| 1432 |
convert[func + "Raw"] = (function(func) { |
| 1433 |
// accept array or plain args |
| 1434 |
return function(arg) { |
| 1435 |
if (typeof arg == "number") |
| 1436 |
arg = Array.prototype.slice.call(arguments); |
| 1437 |
return conversions[func](arg); |
| 1438 |
} |
| 1439 |
})(func); |
| 1440 |
|
| 1441 |
var pair = /(\w+)2(\w+)/.exec(func), |
| 1442 |
from = pair[1], |
| 1443 |
to = pair[2]; |
| 1444 |
|
| 1445 |
// export rgb2hsl and ["rgb"]["hsl"] |
| 1446 |
convert[from] = convert[from] || {}; |
| 1447 |
|
| 1448 |
convert[from][to] = convert[func] = (function(func) { |
| 1449 |
return function(arg) { |
| 1450 |
if (typeof arg == "number") |
| 1451 |
arg = Array.prototype.slice.call(arguments); |
| 1452 |
|
| 1453 |
var val = conversions[func](arg); |
| 1454 |
if (typeof val == "string" || val === undefined) |
| 1455 |
return val; // keyword |
| 1456 |
|
| 1457 |
for (var i = 0; i < val.length; i++) |
| 1458 |
val[i] = Math.round(val[i]); |
| 1459 |
return val; |
| 1460 |
} |
| 1461 |
})(func); |
| 1462 |
} |
| 1463 |
|
| 1464 |
|
| 1465 |
/* Converter does lazy conversion and caching */ |
| 1466 |
var Converter = function() { |
| 1467 |
this.convs = {}; |
| 1468 |
}; |
| 1469 |
|
| 1470 |
/* Either get the values for a space or |
| 1471 |
set the values for a space, depending on args */ |
| 1472 |
Converter.prototype.routeSpace = function(space, args) { |
| 1473 |
var values = args[0]; |
| 1474 |
if (values === undefined) { |
| 1475 |
// color.rgb() |
| 1476 |
return this.getValues(space); |
| 1477 |
} |
| 1478 |
// color.rgb(10, 10, 10) |
| 1479 |
if (typeof values == "number") { |
| 1480 |
values = Array.prototype.slice.call(args); |
| 1481 |
} |
| 1482 |
|
| 1483 |
return this.setValues(space, values); |
| 1484 |
}; |
| 1485 |
|
| 1486 |
/* Set the values for a space, invalidating cache */ |
| 1487 |
Converter.prototype.setValues = function(space, values) { |
| 1488 |
this.space = space; |
| 1489 |
this.convs = {}; |
| 1490 |
this.convs[space] = values; |
| 1491 |
return this; |
| 1492 |
}; |
| 1493 |
|
| 1494 |
/* Get the values for a space. If there's already |
| 1495 |
a conversion for the space, fetch it, otherwise |
| 1496 |
compute it */ |
| 1497 |
Converter.prototype.getValues = function(space) { |
| 1498 |
var vals = this.convs[space]; |
| 1499 |
if (!vals) { |
| 1500 |
var fspace = this.space, |
| 1501 |
from = this.convs[fspace]; |
| 1502 |
vals = convert[fspace][space](from); |
| 1503 |
|
| 1504 |
this.convs[space] = vals; |
| 1505 |
} |
| 1506 |
return vals; |
| 1507 |
}; |
| 1508 |
|
| 1509 |
["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { |
| 1510 |
Converter.prototype[space] = function(vals) { |
| 1511 |
return this.routeSpace(space, arguments); |
| 1512 |
} |
| 1513 |
}); |
| 1514 |
|
| 1515 |
module.exports = convert; |
| 1516 |
},{"4":4}],6:[function(require,module,exports){ |
| 1517 |
'use strict' |
| 1518 |
|
| 1519 |
module.exports = { |
| 1520 |
"aliceblue": [240, 248, 255], |
| 1521 |
"antiquewhite": [250, 235, 215], |
| 1522 |
"aqua": [0, 255, 255], |
| 1523 |
"aquamarine": [127, 255, 212], |
| 1524 |
"azure": [240, 255, 255], |
| 1525 |
"beige": [245, 245, 220], |
| 1526 |
"bisque": [255, 228, 196], |
| 1527 |
"black": [0, 0, 0], |
| 1528 |
"blanchedalmond": [255, 235, 205], |
| 1529 |
"blue": [0, 0, 255], |
| 1530 |
"blueviolet": [138, 43, 226], |
| 1531 |
"brown": [165, 42, 42], |
| 1532 |
"burlywood": [222, 184, 135], |
| 1533 |
"cadetblue": [95, 158, 160], |
| 1534 |
"chartreuse": [127, 255, 0], |
| 1535 |
"chocolate": [210, 105, 30], |
| 1536 |
"coral": [255, 127, 80], |
| 1537 |
"cornflowerblue": [100, 149, 237], |
| 1538 |
"cornsilk": [255, 248, 220], |
| 1539 |
"crimson": [220, 20, 60], |
| 1540 |
"cyan": [0, 255, 255], |
| 1541 |
"darkblue": [0, 0, 139], |
| 1542 |
"darkcyan": [0, 139, 139], |
| 1543 |
"darkgoldenrod": [184, 134, 11], |
| 1544 |
"darkgray": [169, 169, 169], |
| 1545 |
"darkgreen": [0, 100, 0], |
| 1546 |
"darkgrey": [169, 169, 169], |
| 1547 |
"darkkhaki": [189, 183, 107], |
| 1548 |
"darkmagenta": [139, 0, 139], |
| 1549 |
"darkolivegreen": [85, 107, 47], |
| 1550 |
"darkorange": [255, 140, 0], |
| 1551 |
"darkorchid": [153, 50, 204], |
| 1552 |
"darkred": [139, 0, 0], |
| 1553 |
"darksalmon": [233, 150, 122], |
| 1554 |
"darkseagreen": [143, 188, 143], |
| 1555 |
"darkslateblue": [72, 61, 139], |
| 1556 |
"darkslategray": [47, 79, 79], |
| 1557 |
"darkslategrey": [47, 79, 79], |
| 1558 |
"darkturquoise": [0, 206, 209], |
| 1559 |
"darkviolet": [148, 0, 211], |
| 1560 |
"deeppink": [255, 20, 147], |
| 1561 |
"deepskyblue": [0, 191, 255], |
| 1562 |
"dimgray": [105, 105, 105], |
| 1563 |
"dimgrey": [105, 105, 105], |
| 1564 |
"dodgerblue": [30, 144, 255], |
| 1565 |
"firebrick": [178, 34, 34], |
| 1566 |
"floralwhite": [255, 250, 240], |
| 1567 |
"forestgreen": [34, 139, 34], |
| 1568 |
"fuchsia": [255, 0, 255], |
| 1569 |
"gainsboro": [220, 220, 220], |
| 1570 |
"ghostwhite": [248, 248, 255], |
| 1571 |
"gold": [255, 215, 0], |
| 1572 |
"goldenrod": [218, 165, 32], |
| 1573 |
"gray": [128, 128, 128], |
| 1574 |
"green": [0, 128, 0], |
| 1575 |
"greenyellow": [173, 255, 47], |
| 1576 |
"grey": [128, 128, 128], |
| 1577 |
"honeydew": [240, 255, 240], |
| 1578 |
"hotpink": [255, 105, 180], |
| 1579 |
"indianred": [205, 92, 92], |
| 1580 |
"indigo": [75, 0, 130], |
| 1581 |
"ivory": [255, 255, 240], |
| 1582 |
"khaki": [240, 230, 140], |
| 1583 |
"lavender": [230, 230, 250], |
| 1584 |
"lavenderblush": [255, 240, 245], |
| 1585 |
"lawngreen": [124, 252, 0], |
| 1586 |
"lemonchiffon": [255, 250, 205], |
| 1587 |
"lightblue": [173, 216, 230], |
| 1588 |
"lightcoral": [240, 128, 128], |
| 1589 |
"lightcyan": [224, 255, 255], |
| 1590 |
"lightgoldenrodyellow": [250, 250, 210], |
| 1591 |
"lightgray": [211, 211, 211], |
| 1592 |
"lightgreen": [144, 238, 144], |
| 1593 |
"lightgrey": [211, 211, 211], |
| 1594 |
"lightpink": [255, 182, 193], |
| 1595 |
"lightsalmon": [255, 160, 122], |
| 1596 |
"lightseagreen": [32, 178, 170], |
| 1597 |
"lightskyblue": [135, 206, 250], |
| 1598 |
"lightslategray": [119, 136, 153], |
| 1599 |
"lightslategrey": [119, 136, 153], |
| 1600 |
"lightsteelblue": [176, 196, 222], |
| 1601 |
"lightyellow": [255, 255, 224], |
| 1602 |
"lime": [0, 255, 0], |
| 1603 |
"limegreen": [50, 205, 50], |
| 1604 |
"linen": [250, 240, 230], |
| 1605 |
"magenta": [255, 0, 255], |
| 1606 |
"maroon": [128, 0, 0], |
| 1607 |
"mediumaquamarine": [102, 205, 170], |
| 1608 |
"mediumblue": [0, 0, 205], |
| 1609 |
"mediumorchid": [186, 85, 211], |
| 1610 |
"mediumpurple": [147, 112, 219], |
| 1611 |
"mediumseagreen": [60, 179, 113], |
| 1612 |
"mediumslateblue": [123, 104, 238], |
| 1613 |
"mediumspringgreen": [0, 250, 154], |
| 1614 |
"mediumturquoise": [72, 209, 204], |
| 1615 |
"mediumvioletred": [199, 21, 133], |
| 1616 |
"midnightblue": [25, 25, 112], |
| 1617 |
"mintcream": [245, 255, 250], |
| 1618 |
"mistyrose": [255, 228, 225], |
| 1619 |
"moccasin": [255, 228, 181], |
| 1620 |
"navajowhite": [255, 222, 173], |
| 1621 |
"navy": [0, 0, 128], |
| 1622 |
"oldlace": [253, 245, 230], |
| 1623 |
"olive": [128, 128, 0], |
| 1624 |
"olivedrab": [107, 142, 35], |
| 1625 |
"orange": [255, 165, 0], |
| 1626 |
"orangered": [255, 69, 0], |
| 1627 |
"orchid": [218, 112, 214], |
| 1628 |
"palegoldenrod": [238, 232, 170], |
| 1629 |
"palegreen": [152, 251, 152], |
| 1630 |
"paleturquoise": [175, 238, 238], |
| 1631 |
"palevioletred": [219, 112, 147], |
| 1632 |
"papayawhip": [255, 239, 213], |
| 1633 |
"peachpuff": [255, 218, 185], |
| 1634 |
"peru": [205, 133, 63], |
| 1635 |
"pink": [255, 192, 203], |
| 1636 |
"plum": [221, 160, 221], |
| 1637 |
"powderblue": [176, 224, 230], |
| 1638 |
"purple": [128, 0, 128], |
| 1639 |
"rebeccapurple": [102, 51, 153], |
| 1640 |
"red": [255, 0, 0], |
| 1641 |
"rosybrown": [188, 143, 143], |
| 1642 |
"royalblue": [65, 105, 225], |
| 1643 |
"saddlebrown": [139, 69, 19], |
| 1644 |
"salmon": [250, 128, 114], |
| 1645 |
"sandybrown": [244, 164, 96], |
| 1646 |
"seagreen": [46, 139, 87], |
| 1647 |
"seashell": [255, 245, 238], |
| 1648 |
"sienna": [160, 82, 45], |
| 1649 |
"silver": [192, 192, 192], |
| 1650 |
"skyblue": [135, 206, 235], |
| 1651 |
"slateblue": [106, 90, 205], |
| 1652 |
"slategray": [112, 128, 144], |
| 1653 |
"slategrey": [112, 128, 144], |
| 1654 |
"snow": [255, 250, 250], |
| 1655 |
"springgreen": [0, 255, 127], |
| 1656 |
"steelblue": [70, 130, 180], |
| 1657 |
"tan": [210, 180, 140], |
| 1658 |
"teal": [0, 128, 128], |
| 1659 |
"thistle": [216, 191, 216], |
| 1660 |
"tomato": [255, 99, 71], |
| 1661 |
"turquoise": [64, 224, 208], |
| 1662 |
"violet": [238, 130, 238], |
| 1663 |
"wheat": [245, 222, 179], |
| 1664 |
"white": [255, 255, 255], |
| 1665 |
"whitesmoke": [245, 245, 245], |
| 1666 |
"yellow": [255, 255, 0], |
| 1667 |
"yellowgreen": [154, 205, 50] |
| 1668 |
}; |
| 1669 |
|
| 1670 |
},{}],7:[function(require,module,exports){ |
| 1671 |
/** |
| 1672 |
* @namespace Chart |
| 1673 |
*/ |
| 1674 |
var Chart = require(29)(); |
| 1675 |
|
| 1676 |
Chart.helpers = require(45); |
| 1677 |
|
| 1678 |
// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! |
| 1679 |
require(27)(Chart); |
| 1680 |
|
| 1681 |
Chart.defaults = require(25); |
| 1682 |
Chart.Element = require(26); |
| 1683 |
Chart.elements = require(40); |
| 1684 |
Chart.Interaction = require(28); |
| 1685 |
Chart.platform = require(48); |
| 1686 |
|
| 1687 |
require(31)(Chart); |
| 1688 |
require(22)(Chart); |
| 1689 |
require(23)(Chart); |
| 1690 |
require(24)(Chart); |
| 1691 |
require(30)(Chart); |
| 1692 |
require(33)(Chart); |
| 1693 |
require(32)(Chart); |
| 1694 |
require(35)(Chart); |
| 1695 |
|
| 1696 |
require(54)(Chart); |
| 1697 |
require(52)(Chart); |
| 1698 |
require(53)(Chart); |
| 1699 |
require(55)(Chart); |
| 1700 |
require(56)(Chart); |
| 1701 |
require(57)(Chart); |
| 1702 |
|
| 1703 |
// Controllers must be loaded after elements |
| 1704 |
// See Chart.core.datasetController.dataElementType |
| 1705 |
require(15)(Chart); |
| 1706 |
require(16)(Chart); |
| 1707 |
require(17)(Chart); |
| 1708 |
require(18)(Chart); |
| 1709 |
require(19)(Chart); |
| 1710 |
require(20)(Chart); |
| 1711 |
require(21)(Chart); |
| 1712 |
|
| 1713 |
require(8)(Chart); |
| 1714 |
require(9)(Chart); |
| 1715 |
require(10)(Chart); |
| 1716 |
require(11)(Chart); |
| 1717 |
require(12)(Chart); |
| 1718 |
require(13)(Chart); |
| 1719 |
require(14)(Chart); |
| 1720 |
|
| 1721 |
// Loading built-it plugins |
| 1722 |
var plugins = []; |
| 1723 |
|
| 1724 |
plugins.push( |
| 1725 |
require(49)(Chart), |
| 1726 |
require(50)(Chart), |
| 1727 |
require(51)(Chart) |
| 1728 |
); |
| 1729 |
|
| 1730 |
Chart.plugins.register(plugins); |
| 1731 |
|
| 1732 |
Chart.platform.initialize(); |
| 1733 |
|
| 1734 |
module.exports = Chart; |
| 1735 |
if (typeof window !== 'undefined') { |
| 1736 |
window.imagify.Chart = Chart; |
| 1737 |
} |
| 1738 |
|
| 1739 |
// DEPRECATIONS |
| 1740 |
|
| 1741 |
/** |
| 1742 |
* Provided for backward compatibility, use Chart.helpers.canvas instead. |
| 1743 |
* @namespace Chart.canvasHelpers |
| 1744 |
* @deprecated since version 2.6.0 |
| 1745 |
* @todo remove at version 3 |
| 1746 |
* @private |
| 1747 |
*/ |
| 1748 |
Chart.canvasHelpers = Chart.helpers.canvas; |
| 1749 |
|
| 1750 |
},{"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,"35":35,"40":40,"45":45,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"8":8,"9":9}],8:[function(require,module,exports){ |
| 1751 |
'use strict'; |
| 1752 |
|
| 1753 |
module.exports = function(Chart) { |
| 1754 |
|
| 1755 |
Chart.Bar = function(context, config) { |
| 1756 |
config.type = 'bar'; |
| 1757 |
|
| 1758 |
return new Chart(context, config); |
| 1759 |
}; |
| 1760 |
|
| 1761 |
}; |
| 1762 |
|
| 1763 |
},{}],9:[function(require,module,exports){ |
| 1764 |
'use strict'; |
| 1765 |
|
| 1766 |
module.exports = function(Chart) { |
| 1767 |
|
| 1768 |
Chart.Bubble = function(context, config) { |
| 1769 |
config.type = 'bubble'; |
| 1770 |
return new Chart(context, config); |
| 1771 |
}; |
| 1772 |
|
| 1773 |
}; |
| 1774 |
|
| 1775 |
},{}],10:[function(require,module,exports){ |
| 1776 |
'use strict'; |
| 1777 |
|
| 1778 |
module.exports = function(Chart) { |
| 1779 |
|
| 1780 |
Chart.Doughnut = function(context, config) { |
| 1781 |
config.type = 'doughnut'; |
| 1782 |
|
| 1783 |
return new Chart(context, config); |
| 1784 |
}; |
| 1785 |
|
| 1786 |
}; |
| 1787 |
|
| 1788 |
},{}],11:[function(require,module,exports){ |
| 1789 |
'use strict'; |
| 1790 |
|
| 1791 |
module.exports = function(Chart) { |
| 1792 |
|
| 1793 |
Chart.Line = function(context, config) { |
| 1794 |
config.type = 'line'; |
| 1795 |
|
| 1796 |
return new Chart(context, config); |
| 1797 |
}; |
| 1798 |
|
| 1799 |
}; |
| 1800 |
|
| 1801 |
},{}],12:[function(require,module,exports){ |
| 1802 |
'use strict'; |
| 1803 |
|
| 1804 |
module.exports = function(Chart) { |
| 1805 |
|
| 1806 |
Chart.PolarArea = function(context, config) { |
| 1807 |
config.type = 'polarArea'; |
| 1808 |
|
| 1809 |
return new Chart(context, config); |
| 1810 |
}; |
| 1811 |
|
| 1812 |
}; |
| 1813 |
|
| 1814 |
},{}],13:[function(require,module,exports){ |
| 1815 |
'use strict'; |
| 1816 |
|
| 1817 |
module.exports = function(Chart) { |
| 1818 |
|
| 1819 |
Chart.Radar = function(context, config) { |
| 1820 |
config.type = 'radar'; |
| 1821 |
|
| 1822 |
return new Chart(context, config); |
| 1823 |
}; |
| 1824 |
|
| 1825 |
}; |
| 1826 |
|
| 1827 |
},{}],14:[function(require,module,exports){ |
| 1828 |
'use strict'; |
| 1829 |
|
| 1830 |
module.exports = function(Chart) { |
| 1831 |
Chart.Scatter = function(context, config) { |
| 1832 |
config.type = 'scatter'; |
| 1833 |
return new Chart(context, config); |
| 1834 |
}; |
| 1835 |
}; |
| 1836 |
|
| 1837 |
},{}],15:[function(require,module,exports){ |
| 1838 |
'use strict'; |
| 1839 |
|
| 1840 |
var defaults = require(25); |
| 1841 |
var elements = require(40); |
| 1842 |
var helpers = require(45); |
| 1843 |
|
| 1844 |
defaults._set('bar', { |
| 1845 |
hover: { |
| 1846 |
mode: 'label' |
| 1847 |
}, |
| 1848 |
|
| 1849 |
scales: { |
| 1850 |
xAxes: [{ |
| 1851 |
type: 'category', |
| 1852 |
|
| 1853 |
// Specific to Bar Controller |
| 1854 |
categoryPercentage: 0.8, |
| 1855 |
barPercentage: 0.9, |
| 1856 |
|
| 1857 |
// offset settings |
| 1858 |
offset: true, |
| 1859 |
|
| 1860 |
// grid line settings |
| 1861 |
gridLines: { |
| 1862 |
offsetGridLines: true |
| 1863 |
} |
| 1864 |
}], |
| 1865 |
|
| 1866 |
yAxes: [{ |
| 1867 |
type: 'linear' |
| 1868 |
}] |
| 1869 |
} |
| 1870 |
}); |
| 1871 |
|
| 1872 |
defaults._set('horizontalBar', { |
| 1873 |
hover: { |
| 1874 |
mode: 'index', |
| 1875 |
axis: 'y' |
| 1876 |
}, |
| 1877 |
|
| 1878 |
scales: { |
| 1879 |
xAxes: [{ |
| 1880 |
type: 'linear', |
| 1881 |
position: 'bottom' |
| 1882 |
}], |
| 1883 |
|
| 1884 |
yAxes: [{ |
| 1885 |
position: 'left', |
| 1886 |
type: 'category', |
| 1887 |
|
| 1888 |
// Specific to Horizontal Bar Controller |
| 1889 |
categoryPercentage: 0.8, |
| 1890 |
barPercentage: 0.9, |
| 1891 |
|
| 1892 |
// offset settings |
| 1893 |
offset: true, |
| 1894 |
|
| 1895 |
// grid line settings |
| 1896 |
gridLines: { |
| 1897 |
offsetGridLines: true |
| 1898 |
} |
| 1899 |
}] |
| 1900 |
}, |
| 1901 |
|
| 1902 |
elements: { |
| 1903 |
rectangle: { |
| 1904 |
borderSkipped: 'left' |
| 1905 |
} |
| 1906 |
}, |
| 1907 |
|
| 1908 |
tooltips: { |
| 1909 |
callbacks: { |
| 1910 |
title: function(item, data) { |
| 1911 |
// Pick first xLabel for now |
| 1912 |
var title = ''; |
| 1913 |
|
| 1914 |
if (item.length > 0) { |
| 1915 |
if (item[0].yLabel) { |
| 1916 |
title = item[0].yLabel; |
| 1917 |
} else if (data.labels.length > 0 && item[0].index < data.labels.length) { |
| 1918 |
title = data.labels[item[0].index]; |
| 1919 |
} |
| 1920 |
} |
| 1921 |
|
| 1922 |
return title; |
| 1923 |
}, |
| 1924 |
|
| 1925 |
label: function(item, data) { |
| 1926 |
var datasetLabel = data.datasets[item.datasetIndex].label || ''; |
| 1927 |
return datasetLabel + ': ' + item.xLabel; |
| 1928 |
} |
| 1929 |
}, |
| 1930 |
mode: 'index', |
| 1931 |
axis: 'y' |
| 1932 |
} |
| 1933 |
}); |
| 1934 |
|
| 1935 |
module.exports = function(Chart) { |
| 1936 |
|
| 1937 |
Chart.controllers.bar = Chart.DatasetController.extend({ |
| 1938 |
|
| 1939 |
dataElementType: elements.Rectangle, |
| 1940 |
|
| 1941 |
initialize: function() { |
| 1942 |
var me = this; |
| 1943 |
var meta; |
| 1944 |
|
| 1945 |
Chart.DatasetController.prototype.initialize.apply(me, arguments); |
| 1946 |
|
| 1947 |
meta = me.getMeta(); |
| 1948 |
meta.stack = me.getDataset().stack; |
| 1949 |
meta.bar = true; |
| 1950 |
}, |
| 1951 |
|
| 1952 |
update: function(reset) { |
| 1953 |
var me = this; |
| 1954 |
var rects = me.getMeta().data; |
| 1955 |
var i, ilen; |
| 1956 |
|
| 1957 |
me._ruler = me.getRuler(); |
| 1958 |
|
| 1959 |
for (i = 0, ilen = rects.length; i < ilen; ++i) { |
| 1960 |
me.updateElement(rects[i], i, reset); |
| 1961 |
} |
| 1962 |
}, |
| 1963 |
|
| 1964 |
updateElement: function(rectangle, index, reset) { |
| 1965 |
var me = this; |
| 1966 |
var chart = me.chart; |
| 1967 |
var meta = me.getMeta(); |
| 1968 |
var dataset = me.getDataset(); |
| 1969 |
var custom = rectangle.custom || {}; |
| 1970 |
var rectangleOptions = chart.options.elements.rectangle; |
| 1971 |
|
| 1972 |
rectangle._xScale = me.getScaleForId(meta.xAxisID); |
| 1973 |
rectangle._yScale = me.getScaleForId(meta.yAxisID); |
| 1974 |
rectangle._datasetIndex = me.index; |
| 1975 |
rectangle._index = index; |
| 1976 |
|
| 1977 |
rectangle._model = { |
| 1978 |
datasetLabel: dataset.label, |
| 1979 |
label: chart.data.labels[index], |
| 1980 |
borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped, |
| 1981 |
backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor), |
| 1982 |
borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor), |
| 1983 |
borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth) |
| 1984 |
}; |
| 1985 |
|
| 1986 |
me.updateElementGeometry(rectangle, index, reset); |
| 1987 |
|
| 1988 |
rectangle.pivot(); |
| 1989 |
}, |
| 1990 |
|
| 1991 |
/** |
| 1992 |
* @private |
| 1993 |
*/ |
| 1994 |
updateElementGeometry: function(rectangle, index, reset) { |
| 1995 |
var me = this; |
| 1996 |
var model = rectangle._model; |
| 1997 |
var vscale = me.getValueScale(); |
| 1998 |
var base = vscale.getBasePixel(); |
| 1999 |
var horizontal = vscale.isHorizontal(); |
| 2000 |
var ruler = me._ruler || me.getRuler(); |
| 2001 |
var vpixels = me.calculateBarValuePixels(me.index, index); |
| 2002 |
var ipixels = me.calculateBarIndexPixels(me.index, index, ruler); |
| 2003 |
|
| 2004 |
model.horizontal = horizontal; |
| 2005 |
model.base = reset ? base : vpixels.base; |
| 2006 |
model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; |
| 2007 |
model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; |
| 2008 |
model.height = horizontal ? ipixels.size : undefined; |
| 2009 |
model.width = horizontal ? undefined : ipixels.size; |
| 2010 |
}, |
| 2011 |
|
| 2012 |
/** |
| 2013 |
* @private |
| 2014 |
*/ |
| 2015 |
getValueScaleId: function() { |
| 2016 |
return this.getMeta().yAxisID; |
| 2017 |
}, |
| 2018 |
|
| 2019 |
/** |
| 2020 |
* @private |
| 2021 |
*/ |
| 2022 |
getIndexScaleId: function() { |
| 2023 |
return this.getMeta().xAxisID; |
| 2024 |
}, |
| 2025 |
|
| 2026 |
/** |
| 2027 |
* @private |
| 2028 |
*/ |
| 2029 |
getValueScale: function() { |
| 2030 |
return this.getScaleForId(this.getValueScaleId()); |
| 2031 |
}, |
| 2032 |
|
| 2033 |
/** |
| 2034 |
* @private |
| 2035 |
*/ |
| 2036 |
getIndexScale: function() { |
| 2037 |
return this.getScaleForId(this.getIndexScaleId()); |
| 2038 |
}, |
| 2039 |
|
| 2040 |
/** |
| 2041 |
* Returns the effective number of stacks based on groups and bar visibility. |
| 2042 |
* @private |
| 2043 |
*/ |
| 2044 |
getStackCount: function(last) { |
| 2045 |
var me = this; |
| 2046 |
var chart = me.chart; |
| 2047 |
var scale = me.getIndexScale(); |
| 2048 |
var stacked = scale.options.stacked; |
| 2049 |
var ilen = last === undefined ? chart.data.datasets.length : last + 1; |
| 2050 |
var stacks = []; |
| 2051 |
var i, meta; |
| 2052 |
|
| 2053 |
for (i = 0; i < ilen; ++i) { |
| 2054 |
meta = chart.getDatasetMeta(i); |
| 2055 |
if (meta.bar && chart.isDatasetVisible(i) && |
| 2056 |
(stacked === false || |
| 2057 |
(stacked === true && stacks.indexOf(meta.stack) === -1) || |
| 2058 |
(stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { |
| 2059 |
stacks.push(meta.stack); |
| 2060 |
} |
| 2061 |
} |
| 2062 |
|
| 2063 |
return stacks.length; |
| 2064 |
}, |
| 2065 |
|
| 2066 |
/** |
| 2067 |
* Returns the stack index for the given dataset based on groups and bar visibility. |
| 2068 |
* @private |
| 2069 |
*/ |
| 2070 |
getStackIndex: function(datasetIndex) { |
| 2071 |
return this.getStackCount(datasetIndex) - 1; |
| 2072 |
}, |
| 2073 |
|
| 2074 |
/** |
| 2075 |
* @private |
| 2076 |
*/ |
| 2077 |
getRuler: function() { |
| 2078 |
var me = this; |
| 2079 |
var scale = me.getIndexScale(); |
| 2080 |
var stackCount = me.getStackCount(); |
| 2081 |
var datasetIndex = me.index; |
| 2082 |
var pixels = []; |
| 2083 |
var isHorizontal = scale.isHorizontal(); |
| 2084 |
var start = isHorizontal ? scale.left : scale.top; |
| 2085 |
var end = start + (isHorizontal ? scale.width : scale.height); |
| 2086 |
var i, ilen; |
| 2087 |
|
| 2088 |
for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { |
| 2089 |
pixels.push(scale.getPixelForValue(null, i, datasetIndex)); |
| 2090 |
} |
| 2091 |
|
| 2092 |
return { |
| 2093 |
pixels: pixels, |
| 2094 |
start: start, |
| 2095 |
end: end, |
| 2096 |
stackCount: stackCount, |
| 2097 |
scale: scale |
| 2098 |
}; |
| 2099 |
}, |
| 2100 |
|
| 2101 |
/** |
| 2102 |
* Note: pixel values are not clamped to the scale area. |
| 2103 |
* @private |
| 2104 |
*/ |
| 2105 |
calculateBarValuePixels: function(datasetIndex, index) { |
| 2106 |
var me = this; |
| 2107 |
var chart = me.chart; |
| 2108 |
var meta = me.getMeta(); |
| 2109 |
var scale = me.getValueScale(); |
| 2110 |
var datasets = chart.data.datasets; |
| 2111 |
var value = scale.getRightValue(datasets[datasetIndex].data[index]); |
| 2112 |
var stacked = scale.options.stacked; |
| 2113 |
var stack = meta.stack; |
| 2114 |
var start = 0; |
| 2115 |
var i, imeta, ivalue, base, head, size; |
| 2116 |
|
| 2117 |
if (stacked || (stacked === undefined && stack !== undefined)) { |
| 2118 |
for (i = 0; i < datasetIndex; ++i) { |
| 2119 |
imeta = chart.getDatasetMeta(i); |
| 2120 |
|
| 2121 |
if (imeta.bar && |
| 2122 |
imeta.stack === stack && |
| 2123 |
imeta.controller.getValueScaleId() === scale.id && |
| 2124 |
chart.isDatasetVisible(i)) { |
| 2125 |
|
| 2126 |
ivalue = scale.getRightValue(datasets[i].data[index]); |
| 2127 |
if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { |
| 2128 |
start += ivalue; |
| 2129 |
} |
| 2130 |
} |
| 2131 |
} |
| 2132 |
} |
| 2133 |
|
| 2134 |
base = scale.getPixelForValue(start); |
| 2135 |
head = scale.getPixelForValue(start + value); |
| 2136 |
size = (head - base) / 2; |
| 2137 |
|
| 2138 |
return { |
| 2139 |
size: size, |
| 2140 |
base: base, |
| 2141 |
head: head, |
| 2142 |
center: head + size / 2 |
| 2143 |
}; |
| 2144 |
}, |
| 2145 |
|
| 2146 |
/** |
| 2147 |
* @private |
| 2148 |
*/ |
| 2149 |
calculateBarIndexPixels: function(datasetIndex, index, ruler) { |
| 2150 |
var me = this; |
| 2151 |
var options = ruler.scale.options; |
| 2152 |
var stackIndex = me.getStackIndex(datasetIndex); |
| 2153 |
var pixels = ruler.pixels; |
| 2154 |
var base = pixels[index]; |
| 2155 |
var length = pixels.length; |
| 2156 |
var start = ruler.start; |
| 2157 |
var end = ruler.end; |
| 2158 |
var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size; |
| 2159 |
|
| 2160 |
if (length === 1) { |
| 2161 |
leftSampleSize = base > start ? base - start : end - base; |
| 2162 |
rightSampleSize = base < end ? end - base : base - start; |
| 2163 |
} else { |
| 2164 |
if (index > 0) { |
| 2165 |
leftSampleSize = (base - pixels[index - 1]) / 2; |
| 2166 |
if (index === length - 1) { |
| 2167 |
rightSampleSize = leftSampleSize; |
| 2168 |
} |
| 2169 |
} |
| 2170 |
if (index < length - 1) { |
| 2171 |
rightSampleSize = (pixels[index + 1] - base) / 2; |
| 2172 |
if (index === 0) { |
| 2173 |
leftSampleSize = rightSampleSize; |
| 2174 |
} |
| 2175 |
} |
| 2176 |
} |
| 2177 |
|
| 2178 |
leftCategorySize = leftSampleSize * options.categoryPercentage; |
| 2179 |
rightCategorySize = rightSampleSize * options.categoryPercentage; |
| 2180 |
fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount; |
| 2181 |
size = fullBarSize * options.barPercentage; |
| 2182 |
|
| 2183 |
size = Math.min( |
| 2184 |
helpers.valueOrDefault(options.barThickness, size), |
| 2185 |
helpers.valueOrDefault(options.maxBarThickness, Infinity)); |
| 2186 |
|
| 2187 |
base -= leftCategorySize; |
| 2188 |
base += fullBarSize * stackIndex; |
| 2189 |
base += (fullBarSize - size) / 2; |
| 2190 |
|
| 2191 |
return { |
| 2192 |
size: size, |
| 2193 |
base: base, |
| 2194 |
head: base + size, |
| 2195 |
center: base + size / 2 |
| 2196 |
}; |
| 2197 |
}, |
| 2198 |
|
| 2199 |
draw: function() { |
| 2200 |
var me = this; |
| 2201 |
var chart = me.chart; |
| 2202 |
var scale = me.getValueScale(); |
| 2203 |
var rects = me.getMeta().data; |
| 2204 |
var dataset = me.getDataset(); |
| 2205 |
var ilen = rects.length; |
| 2206 |
var i = 0; |
| 2207 |
|
| 2208 |
helpers.canvas.clipArea(chart.ctx, chart.chartArea); |
| 2209 |
|
| 2210 |
for (; i < ilen; ++i) { |
| 2211 |
if (!isNaN(scale.getRightValue(dataset.data[i]))) { |
| 2212 |
rects[i].draw(); |
| 2213 |
} |
| 2214 |
} |
| 2215 |
|
| 2216 |
helpers.canvas.unclipArea(chart.ctx); |
| 2217 |
}, |
| 2218 |
|
| 2219 |
setHoverStyle: function(rectangle) { |
| 2220 |
var dataset = this.chart.data.datasets[rectangle._datasetIndex]; |
| 2221 |
var index = rectangle._index; |
| 2222 |
var custom = rectangle.custom || {}; |
| 2223 |
var model = rectangle._model; |
| 2224 |
|
| 2225 |
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); |
| 2226 |
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor)); |
| 2227 |
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); |
| 2228 |
}, |
| 2229 |
|
| 2230 |
removeHoverStyle: function(rectangle) { |
| 2231 |
var dataset = this.chart.data.datasets[rectangle._datasetIndex]; |
| 2232 |
var index = rectangle._index; |
| 2233 |
var custom = rectangle.custom || {}; |
| 2234 |
var model = rectangle._model; |
| 2235 |
var rectangleElementOptions = this.chart.options.elements.rectangle; |
| 2236 |
|
| 2237 |
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor); |
| 2238 |
model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor); |
| 2239 |
model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth); |
| 2240 |
} |
| 2241 |
}); |
| 2242 |
|
| 2243 |
Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ |
| 2244 |
/** |
| 2245 |
* @private |
| 2246 |
*/ |
| 2247 |
getValueScaleId: function() { |
| 2248 |
return this.getMeta().xAxisID; |
| 2249 |
}, |
| 2250 |
|
| 2251 |
/** |
| 2252 |
* @private |
| 2253 |
*/ |
| 2254 |
getIndexScaleId: function() { |
| 2255 |
return this.getMeta().yAxisID; |
| 2256 |
} |
| 2257 |
}); |
| 2258 |
}; |
| 2259 |
|
| 2260 |
},{"25":25,"40":40,"45":45}],16:[function(require,module,exports){ |
| 2261 |
'use strict'; |
| 2262 |
|
| 2263 |
var defaults = require(25); |
| 2264 |
var elements = require(40); |
| 2265 |
var helpers = require(45); |
| 2266 |
|
| 2267 |
defaults._set('bubble', { |
| 2268 |
hover: { |
| 2269 |
mode: 'single' |
| 2270 |
}, |
| 2271 |
|
| 2272 |
scales: { |
| 2273 |
xAxes: [{ |
| 2274 |
type: 'linear', // bubble should probably use a linear scale by default |
| 2275 |
position: 'bottom', |
| 2276 |
id: 'x-axis-0' // need an ID so datasets can reference the scale |
| 2277 |
}], |
| 2278 |
yAxes: [{ |
| 2279 |
type: 'linear', |
| 2280 |
position: 'left', |
| 2281 |
id: 'y-axis-0' |
| 2282 |
}] |
| 2283 |
}, |
| 2284 |
|
| 2285 |
tooltips: { |
| 2286 |
callbacks: { |
| 2287 |
title: function() { |
| 2288 |
// Title doesn't make sense for scatter since we format the data as a point |
| 2289 |
return ''; |
| 2290 |
}, |
| 2291 |
label: function(item, data) { |
| 2292 |
var datasetLabel = data.datasets[item.datasetIndex].label || ''; |
| 2293 |
var dataPoint = data.datasets[item.datasetIndex].data[item.index]; |
| 2294 |
return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; |
| 2295 |
} |
| 2296 |
} |
| 2297 |
} |
| 2298 |
}); |
| 2299 |
|
| 2300 |
|
| 2301 |
module.exports = function(Chart) { |
| 2302 |
|
| 2303 |
Chart.controllers.bubble = Chart.DatasetController.extend({ |
| 2304 |
/** |
| 2305 |
* @protected |
| 2306 |
*/ |
| 2307 |
dataElementType: elements.Point, |
| 2308 |
|
| 2309 |
/** |
| 2310 |
* @protected |
| 2311 |
*/ |
| 2312 |
update: function(reset) { |
| 2313 |
var me = this; |
| 2314 |
var meta = me.getMeta(); |
| 2315 |
var points = meta.data; |
| 2316 |
|
| 2317 |
// Update Points |
| 2318 |
helpers.each(points, function(point, index) { |
| 2319 |
me.updateElement(point, index, reset); |
| 2320 |
}); |
| 2321 |
}, |
| 2322 |
|
| 2323 |
/** |
| 2324 |
* @protected |
| 2325 |
*/ |
| 2326 |
updateElement: function(point, index, reset) { |
| 2327 |
var me = this; |
| 2328 |
var meta = me.getMeta(); |
| 2329 |
var custom = point.custom || {}; |
| 2330 |
var xScale = me.getScaleForId(meta.xAxisID); |
| 2331 |
var yScale = me.getScaleForId(meta.yAxisID); |
| 2332 |
var options = me._resolveElementOptions(point, index); |
| 2333 |
var data = me.getDataset().data[index]; |
| 2334 |
var dsIndex = me.index; |
| 2335 |
|
| 2336 |
var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); |
| 2337 |
var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); |
| 2338 |
|
| 2339 |
point._xScale = xScale; |
| 2340 |
point._yScale = yScale; |
| 2341 |
point._options = options; |
| 2342 |
point._datasetIndex = dsIndex; |
| 2343 |
point._index = index; |
| 2344 |
point._model = { |
| 2345 |
backgroundColor: options.backgroundColor, |
| 2346 |
borderColor: options.borderColor, |
| 2347 |
borderWidth: options.borderWidth, |
| 2348 |
hitRadius: options.hitRadius, |
| 2349 |
pointStyle: options.pointStyle, |
| 2350 |
radius: reset ? 0 : options.radius, |
| 2351 |
skip: custom.skip || isNaN(x) || isNaN(y), |
| 2352 |
x: x, |
| 2353 |
y: y, |
| 2354 |
}; |
| 2355 |
|
| 2356 |
point.pivot(); |
| 2357 |
}, |
| 2358 |
|
| 2359 |
/** |
| 2360 |
* @protected |
| 2361 |
*/ |
| 2362 |
setHoverStyle: function(point) { |
| 2363 |
var model = point._model; |
| 2364 |
var options = point._options; |
| 2365 |
|
| 2366 |
model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor)); |
| 2367 |
model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor)); |
| 2368 |
model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth); |
| 2369 |
model.radius = options.radius + options.hoverRadius; |
| 2370 |
}, |
| 2371 |
|
| 2372 |
/** |
| 2373 |
* @protected |
| 2374 |
*/ |
| 2375 |
removeHoverStyle: function(point) { |
| 2376 |
var model = point._model; |
| 2377 |
var options = point._options; |
| 2378 |
|
| 2379 |
model.backgroundColor = options.backgroundColor; |
| 2380 |
model.borderColor = options.borderColor; |
| 2381 |
model.borderWidth = options.borderWidth; |
| 2382 |
model.radius = options.radius; |
| 2383 |
}, |
| 2384 |
|
| 2385 |
/** |
| 2386 |
* @private |
| 2387 |
*/ |
| 2388 |
_resolveElementOptions: function(point, index) { |
| 2389 |
var me = this; |
| 2390 |
var chart = me.chart; |
| 2391 |
var datasets = chart.data.datasets; |
| 2392 |
var dataset = datasets[me.index]; |
| 2393 |
var custom = point.custom || {}; |
| 2394 |
var options = chart.options.elements.point; |
| 2395 |
var resolve = helpers.options.resolve; |
| 2396 |
var data = dataset.data[index]; |
| 2397 |
var values = {}; |
| 2398 |
var i, ilen, key; |
| 2399 |
|
| 2400 |
// Scriptable options |
| 2401 |
var context = { |
| 2402 |
chart: chart, |
| 2403 |
dataIndex: index, |
| 2404 |
dataset: dataset, |
| 2405 |
datasetIndex: me.index |
| 2406 |
}; |
| 2407 |
|
| 2408 |
var keys = [ |
| 2409 |
'backgroundColor', |
| 2410 |
'borderColor', |
| 2411 |
'borderWidth', |
| 2412 |
'hoverBackgroundColor', |
| 2413 |
'hoverBorderColor', |
| 2414 |
'hoverBorderWidth', |
| 2415 |
'hoverRadius', |
| 2416 |
'hitRadius', |
| 2417 |
'pointStyle' |
| 2418 |
]; |
| 2419 |
|
| 2420 |
for (i = 0, ilen = keys.length; i < ilen; ++i) { |
| 2421 |
key = keys[i]; |
| 2422 |
values[key] = resolve([ |
| 2423 |
custom[key], |
| 2424 |
dataset[key], |
| 2425 |
options[key] |
| 2426 |
], context, index); |
| 2427 |
} |
| 2428 |
|
| 2429 |
// Custom radius resolution |
| 2430 |
values.radius = resolve([ |
| 2431 |
custom.radius, |
| 2432 |
data ? data.r : undefined, |
| 2433 |
dataset.radius, |
| 2434 |
options.radius |
| 2435 |
], context, index); |
| 2436 |
|
| 2437 |
return values; |
| 2438 |
} |
| 2439 |
}); |
| 2440 |
}; |
| 2441 |
|
| 2442 |
},{"25":25,"40":40,"45":45}],17:[function(require,module,exports){ |
| 2443 |
'use strict'; |
| 2444 |
|
| 2445 |
var defaults = require(25); |
| 2446 |
var elements = require(40); |
| 2447 |
var helpers = require(45); |
| 2448 |
|
| 2449 |
defaults._set('doughnut', { |
| 2450 |
animation: { |
| 2451 |
// Boolean - Whether we animate the rotation of the Doughnut |
| 2452 |
animateRotate: true, |
| 2453 |
// Boolean - Whether we animate scaling the Doughnut from the centre |
| 2454 |
animateScale: false |
| 2455 |
}, |
| 2456 |
hover: { |
| 2457 |
mode: 'single' |
| 2458 |
}, |
| 2459 |
legendCallback: function(chart) { |
| 2460 |
var text = []; |
| 2461 |
text.push('<ul class="' + chart.id + '-legend">'); |
| 2462 |
|
| 2463 |
var data = chart.data; |
| 2464 |
var datasets = data.datasets; |
| 2465 |
var labels = data.labels; |
| 2466 |
|
| 2467 |
if (datasets.length) { |
| 2468 |
for (var i = 0; i < datasets[0].data.length; ++i) { |
| 2469 |
text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); |
| 2470 |
if (labels[i]) { |
| 2471 |
text.push(labels[i]); |
| 2472 |
} |
| 2473 |
text.push('</li>'); |
| 2474 |
} |
| 2475 |
} |
| 2476 |
|
| 2477 |
text.push('</ul>'); |
| 2478 |
return text.join(''); |
| 2479 |
}, |
| 2480 |
legend: { |
| 2481 |
labels: { |
| 2482 |
generateLabels: function(chart) { |
| 2483 |
var data = chart.data; |
| 2484 |
if (data.labels.length && data.datasets.length) { |
| 2485 |
return data.labels.map(function(label, i) { |
| 2486 |
var meta = chart.getDatasetMeta(0); |
| 2487 |
var ds = data.datasets[0]; |
| 2488 |
var arc = meta.data[i]; |
| 2489 |
var custom = arc && arc.custom || {}; |
| 2490 |
var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; |
| 2491 |
var arcOpts = chart.options.elements.arc; |
| 2492 |
var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); |
| 2493 |
var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); |
| 2494 |
var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); |
| 2495 |
|
| 2496 |
return { |
| 2497 |
text: label, |
| 2498 |
fillStyle: fill, |
| 2499 |
strokeStyle: stroke, |
| 2500 |
lineWidth: bw, |
| 2501 |
hidden: isNaN(ds.data[i]) || meta.data[i].hidden, |
| 2502 |
|
| 2503 |
// Extra data used for toggling the correct item |
| 2504 |
index: i |
| 2505 |
}; |
| 2506 |
}); |
| 2507 |
} |
| 2508 |
return []; |
| 2509 |
} |
| 2510 |
}, |
| 2511 |
|
| 2512 |
onClick: function(e, legendItem) { |
| 2513 |
var index = legendItem.index; |
| 2514 |
var chart = this.chart; |
| 2515 |
var i, ilen, meta; |
| 2516 |
|
| 2517 |
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { |
| 2518 |
meta = chart.getDatasetMeta(i); |
| 2519 |
// toggle visibility of index if exists |
| 2520 |
if (meta.data[index]) { |
| 2521 |
meta.data[index].hidden = !meta.data[index].hidden; |
| 2522 |
} |
| 2523 |
} |
| 2524 |
|
| 2525 |
chart.update(); |
| 2526 |
} |
| 2527 |
}, |
| 2528 |
|
| 2529 |
// The percentage of the chart that we cut out of the middle. |
| 2530 |
cutoutPercentage: 50, |
| 2531 |
|
| 2532 |
// The rotation of the chart, where the first data arc begins. |
| 2533 |
rotation: Math.PI * -0.5, |
| 2534 |
|
| 2535 |
// The total circumference of the chart. |
| 2536 |
circumference: Math.PI * 2.0, |
| 2537 |
|
| 2538 |
// Need to override these to give a nice default |
| 2539 |
tooltips: { |
| 2540 |
callbacks: { |
| 2541 |
title: function() { |
| 2542 |
return ''; |
| 2543 |
}, |
| 2544 |
label: function(tooltipItem, data) { |
| 2545 |
var dataLabel = data.labels[tooltipItem.index]; |
| 2546 |
var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; |
| 2547 |
|
| 2548 |
if (helpers.isArray(dataLabel)) { |
| 2549 |
// show value on first line of multiline label |
| 2550 |
// need to clone because we are changing the value |
| 2551 |
dataLabel = dataLabel.slice(); |
| 2552 |
dataLabel[0] += value; |
| 2553 |
} else { |
| 2554 |
dataLabel += value; |
| 2555 |
} |
| 2556 |
|
| 2557 |
return dataLabel; |
| 2558 |
} |
| 2559 |
} |
| 2560 |
} |
| 2561 |
}); |
| 2562 |
|
| 2563 |
defaults._set('pie', helpers.clone(defaults.doughnut)); |
| 2564 |
defaults._set('pie', { |
| 2565 |
cutoutPercentage: 0 |
| 2566 |
}); |
| 2567 |
|
| 2568 |
module.exports = function(Chart) { |
| 2569 |
|
| 2570 |
Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ |
| 2571 |
|
| 2572 |
dataElementType: elements.Arc, |
| 2573 |
|
| 2574 |
linkScales: helpers.noop, |
| 2575 |
|
| 2576 |
// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly |
| 2577 |
getRingIndex: function(datasetIndex) { |
| 2578 |
var ringIndex = 0; |
| 2579 |
|
| 2580 |
for (var j = 0; j < datasetIndex; ++j) { |
| 2581 |
if (this.chart.isDatasetVisible(j)) { |
| 2582 |
++ringIndex; |
| 2583 |
} |
| 2584 |
} |
| 2585 |
|
| 2586 |
return ringIndex; |
| 2587 |
}, |
| 2588 |
|
| 2589 |
update: function(reset) { |
| 2590 |
var me = this; |
| 2591 |
var chart = me.chart; |
| 2592 |
var chartArea = chart.chartArea; |
| 2593 |
var opts = chart.options; |
| 2594 |
var arcOpts = opts.elements.arc; |
| 2595 |
var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth; |
| 2596 |
var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth; |
| 2597 |
var minSize = Math.min(availableWidth, availableHeight); |
| 2598 |
var offset = {x: 0, y: 0}; |
| 2599 |
var meta = me.getMeta(); |
| 2600 |
var cutoutPercentage = opts.cutoutPercentage; |
| 2601 |
var circumference = opts.circumference; |
| 2602 |
|
| 2603 |
// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc |
| 2604 |
if (circumference < Math.PI * 2.0) { |
| 2605 |
var startAngle = opts.rotation % (Math.PI * 2.0); |
| 2606 |
startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); |
| 2607 |
var endAngle = startAngle + circumference; |
| 2608 |
var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; |
| 2609 |
var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; |
| 2610 |
var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); |
| 2611 |
var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); |
| 2612 |
var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); |
| 2613 |
var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); |
| 2614 |
var cutout = cutoutPercentage / 100.0; |
| 2615 |
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))}; |
| 2616 |
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))}; |
| 2617 |
var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; |
| 2618 |
minSize = Math.min(availableWidth / size.width, availableHeight / size.height); |
| 2619 |
offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; |
| 2620 |
} |
| 2621 |
|
| 2622 |
chart.borderWidth = me.getMaxBorderWidth(meta.data); |
| 2623 |
chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); |
| 2624 |
chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); |
| 2625 |
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); |
| 2626 |
chart.offsetX = offset.x * chart.outerRadius; |
| 2627 |
chart.offsetY = offset.y * chart.outerRadius; |
| 2628 |
|
| 2629 |
meta.total = me.calculateTotal(); |
| 2630 |
|
| 2631 |
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); |
| 2632 |
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); |
| 2633 |
|
| 2634 |
helpers.each(meta.data, function(arc, index) { |
| 2635 |
me.updateElement(arc, index, reset); |
| 2636 |
}); |
| 2637 |
}, |
| 2638 |
|
| 2639 |
updateElement: function(arc, index, reset) { |
| 2640 |
var me = this; |
| 2641 |
var chart = me.chart; |
| 2642 |
var chartArea = chart.chartArea; |
| 2643 |
var opts = chart.options; |
| 2644 |
var animationOpts = opts.animation; |
| 2645 |
var centerX = (chartArea.left + chartArea.right) / 2; |
| 2646 |
var centerY = (chartArea.top + chartArea.bottom) / 2; |
| 2647 |
var startAngle = opts.rotation; // non reset case handled later |
| 2648 |
var endAngle = opts.rotation; // non reset case handled later |
| 2649 |
var dataset = me.getDataset(); |
| 2650 |
var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)); |
| 2651 |
var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; |
| 2652 |
var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; |
| 2653 |
var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; |
| 2654 |
|
| 2655 |
helpers.extend(arc, { |
| 2656 |
// Utility |
| 2657 |
_datasetIndex: me.index, |
| 2658 |
_index: index, |
| 2659 |
|
| 2660 |
// Desired view properties |
| 2661 |
_model: { |
| 2662 |
x: centerX + chart.offsetX, |
| 2663 |
y: centerY + chart.offsetY, |
| 2664 |
startAngle: startAngle, |
| 2665 |
endAngle: endAngle, |
| 2666 |
circumference: circumference, |
| 2667 |
outerRadius: outerRadius, |
| 2668 |
innerRadius: innerRadius, |
| 2669 |
label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) |
| 2670 |
} |
| 2671 |
}); |
| 2672 |
|
| 2673 |
var model = arc._model; |
| 2674 |
// Resets the visual styles |
| 2675 |
this.removeHoverStyle(arc); |
| 2676 |
|
| 2677 |
// Set correct angles if not resetting |
| 2678 |
if (!reset || !animationOpts.animateRotate) { |
| 2679 |
if (index === 0) { |
| 2680 |
model.startAngle = opts.rotation; |
| 2681 |
} else { |
| 2682 |
model.startAngle = me.getMeta().data[index - 1]._model.endAngle; |
| 2683 |
} |
| 2684 |
|
| 2685 |
model.endAngle = model.startAngle + model.circumference; |
| 2686 |
} |
| 2687 |
|
| 2688 |
arc.pivot(); |
| 2689 |
}, |
| 2690 |
|
| 2691 |
removeHoverStyle: function(arc) { |
| 2692 |
Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); |
| 2693 |
}, |
| 2694 |
|
| 2695 |
calculateTotal: function() { |
| 2696 |
var dataset = this.getDataset(); |
| 2697 |
var meta = this.getMeta(); |
| 2698 |
var total = 0; |
| 2699 |
var value; |
| 2700 |
|
| 2701 |
helpers.each(meta.data, function(element, index) { |
| 2702 |
value = dataset.data[index]; |
| 2703 |
if (!isNaN(value) && !element.hidden) { |
| 2704 |
total += Math.abs(value); |
| 2705 |
} |
| 2706 |
}); |
| 2707 |
|
| 2708 |
/* if (total === 0) { |
| 2709 |
total = NaN; |
| 2710 |
}*/ |
| 2711 |
|
| 2712 |
return total; |
| 2713 |
}, |
| 2714 |
|
| 2715 |
calculateCircumference: function(value) { |
| 2716 |
var total = this.getMeta().total; |
| 2717 |
if (total > 0 && !isNaN(value)) { |
| 2718 |
return (Math.PI * 2.0) * (value / total); |
| 2719 |
} |
| 2720 |
return 0; |
| 2721 |
}, |
| 2722 |
|
| 2723 |
// gets the max border or hover width to properly scale pie charts |
| 2724 |
getMaxBorderWidth: function(arcs) { |
| 2725 |
var max = 0; |
| 2726 |
var index = this.index; |
| 2727 |
var length = arcs.length; |
| 2728 |
var borderWidth; |
| 2729 |
var hoverWidth; |
| 2730 |
|
| 2731 |
for (var i = 0; i < length; i++) { |
| 2732 |
borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0; |
| 2733 |
hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; |
| 2734 |
|
| 2735 |
max = borderWidth > max ? borderWidth : max; |
| 2736 |
max = hoverWidth > max ? hoverWidth : max; |
| 2737 |
} |
| 2738 |
return max; |
| 2739 |
} |
| 2740 |
}); |
| 2741 |
}; |
| 2742 |
|
| 2743 |
},{"25":25,"40":40,"45":45}],18:[function(require,module,exports){ |
| 2744 |
'use strict'; |
| 2745 |
|
| 2746 |
var defaults = require(25); |
| 2747 |
var elements = require(40); |
| 2748 |
var helpers = require(45); |
| 2749 |
|
| 2750 |
defaults._set('line', { |
| 2751 |
showLines: true, |
| 2752 |
spanGaps: false, |
| 2753 |
|
| 2754 |
hover: { |
| 2755 |
mode: 'label' |
| 2756 |
}, |
| 2757 |
|
| 2758 |
scales: { |
| 2759 |
xAxes: [{ |
| 2760 |
type: 'category', |
| 2761 |
id: 'x-axis-0' |
| 2762 |
}], |
| 2763 |
yAxes: [{ |
| 2764 |
type: 'linear', |
| 2765 |
id: 'y-axis-0' |
| 2766 |
}] |
| 2767 |
} |
| 2768 |
}); |
| 2769 |
|
| 2770 |
module.exports = function(Chart) { |
| 2771 |
|
| 2772 |
function lineEnabled(dataset, options) { |
| 2773 |
return helpers.valueOrDefault(dataset.showLine, options.showLines); |
| 2774 |
} |
| 2775 |
|
| 2776 |
Chart.controllers.line = Chart.DatasetController.extend({ |
| 2777 |
|
| 2778 |
datasetElementType: elements.Line, |
| 2779 |
|
| 2780 |
dataElementType: elements.Point, |
| 2781 |
|
| 2782 |
update: function(reset) { |
| 2783 |
var me = this; |
| 2784 |
var meta = me.getMeta(); |
| 2785 |
var line = meta.dataset; |
| 2786 |
var points = meta.data || []; |
| 2787 |
var options = me.chart.options; |
| 2788 |
var lineElementOptions = options.elements.line; |
| 2789 |
var scale = me.getScaleForId(meta.yAxisID); |
| 2790 |
var i, ilen, custom; |
| 2791 |
var dataset = me.getDataset(); |
| 2792 |
var showLine = lineEnabled(dataset, options); |
| 2793 |
|
| 2794 |
// Update Line |
| 2795 |
if (showLine) { |
| 2796 |
custom = line.custom || {}; |
| 2797 |
|
| 2798 |
// Compatibility: If the properties are defined with only the old name, use those values |
| 2799 |
if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { |
| 2800 |
dataset.lineTension = dataset.tension; |
| 2801 |
} |
| 2802 |
|
| 2803 |
// Utility |
| 2804 |
line._scale = scale; |
| 2805 |
line._datasetIndex = me.index; |
| 2806 |
// Data |
| 2807 |
line._children = points; |
| 2808 |
// Model |
| 2809 |
line._model = { |
| 2810 |
// Appearance |
| 2811 |
// The default behavior of lines is to break at null values, according |
| 2812 |
// to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 |
| 2813 |
// This option gives lines the ability to span gaps |
| 2814 |
spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, |
| 2815 |
tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), |
| 2816 |
backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), |
| 2817 |
borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), |
| 2818 |
borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), |
| 2819 |
borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), |
| 2820 |
borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), |
| 2821 |
borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), |
| 2822 |
borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), |
| 2823 |
fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), |
| 2824 |
steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped), |
| 2825 |
cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode), |
| 2826 |
}; |
| 2827 |
|
| 2828 |
line.pivot(); |
| 2829 |
} |
| 2830 |
|
| 2831 |
// Update Points |
| 2832 |
for (i = 0, ilen = points.length; i < ilen; ++i) { |
| 2833 |
me.updateElement(points[i], i, reset); |
| 2834 |
} |
| 2835 |
|
| 2836 |
if (showLine && line._model.tension !== 0) { |
| 2837 |
me.updateBezierControlPoints(); |
| 2838 |
} |
| 2839 |
|
| 2840 |
// Now pivot the point for animation |
| 2841 |
for (i = 0, ilen = points.length; i < ilen; ++i) { |
| 2842 |
points[i].pivot(); |
| 2843 |
} |
| 2844 |
}, |
| 2845 |
|
| 2846 |
getPointBackgroundColor: function(point, index) { |
| 2847 |
var backgroundColor = this.chart.options.elements.point.backgroundColor; |
| 2848 |
var dataset = this.getDataset(); |
| 2849 |
var custom = point.custom || {}; |
| 2850 |
|
| 2851 |
if (custom.backgroundColor) { |
| 2852 |
backgroundColor = custom.backgroundColor; |
| 2853 |
} else if (dataset.pointBackgroundColor) { |
| 2854 |
backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); |
| 2855 |
} else if (dataset.backgroundColor) { |
| 2856 |
backgroundColor = dataset.backgroundColor; |
| 2857 |
} |
| 2858 |
|
| 2859 |
return backgroundColor; |
| 2860 |
}, |
| 2861 |
|
| 2862 |
getPointBorderColor: function(point, index) { |
| 2863 |
var borderColor = this.chart.options.elements.point.borderColor; |
| 2864 |
var dataset = this.getDataset(); |
| 2865 |
var custom = point.custom || {}; |
| 2866 |
|
| 2867 |
if (custom.borderColor) { |
| 2868 |
borderColor = custom.borderColor; |
| 2869 |
} else if (dataset.pointBorderColor) { |
| 2870 |
borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); |
| 2871 |
} else if (dataset.borderColor) { |
| 2872 |
borderColor = dataset.borderColor; |
| 2873 |
} |
| 2874 |
|
| 2875 |
return borderColor; |
| 2876 |
}, |
| 2877 |
|
| 2878 |
getPointBorderWidth: function(point, index) { |
| 2879 |
var borderWidth = this.chart.options.elements.point.borderWidth; |
| 2880 |
var dataset = this.getDataset(); |
| 2881 |
var custom = point.custom || {}; |
| 2882 |
|
| 2883 |
if (!isNaN(custom.borderWidth)) { |
| 2884 |
borderWidth = custom.borderWidth; |
| 2885 |
} else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) { |
| 2886 |
borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); |
| 2887 |
} else if (!isNaN(dataset.borderWidth)) { |
| 2888 |
borderWidth = dataset.borderWidth; |
| 2889 |
} |
| 2890 |
|
| 2891 |
return borderWidth; |
| 2892 |
}, |
| 2893 |
|
| 2894 |
updateElement: function(point, index, reset) { |
| 2895 |
var me = this; |
| 2896 |
var meta = me.getMeta(); |
| 2897 |
var custom = point.custom || {}; |
| 2898 |
var dataset = me.getDataset(); |
| 2899 |
var datasetIndex = me.index; |
| 2900 |
var value = dataset.data[index]; |
| 2901 |
var yScale = me.getScaleForId(meta.yAxisID); |
| 2902 |
var xScale = me.getScaleForId(meta.xAxisID); |
| 2903 |
var pointOptions = me.chart.options.elements.point; |
| 2904 |
var x, y; |
| 2905 |
|
| 2906 |
// Compatibility: If the properties are defined with only the old name, use those values |
| 2907 |
if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { |
| 2908 |
dataset.pointRadius = dataset.radius; |
| 2909 |
} |
| 2910 |
if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { |
| 2911 |
dataset.pointHitRadius = dataset.hitRadius; |
| 2912 |
} |
| 2913 |
|
| 2914 |
x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); |
| 2915 |
y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); |
| 2916 |
|
| 2917 |
// Utility |
| 2918 |
point._xScale = xScale; |
| 2919 |
point._yScale = yScale; |
| 2920 |
point._datasetIndex = datasetIndex; |
| 2921 |
point._index = index; |
| 2922 |
|
| 2923 |
// Desired view properties |
| 2924 |
point._model = { |
| 2925 |
x: x, |
| 2926 |
y: y, |
| 2927 |
skip: custom.skip || isNaN(x) || isNaN(y), |
| 2928 |
// Appearance |
| 2929 |
radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), |
| 2930 |
pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), |
| 2931 |
backgroundColor: me.getPointBackgroundColor(point, index), |
| 2932 |
borderColor: me.getPointBorderColor(point, index), |
| 2933 |
borderWidth: me.getPointBorderWidth(point, index), |
| 2934 |
tension: meta.dataset._model ? meta.dataset._model.tension : 0, |
| 2935 |
steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, |
| 2936 |
// Tooltip |
| 2937 |
hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) |
| 2938 |
}; |
| 2939 |
}, |
| 2940 |
|
| 2941 |
calculatePointY: function(value, index, datasetIndex) { |
| 2942 |
var me = this; |
| 2943 |
var chart = me.chart; |
| 2944 |
var meta = me.getMeta(); |
| 2945 |
var yScale = me.getScaleForId(meta.yAxisID); |
| 2946 |
var sumPos = 0; |
| 2947 |
var sumNeg = 0; |
| 2948 |
var i, ds, dsMeta; |
| 2949 |
|
| 2950 |
if (yScale.options.stacked) { |
| 2951 |
for (i = 0; i < datasetIndex; i++) { |
| 2952 |
ds = chart.data.datasets[i]; |
| 2953 |
dsMeta = chart.getDatasetMeta(i); |
| 2954 |
if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { |
| 2955 |
var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); |
| 2956 |
if (stackedRightValue < 0) { |
| 2957 |
sumNeg += stackedRightValue || 0; |
| 2958 |
} else { |
| 2959 |
sumPos += stackedRightValue || 0; |
| 2960 |
} |
| 2961 |
} |
| 2962 |
} |
| 2963 |
|
| 2964 |
var rightValue = Number(yScale.getRightValue(value)); |
| 2965 |
if (rightValue < 0) { |
| 2966 |
return yScale.getPixelForValue(sumNeg + rightValue); |
| 2967 |
} |
| 2968 |
return yScale.getPixelForValue(sumPos + rightValue); |
| 2969 |
} |
| 2970 |
|
| 2971 |
return yScale.getPixelForValue(value); |
| 2972 |
}, |
| 2973 |
|
| 2974 |
updateBezierControlPoints: function() { |
| 2975 |
var me = this; |
| 2976 |
var meta = me.getMeta(); |
| 2977 |
var area = me.chart.chartArea; |
| 2978 |
var points = (meta.data || []); |
| 2979 |
var i, ilen, point, model, controlPoints; |
| 2980 |
|
| 2981 |
// Only consider points that are drawn in case the spanGaps option is used |
| 2982 |
if (meta.dataset._model.spanGaps) { |
| 2983 |
points = points.filter(function(pt) { |
| 2984 |
return !pt._model.skip; |
| 2985 |
}); |
| 2986 |
} |
| 2987 |
|
| 2988 |
function capControlPoint(pt, min, max) { |
| 2989 |
return Math.max(Math.min(pt, max), min); |
| 2990 |
} |
| 2991 |
|
| 2992 |
if (meta.dataset._model.cubicInterpolationMode === 'monotone') { |
| 2993 |
helpers.splineCurveMonotone(points); |
| 2994 |
} else { |
| 2995 |
for (i = 0, ilen = points.length; i < ilen; ++i) { |
| 2996 |
point = points[i]; |
| 2997 |
model = point._model; |
| 2998 |
controlPoints = helpers.splineCurve( |
| 2999 |
helpers.previousItem(points, i)._model, |
| 3000 |
model, |
| 3001 |
helpers.nextItem(points, i)._model, |
| 3002 |
meta.dataset._model.tension |
| 3003 |
); |
| 3004 |
model.controlPointPreviousX = controlPoints.previous.x; |
| 3005 |
model.controlPointPreviousY = controlPoints.previous.y; |
| 3006 |
model.controlPointNextX = controlPoints.next.x; |
| 3007 |
model.controlPointNextY = controlPoints.next.y; |
| 3008 |
} |
| 3009 |
} |
| 3010 |
|
| 3011 |
if (me.chart.options.elements.line.capBezierPoints) { |
| 3012 |
for (i = 0, ilen = points.length; i < ilen; ++i) { |
| 3013 |
model = points[i]._model; |
| 3014 |
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); |
| 3015 |
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); |
| 3016 |
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); |
| 3017 |
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); |
| 3018 |
} |
| 3019 |
} |
| 3020 |
}, |
| 3021 |
|
| 3022 |
draw: function() { |
| 3023 |
var me = this; |
| 3024 |
var chart = me.chart; |
| 3025 |
var meta = me.getMeta(); |
| 3026 |
var points = meta.data || []; |
| 3027 |
var area = chart.chartArea; |
| 3028 |
var ilen = points.length; |
| 3029 |
var i = 0; |
| 3030 |
|
| 3031 |
helpers.canvas.clipArea(chart.ctx, area); |
| 3032 |
|
| 3033 |
if (lineEnabled(me.getDataset(), chart.options)) { |
| 3034 |
meta.dataset.draw(); |
| 3035 |
} |
| 3036 |
|
| 3037 |
helpers.canvas.unclipArea(chart.ctx); |
| 3038 |
|
| 3039 |
// Draw the points |
| 3040 |
for (; i < ilen; ++i) { |
| 3041 |
points[i].draw(area); |
| 3042 |
} |
| 3043 |
}, |
| 3044 |
|
| 3045 |
setHoverStyle: function(point) { |
| 3046 |
// Point |
| 3047 |
var dataset = this.chart.data.datasets[point._datasetIndex]; |
| 3048 |
var index = point._index; |
| 3049 |
var custom = point.custom || {}; |
| 3050 |
var model = point._model; |
| 3051 |
|
| 3052 |
model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); |
| 3053 |
model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); |
| 3054 |
model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); |
| 3055 |
model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); |
| 3056 |
}, |
| 3057 |
|
| 3058 |
removeHoverStyle: function(point) { |
| 3059 |
var me = this; |
| 3060 |
var dataset = me.chart.data.datasets[point._datasetIndex]; |
| 3061 |
var index = point._index; |
| 3062 |
var custom = point.custom || {}; |
| 3063 |
var model = point._model; |
| 3064 |
|
| 3065 |
// Compatibility: If the properties are defined with only the old name, use those values |
| 3066 |
if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { |
| 3067 |
dataset.pointRadius = dataset.radius; |
| 3068 |
} |
| 3069 |
|
| 3070 |
model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius); |
| 3071 |
model.backgroundColor = me.getPointBackgroundColor(point, index); |
| 3072 |
model.borderColor = me.getPointBorderColor(point, index); |
| 3073 |
model.borderWidth = me.getPointBorderWidth(point, index); |
| 3074 |
} |
| 3075 |
}); |
| 3076 |
}; |
| 3077 |
|
| 3078 |
},{"25":25,"40":40,"45":45}],19:[function(require,module,exports){ |
| 3079 |
'use strict'; |
| 3080 |
|
| 3081 |
var defaults = require(25); |
| 3082 |
var elements = require(40); |
| 3083 |
var helpers = require(45); |
| 3084 |
|
| 3085 |
defaults._set('polarArea', { |
| 3086 |
scale: { |
| 3087 |
type: 'radialLinear', |
| 3088 |
angleLines: { |
| 3089 |
display: false |
| 3090 |
}, |
| 3091 |
gridLines: { |
| 3092 |
circular: true |
| 3093 |
}, |
| 3094 |
pointLabels: { |
| 3095 |
display: false |
| 3096 |
}, |
| 3097 |
ticks: { |
| 3098 |
beginAtZero: true |
| 3099 |
} |
| 3100 |
}, |
| 3101 |
|
| 3102 |
// Boolean - Whether to animate the rotation of the chart |
| 3103 |
animation: { |
| 3104 |
animateRotate: true, |
| 3105 |
animateScale: true |
| 3106 |
}, |
| 3107 |
|
| 3108 |
startAngle: -0.5 * Math.PI, |
| 3109 |
legendCallback: function(chart) { |
| 3110 |
var text = []; |
| 3111 |
text.push('<ul class="' + chart.id + '-legend">'); |
| 3112 |
|
| 3113 |
var data = chart.data; |
| 3114 |
var datasets = data.datasets; |
| 3115 |
var labels = data.labels; |
| 3116 |
|
| 3117 |
if (datasets.length) { |
| 3118 |
for (var i = 0; i < datasets[0].data.length; ++i) { |
| 3119 |
text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); |
| 3120 |
if (labels[i]) { |
| 3121 |
text.push(labels[i]); |
| 3122 |
} |
| 3123 |
text.push('</li>'); |
| 3124 |
} |
| 3125 |
} |
| 3126 |
|
| 3127 |
text.push('</ul>'); |
| 3128 |
return text.join(''); |
| 3129 |
}, |
| 3130 |
legend: { |
| 3131 |
labels: { |
| 3132 |
generateLabels: function(chart) { |
| 3133 |
var data = chart.data; |
| 3134 |
if (data.labels.length && data.datasets.length) { |
| 3135 |
return data.labels.map(function(label, i) { |
| 3136 |
var meta = chart.getDatasetMeta(0); |
| 3137 |
var ds = data.datasets[0]; |
| 3138 |
var arc = meta.data[i]; |
| 3139 |
var custom = arc.custom || {}; |
| 3140 |
var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; |
| 3141 |
var arcOpts = chart.options.elements.arc; |
| 3142 |
var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); |
| 3143 |
var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); |
| 3144 |
var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); |
| 3145 |
|
| 3146 |
return { |
| 3147 |
text: label, |
| 3148 |
fillStyle: fill, |
| 3149 |
strokeStyle: stroke, |
| 3150 |
lineWidth: bw, |
| 3151 |
hidden: isNaN(ds.data[i]) || meta.data[i].hidden, |
| 3152 |
|
| 3153 |
// Extra data used for toggling the correct item |
| 3154 |
index: i |
| 3155 |
}; |
| 3156 |
}); |
| 3157 |
} |
| 3158 |
return []; |
| 3159 |
} |
| 3160 |
}, |
| 3161 |
|
| 3162 |
onClick: function(e, legendItem) { |
| 3163 |
var index = legendItem.index; |
| 3164 |
var chart = this.chart; |
| 3165 |
var i, ilen, meta; |
| 3166 |
|
| 3167 |
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { |
| 3168 |
meta = chart.getDatasetMeta(i); |
| 3169 |
meta.data[index].hidden = !meta.data[index].hidden; |
| 3170 |
} |
| 3171 |
|
| 3172 |
chart.update(); |
| 3173 |
} |
| 3174 |
}, |
| 3175 |
|
| 3176 |
// Need to override these to give a nice default |
| 3177 |
tooltips: { |
| 3178 |
callbacks: { |
| 3179 |
title: function() { |
| 3180 |
return ''; |
| 3181 |
}, |
| 3182 |
label: function(item, data) { |
| 3183 |
return data.labels[item.index] + ': ' + item.yLabel; |
| 3184 |
} |
| 3185 |
} |
| 3186 |
} |
| 3187 |
}); |
| 3188 |
|
| 3189 |
module.exports = function(Chart) { |
| 3190 |
|
| 3191 |
Chart.controllers.polarArea = Chart.DatasetController.extend({ |
| 3192 |
|
| 3193 |
dataElementType: elements.Arc, |
| 3194 |
|
| 3195 |
linkScales: helpers.noop, |
| 3196 |
|
| 3197 |
update: function(reset) { |
| 3198 |
var me = this; |
| 3199 |
var chart = me.chart; |
| 3200 |
var chartArea = chart.chartArea; |
| 3201 |
var meta = me.getMeta(); |
| 3202 |
var opts = chart.options; |
| 3203 |
var arcOpts = opts.elements.arc; |
| 3204 |
var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); |
| 3205 |
chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); |
| 3206 |
chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); |
| 3207 |
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); |
| 3208 |
|
| 3209 |
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); |
| 3210 |
me.innerRadius = me.outerRadius - chart.radiusLength; |
| 3211 |
|
| 3212 |
meta.count = me.countVisibleElements(); |
| 3213 |
|
| 3214 |
helpers.each(meta.data, function(arc, index) { |
| 3215 |
me.updateElement(arc, index, reset); |
| 3216 |
}); |
| 3217 |
}, |
| 3218 |
|
| 3219 |
updateElement: function(arc, index, reset) { |
| 3220 |
var me = this; |
| 3221 |
var chart = me.chart; |
| 3222 |
var dataset = me.getDataset(); |
| 3223 |
var opts = chart.options; |
| 3224 |
var animationOpts = opts.animation; |
| 3225 |
var scale = chart.scale; |
| 3226 |
var labels = chart.data.labels; |
| 3227 |
|
| 3228 |
var circumference = me.calculateCircumference(dataset.data[index]); |
| 3229 |
var centerX = scale.xCenter; |
| 3230 |
var centerY = scale.yCenter; |
| 3231 |
|
| 3232 |
// If there is NaN data before us, we need to calculate the starting angle correctly. |
| 3233 |
// We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data |
| 3234 |
var visibleCount = 0; |
| 3235 |
var meta = me.getMeta(); |
| 3236 |
for (var i = 0; i < index; ++i) { |
| 3237 |
if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) { |
| 3238 |
++visibleCount; |
| 3239 |
} |
| 3240 |
} |
| 3241 |
|
| 3242 |
// var negHalfPI = -0.5 * Math.PI; |
| 3243 |
var datasetStartAngle = opts.startAngle; |
| 3244 |
var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); |
| 3245 |
var startAngle = datasetStartAngle + (circumference * visibleCount); |
| 3246 |
var endAngle = startAngle + (arc.hidden ? 0 : circumference); |
| 3247 |
|
| 3248 |
var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); |
| 3249 |
|
| 3250 |
helpers.extend(arc, { |
| 3251 |
// Utility |
| 3252 |
_datasetIndex: me.index, |
| 3253 |
_index: index, |
| 3254 |
_scale: scale, |
| 3255 |
|
| 3256 |
// Desired view properties |
| 3257 |
_model: { |
| 3258 |
x: centerX, |
| 3259 |
y: centerY, |
| 3260 |
innerRadius: 0, |
| 3261 |
outerRadius: reset ? resetRadius : distance, |
| 3262 |
startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, |
| 3263 |
endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, |
| 3264 |
label: helpers.valueAtIndexOrDefault(labels, index, labels[index]) |
| 3265 |
} |
| 3266 |
}); |
| 3267 |
|
| 3268 |
// Apply border and fill style |
| 3269 |
me.removeHoverStyle(arc); |
| 3270 |
|
| 3271 |
arc.pivot(); |
| 3272 |
}, |
| 3273 |
|
| 3274 |
removeHoverStyle: function(arc) { |
| 3275 |
Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); |
| 3276 |
}, |
| 3277 |
|
| 3278 |
countVisibleElements: function() { |
| 3279 |
var dataset = this.getDataset(); |
| 3280 |
var meta = this.getMeta(); |
| 3281 |
var count = 0; |
| 3282 |
|
| 3283 |
helpers.each(meta.data, function(element, index) { |
| 3284 |
if (!isNaN(dataset.data[index]) && !element.hidden) { |
| 3285 |
count++; |
| 3286 |
} |
| 3287 |
}); |
| 3288 |
|
| 3289 |
return count; |
| 3290 |
}, |
| 3291 |
|
| 3292 |
calculateCircumference: function(value) { |
| 3293 |
var count = this.getMeta().count; |
| 3294 |
if (count > 0 && !isNaN(value)) { |
| 3295 |
return (2 * Math.PI) / count; |
| 3296 |
} |
| 3297 |
return 0; |
| 3298 |
} |
| 3299 |
}); |
| 3300 |
}; |
| 3301 |
|
| 3302 |
},{"25":25,"40":40,"45":45}],20:[function(require,module,exports){ |
| 3303 |
'use strict'; |
| 3304 |
|
| 3305 |
var defaults = require(25); |
| 3306 |
var elements = require(40); |
| 3307 |
var helpers = require(45); |
| 3308 |
|
| 3309 |
defaults._set('radar', { |
| 3310 |
scale: { |
| 3311 |
type: 'radialLinear' |
| 3312 |
}, |
| 3313 |
elements: { |
| 3314 |
line: { |
| 3315 |
tension: 0 // no bezier in radar |
| 3316 |
} |
| 3317 |
} |
| 3318 |
}); |
| 3319 |
|
| 3320 |
module.exports = function(Chart) { |
| 3321 |
|
| 3322 |
Chart.controllers.radar = Chart.DatasetController.extend({ |
| 3323 |
|
| 3324 |
datasetElementType: elements.Line, |
| 3325 |
|
| 3326 |
dataElementType: elements.Point, |
| 3327 |
|
| 3328 |
linkScales: helpers.noop, |
| 3329 |
|
| 3330 |
update: function(reset) { |
| 3331 |
var me = this; |
| 3332 |
var meta = me.getMeta(); |
| 3333 |
var line = meta.dataset; |
| 3334 |
var points = meta.data; |
| 3335 |
var custom = line.custom || {}; |
| 3336 |
var dataset = me.getDataset(); |
| 3337 |
var lineElementOptions = me.chart.options.elements.line; |
| 3338 |
var scale = me.chart.scale; |
| 3339 |
|
| 3340 |
// Compatibility: If the properties are defined with only the old name, use those values |
| 3341 |
if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { |
| 3342 |
dataset.lineTension = dataset.tension; |
| 3343 |
} |
| 3344 |
|
| 3345 |
helpers.extend(meta.dataset, { |
| 3346 |
// Utility |
| 3347 |
_datasetIndex: me.index, |
| 3348 |
_scale: scale, |
| 3349 |
// Data |
| 3350 |
_children: points, |
| 3351 |
_loop: true, |
| 3352 |
// Model |
| 3353 |
_model: { |
| 3354 |
// Appearance |
| 3355 |
tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), |
| 3356 |
backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), |
| 3357 |
borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), |
| 3358 |
borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), |
| 3359 |
fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), |
| 3360 |
borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), |
| 3361 |
borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), |
| 3362 |
borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), |
| 3363 |
borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), |
| 3364 |
} |
| 3365 |
}); |
| 3366 |
|
| 3367 |
meta.dataset.pivot(); |
| 3368 |
|
| 3369 |
// Update Points |
| 3370 |
helpers.each(points, function(point, index) { |
| 3371 |
me.updateElement(point, index, reset); |
| 3372 |
}, me); |
| 3373 |
|
| 3374 |
// Update bezier control points |
| 3375 |
me.updateBezierControlPoints(); |
| 3376 |
}, |
| 3377 |
updateElement: function(point, index, reset) { |
| 3378 |
var me = this; |
| 3379 |
var custom = point.custom || {}; |
| 3380 |
var dataset = me.getDataset(); |
| 3381 |
var scale = me.chart.scale; |
| 3382 |
var pointElementOptions = me.chart.options.elements.point; |
| 3383 |
var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); |
| 3384 |
|
| 3385 |
// Compatibility: If the properties are defined with only the old name, use those values |
| 3386 |
if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { |
| 3387 |
dataset.pointRadius = dataset.radius; |
| 3388 |
} |
| 3389 |
if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { |
| 3390 |
dataset.pointHitRadius = dataset.hitRadius; |
| 3391 |
} |
| 3392 |
|
| 3393 |
helpers.extend(point, { |
| 3394 |
// Utility |
| 3395 |
_datasetIndex: me.index, |
| 3396 |
_index: index, |
| 3397 |
_scale: scale, |
| 3398 |
|
| 3399 |
// Desired view properties |
| 3400 |
_model: { |
| 3401 |
x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales |
| 3402 |
y: reset ? scale.yCenter : pointPosition.y, |
| 3403 |
|
| 3404 |
// Appearance |
| 3405 |
tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), |
| 3406 |
radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), |
| 3407 |
backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), |
| 3408 |
borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), |
| 3409 |
borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), |
| 3410 |
pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), |
| 3411 |
|
| 3412 |
// Tooltip |
| 3413 |
hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) |
| 3414 |
} |
| 3415 |
}); |
| 3416 |
|
| 3417 |
point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); |
| 3418 |
}, |
| 3419 |
updateBezierControlPoints: function() { |
| 3420 |
var chartArea = this.chart.chartArea; |
| 3421 |
var meta = this.getMeta(); |
| 3422 |
|
| 3423 |
helpers.each(meta.data, function(point, index) { |
| 3424 |
var model = point._model; |
| 3425 |
var controlPoints = helpers.splineCurve( |
| 3426 |
helpers.previousItem(meta.data, index, true)._model, |
| 3427 |
model, |
| 3428 |
helpers.nextItem(meta.data, index, true)._model, |
| 3429 |
model.tension |
| 3430 |
); |
| 3431 |
|
| 3432 |
// Prevent the bezier going outside of the bounds of the graph |
| 3433 |
model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); |
| 3434 |
model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); |
| 3435 |
|
| 3436 |
model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); |
| 3437 |
model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); |
| 3438 |
|
| 3439 |
// Now pivot the point for animation |
| 3440 |
point.pivot(); |
| 3441 |
}); |
| 3442 |
}, |
| 3443 |
|
| 3444 |
setHoverStyle: function(point) { |
| 3445 |
// Point |
| 3446 |
var dataset = this.chart.data.datasets[point._datasetIndex]; |
| 3447 |
var custom = point.custom || {}; |
| 3448 |
var index = point._index; |
| 3449 |
var model = point._model; |
| 3450 |
|
| 3451 |
model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); |
| 3452 |
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); |
| 3453 |
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); |
| 3454 |
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); |
| 3455 |
}, |
| 3456 |
|
| 3457 |
removeHoverStyle: function(point) { |
| 3458 |
var dataset = this.chart.data.datasets[point._datasetIndex]; |
| 3459 |
var custom = point.custom || {}; |
| 3460 |
var index = point._index; |
| 3461 |
var model = point._model; |
| 3462 |
var pointElementOptions = this.chart.options.elements.point; |
| 3463 |
|
| 3464 |
model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius); |
| 3465 |
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor); |
| 3466 |
model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor); |
| 3467 |
model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth); |
| 3468 |
} |
| 3469 |
}); |
| 3470 |
}; |
| 3471 |
|
| 3472 |
},{"25":25,"40":40,"45":45}],21:[function(require,module,exports){ |
| 3473 |
'use strict'; |
| 3474 |
|
| 3475 |
var defaults = require(25); |
| 3476 |
|
| 3477 |
defaults._set('scatter', { |
| 3478 |
hover: { |
| 3479 |
mode: 'single' |
| 3480 |
}, |
| 3481 |
|
| 3482 |
scales: { |
| 3483 |
xAxes: [{ |
| 3484 |
id: 'x-axis-1', // need an ID so datasets can reference the scale |
| 3485 |
type: 'linear', // scatter should not use a category axis |
| 3486 |
position: 'bottom' |
| 3487 |
}], |
| 3488 |
yAxes: [{ |
| 3489 |
id: 'y-axis-1', |
| 3490 |
type: 'linear', |
| 3491 |
position: 'left' |
| 3492 |
}] |
| 3493 |
}, |
| 3494 |
|
| 3495 |
showLines: false, |
| 3496 |
|
| 3497 |
tooltips: { |
| 3498 |
callbacks: { |
| 3499 |
title: function() { |
| 3500 |
return ''; // doesn't make sense for scatter since data are formatted as a point |
| 3501 |
}, |
| 3502 |
label: function(item) { |
| 3503 |
return '(' + item.xLabel + ', ' + item.yLabel + ')'; |
| 3504 |
} |
| 3505 |
} |
| 3506 |
} |
| 3507 |
}); |
| 3508 |
|
| 3509 |
module.exports = function(Chart) { |
| 3510 |
|
| 3511 |
// Scatter charts use line controllers |
| 3512 |
Chart.controllers.scatter = Chart.controllers.line; |
| 3513 |
|
| 3514 |
}; |
| 3515 |
|
| 3516 |
},{"25":25}],22:[function(require,module,exports){ |
| 3517 |
/* global window: false */ |
| 3518 |
'use strict'; |
| 3519 |
|
| 3520 |
var defaults = require(25); |
| 3521 |
var Element = require(26); |
| 3522 |
var helpers = require(45); |
| 3523 |
|
| 3524 |
defaults._set('global', { |
| 3525 |
animation: { |
| 3526 |
duration: 1000, |
| 3527 |
easing: 'easeOutQuart', |
| 3528 |
onProgress: helpers.noop, |
| 3529 |
onComplete: helpers.noop |
| 3530 |
} |
| 3531 |
}); |
| 3532 |
|
| 3533 |
module.exports = function(Chart) { |
| 3534 |
|
| 3535 |
Chart.Animation = Element.extend({ |
| 3536 |
chart: null, // the animation associated chart instance |
| 3537 |
currentStep: 0, // the current animation step |
| 3538 |
numSteps: 60, // default number of steps |
| 3539 |
easing: '', // the easing to use for this animation |
| 3540 |
render: null, // render function used by the animation service |
| 3541 |
|
| 3542 |
onAnimationProgress: null, // user specified callback to fire on each step of the animation |
| 3543 |
onAnimationComplete: null, // user specified callback to fire when the animation finishes |
| 3544 |
}); |
| 3545 |
|
| 3546 |
Chart.animationService = { |
| 3547 |
frameDuration: 17, |
| 3548 |
animations: [], |
| 3549 |
dropFrames: 0, |
| 3550 |
request: null, |
| 3551 |
|
| 3552 |
/** |
| 3553 |
* @param {Chart} chart - The chart to animate. |
| 3554 |
* @param {Chart.Animation} animation - The animation that we will animate. |
| 3555 |
* @param {Number} duration - The animation duration in ms. |
| 3556 |
* @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions |
| 3557 |
*/ |
| 3558 |
addAnimation: function(chart, animation, duration, lazy) { |
| 3559 |
var animations = this.animations; |
| 3560 |
var i, ilen; |
| 3561 |
|
| 3562 |
animation.chart = chart; |
| 3563 |
|
| 3564 |
if (!lazy) { |
| 3565 |
chart.animating = true; |
| 3566 |
} |
| 3567 |
|
| 3568 |
for (i = 0, ilen = animations.length; i < ilen; ++i) { |
| 3569 |
if (animations[i].chart === chart) { |
| 3570 |
animations[i] = animation; |
| 3571 |
return; |
| 3572 |
} |
| 3573 |
} |
| 3574 |
|
| 3575 |
animations.push(animation); |
| 3576 |
|
| 3577 |
// If there are no animations queued, manually kickstart a digest, for lack of a better word |
| 3578 |
if (animations.length === 1) { |
| 3579 |
this.requestAnimationFrame(); |
| 3580 |
} |
| 3581 |
}, |
| 3582 |
|
| 3583 |
cancelAnimation: function(chart) { |
| 3584 |
var index = helpers.findIndex(this.animations, function(animation) { |
| 3585 |
return animation.chart === chart; |
| 3586 |
}); |
| 3587 |
|
| 3588 |
if (index !== -1) { |
| 3589 |
this.animations.splice(index, 1); |
| 3590 |
chart.animating = false; |
| 3591 |
} |
| 3592 |
}, |
| 3593 |
|
| 3594 |
requestAnimationFrame: function() { |
| 3595 |
var me = this; |
| 3596 |
if (me.request === null) { |
| 3597 |
// Skip animation frame requests until the active one is executed. |
| 3598 |
// This can happen when processing mouse events, e.g. 'mousemove' |
| 3599 |
// and 'mouseout' events will trigger multiple renders. |
| 3600 |
me.request = helpers.requestAnimFrame.call(window, function() { |
| 3601 |
me.request = null; |
| 3602 |
me.startDigest(); |
| 3603 |
}); |
| 3604 |
} |
| 3605 |
}, |
| 3606 |
|
| 3607 |
/** |
| 3608 |
* @private |
| 3609 |
*/ |
| 3610 |
startDigest: function() { |
| 3611 |
var me = this; |
| 3612 |
var startTime = Date.now(); |
| 3613 |
var framesToDrop = 0; |
| 3614 |
|
| 3615 |
if (me.dropFrames > 1) { |
| 3616 |
framesToDrop = Math.floor(me.dropFrames); |
| 3617 |
me.dropFrames = me.dropFrames % 1; |
| 3618 |
} |
| 3619 |
|
| 3620 |
me.advance(1 + framesToDrop); |
| 3621 |
|
| 3622 |
var endTime = Date.now(); |
| 3623 |
|
| 3624 |
me.dropFrames += (endTime - startTime) / me.frameDuration; |
| 3625 |
|
| 3626 |
// Do we have more stuff to animate? |
| 3627 |
if (me.animations.length > 0) { |
| 3628 |
me.requestAnimationFrame(); |
| 3629 |
} |
| 3630 |
}, |
| 3631 |
|
| 3632 |
/** |
| 3633 |
* @private |
| 3634 |
*/ |
| 3635 |
advance: function(count) { |
| 3636 |
var animations = this.animations; |
| 3637 |
var animation, chart; |
| 3638 |
var i = 0; |
| 3639 |
|
| 3640 |
while (i < animations.length) { |
| 3641 |
animation = animations[i]; |
| 3642 |
chart = animation.chart; |
| 3643 |
|
| 3644 |
animation.currentStep = (animation.currentStep || 0) + count; |
| 3645 |
animation.currentStep = Math.min(animation.currentStep, animation.numSteps); |
| 3646 |
|
| 3647 |
helpers.callback(animation.render, [chart, animation], chart); |
| 3648 |
helpers.callback(animation.onAnimationProgress, [animation], chart); |
| 3649 |
|
| 3650 |
if (animation.currentStep >= animation.numSteps) { |
| 3651 |
helpers.callback(animation.onAnimationComplete, [animation], chart); |
| 3652 |
chart.animating = false; |
| 3653 |
animations.splice(i, 1); |
| 3654 |
} else { |
| 3655 |
++i; |
| 3656 |
} |
| 3657 |
} |
| 3658 |
} |
| 3659 |
}; |
| 3660 |
|
| 3661 |
/** |
| 3662 |
* Provided for backward compatibility, use Chart.Animation instead |
| 3663 |
* @prop Chart.Animation#animationObject |
| 3664 |
* @deprecated since version 2.6.0 |
| 3665 |
* @todo remove at version 3 |
| 3666 |
*/ |
| 3667 |
Object.defineProperty(Chart.Animation.prototype, 'animationObject', { |
| 3668 |
get: function() { |
| 3669 |
return this; |
| 3670 |
} |
| 3671 |
}); |
| 3672 |
|
| 3673 |
/** |
| 3674 |
* Provided for backward compatibility, use Chart.Animation#chart instead |
| 3675 |
* @prop Chart.Animation#chartInstance |
| 3676 |
* @deprecated since version 2.6.0 |
| 3677 |
* @todo remove at version 3 |
| 3678 |
*/ |
| 3679 |
Object.defineProperty(Chart.Animation.prototype, 'chartInstance', { |
| 3680 |
get: function() { |
| 3681 |
return this.chart; |
| 3682 |
}, |
| 3683 |
set: function(value) { |
| 3684 |
this.chart = value; |
| 3685 |
} |
| 3686 |
}); |
| 3687 |
|
| 3688 |
}; |
| 3689 |
|
| 3690 |
},{"25":25,"26":26,"45":45}],23:[function(require,module,exports){ |
| 3691 |
'use strict'; |
| 3692 |
|
| 3693 |
var defaults = require(25); |
| 3694 |
var helpers = require(45); |
| 3695 |
var Interaction = require(28); |
| 3696 |
var platform = require(48); |
| 3697 |
|
| 3698 |
module.exports = function(Chart) { |
| 3699 |
var plugins = Chart.plugins; |
| 3700 |
|
| 3701 |
// Create a dictionary of chart types, to allow for extension of existing types |
| 3702 |
Chart.types = {}; |
| 3703 |
|
| 3704 |
// Store a reference to each instance - allowing us to globally resize chart instances on window resize. |
| 3705 |
// Destroy method on the chart will remove the instance of the chart from this reference. |
| 3706 |
Chart.instances = {}; |
| 3707 |
|
| 3708 |
// Controllers available for dataset visualization eg. bar, line, slice, etc. |
| 3709 |
Chart.controllers = {}; |
| 3710 |
|
| 3711 |
/** |
| 3712 |
* Initializes the given config with global and chart default values. |
| 3713 |
*/ |
| 3714 |
function initConfig(config) { |
| 3715 |
config = config || {}; |
| 3716 |
|
| 3717 |
// Do NOT use configMerge() for the data object because this method merges arrays |
| 3718 |
// and so would change references to labels and datasets, preventing data updates. |
| 3719 |
var data = config.data = config.data || {}; |
| 3720 |
data.datasets = data.datasets || []; |
| 3721 |
data.labels = data.labels || []; |
| 3722 |
|
| 3723 |
config.options = helpers.configMerge( |
| 3724 |
defaults.global, |
| 3725 |
defaults[config.type], |
| 3726 |
config.options || {}); |
| 3727 |
|
| 3728 |
return config; |
| 3729 |
} |
| 3730 |
|
| 3731 |
/** |
| 3732 |
* Updates the config of the chart |
| 3733 |
* @param chart {Chart} chart to update the options for |
| 3734 |
*/ |
| 3735 |
function updateConfig(chart) { |
| 3736 |
var newOptions = chart.options; |
| 3737 |
|
| 3738 |
// Update Scale(s) with options |
| 3739 |
if (newOptions.scale) { |
| 3740 |
chart.scale.options = newOptions.scale; |
| 3741 |
} else if (newOptions.scales) { |
| 3742 |
newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) { |
| 3743 |
chart.scales[scaleOptions.id].options = scaleOptions; |
| 3744 |
}); |
| 3745 |
} |
| 3746 |
|
| 3747 |
// Tooltip |
| 3748 |
chart.tooltip._options = newOptions.tooltips; |
| 3749 |
} |
| 3750 |
|
| 3751 |
function positionIsHorizontal(position) { |
| 3752 |
return position === 'top' || position === 'bottom'; |
| 3753 |
} |
| 3754 |
|
| 3755 |
helpers.extend(Chart.prototype, /** @lends Chart */ { |
| 3756 |
/** |
| 3757 |
* @private |
| 3758 |
*/ |
| 3759 |
construct: function(item, config) { |
| 3760 |
var me = this; |
| 3761 |
|
| 3762 |
config = initConfig(config); |
| 3763 |
|
| 3764 |
var context = platform.acquireContext(item, config); |
| 3765 |
var canvas = context && context.canvas; |
| 3766 |
var height = canvas && canvas.height; |
| 3767 |
var width = canvas && canvas.width; |
| 3768 |
|
| 3769 |
me.id = helpers.uid(); |
| 3770 |
me.ctx = context; |
| 3771 |
me.canvas = canvas; |
| 3772 |
me.config = config; |
| 3773 |
me.width = width; |
| 3774 |
me.height = height; |
| 3775 |
me.aspectRatio = height ? width / height : null; |
| 3776 |
me.options = config.options; |
| 3777 |
me._bufferedRender = false; |
| 3778 |
|
| 3779 |
/** |
| 3780 |
* Provided for backward compatibility, Chart and Chart.Controller have been merged, |
| 3781 |
* the "instance" still need to be defined since it might be called from plugins. |
| 3782 |
* @prop Chart#chart |
| 3783 |
* @deprecated since version 2.6.0 |
| 3784 |
* @todo remove at version 3 |
| 3785 |
* @private |
| 3786 |
*/ |
| 3787 |
me.chart = me; |
| 3788 |
me.controller = me; // chart.chart.controller #inception |
| 3789 |
|
| 3790 |
// Add the chart instance to the global namespace |
| 3791 |
Chart.instances[me.id] = me; |
| 3792 |
|
| 3793 |
// Define alias to the config data: `chart.data === chart.config.data` |
| 3794 |
Object.defineProperty(me, 'data', { |
| 3795 |
get: function() { |
| 3796 |
return me.config.data; |
| 3797 |
}, |
| 3798 |
set: function(value) { |
| 3799 |
me.config.data = value; |
| 3800 |
} |
| 3801 |
}); |
| 3802 |
|
| 3803 |
if (!context || !canvas) { |
| 3804 |
// The given item is not a compatible context2d element, let's return before finalizing |
| 3805 |
// the chart initialization but after setting basic chart / controller properties that |
| 3806 |
// can help to figure out that the chart is not valid (e.g chart.canvas !== null); |
| 3807 |
// https://github.com/chartjs/Chart.js/issues/2807 |
| 3808 |
console.error("Failed to create chart: can't acquire context from the given item"); |
| 3809 |
return; |
| 3810 |
} |
| 3811 |
|
| 3812 |
me.initialize(); |
| 3813 |
me.update(); |
| 3814 |
}, |
| 3815 |
|
| 3816 |
/** |
| 3817 |
* @private |
| 3818 |
*/ |
| 3819 |
initialize: function() { |
| 3820 |
var me = this; |
| 3821 |
|
| 3822 |
// Before init plugin notification |
| 3823 |
plugins.notify(me, 'beforeInit'); |
| 3824 |
|
| 3825 |
helpers.retinaScale(me, me.options.devicePixelRatio); |
| 3826 |
|
| 3827 |
me.bindEvents(); |
| 3828 |
|
| 3829 |
if (me.options.responsive) { |
| 3830 |
// Initial resize before chart draws (must be silent to preserve initial animations). |
| 3831 |
me.resize(true); |
| 3832 |
} |
| 3833 |
|
| 3834 |
// Make sure scales have IDs and are built before we build any controllers. |
| 3835 |
me.ensureScalesHaveIDs(); |
| 3836 |
me.buildScales(); |
| 3837 |
me.initToolTip(); |
| 3838 |
|
| 3839 |
// After init plugin notification |
| 3840 |
plugins.notify(me, 'afterInit'); |
| 3841 |
|
| 3842 |
return me; |
| 3843 |
}, |
| 3844 |
|
| 3845 |
clear: function() { |
| 3846 |
helpers.canvas.clear(this); |
| 3847 |
return this; |
| 3848 |
}, |
| 3849 |
|
| 3850 |
stop: function() { |
| 3851 |
// Stops any current animation loop occurring |
| 3852 |
Chart.animationService.cancelAnimation(this); |
| 3853 |
return this; |
| 3854 |
}, |
| 3855 |
|
| 3856 |
resize: function(silent) { |
| 3857 |
var me = this; |
| 3858 |
var options = me.options; |
| 3859 |
var canvas = me.canvas; |
| 3860 |
var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; |
| 3861 |
|
| 3862 |
// the canvas render width and height will be casted to integers so make sure that |
| 3863 |
// the canvas display style uses the same integer values to avoid blurring effect. |
| 3864 |
|
| 3865 |
// Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased |
| 3866 |
var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas))); |
| 3867 |
var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas))); |
| 3868 |
|
| 3869 |
if (me.width === newWidth && me.height === newHeight) { |
| 3870 |
return; |
| 3871 |
} |
| 3872 |
|
| 3873 |
canvas.width = me.width = newWidth; |
| 3874 |
canvas.height = me.height = newHeight; |
| 3875 |
canvas.style.width = newWidth + 'px'; |
| 3876 |
canvas.style.height = newHeight + 'px'; |
| 3877 |
|
| 3878 |
helpers.retinaScale(me, options.devicePixelRatio); |
| 3879 |
|
| 3880 |
if (!silent) { |
| 3881 |
// Notify any plugins about the resize |
| 3882 |
var newSize = {width: newWidth, height: newHeight}; |
| 3883 |
plugins.notify(me, 'resize', [newSize]); |
| 3884 |
|
| 3885 |
// Notify of resize |
| 3886 |
if (me.options.onResize) { |
| 3887 |
me.options.onResize(me, newSize); |
| 3888 |
} |
| 3889 |
|
| 3890 |
me.stop(); |
| 3891 |
me.update(me.options.responsiveAnimationDuration); |
| 3892 |
} |
| 3893 |
}, |
| 3894 |
|
| 3895 |
ensureScalesHaveIDs: function() { |
| 3896 |
var options = this.options; |
| 3897 |
var scalesOptions = options.scales || {}; |
| 3898 |
var scaleOptions = options.scale; |
| 3899 |
|
| 3900 |
helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { |
| 3901 |
xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); |
| 3902 |
}); |
| 3903 |
|
| 3904 |
helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { |
| 3905 |
yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); |
| 3906 |
}); |
| 3907 |
|
| 3908 |
if (scaleOptions) { |
| 3909 |
scaleOptions.id = scaleOptions.id || 'scale'; |
| 3910 |
} |
| 3911 |
}, |
| 3912 |
|
| 3913 |
/** |
| 3914 |
* Builds a map of scale ID to scale object for future lookup. |
| 3915 |
*/ |
| 3916 |
buildScales: function() { |
| 3917 |
var me = this; |
| 3918 |
var options = me.options; |
| 3919 |
var scales = me.scales = {}; |
| 3920 |
var items = []; |
| 3921 |
|
| 3922 |
if (options.scales) { |
| 3923 |
items = items.concat( |
| 3924 |
(options.scales.xAxes || []).map(function(xAxisOptions) { |
| 3925 |
return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; |
| 3926 |
}), |
| 3927 |
(options.scales.yAxes || []).map(function(yAxisOptions) { |
| 3928 |
return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; |
| 3929 |
}) |
| 3930 |
); |
| 3931 |
} |
| 3932 |
|
| 3933 |
if (options.scale) { |
| 3934 |
items.push({ |
| 3935 |
options: options.scale, |
| 3936 |
dtype: 'radialLinear', |
| 3937 |
isDefault: true, |
| 3938 |
dposition: 'chartArea' |
| 3939 |
}); |
| 3940 |
} |
| 3941 |
|
| 3942 |
helpers.each(items, function(item) { |
| 3943 |
var scaleOptions = item.options; |
| 3944 |
var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype); |
| 3945 |
var scaleClass = Chart.scaleService.getScaleConstructor(scaleType); |
| 3946 |
if (!scaleClass) { |
| 3947 |
return; |
| 3948 |
} |
| 3949 |
|
| 3950 |
if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { |
| 3951 |
scaleOptions.position = item.dposition; |
| 3952 |
} |
| 3953 |
|
| 3954 |
var scale = new scaleClass({ |
| 3955 |
id: scaleOptions.id, |
| 3956 |
options: scaleOptions, |
| 3957 |
ctx: me.ctx, |
| 3958 |
chart: me |
| 3959 |
}); |
| 3960 |
|
| 3961 |
scales[scale.id] = scale; |
| 3962 |
scale.mergeTicksOptions(); |
| 3963 |
|
| 3964 |
// TODO(SB): I think we should be able to remove this custom case (options.scale) |
| 3965 |
// and consider it as a regular scale part of the "scales"" map only! This would |
| 3966 |
// make the logic easier and remove some useless? custom code. |
| 3967 |
if (item.isDefault) { |
| 3968 |
me.scale = scale; |
| 3969 |
} |
| 3970 |
}); |
| 3971 |
|
| 3972 |
Chart.scaleService.addScalesToLayout(this); |
| 3973 |
}, |
| 3974 |
|
| 3975 |
buildOrUpdateControllers: function() { |
| 3976 |
var me = this; |
| 3977 |
var types = []; |
| 3978 |
var newControllers = []; |
| 3979 |
|
| 3980 |
helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 3981 |
var meta = me.getDatasetMeta(datasetIndex); |
| 3982 |
var type = dataset.type || me.config.type; |
| 3983 |
|
| 3984 |
if (meta.type && meta.type !== type) { |
| 3985 |
me.destroyDatasetMeta(datasetIndex); |
| 3986 |
meta = me.getDatasetMeta(datasetIndex); |
| 3987 |
} |
| 3988 |
meta.type = type; |
| 3989 |
|
| 3990 |
types.push(meta.type); |
| 3991 |
|
| 3992 |
if (meta.controller) { |
| 3993 |
meta.controller.updateIndex(datasetIndex); |
| 3994 |
} else { |
| 3995 |
var ControllerClass = Chart.controllers[meta.type]; |
| 3996 |
if (ControllerClass === undefined) { |
| 3997 |
throw new Error('"' + meta.type + '" is not a chart type.'); |
| 3998 |
} |
| 3999 |
|
| 4000 |
meta.controller = new ControllerClass(me, datasetIndex); |
| 4001 |
newControllers.push(meta.controller); |
| 4002 |
} |
| 4003 |
}, me); |
| 4004 |
|
| 4005 |
return newControllers; |
| 4006 |
}, |
| 4007 |
|
| 4008 |
/** |
| 4009 |
* Reset the elements of all datasets |
| 4010 |
* @private |
| 4011 |
*/ |
| 4012 |
resetElements: function() { |
| 4013 |
var me = this; |
| 4014 |
helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 4015 |
me.getDatasetMeta(datasetIndex).controller.reset(); |
| 4016 |
}, me); |
| 4017 |
}, |
| 4018 |
|
| 4019 |
/** |
| 4020 |
* Resets the chart back to it's state before the initial animation |
| 4021 |
*/ |
| 4022 |
reset: function() { |
| 4023 |
this.resetElements(); |
| 4024 |
this.tooltip.initialize(); |
| 4025 |
}, |
| 4026 |
|
| 4027 |
update: function(config) { |
| 4028 |
var me = this; |
| 4029 |
|
| 4030 |
if (!config || typeof config !== 'object') { |
| 4031 |
// backwards compatibility |
| 4032 |
config = { |
| 4033 |
duration: config, |
| 4034 |
lazy: arguments[1] |
| 4035 |
}; |
| 4036 |
} |
| 4037 |
|
| 4038 |
updateConfig(me); |
| 4039 |
|
| 4040 |
if (plugins.notify(me, 'beforeUpdate') === false) { |
| 4041 |
return; |
| 4042 |
} |
| 4043 |
|
| 4044 |
// In case the entire data object changed |
| 4045 |
me.tooltip._data = me.data; |
| 4046 |
|
| 4047 |
// Make sure dataset controllers are updated and new controllers are reset |
| 4048 |
var newControllers = me.buildOrUpdateControllers(); |
| 4049 |
|
| 4050 |
// Make sure all dataset controllers have correct meta data counts |
| 4051 |
helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 4052 |
me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); |
| 4053 |
}, me); |
| 4054 |
|
| 4055 |
me.updateLayout(); |
| 4056 |
|
| 4057 |
// Can only reset the new controllers after the scales have been updated |
| 4058 |
helpers.each(newControllers, function(controller) { |
| 4059 |
controller.reset(); |
| 4060 |
}); |
| 4061 |
|
| 4062 |
me.updateDatasets(); |
| 4063 |
|
| 4064 |
// Need to reset tooltip in case it is displayed with elements that are removed |
| 4065 |
// after update. |
| 4066 |
me.tooltip.initialize(); |
| 4067 |
|
| 4068 |
// Last active contains items that were previously in the tooltip. |
| 4069 |
// When we reset the tooltip, we need to clear it |
| 4070 |
me.lastActive = []; |
| 4071 |
|
| 4072 |
// Do this before render so that any plugins that need final scale updates can use it |
| 4073 |
plugins.notify(me, 'afterUpdate'); |
| 4074 |
|
| 4075 |
if (me._bufferedRender) { |
| 4076 |
me._bufferedRequest = { |
| 4077 |
duration: config.duration, |
| 4078 |
easing: config.easing, |
| 4079 |
lazy: config.lazy |
| 4080 |
}; |
| 4081 |
} else { |
| 4082 |
me.render(config); |
| 4083 |
} |
| 4084 |
}, |
| 4085 |
|
| 4086 |
/** |
| 4087 |
* Updates the chart layout unless a plugin returns `false` to the `beforeLayout` |
| 4088 |
* hook, in which case, plugins will not be called on `afterLayout`. |
| 4089 |
* @private |
| 4090 |
*/ |
| 4091 |
updateLayout: function() { |
| 4092 |
var me = this; |
| 4093 |
|
| 4094 |
if (plugins.notify(me, 'beforeLayout') === false) { |
| 4095 |
return; |
| 4096 |
} |
| 4097 |
|
| 4098 |
Chart.layoutService.update(this, this.width, this.height); |
| 4099 |
|
| 4100 |
/** |
| 4101 |
* Provided for backward compatibility, use `afterLayout` instead. |
| 4102 |
* @method IPlugin#afterScaleUpdate |
| 4103 |
* @deprecated since version 2.5.0 |
| 4104 |
* @todo remove at version 3 |
| 4105 |
* @private |
| 4106 |
*/ |
| 4107 |
plugins.notify(me, 'afterScaleUpdate'); |
| 4108 |
plugins.notify(me, 'afterLayout'); |
| 4109 |
}, |
| 4110 |
|
| 4111 |
/** |
| 4112 |
* Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` |
| 4113 |
* hook, in which case, plugins will not be called on `afterDatasetsUpdate`. |
| 4114 |
* @private |
| 4115 |
*/ |
| 4116 |
updateDatasets: function() { |
| 4117 |
var me = this; |
| 4118 |
|
| 4119 |
if (plugins.notify(me, 'beforeDatasetsUpdate') === false) { |
| 4120 |
return; |
| 4121 |
} |
| 4122 |
|
| 4123 |
for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { |
| 4124 |
me.updateDataset(i); |
| 4125 |
} |
| 4126 |
|
| 4127 |
plugins.notify(me, 'afterDatasetsUpdate'); |
| 4128 |
}, |
| 4129 |
|
| 4130 |
/** |
| 4131 |
* Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` |
| 4132 |
* hook, in which case, plugins will not be called on `afterDatasetUpdate`. |
| 4133 |
* @private |
| 4134 |
*/ |
| 4135 |
updateDataset: function(index) { |
| 4136 |
var me = this; |
| 4137 |
var meta = me.getDatasetMeta(index); |
| 4138 |
var args = { |
| 4139 |
meta: meta, |
| 4140 |
index: index |
| 4141 |
}; |
| 4142 |
|
| 4143 |
if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { |
| 4144 |
return; |
| 4145 |
} |
| 4146 |
|
| 4147 |
meta.controller.update(); |
| 4148 |
|
| 4149 |
plugins.notify(me, 'afterDatasetUpdate', [args]); |
| 4150 |
}, |
| 4151 |
|
| 4152 |
render: function(config) { |
| 4153 |
var me = this; |
| 4154 |
|
| 4155 |
if (!config || typeof config !== 'object') { |
| 4156 |
// backwards compatibility |
| 4157 |
config = { |
| 4158 |
duration: config, |
| 4159 |
lazy: arguments[1] |
| 4160 |
}; |
| 4161 |
} |
| 4162 |
|
| 4163 |
var duration = config.duration; |
| 4164 |
var lazy = config.lazy; |
| 4165 |
|
| 4166 |
if (plugins.notify(me, 'beforeRender') === false) { |
| 4167 |
return; |
| 4168 |
} |
| 4169 |
|
| 4170 |
var animationOptions = me.options.animation; |
| 4171 |
var onComplete = function(animation) { |
| 4172 |
plugins.notify(me, 'afterRender'); |
| 4173 |
helpers.callback(animationOptions && animationOptions.onComplete, [animation], me); |
| 4174 |
}; |
| 4175 |
|
| 4176 |
if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { |
| 4177 |
var animation = new Chart.Animation({ |
| 4178 |
numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps |
| 4179 |
easing: config.easing || animationOptions.easing, |
| 4180 |
|
| 4181 |
render: function(chart, animationObject) { |
| 4182 |
var easingFunction = helpers.easing.effects[animationObject.easing]; |
| 4183 |
var currentStep = animationObject.currentStep; |
| 4184 |
var stepDecimal = currentStep / animationObject.numSteps; |
| 4185 |
|
| 4186 |
chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); |
| 4187 |
}, |
| 4188 |
|
| 4189 |
onAnimationProgress: animationOptions.onProgress, |
| 4190 |
onAnimationComplete: onComplete |
| 4191 |
}); |
| 4192 |
|
| 4193 |
Chart.animationService.addAnimation(me, animation, duration, lazy); |
| 4194 |
} else { |
| 4195 |
me.draw(); |
| 4196 |
|
| 4197 |
// See https://github.com/chartjs/Chart.js/issues/3781 |
| 4198 |
onComplete(new Chart.Animation({numSteps: 0, chart: me})); |
| 4199 |
} |
| 4200 |
|
| 4201 |
return me; |
| 4202 |
}, |
| 4203 |
|
| 4204 |
draw: function(easingValue) { |
| 4205 |
var me = this; |
| 4206 |
|
| 4207 |
me.clear(); |
| 4208 |
|
| 4209 |
if (helpers.isNullOrUndef(easingValue)) { |
| 4210 |
easingValue = 1; |
| 4211 |
} |
| 4212 |
|
| 4213 |
me.transition(easingValue); |
| 4214 |
|
| 4215 |
if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) { |
| 4216 |
return; |
| 4217 |
} |
| 4218 |
|
| 4219 |
// Draw all the scales |
| 4220 |
helpers.each(me.boxes, function(box) { |
| 4221 |
box.draw(me.chartArea); |
| 4222 |
}, me); |
| 4223 |
|
| 4224 |
if (me.scale) { |
| 4225 |
me.scale.draw(); |
| 4226 |
} |
| 4227 |
|
| 4228 |
me.drawDatasets(easingValue); |
| 4229 |
me._drawTooltip(easingValue); |
| 4230 |
|
| 4231 |
plugins.notify(me, 'afterDraw', [easingValue]); |
| 4232 |
}, |
| 4233 |
|
| 4234 |
/** |
| 4235 |
* @private |
| 4236 |
*/ |
| 4237 |
transition: function(easingValue) { |
| 4238 |
var me = this; |
| 4239 |
|
| 4240 |
for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { |
| 4241 |
if (me.isDatasetVisible(i)) { |
| 4242 |
me.getDatasetMeta(i).controller.transition(easingValue); |
| 4243 |
} |
| 4244 |
} |
| 4245 |
|
| 4246 |
me.tooltip.transition(easingValue); |
| 4247 |
}, |
| 4248 |
|
| 4249 |
/** |
| 4250 |
* Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` |
| 4251 |
* hook, in which case, plugins will not be called on `afterDatasetsDraw`. |
| 4252 |
* @private |
| 4253 |
*/ |
| 4254 |
drawDatasets: function(easingValue) { |
| 4255 |
var me = this; |
| 4256 |
|
| 4257 |
if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { |
| 4258 |
return; |
| 4259 |
} |
| 4260 |
|
| 4261 |
// Draw datasets reversed to support proper line stacking |
| 4262 |
for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) { |
| 4263 |
if (me.isDatasetVisible(i)) { |
| 4264 |
me.drawDataset(i, easingValue); |
| 4265 |
} |
| 4266 |
} |
| 4267 |
|
| 4268 |
plugins.notify(me, 'afterDatasetsDraw', [easingValue]); |
| 4269 |
}, |
| 4270 |
|
| 4271 |
/** |
| 4272 |
* Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` |
| 4273 |
* hook, in which case, plugins will not be called on `afterDatasetDraw`. |
| 4274 |
* @private |
| 4275 |
*/ |
| 4276 |
drawDataset: function(index, easingValue) { |
| 4277 |
var me = this; |
| 4278 |
var meta = me.getDatasetMeta(index); |
| 4279 |
var args = { |
| 4280 |
meta: meta, |
| 4281 |
index: index, |
| 4282 |
easingValue: easingValue |
| 4283 |
}; |
| 4284 |
|
| 4285 |
if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { |
| 4286 |
return; |
| 4287 |
} |
| 4288 |
|
| 4289 |
meta.controller.draw(easingValue); |
| 4290 |
|
| 4291 |
plugins.notify(me, 'afterDatasetDraw', [args]); |
| 4292 |
}, |
| 4293 |
|
| 4294 |
/** |
| 4295 |
* Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` |
| 4296 |
* hook, in which case, plugins will not be called on `afterTooltipDraw`. |
| 4297 |
* @private |
| 4298 |
*/ |
| 4299 |
_drawTooltip: function(easingValue) { |
| 4300 |
var me = this; |
| 4301 |
var tooltip = me.tooltip; |
| 4302 |
var args = { |
| 4303 |
tooltip: tooltip, |
| 4304 |
easingValue: easingValue |
| 4305 |
}; |
| 4306 |
|
| 4307 |
if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { |
| 4308 |
return; |
| 4309 |
} |
| 4310 |
|
| 4311 |
tooltip.draw(); |
| 4312 |
|
| 4313 |
plugins.notify(me, 'afterTooltipDraw', [args]); |
| 4314 |
}, |
| 4315 |
|
| 4316 |
// Get the single element that was clicked on |
| 4317 |
// @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw |
| 4318 |
getElementAtEvent: function(e) { |
| 4319 |
return Interaction.modes.single(this, e); |
| 4320 |
}, |
| 4321 |
|
| 4322 |
getElementsAtEvent: function(e) { |
| 4323 |
return Interaction.modes.label(this, e, {intersect: true}); |
| 4324 |
}, |
| 4325 |
|
| 4326 |
getElementsAtXAxis: function(e) { |
| 4327 |
return Interaction.modes['x-axis'](this, e, {intersect: true}); |
| 4328 |
}, |
| 4329 |
|
| 4330 |
getElementsAtEventForMode: function(e, mode, options) { |
| 4331 |
var method = Interaction.modes[mode]; |
| 4332 |
if (typeof method === 'function') { |
| 4333 |
return method(this, e, options); |
| 4334 |
} |
| 4335 |
|
| 4336 |
return []; |
| 4337 |
}, |
| 4338 |
|
| 4339 |
getDatasetAtEvent: function(e) { |
| 4340 |
return Interaction.modes.dataset(this, e, {intersect: true}); |
| 4341 |
}, |
| 4342 |
|
| 4343 |
getDatasetMeta: function(datasetIndex) { |
| 4344 |
var me = this; |
| 4345 |
var dataset = me.data.datasets[datasetIndex]; |
| 4346 |
if (!dataset._meta) { |
| 4347 |
dataset._meta = {}; |
| 4348 |
} |
| 4349 |
|
| 4350 |
var meta = dataset._meta[me.id]; |
| 4351 |
if (!meta) { |
| 4352 |
meta = dataset._meta[me.id] = { |
| 4353 |
type: null, |
| 4354 |
data: [], |
| 4355 |
dataset: null, |
| 4356 |
controller: null, |
| 4357 |
hidden: null, // See isDatasetVisible() comment |
| 4358 |
xAxisID: null, |
| 4359 |
yAxisID: null |
| 4360 |
}; |
| 4361 |
} |
| 4362 |
|
| 4363 |
return meta; |
| 4364 |
}, |
| 4365 |
|
| 4366 |
getVisibleDatasetCount: function() { |
| 4367 |
var count = 0; |
| 4368 |
for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { |
| 4369 |
if (this.isDatasetVisible(i)) { |
| 4370 |
count++; |
| 4371 |
} |
| 4372 |
} |
| 4373 |
return count; |
| 4374 |
}, |
| 4375 |
|
| 4376 |
isDatasetVisible: function(datasetIndex) { |
| 4377 |
var meta = this.getDatasetMeta(datasetIndex); |
| 4378 |
|
| 4379 |
// meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, |
| 4380 |
// the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. |
| 4381 |
return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; |
| 4382 |
}, |
| 4383 |
|
| 4384 |
generateLegend: function() { |
| 4385 |
return this.options.legendCallback(this); |
| 4386 |
}, |
| 4387 |
|
| 4388 |
/** |
| 4389 |
* @private |
| 4390 |
*/ |
| 4391 |
destroyDatasetMeta: function(datasetIndex) { |
| 4392 |
var id = this.id; |
| 4393 |
var dataset = this.data.datasets[datasetIndex]; |
| 4394 |
var meta = dataset._meta && dataset._meta[id]; |
| 4395 |
|
| 4396 |
if (meta) { |
| 4397 |
meta.controller.destroy(); |
| 4398 |
delete dataset._meta[id]; |
| 4399 |
} |
| 4400 |
}, |
| 4401 |
|
| 4402 |
destroy: function() { |
| 4403 |
var me = this; |
| 4404 |
var canvas = me.canvas; |
| 4405 |
var i, ilen; |
| 4406 |
|
| 4407 |
me.stop(); |
| 4408 |
|
| 4409 |
// dataset controllers need to cleanup associated data |
| 4410 |
for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { |
| 4411 |
me.destroyDatasetMeta(i); |
| 4412 |
} |
| 4413 |
|
| 4414 |
if (canvas) { |
| 4415 |
me.unbindEvents(); |
| 4416 |
helpers.canvas.clear(me); |
| 4417 |
platform.releaseContext(me.ctx); |
| 4418 |
me.canvas = null; |
| 4419 |
me.ctx = null; |
| 4420 |
} |
| 4421 |
|
| 4422 |
plugins.notify(me, 'destroy'); |
| 4423 |
|
| 4424 |
delete Chart.instances[me.id]; |
| 4425 |
}, |
| 4426 |
|
| 4427 |
toBase64Image: function() { |
| 4428 |
return this.canvas.toDataURL.apply(this.canvas, arguments); |
| 4429 |
}, |
| 4430 |
|
| 4431 |
initToolTip: function() { |
| 4432 |
var me = this; |
| 4433 |
me.tooltip = new Chart.Tooltip({ |
| 4434 |
_chart: me, |
| 4435 |
_chartInstance: me, // deprecated, backward compatibility |
| 4436 |
_data: me.data, |
| 4437 |
_options: me.options.tooltips |
| 4438 |
}, me); |
| 4439 |
}, |
| 4440 |
|
| 4441 |
/** |
| 4442 |
* @private |
| 4443 |
*/ |
| 4444 |
bindEvents: function() { |
| 4445 |
var me = this; |
| 4446 |
var listeners = me._listeners = {}; |
| 4447 |
var listener = function() { |
| 4448 |
me.eventHandler.apply(me, arguments); |
| 4449 |
}; |
| 4450 |
|
| 4451 |
helpers.each(me.options.events, function(type) { |
| 4452 |
platform.addEventListener(me, type, listener); |
| 4453 |
listeners[type] = listener; |
| 4454 |
}); |
| 4455 |
|
| 4456 |
// Elements used to detect size change should not be injected for non responsive charts. |
| 4457 |
// See https://github.com/chartjs/Chart.js/issues/2210 |
| 4458 |
if (me.options.responsive) { |
| 4459 |
listener = function() { |
| 4460 |
me.resize(); |
| 4461 |
}; |
| 4462 |
|
| 4463 |
platform.addEventListener(me, 'resize', listener); |
| 4464 |
listeners.resize = listener; |
| 4465 |
} |
| 4466 |
}, |
| 4467 |
|
| 4468 |
/** |
| 4469 |
* @private |
| 4470 |
*/ |
| 4471 |
unbindEvents: function() { |
| 4472 |
var me = this; |
| 4473 |
var listeners = me._listeners; |
| 4474 |
if (!listeners) { |
| 4475 |
return; |
| 4476 |
} |
| 4477 |
|
| 4478 |
delete me._listeners; |
| 4479 |
helpers.each(listeners, function(listener, type) { |
| 4480 |
platform.removeEventListener(me, type, listener); |
| 4481 |
}); |
| 4482 |
}, |
| 4483 |
|
| 4484 |
updateHoverStyle: function(elements, mode, enabled) { |
| 4485 |
var method = enabled ? 'setHoverStyle' : 'removeHoverStyle'; |
| 4486 |
var element, i, ilen; |
| 4487 |
|
| 4488 |
for (i = 0, ilen = elements.length; i < ilen; ++i) { |
| 4489 |
element = elements[i]; |
| 4490 |
if (element) { |
| 4491 |
this.getDatasetMeta(element._datasetIndex).controller[method](element); |
| 4492 |
} |
| 4493 |
} |
| 4494 |
}, |
| 4495 |
|
| 4496 |
/** |
| 4497 |
* @private |
| 4498 |
*/ |
| 4499 |
eventHandler: function(e) { |
| 4500 |
var me = this; |
| 4501 |
var tooltip = me.tooltip; |
| 4502 |
|
| 4503 |
if (plugins.notify(me, 'beforeEvent', [e]) === false) { |
| 4504 |
return; |
| 4505 |
} |
| 4506 |
|
| 4507 |
// Buffer any update calls so that renders do not occur |
| 4508 |
me._bufferedRender = true; |
| 4509 |
me._bufferedRequest = null; |
| 4510 |
|
| 4511 |
var changed = me.handleEvent(e); |
| 4512 |
changed |= tooltip && tooltip.handleEvent(e); |
| 4513 |
|
| 4514 |
plugins.notify(me, 'afterEvent', [e]); |
| 4515 |
|
| 4516 |
var bufferedRequest = me._bufferedRequest; |
| 4517 |
if (bufferedRequest) { |
| 4518 |
// If we have an update that was triggered, we need to do a normal render |
| 4519 |
me.render(bufferedRequest); |
| 4520 |
} else if (changed && !me.animating) { |
| 4521 |
// If entering, leaving, or changing elements, animate the change via pivot |
| 4522 |
me.stop(); |
| 4523 |
|
| 4524 |
// We only need to render at this point. Updating will cause scales to be |
| 4525 |
// recomputed generating flicker & using more memory than necessary. |
| 4526 |
me.render(me.options.hover.animationDuration, true); |
| 4527 |
} |
| 4528 |
|
| 4529 |
me._bufferedRender = false; |
| 4530 |
me._bufferedRequest = null; |
| 4531 |
|
| 4532 |
return me; |
| 4533 |
}, |
| 4534 |
|
| 4535 |
/** |
| 4536 |
* Handle an event |
| 4537 |
* @private |
| 4538 |
* @param {IEvent} event the event to handle |
| 4539 |
* @return {Boolean} true if the chart needs to re-render |
| 4540 |
*/ |
| 4541 |
handleEvent: function(e) { |
| 4542 |
var me = this; |
| 4543 |
var options = me.options || {}; |
| 4544 |
var hoverOptions = options.hover; |
| 4545 |
var changed = false; |
| 4546 |
|
| 4547 |
me.lastActive = me.lastActive || []; |
| 4548 |
|
| 4549 |
// Find Active Elements for hover and tooltips |
| 4550 |
if (e.type === 'mouseout') { |
| 4551 |
me.active = []; |
| 4552 |
} else { |
| 4553 |
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); |
| 4554 |
} |
| 4555 |
|
| 4556 |
// Invoke onHover hook |
| 4557 |
// Need to call with native event here to not break backwards compatibility |
| 4558 |
helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); |
| 4559 |
|
| 4560 |
if (e.type === 'mouseup' || e.type === 'click') { |
| 4561 |
if (options.onClick) { |
| 4562 |
// Use e.native here for backwards compatibility |
| 4563 |
options.onClick.call(me, e.native, me.active); |
| 4564 |
} |
| 4565 |
} |
| 4566 |
|
| 4567 |
// Remove styling for last active (even if it may still be active) |
| 4568 |
if (me.lastActive.length) { |
| 4569 |
me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); |
| 4570 |
} |
| 4571 |
|
| 4572 |
// Built in hover styling |
| 4573 |
if (me.active.length && hoverOptions.mode) { |
| 4574 |
me.updateHoverStyle(me.active, hoverOptions.mode, true); |
| 4575 |
} |
| 4576 |
|
| 4577 |
changed = !helpers.arrayEquals(me.active, me.lastActive); |
| 4578 |
|
| 4579 |
// Remember Last Actives |
| 4580 |
me.lastActive = me.active; |
| 4581 |
|
| 4582 |
return changed; |
| 4583 |
} |
| 4584 |
}); |
| 4585 |
|
| 4586 |
/** |
| 4587 |
* Provided for backward compatibility, use Chart instead. |
| 4588 |
* @class Chart.Controller |
| 4589 |
* @deprecated since version 2.6.0 |
| 4590 |
* @todo remove at version 3 |
| 4591 |
* @private |
| 4592 |
*/ |
| 4593 |
Chart.Controller = Chart; |
| 4594 |
}; |
| 4595 |
|
| 4596 |
},{"25":25,"28":28,"45":45,"48":48}],24:[function(require,module,exports){ |
| 4597 |
'use strict'; |
| 4598 |
|
| 4599 |
var helpers = require(45); |
| 4600 |
|
| 4601 |
module.exports = function(Chart) { |
| 4602 |
|
| 4603 |
var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; |
| 4604 |
|
| 4605 |
/** |
| 4606 |
* Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', |
| 4607 |
* 'unshift') and notify the listener AFTER the array has been altered. Listeners are |
| 4608 |
* called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. |
| 4609 |
*/ |
| 4610 |
function listenArrayEvents(array, listener) { |
| 4611 |
if (array._chartjs) { |
| 4612 |
array._chartjs.listeners.push(listener); |
| 4613 |
return; |
| 4614 |
} |
| 4615 |
|
| 4616 |
Object.defineProperty(array, '_chartjs', { |
| 4617 |
configurable: true, |
| 4618 |
enumerable: false, |
| 4619 |
value: { |
| 4620 |
listeners: [listener] |
| 4621 |
} |
| 4622 |
}); |
| 4623 |
|
| 4624 |
arrayEvents.forEach(function(key) { |
| 4625 |
var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); |
| 4626 |
var base = array[key]; |
| 4627 |
|
| 4628 |
Object.defineProperty(array, key, { |
| 4629 |
configurable: true, |
| 4630 |
enumerable: false, |
| 4631 |
value: function() { |
| 4632 |
var args = Array.prototype.slice.call(arguments); |
| 4633 |
var res = base.apply(this, args); |
| 4634 |
|
| 4635 |
helpers.each(array._chartjs.listeners, function(object) { |
| 4636 |
if (typeof object[method] === 'function') { |
| 4637 |
object[method].apply(object, args); |
| 4638 |
} |
| 4639 |
}); |
| 4640 |
|
| 4641 |
return res; |
| 4642 |
} |
| 4643 |
}); |
| 4644 |
}); |
| 4645 |
} |
| 4646 |
|
| 4647 |
/** |
| 4648 |
* Removes the given array event listener and cleanup extra attached properties (such as |
| 4649 |
* the _chartjs stub and overridden methods) if array doesn't have any more listeners. |
| 4650 |
*/ |
| 4651 |
function unlistenArrayEvents(array, listener) { |
| 4652 |
var stub = array._chartjs; |
| 4653 |
if (!stub) { |
| 4654 |
return; |
| 4655 |
} |
| 4656 |
|
| 4657 |
var listeners = stub.listeners; |
| 4658 |
var index = listeners.indexOf(listener); |
| 4659 |
if (index !== -1) { |
| 4660 |
listeners.splice(index, 1); |
| 4661 |
} |
| 4662 |
|
| 4663 |
if (listeners.length > 0) { |
| 4664 |
return; |
| 4665 |
} |
| 4666 |
|
| 4667 |
arrayEvents.forEach(function(key) { |
| 4668 |
delete array[key]; |
| 4669 |
}); |
| 4670 |
|
| 4671 |
delete array._chartjs; |
| 4672 |
} |
| 4673 |
|
| 4674 |
// Base class for all dataset controllers (line, bar, etc) |
| 4675 |
Chart.DatasetController = function(chart, datasetIndex) { |
| 4676 |
this.initialize(chart, datasetIndex); |
| 4677 |
}; |
| 4678 |
|
| 4679 |
helpers.extend(Chart.DatasetController.prototype, { |
| 4680 |
|
| 4681 |
/** |
| 4682 |
* Element type used to generate a meta dataset (e.g. Chart.element.Line). |
| 4683 |
* @type {Chart.core.element} |
| 4684 |
*/ |
| 4685 |
datasetElementType: null, |
| 4686 |
|
| 4687 |
/** |
| 4688 |
* Element type used to generate a meta data (e.g. Chart.element.Point). |
| 4689 |
* @type {Chart.core.element} |
| 4690 |
*/ |
| 4691 |
dataElementType: null, |
| 4692 |
|
| 4693 |
initialize: function(chart, datasetIndex) { |
| 4694 |
var me = this; |
| 4695 |
me.chart = chart; |
| 4696 |
me.index = datasetIndex; |
| 4697 |
me.linkScales(); |
| 4698 |
me.addElements(); |
| 4699 |
}, |
| 4700 |
|
| 4701 |
updateIndex: function(datasetIndex) { |
| 4702 |
this.index = datasetIndex; |
| 4703 |
}, |
| 4704 |
|
| 4705 |
linkScales: function() { |
| 4706 |
var me = this; |
| 4707 |
var meta = me.getMeta(); |
| 4708 |
var dataset = me.getDataset(); |
| 4709 |
|
| 4710 |
if (meta.xAxisID === null) { |
| 4711 |
meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; |
| 4712 |
} |
| 4713 |
if (meta.yAxisID === null) { |
| 4714 |
meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; |
| 4715 |
} |
| 4716 |
}, |
| 4717 |
|
| 4718 |
getDataset: function() { |
| 4719 |
return this.chart.data.datasets[this.index]; |
| 4720 |
}, |
| 4721 |
|
| 4722 |
getMeta: function() { |
| 4723 |
return this.chart.getDatasetMeta(this.index); |
| 4724 |
}, |
| 4725 |
|
| 4726 |
getScaleForId: function(scaleID) { |
| 4727 |
return this.chart.scales[scaleID]; |
| 4728 |
}, |
| 4729 |
|
| 4730 |
reset: function() { |
| 4731 |
this.update(true); |
| 4732 |
}, |
| 4733 |
|
| 4734 |
/** |
| 4735 |
* @private |
| 4736 |
*/ |
| 4737 |
destroy: function() { |
| 4738 |
if (this._data) { |
| 4739 |
unlistenArrayEvents(this._data, this); |
| 4740 |
} |
| 4741 |
}, |
| 4742 |
|
| 4743 |
createMetaDataset: function() { |
| 4744 |
var me = this; |
| 4745 |
var type = me.datasetElementType; |
| 4746 |
return type && new type({ |
| 4747 |
_chart: me.chart, |
| 4748 |
_datasetIndex: me.index |
| 4749 |
}); |
| 4750 |
}, |
| 4751 |
|
| 4752 |
createMetaData: function(index) { |
| 4753 |
var me = this; |
| 4754 |
var type = me.dataElementType; |
| 4755 |
return type && new type({ |
| 4756 |
_chart: me.chart, |
| 4757 |
_datasetIndex: me.index, |
| 4758 |
_index: index |
| 4759 |
}); |
| 4760 |
}, |
| 4761 |
|
| 4762 |
addElements: function() { |
| 4763 |
var me = this; |
| 4764 |
var meta = me.getMeta(); |
| 4765 |
var data = me.getDataset().data || []; |
| 4766 |
var metaData = meta.data; |
| 4767 |
var i, ilen; |
| 4768 |
|
| 4769 |
for (i = 0, ilen = data.length; i < ilen; ++i) { |
| 4770 |
metaData[i] = metaData[i] || me.createMetaData(i); |
| 4771 |
} |
| 4772 |
|
| 4773 |
meta.dataset = meta.dataset || me.createMetaDataset(); |
| 4774 |
}, |
| 4775 |
|
| 4776 |
addElementAndReset: function(index) { |
| 4777 |
var element = this.createMetaData(index); |
| 4778 |
this.getMeta().data.splice(index, 0, element); |
| 4779 |
this.updateElement(element, index, true); |
| 4780 |
}, |
| 4781 |
|
| 4782 |
buildOrUpdateElements: function() { |
| 4783 |
var me = this; |
| 4784 |
var dataset = me.getDataset(); |
| 4785 |
var data = dataset.data || (dataset.data = []); |
| 4786 |
|
| 4787 |
// In order to correctly handle data addition/deletion animation (an thus simulate |
| 4788 |
// real-time charts), we need to monitor these data modifications and synchronize |
| 4789 |
// the internal meta data accordingly. |
| 4790 |
if (me._data !== data) { |
| 4791 |
if (me._data) { |
| 4792 |
// This case happens when the user replaced the data array instance. |
| 4793 |
unlistenArrayEvents(me._data, me); |
| 4794 |
} |
| 4795 |
|
| 4796 |
listenArrayEvents(data, me); |
| 4797 |
me._data = data; |
| 4798 |
} |
| 4799 |
|
| 4800 |
// Re-sync meta data in case the user replaced the data array or if we missed |
| 4801 |
// any updates and so make sure that we handle number of datapoints changing. |
| 4802 |
me.resyncElements(); |
| 4803 |
}, |
| 4804 |
|
| 4805 |
update: helpers.noop, |
| 4806 |
|
| 4807 |
transition: function(easingValue) { |
| 4808 |
var meta = this.getMeta(); |
| 4809 |
var elements = meta.data || []; |
| 4810 |
var ilen = elements.length; |
| 4811 |
var i = 0; |
| 4812 |
|
| 4813 |
for (; i < ilen; ++i) { |
| 4814 |
elements[i].transition(easingValue); |
| 4815 |
} |
| 4816 |
|
| 4817 |
if (meta.dataset) { |
| 4818 |
meta.dataset.transition(easingValue); |
| 4819 |
} |
| 4820 |
}, |
| 4821 |
|
| 4822 |
draw: function() { |
| 4823 |
var meta = this.getMeta(); |
| 4824 |
var elements = meta.data || []; |
| 4825 |
var ilen = elements.length; |
| 4826 |
var i = 0; |
| 4827 |
|
| 4828 |
if (meta.dataset) { |
| 4829 |
meta.dataset.draw(); |
| 4830 |
} |
| 4831 |
|
| 4832 |
for (; i < ilen; ++i) { |
| 4833 |
elements[i].draw(); |
| 4834 |
} |
| 4835 |
}, |
| 4836 |
|
| 4837 |
removeHoverStyle: function(element, elementOpts) { |
| 4838 |
var dataset = this.chart.data.datasets[element._datasetIndex]; |
| 4839 |
var index = element._index; |
| 4840 |
var custom = element.custom || {}; |
| 4841 |
var valueOrDefault = helpers.valueAtIndexOrDefault; |
| 4842 |
var model = element._model; |
| 4843 |
|
| 4844 |
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); |
| 4845 |
model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); |
| 4846 |
model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); |
| 4847 |
}, |
| 4848 |
|
| 4849 |
setHoverStyle: function(element) { |
| 4850 |
var dataset = this.chart.data.datasets[element._datasetIndex]; |
| 4851 |
var index = element._index; |
| 4852 |
var custom = element.custom || {}; |
| 4853 |
var valueOrDefault = helpers.valueAtIndexOrDefault; |
| 4854 |
var getHoverColor = helpers.getHoverColor; |
| 4855 |
var model = element._model; |
| 4856 |
|
| 4857 |
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); |
| 4858 |
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); |
| 4859 |
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); |
| 4860 |
}, |
| 4861 |
|
| 4862 |
/** |
| 4863 |
* @private |
| 4864 |
*/ |
| 4865 |
resyncElements: function() { |
| 4866 |
var me = this; |
| 4867 |
var meta = me.getMeta(); |
| 4868 |
var data = me.getDataset().data; |
| 4869 |
var numMeta = meta.data.length; |
| 4870 |
var numData = data.length; |
| 4871 |
|
| 4872 |
if (numData < numMeta) { |
| 4873 |
meta.data.splice(numData, numMeta - numData); |
| 4874 |
} else if (numData > numMeta) { |
| 4875 |
me.insertElements(numMeta, numData - numMeta); |
| 4876 |
} |
| 4877 |
}, |
| 4878 |
|
| 4879 |
/** |
| 4880 |
* @private |
| 4881 |
*/ |
| 4882 |
insertElements: function(start, count) { |
| 4883 |
for (var i = 0; i < count; ++i) { |
| 4884 |
this.addElementAndReset(start + i); |
| 4885 |
} |
| 4886 |
}, |
| 4887 |
|
| 4888 |
/** |
| 4889 |
* @private |
| 4890 |
*/ |
| 4891 |
onDataPush: function() { |
| 4892 |
this.insertElements(this.getDataset().data.length - 1, arguments.length); |
| 4893 |
}, |
| 4894 |
|
| 4895 |
/** |
| 4896 |
* @private |
| 4897 |
*/ |
| 4898 |
onDataPop: function() { |
| 4899 |
this.getMeta().data.pop(); |
| 4900 |
}, |
| 4901 |
|
| 4902 |
/** |
| 4903 |
* @private |
| 4904 |
*/ |
| 4905 |
onDataShift: function() { |
| 4906 |
this.getMeta().data.shift(); |
| 4907 |
}, |
| 4908 |
|
| 4909 |
/** |
| 4910 |
* @private |
| 4911 |
*/ |
| 4912 |
onDataSplice: function(start, count) { |
| 4913 |
this.getMeta().data.splice(start, count); |
| 4914 |
this.insertElements(start, arguments.length - 2); |
| 4915 |
}, |
| 4916 |
|
| 4917 |
/** |
| 4918 |
* @private |
| 4919 |
*/ |
| 4920 |
onDataUnshift: function() { |
| 4921 |
this.insertElements(0, arguments.length); |
| 4922 |
} |
| 4923 |
}); |
| 4924 |
|
| 4925 |
Chart.DatasetController.extend = helpers.inherits; |
| 4926 |
}; |
| 4927 |
|
| 4928 |
},{"45":45}],25:[function(require,module,exports){ |
| 4929 |
'use strict'; |
| 4930 |
|
| 4931 |
var helpers = require(45); |
| 4932 |
|
| 4933 |
module.exports = { |
| 4934 |
/** |
| 4935 |
* @private |
| 4936 |
*/ |
| 4937 |
_set: function(scope, values) { |
| 4938 |
return helpers.merge(this[scope] || (this[scope] = {}), values); |
| 4939 |
} |
| 4940 |
}; |
| 4941 |
|
| 4942 |
},{"45":45}],26:[function(require,module,exports){ |
| 4943 |
'use strict'; |
| 4944 |
|
| 4945 |
var color = require(3); |
| 4946 |
var helpers = require(45); |
| 4947 |
|
| 4948 |
function interpolate(start, view, model, ease) { |
| 4949 |
var keys = Object.keys(model); |
| 4950 |
var i, ilen, key, actual, origin, target, type, c0, c1; |
| 4951 |
|
| 4952 |
for (i = 0, ilen = keys.length; i < ilen; ++i) { |
| 4953 |
key = keys[i]; |
| 4954 |
|
| 4955 |
target = model[key]; |
| 4956 |
|
| 4957 |
// if a value is added to the model after pivot() has been called, the view |
| 4958 |
// doesn't contain it, so let's initialize the view to the target value. |
| 4959 |
if (!view.hasOwnProperty(key)) { |
| 4960 |
view[key] = target; |
| 4961 |
} |
| 4962 |
|
| 4963 |
actual = view[key]; |
| 4964 |
|
| 4965 |
if (actual === target || key[0] === '_') { |
| 4966 |
continue; |
| 4967 |
} |
| 4968 |
|
| 4969 |
if (!start.hasOwnProperty(key)) { |
| 4970 |
start[key] = actual; |
| 4971 |
} |
| 4972 |
|
| 4973 |
origin = start[key]; |
| 4974 |
|
| 4975 |
type = typeof target; |
| 4976 |
|
| 4977 |
if (type === typeof origin) { |
| 4978 |
if (type === 'string') { |
| 4979 |
c0 = color(origin); |
| 4980 |
if (c0.valid) { |
| 4981 |
c1 = color(target); |
| 4982 |
if (c1.valid) { |
| 4983 |
view[key] = c1.mix(c0, ease).rgbString(); |
| 4984 |
continue; |
| 4985 |
} |
| 4986 |
} |
| 4987 |
} else if (type === 'number' && isFinite(origin) && isFinite(target)) { |
| 4988 |
view[key] = origin + (target - origin) * ease; |
| 4989 |
continue; |
| 4990 |
} |
| 4991 |
} |
| 4992 |
|
| 4993 |
view[key] = target; |
| 4994 |
} |
| 4995 |
} |
| 4996 |
|
| 4997 |
var Element = function(configuration) { |
| 4998 |
helpers.extend(this, configuration); |
| 4999 |
this.initialize.apply(this, arguments); |
| 5000 |
}; |
| 5001 |
|
| 5002 |
helpers.extend(Element.prototype, { |
| 5003 |
|
| 5004 |
initialize: function() { |
| 5005 |
this.hidden = false; |
| 5006 |
}, |
| 5007 |
|
| 5008 |
pivot: function() { |
| 5009 |
var me = this; |
| 5010 |
if (!me._view) { |
| 5011 |
me._view = helpers.clone(me._model); |
| 5012 |
} |
| 5013 |
me._start = {}; |
| 5014 |
return me; |
| 5015 |
}, |
| 5016 |
|
| 5017 |
transition: function(ease) { |
| 5018 |
var me = this; |
| 5019 |
var model = me._model; |
| 5020 |
var start = me._start; |
| 5021 |
var view = me._view; |
| 5022 |
|
| 5023 |
// No animation -> No Transition |
| 5024 |
if (!model || ease === 1) { |
| 5025 |
me._view = model; |
| 5026 |
me._start = null; |
| 5027 |
return me; |
| 5028 |
} |
| 5029 |
|
| 5030 |
if (!view) { |
| 5031 |
view = me._view = {}; |
| 5032 |
} |
| 5033 |
|
| 5034 |
if (!start) { |
| 5035 |
start = me._start = {}; |
| 5036 |
} |
| 5037 |
|
| 5038 |
interpolate(start, view, model, ease); |
| 5039 |
|
| 5040 |
return me; |
| 5041 |
}, |
| 5042 |
|
| 5043 |
tooltipPosition: function() { |
| 5044 |
return { |
| 5045 |
x: this._model.x, |
| 5046 |
y: this._model.y |
| 5047 |
}; |
| 5048 |
}, |
| 5049 |
|
| 5050 |
hasValue: function() { |
| 5051 |
return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); |
| 5052 |
} |
| 5053 |
}); |
| 5054 |
|
| 5055 |
Element.extend = helpers.inherits; |
| 5056 |
|
| 5057 |
module.exports = Element; |
| 5058 |
|
| 5059 |
},{"3":3,"45":45}],27:[function(require,module,exports){ |
| 5060 |
/* global window: false */ |
| 5061 |
/* global document: false */ |
| 5062 |
'use strict'; |
| 5063 |
|
| 5064 |
var color = require(3); |
| 5065 |
var defaults = require(25); |
| 5066 |
var helpers = require(45); |
| 5067 |
|
| 5068 |
module.exports = function(Chart) { |
| 5069 |
|
| 5070 |
// -- Basic js utility methods |
| 5071 |
|
| 5072 |
helpers.configMerge = function(/* objects ... */) { |
| 5073 |
return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { |
| 5074 |
merger: function(key, target, source, options) { |
| 5075 |
var tval = target[key] || {}; |
| 5076 |
var sval = source[key]; |
| 5077 |
|
| 5078 |
if (key === 'scales') { |
| 5079 |
// scale config merging is complex. Add our own function here for that |
| 5080 |
target[key] = helpers.scaleMerge(tval, sval); |
| 5081 |
} else if (key === 'scale') { |
| 5082 |
// used in polar area & radar charts since there is only one scale |
| 5083 |
target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]); |
| 5084 |
} else { |
| 5085 |
helpers._merger(key, target, source, options); |
| 5086 |
} |
| 5087 |
} |
| 5088 |
}); |
| 5089 |
}; |
| 5090 |
|
| 5091 |
helpers.scaleMerge = function(/* objects ... */) { |
| 5092 |
return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { |
| 5093 |
merger: function(key, target, source, options) { |
| 5094 |
if (key === 'xAxes' || key === 'yAxes') { |
| 5095 |
var slen = source[key].length; |
| 5096 |
var i, type, scale; |
| 5097 |
|
| 5098 |
if (!target[key]) { |
| 5099 |
target[key] = []; |
| 5100 |
} |
| 5101 |
|
| 5102 |
for (i = 0; i < slen; ++i) { |
| 5103 |
scale = source[key][i]; |
| 5104 |
type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear'); |
| 5105 |
|
| 5106 |
if (i >= target[key].length) { |
| 5107 |
target[key].push({}); |
| 5108 |
} |
| 5109 |
|
| 5110 |
if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { |
| 5111 |
// new/untyped scale or type changed: let's apply the new defaults |
| 5112 |
// then merge source scale to correctly overwrite the defaults. |
| 5113 |
helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]); |
| 5114 |
} else { |
| 5115 |
// scales type are the same |
| 5116 |
helpers.merge(target[key][i], scale); |
| 5117 |
} |
| 5118 |
} |
| 5119 |
} else { |
| 5120 |
helpers._merger(key, target, source, options); |
| 5121 |
} |
| 5122 |
} |
| 5123 |
}); |
| 5124 |
}; |
| 5125 |
|
| 5126 |
helpers.where = function(collection, filterCallback) { |
| 5127 |
if (helpers.isArray(collection) && Array.prototype.filter) { |
| 5128 |
return collection.filter(filterCallback); |
| 5129 |
} |
| 5130 |
var filtered = []; |
| 5131 |
|
| 5132 |
helpers.each(collection, function(item) { |
| 5133 |
if (filterCallback(item)) { |
| 5134 |
filtered.push(item); |
| 5135 |
} |
| 5136 |
}); |
| 5137 |
|
| 5138 |
return filtered; |
| 5139 |
}; |
| 5140 |
helpers.findIndex = Array.prototype.findIndex ? |
| 5141 |
function(array, callback, scope) { |
| 5142 |
return array.findIndex(callback, scope); |
| 5143 |
} : |
| 5144 |
function(array, callback, scope) { |
| 5145 |
scope = scope === undefined ? array : scope; |
| 5146 |
for (var i = 0, ilen = array.length; i < ilen; ++i) { |
| 5147 |
if (callback.call(scope, array[i], i, array)) { |
| 5148 |
return i; |
| 5149 |
} |
| 5150 |
} |
| 5151 |
return -1; |
| 5152 |
}; |
| 5153 |
helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { |
| 5154 |
// Default to start of the array |
| 5155 |
if (helpers.isNullOrUndef(startIndex)) { |
| 5156 |
startIndex = -1; |
| 5157 |
} |
| 5158 |
for (var i = startIndex + 1; i < arrayToSearch.length; i++) { |
| 5159 |
var currentItem = arrayToSearch[i]; |
| 5160 |
if (filterCallback(currentItem)) { |
| 5161 |
return currentItem; |
| 5162 |
} |
| 5163 |
} |
| 5164 |
}; |
| 5165 |
helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { |
| 5166 |
// Default to end of the array |
| 5167 |
if (helpers.isNullOrUndef(startIndex)) { |
| 5168 |
startIndex = arrayToSearch.length; |
| 5169 |
} |
| 5170 |
for (var i = startIndex - 1; i >= 0; i--) { |
| 5171 |
var currentItem = arrayToSearch[i]; |
| 5172 |
if (filterCallback(currentItem)) { |
| 5173 |
return currentItem; |
| 5174 |
} |
| 5175 |
} |
| 5176 |
}; |
| 5177 |
|
| 5178 |
// -- Math methods |
| 5179 |
helpers.isNumber = function(n) { |
| 5180 |
return !isNaN(parseFloat(n)) && isFinite(n); |
| 5181 |
}; |
| 5182 |
helpers.almostEquals = function(x, y, epsilon) { |
| 5183 |
return Math.abs(x - y) < epsilon; |
| 5184 |
}; |
| 5185 |
helpers.almostWhole = function(x, epsilon) { |
| 5186 |
var rounded = Math.round(x); |
| 5187 |
return (((rounded - epsilon) < x) && ((rounded + epsilon) > x)); |
| 5188 |
}; |
| 5189 |
helpers.max = function(array) { |
| 5190 |
return array.reduce(function(max, value) { |
| 5191 |
if (!isNaN(value)) { |
| 5192 |
return Math.max(max, value); |
| 5193 |
} |
| 5194 |
return max; |
| 5195 |
}, Number.NEGATIVE_INFINITY); |
| 5196 |
}; |
| 5197 |
helpers.min = function(array) { |
| 5198 |
return array.reduce(function(min, value) { |
| 5199 |
if (!isNaN(value)) { |
| 5200 |
return Math.min(min, value); |
| 5201 |
} |
| 5202 |
return min; |
| 5203 |
}, Number.POSITIVE_INFINITY); |
| 5204 |
}; |
| 5205 |
helpers.sign = Math.sign ? |
| 5206 |
function(x) { |
| 5207 |
return Math.sign(x); |
| 5208 |
} : |
| 5209 |
function(x) { |
| 5210 |
x = +x; // convert to a number |
| 5211 |
if (x === 0 || isNaN(x)) { |
| 5212 |
return x; |
| 5213 |
} |
| 5214 |
return x > 0 ? 1 : -1; |
| 5215 |
}; |
| 5216 |
helpers.log10 = Math.log10 ? |
| 5217 |
function(x) { |
| 5218 |
return Math.log10(x); |
| 5219 |
} : |
| 5220 |
function(x) { |
| 5221 |
return Math.log(x) / Math.LN10; |
| 5222 |
}; |
| 5223 |
helpers.toRadians = function(degrees) { |
| 5224 |
return degrees * (Math.PI / 180); |
| 5225 |
}; |
| 5226 |
helpers.toDegrees = function(radians) { |
| 5227 |
return radians * (180 / Math.PI); |
| 5228 |
}; |
| 5229 |
// Gets the angle from vertical upright to the point about a centre. |
| 5230 |
helpers.getAngleFromPoint = function(centrePoint, anglePoint) { |
| 5231 |
var distanceFromXCenter = anglePoint.x - centrePoint.x; |
| 5232 |
var distanceFromYCenter = anglePoint.y - centrePoint.y; |
| 5233 |
var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); |
| 5234 |
|
| 5235 |
var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); |
| 5236 |
|
| 5237 |
if (angle < (-0.5 * Math.PI)) { |
| 5238 |
angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] |
| 5239 |
} |
| 5240 |
|
| 5241 |
return { |
| 5242 |
angle: angle, |
| 5243 |
distance: radialDistanceFromCenter |
| 5244 |
}; |
| 5245 |
}; |
| 5246 |
helpers.distanceBetweenPoints = function(pt1, pt2) { |
| 5247 |
return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); |
| 5248 |
}; |
| 5249 |
helpers.aliasPixel = function(pixelWidth) { |
| 5250 |
return (pixelWidth % 2 === 0) ? 0 : 0.5; |
| 5251 |
}; |
| 5252 |
helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { |
| 5253 |
// Props to Rob Spencer at scaled innovation for his post on splining between points |
| 5254 |
// http://scaledinnovation.com/analytics/splines/aboutSplines.html |
| 5255 |
|
| 5256 |
// This function must also respect "skipped" points |
| 5257 |
|
| 5258 |
var previous = firstPoint.skip ? middlePoint : firstPoint; |
| 5259 |
var current = middlePoint; |
| 5260 |
var next = afterPoint.skip ? middlePoint : afterPoint; |
| 5261 |
|
| 5262 |
var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); |
| 5263 |
var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); |
| 5264 |
|
| 5265 |
var s01 = d01 / (d01 + d12); |
| 5266 |
var s12 = d12 / (d01 + d12); |
| 5267 |
|
| 5268 |
// If all points are the same, s01 & s02 will be inf |
| 5269 |
s01 = isNaN(s01) ? 0 : s01; |
| 5270 |
s12 = isNaN(s12) ? 0 : s12; |
| 5271 |
|
| 5272 |
var fa = t * s01; // scaling factor for triangle Ta |
| 5273 |
var fb = t * s12; |
| 5274 |
|
| 5275 |
return { |
| 5276 |
previous: { |
| 5277 |
x: current.x - fa * (next.x - previous.x), |
| 5278 |
y: current.y - fa * (next.y - previous.y) |
| 5279 |
}, |
| 5280 |
next: { |
| 5281 |
x: current.x + fb * (next.x - previous.x), |
| 5282 |
y: current.y + fb * (next.y - previous.y) |
| 5283 |
} |
| 5284 |
}; |
| 5285 |
}; |
| 5286 |
helpers.EPSILON = Number.EPSILON || 1e-14; |
| 5287 |
helpers.splineCurveMonotone = function(points) { |
| 5288 |
// This function calculates Bézier control points in a similar way than |splineCurve|, |
| 5289 |
// but preserves monotonicity of the provided data and ensures no local extremums are added |
| 5290 |
// between the dataset discrete points due to the interpolation. |
| 5291 |
// See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation |
| 5292 |
|
| 5293 |
var pointsWithTangents = (points || []).map(function(point) { |
| 5294 |
return { |
| 5295 |
model: point._model, |
| 5296 |
deltaK: 0, |
| 5297 |
mK: 0 |
| 5298 |
}; |
| 5299 |
}); |
| 5300 |
|
| 5301 |
// Calculate slopes (deltaK) and initialize tangents (mK) |
| 5302 |
var pointsLen = pointsWithTangents.length; |
| 5303 |
var i, pointBefore, pointCurrent, pointAfter; |
| 5304 |
for (i = 0; i < pointsLen; ++i) { |
| 5305 |
pointCurrent = pointsWithTangents[i]; |
| 5306 |
if (pointCurrent.model.skip) { |
| 5307 |
continue; |
| 5308 |
} |
| 5309 |
|
| 5310 |
pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; |
| 5311 |
pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; |
| 5312 |
if (pointAfter && !pointAfter.model.skip) { |
| 5313 |
var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); |
| 5314 |
|
| 5315 |
// In the case of two points that appear at the same x pixel, slopeDeltaX is 0 |
| 5316 |
pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; |
| 5317 |
} |
| 5318 |
|
| 5319 |
if (!pointBefore || pointBefore.model.skip) { |
| 5320 |
pointCurrent.mK = pointCurrent.deltaK; |
| 5321 |
} else if (!pointAfter || pointAfter.model.skip) { |
| 5322 |
pointCurrent.mK = pointBefore.deltaK; |
| 5323 |
} else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { |
| 5324 |
pointCurrent.mK = 0; |
| 5325 |
} else { |
| 5326 |
pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; |
| 5327 |
} |
| 5328 |
} |
| 5329 |
|
| 5330 |
// Adjust tangents to ensure monotonic properties |
| 5331 |
var alphaK, betaK, tauK, squaredMagnitude; |
| 5332 |
for (i = 0; i < pointsLen - 1; ++i) { |
| 5333 |
pointCurrent = pointsWithTangents[i]; |
| 5334 |
pointAfter = pointsWithTangents[i + 1]; |
| 5335 |
if (pointCurrent.model.skip || pointAfter.model.skip) { |
| 5336 |
continue; |
| 5337 |
} |
| 5338 |
|
| 5339 |
if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { |
| 5340 |
pointCurrent.mK = pointAfter.mK = 0; |
| 5341 |
continue; |
| 5342 |
} |
| 5343 |
|
| 5344 |
alphaK = pointCurrent.mK / pointCurrent.deltaK; |
| 5345 |
betaK = pointAfter.mK / pointCurrent.deltaK; |
| 5346 |
squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); |
| 5347 |
if (squaredMagnitude <= 9) { |
| 5348 |
continue; |
| 5349 |
} |
| 5350 |
|
| 5351 |
tauK = 3 / Math.sqrt(squaredMagnitude); |
| 5352 |
pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; |
| 5353 |
pointAfter.mK = betaK * tauK * pointCurrent.deltaK; |
| 5354 |
} |
| 5355 |
|
| 5356 |
// Compute control points |
| 5357 |
var deltaX; |
| 5358 |
for (i = 0; i < pointsLen; ++i) { |
| 5359 |
pointCurrent = pointsWithTangents[i]; |
| 5360 |
if (pointCurrent.model.skip) { |
| 5361 |
continue; |
| 5362 |
} |
| 5363 |
|
| 5364 |
pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; |
| 5365 |
pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; |
| 5366 |
if (pointBefore && !pointBefore.model.skip) { |
| 5367 |
deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; |
| 5368 |
pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; |
| 5369 |
pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; |
| 5370 |
} |
| 5371 |
if (pointAfter && !pointAfter.model.skip) { |
| 5372 |
deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; |
| 5373 |
pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; |
| 5374 |
pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; |
| 5375 |
} |
| 5376 |
} |
| 5377 |
}; |
| 5378 |
helpers.nextItem = function(collection, index, loop) { |
| 5379 |
if (loop) { |
| 5380 |
return index >= collection.length - 1 ? collection[0] : collection[index + 1]; |
| 5381 |
} |
| 5382 |
return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; |
| 5383 |
}; |
| 5384 |
helpers.previousItem = function(collection, index, loop) { |
| 5385 |
if (loop) { |
| 5386 |
return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; |
| 5387 |
} |
| 5388 |
return index <= 0 ? collection[0] : collection[index - 1]; |
| 5389 |
}; |
| 5390 |
// Implementation of the nice number algorithm used in determining where axis labels will go |
| 5391 |
helpers.niceNum = function(range, round) { |
| 5392 |
var exponent = Math.floor(helpers.log10(range)); |
| 5393 |
var fraction = range / Math.pow(10, exponent); |
| 5394 |
var niceFraction; |
| 5395 |
|
| 5396 |
if (round) { |
| 5397 |
if (fraction < 1.5) { |
| 5398 |
niceFraction = 1; |
| 5399 |
} else if (fraction < 3) { |
| 5400 |
niceFraction = 2; |
| 5401 |
} else if (fraction < 7) { |
| 5402 |
niceFraction = 5; |
| 5403 |
} else { |
| 5404 |
niceFraction = 10; |
| 5405 |
} |
| 5406 |
} else if (fraction <= 1.0) { |
| 5407 |
niceFraction = 1; |
| 5408 |
} else if (fraction <= 2) { |
| 5409 |
niceFraction = 2; |
| 5410 |
} else if (fraction <= 5) { |
| 5411 |
niceFraction = 5; |
| 5412 |
} else { |
| 5413 |
niceFraction = 10; |
| 5414 |
} |
| 5415 |
|
| 5416 |
return niceFraction * Math.pow(10, exponent); |
| 5417 |
}; |
| 5418 |
// Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ |
| 5419 |
helpers.requestAnimFrame = (function() { |
| 5420 |
if (typeof window === 'undefined') { |
| 5421 |
return function(callback) { |
| 5422 |
callback(); |
| 5423 |
}; |
| 5424 |
} |
| 5425 |
return window.requestAnimationFrame || |
| 5426 |
window.webkitRequestAnimationFrame || |
| 5427 |
window.mozRequestAnimationFrame || |
| 5428 |
window.oRequestAnimationFrame || |
| 5429 |
window.msRequestAnimationFrame || |
| 5430 |
function(callback) { |
| 5431 |
return window.setTimeout(callback, 1000 / 60); |
| 5432 |
}; |
| 5433 |
}()); |
| 5434 |
// -- DOM methods |
| 5435 |
helpers.getRelativePosition = function(evt, chart) { |
| 5436 |
var mouseX, mouseY; |
| 5437 |
var e = evt.originalEvent || evt; |
| 5438 |
var canvas = evt.currentTarget || evt.srcElement; |
| 5439 |
var boundingRect = canvas.getBoundingClientRect(); |
| 5440 |
|
| 5441 |
var touches = e.touches; |
| 5442 |
if (touches && touches.length > 0) { |
| 5443 |
mouseX = touches[0].clientX; |
| 5444 |
mouseY = touches[0].clientY; |
| 5445 |
|
| 5446 |
} else { |
| 5447 |
mouseX = e.clientX; |
| 5448 |
mouseY = e.clientY; |
| 5449 |
} |
| 5450 |
|
| 5451 |
// Scale mouse coordinates into canvas coordinates |
| 5452 |
// by following the pattern laid out by 'jerryj' in the comments of |
| 5453 |
// http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ |
| 5454 |
var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); |
| 5455 |
var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); |
| 5456 |
var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); |
| 5457 |
var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); |
| 5458 |
var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; |
| 5459 |
var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; |
| 5460 |
|
| 5461 |
// We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However |
| 5462 |
// the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here |
| 5463 |
mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); |
| 5464 |
mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); |
| 5465 |
|
| 5466 |
return { |
| 5467 |
x: mouseX, |
| 5468 |
y: mouseY |
| 5469 |
}; |
| 5470 |
|
| 5471 |
}; |
| 5472 |
|
| 5473 |
// Private helper function to convert max-width/max-height values that may be percentages into a number |
| 5474 |
function parseMaxStyle(styleValue, node, parentProperty) { |
| 5475 |
var valueInPixels; |
| 5476 |
if (typeof styleValue === 'string') { |
| 5477 |
valueInPixels = parseInt(styleValue, 10); |
| 5478 |
|
| 5479 |
if (styleValue.indexOf('%') !== -1) { |
| 5480 |
// percentage * size in dimension |
| 5481 |
valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; |
| 5482 |
} |
| 5483 |
} else { |
| 5484 |
valueInPixels = styleValue; |
| 5485 |
} |
| 5486 |
|
| 5487 |
return valueInPixels; |
| 5488 |
} |
| 5489 |
|
| 5490 |
/** |
| 5491 |
* Returns if the given value contains an effective constraint. |
| 5492 |
* @private |
| 5493 |
*/ |
| 5494 |
function isConstrainedValue(value) { |
| 5495 |
return value !== undefined && value !== null && value !== 'none'; |
| 5496 |
} |
| 5497 |
|
| 5498 |
// Private helper to get a constraint dimension |
| 5499 |
// @param domNode : the node to check the constraint on |
| 5500 |
// @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) |
| 5501 |
// @param percentageProperty : property of parent to use when calculating width as a percentage |
| 5502 |
// @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser |
| 5503 |
function getConstraintDimension(domNode, maxStyle, percentageProperty) { |
| 5504 |
var view = document.defaultView; |
| 5505 |
var parentNode = domNode.parentNode; |
| 5506 |
var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; |
| 5507 |
var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; |
| 5508 |
var hasCNode = isConstrainedValue(constrainedNode); |
| 5509 |
var hasCContainer = isConstrainedValue(constrainedContainer); |
| 5510 |
var infinity = Number.POSITIVE_INFINITY; |
| 5511 |
|
| 5512 |
if (hasCNode || hasCContainer) { |
| 5513 |
return Math.min( |
| 5514 |
hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, |
| 5515 |
hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); |
| 5516 |
} |
| 5517 |
|
| 5518 |
return 'none'; |
| 5519 |
} |
| 5520 |
// returns Number or undefined if no constraint |
| 5521 |
helpers.getConstraintWidth = function(domNode) { |
| 5522 |
return getConstraintDimension(domNode, 'max-width', 'clientWidth'); |
| 5523 |
}; |
| 5524 |
// returns Number or undefined if no constraint |
| 5525 |
helpers.getConstraintHeight = function(domNode) { |
| 5526 |
return getConstraintDimension(domNode, 'max-height', 'clientHeight'); |
| 5527 |
}; |
| 5528 |
helpers.getMaximumWidth = function(domNode) { |
| 5529 |
var container = domNode.parentNode; |
| 5530 |
if (!container) { |
| 5531 |
return domNode.clientWidth; |
| 5532 |
} |
| 5533 |
|
| 5534 |
var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10); |
| 5535 |
var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10); |
| 5536 |
var w = container.clientWidth - paddingLeft - paddingRight; |
| 5537 |
var cw = helpers.getConstraintWidth(domNode); |
| 5538 |
return isNaN(cw) ? w : Math.min(w, cw); |
| 5539 |
}; |
| 5540 |
helpers.getMaximumHeight = function(domNode) { |
| 5541 |
var container = domNode.parentNode; |
| 5542 |
if (!container) { |
| 5543 |
return domNode.clientHeight; |
| 5544 |
} |
| 5545 |
|
| 5546 |
var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10); |
| 5547 |
var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10); |
| 5548 |
var h = container.clientHeight - paddingTop - paddingBottom; |
| 5549 |
var ch = helpers.getConstraintHeight(domNode); |
| 5550 |
return isNaN(ch) ? h : Math.min(h, ch); |
| 5551 |
}; |
| 5552 |
helpers.getStyle = function(el, property) { |
| 5553 |
return el.currentStyle ? |
| 5554 |
el.currentStyle[property] : |
| 5555 |
document.defaultView.getComputedStyle(el, null).getPropertyValue(property); |
| 5556 |
}; |
| 5557 |
helpers.retinaScale = function(chart, forceRatio) { |
| 5558 |
var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1; |
| 5559 |
if (pixelRatio === 1) { |
| 5560 |
return; |
| 5561 |
} |
| 5562 |
|
| 5563 |
var canvas = chart.canvas; |
| 5564 |
var height = chart.height; |
| 5565 |
var width = chart.width; |
| 5566 |
|
| 5567 |
canvas.height = height * pixelRatio; |
| 5568 |
canvas.width = width * pixelRatio; |
| 5569 |
chart.ctx.scale(pixelRatio, pixelRatio); |
| 5570 |
|
| 5571 |
// If no style has been set on the canvas, the render size is used as display size, |
| 5572 |
// making the chart visually bigger, so let's enforce it to the "correct" values. |
| 5573 |
// See https://github.com/chartjs/Chart.js/issues/3575 |
| 5574 |
canvas.style.height = height + 'px'; |
| 5575 |
canvas.style.width = width + 'px'; |
| 5576 |
}; |
| 5577 |
// -- Canvas methods |
| 5578 |
helpers.fontString = function(pixelSize, fontStyle, fontFamily) { |
| 5579 |
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; |
| 5580 |
}; |
| 5581 |
helpers.longestText = function(ctx, font, arrayOfThings, cache) { |
| 5582 |
cache = cache || {}; |
| 5583 |
var data = cache.data = cache.data || {}; |
| 5584 |
var gc = cache.garbageCollect = cache.garbageCollect || []; |
| 5585 |
|
| 5586 |
if (cache.font !== font) { |
| 5587 |
data = cache.data = {}; |
| 5588 |
gc = cache.garbageCollect = []; |
| 5589 |
cache.font = font; |
| 5590 |
} |
| 5591 |
|
| 5592 |
ctx.font = font; |
| 5593 |
var longest = 0; |
| 5594 |
helpers.each(arrayOfThings, function(thing) { |
| 5595 |
// Undefined strings and arrays should not be measured |
| 5596 |
if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { |
| 5597 |
longest = helpers.measureText(ctx, data, gc, longest, thing); |
| 5598 |
} else if (helpers.isArray(thing)) { |
| 5599 |
// if it is an array lets measure each element |
| 5600 |
// to do maybe simplify this function a bit so we can do this more recursively? |
| 5601 |
helpers.each(thing, function(nestedThing) { |
| 5602 |
// Undefined strings and arrays should not be measured |
| 5603 |
if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { |
| 5604 |
longest = helpers.measureText(ctx, data, gc, longest, nestedThing); |
| 5605 |
} |
| 5606 |
}); |
| 5607 |
} |
| 5608 |
}); |
| 5609 |
|
| 5610 |
var gcLen = gc.length / 2; |
| 5611 |
if (gcLen > arrayOfThings.length) { |
| 5612 |
for (var i = 0; i < gcLen; i++) { |
| 5613 |
delete data[gc[i]]; |
| 5614 |
} |
| 5615 |
gc.splice(0, gcLen); |
| 5616 |
} |
| 5617 |
return longest; |
| 5618 |
}; |
| 5619 |
helpers.measureText = function(ctx, data, gc, longest, string) { |
| 5620 |
var textWidth = data[string]; |
| 5621 |
if (!textWidth) { |
| 5622 |
textWidth = data[string] = ctx.measureText(string).width; |
| 5623 |
gc.push(string); |
| 5624 |
} |
| 5625 |
if (textWidth > longest) { |
| 5626 |
longest = textWidth; |
| 5627 |
} |
| 5628 |
return longest; |
| 5629 |
}; |
| 5630 |
helpers.numberOfLabelLines = function(arrayOfThings) { |
| 5631 |
var numberOfLines = 1; |
| 5632 |
helpers.each(arrayOfThings, function(thing) { |
| 5633 |
if (helpers.isArray(thing)) { |
| 5634 |
if (thing.length > numberOfLines) { |
| 5635 |
numberOfLines = thing.length; |
| 5636 |
} |
| 5637 |
} |
| 5638 |
}); |
| 5639 |
return numberOfLines; |
| 5640 |
}; |
| 5641 |
|
| 5642 |
helpers.color = !color ? |
| 5643 |
function(value) { |
| 5644 |
console.error('Color.js not found!'); |
| 5645 |
return value; |
| 5646 |
} : |
| 5647 |
function(value) { |
| 5648 |
/* global CanvasGradient */ |
| 5649 |
if (value instanceof CanvasGradient) { |
| 5650 |
value = defaults.global.defaultColor; |
| 5651 |
} |
| 5652 |
|
| 5653 |
return color(value); |
| 5654 |
}; |
| 5655 |
|
| 5656 |
helpers.getHoverColor = function(colorValue) { |
| 5657 |
/* global CanvasPattern */ |
| 5658 |
return (colorValue instanceof CanvasPattern) ? |
| 5659 |
colorValue : |
| 5660 |
helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString(); |
| 5661 |
}; |
| 5662 |
}; |
| 5663 |
|
| 5664 |
},{"25":25,"3":3,"45":45}],28:[function(require,module,exports){ |
| 5665 |
'use strict'; |
| 5666 |
|
| 5667 |
var helpers = require(45); |
| 5668 |
|
| 5669 |
/** |
| 5670 |
* Helper function to get relative position for an event |
| 5671 |
* @param {Event|IEvent} event - The event to get the position for |
| 5672 |
* @param {Chart} chart - The chart |
| 5673 |
* @returns {Point} the event position |
| 5674 |
*/ |
| 5675 |
function getRelativePosition(e, chart) { |
| 5676 |
if (e.native) { |
| 5677 |
return { |
| 5678 |
x: e.x, |
| 5679 |
y: e.y |
| 5680 |
}; |
| 5681 |
} |
| 5682 |
|
| 5683 |
return helpers.getRelativePosition(e, chart); |
| 5684 |
} |
| 5685 |
|
| 5686 |
/** |
| 5687 |
* Helper function to traverse all of the visible elements in the chart |
| 5688 |
* @param chart {chart} the chart |
| 5689 |
* @param handler {Function} the callback to execute for each visible item |
| 5690 |
*/ |
| 5691 |
function parseVisibleItems(chart, handler) { |
| 5692 |
var datasets = chart.data.datasets; |
| 5693 |
var meta, i, j, ilen, jlen; |
| 5694 |
|
| 5695 |
for (i = 0, ilen = datasets.length; i < ilen; ++i) { |
| 5696 |
if (!chart.isDatasetVisible(i)) { |
| 5697 |
continue; |
| 5698 |
} |
| 5699 |
|
| 5700 |
meta = chart.getDatasetMeta(i); |
| 5701 |
for (j = 0, jlen = meta.data.length; j < jlen; ++j) { |
| 5702 |
var element = meta.data[j]; |
| 5703 |
if (!element._view.skip) { |
| 5704 |
handler(element); |
| 5705 |
} |
| 5706 |
} |
| 5707 |
} |
| 5708 |
} |
| 5709 |
|
| 5710 |
/** |
| 5711 |
* Helper function to get the items that intersect the event position |
| 5712 |
* @param items {ChartElement[]} elements to filter |
| 5713 |
* @param position {Point} the point to be nearest to |
| 5714 |
* @return {ChartElement[]} the nearest items |
| 5715 |
*/ |
| 5716 |
function getIntersectItems(chart, position) { |
| 5717 |
var elements = []; |
| 5718 |
|
| 5719 |
parseVisibleItems(chart, function(element) { |
| 5720 |
if (element.inRange(position.x, position.y)) { |
| 5721 |
elements.push(element); |
| 5722 |
} |
| 5723 |
}); |
| 5724 |
|
| 5725 |
return elements; |
| 5726 |
} |
| 5727 |
|
| 5728 |
/** |
| 5729 |
* Helper function to get the items nearest to the event position considering all visible items in teh chart |
| 5730 |
* @param chart {Chart} the chart to look at elements from |
| 5731 |
* @param position {Point} the point to be nearest to |
| 5732 |
* @param intersect {Boolean} if true, only consider items that intersect the position |
| 5733 |
* @param distanceMetric {Function} function to provide the distance between points |
| 5734 |
* @return {ChartElement[]} the nearest items |
| 5735 |
*/ |
| 5736 |
function getNearestItems(chart, position, intersect, distanceMetric) { |
| 5737 |
var minDistance = Number.POSITIVE_INFINITY; |
| 5738 |
var nearestItems = []; |
| 5739 |
|
| 5740 |
parseVisibleItems(chart, function(element) { |
| 5741 |
if (intersect && !element.inRange(position.x, position.y)) { |
| 5742 |
return; |
| 5743 |
} |
| 5744 |
|
| 5745 |
var center = element.getCenterPoint(); |
| 5746 |
var distance = distanceMetric(position, center); |
| 5747 |
|
| 5748 |
if (distance < minDistance) { |
| 5749 |
nearestItems = [element]; |
| 5750 |
minDistance = distance; |
| 5751 |
} else if (distance === minDistance) { |
| 5752 |
// Can have multiple items at the same distance in which case we sort by size |
| 5753 |
nearestItems.push(element); |
| 5754 |
} |
| 5755 |
}); |
| 5756 |
|
| 5757 |
return nearestItems; |
| 5758 |
} |
| 5759 |
|
| 5760 |
/** |
| 5761 |
* Get a distance metric function for two points based on the |
| 5762 |
* axis mode setting |
| 5763 |
* @param {String} axis the axis mode. x|y|xy |
| 5764 |
*/ |
| 5765 |
function getDistanceMetricForAxis(axis) { |
| 5766 |
var useX = axis.indexOf('x') !== -1; |
| 5767 |
var useY = axis.indexOf('y') !== -1; |
| 5768 |
|
| 5769 |
return function(pt1, pt2) { |
| 5770 |
var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; |
| 5771 |
var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; |
| 5772 |
return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); |
| 5773 |
}; |
| 5774 |
} |
| 5775 |
|
| 5776 |
function indexMode(chart, e, options) { |
| 5777 |
var position = getRelativePosition(e, chart); |
| 5778 |
// Default axis for index mode is 'x' to match old behaviour |
| 5779 |
options.axis = options.axis || 'x'; |
| 5780 |
var distanceMetric = getDistanceMetricForAxis(options.axis); |
| 5781 |
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); |
| 5782 |
var elements = []; |
| 5783 |
|
| 5784 |
if (!items.length) { |
| 5785 |
return []; |
| 5786 |
} |
| 5787 |
|
| 5788 |
chart.data.datasets.forEach(function(dataset, datasetIndex) { |
| 5789 |
if (chart.isDatasetVisible(datasetIndex)) { |
| 5790 |
var meta = chart.getDatasetMeta(datasetIndex); |
| 5791 |
var element = meta.data[items[0]._index]; |
| 5792 |
|
| 5793 |
// don't count items that are skipped (null data) |
| 5794 |
if (element && !element._view.skip) { |
| 5795 |
elements.push(element); |
| 5796 |
} |
| 5797 |
} |
| 5798 |
}); |
| 5799 |
|
| 5800 |
return elements; |
| 5801 |
} |
| 5802 |
|
| 5803 |
/** |
| 5804 |
* @interface IInteractionOptions |
| 5805 |
*/ |
| 5806 |
/** |
| 5807 |
* If true, only consider items that intersect the point |
| 5808 |
* @name IInterfaceOptions#boolean |
| 5809 |
* @type Boolean |
| 5810 |
*/ |
| 5811 |
|
| 5812 |
/** |
| 5813 |
* Contains interaction related functions |
| 5814 |
* @namespace Chart.Interaction |
| 5815 |
*/ |
| 5816 |
module.exports = { |
| 5817 |
// Helper function for different modes |
| 5818 |
modes: { |
| 5819 |
single: function(chart, e) { |
| 5820 |
var position = getRelativePosition(e, chart); |
| 5821 |
var elements = []; |
| 5822 |
|
| 5823 |
parseVisibleItems(chart, function(element) { |
| 5824 |
if (element.inRange(position.x, position.y)) { |
| 5825 |
elements.push(element); |
| 5826 |
return elements; |
| 5827 |
} |
| 5828 |
}); |
| 5829 |
|
| 5830 |
return elements.slice(0, 1); |
| 5831 |
}, |
| 5832 |
|
| 5833 |
/** |
| 5834 |
* @function Chart.Interaction.modes.label |
| 5835 |
* @deprecated since version 2.4.0 |
| 5836 |
* @todo remove at version 3 |
| 5837 |
* @private |
| 5838 |
*/ |
| 5839 |
label: indexMode, |
| 5840 |
|
| 5841 |
/** |
| 5842 |
* Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something |
| 5843 |
* If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item |
| 5844 |
* @function Chart.Interaction.modes.index |
| 5845 |
* @since v2.4.0 |
| 5846 |
* @param chart {chart} the chart we are returning items from |
| 5847 |
* @param e {Event} the event we are find things at |
| 5848 |
* @param options {IInteractionOptions} options to use during interaction |
| 5849 |
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned |
| 5850 |
*/ |
| 5851 |
index: indexMode, |
| 5852 |
|
| 5853 |
/** |
| 5854 |
* Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something |
| 5855 |
* If the options.intersect is false, we find the nearest item and return the items in that dataset |
| 5856 |
* @function Chart.Interaction.modes.dataset |
| 5857 |
* @param chart {chart} the chart we are returning items from |
| 5858 |
* @param e {Event} the event we are find things at |
| 5859 |
* @param options {IInteractionOptions} options to use during interaction |
| 5860 |
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned |
| 5861 |
*/ |
| 5862 |
dataset: function(chart, e, options) { |
| 5863 |
var position = getRelativePosition(e, chart); |
| 5864 |
options.axis = options.axis || 'xy'; |
| 5865 |
var distanceMetric = getDistanceMetricForAxis(options.axis); |
| 5866 |
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); |
| 5867 |
|
| 5868 |
if (items.length > 0) { |
| 5869 |
items = chart.getDatasetMeta(items[0]._datasetIndex).data; |
| 5870 |
} |
| 5871 |
|
| 5872 |
return items; |
| 5873 |
}, |
| 5874 |
|
| 5875 |
/** |
| 5876 |
* @function Chart.Interaction.modes.x-axis |
| 5877 |
* @deprecated since version 2.4.0. Use index mode and intersect == true |
| 5878 |
* @todo remove at version 3 |
| 5879 |
* @private |
| 5880 |
*/ |
| 5881 |
'x-axis': function(chart, e) { |
| 5882 |
return indexMode(chart, e, {intersect: false}); |
| 5883 |
}, |
| 5884 |
|
| 5885 |
/** |
| 5886 |
* Point mode returns all elements that hit test based on the event position |
| 5887 |
* of the event |
| 5888 |
* @function Chart.Interaction.modes.intersect |
| 5889 |
* @param chart {chart} the chart we are returning items from |
| 5890 |
* @param e {Event} the event we are find things at |
| 5891 |
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned |
| 5892 |
*/ |
| 5893 |
point: function(chart, e) { |
| 5894 |
var position = getRelativePosition(e, chart); |
| 5895 |
return getIntersectItems(chart, position); |
| 5896 |
}, |
| 5897 |
|
| 5898 |
/** |
| 5899 |
* nearest mode returns the element closest to the point |
| 5900 |
* @function Chart.Interaction.modes.intersect |
| 5901 |
* @param chart {chart} the chart we are returning items from |
| 5902 |
* @param e {Event} the event we are find things at |
| 5903 |
* @param options {IInteractionOptions} options to use |
| 5904 |
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned |
| 5905 |
*/ |
| 5906 |
nearest: function(chart, e, options) { |
| 5907 |
var position = getRelativePosition(e, chart); |
| 5908 |
options.axis = options.axis || 'xy'; |
| 5909 |
var distanceMetric = getDistanceMetricForAxis(options.axis); |
| 5910 |
var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric); |
| 5911 |
|
| 5912 |
// We have multiple items at the same distance from the event. Now sort by smallest |
| 5913 |
if (nearestItems.length > 1) { |
| 5914 |
nearestItems.sort(function(a, b) { |
| 5915 |
var sizeA = a.getArea(); |
| 5916 |
var sizeB = b.getArea(); |
| 5917 |
var ret = sizeA - sizeB; |
| 5918 |
|
| 5919 |
if (ret === 0) { |
| 5920 |
// if equal sort by dataset index |
| 5921 |
ret = a._datasetIndex - b._datasetIndex; |
| 5922 |
} |
| 5923 |
|
| 5924 |
return ret; |
| 5925 |
}); |
| 5926 |
} |
| 5927 |
|
| 5928 |
// Return only 1 item |
| 5929 |
return nearestItems.slice(0, 1); |
| 5930 |
}, |
| 5931 |
|
| 5932 |
/** |
| 5933 |
* x mode returns the elements that hit-test at the current x coordinate |
| 5934 |
* @function Chart.Interaction.modes.x |
| 5935 |
* @param chart {chart} the chart we are returning items from |
| 5936 |
* @param e {Event} the event we are find things at |
| 5937 |
* @param options {IInteractionOptions} options to use |
| 5938 |
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned |
| 5939 |
*/ |
| 5940 |
x: function(chart, e, options) { |
| 5941 |
var position = getRelativePosition(e, chart); |
| 5942 |
var items = []; |
| 5943 |
var intersectsItem = false; |
| 5944 |
|
| 5945 |
parseVisibleItems(chart, function(element) { |
| 5946 |
if (element.inXRange(position.x)) { |
| 5947 |
items.push(element); |
| 5948 |
} |
| 5949 |
|
| 5950 |
if (element.inRange(position.x, position.y)) { |
| 5951 |
intersectsItem = true; |
| 5952 |
} |
| 5953 |
}); |
| 5954 |
|
| 5955 |
// If we want to trigger on an intersect and we don't have any items |
| 5956 |
// that intersect the position, return nothing |
| 5957 |
if (options.intersect && !intersectsItem) { |
| 5958 |
items = []; |
| 5959 |
} |
| 5960 |
return items; |
| 5961 |
}, |
| 5962 |
|
| 5963 |
/** |
| 5964 |
* y mode returns the elements that hit-test at the current y coordinate |
| 5965 |
* @function Chart.Interaction.modes.y |
| 5966 |
* @param chart {chart} the chart we are returning items from |
| 5967 |
* @param e {Event} the event we are find things at |
| 5968 |
* @param options {IInteractionOptions} options to use |
| 5969 |
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned |
| 5970 |
*/ |
| 5971 |
y: function(chart, e, options) { |
| 5972 |
var position = getRelativePosition(e, chart); |
| 5973 |
var items = []; |
| 5974 |
var intersectsItem = false; |
| 5975 |
|
| 5976 |
parseVisibleItems(chart, function(element) { |
| 5977 |
if (element.inYRange(position.y)) { |
| 5978 |
items.push(element); |
| 5979 |
} |
| 5980 |
|
| 5981 |
if (element.inRange(position.x, position.y)) { |
| 5982 |
intersectsItem = true; |
| 5983 |
} |
| 5984 |
}); |
| 5985 |
|
| 5986 |
// If we want to trigger on an intersect and we don't have any items |
| 5987 |
// that intersect the position, return nothing |
| 5988 |
if (options.intersect && !intersectsItem) { |
| 5989 |
items = []; |
| 5990 |
} |
| 5991 |
return items; |
| 5992 |
} |
| 5993 |
} |
| 5994 |
}; |
| 5995 |
|
| 5996 |
},{"45":45}],29:[function(require,module,exports){ |
| 5997 |
'use strict'; |
| 5998 |
|
| 5999 |
var defaults = require(25); |
| 6000 |
|
| 6001 |
defaults._set('global', { |
| 6002 |
responsive: true, |
| 6003 |
responsiveAnimationDuration: 0, |
| 6004 |
maintainAspectRatio: true, |
| 6005 |
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], |
| 6006 |
hover: { |
| 6007 |
onHover: null, |
| 6008 |
mode: 'nearest', |
| 6009 |
intersect: true, |
| 6010 |
animationDuration: 400 |
| 6011 |
}, |
| 6012 |
onClick: null, |
| 6013 |
defaultColor: 'rgba(0,0,0,0.1)', |
| 6014 |
defaultFontColor: '#666', |
| 6015 |
defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", |
| 6016 |
defaultFontSize: 12, |
| 6017 |
defaultFontStyle: 'normal', |
| 6018 |
showLines: true, |
| 6019 |
|
| 6020 |
// Element defaults defined in element extensions |
| 6021 |
elements: {}, |
| 6022 |
|
| 6023 |
// Layout options such as padding |
| 6024 |
layout: { |
| 6025 |
padding: { |
| 6026 |
top: 0, |
| 6027 |
right: 0, |
| 6028 |
bottom: 0, |
| 6029 |
left: 0 |
| 6030 |
} |
| 6031 |
} |
| 6032 |
}); |
| 6033 |
|
| 6034 |
module.exports = function() { |
| 6035 |
|
| 6036 |
// Occupy the global variable of Chart, and create a simple base class |
| 6037 |
var Chart = function(item, config) { |
| 6038 |
this.construct(item, config); |
| 6039 |
return this; |
| 6040 |
}; |
| 6041 |
|
| 6042 |
Chart.Chart = Chart; |
| 6043 |
|
| 6044 |
return Chart; |
| 6045 |
}; |
| 6046 |
|
| 6047 |
},{"25":25}],30:[function(require,module,exports){ |
| 6048 |
'use strict'; |
| 6049 |
|
| 6050 |
var helpers = require(45); |
| 6051 |
|
| 6052 |
module.exports = function(Chart) { |
| 6053 |
|
| 6054 |
function filterByPosition(array, position) { |
| 6055 |
return helpers.where(array, function(v) { |
| 6056 |
return v.position === position; |
| 6057 |
}); |
| 6058 |
} |
| 6059 |
|
| 6060 |
function sortByWeight(array, reverse) { |
| 6061 |
array.forEach(function(v, i) { |
| 6062 |
v._tmpIndex_ = i; |
| 6063 |
return v; |
| 6064 |
}); |
| 6065 |
array.sort(function(a, b) { |
| 6066 |
var v0 = reverse ? b : a; |
| 6067 |
var v1 = reverse ? a : b; |
| 6068 |
return v0.weight === v1.weight ? |
| 6069 |
v0._tmpIndex_ - v1._tmpIndex_ : |
| 6070 |
v0.weight - v1.weight; |
| 6071 |
}); |
| 6072 |
array.forEach(function(v) { |
| 6073 |
delete v._tmpIndex_; |
| 6074 |
}); |
| 6075 |
} |
| 6076 |
|
| 6077 |
/** |
| 6078 |
* @interface ILayoutItem |
| 6079 |
* @prop {String} position - The position of the item in the chart layout. Possible values are |
| 6080 |
* 'left', 'top', 'right', 'bottom', and 'chartArea' |
| 6081 |
* @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area |
| 6082 |
* @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down |
| 6083 |
* @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) |
| 6084 |
* @prop {Function} update - Takes two parameters: width and height. Returns size of item |
| 6085 |
* @prop {Function} getPadding - Returns an object with padding on the edges |
| 6086 |
* @prop {Number} width - Width of item. Must be valid after update() |
| 6087 |
* @prop {Number} height - Height of item. Must be valid after update() |
| 6088 |
* @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update |
| 6089 |
* @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update |
| 6090 |
* @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update |
| 6091 |
* @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update |
| 6092 |
*/ |
| 6093 |
|
| 6094 |
// The layout service is very self explanatory. It's responsible for the layout within a chart. |
| 6095 |
// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need |
| 6096 |
// It is this service's responsibility of carrying out that layout. |
| 6097 |
Chart.layoutService = { |
| 6098 |
defaults: {}, |
| 6099 |
|
| 6100 |
/** |
| 6101 |
* Register a box to a chart. |
| 6102 |
* A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. |
| 6103 |
* @param {Chart} chart - the chart to use |
| 6104 |
* @param {ILayoutItem} item - the item to add to be layed out |
| 6105 |
*/ |
| 6106 |
addBox: function(chart, item) { |
| 6107 |
if (!chart.boxes) { |
| 6108 |
chart.boxes = []; |
| 6109 |
} |
| 6110 |
|
| 6111 |
// initialize item with default values |
| 6112 |
item.fullWidth = item.fullWidth || false; |
| 6113 |
item.position = item.position || 'top'; |
| 6114 |
item.weight = item.weight || 0; |
| 6115 |
|
| 6116 |
chart.boxes.push(item); |
| 6117 |
}, |
| 6118 |
|
| 6119 |
/** |
| 6120 |
* Remove a layoutItem from a chart |
| 6121 |
* @param {Chart} chart - the chart to remove the box from |
| 6122 |
* @param {Object} layoutItem - the item to remove from the layout |
| 6123 |
*/ |
| 6124 |
removeBox: function(chart, layoutItem) { |
| 6125 |
var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; |
| 6126 |
if (index !== -1) { |
| 6127 |
chart.boxes.splice(index, 1); |
| 6128 |
} |
| 6129 |
}, |
| 6130 |
|
| 6131 |
/** |
| 6132 |
* Sets (or updates) options on the given `item`. |
| 6133 |
* @param {Chart} chart - the chart in which the item lives (or will be added to) |
| 6134 |
* @param {Object} item - the item to configure with the given options |
| 6135 |
* @param {Object} options - the new item options. |
| 6136 |
*/ |
| 6137 |
configure: function(chart, item, options) { |
| 6138 |
var props = ['fullWidth', 'position', 'weight']; |
| 6139 |
var ilen = props.length; |
| 6140 |
var i = 0; |
| 6141 |
var prop; |
| 6142 |
|
| 6143 |
for (; i < ilen; ++i) { |
| 6144 |
prop = props[i]; |
| 6145 |
if (options.hasOwnProperty(prop)) { |
| 6146 |
item[prop] = options[prop]; |
| 6147 |
} |
| 6148 |
} |
| 6149 |
}, |
| 6150 |
|
| 6151 |
/** |
| 6152 |
* Fits boxes of the given chart into the given size by having each box measure itself |
| 6153 |
* then running a fitting algorithm |
| 6154 |
* @param {Chart} chart - the chart |
| 6155 |
* @param {Number} width - the width to fit into |
| 6156 |
* @param {Number} height - the height to fit into |
| 6157 |
*/ |
| 6158 |
update: function(chart, width, height) { |
| 6159 |
if (!chart) { |
| 6160 |
return; |
| 6161 |
} |
| 6162 |
|
| 6163 |
var layoutOptions = chart.options.layout || {}; |
| 6164 |
var padding = helpers.options.toPadding(layoutOptions.padding); |
| 6165 |
var leftPadding = padding.left; |
| 6166 |
var rightPadding = padding.right; |
| 6167 |
var topPadding = padding.top; |
| 6168 |
var bottomPadding = padding.bottom; |
| 6169 |
|
| 6170 |
var leftBoxes = filterByPosition(chart.boxes, 'left'); |
| 6171 |
var rightBoxes = filterByPosition(chart.boxes, 'right'); |
| 6172 |
var topBoxes = filterByPosition(chart.boxes, 'top'); |
| 6173 |
var bottomBoxes = filterByPosition(chart.boxes, 'bottom'); |
| 6174 |
var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea'); |
| 6175 |
|
| 6176 |
// Sort boxes by weight. A higher weight is further away from the chart area |
| 6177 |
sortByWeight(leftBoxes, true); |
| 6178 |
sortByWeight(rightBoxes, false); |
| 6179 |
sortByWeight(topBoxes, true); |
| 6180 |
sortByWeight(bottomBoxes, false); |
| 6181 |
|
| 6182 |
// Essentially we now have any number of boxes on each of the 4 sides. |
| 6183 |
// Our canvas looks like the following. |
| 6184 |
// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and |
| 6185 |
// B1 is the bottom axis |
| 6186 |
// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays |
| 6187 |
// These locations are single-box locations only, when trying to register a chartArea location that is already taken, |
| 6188 |
// an error will be thrown. |
| 6189 |
// |
| 6190 |
// |----------------------------------------------------| |
| 6191 |
// | T1 (Full Width) | |
| 6192 |
// |----------------------------------------------------| |
| 6193 |
// | | | T2 | | |
| 6194 |
// | |----|-------------------------------------|----| |
| 6195 |
// | | | C1 | | C2 | | |
| 6196 |
// | | |----| |----| | |
| 6197 |
// | | | | | |
| 6198 |
// | L1 | L2 | ChartArea (C0) | R1 | |
| 6199 |
// | | | | | |
| 6200 |
// | | |----| |----| | |
| 6201 |
// | | | C3 | | C4 | | |
| 6202 |
// | |----|-------------------------------------|----| |
| 6203 |
// | | | B1 | | |
| 6204 |
// |----------------------------------------------------| |
| 6205 |
// | B2 (Full Width) | |
| 6206 |
// |----------------------------------------------------| |
| 6207 |
// |
| 6208 |
// What we do to find the best sizing, we do the following |
| 6209 |
// 1. Determine the minimum size of the chart area. |
| 6210 |
// 2. Split the remaining width equally between each vertical axis |
| 6211 |
// 3. Split the remaining height equally between each horizontal axis |
| 6212 |
// 4. Give each layout the maximum size it can be. The layout will return it's minimum size |
| 6213 |
// 5. Adjust the sizes of each axis based on it's minimum reported size. |
| 6214 |
// 6. Refit each axis |
| 6215 |
// 7. Position each axis in the final location |
| 6216 |
// 8. Tell the chart the final location of the chart area |
| 6217 |
// 9. Tell any axes that overlay the chart area the positions of the chart area |
| 6218 |
|
| 6219 |
// Step 1 |
| 6220 |
var chartWidth = width - leftPadding - rightPadding; |
| 6221 |
var chartHeight = height - topPadding - bottomPadding; |
| 6222 |
var chartAreaWidth = chartWidth / 2; // min 50% |
| 6223 |
var chartAreaHeight = chartHeight / 2; // min 50% |
| 6224 |
|
| 6225 |
// Step 2 |
| 6226 |
var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); |
| 6227 |
|
| 6228 |
// Step 3 |
| 6229 |
var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); |
| 6230 |
|
| 6231 |
// Step 4 |
| 6232 |
var maxChartAreaWidth = chartWidth; |
| 6233 |
var maxChartAreaHeight = chartHeight; |
| 6234 |
var minBoxSizes = []; |
| 6235 |
|
| 6236 |
function getMinimumBoxSize(box) { |
| 6237 |
var minSize; |
| 6238 |
var isHorizontal = box.isHorizontal(); |
| 6239 |
|
| 6240 |
if (isHorizontal) { |
| 6241 |
minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); |
| 6242 |
maxChartAreaHeight -= minSize.height; |
| 6243 |
} else { |
| 6244 |
minSize = box.update(verticalBoxWidth, chartAreaHeight); |
| 6245 |
maxChartAreaWidth -= minSize.width; |
| 6246 |
} |
| 6247 |
|
| 6248 |
minBoxSizes.push({ |
| 6249 |
horizontal: isHorizontal, |
| 6250 |
minSize: minSize, |
| 6251 |
box: box, |
| 6252 |
}); |
| 6253 |
} |
| 6254 |
|
| 6255 |
helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); |
| 6256 |
|
| 6257 |
// If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478) |
| 6258 |
var maxHorizontalLeftPadding = 0; |
| 6259 |
var maxHorizontalRightPadding = 0; |
| 6260 |
var maxVerticalTopPadding = 0; |
| 6261 |
var maxVerticalBottomPadding = 0; |
| 6262 |
|
| 6263 |
helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) { |
| 6264 |
if (horizontalBox.getPadding) { |
| 6265 |
var boxPadding = horizontalBox.getPadding(); |
| 6266 |
maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left); |
| 6267 |
maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right); |
| 6268 |
} |
| 6269 |
}); |
| 6270 |
|
| 6271 |
helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) { |
| 6272 |
if (verticalBox.getPadding) { |
| 6273 |
var boxPadding = verticalBox.getPadding(); |
| 6274 |
maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top); |
| 6275 |
maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom); |
| 6276 |
} |
| 6277 |
}); |
| 6278 |
|
| 6279 |
// At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could |
| 6280 |
// be if the axes are drawn at their minimum sizes. |
| 6281 |
// Steps 5 & 6 |
| 6282 |
var totalLeftBoxesWidth = leftPadding; |
| 6283 |
var totalRightBoxesWidth = rightPadding; |
| 6284 |
var totalTopBoxesHeight = topPadding; |
| 6285 |
var totalBottomBoxesHeight = bottomPadding; |
| 6286 |
|
| 6287 |
// Function to fit a box |
| 6288 |
function fitBox(box) { |
| 6289 |
var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) { |
| 6290 |
return minBox.box === box; |
| 6291 |
}); |
| 6292 |
|
| 6293 |
if (minBoxSize) { |
| 6294 |
if (box.isHorizontal()) { |
| 6295 |
var scaleMargin = { |
| 6296 |
left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding), |
| 6297 |
right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding), |
| 6298 |
top: 0, |
| 6299 |
bottom: 0 |
| 6300 |
}; |
| 6301 |
|
| 6302 |
// Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends |
| 6303 |
// on the margin. Sometimes they need to increase in size slightly |
| 6304 |
box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); |
| 6305 |
} else { |
| 6306 |
box.update(minBoxSize.minSize.width, maxChartAreaHeight); |
| 6307 |
} |
| 6308 |
} |
| 6309 |
} |
| 6310 |
|
| 6311 |
// Update, and calculate the left and right margins for the horizontal boxes |
| 6312 |
helpers.each(leftBoxes.concat(rightBoxes), fitBox); |
| 6313 |
|
| 6314 |
helpers.each(leftBoxes, function(box) { |
| 6315 |
totalLeftBoxesWidth += box.width; |
| 6316 |
}); |
| 6317 |
|
| 6318 |
helpers.each(rightBoxes, function(box) { |
| 6319 |
totalRightBoxesWidth += box.width; |
| 6320 |
}); |
| 6321 |
|
| 6322 |
// Set the Left and Right margins for the horizontal boxes |
| 6323 |
helpers.each(topBoxes.concat(bottomBoxes), fitBox); |
| 6324 |
|
| 6325 |
// Figure out how much margin is on the top and bottom of the vertical boxes |
| 6326 |
helpers.each(topBoxes, function(box) { |
| 6327 |
totalTopBoxesHeight += box.height; |
| 6328 |
}); |
| 6329 |
|
| 6330 |
helpers.each(bottomBoxes, function(box) { |
| 6331 |
totalBottomBoxesHeight += box.height; |
| 6332 |
}); |
| 6333 |
|
| 6334 |
function finalFitVerticalBox(box) { |
| 6335 |
var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) { |
| 6336 |
return minSize.box === box; |
| 6337 |
}); |
| 6338 |
|
| 6339 |
var scaleMargin = { |
| 6340 |
left: 0, |
| 6341 |
right: 0, |
| 6342 |
top: totalTopBoxesHeight, |
| 6343 |
bottom: totalBottomBoxesHeight |
| 6344 |
}; |
| 6345 |
|
| 6346 |
if (minBoxSize) { |
| 6347 |
box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); |
| 6348 |
} |
| 6349 |
} |
| 6350 |
|
| 6351 |
// Let the left layout know the final margin |
| 6352 |
helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); |
| 6353 |
|
| 6354 |
// Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) |
| 6355 |
totalLeftBoxesWidth = leftPadding; |
| 6356 |
totalRightBoxesWidth = rightPadding; |
| 6357 |
totalTopBoxesHeight = topPadding; |
| 6358 |
totalBottomBoxesHeight = bottomPadding; |
| 6359 |
|
| 6360 |
helpers.each(leftBoxes, function(box) { |
| 6361 |
totalLeftBoxesWidth += box.width; |
| 6362 |
}); |
| 6363 |
|
| 6364 |
helpers.each(rightBoxes, function(box) { |
| 6365 |
totalRightBoxesWidth += box.width; |
| 6366 |
}); |
| 6367 |
|
| 6368 |
helpers.each(topBoxes, function(box) { |
| 6369 |
totalTopBoxesHeight += box.height; |
| 6370 |
}); |
| 6371 |
helpers.each(bottomBoxes, function(box) { |
| 6372 |
totalBottomBoxesHeight += box.height; |
| 6373 |
}); |
| 6374 |
|
| 6375 |
// We may be adding some padding to account for rotated x axis labels |
| 6376 |
var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0); |
| 6377 |
totalLeftBoxesWidth += leftPaddingAddition; |
| 6378 |
totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0); |
| 6379 |
|
| 6380 |
var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0); |
| 6381 |
totalTopBoxesHeight += topPaddingAddition; |
| 6382 |
totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0); |
| 6383 |
|
| 6384 |
// Figure out if our chart area changed. This would occur if the dataset layout label rotation |
| 6385 |
// changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do |
| 6386 |
// without calling `fit` again |
| 6387 |
var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; |
| 6388 |
var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; |
| 6389 |
|
| 6390 |
if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { |
| 6391 |
helpers.each(leftBoxes, function(box) { |
| 6392 |
box.height = newMaxChartAreaHeight; |
| 6393 |
}); |
| 6394 |
|
| 6395 |
helpers.each(rightBoxes, function(box) { |
| 6396 |
box.height = newMaxChartAreaHeight; |
| 6397 |
}); |
| 6398 |
|
| 6399 |
helpers.each(topBoxes, function(box) { |
| 6400 |
if (!box.fullWidth) { |
| 6401 |
box.width = newMaxChartAreaWidth; |
| 6402 |
} |
| 6403 |
}); |
| 6404 |
|
| 6405 |
helpers.each(bottomBoxes, function(box) { |
| 6406 |
if (!box.fullWidth) { |
| 6407 |
box.width = newMaxChartAreaWidth; |
| 6408 |
} |
| 6409 |
}); |
| 6410 |
|
| 6411 |
maxChartAreaHeight = newMaxChartAreaHeight; |
| 6412 |
maxChartAreaWidth = newMaxChartAreaWidth; |
| 6413 |
} |
| 6414 |
|
| 6415 |
// Step 7 - Position the boxes |
| 6416 |
var left = leftPadding + leftPaddingAddition; |
| 6417 |
var top = topPadding + topPaddingAddition; |
| 6418 |
|
| 6419 |
function placeBox(box) { |
| 6420 |
if (box.isHorizontal()) { |
| 6421 |
box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth; |
| 6422 |
box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth; |
| 6423 |
box.top = top; |
| 6424 |
box.bottom = top + box.height; |
| 6425 |
|
| 6426 |
// Move to next point |
| 6427 |
top = box.bottom; |
| 6428 |
|
| 6429 |
} else { |
| 6430 |
|
| 6431 |
box.left = left; |
| 6432 |
box.right = left + box.width; |
| 6433 |
box.top = totalTopBoxesHeight; |
| 6434 |
box.bottom = totalTopBoxesHeight + maxChartAreaHeight; |
| 6435 |
|
| 6436 |
// Move to next point |
| 6437 |
left = box.right; |
| 6438 |
} |
| 6439 |
} |
| 6440 |
|
| 6441 |
helpers.each(leftBoxes.concat(topBoxes), placeBox); |
| 6442 |
|
| 6443 |
// Account for chart width and height |
| 6444 |
left += maxChartAreaWidth; |
| 6445 |
top += maxChartAreaHeight; |
| 6446 |
|
| 6447 |
helpers.each(rightBoxes, placeBox); |
| 6448 |
helpers.each(bottomBoxes, placeBox); |
| 6449 |
|
| 6450 |
// Step 8 |
| 6451 |
chart.chartArea = { |
| 6452 |
left: totalLeftBoxesWidth, |
| 6453 |
top: totalTopBoxesHeight, |
| 6454 |
right: totalLeftBoxesWidth + maxChartAreaWidth, |
| 6455 |
bottom: totalTopBoxesHeight + maxChartAreaHeight |
| 6456 |
}; |
| 6457 |
|
| 6458 |
// Step 9 |
| 6459 |
helpers.each(chartAreaBoxes, function(box) { |
| 6460 |
box.left = chart.chartArea.left; |
| 6461 |
box.top = chart.chartArea.top; |
| 6462 |
box.right = chart.chartArea.right; |
| 6463 |
box.bottom = chart.chartArea.bottom; |
| 6464 |
|
| 6465 |
box.update(maxChartAreaWidth, maxChartAreaHeight); |
| 6466 |
}); |
| 6467 |
} |
| 6468 |
}; |
| 6469 |
}; |
| 6470 |
|
| 6471 |
},{"45":45}],31:[function(require,module,exports){ |
| 6472 |
'use strict'; |
| 6473 |
|
| 6474 |
var defaults = require(25); |
| 6475 |
var Element = require(26); |
| 6476 |
var helpers = require(45); |
| 6477 |
|
| 6478 |
defaults._set('global', { |
| 6479 |
plugins: {} |
| 6480 |
}); |
| 6481 |
|
| 6482 |
module.exports = function(Chart) { |
| 6483 |
|
| 6484 |
/** |
| 6485 |
* The plugin service singleton |
| 6486 |
* @namespace Chart.plugins |
| 6487 |
* @since 2.1.0 |
| 6488 |
*/ |
| 6489 |
Chart.plugins = { |
| 6490 |
/** |
| 6491 |
* Globally registered plugins. |
| 6492 |
* @private |
| 6493 |
*/ |
| 6494 |
_plugins: [], |
| 6495 |
|
| 6496 |
/** |
| 6497 |
* This identifier is used to invalidate the descriptors cache attached to each chart |
| 6498 |
* when a global plugin is registered or unregistered. In this case, the cache ID is |
| 6499 |
* incremented and descriptors are regenerated during following API calls. |
| 6500 |
* @private |
| 6501 |
*/ |
| 6502 |
_cacheId: 0, |
| 6503 |
|
| 6504 |
/** |
| 6505 |
* Registers the given plugin(s) if not already registered. |
| 6506 |
* @param {Array|Object} plugins plugin instance(s). |
| 6507 |
*/ |
| 6508 |
register: function(plugins) { |
| 6509 |
var p = this._plugins; |
| 6510 |
([]).concat(plugins).forEach(function(plugin) { |
| 6511 |
if (p.indexOf(plugin) === -1) { |
| 6512 |
p.push(plugin); |
| 6513 |
} |
| 6514 |
}); |
| 6515 |
|
| 6516 |
this._cacheId++; |
| 6517 |
}, |
| 6518 |
|
| 6519 |
/** |
| 6520 |
* Unregisters the given plugin(s) only if registered. |
| 6521 |
* @param {Array|Object} plugins plugin instance(s). |
| 6522 |
*/ |
| 6523 |
unregister: function(plugins) { |
| 6524 |
var p = this._plugins; |
| 6525 |
([]).concat(plugins).forEach(function(plugin) { |
| 6526 |
var idx = p.indexOf(plugin); |
| 6527 |
if (idx !== -1) { |
| 6528 |
p.splice(idx, 1); |
| 6529 |
} |
| 6530 |
}); |
| 6531 |
|
| 6532 |
this._cacheId++; |
| 6533 |
}, |
| 6534 |
|
| 6535 |
/** |
| 6536 |
* Remove all registered plugins. |
| 6537 |
* @since 2.1.5 |
| 6538 |
*/ |
| 6539 |
clear: function() { |
| 6540 |
this._plugins = []; |
| 6541 |
this._cacheId++; |
| 6542 |
}, |
| 6543 |
|
| 6544 |
/** |
| 6545 |
* Returns the number of registered plugins? |
| 6546 |
* @returns {Number} |
| 6547 |
* @since 2.1.5 |
| 6548 |
*/ |
| 6549 |
count: function() { |
| 6550 |
return this._plugins.length; |
| 6551 |
}, |
| 6552 |
|
| 6553 |
/** |
| 6554 |
* Returns all registered plugin instances. |
| 6555 |
* @returns {Array} array of plugin objects. |
| 6556 |
* @since 2.1.5 |
| 6557 |
*/ |
| 6558 |
getAll: function() { |
| 6559 |
return this._plugins; |
| 6560 |
}, |
| 6561 |
|
| 6562 |
/** |
| 6563 |
* Calls enabled plugins for `chart` on the specified hook and with the given args. |
| 6564 |
* This method immediately returns as soon as a plugin explicitly returns false. The |
| 6565 |
* returned value can be used, for instance, to interrupt the current action. |
| 6566 |
* @param {Object} chart - The chart instance for which plugins should be called. |
| 6567 |
* @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). |
| 6568 |
* @param {Array} [args] - Extra arguments to apply to the hook call. |
| 6569 |
* @returns {Boolean} false if any of the plugins return false, else returns true. |
| 6570 |
*/ |
| 6571 |
notify: function(chart, hook, args) { |
| 6572 |
var descriptors = this.descriptors(chart); |
| 6573 |
var ilen = descriptors.length; |
| 6574 |
var i, descriptor, plugin, params, method; |
| 6575 |
|
| 6576 |
for (i = 0; i < ilen; ++i) { |
| 6577 |
descriptor = descriptors[i]; |
| 6578 |
plugin = descriptor.plugin; |
| 6579 |
method = plugin[hook]; |
| 6580 |
if (typeof method === 'function') { |
| 6581 |
params = [chart].concat(args || []); |
| 6582 |
params.push(descriptor.options); |
| 6583 |
if (method.apply(plugin, params) === false) { |
| 6584 |
return false; |
| 6585 |
} |
| 6586 |
} |
| 6587 |
} |
| 6588 |
|
| 6589 |
return true; |
| 6590 |
}, |
| 6591 |
|
| 6592 |
/** |
| 6593 |
* Returns descriptors of enabled plugins for the given chart. |
| 6594 |
* @returns {Array} [{ plugin, options }] |
| 6595 |
* @private |
| 6596 |
*/ |
| 6597 |
descriptors: function(chart) { |
| 6598 |
var cache = chart._plugins || (chart._plugins = {}); |
| 6599 |
if (cache.id === this._cacheId) { |
| 6600 |
return cache.descriptors; |
| 6601 |
} |
| 6602 |
|
| 6603 |
var plugins = []; |
| 6604 |
var descriptors = []; |
| 6605 |
var config = (chart && chart.config) || {}; |
| 6606 |
var options = (config.options && config.options.plugins) || {}; |
| 6607 |
|
| 6608 |
this._plugins.concat(config.plugins || []).forEach(function(plugin) { |
| 6609 |
var idx = plugins.indexOf(plugin); |
| 6610 |
if (idx !== -1) { |
| 6611 |
return; |
| 6612 |
} |
| 6613 |
|
| 6614 |
var id = plugin.id; |
| 6615 |
var opts = options[id]; |
| 6616 |
if (opts === false) { |
| 6617 |
return; |
| 6618 |
} |
| 6619 |
|
| 6620 |
if (opts === true) { |
| 6621 |
opts = helpers.clone(defaults.global.plugins[id]); |
| 6622 |
} |
| 6623 |
|
| 6624 |
plugins.push(plugin); |
| 6625 |
descriptors.push({ |
| 6626 |
plugin: plugin, |
| 6627 |
options: opts || {} |
| 6628 |
}); |
| 6629 |
}); |
| 6630 |
|
| 6631 |
cache.descriptors = descriptors; |
| 6632 |
cache.id = this._cacheId; |
| 6633 |
return descriptors; |
| 6634 |
} |
| 6635 |
}; |
| 6636 |
|
| 6637 |
/** |
| 6638 |
* Plugin extension hooks. |
| 6639 |
* @interface IPlugin |
| 6640 |
* @since 2.1.0 |
| 6641 |
*/ |
| 6642 |
/** |
| 6643 |
* @method IPlugin#beforeInit |
| 6644 |
* @desc Called before initializing `chart`. |
| 6645 |
* @param {Chart.Controller} chart - The chart instance. |
| 6646 |
* @param {Object} options - The plugin options. |
| 6647 |
*/ |
| 6648 |
/** |
| 6649 |
* @method IPlugin#afterInit |
| 6650 |
* @desc Called after `chart` has been initialized and before the first update. |
| 6651 |
* @param {Chart.Controller} chart - The chart instance. |
| 6652 |
* @param {Object} options - The plugin options. |
| 6653 |
*/ |
| 6654 |
/** |
| 6655 |
* @method IPlugin#beforeUpdate |
| 6656 |
* @desc Called before updating `chart`. If any plugin returns `false`, the update |
| 6657 |
* is cancelled (and thus subsequent render(s)) until another `update` is triggered. |
| 6658 |
* @param {Chart.Controller} chart - The chart instance. |
| 6659 |
* @param {Object} options - The plugin options. |
| 6660 |
* @returns {Boolean} `false` to cancel the chart update. |
| 6661 |
*/ |
| 6662 |
/** |
| 6663 |
* @method IPlugin#afterUpdate |
| 6664 |
* @desc Called after `chart` has been updated and before rendering. Note that this |
| 6665 |
* hook will not be called if the chart update has been previously cancelled. |
| 6666 |
* @param {Chart.Controller} chart - The chart instance. |
| 6667 |
* @param {Object} options - The plugin options. |
| 6668 |
*/ |
| 6669 |
/** |
| 6670 |
* @method IPlugin#beforeDatasetsUpdate |
| 6671 |
* @desc Called before updating the `chart` datasets. If any plugin returns `false`, |
| 6672 |
* the datasets update is cancelled until another `update` is triggered. |
| 6673 |
* @param {Chart.Controller} chart - The chart instance. |
| 6674 |
* @param {Object} options - The plugin options. |
| 6675 |
* @returns {Boolean} false to cancel the datasets update. |
| 6676 |
* @since version 2.1.5 |
| 6677 |
*/ |
| 6678 |
/** |
| 6679 |
* @method IPlugin#afterDatasetsUpdate |
| 6680 |
* @desc Called after the `chart` datasets have been updated. Note that this hook |
| 6681 |
* will not be called if the datasets update has been previously cancelled. |
| 6682 |
* @param {Chart.Controller} chart - The chart instance. |
| 6683 |
* @param {Object} options - The plugin options. |
| 6684 |
* @since version 2.1.5 |
| 6685 |
*/ |
| 6686 |
/** |
| 6687 |
* @method IPlugin#beforeDatasetUpdate |
| 6688 |
* @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin |
| 6689 |
* returns `false`, the datasets update is cancelled until another `update` is triggered. |
| 6690 |
* @param {Chart} chart - The chart instance. |
| 6691 |
* @param {Object} args - The call arguments. |
| 6692 |
* @param {Number} args.index - The dataset index. |
| 6693 |
* @param {Object} args.meta - The dataset metadata. |
| 6694 |
* @param {Object} options - The plugin options. |
| 6695 |
* @returns {Boolean} `false` to cancel the chart datasets drawing. |
| 6696 |
*/ |
| 6697 |
/** |
| 6698 |
* @method IPlugin#afterDatasetUpdate |
| 6699 |
* @desc Called after the `chart` datasets at the given `args.index` has been updated. Note |
| 6700 |
* that this hook will not be called if the datasets update has been previously cancelled. |
| 6701 |
* @param {Chart} chart - The chart instance. |
| 6702 |
* @param {Object} args - The call arguments. |
| 6703 |
* @param {Number} args.index - The dataset index. |
| 6704 |
* @param {Object} args.meta - The dataset metadata. |
| 6705 |
* @param {Object} options - The plugin options. |
| 6706 |
*/ |
| 6707 |
/** |
| 6708 |
* @method IPlugin#beforeLayout |
| 6709 |
* @desc Called before laying out `chart`. If any plugin returns `false`, |
| 6710 |
* the layout update is cancelled until another `update` is triggered. |
| 6711 |
* @param {Chart.Controller} chart - The chart instance. |
| 6712 |
* @param {Object} options - The plugin options. |
| 6713 |
* @returns {Boolean} `false` to cancel the chart layout. |
| 6714 |
*/ |
| 6715 |
/** |
| 6716 |
* @method IPlugin#afterLayout |
| 6717 |
* @desc Called after the `chart` has been layed out. Note that this hook will not |
| 6718 |
* be called if the layout update has been previously cancelled. |
| 6719 |
* @param {Chart.Controller} chart - The chart instance. |
| 6720 |
* @param {Object} options - The plugin options. |
| 6721 |
*/ |
| 6722 |
/** |
| 6723 |
* @method IPlugin#beforeRender |
| 6724 |
* @desc Called before rendering `chart`. If any plugin returns `false`, |
| 6725 |
* the rendering is cancelled until another `render` is triggered. |
| 6726 |
* @param {Chart.Controller} chart - The chart instance. |
| 6727 |
* @param {Object} options - The plugin options. |
| 6728 |
* @returns {Boolean} `false` to cancel the chart rendering. |
| 6729 |
*/ |
| 6730 |
/** |
| 6731 |
* @method IPlugin#afterRender |
| 6732 |
* @desc Called after the `chart` has been fully rendered (and animation completed). Note |
| 6733 |
* that this hook will not be called if the rendering has been previously cancelled. |
| 6734 |
* @param {Chart.Controller} chart - The chart instance. |
| 6735 |
* @param {Object} options - The plugin options. |
| 6736 |
*/ |
| 6737 |
/** |
| 6738 |
* @method IPlugin#beforeDraw |
| 6739 |
* @desc Called before drawing `chart` at every animation frame specified by the given |
| 6740 |
* easing value. If any plugin returns `false`, the frame drawing is cancelled until |
| 6741 |
* another `render` is triggered. |
| 6742 |
* @param {Chart.Controller} chart - The chart instance. |
| 6743 |
* @param {Number} easingValue - The current animation value, between 0.0 and 1.0. |
| 6744 |
* @param {Object} options - The plugin options. |
| 6745 |
* @returns {Boolean} `false` to cancel the chart drawing. |
| 6746 |
*/ |
| 6747 |
/** |
| 6748 |
* @method IPlugin#afterDraw |
| 6749 |
* @desc Called after the `chart` has been drawn for the specific easing value. Note |
| 6750 |
* that this hook will not be called if the drawing has been previously cancelled. |
| 6751 |
* @param {Chart.Controller} chart - The chart instance. |
| 6752 |
* @param {Number} easingValue - The current animation value, between 0.0 and 1.0. |
| 6753 |
* @param {Object} options - The plugin options. |
| 6754 |
*/ |
| 6755 |
/** |
| 6756 |
* @method IPlugin#beforeDatasetsDraw |
| 6757 |
* @desc Called before drawing the `chart` datasets. If any plugin returns `false`, |
| 6758 |
* the datasets drawing is cancelled until another `render` is triggered. |
| 6759 |
* @param {Chart.Controller} chart - The chart instance. |
| 6760 |
* @param {Number} easingValue - The current animation value, between 0.0 and 1.0. |
| 6761 |
* @param {Object} options - The plugin options. |
| 6762 |
* @returns {Boolean} `false` to cancel the chart datasets drawing. |
| 6763 |
*/ |
| 6764 |
/** |
| 6765 |
* @method IPlugin#afterDatasetsDraw |
| 6766 |
* @desc Called after the `chart` datasets have been drawn. Note that this hook |
| 6767 |
* will not be called if the datasets drawing has been previously cancelled. |
| 6768 |
* @param {Chart.Controller} chart - The chart instance. |
| 6769 |
* @param {Number} easingValue - The current animation value, between 0.0 and 1.0. |
| 6770 |
* @param {Object} options - The plugin options. |
| 6771 |
*/ |
| 6772 |
/** |
| 6773 |
* @method IPlugin#beforeDatasetDraw |
| 6774 |
* @desc Called before drawing the `chart` dataset at the given `args.index` (datasets |
| 6775 |
* are drawn in the reverse order). If any plugin returns `false`, the datasets drawing |
| 6776 |
* is cancelled until another `render` is triggered. |
| 6777 |
* @param {Chart} chart - The chart instance. |
| 6778 |
* @param {Object} args - The call arguments. |
| 6779 |
* @param {Number} args.index - The dataset index. |
| 6780 |
* @param {Object} args.meta - The dataset metadata. |
| 6781 |
* @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. |
| 6782 |
* @param {Object} options - The plugin options. |
| 6783 |
* @returns {Boolean} `false` to cancel the chart datasets drawing. |
| 6784 |
*/ |
| 6785 |
/** |
| 6786 |
* @method IPlugin#afterDatasetDraw |
| 6787 |
* @desc Called after the `chart` datasets at the given `args.index` have been drawn |
| 6788 |
* (datasets are drawn in the reverse order). Note that this hook will not be called |
| 6789 |
* if the datasets drawing has been previously cancelled. |
| 6790 |
* @param {Chart} chart - The chart instance. |
| 6791 |
* @param {Object} args - The call arguments. |
| 6792 |
* @param {Number} args.index - The dataset index. |
| 6793 |
* @param {Object} args.meta - The dataset metadata. |
| 6794 |
* @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. |
| 6795 |
* @param {Object} options - The plugin options. |
| 6796 |
*/ |
| 6797 |
/** |
| 6798 |
* @method IPlugin#beforeTooltipDraw |
| 6799 |
* @desc Called before drawing the `tooltip`. If any plugin returns `false`, |
| 6800 |
* the tooltip drawing is cancelled until another `render` is triggered. |
| 6801 |
* @param {Chart} chart - The chart instance. |
| 6802 |
* @param {Object} args - The call arguments. |
| 6803 |
* @param {Object} args.tooltip - The tooltip. |
| 6804 |
* @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. |
| 6805 |
* @param {Object} options - The plugin options. |
| 6806 |
* @returns {Boolean} `false` to cancel the chart tooltip drawing. |
| 6807 |
*/ |
| 6808 |
/** |
| 6809 |
* @method IPlugin#afterTooltipDraw |
| 6810 |
* @desc Called after drawing the `tooltip`. Note that this hook will not |
| 6811 |
* be called if the tooltip drawing has been previously cancelled. |
| 6812 |
* @param {Chart} chart - The chart instance. |
| 6813 |
* @param {Object} args - The call arguments. |
| 6814 |
* @param {Object} args.tooltip - The tooltip. |
| 6815 |
* @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. |
| 6816 |
* @param {Object} options - The plugin options. |
| 6817 |
*/ |
| 6818 |
/** |
| 6819 |
* @method IPlugin#beforeEvent |
| 6820 |
* @desc Called before processing the specified `event`. If any plugin returns `false`, |
| 6821 |
* the event will be discarded. |
| 6822 |
* @param {Chart.Controller} chart - The chart instance. |
| 6823 |
* @param {IEvent} event - The event object. |
| 6824 |
* @param {Object} options - The plugin options. |
| 6825 |
*/ |
| 6826 |
/** |
| 6827 |
* @method IPlugin#afterEvent |
| 6828 |
* @desc Called after the `event` has been consumed. Note that this hook |
| 6829 |
* will not be called if the `event` has been previously discarded. |
| 6830 |
* @param {Chart.Controller} chart - The chart instance. |
| 6831 |
* @param {IEvent} event - The event object. |
| 6832 |
* @param {Object} options - The plugin options. |
| 6833 |
*/ |
| 6834 |
/** |
| 6835 |
* @method IPlugin#resize |
| 6836 |
* @desc Called after the chart as been resized. |
| 6837 |
* @param {Chart.Controller} chart - The chart instance. |
| 6838 |
* @param {Number} size - The new canvas display size (eq. canvas.style width & height). |
| 6839 |
* @param {Object} options - The plugin options. |
| 6840 |
*/ |
| 6841 |
/** |
| 6842 |
* @method IPlugin#destroy |
| 6843 |
* @desc Called after the chart as been destroyed. |
| 6844 |
* @param {Chart.Controller} chart - The chart instance. |
| 6845 |
* @param {Object} options - The plugin options. |
| 6846 |
*/ |
| 6847 |
|
| 6848 |
/** |
| 6849 |
* Provided for backward compatibility, use Chart.plugins instead |
| 6850 |
* @namespace Chart.pluginService |
| 6851 |
* @deprecated since version 2.1.5 |
| 6852 |
* @todo remove at version 3 |
| 6853 |
* @private |
| 6854 |
*/ |
| 6855 |
Chart.pluginService = Chart.plugins; |
| 6856 |
|
| 6857 |
/** |
| 6858 |
* Provided for backward compatibility, inheriting from Chart.PlugingBase has no |
| 6859 |
* effect, instead simply create/register plugins via plain JavaScript objects. |
| 6860 |
* @interface Chart.PluginBase |
| 6861 |
* @deprecated since version 2.5.0 |
| 6862 |
* @todo remove at version 3 |
| 6863 |
* @private |
| 6864 |
*/ |
| 6865 |
Chart.PluginBase = Element.extend({}); |
| 6866 |
}; |
| 6867 |
|
| 6868 |
},{"25":25,"26":26,"45":45}],32:[function(require,module,exports){ |
| 6869 |
'use strict'; |
| 6870 |
|
| 6871 |
var defaults = require(25); |
| 6872 |
var Element = require(26); |
| 6873 |
var helpers = require(45); |
| 6874 |
var Ticks = require(34); |
| 6875 |
|
| 6876 |
defaults._set('scale', { |
| 6877 |
display: true, |
| 6878 |
position: 'left', |
| 6879 |
offset: false, |
| 6880 |
|
| 6881 |
// grid line settings |
| 6882 |
gridLines: { |
| 6883 |
display: true, |
| 6884 |
color: 'rgba(0, 0, 0, 0.1)', |
| 6885 |
lineWidth: 1, |
| 6886 |
drawBorder: true, |
| 6887 |
drawOnChartArea: true, |
| 6888 |
drawTicks: true, |
| 6889 |
tickMarkLength: 10, |
| 6890 |
zeroLineWidth: 1, |
| 6891 |
zeroLineColor: 'rgba(0,0,0,0.25)', |
| 6892 |
zeroLineBorderDash: [], |
| 6893 |
zeroLineBorderDashOffset: 0.0, |
| 6894 |
offsetGridLines: false, |
| 6895 |
borderDash: [], |
| 6896 |
borderDashOffset: 0.0 |
| 6897 |
}, |
| 6898 |
|
| 6899 |
// scale label |
| 6900 |
scaleLabel: { |
| 6901 |
// display property |
| 6902 |
display: false, |
| 6903 |
|
| 6904 |
// actual label |
| 6905 |
labelString: '', |
| 6906 |
|
| 6907 |
// line height |
| 6908 |
lineHeight: 1.2, |
| 6909 |
|
| 6910 |
// top/bottom padding |
| 6911 |
padding: { |
| 6912 |
top: 4, |
| 6913 |
bottom: 4 |
| 6914 |
} |
| 6915 |
}, |
| 6916 |
|
| 6917 |
// label settings |
| 6918 |
ticks: { |
| 6919 |
beginAtZero: false, |
| 6920 |
minRotation: 0, |
| 6921 |
maxRotation: 50, |
| 6922 |
mirror: false, |
| 6923 |
padding: 0, |
| 6924 |
reverse: false, |
| 6925 |
display: true, |
| 6926 |
autoSkip: true, |
| 6927 |
autoSkipPadding: 0, |
| 6928 |
labelOffset: 0, |
| 6929 |
// We pass through arrays to be rendered as multiline labels, we convert Others to strings here. |
| 6930 |
callback: Ticks.formatters.values, |
| 6931 |
minor: {}, |
| 6932 |
major: {} |
| 6933 |
} |
| 6934 |
}); |
| 6935 |
|
| 6936 |
function labelsFromTicks(ticks) { |
| 6937 |
var labels = []; |
| 6938 |
var i, ilen; |
| 6939 |
|
| 6940 |
for (i = 0, ilen = ticks.length; i < ilen; ++i) { |
| 6941 |
labels.push(ticks[i].label); |
| 6942 |
} |
| 6943 |
|
| 6944 |
return labels; |
| 6945 |
} |
| 6946 |
|
| 6947 |
function getLineValue(scale, index, offsetGridLines) { |
| 6948 |
var lineValue = scale.getPixelForTick(index); |
| 6949 |
|
| 6950 |
if (offsetGridLines) { |
| 6951 |
if (index === 0) { |
| 6952 |
lineValue -= (scale.getPixelForTick(1) - lineValue) / 2; |
| 6953 |
} else { |
| 6954 |
lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2; |
| 6955 |
} |
| 6956 |
} |
| 6957 |
return lineValue; |
| 6958 |
} |
| 6959 |
|
| 6960 |
module.exports = function(Chart) { |
| 6961 |
|
| 6962 |
function computeTextSize(context, tick, font) { |
| 6963 |
return helpers.isArray(tick) ? |
| 6964 |
helpers.longestText(context, font, tick) : |
| 6965 |
context.measureText(tick).width; |
| 6966 |
} |
| 6967 |
|
| 6968 |
function parseFontOptions(options) { |
| 6969 |
var valueOrDefault = helpers.valueOrDefault; |
| 6970 |
var globalDefaults = defaults.global; |
| 6971 |
var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); |
| 6972 |
var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); |
| 6973 |
var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); |
| 6974 |
|
| 6975 |
return { |
| 6976 |
size: size, |
| 6977 |
style: style, |
| 6978 |
family: family, |
| 6979 |
font: helpers.fontString(size, style, family) |
| 6980 |
}; |
| 6981 |
} |
| 6982 |
|
| 6983 |
function parseLineHeight(options) { |
| 6984 |
return helpers.options.toLineHeight( |
| 6985 |
helpers.valueOrDefault(options.lineHeight, 1.2), |
| 6986 |
helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); |
| 6987 |
} |
| 6988 |
|
| 6989 |
Chart.Scale = Element.extend({ |
| 6990 |
/** |
| 6991 |
* Get the padding needed for the scale |
| 6992 |
* @method getPadding |
| 6993 |
* @private |
| 6994 |
* @returns {Padding} the necessary padding |
| 6995 |
*/ |
| 6996 |
getPadding: function() { |
| 6997 |
var me = this; |
| 6998 |
return { |
| 6999 |
left: me.paddingLeft || 0, |
| 7000 |
top: me.paddingTop || 0, |
| 7001 |
right: me.paddingRight || 0, |
| 7002 |
bottom: me.paddingBottom || 0 |
| 7003 |
}; |
| 7004 |
}, |
| 7005 |
|
| 7006 |
/** |
| 7007 |
* Returns the scale tick objects ({label, major}) |
| 7008 |
* @since 2.7 |
| 7009 |
*/ |
| 7010 |
getTicks: function() { |
| 7011 |
return this._ticks; |
| 7012 |
}, |
| 7013 |
|
| 7014 |
// These methods are ordered by lifecyle. Utilities then follow. |
| 7015 |
// Any function defined here is inherited by all scale types. |
| 7016 |
// Any function can be extended by the scale type |
| 7017 |
|
| 7018 |
mergeTicksOptions: function() { |
| 7019 |
var ticks = this.options.ticks; |
| 7020 |
if (ticks.minor === false) { |
| 7021 |
ticks.minor = { |
| 7022 |
display: false |
| 7023 |
}; |
| 7024 |
} |
| 7025 |
if (ticks.major === false) { |
| 7026 |
ticks.major = { |
| 7027 |
display: false |
| 7028 |
}; |
| 7029 |
} |
| 7030 |
for (var key in ticks) { |
| 7031 |
if (key !== 'major' && key !== 'minor') { |
| 7032 |
if (typeof ticks.minor[key] === 'undefined') { |
| 7033 |
ticks.minor[key] = ticks[key]; |
| 7034 |
} |
| 7035 |
if (typeof ticks.major[key] === 'undefined') { |
| 7036 |
ticks.major[key] = ticks[key]; |
| 7037 |
} |
| 7038 |
} |
| 7039 |
} |
| 7040 |
}, |
| 7041 |
beforeUpdate: function() { |
| 7042 |
helpers.callback(this.options.beforeUpdate, [this]); |
| 7043 |
}, |
| 7044 |
update: function(maxWidth, maxHeight, margins) { |
| 7045 |
var me = this; |
| 7046 |
var i, ilen, labels, label, ticks, tick; |
| 7047 |
|
| 7048 |
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) |
| 7049 |
me.beforeUpdate(); |
| 7050 |
|
| 7051 |
// Absorb the master measurements |
| 7052 |
me.maxWidth = maxWidth; |
| 7053 |
me.maxHeight = maxHeight; |
| 7054 |
me.margins = helpers.extend({ |
| 7055 |
left: 0, |
| 7056 |
right: 0, |
| 7057 |
top: 0, |
| 7058 |
bottom: 0 |
| 7059 |
}, margins); |
| 7060 |
me.longestTextCache = me.longestTextCache || {}; |
| 7061 |
|
| 7062 |
// Dimensions |
| 7063 |
me.beforeSetDimensions(); |
| 7064 |
me.setDimensions(); |
| 7065 |
me.afterSetDimensions(); |
| 7066 |
|
| 7067 |
// Data min/max |
| 7068 |
me.beforeDataLimits(); |
| 7069 |
me.determineDataLimits(); |
| 7070 |
me.afterDataLimits(); |
| 7071 |
|
| 7072 |
// Ticks - `this.ticks` is now DEPRECATED! |
| 7073 |
// Internal ticks are now stored as objects in the PRIVATE `this._ticks` member |
| 7074 |
// and must not be accessed directly from outside this class. `this.ticks` being |
| 7075 |
// around for long time and not marked as private, we can't change its structure |
| 7076 |
// without unexpected breaking changes. If you need to access the scale ticks, |
| 7077 |
// use scale.getTicks() instead. |
| 7078 |
|
| 7079 |
me.beforeBuildTicks(); |
| 7080 |
|
| 7081 |
// New implementations should return an array of objects but for BACKWARD COMPAT, |
| 7082 |
// we still support no return (`this.ticks` internally set by calling this method). |
| 7083 |
ticks = me.buildTicks() || []; |
| 7084 |
|
| 7085 |
me.afterBuildTicks(); |
| 7086 |
|
| 7087 |
me.beforeTickToLabelConversion(); |
| 7088 |
|
| 7089 |
// New implementations should return the formatted tick labels but for BACKWARD |
| 7090 |
// COMPAT, we still support no return (`this.ticks` internally changed by calling |
| 7091 |
// this method and supposed to contain only string values). |
| 7092 |
labels = me.convertTicksToLabels(ticks) || me.ticks; |
| 7093 |
|
| 7094 |
me.afterTickToLabelConversion(); |
| 7095 |
|
| 7096 |
me.ticks = labels; // BACKWARD COMPATIBILITY |
| 7097 |
|
| 7098 |
// IMPORTANT: from this point, we consider that `this.ticks` will NEVER change! |
| 7099 |
|
| 7100 |
// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) |
| 7101 |
for (i = 0, ilen = labels.length; i < ilen; ++i) { |
| 7102 |
label = labels[i]; |
| 7103 |
tick = ticks[i]; |
| 7104 |
if (!tick) { |
| 7105 |
ticks.push(tick = { |
| 7106 |
label: label, |
| 7107 |
major: false |
| 7108 |
}); |
| 7109 |
} else { |
| 7110 |
tick.label = label; |
| 7111 |
} |
| 7112 |
} |
| 7113 |
|
| 7114 |
me._ticks = ticks; |
| 7115 |
|
| 7116 |
// Tick Rotation |
| 7117 |
me.beforeCalculateTickRotation(); |
| 7118 |
me.calculateTickRotation(); |
| 7119 |
me.afterCalculateTickRotation(); |
| 7120 |
// Fit |
| 7121 |
me.beforeFit(); |
| 7122 |
me.fit(); |
| 7123 |
me.afterFit(); |
| 7124 |
// |
| 7125 |
me.afterUpdate(); |
| 7126 |
|
| 7127 |
return me.minSize; |
| 7128 |
|
| 7129 |
}, |
| 7130 |
afterUpdate: function() { |
| 7131 |
helpers.callback(this.options.afterUpdate, [this]); |
| 7132 |
}, |
| 7133 |
|
| 7134 |
// |
| 7135 |
|
| 7136 |
beforeSetDimensions: function() { |
| 7137 |
helpers.callback(this.options.beforeSetDimensions, [this]); |
| 7138 |
}, |
| 7139 |
setDimensions: function() { |
| 7140 |
var me = this; |
| 7141 |
// Set the unconstrained dimension before label rotation |
| 7142 |
if (me.isHorizontal()) { |
| 7143 |
// Reset position before calculating rotation |
| 7144 |
me.width = me.maxWidth; |
| 7145 |
me.left = 0; |
| 7146 |
me.right = me.width; |
| 7147 |
} else { |
| 7148 |
me.height = me.maxHeight; |
| 7149 |
|
| 7150 |
// Reset position before calculating rotation |
| 7151 |
me.top = 0; |
| 7152 |
me.bottom = me.height; |
| 7153 |
} |
| 7154 |
|
| 7155 |
// Reset padding |
| 7156 |
me.paddingLeft = 0; |
| 7157 |
me.paddingTop = 0; |
| 7158 |
me.paddingRight = 0; |
| 7159 |
me.paddingBottom = 0; |
| 7160 |
}, |
| 7161 |
afterSetDimensions: function() { |
| 7162 |
helpers.callback(this.options.afterSetDimensions, [this]); |
| 7163 |
}, |
| 7164 |
|
| 7165 |
// Data limits |
| 7166 |
beforeDataLimits: function() { |
| 7167 |
helpers.callback(this.options.beforeDataLimits, [this]); |
| 7168 |
}, |
| 7169 |
determineDataLimits: helpers.noop, |
| 7170 |
afterDataLimits: function() { |
| 7171 |
helpers.callback(this.options.afterDataLimits, [this]); |
| 7172 |
}, |
| 7173 |
|
| 7174 |
// |
| 7175 |
beforeBuildTicks: function() { |
| 7176 |
helpers.callback(this.options.beforeBuildTicks, [this]); |
| 7177 |
}, |
| 7178 |
buildTicks: helpers.noop, |
| 7179 |
afterBuildTicks: function() { |
| 7180 |
helpers.callback(this.options.afterBuildTicks, [this]); |
| 7181 |
}, |
| 7182 |
|
| 7183 |
beforeTickToLabelConversion: function() { |
| 7184 |
helpers.callback(this.options.beforeTickToLabelConversion, [this]); |
| 7185 |
}, |
| 7186 |
convertTicksToLabels: function() { |
| 7187 |
var me = this; |
| 7188 |
// Convert ticks to strings |
| 7189 |
var tickOpts = me.options.ticks; |
| 7190 |
me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); |
| 7191 |
}, |
| 7192 |
afterTickToLabelConversion: function() { |
| 7193 |
helpers.callback(this.options.afterTickToLabelConversion, [this]); |
| 7194 |
}, |
| 7195 |
|
| 7196 |
// |
| 7197 |
|
| 7198 |
beforeCalculateTickRotation: function() { |
| 7199 |
helpers.callback(this.options.beforeCalculateTickRotation, [this]); |
| 7200 |
}, |
| 7201 |
calculateTickRotation: function() { |
| 7202 |
var me = this; |
| 7203 |
var context = me.ctx; |
| 7204 |
var tickOpts = me.options.ticks; |
| 7205 |
var labels = labelsFromTicks(me._ticks); |
| 7206 |
|
| 7207 |
// Get the width of each grid by calculating the difference |
| 7208 |
// between x offsets between 0 and 1. |
| 7209 |
var tickFont = parseFontOptions(tickOpts); |
| 7210 |
context.font = tickFont.font; |
| 7211 |
|
| 7212 |
var labelRotation = tickOpts.minRotation || 0; |
| 7213 |
|
| 7214 |
if (labels.length && me.options.display && me.isHorizontal()) { |
| 7215 |
var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache); |
| 7216 |
var labelWidth = originalLabelWidth; |
| 7217 |
var cosRotation, sinRotation; |
| 7218 |
|
| 7219 |
// Allow 3 pixels x2 padding either side for label readability |
| 7220 |
var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; |
| 7221 |
|
| 7222 |
// Max label rotation can be set or default to 90 - also act as a loop counter |
| 7223 |
while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { |
| 7224 |
var angleRadians = helpers.toRadians(labelRotation); |
| 7225 |
cosRotation = Math.cos(angleRadians); |
| 7226 |
sinRotation = Math.sin(angleRadians); |
| 7227 |
|
| 7228 |
if (sinRotation * originalLabelWidth > me.maxHeight) { |
| 7229 |
// go back one step |
| 7230 |
labelRotation--; |
| 7231 |
break; |
| 7232 |
} |
| 7233 |
|
| 7234 |
labelRotation++; |
| 7235 |
labelWidth = cosRotation * originalLabelWidth; |
| 7236 |
} |
| 7237 |
} |
| 7238 |
|
| 7239 |
me.labelRotation = labelRotation; |
| 7240 |
}, |
| 7241 |
afterCalculateTickRotation: function() { |
| 7242 |
helpers.callback(this.options.afterCalculateTickRotation, [this]); |
| 7243 |
}, |
| 7244 |
|
| 7245 |
// |
| 7246 |
|
| 7247 |
beforeFit: function() { |
| 7248 |
helpers.callback(this.options.beforeFit, [this]); |
| 7249 |
}, |
| 7250 |
fit: function() { |
| 7251 |
var me = this; |
| 7252 |
// Reset |
| 7253 |
var minSize = me.minSize = { |
| 7254 |
width: 0, |
| 7255 |
height: 0 |
| 7256 |
}; |
| 7257 |
|
| 7258 |
var labels = labelsFromTicks(me._ticks); |
| 7259 |
|
| 7260 |
var opts = me.options; |
| 7261 |
var tickOpts = opts.ticks; |
| 7262 |
var scaleLabelOpts = opts.scaleLabel; |
| 7263 |
var gridLineOpts = opts.gridLines; |
| 7264 |
var display = opts.display; |
| 7265 |
var isHorizontal = me.isHorizontal(); |
| 7266 |
|
| 7267 |
var tickFont = parseFontOptions(tickOpts); |
| 7268 |
var tickMarkLength = opts.gridLines.tickMarkLength; |
| 7269 |
|
| 7270 |
// Width |
| 7271 |
if (isHorizontal) { |
| 7272 |
// subtract the margins to line up with the chartArea if we are a full width scale |
| 7273 |
minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; |
| 7274 |
} else { |
| 7275 |
minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0; |
| 7276 |
} |
| 7277 |
|
| 7278 |
// height |
| 7279 |
if (isHorizontal) { |
| 7280 |
minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0; |
| 7281 |
} else { |
| 7282 |
minSize.height = me.maxHeight; // fill all the height |
| 7283 |
} |
| 7284 |
|
| 7285 |
// Are we showing a title for the scale? |
| 7286 |
if (scaleLabelOpts.display && display) { |
| 7287 |
var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); |
| 7288 |
var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); |
| 7289 |
var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height; |
| 7290 |
|
| 7291 |
if (isHorizontal) { |
| 7292 |
minSize.height += deltaHeight; |
| 7293 |
} else { |
| 7294 |
minSize.width += deltaHeight; |
| 7295 |
} |
| 7296 |
} |
| 7297 |
|
| 7298 |
// Don't bother fitting the ticks if we are not showing them |
| 7299 |
if (tickOpts.display && display) { |
| 7300 |
var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache); |
| 7301 |
var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels); |
| 7302 |
var lineSpace = tickFont.size * 0.5; |
| 7303 |
var tickPadding = me.options.ticks.padding; |
| 7304 |
|
| 7305 |
if (isHorizontal) { |
| 7306 |
// A horizontal axis is more constrained by the height. |
| 7307 |
me.longestLabelWidth = largestTextWidth; |
| 7308 |
|
| 7309 |
var angleRadians = helpers.toRadians(me.labelRotation); |
| 7310 |
var cosRotation = Math.cos(angleRadians); |
| 7311 |
var sinRotation = Math.sin(angleRadians); |
| 7312 |
|
| 7313 |
// TODO - improve this calculation |
| 7314 |
var labelHeight = (sinRotation * largestTextWidth) |
| 7315 |
+ (tickFont.size * tallestLabelHeightInLines) |
| 7316 |
+ (lineSpace * (tallestLabelHeightInLines - 1)) |
| 7317 |
+ lineSpace; // padding |
| 7318 |
|
| 7319 |
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); |
| 7320 |
|
| 7321 |
me.ctx.font = tickFont.font; |
| 7322 |
var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font); |
| 7323 |
var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font); |
| 7324 |
|
| 7325 |
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned |
| 7326 |
// which means that the right padding is dominated by the font height |
| 7327 |
if (me.labelRotation !== 0) { |
| 7328 |
me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges |
| 7329 |
me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3; |
| 7330 |
} else { |
| 7331 |
me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges |
| 7332 |
me.paddingRight = lastLabelWidth / 2 + 3; |
| 7333 |
} |
| 7334 |
} else { |
| 7335 |
// A vertical axis is more constrained by the width. Labels are the |
| 7336 |
// dominant factor here, so get that length first and account for padding |
| 7337 |
if (tickOpts.mirror) { |
| 7338 |
largestTextWidth = 0; |
| 7339 |
} else { |
| 7340 |
// use lineSpace for consistency with horizontal axis |
| 7341 |
// tickPadding is not implemented for horizontal |
| 7342 |
largestTextWidth += tickPadding + lineSpace; |
| 7343 |
} |
| 7344 |
|
| 7345 |
minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth); |
| 7346 |
|
| 7347 |
me.paddingTop = tickFont.size / 2; |
| 7348 |
me.paddingBottom = tickFont.size / 2; |
| 7349 |
} |
| 7350 |
} |
| 7351 |
|
| 7352 |
me.handleMargins(); |
| 7353 |
|
| 7354 |
me.width = minSize.width; |
| 7355 |
me.height = minSize.height; |
| 7356 |
}, |
| 7357 |
|
| 7358 |
/** |
| 7359 |
* Handle margins and padding interactions |
| 7360 |
* @private |
| 7361 |
*/ |
| 7362 |
handleMargins: function() { |
| 7363 |
var me = this; |
| 7364 |
if (me.margins) { |
| 7365 |
me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); |
| 7366 |
me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); |
| 7367 |
me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); |
| 7368 |
me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); |
| 7369 |
} |
| 7370 |
}, |
| 7371 |
|
| 7372 |
afterFit: function() { |
| 7373 |
helpers.callback(this.options.afterFit, [this]); |
| 7374 |
}, |
| 7375 |
|
| 7376 |
// Shared Methods |
| 7377 |
isHorizontal: function() { |
| 7378 |
return this.options.position === 'top' || this.options.position === 'bottom'; |
| 7379 |
}, |
| 7380 |
isFullWidth: function() { |
| 7381 |
return (this.options.fullWidth); |
| 7382 |
}, |
| 7383 |
|
| 7384 |
// 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 |
| 7385 |
getRightValue: function(rawValue) { |
| 7386 |
// Null and undefined values first |
| 7387 |
if (helpers.isNullOrUndef(rawValue)) { |
| 7388 |
return NaN; |
| 7389 |
} |
| 7390 |
// isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values |
| 7391 |
if (typeof rawValue === 'number' && !isFinite(rawValue)) { |
| 7392 |
return NaN; |
| 7393 |
} |
| 7394 |
// If it is in fact an object, dive in one more level |
| 7395 |
if (rawValue) { |
| 7396 |
if (this.isHorizontal()) { |
| 7397 |
if (rawValue.x !== undefined) { |
| 7398 |
return this.getRightValue(rawValue.x); |
| 7399 |
} |
| 7400 |
} else if (rawValue.y !== undefined) { |
| 7401 |
return this.getRightValue(rawValue.y); |
| 7402 |
} |
| 7403 |
} |
| 7404 |
|
| 7405 |
// Value is good, return it |
| 7406 |
return rawValue; |
| 7407 |
}, |
| 7408 |
|
| 7409 |
/** |
| 7410 |
* Used to get the value to display in the tooltip for the data at the given index |
| 7411 |
* @param index |
| 7412 |
* @param datasetIndex |
| 7413 |
*/ |
| 7414 |
getLabelForIndex: helpers.noop, |
| 7415 |
|
| 7416 |
/** |
| 7417 |
* Returns the location of the given data point. Value can either be an index or a numerical value |
| 7418 |
* The coordinate (0, 0) is at the upper-left corner of the canvas |
| 7419 |
* @param value |
| 7420 |
* @param index |
| 7421 |
* @param datasetIndex |
| 7422 |
*/ |
| 7423 |
getPixelForValue: helpers.noop, |
| 7424 |
|
| 7425 |
/** |
| 7426 |
* Used to get the data value from a given pixel. This is the inverse of getPixelForValue |
| 7427 |
* The coordinate (0, 0) is at the upper-left corner of the canvas |
| 7428 |
* @param pixel |
| 7429 |
*/ |
| 7430 |
getValueForPixel: helpers.noop, |
| 7431 |
|
| 7432 |
/** |
| 7433 |
* Returns the location of the tick at the given index |
| 7434 |
* The coordinate (0, 0) is at the upper-left corner of the canvas |
| 7435 |
*/ |
| 7436 |
getPixelForTick: function(index) { |
| 7437 |
var me = this; |
| 7438 |
var offset = me.options.offset; |
| 7439 |
if (me.isHorizontal()) { |
| 7440 |
var innerWidth = me.width - (me.paddingLeft + me.paddingRight); |
| 7441 |
var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1); |
| 7442 |
var pixel = (tickWidth * index) + me.paddingLeft; |
| 7443 |
|
| 7444 |
if (offset) { |
| 7445 |
pixel += tickWidth / 2; |
| 7446 |
} |
| 7447 |
|
| 7448 |
var finalVal = me.left + Math.round(pixel); |
| 7449 |
finalVal += me.isFullWidth() ? me.margins.left : 0; |
| 7450 |
return finalVal; |
| 7451 |
} |
| 7452 |
var innerHeight = me.height - (me.paddingTop + me.paddingBottom); |
| 7453 |
return me.top + (index * (innerHeight / (me._ticks.length - 1))); |
| 7454 |
}, |
| 7455 |
|
| 7456 |
/** |
| 7457 |
* Utility for getting the pixel location of a percentage of scale |
| 7458 |
* The coordinate (0, 0) is at the upper-left corner of the canvas |
| 7459 |
*/ |
| 7460 |
getPixelForDecimal: function(decimal) { |
| 7461 |
var me = this; |
| 7462 |
if (me.isHorizontal()) { |
| 7463 |
var innerWidth = me.width - (me.paddingLeft + me.paddingRight); |
| 7464 |
var valueOffset = (innerWidth * decimal) + me.paddingLeft; |
| 7465 |
|
| 7466 |
var finalVal = me.left + Math.round(valueOffset); |
| 7467 |
finalVal += me.isFullWidth() ? me.margins.left : 0; |
| 7468 |
return finalVal; |
| 7469 |
} |
| 7470 |
return me.top + (decimal * me.height); |
| 7471 |
}, |
| 7472 |
|
| 7473 |
/** |
| 7474 |
* Returns the pixel for the minimum chart value |
| 7475 |
* The coordinate (0, 0) is at the upper-left corner of the canvas |
| 7476 |
*/ |
| 7477 |
getBasePixel: function() { |
| 7478 |
return this.getPixelForValue(this.getBaseValue()); |
| 7479 |
}, |
| 7480 |
|
| 7481 |
getBaseValue: function() { |
| 7482 |
var me = this; |
| 7483 |
var min = me.min; |
| 7484 |
var max = me.max; |
| 7485 |
|
| 7486 |
return me.beginAtZero ? 0 : |
| 7487 |
min < 0 && max < 0 ? max : |
| 7488 |
min > 0 && max > 0 ? min : |
| 7489 |
0; |
| 7490 |
}, |
| 7491 |
|
| 7492 |
/** |
| 7493 |
* Returns a subset of ticks to be plotted to avoid overlapping labels. |
| 7494 |
* @private |
| 7495 |
*/ |
| 7496 |
_autoSkip: function(ticks) { |
| 7497 |
var skipRatio; |
| 7498 |
var me = this; |
| 7499 |
var isHorizontal = me.isHorizontal(); |
| 7500 |
var optionTicks = me.options.ticks.minor; |
| 7501 |
var tickCount = ticks.length; |
| 7502 |
var labelRotationRadians = helpers.toRadians(me.labelRotation); |
| 7503 |
var cosRotation = Math.cos(labelRotationRadians); |
| 7504 |
var longestRotatedLabel = me.longestLabelWidth * cosRotation; |
| 7505 |
var result = []; |
| 7506 |
var i, tick, shouldSkip; |
| 7507 |
|
| 7508 |
// figure out the maximum number of gridlines to show |
| 7509 |
var maxTicks; |
| 7510 |
if (optionTicks.maxTicksLimit) { |
| 7511 |
maxTicks = optionTicks.maxTicksLimit; |
| 7512 |
} |
| 7513 |
|
| 7514 |
if (isHorizontal) { |
| 7515 |
skipRatio = false; |
| 7516 |
|
| 7517 |
if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) { |
| 7518 |
skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight))); |
| 7519 |
} |
| 7520 |
|
| 7521 |
// if they defined a max number of optionTicks, |
| 7522 |
// increase skipRatio until that number is met |
| 7523 |
if (maxTicks && tickCount > maxTicks) { |
| 7524 |
skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks)); |
| 7525 |
} |
| 7526 |
} |
| 7527 |
|
| 7528 |
for (i = 0; i < tickCount; i++) { |
| 7529 |
tick = ticks[i]; |
| 7530 |
|
| 7531 |
// Since we always show the last tick,we need may need to hide the last shown one before |
| 7532 |
shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount); |
| 7533 |
if (shouldSkip && i !== tickCount - 1) { |
| 7534 |
// leave tick in place but make sure it's not displayed (#4635) |
| 7535 |
delete tick.label; |
| 7536 |
} |
| 7537 |
result.push(tick); |
| 7538 |
} |
| 7539 |
return result; |
| 7540 |
}, |
| 7541 |
|
| 7542 |
// Actually draw the scale on the canvas |
| 7543 |
// @param {rectangle} chartArea : the area of the chart to draw full grid lines on |
| 7544 |
draw: function(chartArea) { |
| 7545 |
var me = this; |
| 7546 |
var options = me.options; |
| 7547 |
if (!options.display) { |
| 7548 |
return; |
| 7549 |
} |
| 7550 |
|
| 7551 |
var context = me.ctx; |
| 7552 |
var globalDefaults = defaults.global; |
| 7553 |
var optionTicks = options.ticks.minor; |
| 7554 |
var optionMajorTicks = options.ticks.major || optionTicks; |
| 7555 |
var gridLines = options.gridLines; |
| 7556 |
var scaleLabel = options.scaleLabel; |
| 7557 |
|
| 7558 |
var isRotated = me.labelRotation !== 0; |
| 7559 |
var isHorizontal = me.isHorizontal(); |
| 7560 |
|
| 7561 |
var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); |
| 7562 |
var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); |
| 7563 |
var tickFont = parseFontOptions(optionTicks); |
| 7564 |
var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); |
| 7565 |
var majorTickFont = parseFontOptions(optionMajorTicks); |
| 7566 |
|
| 7567 |
var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; |
| 7568 |
|
| 7569 |
var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); |
| 7570 |
var scaleLabelFont = parseFontOptions(scaleLabel); |
| 7571 |
var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); |
| 7572 |
var labelRotationRadians = helpers.toRadians(me.labelRotation); |
| 7573 |
|
| 7574 |
var itemsToDraw = []; |
| 7575 |
|
| 7576 |
var xTickStart = options.position === 'right' ? me.left : me.right - tl; |
| 7577 |
var xTickEnd = options.position === 'right' ? me.left + tl : me.right; |
| 7578 |
var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl; |
| 7579 |
var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom; |
| 7580 |
|
| 7581 |
helpers.each(ticks, function(tick, index) { |
| 7582 |
// autoskipper skipped this tick (#4635) |
| 7583 |
if (helpers.isNullOrUndef(tick.label)) { |
| 7584 |
return; |
| 7585 |
} |
| 7586 |
|
| 7587 |
var label = tick.label; |
| 7588 |
var lineWidth, lineColor, borderDash, borderDashOffset; |
| 7589 |
if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) { |
| 7590 |
// Draw the first index specially |
| 7591 |
lineWidth = gridLines.zeroLineWidth; |
| 7592 |
lineColor = gridLines.zeroLineColor; |
| 7593 |
borderDash = gridLines.zeroLineBorderDash; |
| 7594 |
borderDashOffset = gridLines.zeroLineBorderDashOffset; |
| 7595 |
} else { |
| 7596 |
lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index); |
| 7597 |
lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index); |
| 7598 |
borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash); |
| 7599 |
borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset); |
| 7600 |
} |
| 7601 |
|
| 7602 |
// Common properties |
| 7603 |
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; |
| 7604 |
var textAlign = 'middle'; |
| 7605 |
var textBaseline = 'middle'; |
| 7606 |
var tickPadding = optionTicks.padding; |
| 7607 |
|
| 7608 |
if (isHorizontal) { |
| 7609 |
var labelYOffset = tl + tickPadding; |
| 7610 |
|
| 7611 |
if (options.position === 'bottom') { |
| 7612 |
// bottom |
| 7613 |
textBaseline = !isRotated ? 'top' : 'middle'; |
| 7614 |
textAlign = !isRotated ? 'center' : 'right'; |
| 7615 |
labelY = me.top + labelYOffset; |
| 7616 |
} else { |
| 7617 |
// top |
| 7618 |
textBaseline = !isRotated ? 'bottom' : 'middle'; |
| 7619 |
textAlign = !isRotated ? 'center' : 'left'; |
| 7620 |
labelY = me.bottom - labelYOffset; |
| 7621 |
} |
| 7622 |
|
| 7623 |
var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); |
| 7624 |
if (xLineValue < me.left) { |
| 7625 |
lineColor = 'rgba(0,0,0,0)'; |
| 7626 |
} |
| 7627 |
xLineValue += helpers.aliasPixel(lineWidth); |
| 7628 |
|
| 7629 |
labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) |
| 7630 |
|
| 7631 |
tx1 = tx2 = x1 = x2 = xLineValue; |
| 7632 |
ty1 = yTickStart; |
| 7633 |
ty2 = yTickEnd; |
| 7634 |
y1 = chartArea.top; |
| 7635 |
y2 = chartArea.bottom; |
| 7636 |
} else { |
| 7637 |
var isLeft = options.position === 'left'; |
| 7638 |
var labelXOffset; |
| 7639 |
|
| 7640 |
if (optionTicks.mirror) { |
| 7641 |
textAlign = isLeft ? 'left' : 'right'; |
| 7642 |
labelXOffset = tickPadding; |
| 7643 |
} else { |
| 7644 |
textAlign = isLeft ? 'right' : 'left'; |
| 7645 |
labelXOffset = tl + tickPadding; |
| 7646 |
} |
| 7647 |
|
| 7648 |
labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset; |
| 7649 |
|
| 7650 |
var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); |
| 7651 |
if (yLineValue < me.top) { |
| 7652 |
lineColor = 'rgba(0,0,0,0)'; |
| 7653 |
} |
| 7654 |
yLineValue += helpers.aliasPixel(lineWidth); |
| 7655 |
|
| 7656 |
labelY = me.getPixelForTick(index) + optionTicks.labelOffset; |
| 7657 |
|
| 7658 |
tx1 = xTickStart; |
| 7659 |
tx2 = xTickEnd; |
| 7660 |
x1 = chartArea.left; |
| 7661 |
x2 = chartArea.right; |
| 7662 |
ty1 = ty2 = y1 = y2 = yLineValue; |
| 7663 |
} |
| 7664 |
|
| 7665 |
itemsToDraw.push({ |
| 7666 |
tx1: tx1, |
| 7667 |
ty1: ty1, |
| 7668 |
tx2: tx2, |
| 7669 |
ty2: ty2, |
| 7670 |
x1: x1, |
| 7671 |
y1: y1, |
| 7672 |
x2: x2, |
| 7673 |
y2: y2, |
| 7674 |
labelX: labelX, |
| 7675 |
labelY: labelY, |
| 7676 |
glWidth: lineWidth, |
| 7677 |
glColor: lineColor, |
| 7678 |
glBorderDash: borderDash, |
| 7679 |
glBorderDashOffset: borderDashOffset, |
| 7680 |
rotation: -1 * labelRotationRadians, |
| 7681 |
label: label, |
| 7682 |
major: tick.major, |
| 7683 |
textBaseline: textBaseline, |
| 7684 |
textAlign: textAlign |
| 7685 |
}); |
| 7686 |
}); |
| 7687 |
|
| 7688 |
// Draw all of the tick labels, tick marks, and grid lines at the correct places |
| 7689 |
helpers.each(itemsToDraw, function(itemToDraw) { |
| 7690 |
if (gridLines.display) { |
| 7691 |
context.save(); |
| 7692 |
context.lineWidth = itemToDraw.glWidth; |
| 7693 |
context.strokeStyle = itemToDraw.glColor; |
| 7694 |
if (context.setLineDash) { |
| 7695 |
context.setLineDash(itemToDraw.glBorderDash); |
| 7696 |
context.lineDashOffset = itemToDraw.glBorderDashOffset; |
| 7697 |
} |
| 7698 |
|
| 7699 |
context.beginPath(); |
| 7700 |
|
| 7701 |
if (gridLines.drawTicks) { |
| 7702 |
context.moveTo(itemToDraw.tx1, itemToDraw.ty1); |
| 7703 |
context.lineTo(itemToDraw.tx2, itemToDraw.ty2); |
| 7704 |
} |
| 7705 |
|
| 7706 |
if (gridLines.drawOnChartArea) { |
| 7707 |
context.moveTo(itemToDraw.x1, itemToDraw.y1); |
| 7708 |
context.lineTo(itemToDraw.x2, itemToDraw.y2); |
| 7709 |
} |
| 7710 |
|
| 7711 |
context.stroke(); |
| 7712 |
context.restore(); |
| 7713 |
} |
| 7714 |
|
| 7715 |
if (optionTicks.display) { |
| 7716 |
// Make sure we draw text in the correct color and font |
| 7717 |
context.save(); |
| 7718 |
context.translate(itemToDraw.labelX, itemToDraw.labelY); |
| 7719 |
context.rotate(itemToDraw.rotation); |
| 7720 |
context.font = itemToDraw.major ? majorTickFont.font : tickFont.font; |
| 7721 |
context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; |
| 7722 |
context.textBaseline = itemToDraw.textBaseline; |
| 7723 |
context.textAlign = itemToDraw.textAlign; |
| 7724 |
|
| 7725 |
var label = itemToDraw.label; |
| 7726 |
if (helpers.isArray(label)) { |
| 7727 |
for (var i = 0, y = 0; i < label.length; ++i) { |
| 7728 |
// We just make sure the multiline element is a string here.. |
| 7729 |
context.fillText('' + label[i], 0, y); |
| 7730 |
// apply same lineSpacing as calculated @ L#320 |
| 7731 |
y += (tickFont.size * 1.5); |
| 7732 |
} |
| 7733 |
} else { |
| 7734 |
context.fillText(label, 0, 0); |
| 7735 |
} |
| 7736 |
context.restore(); |
| 7737 |
} |
| 7738 |
}); |
| 7739 |
|
| 7740 |
if (scaleLabel.display) { |
| 7741 |
// Draw the scale label |
| 7742 |
var scaleLabelX; |
| 7743 |
var scaleLabelY; |
| 7744 |
var rotation = 0; |
| 7745 |
var halfLineHeight = parseLineHeight(scaleLabel) / 2; |
| 7746 |
|
| 7747 |
if (isHorizontal) { |
| 7748 |
scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width |
| 7749 |
scaleLabelY = options.position === 'bottom' |
| 7750 |
? me.bottom - halfLineHeight - scaleLabelPadding.bottom |
| 7751 |
: me.top + halfLineHeight + scaleLabelPadding.top; |
| 7752 |
} else { |
| 7753 |
var isLeft = options.position === 'left'; |
| 7754 |
scaleLabelX = isLeft |
| 7755 |
? me.left + halfLineHeight + scaleLabelPadding.top |
| 7756 |
: me.right - halfLineHeight - scaleLabelPadding.top; |
| 7757 |
scaleLabelY = me.top + ((me.bottom - me.top) / 2); |
| 7758 |
rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; |
| 7759 |
} |
| 7760 |
|
| 7761 |
context.save(); |
| 7762 |
context.translate(scaleLabelX, scaleLabelY); |
| 7763 |
context.rotate(rotation); |
| 7764 |
context.textAlign = 'center'; |
| 7765 |
context.textBaseline = 'middle'; |
| 7766 |
context.fillStyle = scaleLabelFontColor; // render in correct colour |
| 7767 |
context.font = scaleLabelFont.font; |
| 7768 |
context.fillText(scaleLabel.labelString, 0, 0); |
| 7769 |
context.restore(); |
| 7770 |
} |
| 7771 |
|
| 7772 |
if (gridLines.drawBorder) { |
| 7773 |
// Draw the line at the edge of the axis |
| 7774 |
context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0); |
| 7775 |
context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0); |
| 7776 |
var x1 = me.left; |
| 7777 |
var x2 = me.right; |
| 7778 |
var y1 = me.top; |
| 7779 |
var y2 = me.bottom; |
| 7780 |
|
| 7781 |
var aliasPixel = helpers.aliasPixel(context.lineWidth); |
| 7782 |
if (isHorizontal) { |
| 7783 |
y1 = y2 = options.position === 'top' ? me.bottom : me.top; |
| 7784 |
y1 += aliasPixel; |
| 7785 |
y2 += aliasPixel; |
| 7786 |
} else { |
| 7787 |
x1 = x2 = options.position === 'left' ? me.right : me.left; |
| 7788 |
x1 += aliasPixel; |
| 7789 |
x2 += aliasPixel; |
| 7790 |
} |
| 7791 |
|
| 7792 |
context.beginPath(); |
| 7793 |
context.moveTo(x1, y1); |
| 7794 |
context.lineTo(x2, y2); |
| 7795 |
context.stroke(); |
| 7796 |
} |
| 7797 |
} |
| 7798 |
}); |
| 7799 |
}; |
| 7800 |
|
| 7801 |
},{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){ |
| 7802 |
'use strict'; |
| 7803 |
|
| 7804 |
var defaults = require(25); |
| 7805 |
var helpers = require(45); |
| 7806 |
|
| 7807 |
module.exports = function(Chart) { |
| 7808 |
|
| 7809 |
Chart.scaleService = { |
| 7810 |
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then |
| 7811 |
// use the new chart options to grab the correct scale |
| 7812 |
constructors: {}, |
| 7813 |
// Use a registration function so that we can move to an ES6 map when we no longer need to support |
| 7814 |
// old browsers |
| 7815 |
|
| 7816 |
// Scale config defaults |
| 7817 |
defaults: {}, |
| 7818 |
registerScaleType: function(type, scaleConstructor, scaleDefaults) { |
| 7819 |
this.constructors[type] = scaleConstructor; |
| 7820 |
this.defaults[type] = helpers.clone(scaleDefaults); |
| 7821 |
}, |
| 7822 |
getScaleConstructor: function(type) { |
| 7823 |
return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; |
| 7824 |
}, |
| 7825 |
getScaleDefaults: function(type) { |
| 7826 |
// Return the scale defaults merged with the global settings so that we always use the latest ones |
| 7827 |
return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {}; |
| 7828 |
}, |
| 7829 |
updateScaleDefaults: function(type, additions) { |
| 7830 |
var me = this; |
| 7831 |
if (me.defaults.hasOwnProperty(type)) { |
| 7832 |
me.defaults[type] = helpers.extend(me.defaults[type], additions); |
| 7833 |
} |
| 7834 |
}, |
| 7835 |
addScalesToLayout: function(chart) { |
| 7836 |
// Adds each scale to the chart.boxes array to be sized accordingly |
| 7837 |
helpers.each(chart.scales, function(scale) { |
| 7838 |
// Set ILayoutItem parameters for backwards compatibility |
| 7839 |
scale.fullWidth = scale.options.fullWidth; |
| 7840 |
scale.position = scale.options.position; |
| 7841 |
scale.weight = scale.options.weight; |
| 7842 |
Chart.layoutService.addBox(chart, scale); |
| 7843 |
}); |
| 7844 |
} |
| 7845 |
}; |
| 7846 |
}; |
| 7847 |
|
| 7848 |
},{"25":25,"45":45}],34:[function(require,module,exports){ |
| 7849 |
'use strict'; |
| 7850 |
|
| 7851 |
var helpers = require(45); |
| 7852 |
|
| 7853 |
/** |
| 7854 |
* Namespace to hold static tick generation functions |
| 7855 |
* @namespace Chart.Ticks |
| 7856 |
*/ |
| 7857 |
module.exports = { |
| 7858 |
/** |
| 7859 |
* Namespace to hold generators for different types of ticks |
| 7860 |
* @namespace Chart.Ticks.generators |
| 7861 |
*/ |
| 7862 |
generators: { |
| 7863 |
/** |
| 7864 |
* Interface for the options provided to the numeric tick generator |
| 7865 |
* @interface INumericTickGenerationOptions |
| 7866 |
*/ |
| 7867 |
/** |
| 7868 |
* The maximum number of ticks to display |
| 7869 |
* @name INumericTickGenerationOptions#maxTicks |
| 7870 |
* @type Number |
| 7871 |
*/ |
| 7872 |
/** |
| 7873 |
* The distance between each tick. |
| 7874 |
* @name INumericTickGenerationOptions#stepSize |
| 7875 |
* @type Number |
| 7876 |
* @optional |
| 7877 |
*/ |
| 7878 |
/** |
| 7879 |
* Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum |
| 7880 |
* @name INumericTickGenerationOptions#min |
| 7881 |
* @type Number |
| 7882 |
* @optional |
| 7883 |
*/ |
| 7884 |
/** |
| 7885 |
* The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum |
| 7886 |
* @name INumericTickGenerationOptions#max |
| 7887 |
* @type Number |
| 7888 |
* @optional |
| 7889 |
*/ |
| 7890 |
|
| 7891 |
/** |
| 7892 |
* Generate a set of linear ticks |
| 7893 |
* @method Chart.Ticks.generators.linear |
| 7894 |
* @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks |
| 7895 |
* @param dataRange {IRange} the range of the data |
| 7896 |
* @returns {Array<Number>} array of tick values |
| 7897 |
*/ |
| 7898 |
linear: function(generationOptions, dataRange) { |
| 7899 |
var ticks = []; |
| 7900 |
// To get a "nice" value for the tick spacing, we will use the appropriately named |
| 7901 |
// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks |
| 7902 |
// for details. |
| 7903 |
|
| 7904 |
var spacing; |
| 7905 |
if (generationOptions.stepSize && generationOptions.stepSize > 0) { |
| 7906 |
spacing = generationOptions.stepSize; |
| 7907 |
} else { |
| 7908 |
var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); |
| 7909 |
spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true); |
| 7910 |
} |
| 7911 |
var niceMin = Math.floor(dataRange.min / spacing) * spacing; |
| 7912 |
var niceMax = Math.ceil(dataRange.max / spacing) * spacing; |
| 7913 |
|
| 7914 |
// If min, max and stepSize is set and they make an evenly spaced scale use it. |
| 7915 |
if (generationOptions.min && generationOptions.max && generationOptions.stepSize) { |
| 7916 |
// If very close to our whole number, use it. |
| 7917 |
if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) { |
| 7918 |
niceMin = generationOptions.min; |
| 7919 |
niceMax = generationOptions.max; |
| 7920 |
} |
| 7921 |
} |
| 7922 |
|
| 7923 |
var numSpaces = (niceMax - niceMin) / spacing; |
| 7924 |
// If very close to our rounded value, use it. |
| 7925 |
if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { |
| 7926 |
numSpaces = Math.round(numSpaces); |
| 7927 |
} else { |
| 7928 |
numSpaces = Math.ceil(numSpaces); |
| 7929 |
} |
| 7930 |
|
| 7931 |
// Put the values into the ticks array |
| 7932 |
ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin); |
| 7933 |
for (var j = 1; j < numSpaces; ++j) { |
| 7934 |
ticks.push(niceMin + (j * spacing)); |
| 7935 |
} |
| 7936 |
ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax); |
| 7937 |
|
| 7938 |
return ticks; |
| 7939 |
}, |
| 7940 |
|
| 7941 |
/** |
| 7942 |
* Generate a set of logarithmic ticks |
| 7943 |
* @method Chart.Ticks.generators.logarithmic |
| 7944 |
* @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks |
| 7945 |
* @param dataRange {IRange} the range of the data |
| 7946 |
* @returns {Array<Number>} array of tick values |
| 7947 |
*/ |
| 7948 |
logarithmic: function(generationOptions, dataRange) { |
| 7949 |
var ticks = []; |
| 7950 |
var valueOrDefault = helpers.valueOrDefault; |
| 7951 |
|
| 7952 |
// Figure out what the max number of ticks we can support it is based on the size of |
| 7953 |
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50 |
| 7954 |
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on |
| 7955 |
// the graph |
| 7956 |
var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min)))); |
| 7957 |
|
| 7958 |
var endExp = Math.floor(helpers.log10(dataRange.max)); |
| 7959 |
var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); |
| 7960 |
var exp, significand; |
| 7961 |
|
| 7962 |
if (tickVal === 0) { |
| 7963 |
exp = Math.floor(helpers.log10(dataRange.minNotZero)); |
| 7964 |
significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); |
| 7965 |
|
| 7966 |
ticks.push(tickVal); |
| 7967 |
tickVal = significand * Math.pow(10, exp); |
| 7968 |
} else { |
| 7969 |
exp = Math.floor(helpers.log10(tickVal)); |
| 7970 |
significand = Math.floor(tickVal / Math.pow(10, exp)); |
| 7971 |
} |
| 7972 |
|
| 7973 |
do { |
| 7974 |
ticks.push(tickVal); |
| 7975 |
|
| 7976 |
++significand; |
| 7977 |
if (significand === 10) { |
| 7978 |
significand = 1; |
| 7979 |
++exp; |
| 7980 |
} |
| 7981 |
|
| 7982 |
tickVal = significand * Math.pow(10, exp); |
| 7983 |
} while (exp < endExp || (exp === endExp && significand < endSignificand)); |
| 7984 |
|
| 7985 |
var lastTick = valueOrDefault(generationOptions.max, tickVal); |
| 7986 |
ticks.push(lastTick); |
| 7987 |
|
| 7988 |
return ticks; |
| 7989 |
} |
| 7990 |
}, |
| 7991 |
|
| 7992 |
/** |
| 7993 |
* Namespace to hold formatters for different types of ticks |
| 7994 |
* @namespace Chart.Ticks.formatters |
| 7995 |
*/ |
| 7996 |
formatters: { |
| 7997 |
/** |
| 7998 |
* Formatter for value labels |
| 7999 |
* @method Chart.Ticks.formatters.values |
| 8000 |
* @param value the value to display |
| 8001 |
* @return {String|Array} the label to display |
| 8002 |
*/ |
| 8003 |
values: function(value) { |
| 8004 |
return helpers.isArray(value) ? value : '' + value; |
| 8005 |
}, |
| 8006 |
|
| 8007 |
/** |
| 8008 |
* Formatter for linear numeric ticks |
| 8009 |
* @method Chart.Ticks.formatters.linear |
| 8010 |
* @param tickValue {Number} the value to be formatted |
| 8011 |
* @param index {Number} the position of the tickValue parameter in the ticks array |
| 8012 |
* @param ticks {Array<Number>} the list of ticks being converted |
| 8013 |
* @return {String} string representation of the tickValue parameter |
| 8014 |
*/ |
| 8015 |
linear: function(tickValue, index, ticks) { |
| 8016 |
// If we have lots of ticks, don't use the ones |
| 8017 |
var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; |
| 8018 |
|
| 8019 |
// If we have a number like 2.5 as the delta, figure out how many decimal places we need |
| 8020 |
if (Math.abs(delta) > 1) { |
| 8021 |
if (tickValue !== Math.floor(tickValue)) { |
| 8022 |
// not an integer |
| 8023 |
delta = tickValue - Math.floor(tickValue); |
| 8024 |
} |
| 8025 |
} |
| 8026 |
|
| 8027 |
var logDelta = helpers.log10(Math.abs(delta)); |
| 8028 |
var tickString = ''; |
| 8029 |
|
| 8030 |
if (tickValue !== 0) { |
| 8031 |
var numDecimal = -1 * Math.floor(logDelta); |
| 8032 |
numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places |
| 8033 |
tickString = tickValue.toFixed(numDecimal); |
| 8034 |
} else { |
| 8035 |
tickString = '0'; // never show decimal places for 0 |
| 8036 |
} |
| 8037 |
|
| 8038 |
return tickString; |
| 8039 |
}, |
| 8040 |
|
| 8041 |
logarithmic: function(tickValue, index, ticks) { |
| 8042 |
var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); |
| 8043 |
|
| 8044 |
if (tickValue === 0) { |
| 8045 |
return '0'; |
| 8046 |
} else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { |
| 8047 |
return tickValue.toExponential(); |
| 8048 |
} |
| 8049 |
return ''; |
| 8050 |
} |
| 8051 |
} |
| 8052 |
}; |
| 8053 |
|
| 8054 |
},{"45":45}],35:[function(require,module,exports){ |
| 8055 |
'use strict'; |
| 8056 |
|
| 8057 |
var defaults = require(25); |
| 8058 |
var Element = require(26); |
| 8059 |
var helpers = require(45); |
| 8060 |
|
| 8061 |
defaults._set('global', { |
| 8062 |
tooltips: { |
| 8063 |
enabled: true, |
| 8064 |
custom: null, |
| 8065 |
mode: 'nearest', |
| 8066 |
position: 'average', |
| 8067 |
intersect: true, |
| 8068 |
backgroundColor: 'rgba(0,0,0,0.8)', |
| 8069 |
titleFontStyle: 'bold', |
| 8070 |
titleSpacing: 2, |
| 8071 |
titleMarginBottom: 6, |
| 8072 |
titleFontColor: '#fff', |
| 8073 |
titleAlign: 'left', |
| 8074 |
bodySpacing: 2, |
| 8075 |
bodyFontColor: '#fff', |
| 8076 |
bodyAlign: 'left', |
| 8077 |
footerFontStyle: 'bold', |
| 8078 |
footerSpacing: 2, |
| 8079 |
footerMarginTop: 6, |
| 8080 |
footerFontColor: '#fff', |
| 8081 |
footerAlign: 'left', |
| 8082 |
yPadding: 6, |
| 8083 |
xPadding: 6, |
| 8084 |
caretPadding: 2, |
| 8085 |
caretSize: 5, |
| 8086 |
cornerRadius: 6, |
| 8087 |
multiKeyBackground: '#fff', |
| 8088 |
displayColors: true, |
| 8089 |
borderColor: 'rgba(0,0,0,0)', |
| 8090 |
borderWidth: 0, |
| 8091 |
callbacks: { |
| 8092 |
// Args are: (tooltipItems, data) |
| 8093 |
beforeTitle: helpers.noop, |
| 8094 |
title: function(tooltipItems, data) { |
| 8095 |
// Pick first xLabel for now |
| 8096 |
var title = ''; |
| 8097 |
var labels = data.labels; |
| 8098 |
var labelCount = labels ? labels.length : 0; |
| 8099 |
|
| 8100 |
if (tooltipItems.length > 0) { |
| 8101 |
var item = tooltipItems[0]; |
| 8102 |
|
| 8103 |
if (item.xLabel) { |
| 8104 |
title = item.xLabel; |
| 8105 |
} else if (labelCount > 0 && item.index < labelCount) { |
| 8106 |
title = labels[item.index]; |
| 8107 |
} |
| 8108 |
} |
| 8109 |
|
| 8110 |
return title; |
| 8111 |
}, |
| 8112 |
afterTitle: helpers.noop, |
| 8113 |
|
| 8114 |
// Args are: (tooltipItems, data) |
| 8115 |
beforeBody: helpers.noop, |
| 8116 |
|
| 8117 |
// Args are: (tooltipItem, data) |
| 8118 |
beforeLabel: helpers.noop, |
| 8119 |
label: function(tooltipItem, data) { |
| 8120 |
var label = data.datasets[tooltipItem.datasetIndex].label || ''; |
| 8121 |
|
| 8122 |
if (label) { |
| 8123 |
label += ': '; |
| 8124 |
} |
| 8125 |
label += tooltipItem.yLabel; |
| 8126 |
return label; |
| 8127 |
}, |
| 8128 |
labelColor: function(tooltipItem, chart) { |
| 8129 |
var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); |
| 8130 |
var activeElement = meta.data[tooltipItem.index]; |
| 8131 |
var view = activeElement._view; |
| 8132 |
return { |
| 8133 |
borderColor: view.borderColor, |
| 8134 |
backgroundColor: view.backgroundColor |
| 8135 |
}; |
| 8136 |
}, |
| 8137 |
labelTextColor: function() { |
| 8138 |
return this._options.bodyFontColor; |
| 8139 |
}, |
| 8140 |
afterLabel: helpers.noop, |
| 8141 |
|
| 8142 |
// Args are: (tooltipItems, data) |
| 8143 |
afterBody: helpers.noop, |
| 8144 |
|
| 8145 |
// Args are: (tooltipItems, data) |
| 8146 |
beforeFooter: helpers.noop, |
| 8147 |
footer: helpers.noop, |
| 8148 |
afterFooter: helpers.noop |
| 8149 |
} |
| 8150 |
} |
| 8151 |
}); |
| 8152 |
|
| 8153 |
module.exports = function(Chart) { |
| 8154 |
|
| 8155 |
/** |
| 8156 |
* Helper method to merge the opacity into a color |
| 8157 |
*/ |
| 8158 |
function mergeOpacity(colorString, opacity) { |
| 8159 |
var color = helpers.color(colorString); |
| 8160 |
return color.alpha(opacity * color.alpha()).rgbaString(); |
| 8161 |
} |
| 8162 |
|
| 8163 |
// Helper to push or concat based on if the 2nd parameter is an array or not |
| 8164 |
function pushOrConcat(base, toPush) { |
| 8165 |
if (toPush) { |
| 8166 |
if (helpers.isArray(toPush)) { |
| 8167 |
// base = base.concat(toPush); |
| 8168 |
Array.prototype.push.apply(base, toPush); |
| 8169 |
} else { |
| 8170 |
base.push(toPush); |
| 8171 |
} |
| 8172 |
} |
| 8173 |
|
| 8174 |
return base; |
| 8175 |
} |
| 8176 |
|
| 8177 |
// Private helper to create a tooltip item model |
| 8178 |
// @param element : the chart element (point, arc, bar) to create the tooltip item for |
| 8179 |
// @return : new tooltip item |
| 8180 |
function createTooltipItem(element) { |
| 8181 |
var xScale = element._xScale; |
| 8182 |
var yScale = element._yScale || element._scale; // handle radar || polarArea charts |
| 8183 |
var index = element._index; |
| 8184 |
var datasetIndex = element._datasetIndex; |
| 8185 |
|
| 8186 |
return { |
| 8187 |
xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', |
| 8188 |
yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', |
| 8189 |
index: index, |
| 8190 |
datasetIndex: datasetIndex, |
| 8191 |
x: element._model.x, |
| 8192 |
y: element._model.y |
| 8193 |
}; |
| 8194 |
} |
| 8195 |
|
| 8196 |
/** |
| 8197 |
* Helper to get the reset model for the tooltip |
| 8198 |
* @param tooltipOpts {Object} the tooltip options |
| 8199 |
*/ |
| 8200 |
function getBaseModel(tooltipOpts) { |
| 8201 |
var globalDefaults = defaults.global; |
| 8202 |
var valueOrDefault = helpers.valueOrDefault; |
| 8203 |
|
| 8204 |
return { |
| 8205 |
// Positioning |
| 8206 |
xPadding: tooltipOpts.xPadding, |
| 8207 |
yPadding: tooltipOpts.yPadding, |
| 8208 |
xAlign: tooltipOpts.xAlign, |
| 8209 |
yAlign: tooltipOpts.yAlign, |
| 8210 |
|
| 8211 |
// Body |
| 8212 |
bodyFontColor: tooltipOpts.bodyFontColor, |
| 8213 |
_bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), |
| 8214 |
_bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), |
| 8215 |
_bodyAlign: tooltipOpts.bodyAlign, |
| 8216 |
bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), |
| 8217 |
bodySpacing: tooltipOpts.bodySpacing, |
| 8218 |
|
| 8219 |
// Title |
| 8220 |
titleFontColor: tooltipOpts.titleFontColor, |
| 8221 |
_titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), |
| 8222 |
_titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), |
| 8223 |
titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), |
| 8224 |
_titleAlign: tooltipOpts.titleAlign, |
| 8225 |
titleSpacing: tooltipOpts.titleSpacing, |
| 8226 |
titleMarginBottom: tooltipOpts.titleMarginBottom, |
| 8227 |
|
| 8228 |
// Footer |
| 8229 |
footerFontColor: tooltipOpts.footerFontColor, |
| 8230 |
_footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), |
| 8231 |
_footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), |
| 8232 |
footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), |
| 8233 |
_footerAlign: tooltipOpts.footerAlign, |
| 8234 |
footerSpacing: tooltipOpts.footerSpacing, |
| 8235 |
footerMarginTop: tooltipOpts.footerMarginTop, |
| 8236 |
|
| 8237 |
// Appearance |
| 8238 |
caretSize: tooltipOpts.caretSize, |
| 8239 |
cornerRadius: tooltipOpts.cornerRadius, |
| 8240 |
backgroundColor: tooltipOpts.backgroundColor, |
| 8241 |
opacity: 0, |
| 8242 |
legendColorBackground: tooltipOpts.multiKeyBackground, |
| 8243 |
displayColors: tooltipOpts.displayColors, |
| 8244 |
borderColor: tooltipOpts.borderColor, |
| 8245 |
borderWidth: tooltipOpts.borderWidth |
| 8246 |
}; |
| 8247 |
} |
| 8248 |
|
| 8249 |
/** |
| 8250 |
* Get the size of the tooltip |
| 8251 |
*/ |
| 8252 |
function getTooltipSize(tooltip, model) { |
| 8253 |
var ctx = tooltip._chart.ctx; |
| 8254 |
|
| 8255 |
var height = model.yPadding * 2; // Tooltip Padding |
| 8256 |
var width = 0; |
| 8257 |
|
| 8258 |
// Count of all lines in the body |
| 8259 |
var body = model.body; |
| 8260 |
var combinedBodyLength = body.reduce(function(count, bodyItem) { |
| 8261 |
return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; |
| 8262 |
}, 0); |
| 8263 |
combinedBodyLength += model.beforeBody.length + model.afterBody.length; |
| 8264 |
|
| 8265 |
var titleLineCount = model.title.length; |
| 8266 |
var footerLineCount = model.footer.length; |
| 8267 |
var titleFontSize = model.titleFontSize; |
| 8268 |
var bodyFontSize = model.bodyFontSize; |
| 8269 |
var footerFontSize = model.footerFontSize; |
| 8270 |
|
| 8271 |
height += titleLineCount * titleFontSize; // Title Lines |
| 8272 |
height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing |
| 8273 |
height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin |
| 8274 |
height += combinedBodyLength * bodyFontSize; // Body Lines |
| 8275 |
height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing |
| 8276 |
height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin |
| 8277 |
height += footerLineCount * (footerFontSize); // Footer Lines |
| 8278 |
height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing |
| 8279 |
|
| 8280 |
// Title width |
| 8281 |
var widthPadding = 0; |
| 8282 |
var maxLineWidth = function(line) { |
| 8283 |
width = Math.max(width, ctx.measureText(line).width + widthPadding); |
| 8284 |
}; |
| 8285 |
|
| 8286 |
ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); |
| 8287 |
helpers.each(model.title, maxLineWidth); |
| 8288 |
|
| 8289 |
// Body width |
| 8290 |
ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); |
| 8291 |
helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth); |
| 8292 |
|
| 8293 |
// Body lines may include some extra width due to the color box |
| 8294 |
widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; |
| 8295 |
helpers.each(body, function(bodyItem) { |
| 8296 |
helpers.each(bodyItem.before, maxLineWidth); |
| 8297 |
helpers.each(bodyItem.lines, maxLineWidth); |
| 8298 |
helpers.each(bodyItem.after, maxLineWidth); |
| 8299 |
}); |
| 8300 |
|
| 8301 |
// Reset back to 0 |
| 8302 |
widthPadding = 0; |
| 8303 |
|
| 8304 |
// Footer width |
| 8305 |
ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); |
| 8306 |
helpers.each(model.footer, maxLineWidth); |
| 8307 |
|
| 8308 |
// Add padding |
| 8309 |
width += 2 * model.xPadding; |
| 8310 |
|
| 8311 |
return { |
| 8312 |
width: width, |
| 8313 |
height: height |
| 8314 |
}; |
| 8315 |
} |
| 8316 |
|
| 8317 |
/** |
| 8318 |
* Helper to get the alignment of a tooltip given the size |
| 8319 |
*/ |
| 8320 |
function determineAlignment(tooltip, size) { |
| 8321 |
var model = tooltip._model; |
| 8322 |
var chart = tooltip._chart; |
| 8323 |
var chartArea = tooltip._chart.chartArea; |
| 8324 |
var xAlign = 'center'; |
| 8325 |
var yAlign = 'center'; |
| 8326 |
|
| 8327 |
if (model.y < size.height) { |
| 8328 |
yAlign = 'top'; |
| 8329 |
} else if (model.y > (chart.height - size.height)) { |
| 8330 |
yAlign = 'bottom'; |
| 8331 |
} |
| 8332 |
|
| 8333 |
var lf, rf; // functions to determine left, right alignment |
| 8334 |
var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart |
| 8335 |
var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges |
| 8336 |
var midX = (chartArea.left + chartArea.right) / 2; |
| 8337 |
var midY = (chartArea.top + chartArea.bottom) / 2; |
| 8338 |
|
| 8339 |
if (yAlign === 'center') { |
| 8340 |
lf = function(x) { |
| 8341 |
return x <= midX; |
| 8342 |
}; |
| 8343 |
rf = function(x) { |
| 8344 |
return x > midX; |
| 8345 |
}; |
| 8346 |
} else { |
| 8347 |
lf = function(x) { |
| 8348 |
return x <= (size.width / 2); |
| 8349 |
}; |
| 8350 |
rf = function(x) { |
| 8351 |
return x >= (chart.width - (size.width / 2)); |
| 8352 |
}; |
| 8353 |
} |
| 8354 |
|
| 8355 |
olf = function(x) { |
| 8356 |
return x + size.width > chart.width; |
| 8357 |
}; |
| 8358 |
orf = function(x) { |
| 8359 |
return x - size.width < 0; |
| 8360 |
}; |
| 8361 |
yf = function(y) { |
| 8362 |
return y <= midY ? 'top' : 'bottom'; |
| 8363 |
}; |
| 8364 |
|
| 8365 |
if (lf(model.x)) { |
| 8366 |
xAlign = 'left'; |
| 8367 |
|
| 8368 |
// Is tooltip too wide and goes over the right side of the chart.? |
| 8369 |
if (olf(model.x)) { |
| 8370 |
xAlign = 'center'; |
| 8371 |
yAlign = yf(model.y); |
| 8372 |
} |
| 8373 |
} else if (rf(model.x)) { |
| 8374 |
xAlign = 'right'; |
| 8375 |
|
| 8376 |
// Is tooltip too wide and goes outside left edge of canvas? |
| 8377 |
if (orf(model.x)) { |
| 8378 |
xAlign = 'center'; |
| 8379 |
yAlign = yf(model.y); |
| 8380 |
} |
| 8381 |
} |
| 8382 |
|
| 8383 |
var opts = tooltip._options; |
| 8384 |
return { |
| 8385 |
xAlign: opts.xAlign ? opts.xAlign : xAlign, |
| 8386 |
yAlign: opts.yAlign ? opts.yAlign : yAlign |
| 8387 |
}; |
| 8388 |
} |
| 8389 |
|
| 8390 |
/** |
| 8391 |
* @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment |
| 8392 |
*/ |
| 8393 |
function getBackgroundPoint(vm, size, alignment) { |
| 8394 |
// Background Position |
| 8395 |
var x = vm.x; |
| 8396 |
var y = vm.y; |
| 8397 |
|
| 8398 |
var caretSize = vm.caretSize; |
| 8399 |
var caretPadding = vm.caretPadding; |
| 8400 |
var cornerRadius = vm.cornerRadius; |
| 8401 |
var xAlign = alignment.xAlign; |
| 8402 |
var yAlign = alignment.yAlign; |
| 8403 |
var paddingAndSize = caretSize + caretPadding; |
| 8404 |
var radiusAndPadding = cornerRadius + caretPadding; |
| 8405 |
|
| 8406 |
if (xAlign === 'right') { |
| 8407 |
x -= size.width; |
| 8408 |
} else if (xAlign === 'center') { |
| 8409 |
x -= (size.width / 2); |
| 8410 |
} |
| 8411 |
|
| 8412 |
if (yAlign === 'top') { |
| 8413 |
y += paddingAndSize; |
| 8414 |
} else if (yAlign === 'bottom') { |
| 8415 |
y -= size.height + paddingAndSize; |
| 8416 |
} else { |
| 8417 |
y -= (size.height / 2); |
| 8418 |
} |
| 8419 |
|
| 8420 |
if (yAlign === 'center') { |
| 8421 |
if (xAlign === 'left') { |
| 8422 |
x += paddingAndSize; |
| 8423 |
} else if (xAlign === 'right') { |
| 8424 |
x -= paddingAndSize; |
| 8425 |
} |
| 8426 |
} else if (xAlign === 'left') { |
| 8427 |
x -= radiusAndPadding; |
| 8428 |
} else if (xAlign === 'right') { |
| 8429 |
x += radiusAndPadding; |
| 8430 |
} |
| 8431 |
|
| 8432 |
return { |
| 8433 |
x: x, |
| 8434 |
y: y |
| 8435 |
}; |
| 8436 |
} |
| 8437 |
|
| 8438 |
Chart.Tooltip = Element.extend({ |
| 8439 |
initialize: function() { |
| 8440 |
this._model = getBaseModel(this._options); |
| 8441 |
this._lastActive = []; |
| 8442 |
}, |
| 8443 |
|
| 8444 |
// Get the title |
| 8445 |
// Args are: (tooltipItem, data) |
| 8446 |
getTitle: function() { |
| 8447 |
var me = this; |
| 8448 |
var opts = me._options; |
| 8449 |
var callbacks = opts.callbacks; |
| 8450 |
|
| 8451 |
var beforeTitle = callbacks.beforeTitle.apply(me, arguments); |
| 8452 |
var title = callbacks.title.apply(me, arguments); |
| 8453 |
var afterTitle = callbacks.afterTitle.apply(me, arguments); |
| 8454 |
|
| 8455 |
var lines = []; |
| 8456 |
lines = pushOrConcat(lines, beforeTitle); |
| 8457 |
lines = pushOrConcat(lines, title); |
| 8458 |
lines = pushOrConcat(lines, afterTitle); |
| 8459 |
|
| 8460 |
return lines; |
| 8461 |
}, |
| 8462 |
|
| 8463 |
// Args are: (tooltipItem, data) |
| 8464 |
getBeforeBody: function() { |
| 8465 |
var lines = this._options.callbacks.beforeBody.apply(this, arguments); |
| 8466 |
return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; |
| 8467 |
}, |
| 8468 |
|
| 8469 |
// Args are: (tooltipItem, data) |
| 8470 |
getBody: function(tooltipItems, data) { |
| 8471 |
var me = this; |
| 8472 |
var callbacks = me._options.callbacks; |
| 8473 |
var bodyItems = []; |
| 8474 |
|
| 8475 |
helpers.each(tooltipItems, function(tooltipItem) { |
| 8476 |
var bodyItem = { |
| 8477 |
before: [], |
| 8478 |
lines: [], |
| 8479 |
after: [] |
| 8480 |
}; |
| 8481 |
pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data)); |
| 8482 |
pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); |
| 8483 |
pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data)); |
| 8484 |
|
| 8485 |
bodyItems.push(bodyItem); |
| 8486 |
}); |
| 8487 |
|
| 8488 |
return bodyItems; |
| 8489 |
}, |
| 8490 |
|
| 8491 |
// Args are: (tooltipItem, data) |
| 8492 |
getAfterBody: function() { |
| 8493 |
var lines = this._options.callbacks.afterBody.apply(this, arguments); |
| 8494 |
return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; |
| 8495 |
}, |
| 8496 |
|
| 8497 |
// Get the footer and beforeFooter and afterFooter lines |
| 8498 |
// Args are: (tooltipItem, data) |
| 8499 |
getFooter: function() { |
| 8500 |
var me = this; |
| 8501 |
var callbacks = me._options.callbacks; |
| 8502 |
|
| 8503 |
var beforeFooter = callbacks.beforeFooter.apply(me, arguments); |
| 8504 |
var footer = callbacks.footer.apply(me, arguments); |
| 8505 |
var afterFooter = callbacks.afterFooter.apply(me, arguments); |
| 8506 |
|
| 8507 |
var lines = []; |
| 8508 |
lines = pushOrConcat(lines, beforeFooter); |
| 8509 |
lines = pushOrConcat(lines, footer); |
| 8510 |
lines = pushOrConcat(lines, afterFooter); |
| 8511 |
|
| 8512 |
return lines; |
| 8513 |
}, |
| 8514 |
|
| 8515 |
update: function(changed) { |
| 8516 |
var me = this; |
| 8517 |
var opts = me._options; |
| 8518 |
|
| 8519 |
// Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition |
| 8520 |
// 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 |
| 8521 |
// which breaks any animations. |
| 8522 |
var existingModel = me._model; |
| 8523 |
var model = me._model = getBaseModel(opts); |
| 8524 |
var active = me._active; |
| 8525 |
|
| 8526 |
var data = me._data; |
| 8527 |
|
| 8528 |
// In the case where active.length === 0 we need to keep these at existing values for good animations |
| 8529 |
var alignment = { |
| 8530 |
xAlign: existingModel.xAlign, |
| 8531 |
yAlign: existingModel.yAlign |
| 8532 |
}; |
| 8533 |
var backgroundPoint = { |
| 8534 |
x: existingModel.x, |
| 8535 |
y: existingModel.y |
| 8536 |
}; |
| 8537 |
var tooltipSize = { |
| 8538 |
width: existingModel.width, |
| 8539 |
height: existingModel.height |
| 8540 |
}; |
| 8541 |
var tooltipPosition = { |
| 8542 |
x: existingModel.caretX, |
| 8543 |
y: existingModel.caretY |
| 8544 |
}; |
| 8545 |
|
| 8546 |
var i, len; |
| 8547 |
|
| 8548 |
if (active.length) { |
| 8549 |
model.opacity = 1; |
| 8550 |
|
| 8551 |
var labelColors = []; |
| 8552 |
var labelTextColors = []; |
| 8553 |
tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition); |
| 8554 |
|
| 8555 |
var tooltipItems = []; |
| 8556 |
for (i = 0, len = active.length; i < len; ++i) { |
| 8557 |
tooltipItems.push(createTooltipItem(active[i])); |
| 8558 |
} |
| 8559 |
|
| 8560 |
// If the user provided a filter function, use it to modify the tooltip items |
| 8561 |
if (opts.filter) { |
| 8562 |
tooltipItems = tooltipItems.filter(function(a) { |
| 8563 |
return opts.filter(a, data); |
| 8564 |
}); |
| 8565 |
} |
| 8566 |
|
| 8567 |
// If the user provided a sorting function, use it to modify the tooltip items |
| 8568 |
if (opts.itemSort) { |
| 8569 |
tooltipItems = tooltipItems.sort(function(a, b) { |
| 8570 |
return opts.itemSort(a, b, data); |
| 8571 |
}); |
| 8572 |
} |
| 8573 |
|
| 8574 |
// Determine colors for boxes |
| 8575 |
helpers.each(tooltipItems, function(tooltipItem) { |
| 8576 |
labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); |
| 8577 |
labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); |
| 8578 |
}); |
| 8579 |
|
| 8580 |
|
| 8581 |
// Build the Text Lines |
| 8582 |
model.title = me.getTitle(tooltipItems, data); |
| 8583 |
model.beforeBody = me.getBeforeBody(tooltipItems, data); |
| 8584 |
model.body = me.getBody(tooltipItems, data); |
| 8585 |
model.afterBody = me.getAfterBody(tooltipItems, data); |
| 8586 |
model.footer = me.getFooter(tooltipItems, data); |
| 8587 |
|
| 8588 |
// Initial positioning and colors |
| 8589 |
model.x = Math.round(tooltipPosition.x); |
| 8590 |
model.y = Math.round(tooltipPosition.y); |
| 8591 |
model.caretPadding = opts.caretPadding; |
| 8592 |
model.labelColors = labelColors; |
| 8593 |
model.labelTextColors = labelTextColors; |
| 8594 |
|
| 8595 |
// data points |
| 8596 |
model.dataPoints = tooltipItems; |
| 8597 |
|
| 8598 |
// We need to determine alignment of the tooltip |
| 8599 |
tooltipSize = getTooltipSize(this, model); |
| 8600 |
alignment = determineAlignment(this, tooltipSize); |
| 8601 |
// Final Size and Position |
| 8602 |
backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment); |
| 8603 |
} else { |
| 8604 |
model.opacity = 0; |
| 8605 |
} |
| 8606 |
|
| 8607 |
model.xAlign = alignment.xAlign; |
| 8608 |
model.yAlign = alignment.yAlign; |
| 8609 |
model.x = backgroundPoint.x; |
| 8610 |
model.y = backgroundPoint.y; |
| 8611 |
model.width = tooltipSize.width; |
| 8612 |
model.height = tooltipSize.height; |
| 8613 |
|
| 8614 |
// Point where the caret on the tooltip points to |
| 8615 |
model.caretX = tooltipPosition.x; |
| 8616 |
model.caretY = tooltipPosition.y; |
| 8617 |
|
| 8618 |
me._model = model; |
| 8619 |
|
| 8620 |
if (changed && opts.custom) { |
| 8621 |
opts.custom.call(me, model); |
| 8622 |
} |
| 8623 |
|
| 8624 |
return me; |
| 8625 |
}, |
| 8626 |
drawCaret: function(tooltipPoint, size) { |
| 8627 |
var ctx = this._chart.ctx; |
| 8628 |
var vm = this._view; |
| 8629 |
var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); |
| 8630 |
|
| 8631 |
ctx.lineTo(caretPosition.x1, caretPosition.y1); |
| 8632 |
ctx.lineTo(caretPosition.x2, caretPosition.y2); |
| 8633 |
ctx.lineTo(caretPosition.x3, caretPosition.y3); |
| 8634 |
}, |
| 8635 |
getCaretPosition: function(tooltipPoint, size, vm) { |
| 8636 |
var x1, x2, x3, y1, y2, y3; |
| 8637 |
var caretSize = vm.caretSize; |
| 8638 |
var cornerRadius = vm.cornerRadius; |
| 8639 |
var xAlign = vm.xAlign; |
| 8640 |
var yAlign = vm.yAlign; |
| 8641 |
var ptX = tooltipPoint.x; |
| 8642 |
var ptY = tooltipPoint.y; |
| 8643 |
var width = size.width; |
| 8644 |
var height = size.height; |
| 8645 |
|
| 8646 |
if (yAlign === 'center') { |
| 8647 |
y2 = ptY + (height / 2); |
| 8648 |
|
| 8649 |
if (xAlign === 'left') { |
| 8650 |
x1 = ptX; |
| 8651 |
x2 = x1 - caretSize; |
| 8652 |
x3 = x1; |
| 8653 |
|
| 8654 |
y1 = y2 + caretSize; |
| 8655 |
y3 = y2 - caretSize; |
| 8656 |
} else { |
| 8657 |
x1 = ptX + width; |
| 8658 |
x2 = x1 + caretSize; |
| 8659 |
x3 = x1; |
| 8660 |
|
| 8661 |
y1 = y2 - caretSize; |
| 8662 |
y3 = y2 + caretSize; |
| 8663 |
} |
| 8664 |
} else { |
| 8665 |
if (xAlign === 'left') { |
| 8666 |
x2 = ptX + cornerRadius + (caretSize); |
| 8667 |
x1 = x2 - caretSize; |
| 8668 |
x3 = x2 + caretSize; |
| 8669 |
} else if (xAlign === 'right') { |
| 8670 |
x2 = ptX + width - cornerRadius - caretSize; |
| 8671 |
x1 = x2 - caretSize; |
| 8672 |
x3 = x2 + caretSize; |
| 8673 |
} else { |
| 8674 |
x2 = ptX + (width / 2); |
| 8675 |
x1 = x2 - caretSize; |
| 8676 |
x3 = x2 + caretSize; |
| 8677 |
} |
| 8678 |
if (yAlign === 'top') { |
| 8679 |
y1 = ptY; |
| 8680 |
y2 = y1 - caretSize; |
| 8681 |
y3 = y1; |
| 8682 |
} else { |
| 8683 |
y1 = ptY + height; |
| 8684 |
y2 = y1 + caretSize; |
| 8685 |
y3 = y1; |
| 8686 |
// invert drawing order |
| 8687 |
var tmp = x3; |
| 8688 |
x3 = x1; |
| 8689 |
x1 = tmp; |
| 8690 |
} |
| 8691 |
} |
| 8692 |
return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; |
| 8693 |
}, |
| 8694 |
drawTitle: function(pt, vm, ctx, opacity) { |
| 8695 |
var title = vm.title; |
| 8696 |
|
| 8697 |
if (title.length) { |
| 8698 |
ctx.textAlign = vm._titleAlign; |
| 8699 |
ctx.textBaseline = 'top'; |
| 8700 |
|
| 8701 |
var titleFontSize = vm.titleFontSize; |
| 8702 |
var titleSpacing = vm.titleSpacing; |
| 8703 |
|
| 8704 |
ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); |
| 8705 |
ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); |
| 8706 |
|
| 8707 |
var i, len; |
| 8708 |
for (i = 0, len = title.length; i < len; ++i) { |
| 8709 |
ctx.fillText(title[i], pt.x, pt.y); |
| 8710 |
pt.y += titleFontSize + titleSpacing; // Line Height and spacing |
| 8711 |
|
| 8712 |
if (i + 1 === title.length) { |
| 8713 |
pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing |
| 8714 |
} |
| 8715 |
} |
| 8716 |
} |
| 8717 |
}, |
| 8718 |
drawBody: function(pt, vm, ctx, opacity) { |
| 8719 |
var bodyFontSize = vm.bodyFontSize; |
| 8720 |
var bodySpacing = vm.bodySpacing; |
| 8721 |
var body = vm.body; |
| 8722 |
|
| 8723 |
ctx.textAlign = vm._bodyAlign; |
| 8724 |
ctx.textBaseline = 'top'; |
| 8725 |
ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); |
| 8726 |
|
| 8727 |
// Before Body |
| 8728 |
var xLinePadding = 0; |
| 8729 |
var fillLineOfText = function(line) { |
| 8730 |
ctx.fillText(line, pt.x + xLinePadding, pt.y); |
| 8731 |
pt.y += bodyFontSize + bodySpacing; |
| 8732 |
}; |
| 8733 |
|
| 8734 |
// Before body lines |
| 8735 |
ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity); |
| 8736 |
helpers.each(vm.beforeBody, fillLineOfText); |
| 8737 |
|
| 8738 |
var drawColorBoxes = vm.displayColors; |
| 8739 |
xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; |
| 8740 |
|
| 8741 |
// Draw body lines now |
| 8742 |
helpers.each(body, function(bodyItem, i) { |
| 8743 |
var textColor = mergeOpacity(vm.labelTextColors[i], opacity); |
| 8744 |
ctx.fillStyle = textColor; |
| 8745 |
helpers.each(bodyItem.before, fillLineOfText); |
| 8746 |
|
| 8747 |
helpers.each(bodyItem.lines, function(line) { |
| 8748 |
// Draw Legend-like boxes if needed |
| 8749 |
if (drawColorBoxes) { |
| 8750 |
// Fill a white rect so that colours merge nicely if the opacity is < 1 |
| 8751 |
ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); |
| 8752 |
ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); |
| 8753 |
|
| 8754 |
// Border |
| 8755 |
ctx.lineWidth = 1; |
| 8756 |
ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); |
| 8757 |
ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); |
| 8758 |
|
| 8759 |
// Inner square |
| 8760 |
ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); |
| 8761 |
ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); |
| 8762 |
ctx.fillStyle = textColor; |
| 8763 |
} |
| 8764 |
|
| 8765 |
fillLineOfText(line); |
| 8766 |
}); |
| 8767 |
|
| 8768 |
helpers.each(bodyItem.after, fillLineOfText); |
| 8769 |
}); |
| 8770 |
|
| 8771 |
// Reset back to 0 for after body |
| 8772 |
xLinePadding = 0; |
| 8773 |
|
| 8774 |
// After body lines |
| 8775 |
helpers.each(vm.afterBody, fillLineOfText); |
| 8776 |
pt.y -= bodySpacing; // Remove last body spacing |
| 8777 |
}, |
| 8778 |
drawFooter: function(pt, vm, ctx, opacity) { |
| 8779 |
var footer = vm.footer; |
| 8780 |
|
| 8781 |
if (footer.length) { |
| 8782 |
pt.y += vm.footerMarginTop; |
| 8783 |
|
| 8784 |
ctx.textAlign = vm._footerAlign; |
| 8785 |
ctx.textBaseline = 'top'; |
| 8786 |
|
| 8787 |
ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); |
| 8788 |
ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); |
| 8789 |
|
| 8790 |
helpers.each(footer, function(line) { |
| 8791 |
ctx.fillText(line, pt.x, pt.y); |
| 8792 |
pt.y += vm.footerFontSize + vm.footerSpacing; |
| 8793 |
}); |
| 8794 |
} |
| 8795 |
}, |
| 8796 |
drawBackground: function(pt, vm, ctx, tooltipSize, opacity) { |
| 8797 |
ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); |
| 8798 |
ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity); |
| 8799 |
ctx.lineWidth = vm.borderWidth; |
| 8800 |
var xAlign = vm.xAlign; |
| 8801 |
var yAlign = vm.yAlign; |
| 8802 |
var x = pt.x; |
| 8803 |
var y = pt.y; |
| 8804 |
var width = tooltipSize.width; |
| 8805 |
var height = tooltipSize.height; |
| 8806 |
var radius = vm.cornerRadius; |
| 8807 |
|
| 8808 |
ctx.beginPath(); |
| 8809 |
ctx.moveTo(x + radius, y); |
| 8810 |
if (yAlign === 'top') { |
| 8811 |
this.drawCaret(pt, tooltipSize); |
| 8812 |
} |
| 8813 |
ctx.lineTo(x + width - radius, y); |
| 8814 |
ctx.quadraticCurveTo(x + width, y, x + width, y + radius); |
| 8815 |
if (yAlign === 'center' && xAlign === 'right') { |
| 8816 |
this.drawCaret(pt, tooltipSize); |
| 8817 |
} |
| 8818 |
ctx.lineTo(x + width, y + height - radius); |
| 8819 |
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); |
| 8820 |
if (yAlign === 'bottom') { |
| 8821 |
this.drawCaret(pt, tooltipSize); |
| 8822 |
} |
| 8823 |
ctx.lineTo(x + radius, y + height); |
| 8824 |
ctx.quadraticCurveTo(x, y + height, x, y + height - radius); |
| 8825 |
if (yAlign === 'center' && xAlign === 'left') { |
| 8826 |
this.drawCaret(pt, tooltipSize); |
| 8827 |
} |
| 8828 |
ctx.lineTo(x, y + radius); |
| 8829 |
ctx.quadraticCurveTo(x, y, x + radius, y); |
| 8830 |
ctx.closePath(); |
| 8831 |
|
| 8832 |
ctx.fill(); |
| 8833 |
|
| 8834 |
if (vm.borderWidth > 0) { |
| 8835 |
ctx.stroke(); |
| 8836 |
} |
| 8837 |
}, |
| 8838 |
draw: function() { |
| 8839 |
var ctx = this._chart.ctx; |
| 8840 |
var vm = this._view; |
| 8841 |
|
| 8842 |
if (vm.opacity === 0) { |
| 8843 |
return; |
| 8844 |
} |
| 8845 |
|
| 8846 |
var tooltipSize = { |
| 8847 |
width: vm.width, |
| 8848 |
height: vm.height |
| 8849 |
}; |
| 8850 |
var pt = { |
| 8851 |
x: vm.x, |
| 8852 |
y: vm.y |
| 8853 |
}; |
| 8854 |
|
| 8855 |
// IE11/Edge does not like very small opacities, so snap to 0 |
| 8856 |
var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; |
| 8857 |
|
| 8858 |
// Truthy/falsey value for empty tooltip |
| 8859 |
var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; |
| 8860 |
|
| 8861 |
if (this._options.enabled && hasTooltipContent) { |
| 8862 |
// Draw Background |
| 8863 |
this.drawBackground(pt, vm, ctx, tooltipSize, opacity); |
| 8864 |
|
| 8865 |
// Draw Title, Body, and Footer |
| 8866 |
pt.x += vm.xPadding; |
| 8867 |
pt.y += vm.yPadding; |
| 8868 |
|
| 8869 |
// Titles |
| 8870 |
this.drawTitle(pt, vm, ctx, opacity); |
| 8871 |
|
| 8872 |
// Body |
| 8873 |
this.drawBody(pt, vm, ctx, opacity); |
| 8874 |
|
| 8875 |
// Footer |
| 8876 |
this.drawFooter(pt, vm, ctx, opacity); |
| 8877 |
} |
| 8878 |
}, |
| 8879 |
|
| 8880 |
/** |
| 8881 |
* Handle an event |
| 8882 |
* @private |
| 8883 |
* @param {IEvent} event - The event to handle |
| 8884 |
* @returns {Boolean} true if the tooltip changed |
| 8885 |
*/ |
| 8886 |
handleEvent: function(e) { |
| 8887 |
var me = this; |
| 8888 |
var options = me._options; |
| 8889 |
var changed = false; |
| 8890 |
|
| 8891 |
me._lastActive = me._lastActive || []; |
| 8892 |
|
| 8893 |
// Find Active Elements for tooltips |
| 8894 |
if (e.type === 'mouseout') { |
| 8895 |
me._active = []; |
| 8896 |
} else { |
| 8897 |
me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); |
| 8898 |
} |
| 8899 |
|
| 8900 |
// Remember Last Actives |
| 8901 |
changed = !helpers.arrayEquals(me._active, me._lastActive); |
| 8902 |
|
| 8903 |
// If tooltip didn't change, do not handle the target event |
| 8904 |
if (!changed) { |
| 8905 |
return false; |
| 8906 |
} |
| 8907 |
|
| 8908 |
me._lastActive = me._active; |
| 8909 |
|
| 8910 |
if (options.enabled || options.custom) { |
| 8911 |
me._eventPosition = { |
| 8912 |
x: e.x, |
| 8913 |
y: e.y |
| 8914 |
}; |
| 8915 |
|
| 8916 |
var model = me._model; |
| 8917 |
me.update(true); |
| 8918 |
me.pivot(); |
| 8919 |
|
| 8920 |
// See if our tooltip position changed |
| 8921 |
changed |= (model.x !== me._model.x) || (model.y !== me._model.y); |
| 8922 |
} |
| 8923 |
|
| 8924 |
return changed; |
| 8925 |
} |
| 8926 |
}); |
| 8927 |
|
| 8928 |
/** |
| 8929 |
* @namespace Chart.Tooltip.positioners |
| 8930 |
*/ |
| 8931 |
Chart.Tooltip.positioners = { |
| 8932 |
/** |
| 8933 |
* Average mode places the tooltip at the average position of the elements shown |
| 8934 |
* @function Chart.Tooltip.positioners.average |
| 8935 |
* @param elements {ChartElement[]} the elements being displayed in the tooltip |
| 8936 |
* @returns {Point} tooltip position |
| 8937 |
*/ |
| 8938 |
average: function(elements) { |
| 8939 |
if (!elements.length) { |
| 8940 |
return false; |
| 8941 |
} |
| 8942 |
|
| 8943 |
var i, len; |
| 8944 |
var x = 0; |
| 8945 |
var y = 0; |
| 8946 |
var count = 0; |
| 8947 |
|
| 8948 |
for (i = 0, len = elements.length; i < len; ++i) { |
| 8949 |
var el = elements[i]; |
| 8950 |
if (el && el.hasValue()) { |
| 8951 |
var pos = el.tooltipPosition(); |
| 8952 |
x += pos.x; |
| 8953 |
y += pos.y; |
| 8954 |
++count; |
| 8955 |
} |
| 8956 |
} |
| 8957 |
|
| 8958 |
return { |
| 8959 |
x: Math.round(x / count), |
| 8960 |
y: Math.round(y / count) |
| 8961 |
}; |
| 8962 |
}, |
| 8963 |
|
| 8964 |
/** |
| 8965 |
* Gets the tooltip position nearest of the item nearest to the event position |
| 8966 |
* @function Chart.Tooltip.positioners.nearest |
| 8967 |
* @param elements {Chart.Element[]} the tooltip elements |
| 8968 |
* @param eventPosition {Point} the position of the event in canvas coordinates |
| 8969 |
* @returns {Point} the tooltip position |
| 8970 |
*/ |
| 8971 |
nearest: function(elements, eventPosition) { |
| 8972 |
var x = eventPosition.x; |
| 8973 |
var y = eventPosition.y; |
| 8974 |
var minDistance = Number.POSITIVE_INFINITY; |
| 8975 |
var i, len, nearestElement; |
| 8976 |
|
| 8977 |
for (i = 0, len = elements.length; i < len; ++i) { |
| 8978 |
var el = elements[i]; |
| 8979 |
if (el && el.hasValue()) { |
| 8980 |
var center = el.getCenterPoint(); |
| 8981 |
var d = helpers.distanceBetweenPoints(eventPosition, center); |
| 8982 |
|
| 8983 |
if (d < minDistance) { |
| 8984 |
minDistance = d; |
| 8985 |
nearestElement = el; |
| 8986 |
} |
| 8987 |
} |
| 8988 |
} |
| 8989 |
|
| 8990 |
if (nearestElement) { |
| 8991 |
var tp = nearestElement.tooltipPosition(); |
| 8992 |
x = tp.x; |
| 8993 |
y = tp.y; |
| 8994 |
} |
| 8995 |
|
| 8996 |
return { |
| 8997 |
x: x, |
| 8998 |
y: y |
| 8999 |
}; |
| 9000 |
} |
| 9001 |
}; |
| 9002 |
}; |
| 9003 |
|
| 9004 |
},{"25":25,"26":26,"45":45}],36:[function(require,module,exports){ |
| 9005 |
'use strict'; |
| 9006 |
|
| 9007 |
var defaults = require(25); |
| 9008 |
var Element = require(26); |
| 9009 |
var helpers = require(45); |
| 9010 |
|
| 9011 |
defaults._set('global', { |
| 9012 |
elements: { |
| 9013 |
arc: { |
| 9014 |
backgroundColor: defaults.global.defaultColor, |
| 9015 |
borderColor: '#fff', |
| 9016 |
borderWidth: 2 |
| 9017 |
} |
| 9018 |
} |
| 9019 |
}); |
| 9020 |
|
| 9021 |
module.exports = Element.extend({ |
| 9022 |
inLabelRange: function(mouseX) { |
| 9023 |
var vm = this._view; |
| 9024 |
|
| 9025 |
if (vm) { |
| 9026 |
return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); |
| 9027 |
} |
| 9028 |
return false; |
| 9029 |
}, |
| 9030 |
|
| 9031 |
inRange: function(chartX, chartY) { |
| 9032 |
var vm = this._view; |
| 9033 |
|
| 9034 |
if (vm) { |
| 9035 |
var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY}); |
| 9036 |
var angle = pointRelativePosition.angle; |
| 9037 |
var distance = pointRelativePosition.distance; |
| 9038 |
|
| 9039 |
// Sanitise angle range |
| 9040 |
var startAngle = vm.startAngle; |
| 9041 |
var endAngle = vm.endAngle; |
| 9042 |
while (endAngle < startAngle) { |
| 9043 |
endAngle += 2.0 * Math.PI; |
| 9044 |
} |
| 9045 |
while (angle > endAngle) { |
| 9046 |
angle -= 2.0 * Math.PI; |
| 9047 |
} |
| 9048 |
while (angle < startAngle) { |
| 9049 |
angle += 2.0 * Math.PI; |
| 9050 |
} |
| 9051 |
|
| 9052 |
// Check if within the range of the open/close angle |
| 9053 |
var betweenAngles = (angle >= startAngle && angle <= endAngle); |
| 9054 |
var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); |
| 9055 |
|
| 9056 |
return (betweenAngles && withinRadius); |
| 9057 |
} |
| 9058 |
return false; |
| 9059 |
}, |
| 9060 |
|
| 9061 |
getCenterPoint: function() { |
| 9062 |
var vm = this._view; |
| 9063 |
var halfAngle = (vm.startAngle + vm.endAngle) / 2; |
| 9064 |
var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; |
| 9065 |
return { |
| 9066 |
x: vm.x + Math.cos(halfAngle) * halfRadius, |
| 9067 |
y: vm.y + Math.sin(halfAngle) * halfRadius |
| 9068 |
}; |
| 9069 |
}, |
| 9070 |
|
| 9071 |
getArea: function() { |
| 9072 |
var vm = this._view; |
| 9073 |
return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); |
| 9074 |
}, |
| 9075 |
|
| 9076 |
tooltipPosition: function() { |
| 9077 |
var vm = this._view; |
| 9078 |
var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); |
| 9079 |
var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; |
| 9080 |
|
| 9081 |
return { |
| 9082 |
x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), |
| 9083 |
y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) |
| 9084 |
}; |
| 9085 |
}, |
| 9086 |
|
| 9087 |
draw: function() { |
| 9088 |
var ctx = this._chart.ctx; |
| 9089 |
var vm = this._view; |
| 9090 |
var sA = vm.startAngle; |
| 9091 |
var eA = vm.endAngle; |
| 9092 |
|
| 9093 |
ctx.beginPath(); |
| 9094 |
|
| 9095 |
ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); |
| 9096 |
ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); |
| 9097 |
|
| 9098 |
ctx.closePath(); |
| 9099 |
ctx.strokeStyle = vm.borderColor; |
| 9100 |
ctx.lineWidth = vm.borderWidth; |
| 9101 |
|
| 9102 |
ctx.fillStyle = vm.backgroundColor; |
| 9103 |
|
| 9104 |
ctx.fill(); |
| 9105 |
ctx.lineJoin = 'bevel'; |
| 9106 |
|
| 9107 |
if (vm.borderWidth) { |
| 9108 |
ctx.stroke(); |
| 9109 |
} |
| 9110 |
} |
| 9111 |
}); |
| 9112 |
|
| 9113 |
},{"25":25,"26":26,"45":45}],37:[function(require,module,exports){ |
| 9114 |
'use strict'; |
| 9115 |
|
| 9116 |
var defaults = require(25); |
| 9117 |
var Element = require(26); |
| 9118 |
var helpers = require(45); |
| 9119 |
|
| 9120 |
var globalDefaults = defaults.global; |
| 9121 |
|
| 9122 |
defaults._set('global', { |
| 9123 |
elements: { |
| 9124 |
line: { |
| 9125 |
tension: 0.4, |
| 9126 |
backgroundColor: globalDefaults.defaultColor, |
| 9127 |
borderWidth: 3, |
| 9128 |
borderColor: globalDefaults.defaultColor, |
| 9129 |
borderCapStyle: 'butt', |
| 9130 |
borderDash: [], |
| 9131 |
borderDashOffset: 0.0, |
| 9132 |
borderJoinStyle: 'miter', |
| 9133 |
capBezierPoints: true, |
| 9134 |
fill: true, // do we fill in the area between the line and its base axis |
| 9135 |
} |
| 9136 |
} |
| 9137 |
}); |
| 9138 |
|
| 9139 |
module.exports = Element.extend({ |
| 9140 |
draw: function() { |
| 9141 |
var me = this; |
| 9142 |
var vm = me._view; |
| 9143 |
var ctx = me._chart.ctx; |
| 9144 |
var spanGaps = vm.spanGaps; |
| 9145 |
var points = me._children.slice(); // clone array |
| 9146 |
var globalOptionLineElements = globalDefaults.elements.line; |
| 9147 |
var lastDrawnIndex = -1; |
| 9148 |
var index, current, previous, currentVM; |
| 9149 |
|
| 9150 |
// If we are looping, adding the first point again |
| 9151 |
if (me._loop && points.length) { |
| 9152 |
points.push(points[0]); |
| 9153 |
} |
| 9154 |
|
| 9155 |
ctx.save(); |
| 9156 |
|
| 9157 |
// Stroke Line Options |
| 9158 |
ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; |
| 9159 |
|
| 9160 |
// IE 9 and 10 do not support line dash |
| 9161 |
if (ctx.setLineDash) { |
| 9162 |
ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); |
| 9163 |
} |
| 9164 |
|
| 9165 |
ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; |
| 9166 |
ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; |
| 9167 |
ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; |
| 9168 |
ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; |
| 9169 |
|
| 9170 |
// Stroke Line |
| 9171 |
ctx.beginPath(); |
| 9172 |
lastDrawnIndex = -1; |
| 9173 |
|
| 9174 |
for (index = 0; index < points.length; ++index) { |
| 9175 |
current = points[index]; |
| 9176 |
previous = helpers.previousItem(points, index); |
| 9177 |
currentVM = current._view; |
| 9178 |
|
| 9179 |
// First point moves to it's starting position no matter what |
| 9180 |
if (index === 0) { |
| 9181 |
if (!currentVM.skip) { |
| 9182 |
ctx.moveTo(currentVM.x, currentVM.y); |
| 9183 |
lastDrawnIndex = index; |
| 9184 |
} |
| 9185 |
} else { |
| 9186 |
previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; |
| 9187 |
|
| 9188 |
if (!currentVM.skip) { |
| 9189 |
if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { |
| 9190 |
// There was a gap and this is the first point after the gap |
| 9191 |
ctx.moveTo(currentVM.x, currentVM.y); |
| 9192 |
} else { |
| 9193 |
// Line to next point |
| 9194 |
helpers.canvas.lineTo(ctx, previous._view, current._view); |
| 9195 |
} |
| 9196 |
lastDrawnIndex = index; |
| 9197 |
} |
| 9198 |
} |
| 9199 |
} |
| 9200 |
|
| 9201 |
ctx.stroke(); |
| 9202 |
ctx.restore(); |
| 9203 |
} |
| 9204 |
}); |
| 9205 |
|
| 9206 |
},{"25":25,"26":26,"45":45}],38:[function(require,module,exports){ |
| 9207 |
'use strict'; |
| 9208 |
|
| 9209 |
var defaults = require(25); |
| 9210 |
var Element = require(26); |
| 9211 |
var helpers = require(45); |
| 9212 |
|
| 9213 |
var defaultColor = defaults.global.defaultColor; |
| 9214 |
|
| 9215 |
defaults._set('global', { |
| 9216 |
elements: { |
| 9217 |
point: { |
| 9218 |
radius: 3, |
| 9219 |
pointStyle: 'circle', |
| 9220 |
backgroundColor: defaultColor, |
| 9221 |
borderColor: defaultColor, |
| 9222 |
borderWidth: 1, |
| 9223 |
// Hover |
| 9224 |
hitRadius: 1, |
| 9225 |
hoverRadius: 4, |
| 9226 |
hoverBorderWidth: 1 |
| 9227 |
} |
| 9228 |
} |
| 9229 |
}); |
| 9230 |
|
| 9231 |
function xRange(mouseX) { |
| 9232 |
var vm = this._view; |
| 9233 |
return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; |
| 9234 |
} |
| 9235 |
|
| 9236 |
function yRange(mouseY) { |
| 9237 |
var vm = this._view; |
| 9238 |
return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; |
| 9239 |
} |
| 9240 |
|
| 9241 |
module.exports = Element.extend({ |
| 9242 |
inRange: function(mouseX, mouseY) { |
| 9243 |
var vm = this._view; |
| 9244 |
return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; |
| 9245 |
}, |
| 9246 |
|
| 9247 |
inLabelRange: xRange, |
| 9248 |
inXRange: xRange, |
| 9249 |
inYRange: yRange, |
| 9250 |
|
| 9251 |
getCenterPoint: function() { |
| 9252 |
var vm = this._view; |
| 9253 |
return { |
| 9254 |
x: vm.x, |
| 9255 |
y: vm.y |
| 9256 |
}; |
| 9257 |
}, |
| 9258 |
|
| 9259 |
getArea: function() { |
| 9260 |
return Math.PI * Math.pow(this._view.radius, 2); |
| 9261 |
}, |
| 9262 |
|
| 9263 |
tooltipPosition: function() { |
| 9264 |
var vm = this._view; |
| 9265 |
return { |
| 9266 |
x: vm.x, |
| 9267 |
y: vm.y, |
| 9268 |
padding: vm.radius + vm.borderWidth |
| 9269 |
}; |
| 9270 |
}, |
| 9271 |
|
| 9272 |
draw: function(chartArea) { |
| 9273 |
var vm = this._view; |
| 9274 |
var model = this._model; |
| 9275 |
var ctx = this._chart.ctx; |
| 9276 |
var pointStyle = vm.pointStyle; |
| 9277 |
var radius = vm.radius; |
| 9278 |
var x = vm.x; |
| 9279 |
var y = vm.y; |
| 9280 |
var color = helpers.color; |
| 9281 |
var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) |
| 9282 |
var ratio = 0; |
| 9283 |
|
| 9284 |
if (vm.skip) { |
| 9285 |
return; |
| 9286 |
} |
| 9287 |
|
| 9288 |
ctx.strokeStyle = vm.borderColor || defaultColor; |
| 9289 |
ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); |
| 9290 |
ctx.fillStyle = vm.backgroundColor || defaultColor; |
| 9291 |
|
| 9292 |
// Cliping for Points. |
| 9293 |
// going out from inner charArea? |
| 9294 |
if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) { |
| 9295 |
// Point fade out |
| 9296 |
if (model.x < chartArea.left) { |
| 9297 |
ratio = (x - model.x) / (chartArea.left - model.x); |
| 9298 |
} else if (chartArea.right * errMargin < model.x) { |
| 9299 |
ratio = (model.x - x) / (model.x - chartArea.right); |
| 9300 |
} else if (model.y < chartArea.top) { |
| 9301 |
ratio = (y - model.y) / (chartArea.top - model.y); |
| 9302 |
} else if (chartArea.bottom * errMargin < model.y) { |
| 9303 |
ratio = (model.y - y) / (model.y - chartArea.bottom); |
| 9304 |
} |
| 9305 |
ratio = Math.round(ratio * 100) / 100; |
| 9306 |
ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString(); |
| 9307 |
ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString(); |
| 9308 |
} |
| 9309 |
|
| 9310 |
helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y); |
| 9311 |
} |
| 9312 |
}); |
| 9313 |
|
| 9314 |
},{"25":25,"26":26,"45":45}],39:[function(require,module,exports){ |
| 9315 |
'use strict'; |
| 9316 |
|
| 9317 |
var defaults = require(25); |
| 9318 |
var Element = require(26); |
| 9319 |
|
| 9320 |
defaults._set('global', { |
| 9321 |
elements: { |
| 9322 |
rectangle: { |
| 9323 |
backgroundColor: defaults.global.defaultColor, |
| 9324 |
borderColor: defaults.global.defaultColor, |
| 9325 |
borderSkipped: 'bottom', |
| 9326 |
borderWidth: 0 |
| 9327 |
} |
| 9328 |
} |
| 9329 |
}); |
| 9330 |
|
| 9331 |
function isVertical(bar) { |
| 9332 |
return bar._view.width !== undefined; |
| 9333 |
} |
| 9334 |
|
| 9335 |
/** |
| 9336 |
* Helper function to get the bounds of the bar regardless of the orientation |
| 9337 |
* @param bar {Chart.Element.Rectangle} the bar |
| 9338 |
* @return {Bounds} bounds of the bar |
| 9339 |
* @private |
| 9340 |
*/ |
| 9341 |
function getBarBounds(bar) { |
| 9342 |
var vm = bar._view; |
| 9343 |
var x1, x2, y1, y2; |
| 9344 |
|
| 9345 |
if (isVertical(bar)) { |
| 9346 |
// vertical |
| 9347 |
var halfWidth = vm.width / 2; |
| 9348 |
x1 = vm.x - halfWidth; |
| 9349 |
x2 = vm.x + halfWidth; |
| 9350 |
y1 = Math.min(vm.y, vm.base); |
| 9351 |
y2 = Math.max(vm.y, vm.base); |
| 9352 |
} else { |
| 9353 |
// horizontal bar |
| 9354 |
var halfHeight = vm.height / 2; |
| 9355 |
x1 = Math.min(vm.x, vm.base); |
| 9356 |
x2 = Math.max(vm.x, vm.base); |
| 9357 |
y1 = vm.y - halfHeight; |
| 9358 |
y2 = vm.y + halfHeight; |
| 9359 |
} |
| 9360 |
|
| 9361 |
return { |
| 9362 |
left: x1, |
| 9363 |
top: y1, |
| 9364 |
right: x2, |
| 9365 |
bottom: y2 |
| 9366 |
}; |
| 9367 |
} |
| 9368 |
|
| 9369 |
module.exports = Element.extend({ |
| 9370 |
draw: function() { |
| 9371 |
var ctx = this._chart.ctx; |
| 9372 |
var vm = this._view; |
| 9373 |
var left, right, top, bottom, signX, signY, borderSkipped; |
| 9374 |
var borderWidth = vm.borderWidth; |
| 9375 |
|
| 9376 |
if (!vm.horizontal) { |
| 9377 |
// bar |
| 9378 |
left = vm.x - vm.width / 2; |
| 9379 |
right = vm.x + vm.width / 2; |
| 9380 |
top = vm.y; |
| 9381 |
bottom = vm.base; |
| 9382 |
signX = 1; |
| 9383 |
signY = bottom > top ? 1 : -1; |
| 9384 |
borderSkipped = vm.borderSkipped || 'bottom'; |
| 9385 |
} else { |
| 9386 |
// horizontal bar |
| 9387 |
left = vm.base; |
| 9388 |
right = vm.x; |
| 9389 |
top = vm.y - vm.height / 2; |
| 9390 |
bottom = vm.y + vm.height / 2; |
| 9391 |
signX = right > left ? 1 : -1; |
| 9392 |
signY = 1; |
| 9393 |
borderSkipped = vm.borderSkipped || 'left'; |
| 9394 |
} |
| 9395 |
|
| 9396 |
// Canvas doesn't allow us to stroke inside the width so we can |
| 9397 |
// adjust the sizes to fit if we're setting a stroke on the line |
| 9398 |
if (borderWidth) { |
| 9399 |
// borderWidth shold be less than bar width and bar height. |
| 9400 |
var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); |
| 9401 |
borderWidth = borderWidth > barSize ? barSize : borderWidth; |
| 9402 |
var halfStroke = borderWidth / 2; |
| 9403 |
// Adjust borderWidth when bar top position is near vm.base(zero). |
| 9404 |
var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0); |
| 9405 |
var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0); |
| 9406 |
var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0); |
| 9407 |
var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0); |
| 9408 |
// not become a vertical line? |
| 9409 |
if (borderLeft !== borderRight) { |
| 9410 |
top = borderTop; |
| 9411 |
bottom = borderBottom; |
| 9412 |
} |
| 9413 |
// not become a horizontal line? |
| 9414 |
if (borderTop !== borderBottom) { |
| 9415 |
left = borderLeft; |
| 9416 |
right = borderRight; |
| 9417 |
} |
| 9418 |
} |
| 9419 |
|
| 9420 |
ctx.beginPath(); |
| 9421 |
ctx.fillStyle = vm.backgroundColor; |
| 9422 |
ctx.strokeStyle = vm.borderColor; |
| 9423 |
ctx.lineWidth = borderWidth; |
| 9424 |
|
| 9425 |
// Corner points, from bottom-left to bottom-right clockwise |
| 9426 |
// | 1 2 | |
| 9427 |
// | 0 3 | |
| 9428 |
var corners = [ |
| 9429 |
[left, bottom], |
| 9430 |
[left, top], |
| 9431 |
[right, top], |
| 9432 |
[right, bottom] |
| 9433 |
]; |
| 9434 |
|
| 9435 |
// Find first (starting) corner with fallback to 'bottom' |
| 9436 |
var borders = ['bottom', 'left', 'top', 'right']; |
| 9437 |
var startCorner = borders.indexOf(borderSkipped, 0); |
| 9438 |
if (startCorner === -1) { |
| 9439 |
startCorner = 0; |
| 9440 |
} |
| 9441 |
|
| 9442 |
function cornerAt(index) { |
| 9443 |
return corners[(startCorner + index) % 4]; |
| 9444 |
} |
| 9445 |
|
| 9446 |
// Draw rectangle from 'startCorner' |
| 9447 |
var corner = cornerAt(0); |
| 9448 |
ctx.moveTo(corner[0], corner[1]); |
| 9449 |
|
| 9450 |
for (var i = 1; i < 4; i++) { |
| 9451 |
corner = cornerAt(i); |
| 9452 |
ctx.lineTo(corner[0], corner[1]); |
| 9453 |
} |
| 9454 |
|
| 9455 |
ctx.fill(); |
| 9456 |
if (borderWidth) { |
| 9457 |
ctx.stroke(); |
| 9458 |
} |
| 9459 |
}, |
| 9460 |
|
| 9461 |
height: function() { |
| 9462 |
var vm = this._view; |
| 9463 |
return vm.base - vm.y; |
| 9464 |
}, |
| 9465 |
|
| 9466 |
inRange: function(mouseX, mouseY) { |
| 9467 |
var inRange = false; |
| 9468 |
|
| 9469 |
if (this._view) { |
| 9470 |
var bounds = getBarBounds(this); |
| 9471 |
inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; |
| 9472 |
} |
| 9473 |
|
| 9474 |
return inRange; |
| 9475 |
}, |
| 9476 |
|
| 9477 |
inLabelRange: function(mouseX, mouseY) { |
| 9478 |
var me = this; |
| 9479 |
if (!me._view) { |
| 9480 |
return false; |
| 9481 |
} |
| 9482 |
|
| 9483 |
var inRange = false; |
| 9484 |
var bounds = getBarBounds(me); |
| 9485 |
|
| 9486 |
if (isVertical(me)) { |
| 9487 |
inRange = mouseX >= bounds.left && mouseX <= bounds.right; |
| 9488 |
} else { |
| 9489 |
inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; |
| 9490 |
} |
| 9491 |
|
| 9492 |
return inRange; |
| 9493 |
}, |
| 9494 |
|
| 9495 |
inXRange: function(mouseX) { |
| 9496 |
var bounds = getBarBounds(this); |
| 9497 |
return mouseX >= bounds.left && mouseX <= bounds.right; |
| 9498 |
}, |
| 9499 |
|
| 9500 |
inYRange: function(mouseY) { |
| 9501 |
var bounds = getBarBounds(this); |
| 9502 |
return mouseY >= bounds.top && mouseY <= bounds.bottom; |
| 9503 |
}, |
| 9504 |
|
| 9505 |
getCenterPoint: function() { |
| 9506 |
var vm = this._view; |
| 9507 |
var x, y; |
| 9508 |
if (isVertical(this)) { |
| 9509 |
x = vm.x; |
| 9510 |
y = (vm.y + vm.base) / 2; |
| 9511 |
} else { |
| 9512 |
x = (vm.x + vm.base) / 2; |
| 9513 |
y = vm.y; |
| 9514 |
} |
| 9515 |
|
| 9516 |
return {x: x, y: y}; |
| 9517 |
}, |
| 9518 |
|
| 9519 |
getArea: function() { |
| 9520 |
var vm = this._view; |
| 9521 |
return vm.width * Math.abs(vm.y - vm.base); |
| 9522 |
}, |
| 9523 |
|
| 9524 |
tooltipPosition: function() { |
| 9525 |
var vm = this._view; |
| 9526 |
return { |
| 9527 |
x: vm.x, |
| 9528 |
y: vm.y |
| 9529 |
}; |
| 9530 |
} |
| 9531 |
}); |
| 9532 |
|
| 9533 |
},{"25":25,"26":26}],40:[function(require,module,exports){ |
| 9534 |
'use strict'; |
| 9535 |
|
| 9536 |
module.exports = {}; |
| 9537 |
module.exports.Arc = require(36); |
| 9538 |
module.exports.Line = require(37); |
| 9539 |
module.exports.Point = require(38); |
| 9540 |
module.exports.Rectangle = require(39); |
| 9541 |
|
| 9542 |
},{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){ |
| 9543 |
'use strict'; |
| 9544 |
|
| 9545 |
var helpers = require(42); |
| 9546 |
|
| 9547 |
/** |
| 9548 |
* @namespace Chart.helpers.canvas |
| 9549 |
*/ |
| 9550 |
var exports = module.exports = { |
| 9551 |
/** |
| 9552 |
* Clears the entire canvas associated to the given `chart`. |
| 9553 |
* @param {Chart} chart - The chart for which to clear the canvas. |
| 9554 |
*/ |
| 9555 |
clear: function(chart) { |
| 9556 |
chart.ctx.clearRect(0, 0, chart.width, chart.height); |
| 9557 |
}, |
| 9558 |
|
| 9559 |
/** |
| 9560 |
* Creates a "path" for a rectangle with rounded corners at position (x, y) with a |
| 9561 |
* given size (width, height) and the same `radius` for all corners. |
| 9562 |
* @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. |
| 9563 |
* @param {Number} x - The x axis of the coordinate for the rectangle starting point. |
| 9564 |
* @param {Number} y - The y axis of the coordinate for the rectangle starting point. |
| 9565 |
* @param {Number} width - The rectangle's width. |
| 9566 |
* @param {Number} height - The rectangle's height. |
| 9567 |
* @param {Number} radius - The rounded amount (in pixels) for the four corners. |
| 9568 |
* @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? |
| 9569 |
*/ |
| 9570 |
roundedRect: function(ctx, x, y, width, height, radius) { |
| 9571 |
if (radius) { |
| 9572 |
var rx = Math.min(radius, width / 2); |
| 9573 |
var ry = Math.min(radius, height / 2); |
| 9574 |
|
| 9575 |
ctx.moveTo(x + rx, y); |
| 9576 |
ctx.lineTo(x + width - rx, y); |
| 9577 |
ctx.quadraticCurveTo(x + width, y, x + width, y + ry); |
| 9578 |
ctx.lineTo(x + width, y + height - ry); |
| 9579 |
ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height); |
| 9580 |
ctx.lineTo(x + rx, y + height); |
| 9581 |
ctx.quadraticCurveTo(x, y + height, x, y + height - ry); |
| 9582 |
ctx.lineTo(x, y + ry); |
| 9583 |
ctx.quadraticCurveTo(x, y, x + rx, y); |
| 9584 |
} else { |
| 9585 |
ctx.rect(x, y, width, height); |
| 9586 |
} |
| 9587 |
}, |
| 9588 |
|
| 9589 |
drawPoint: function(ctx, style, radius, x, y) { |
| 9590 |
var type, edgeLength, xOffset, yOffset, height, size; |
| 9591 |
|
| 9592 |
if (style && typeof style === 'object') { |
| 9593 |
type = style.toString(); |
| 9594 |
if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { |
| 9595 |
ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height); |
| 9596 |
return; |
| 9597 |
} |
| 9598 |
} |
| 9599 |
|
| 9600 |
if (isNaN(radius) || radius <= 0) { |
| 9601 |
return; |
| 9602 |
} |
| 9603 |
|
| 9604 |
switch (style) { |
| 9605 |
// Default includes circle |
| 9606 |
default: |
| 9607 |
ctx.beginPath(); |
| 9608 |
ctx.arc(x, y, radius, 0, Math.PI * 2); |
| 9609 |
ctx.closePath(); |
| 9610 |
ctx.fill(); |
| 9611 |
break; |
| 9612 |
case 'triangle': |
| 9613 |
ctx.beginPath(); |
| 9614 |
edgeLength = 3 * radius / Math.sqrt(3); |
| 9615 |
height = edgeLength * Math.sqrt(3) / 2; |
| 9616 |
ctx.moveTo(x - edgeLength / 2, y + height / 3); |
| 9617 |
ctx.lineTo(x + edgeLength / 2, y + height / 3); |
| 9618 |
ctx.lineTo(x, y - 2 * height / 3); |
| 9619 |
ctx.closePath(); |
| 9620 |
ctx.fill(); |
| 9621 |
break; |
| 9622 |
case 'rect': |
| 9623 |
size = 1 / Math.SQRT2 * radius; |
| 9624 |
ctx.beginPath(); |
| 9625 |
ctx.fillRect(x - size, y - size, 2 * size, 2 * size); |
| 9626 |
ctx.strokeRect(x - size, y - size, 2 * size, 2 * size); |
| 9627 |
break; |
| 9628 |
case 'rectRounded': |
| 9629 |
var offset = radius / Math.SQRT2; |
| 9630 |
var leftX = x - offset; |
| 9631 |
var topY = y - offset; |
| 9632 |
var sideSize = Math.SQRT2 * radius; |
| 9633 |
ctx.beginPath(); |
| 9634 |
this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2); |
| 9635 |
ctx.closePath(); |
| 9636 |
ctx.fill(); |
| 9637 |
break; |
| 9638 |
case 'rectRot': |
| 9639 |
size = 1 / Math.SQRT2 * radius; |
| 9640 |
ctx.beginPath(); |
| 9641 |
ctx.moveTo(x - size, y); |
| 9642 |
ctx.lineTo(x, y + size); |
| 9643 |
ctx.lineTo(x + size, y); |
| 9644 |
ctx.lineTo(x, y - size); |
| 9645 |
ctx.closePath(); |
| 9646 |
ctx.fill(); |
| 9647 |
break; |
| 9648 |
case 'cross': |
| 9649 |
ctx.beginPath(); |
| 9650 |
ctx.moveTo(x, y + radius); |
| 9651 |
ctx.lineTo(x, y - radius); |
| 9652 |
ctx.moveTo(x - radius, y); |
| 9653 |
ctx.lineTo(x + radius, y); |
| 9654 |
ctx.closePath(); |
| 9655 |
break; |
| 9656 |
case 'crossRot': |
| 9657 |
ctx.beginPath(); |
| 9658 |
xOffset = Math.cos(Math.PI / 4) * radius; |
| 9659 |
yOffset = Math.sin(Math.PI / 4) * radius; |
| 9660 |
ctx.moveTo(x - xOffset, y - yOffset); |
| 9661 |
ctx.lineTo(x + xOffset, y + yOffset); |
| 9662 |
ctx.moveTo(x - xOffset, y + yOffset); |
| 9663 |
ctx.lineTo(x + xOffset, y - yOffset); |
| 9664 |
ctx.closePath(); |
| 9665 |
break; |
| 9666 |
case 'star': |
| 9667 |
ctx.beginPath(); |
| 9668 |
ctx.moveTo(x, y + radius); |
| 9669 |
ctx.lineTo(x, y - radius); |
| 9670 |
ctx.moveTo(x - radius, y); |
| 9671 |
ctx.lineTo(x + radius, y); |
| 9672 |
xOffset = Math.cos(Math.PI / 4) * radius; |
| 9673 |
yOffset = Math.sin(Math.PI / 4) * radius; |
| 9674 |
ctx.moveTo(x - xOffset, y - yOffset); |
| 9675 |
ctx.lineTo(x + xOffset, y + yOffset); |
| 9676 |
ctx.moveTo(x - xOffset, y + yOffset); |
| 9677 |
ctx.lineTo(x + xOffset, y - yOffset); |
| 9678 |
ctx.closePath(); |
| 9679 |
break; |
| 9680 |
case 'line': |
| 9681 |
ctx.beginPath(); |
| 9682 |
ctx.moveTo(x - radius, y); |
| 9683 |
ctx.lineTo(x + radius, y); |
| 9684 |
ctx.closePath(); |
| 9685 |
break; |
| 9686 |
case 'dash': |
| 9687 |
ctx.beginPath(); |
| 9688 |
ctx.moveTo(x, y); |
| 9689 |
ctx.lineTo(x + radius, y); |
| 9690 |
ctx.closePath(); |
| 9691 |
break; |
| 9692 |
} |
| 9693 |
|
| 9694 |
ctx.stroke(); |
| 9695 |
}, |
| 9696 |
|
| 9697 |
clipArea: function(ctx, area) { |
| 9698 |
ctx.save(); |
| 9699 |
ctx.beginPath(); |
| 9700 |
ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); |
| 9701 |
ctx.clip(); |
| 9702 |
}, |
| 9703 |
|
| 9704 |
unclipArea: function(ctx) { |
| 9705 |
ctx.restore(); |
| 9706 |
}, |
| 9707 |
|
| 9708 |
lineTo: function(ctx, previous, target, flip) { |
| 9709 |
if (target.steppedLine) { |
| 9710 |
if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) { |
| 9711 |
ctx.lineTo(previous.x, target.y); |
| 9712 |
} else { |
| 9713 |
ctx.lineTo(target.x, previous.y); |
| 9714 |
} |
| 9715 |
ctx.lineTo(target.x, target.y); |
| 9716 |
return; |
| 9717 |
} |
| 9718 |
|
| 9719 |
if (!target.tension) { |
| 9720 |
ctx.lineTo(target.x, target.y); |
| 9721 |
return; |
| 9722 |
} |
| 9723 |
|
| 9724 |
ctx.bezierCurveTo( |
| 9725 |
flip ? previous.controlPointPreviousX : previous.controlPointNextX, |
| 9726 |
flip ? previous.controlPointPreviousY : previous.controlPointNextY, |
| 9727 |
flip ? target.controlPointNextX : target.controlPointPreviousX, |
| 9728 |
flip ? target.controlPointNextY : target.controlPointPreviousY, |
| 9729 |
target.x, |
| 9730 |
target.y); |
| 9731 |
} |
| 9732 |
}; |
| 9733 |
|
| 9734 |
// DEPRECATIONS |
| 9735 |
|
| 9736 |
/** |
| 9737 |
* Provided for backward compatibility, use Chart.helpers.canvas.clear instead. |
| 9738 |
* @namespace Chart.helpers.clear |
| 9739 |
* @deprecated since version 2.7.0 |
| 9740 |
* @todo remove at version 3 |
| 9741 |
* @private |
| 9742 |
*/ |
| 9743 |
helpers.clear = exports.clear; |
| 9744 |
|
| 9745 |
/** |
| 9746 |
* Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. |
| 9747 |
* @namespace Chart.helpers.drawRoundedRectangle |
| 9748 |
* @deprecated since version 2.7.0 |
| 9749 |
* @todo remove at version 3 |
| 9750 |
* @private |
| 9751 |
*/ |
| 9752 |
helpers.drawRoundedRectangle = function(ctx) { |
| 9753 |
ctx.beginPath(); |
| 9754 |
exports.roundedRect.apply(exports, arguments); |
| 9755 |
ctx.closePath(); |
| 9756 |
}; |
| 9757 |
|
| 9758 |
},{"42":42}],42:[function(require,module,exports){ |
| 9759 |
'use strict'; |
| 9760 |
|
| 9761 |
/** |
| 9762 |
* @namespace Chart.helpers |
| 9763 |
*/ |
| 9764 |
var helpers = { |
| 9765 |
/** |
| 9766 |
* An empty function that can be used, for example, for optional callback. |
| 9767 |
*/ |
| 9768 |
noop: function() {}, |
| 9769 |
|
| 9770 |
/** |
| 9771 |
* Returns a unique id, sequentially generated from a global variable. |
| 9772 |
* @returns {Number} |
| 9773 |
* @function |
| 9774 |
*/ |
| 9775 |
uid: (function() { |
| 9776 |
var id = 0; |
| 9777 |
return function() { |
| 9778 |
return id++; |
| 9779 |
}; |
| 9780 |
}()), |
| 9781 |
|
| 9782 |
/** |
| 9783 |
* Returns true if `value` is neither null nor undefined, else returns false. |
| 9784 |
* @param {*} value - The value to test. |
| 9785 |
* @returns {Boolean} |
| 9786 |
* @since 2.7.0 |
| 9787 |
*/ |
| 9788 |
isNullOrUndef: function(value) { |
| 9789 |
return value === null || typeof value === 'undefined'; |
| 9790 |
}, |
| 9791 |
|
| 9792 |
/** |
| 9793 |
* Returns true if `value` is an array, else returns false. |
| 9794 |
* @param {*} value - The value to test. |
| 9795 |
* @returns {Boolean} |
| 9796 |
* @function |
| 9797 |
*/ |
| 9798 |
isArray: Array.isArray ? Array.isArray : function(value) { |
| 9799 |
return Object.prototype.toString.call(value) === '[object Array]'; |
| 9800 |
}, |
| 9801 |
|
| 9802 |
/** |
| 9803 |
* Returns true if `value` is an object (excluding null), else returns false. |
| 9804 |
* @param {*} value - The value to test. |
| 9805 |
* @returns {Boolean} |
| 9806 |
* @since 2.7.0 |
| 9807 |
*/ |
| 9808 |
isObject: function(value) { |
| 9809 |
return value !== null && Object.prototype.toString.call(value) === '[object Object]'; |
| 9810 |
}, |
| 9811 |
|
| 9812 |
/** |
| 9813 |
* Returns `value` if defined, else returns `defaultValue`. |
| 9814 |
* @param {*} value - The value to return if defined. |
| 9815 |
* @param {*} defaultValue - The value to return if `value` is undefined. |
| 9816 |
* @returns {*} |
| 9817 |
*/ |
| 9818 |
valueOrDefault: function(value, defaultValue) { |
| 9819 |
return typeof value === 'undefined' ? defaultValue : value; |
| 9820 |
}, |
| 9821 |
|
| 9822 |
/** |
| 9823 |
* Returns value at the given `index` in array if defined, else returns `defaultValue`. |
| 9824 |
* @param {Array} value - The array to lookup for value at `index`. |
| 9825 |
* @param {Number} index - The index in `value` to lookup for value. |
| 9826 |
* @param {*} defaultValue - The value to return if `value[index]` is undefined. |
| 9827 |
* @returns {*} |
| 9828 |
*/ |
| 9829 |
valueAtIndexOrDefault: function(value, index, defaultValue) { |
| 9830 |
return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); |
| 9831 |
}, |
| 9832 |
|
| 9833 |
/** |
| 9834 |
* Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the |
| 9835 |
* value returned by `fn`. If `fn` is not a function, this method returns undefined. |
| 9836 |
* @param {Function} fn - The function to call. |
| 9837 |
* @param {Array|undefined|null} args - The arguments with which `fn` should be called. |
| 9838 |
* @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. |
| 9839 |
* @returns {*} |
| 9840 |
*/ |
| 9841 |
callback: function(fn, args, thisArg) { |
| 9842 |
if (fn && typeof fn.call === 'function') { |
| 9843 |
return fn.apply(thisArg, args); |
| 9844 |
} |
| 9845 |
}, |
| 9846 |
|
| 9847 |
/** |
| 9848 |
* Note(SB) for performance sake, this method should only be used when loopable type |
| 9849 |
* is unknown or in none intensive code (not called often and small loopable). Else |
| 9850 |
* it's preferable to use a regular for() loop and save extra function calls. |
| 9851 |
* @param {Object|Array} loopable - The object or array to be iterated. |
| 9852 |
* @param {Function} fn - The function to call for each item. |
| 9853 |
* @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. |
| 9854 |
* @param {Boolean} [reverse] - If true, iterates backward on the loopable. |
| 9855 |
*/ |
| 9856 |
each: function(loopable, fn, thisArg, reverse) { |
| 9857 |
var i, len, keys; |
| 9858 |
if (helpers.isArray(loopable)) { |
| 9859 |
len = loopable.length; |
| 9860 |
if (reverse) { |
| 9861 |
for (i = len - 1; i >= 0; i--) { |
| 9862 |
fn.call(thisArg, loopable[i], i); |
| 9863 |
} |
| 9864 |
} else { |
| 9865 |
for (i = 0; i < len; i++) { |
| 9866 |
fn.call(thisArg, loopable[i], i); |
| 9867 |
} |
| 9868 |
} |
| 9869 |
} else if (helpers.isObject(loopable)) { |
| 9870 |
keys = Object.keys(loopable); |
| 9871 |
len = keys.length; |
| 9872 |
for (i = 0; i < len; i++) { |
| 9873 |
fn.call(thisArg, loopable[keys[i]], keys[i]); |
| 9874 |
} |
| 9875 |
} |
| 9876 |
}, |
| 9877 |
|
| 9878 |
/** |
| 9879 |
* Returns true if the `a0` and `a1` arrays have the same content, else returns false. |
| 9880 |
* @see http://stackoverflow.com/a/14853974 |
| 9881 |
* @param {Array} a0 - The array to compare |
| 9882 |
* @param {Array} a1 - The array to compare |
| 9883 |
* @returns {Boolean} |
| 9884 |
*/ |
| 9885 |
arrayEquals: function(a0, a1) { |
| 9886 |
var i, ilen, v0, v1; |
| 9887 |
|
| 9888 |
if (!a0 || !a1 || a0.length !== a1.length) { |
| 9889 |
return false; |
| 9890 |
} |
| 9891 |
|
| 9892 |
for (i = 0, ilen = a0.length; i < ilen; ++i) { |
| 9893 |
v0 = a0[i]; |
| 9894 |
v1 = a1[i]; |
| 9895 |
|
| 9896 |
if (v0 instanceof Array && v1 instanceof Array) { |
| 9897 |
if (!helpers.arrayEquals(v0, v1)) { |
| 9898 |
return false; |
| 9899 |
} |
| 9900 |
} else if (v0 !== v1) { |
| 9901 |
// NOTE: two different object instances will never be equal: {x:20} != {x:20} |
| 9902 |
return false; |
| 9903 |
} |
| 9904 |
} |
| 9905 |
|
| 9906 |
return true; |
| 9907 |
}, |
| 9908 |
|
| 9909 |
/** |
| 9910 |
* Returns a deep copy of `source` without keeping references on objects and arrays. |
| 9911 |
* @param {*} source - The value to clone. |
| 9912 |
* @returns {*} |
| 9913 |
*/ |
| 9914 |
clone: function(source) { |
| 9915 |
if (helpers.isArray(source)) { |
| 9916 |
return source.map(helpers.clone); |
| 9917 |
} |
| 9918 |
|
| 9919 |
if (helpers.isObject(source)) { |
| 9920 |
var target = {}; |
| 9921 |
var keys = Object.keys(source); |
| 9922 |
var klen = keys.length; |
| 9923 |
var k = 0; |
| 9924 |
|
| 9925 |
for (; k < klen; ++k) { |
| 9926 |
target[keys[k]] = helpers.clone(source[keys[k]]); |
| 9927 |
} |
| 9928 |
|
| 9929 |
return target; |
| 9930 |
} |
| 9931 |
|
| 9932 |
return source; |
| 9933 |
}, |
| 9934 |
|
| 9935 |
/** |
| 9936 |
* The default merger when Chart.helpers.merge is called without merger option. |
| 9937 |
* Note(SB): this method is also used by configMerge and scaleMerge as fallback. |
| 9938 |
* @private |
| 9939 |
*/ |
| 9940 |
_merger: function(key, target, source, options) { |
| 9941 |
var tval = target[key]; |
| 9942 |
var sval = source[key]; |
| 9943 |
|
| 9944 |
if (helpers.isObject(tval) && helpers.isObject(sval)) { |
| 9945 |
helpers.merge(tval, sval, options); |
| 9946 |
} else { |
| 9947 |
target[key] = helpers.clone(sval); |
| 9948 |
} |
| 9949 |
}, |
| 9950 |
|
| 9951 |
/** |
| 9952 |
* Merges source[key] in target[key] only if target[key] is undefined. |
| 9953 |
* @private |
| 9954 |
*/ |
| 9955 |
_mergerIf: function(key, target, source) { |
| 9956 |
var tval = target[key]; |
| 9957 |
var sval = source[key]; |
| 9958 |
|
| 9959 |
if (helpers.isObject(tval) && helpers.isObject(sval)) { |
| 9960 |
helpers.mergeIf(tval, sval); |
| 9961 |
} else if (!target.hasOwnProperty(key)) { |
| 9962 |
target[key] = helpers.clone(sval); |
| 9963 |
} |
| 9964 |
}, |
| 9965 |
|
| 9966 |
/** |
| 9967 |
* Recursively deep copies `source` properties into `target` with the given `options`. |
| 9968 |
* IMPORTANT: `target` is not cloned and will be updated with `source` properties. |
| 9969 |
* @param {Object} target - The target object in which all sources are merged into. |
| 9970 |
* @param {Object|Array(Object)} source - Object(s) to merge into `target`. |
| 9971 |
* @param {Object} [options] - Merging options: |
| 9972 |
* @param {Function} [options.merger] - The merge method (key, target, source, options) |
| 9973 |
* @returns {Object} The `target` object. |
| 9974 |
*/ |
| 9975 |
merge: function(target, source, options) { |
| 9976 |
var sources = helpers.isArray(source) ? source : [source]; |
| 9977 |
var ilen = sources.length; |
| 9978 |
var merge, i, keys, klen, k; |
| 9979 |
|
| 9980 |
if (!helpers.isObject(target)) { |
| 9981 |
return target; |
| 9982 |
} |
| 9983 |
|
| 9984 |
options = options || {}; |
| 9985 |
merge = options.merger || helpers._merger; |
| 9986 |
|
| 9987 |
for (i = 0; i < ilen; ++i) { |
| 9988 |
source = sources[i]; |
| 9989 |
if (!helpers.isObject(source)) { |
| 9990 |
continue; |
| 9991 |
} |
| 9992 |
|
| 9993 |
keys = Object.keys(source); |
| 9994 |
for (k = 0, klen = keys.length; k < klen; ++k) { |
| 9995 |
merge(keys[k], target, source, options); |
| 9996 |
} |
| 9997 |
} |
| 9998 |
|
| 9999 |
return target; |
| 10000 |
}, |
| 10001 |
|
| 10002 |
/** |
| 10003 |
* Recursively deep copies `source` properties into `target` *only* if not defined in target. |
| 10004 |
* IMPORTANT: `target` is not cloned and will be updated with `source` properties. |
| 10005 |
* @param {Object} target - The target object in which all sources are merged into. |
| 10006 |
* @param {Object|Array(Object)} source - Object(s) to merge into `target`. |
| 10007 |
* @returns {Object} The `target` object. |
| 10008 |
*/ |
| 10009 |
mergeIf: function(target, source) { |
| 10010 |
return helpers.merge(target, source, {merger: helpers._mergerIf}); |
| 10011 |
}, |
| 10012 |
|
| 10013 |
/** |
| 10014 |
* Applies the contents of two or more objects together into the first object. |
| 10015 |
* @param {Object} target - The target object in which all objects are merged into. |
| 10016 |
* @param {Object} arg1 - Object containing additional properties to merge in target. |
| 10017 |
* @param {Object} argN - Additional objects containing properties to merge in target. |
| 10018 |
* @returns {Object} The `target` object. |
| 10019 |
*/ |
| 10020 |
extend: function(target) { |
| 10021 |
var setFn = function(value, key) { |
| 10022 |
target[key] = value; |
| 10023 |
}; |
| 10024 |
for (var i = 1, ilen = arguments.length; i < ilen; ++i) { |
| 10025 |
helpers.each(arguments[i], setFn); |
| 10026 |
} |
| 10027 |
return target; |
| 10028 |
}, |
| 10029 |
|
| 10030 |
/** |
| 10031 |
* Basic javascript inheritance based on the model created in Backbone.js |
| 10032 |
*/ |
| 10033 |
inherits: function(extensions) { |
| 10034 |
var me = this; |
| 10035 |
var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { |
| 10036 |
return me.apply(this, arguments); |
| 10037 |
}; |
| 10038 |
|
| 10039 |
var Surrogate = function() { |
| 10040 |
this.constructor = ChartElement; |
| 10041 |
}; |
| 10042 |
|
| 10043 |
Surrogate.prototype = me.prototype; |
| 10044 |
ChartElement.prototype = new Surrogate(); |
| 10045 |
ChartElement.extend = helpers.inherits; |
| 10046 |
|
| 10047 |
if (extensions) { |
| 10048 |
helpers.extend(ChartElement.prototype, extensions); |
| 10049 |
} |
| 10050 |
|
| 10051 |
ChartElement.__super__ = me.prototype; |
| 10052 |
return ChartElement; |
| 10053 |
} |
| 10054 |
}; |
| 10055 |
|
| 10056 |
module.exports = helpers; |
| 10057 |
|
| 10058 |
// DEPRECATIONS |
| 10059 |
|
| 10060 |
/** |
| 10061 |
* Provided for backward compatibility, use Chart.helpers.callback instead. |
| 10062 |
* @function Chart.helpers.callCallback |
| 10063 |
* @deprecated since version 2.6.0 |
| 10064 |
* @todo remove at version 3 |
| 10065 |
* @private |
| 10066 |
*/ |
| 10067 |
helpers.callCallback = helpers.callback; |
| 10068 |
|
| 10069 |
/** |
| 10070 |
* Provided for backward compatibility, use Array.prototype.indexOf instead. |
| 10071 |
* Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ |
| 10072 |
* @function Chart.helpers.indexOf |
| 10073 |
* @deprecated since version 2.7.0 |
| 10074 |
* @todo remove at version 3 |
| 10075 |
* @private |
| 10076 |
*/ |
| 10077 |
helpers.indexOf = function(array, item, fromIndex) { |
| 10078 |
return Array.prototype.indexOf.call(array, item, fromIndex); |
| 10079 |
}; |
| 10080 |
|
| 10081 |
/** |
| 10082 |
* Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. |
| 10083 |
* @function Chart.helpers.getValueOrDefault |
| 10084 |
* @deprecated since version 2.7.0 |
| 10085 |
* @todo remove at version 3 |
| 10086 |
* @private |
| 10087 |
*/ |
| 10088 |
helpers.getValueOrDefault = helpers.valueOrDefault; |
| 10089 |
|
| 10090 |
/** |
| 10091 |
* Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. |
| 10092 |
* @function Chart.helpers.getValueAtIndexOrDefault |
| 10093 |
* @deprecated since version 2.7.0 |
| 10094 |
* @todo remove at version 3 |
| 10095 |
* @private |
| 10096 |
*/ |
| 10097 |
helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; |
| 10098 |
|
| 10099 |
},{}],43:[function(require,module,exports){ |
| 10100 |
'use strict'; |
| 10101 |
|
| 10102 |
var helpers = require(42); |
| 10103 |
|
| 10104 |
/** |
| 10105 |
* Easing functions adapted from Robert Penner's easing equations. |
| 10106 |
* @namespace Chart.helpers.easingEffects |
| 10107 |
* @see http://www.robertpenner.com/easing/ |
| 10108 |
*/ |
| 10109 |
var effects = { |
| 10110 |
linear: function(t) { |
| 10111 |
return t; |
| 10112 |
}, |
| 10113 |
|
| 10114 |
easeInQuad: function(t) { |
| 10115 |
return t * t; |
| 10116 |
}, |
| 10117 |
|
| 10118 |
easeOutQuad: function(t) { |
| 10119 |
return -t * (t - 2); |
| 10120 |
}, |
| 10121 |
|
| 10122 |
easeInOutQuad: function(t) { |
| 10123 |
if ((t /= 0.5) < 1) { |
| 10124 |
return 0.5 * t * t; |
| 10125 |
} |
| 10126 |
return -0.5 * ((--t) * (t - 2) - 1); |
| 10127 |
}, |
| 10128 |
|
| 10129 |
easeInCubic: function(t) { |
| 10130 |
return t * t * t; |
| 10131 |
}, |
| 10132 |
|
| 10133 |
easeOutCubic: function(t) { |
| 10134 |
return (t = t - 1) * t * t + 1; |
| 10135 |
}, |
| 10136 |
|
| 10137 |
easeInOutCubic: function(t) { |
| 10138 |
if ((t /= 0.5) < 1) { |
| 10139 |
return 0.5 * t * t * t; |
| 10140 |
} |
| 10141 |
return 0.5 * ((t -= 2) * t * t + 2); |
| 10142 |
}, |
| 10143 |
|
| 10144 |
easeInQuart: function(t) { |
| 10145 |
return t * t * t * t; |
| 10146 |
}, |
| 10147 |
|
| 10148 |
easeOutQuart: function(t) { |
| 10149 |
return -((t = t - 1) * t * t * t - 1); |
| 10150 |
}, |
| 10151 |
|
| 10152 |
easeInOutQuart: function(t) { |
| 10153 |
if ((t /= 0.5) < 1) { |
| 10154 |
return 0.5 * t * t * t * t; |
| 10155 |
} |
| 10156 |
return -0.5 * ((t -= 2) * t * t * t - 2); |
| 10157 |
}, |
| 10158 |
|
| 10159 |
easeInQuint: function(t) { |
| 10160 |
return t * t * t * t * t; |
| 10161 |
}, |
| 10162 |
|
| 10163 |
easeOutQuint: function(t) { |
| 10164 |
return (t = t - 1) * t * t * t * t + 1; |
| 10165 |
}, |
| 10166 |
|
| 10167 |
easeInOutQuint: function(t) { |
| 10168 |
if ((t /= 0.5) < 1) { |
| 10169 |
return 0.5 * t * t * t * t * t; |
| 10170 |
} |
| 10171 |
return 0.5 * ((t -= 2) * t * t * t * t + 2); |
| 10172 |
}, |
| 10173 |
|
| 10174 |
easeInSine: function(t) { |
| 10175 |
return -Math.cos(t * (Math.PI / 2)) + 1; |
| 10176 |
}, |
| 10177 |
|
| 10178 |
easeOutSine: function(t) { |
| 10179 |
return Math.sin(t * (Math.PI / 2)); |
| 10180 |
}, |
| 10181 |
|
| 10182 |
easeInOutSine: function(t) { |
| 10183 |
return -0.5 * (Math.cos(Math.PI * t) - 1); |
| 10184 |
}, |
| 10185 |
|
| 10186 |
easeInExpo: function(t) { |
| 10187 |
return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); |
| 10188 |
}, |
| 10189 |
|
| 10190 |
easeOutExpo: function(t) { |
| 10191 |
return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; |
| 10192 |
}, |
| 10193 |
|
| 10194 |
easeInOutExpo: function(t) { |
| 10195 |
if (t === 0) { |
| 10196 |
return 0; |
| 10197 |
} |
| 10198 |
if (t === 1) { |
| 10199 |
return 1; |
| 10200 |
} |
| 10201 |
if ((t /= 0.5) < 1) { |
| 10202 |
return 0.5 * Math.pow(2, 10 * (t - 1)); |
| 10203 |
} |
| 10204 |
return 0.5 * (-Math.pow(2, -10 * --t) + 2); |
| 10205 |
}, |
| 10206 |
|
| 10207 |
easeInCirc: function(t) { |
| 10208 |
if (t >= 1) { |
| 10209 |
return t; |
| 10210 |
} |
| 10211 |
return -(Math.sqrt(1 - t * t) - 1); |
| 10212 |
}, |
| 10213 |
|
| 10214 |
easeOutCirc: function(t) { |
| 10215 |
return Math.sqrt(1 - (t = t - 1) * t); |
| 10216 |
}, |
| 10217 |
|
| 10218 |
easeInOutCirc: function(t) { |
| 10219 |
if ((t /= 0.5) < 1) { |
| 10220 |
return -0.5 * (Math.sqrt(1 - t * t) - 1); |
| 10221 |
} |
| 10222 |
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); |
| 10223 |
}, |
| 10224 |
|
| 10225 |
easeInElastic: function(t) { |
| 10226 |
var s = 1.70158; |
| 10227 |
var p = 0; |
| 10228 |
var a = 1; |
| 10229 |
if (t === 0) { |
| 10230 |
return 0; |
| 10231 |
} |
| 10232 |
if (t === 1) { |
| 10233 |
return 1; |
| 10234 |
} |
| 10235 |
if (!p) { |
| 10236 |
p = 0.3; |
| 10237 |
} |
| 10238 |
if (a < 1) { |
| 10239 |
a = 1; |
| 10240 |
s = p / 4; |
| 10241 |
} else { |
| 10242 |
s = p / (2 * Math.PI) * Math.asin(1 / a); |
| 10243 |
} |
| 10244 |
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); |
| 10245 |
}, |
| 10246 |
|
| 10247 |
easeOutElastic: function(t) { |
| 10248 |
var s = 1.70158; |
| 10249 |
var p = 0; |
| 10250 |
var a = 1; |
| 10251 |
if (t === 0) { |
| 10252 |
return 0; |
| 10253 |
} |
| 10254 |
if (t === 1) { |
| 10255 |
return 1; |
| 10256 |
} |
| 10257 |
if (!p) { |
| 10258 |
p = 0.3; |
| 10259 |
} |
| 10260 |
if (a < 1) { |
| 10261 |
a = 1; |
| 10262 |
s = p / 4; |
| 10263 |
} else { |
| 10264 |
s = p / (2 * Math.PI) * Math.asin(1 / a); |
| 10265 |
} |
| 10266 |
return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; |
| 10267 |
}, |
| 10268 |
|
| 10269 |
easeInOutElastic: function(t) { |
| 10270 |
var s = 1.70158; |
| 10271 |
var p = 0; |
| 10272 |
var a = 1; |
| 10273 |
if (t === 0) { |
| 10274 |
return 0; |
| 10275 |
} |
| 10276 |
if ((t /= 0.5) === 2) { |
| 10277 |
return 1; |
| 10278 |
} |
| 10279 |
if (!p) { |
| 10280 |
p = 0.45; |
| 10281 |
} |
| 10282 |
if (a < 1) { |
| 10283 |
a = 1; |
| 10284 |
s = p / 4; |
| 10285 |
} else { |
| 10286 |
s = p / (2 * Math.PI) * Math.asin(1 / a); |
| 10287 |
} |
| 10288 |
if (t < 1) { |
| 10289 |
return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); |
| 10290 |
} |
| 10291 |
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; |
| 10292 |
}, |
| 10293 |
easeInBack: function(t) { |
| 10294 |
var s = 1.70158; |
| 10295 |
return t * t * ((s + 1) * t - s); |
| 10296 |
}, |
| 10297 |
|
| 10298 |
easeOutBack: function(t) { |
| 10299 |
var s = 1.70158; |
| 10300 |
return (t = t - 1) * t * ((s + 1) * t + s) + 1; |
| 10301 |
}, |
| 10302 |
|
| 10303 |
easeInOutBack: function(t) { |
| 10304 |
var s = 1.70158; |
| 10305 |
if ((t /= 0.5) < 1) { |
| 10306 |
return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); |
| 10307 |
} |
| 10308 |
return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); |
| 10309 |
}, |
| 10310 |
|
| 10311 |
easeInBounce: function(t) { |
| 10312 |
return 1 - effects.easeOutBounce(1 - t); |
| 10313 |
}, |
| 10314 |
|
| 10315 |
easeOutBounce: function(t) { |
| 10316 |
if (t < (1 / 2.75)) { |
| 10317 |
return 7.5625 * t * t; |
| 10318 |
} |
| 10319 |
if (t < (2 / 2.75)) { |
| 10320 |
return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; |
| 10321 |
} |
| 10322 |
if (t < (2.5 / 2.75)) { |
| 10323 |
return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; |
| 10324 |
} |
| 10325 |
return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; |
| 10326 |
}, |
| 10327 |
|
| 10328 |
easeInOutBounce: function(t) { |
| 10329 |
if (t < 0.5) { |
| 10330 |
return effects.easeInBounce(t * 2) * 0.5; |
| 10331 |
} |
| 10332 |
return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; |
| 10333 |
} |
| 10334 |
}; |
| 10335 |
|
| 10336 |
module.exports = { |
| 10337 |
effects: effects |
| 10338 |
}; |
| 10339 |
|
| 10340 |
// DEPRECATIONS |
| 10341 |
|
| 10342 |
/** |
| 10343 |
* Provided for backward compatibility, use Chart.helpers.easing.effects instead. |
| 10344 |
* @function Chart.helpers.easingEffects |
| 10345 |
* @deprecated since version 2.7.0 |
| 10346 |
* @todo remove at version 3 |
| 10347 |
* @private |
| 10348 |
*/ |
| 10349 |
helpers.easingEffects = effects; |
| 10350 |
|
| 10351 |
},{"42":42}],44:[function(require,module,exports){ |
| 10352 |
'use strict'; |
| 10353 |
|
| 10354 |
var helpers = require(42); |
| 10355 |
|
| 10356 |
/** |
| 10357 |
* @alias Chart.helpers.options |
| 10358 |
* @namespace |
| 10359 |
*/ |
| 10360 |
module.exports = { |
| 10361 |
/** |
| 10362 |
* Converts the given line height `value` in pixels for a specific font `size`. |
| 10363 |
* @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). |
| 10364 |
* @param {Number} size - The font size (in pixels) used to resolve relative `value`. |
| 10365 |
* @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid). |
| 10366 |
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height |
| 10367 |
* @since 2.7.0 |
| 10368 |
*/ |
| 10369 |
toLineHeight: function(value, size) { |
| 10370 |
var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); |
| 10371 |
if (!matches || matches[1] === 'normal') { |
| 10372 |
return size * 1.2; |
| 10373 |
} |
| 10374 |
|
| 10375 |
value = +matches[2]; |
| 10376 |
|
| 10377 |
switch (matches[3]) { |
| 10378 |
case 'px': |
| 10379 |
return value; |
| 10380 |
case '%': |
| 10381 |
value /= 100; |
| 10382 |
break; |
| 10383 |
default: |
| 10384 |
break; |
| 10385 |
} |
| 10386 |
|
| 10387 |
return size * value; |
| 10388 |
}, |
| 10389 |
|
| 10390 |
/** |
| 10391 |
* Converts the given value into a padding object with pre-computed width/height. |
| 10392 |
* @param {Number|Object} value - If a number, set the value to all TRBL component, |
| 10393 |
* else, if and object, use defined properties and sets undefined ones to 0. |
| 10394 |
* @returns {Object} The padding values (top, right, bottom, left, width, height) |
| 10395 |
* @since 2.7.0 |
| 10396 |
*/ |
| 10397 |
toPadding: function(value) { |
| 10398 |
var t, r, b, l; |
| 10399 |
|
| 10400 |
if (helpers.isObject(value)) { |
| 10401 |
t = +value.top || 0; |
| 10402 |
r = +value.right || 0; |
| 10403 |
b = +value.bottom || 0; |
| 10404 |
l = +value.left || 0; |
| 10405 |
} else { |
| 10406 |
t = r = b = l = +value || 0; |
| 10407 |
} |
| 10408 |
|
| 10409 |
return { |
| 10410 |
top: t, |
| 10411 |
right: r, |
| 10412 |
bottom: b, |
| 10413 |
left: l, |
| 10414 |
height: t + b, |
| 10415 |
width: l + r |
| 10416 |
}; |
| 10417 |
}, |
| 10418 |
|
| 10419 |
/** |
| 10420 |
* Evaluates the given `inputs` sequentially and returns the first defined value. |
| 10421 |
* @param {Array[]} inputs - An array of values, falling back to the last value. |
| 10422 |
* @param {Object} [context] - If defined and the current value is a function, the value |
| 10423 |
* is called with `context` as first argument and the result becomes the new input. |
| 10424 |
* @param {Number} [index] - If defined and the current value is an array, the value |
| 10425 |
* at `index` become the new input. |
| 10426 |
* @since 2.7.0 |
| 10427 |
*/ |
| 10428 |
resolve: function(inputs, context, index) { |
| 10429 |
var i, ilen, value; |
| 10430 |
|
| 10431 |
for (i = 0, ilen = inputs.length; i < ilen; ++i) { |
| 10432 |
value = inputs[i]; |
| 10433 |
if (value === undefined) { |
| 10434 |
continue; |
| 10435 |
} |
| 10436 |
if (context !== undefined && typeof value === 'function') { |
| 10437 |
value = value(context); |
| 10438 |
} |
| 10439 |
if (index !== undefined && helpers.isArray(value)) { |
| 10440 |
value = value[index]; |
| 10441 |
} |
| 10442 |
if (value !== undefined) { |
| 10443 |
return value; |
| 10444 |
} |
| 10445 |
} |
| 10446 |
} |
| 10447 |
}; |
| 10448 |
|
| 10449 |
},{"42":42}],45:[function(require,module,exports){ |
| 10450 |
'use strict'; |
| 10451 |
|
| 10452 |
module.exports = require(42); |
| 10453 |
module.exports.easing = require(43); |
| 10454 |
module.exports.canvas = require(41); |
| 10455 |
module.exports.options = require(44); |
| 10456 |
|
| 10457 |
},{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){ |
| 10458 |
/** |
| 10459 |
* Platform fallback implementation (minimal). |
| 10460 |
* @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 |
| 10461 |
*/ |
| 10462 |
|
| 10463 |
module.exports = { |
| 10464 |
acquireContext: function(item) { |
| 10465 |
if (item && item.canvas) { |
| 10466 |
// Support for any object associated to a canvas (including a context2d) |
| 10467 |
item = item.canvas; |
| 10468 |
} |
| 10469 |
|
| 10470 |
return item && item.getContext('2d') || null; |
| 10471 |
} |
| 10472 |
}; |
| 10473 |
|
| 10474 |
},{}],47:[function(require,module,exports){ |
| 10475 |
/** |
| 10476 |
* Chart.Platform implementation for targeting a web browser |
| 10477 |
*/ |
| 10478 |
|
| 10479 |
'use strict'; |
| 10480 |
|
| 10481 |
var helpers = require(45); |
| 10482 |
|
| 10483 |
var EXPANDO_KEY = '$chartjs'; |
| 10484 |
var CSS_PREFIX = 'chartjs-'; |
| 10485 |
var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; |
| 10486 |
var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; |
| 10487 |
var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; |
| 10488 |
|
| 10489 |
/** |
| 10490 |
* DOM event types -> Chart.js event types. |
| 10491 |
* Note: only events with different types are mapped. |
| 10492 |
* @see https://developer.mozilla.org/en-US/docs/Web/Events |
| 10493 |
*/ |
| 10494 |
var EVENT_TYPES = { |
| 10495 |
touchstart: 'mousedown', |
| 10496 |
touchmove: 'mousemove', |
| 10497 |
touchend: 'mouseup', |
| 10498 |
pointerenter: 'mouseenter', |
| 10499 |
pointerdown: 'mousedown', |
| 10500 |
pointermove: 'mousemove', |
| 10501 |
pointerup: 'mouseup', |
| 10502 |
pointerleave: 'mouseout', |
| 10503 |
pointerout: 'mouseout' |
| 10504 |
}; |
| 10505 |
|
| 10506 |
/** |
| 10507 |
* The "used" size is the final value of a dimension property after all calculations have |
| 10508 |
* been performed. This method uses the computed style of `element` but returns undefined |
| 10509 |
* if the computed style is not expressed in pixels. That can happen in some cases where |
| 10510 |
* `element` has a size relative to its parent and this last one is not yet displayed, |
| 10511 |
* for example because of `display: none` on a parent node. |
| 10512 |
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value |
| 10513 |
* @returns {Number} Size in pixels or undefined if unknown. |
| 10514 |
*/ |
| 10515 |
function readUsedSize(element, property) { |
| 10516 |
var value = helpers.getStyle(element, property); |
| 10517 |
var matches = value && value.match(/^(\d+)(\.\d+)?px$/); |
| 10518 |
return matches ? Number(matches[1]) : undefined; |
| 10519 |
} |
| 10520 |
|
| 10521 |
/** |
| 10522 |
* Initializes the canvas style and render size without modifying the canvas display size, |
| 10523 |
* since responsiveness is handled by the controller.resize() method. The config is used |
| 10524 |
* to determine the aspect ratio to apply in case no explicit height has been specified. |
| 10525 |
*/ |
| 10526 |
function initCanvas(canvas, config) { |
| 10527 |
var style = canvas.style; |
| 10528 |
|
| 10529 |
// NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it |
| 10530 |
// returns null or '' if no explicit value has been set to the canvas attribute. |
| 10531 |
var renderHeight = canvas.getAttribute('height'); |
| 10532 |
var renderWidth = canvas.getAttribute('width'); |
| 10533 |
|
| 10534 |
// Chart.js modifies some canvas values that we want to restore on destroy |
| 10535 |
canvas[EXPANDO_KEY] = { |
| 10536 |
initial: { |
| 10537 |
height: renderHeight, |
| 10538 |
width: renderWidth, |
| 10539 |
style: { |
| 10540 |
display: style.display, |
| 10541 |
height: style.height, |
| 10542 |
width: style.width |
| 10543 |
} |
| 10544 |
} |
| 10545 |
}; |
| 10546 |
|
| 10547 |
// Force canvas to display as block to avoid extra space caused by inline |
| 10548 |
// elements, which would interfere with the responsive resize process. |
| 10549 |
// https://github.com/chartjs/Chart.js/issues/2538 |
| 10550 |
style.display = style.display || 'block'; |
| 10551 |
|
| 10552 |
if (renderWidth === null || renderWidth === '') { |
| 10553 |
var displayWidth = readUsedSize(canvas, 'width'); |
| 10554 |
if (displayWidth !== undefined) { |
| 10555 |
canvas.width = displayWidth; |
| 10556 |
} |
| 10557 |
} |
| 10558 |
|
| 10559 |
if (renderHeight === null || renderHeight === '') { |
| 10560 |
if (canvas.style.height === '') { |
| 10561 |
// If no explicit render height and style height, let's apply the aspect ratio, |
| 10562 |
// which one can be specified by the user but also by charts as default option |
| 10563 |
// (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. |
| 10564 |
canvas.height = canvas.width / (config.options.aspectRatio || 2); |
| 10565 |
} else { |
| 10566 |
var displayHeight = readUsedSize(canvas, 'height'); |
| 10567 |
if (displayWidth !== undefined) { |
| 10568 |
canvas.height = displayHeight; |
| 10569 |
} |
| 10570 |
} |
| 10571 |
} |
| 10572 |
|
| 10573 |
return canvas; |
| 10574 |
} |
| 10575 |
|
| 10576 |
/** |
| 10577 |
* Detects support for options object argument in addEventListener. |
| 10578 |
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support |
| 10579 |
* @private |
| 10580 |
*/ |
| 10581 |
var supportsEventListenerOptions = (function() { |
| 10582 |
var supports = false; |
| 10583 |
try { |
| 10584 |
var options = Object.defineProperty({}, 'passive', { |
| 10585 |
get: function() { |
| 10586 |
supports = true; |
| 10587 |
} |
| 10588 |
}); |
| 10589 |
window.addEventListener('e', null, options); |
| 10590 |
} catch (e) { |
| 10591 |
// continue regardless of error |
| 10592 |
} |
| 10593 |
return supports; |
| 10594 |
}()); |
| 10595 |
|
| 10596 |
// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. |
| 10597 |
// https://github.com/chartjs/Chart.js/issues/4287 |
| 10598 |
var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; |
| 10599 |
|
| 10600 |
function addEventListener(node, type, listener) { |
| 10601 |
node.addEventListener(type, listener, eventListenerOptions); |
| 10602 |
} |
| 10603 |
|
| 10604 |
function removeEventListener(node, type, listener) { |
| 10605 |
node.removeEventListener(type, listener, eventListenerOptions); |
| 10606 |
} |
| 10607 |
|
| 10608 |
function createEvent(type, chart, x, y, nativeEvent) { |
| 10609 |
return { |
| 10610 |
type: type, |
| 10611 |
chart: chart, |
| 10612 |
native: nativeEvent || null, |
| 10613 |
x: x !== undefined ? x : null, |
| 10614 |
y: y !== undefined ? y : null, |
| 10615 |
}; |
| 10616 |
} |
| 10617 |
|
| 10618 |
function fromNativeEvent(event, chart) { |
| 10619 |
var type = EVENT_TYPES[event.type] || event.type; |
| 10620 |
var pos = helpers.getRelativePosition(event, chart); |
| 10621 |
return createEvent(type, chart, pos.x, pos.y, event); |
| 10622 |
} |
| 10623 |
|
| 10624 |
function throttled(fn, thisArg) { |
| 10625 |
var ticking = false; |
| 10626 |
var args = []; |
| 10627 |
|
| 10628 |
return function() { |
| 10629 |
args = Array.prototype.slice.call(arguments); |
| 10630 |
thisArg = thisArg || this; |
| 10631 |
|
| 10632 |
if (!ticking) { |
| 10633 |
ticking = true; |
| 10634 |
helpers.requestAnimFrame.call(window, function() { |
| 10635 |
ticking = false; |
| 10636 |
fn.apply(thisArg, args); |
| 10637 |
}); |
| 10638 |
} |
| 10639 |
}; |
| 10640 |
} |
| 10641 |
|
| 10642 |
// Implementation based on https://github.com/marcj/css-element-queries |
| 10643 |
function createResizer(handler) { |
| 10644 |
var resizer = document.createElement('div'); |
| 10645 |
var cls = CSS_PREFIX + 'size-monitor'; |
| 10646 |
var maxSize = 1000000; |
| 10647 |
var style = |
| 10648 |
'position:absolute;' + |
| 10649 |
'left:0;' + |
| 10650 |
'top:0;' + |
| 10651 |
'right:0;' + |
| 10652 |
'bottom:0;' + |
| 10653 |
'overflow:hidden;' + |
| 10654 |
'pointer-events:none;' + |
| 10655 |
'visibility:hidden;' + |
| 10656 |
'z-index:-1;'; |
| 10657 |
|
| 10658 |
resizer.style.cssText = style; |
| 10659 |
resizer.className = cls; |
| 10660 |
resizer.innerHTML = |
| 10661 |
'<div class="' + cls + '-expand" style="' + style + '">' + |
| 10662 |
'<div style="' + |
| 10663 |
'position:absolute;' + |
| 10664 |
'width:' + maxSize + 'px;' + |
| 10665 |
'height:' + maxSize + 'px;' + |
| 10666 |
'left:0;' + |
| 10667 |
'top:0">' + |
| 10668 |
'</div>' + |
| 10669 |
'</div>' + |
| 10670 |
'<div class="' + cls + '-shrink" style="' + style + '">' + |
| 10671 |
'<div style="' + |
| 10672 |
'position:absolute;' + |
| 10673 |
'width:200%;' + |
| 10674 |
'height:200%;' + |
| 10675 |
'left:0; ' + |
| 10676 |
'top:0">' + |
| 10677 |
'</div>' + |
| 10678 |
'</div>'; |
| 10679 |
|
| 10680 |
var expand = resizer.childNodes[0]; |
| 10681 |
var shrink = resizer.childNodes[1]; |
| 10682 |
|
| 10683 |
resizer._reset = function() { |
| 10684 |
expand.scrollLeft = maxSize; |
| 10685 |
expand.scrollTop = maxSize; |
| 10686 |
shrink.scrollLeft = maxSize; |
| 10687 |
shrink.scrollTop = maxSize; |
| 10688 |
}; |
| 10689 |
var onScroll = function() { |
| 10690 |
resizer._reset(); |
| 10691 |
handler(); |
| 10692 |
}; |
| 10693 |
|
| 10694 |
addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand')); |
| 10695 |
addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); |
| 10696 |
|
| 10697 |
return resizer; |
| 10698 |
} |
| 10699 |
|
| 10700 |
// https://davidwalsh.name/detect-node-insertion |
| 10701 |
function watchForRender(node, handler) { |
| 10702 |
var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); |
| 10703 |
var proxy = expando.renderProxy = function(e) { |
| 10704 |
if (e.animationName === CSS_RENDER_ANIMATION) { |
| 10705 |
handler(); |
| 10706 |
} |
| 10707 |
}; |
| 10708 |
|
| 10709 |
helpers.each(ANIMATION_START_EVENTS, function(type) { |
| 10710 |
addEventListener(node, type, proxy); |
| 10711 |
}); |
| 10712 |
|
| 10713 |
// #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class |
| 10714 |
// is removed then added back immediately (same animation frame?). Accessing the |
| 10715 |
// `offsetParent` property will force a reflow and re-evaluate the CSS animation. |
| 10716 |
// https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics |
| 10717 |
// https://github.com/chartjs/Chart.js/issues/4737 |
| 10718 |
expando.reflow = !!node.offsetParent; |
| 10719 |
|
| 10720 |
node.classList.add(CSS_RENDER_MONITOR); |
| 10721 |
} |
| 10722 |
|
| 10723 |
function unwatchForRender(node) { |
| 10724 |
var expando = node[EXPANDO_KEY] || {}; |
| 10725 |
var proxy = expando.renderProxy; |
| 10726 |
|
| 10727 |
if (proxy) { |
| 10728 |
helpers.each(ANIMATION_START_EVENTS, function(type) { |
| 10729 |
removeEventListener(node, type, proxy); |
| 10730 |
}); |
| 10731 |
|
| 10732 |
delete expando.renderProxy; |
| 10733 |
} |
| 10734 |
|
| 10735 |
node.classList.remove(CSS_RENDER_MONITOR); |
| 10736 |
} |
| 10737 |
|
| 10738 |
function addResizeListener(node, listener, chart) { |
| 10739 |
var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); |
| 10740 |
|
| 10741 |
// Let's keep track of this added resizer and thus avoid DOM query when removing it. |
| 10742 |
var resizer = expando.resizer = createResizer(throttled(function() { |
| 10743 |
if (expando.resizer) { |
| 10744 |
return listener(createEvent('resize', chart)); |
| 10745 |
} |
| 10746 |
})); |
| 10747 |
|
| 10748 |
// The resizer needs to be attached to the node parent, so we first need to be |
| 10749 |
// sure that `node` is attached to the DOM before injecting the resizer element. |
| 10750 |
watchForRender(node, function() { |
| 10751 |
if (expando.resizer) { |
| 10752 |
var container = node.parentNode; |
| 10753 |
if (container && container !== resizer.parentNode) { |
| 10754 |
container.insertBefore(resizer, container.firstChild); |
| 10755 |
} |
| 10756 |
|
| 10757 |
// The container size might have changed, let's reset the resizer state. |
| 10758 |
resizer._reset(); |
| 10759 |
} |
| 10760 |
}); |
| 10761 |
} |
| 10762 |
|
| 10763 |
function removeResizeListener(node) { |
| 10764 |
var expando = node[EXPANDO_KEY] || {}; |
| 10765 |
var resizer = expando.resizer; |
| 10766 |
|
| 10767 |
delete expando.resizer; |
| 10768 |
unwatchForRender(node); |
| 10769 |
|
| 10770 |
if (resizer && resizer.parentNode) { |
| 10771 |
resizer.parentNode.removeChild(resizer); |
| 10772 |
} |
| 10773 |
} |
| 10774 |
|
| 10775 |
function injectCSS(platform, css) { |
| 10776 |
// http://stackoverflow.com/q/3922139 |
| 10777 |
var style = platform._style || document.createElement('style'); |
| 10778 |
if (!platform._style) { |
| 10779 |
platform._style = style; |
| 10780 |
css = '/* Chart.js */\n' + css; |
| 10781 |
style.setAttribute('type', 'text/css'); |
| 10782 |
document.getElementsByTagName('head')[0].appendChild(style); |
| 10783 |
} |
| 10784 |
|
| 10785 |
style.appendChild(document.createTextNode(css)); |
| 10786 |
} |
| 10787 |
|
| 10788 |
module.exports = { |
| 10789 |
/** |
| 10790 |
* This property holds whether this platform is enabled for the current environment. |
| 10791 |
* Currently used by platform.js to select the proper implementation. |
| 10792 |
* @private |
| 10793 |
*/ |
| 10794 |
_enabled: typeof window !== 'undefined' && typeof document !== 'undefined', |
| 10795 |
|
| 10796 |
initialize: function() { |
| 10797 |
var keyframes = 'from{opacity:0.99}to{opacity:1}'; |
| 10798 |
|
| 10799 |
injectCSS(this, |
| 10800 |
// DOM rendering detection |
| 10801 |
// https://davidwalsh.name/detect-node-insertion |
| 10802 |
'@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + |
| 10803 |
'@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + |
| 10804 |
'.' + CSS_RENDER_MONITOR + '{' + |
| 10805 |
'-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + |
| 10806 |
'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + |
| 10807 |
'}' |
| 10808 |
); |
| 10809 |
}, |
| 10810 |
|
| 10811 |
acquireContext: function(item, config) { |
| 10812 |
if (typeof item === 'string') { |
| 10813 |
item = document.getElementById(item); |
| 10814 |
} else if (item.length) { |
| 10815 |
// Support for array based queries (such as jQuery) |
| 10816 |
item = item[0]; |
| 10817 |
} |
| 10818 |
|
| 10819 |
if (item && item.canvas) { |
| 10820 |
// Support for any object associated to a canvas (including a context2d) |
| 10821 |
item = item.canvas; |
| 10822 |
} |
| 10823 |
|
| 10824 |
// To prevent canvas fingerprinting, some add-ons undefine the getContext |
| 10825 |
// method, for example: https://github.com/kkapsner/CanvasBlocker |
| 10826 |
// https://github.com/chartjs/Chart.js/issues/2807 |
| 10827 |
var context = item && item.getContext && item.getContext('2d'); |
| 10828 |
|
| 10829 |
// `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is |
| 10830 |
// inside an iframe or when running in a protected environment. We could guess the |
| 10831 |
// types from their toString() value but let's keep things flexible and assume it's |
| 10832 |
// a sufficient condition if the item has a context2D which has item as `canvas`. |
| 10833 |
// https://github.com/chartjs/Chart.js/issues/3887 |
| 10834 |
// https://github.com/chartjs/Chart.js/issues/4102 |
| 10835 |
// https://github.com/chartjs/Chart.js/issues/4152 |
| 10836 |
if (context && context.canvas === item) { |
| 10837 |
initCanvas(item, config); |
| 10838 |
return context; |
| 10839 |
} |
| 10840 |
|
| 10841 |
return null; |
| 10842 |
}, |
| 10843 |
|
| 10844 |
releaseContext: function(context) { |
| 10845 |
var canvas = context.canvas; |
| 10846 |
if (!canvas[EXPANDO_KEY]) { |
| 10847 |
return; |
| 10848 |
} |
| 10849 |
|
| 10850 |
var initial = canvas[EXPANDO_KEY].initial; |
| 10851 |
['height', 'width'].forEach(function(prop) { |
| 10852 |
var value = initial[prop]; |
| 10853 |
if (helpers.isNullOrUndef(value)) { |
| 10854 |
canvas.removeAttribute(prop); |
| 10855 |
} else { |
| 10856 |
canvas.setAttribute(prop, value); |
| 10857 |
} |
| 10858 |
}); |
| 10859 |
|
| 10860 |
helpers.each(initial.style || {}, function(value, key) { |
| 10861 |
canvas.style[key] = value; |
| 10862 |
}); |
| 10863 |
|
| 10864 |
// The canvas render size might have been changed (and thus the state stack discarded), |
| 10865 |
// we can't use save() and restore() to restore the initial state. So make sure that at |
| 10866 |
// least the canvas context is reset to the default state by setting the canvas width. |
| 10867 |
// https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html |
| 10868 |
canvas.width = canvas.width; |
| 10869 |
|
| 10870 |
delete canvas[EXPANDO_KEY]; |
| 10871 |
}, |
| 10872 |
|
| 10873 |
addEventListener: function(chart, type, listener) { |
| 10874 |
var canvas = chart.canvas; |
| 10875 |
if (type === 'resize') { |
| 10876 |
// Note: the resize event is not supported on all browsers. |
| 10877 |
addResizeListener(canvas, listener, chart); |
| 10878 |
return; |
| 10879 |
} |
| 10880 |
|
| 10881 |
var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); |
| 10882 |
var proxies = expando.proxies || (expando.proxies = {}); |
| 10883 |
var proxy = proxies[chart.id + '_' + type] = function(event) { |
| 10884 |
listener(fromNativeEvent(event, chart)); |
| 10885 |
}; |
| 10886 |
|
| 10887 |
addEventListener(canvas, type, proxy); |
| 10888 |
}, |
| 10889 |
|
| 10890 |
removeEventListener: function(chart, type, listener) { |
| 10891 |
var canvas = chart.canvas; |
| 10892 |
if (type === 'resize') { |
| 10893 |
// Note: the resize event is not supported on all browsers. |
| 10894 |
removeResizeListener(canvas, listener); |
| 10895 |
return; |
| 10896 |
} |
| 10897 |
|
| 10898 |
var expando = listener[EXPANDO_KEY] || {}; |
| 10899 |
var proxies = expando.proxies || {}; |
| 10900 |
var proxy = proxies[chart.id + '_' + type]; |
| 10901 |
if (!proxy) { |
| 10902 |
return; |
| 10903 |
} |
| 10904 |
|
| 10905 |
removeEventListener(canvas, type, proxy); |
| 10906 |
} |
| 10907 |
}; |
| 10908 |
|
| 10909 |
// DEPRECATIONS |
| 10910 |
|
| 10911 |
/** |
| 10912 |
* Provided for backward compatibility, use EventTarget.addEventListener instead. |
| 10913 |
* EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ |
| 10914 |
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener |
| 10915 |
* @function Chart.helpers.addEvent |
| 10916 |
* @deprecated since version 2.7.0 |
| 10917 |
* @todo remove at version 3 |
| 10918 |
* @private |
| 10919 |
*/ |
| 10920 |
helpers.addEvent = addEventListener; |
| 10921 |
|
| 10922 |
/** |
| 10923 |
* Provided for backward compatibility, use EventTarget.removeEventListener instead. |
| 10924 |
* EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ |
| 10925 |
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener |
| 10926 |
* @function Chart.helpers.removeEvent |
| 10927 |
* @deprecated since version 2.7.0 |
| 10928 |
* @todo remove at version 3 |
| 10929 |
* @private |
| 10930 |
*/ |
| 10931 |
helpers.removeEvent = removeEventListener; |
| 10932 |
|
| 10933 |
},{"45":45}],48:[function(require,module,exports){ |
| 10934 |
'use strict'; |
| 10935 |
|
| 10936 |
var helpers = require(45); |
| 10937 |
var basic = require(46); |
| 10938 |
var dom = require(47); |
| 10939 |
|
| 10940 |
// @TODO Make possible to select another platform at build time. |
| 10941 |
var implementation = dom._enabled ? dom : basic; |
| 10942 |
|
| 10943 |
/** |
| 10944 |
* @namespace Chart.platform |
| 10945 |
* @see https://chartjs.gitbooks.io/proposals/content/Platform.html |
| 10946 |
* @since 2.4.0 |
| 10947 |
*/ |
| 10948 |
module.exports = helpers.extend({ |
| 10949 |
/** |
| 10950 |
* @since 2.7.0 |
| 10951 |
*/ |
| 10952 |
initialize: function() {}, |
| 10953 |
|
| 10954 |
/** |
| 10955 |
* Called at chart construction time, returns a context2d instance implementing |
| 10956 |
* the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. |
| 10957 |
* @param {*} item - The native item from which to acquire context (platform specific) |
| 10958 |
* @param {Object} options - The chart options |
| 10959 |
* @returns {CanvasRenderingContext2D} context2d instance |
| 10960 |
*/ |
| 10961 |
acquireContext: function() {}, |
| 10962 |
|
| 10963 |
/** |
| 10964 |
* Called at chart destruction time, releases any resources associated to the context |
| 10965 |
* previously returned by the acquireContext() method. |
| 10966 |
* @param {CanvasRenderingContext2D} context - The context2d instance |
| 10967 |
* @returns {Boolean} true if the method succeeded, else false |
| 10968 |
*/ |
| 10969 |
releaseContext: function() {}, |
| 10970 |
|
| 10971 |
/** |
| 10972 |
* Registers the specified listener on the given chart. |
| 10973 |
* @param {Chart} chart - Chart from which to listen for event |
| 10974 |
* @param {String} type - The ({@link IEvent}) type to listen for |
| 10975 |
* @param {Function} listener - Receives a notification (an object that implements |
| 10976 |
* the {@link IEvent} interface) when an event of the specified type occurs. |
| 10977 |
*/ |
| 10978 |
addEventListener: function() {}, |
| 10979 |
|
| 10980 |
/** |
| 10981 |
* Removes the specified listener previously registered with addEventListener. |
| 10982 |
* @param {Chart} chart -Chart from which to remove the listener |
| 10983 |
* @param {String} type - The ({@link IEvent}) type to remove |
| 10984 |
* @param {Function} listener - The listener function to remove from the event target. |
| 10985 |
*/ |
| 10986 |
removeEventListener: function() {} |
| 10987 |
|
| 10988 |
}, implementation); |
| 10989 |
|
| 10990 |
/** |
| 10991 |
* @interface IPlatform |
| 10992 |
* Allows abstracting platform dependencies away from the chart |
| 10993 |
* @borrows Chart.platform.acquireContext as acquireContext |
| 10994 |
* @borrows Chart.platform.releaseContext as releaseContext |
| 10995 |
* @borrows Chart.platform.addEventListener as addEventListener |
| 10996 |
* @borrows Chart.platform.removeEventListener as removeEventListener |
| 10997 |
*/ |
| 10998 |
|
| 10999 |
/** |
| 11000 |
* @interface IEvent |
| 11001 |
* @prop {String} type - The event type name, possible values are: |
| 11002 |
* 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout', |
| 11003 |
* 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize' |
| 11004 |
* @prop {*} native - The original native event (null for emulated events, e.g. 'resize') |
| 11005 |
* @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events) |
| 11006 |
* @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) |
| 11007 |
*/ |
| 11008 |
|
| 11009 |
},{"45":45,"46":46,"47":47}],49:[function(require,module,exports){ |
| 11010 |
/** |
| 11011 |
* Plugin based on discussion from the following Chart.js issues: |
| 11012 |
* @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569 |
| 11013 |
* @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897 |
| 11014 |
*/ |
| 11015 |
|
| 11016 |
'use strict'; |
| 11017 |
|
| 11018 |
var defaults = require(25); |
| 11019 |
var elements = require(40); |
| 11020 |
var helpers = require(45); |
| 11021 |
|
| 11022 |
defaults._set('global', { |
| 11023 |
plugins: { |
| 11024 |
filler: { |
| 11025 |
propagate: true |
| 11026 |
} |
| 11027 |
} |
| 11028 |
}); |
| 11029 |
|
| 11030 |
module.exports = function() { |
| 11031 |
|
| 11032 |
var mappers = { |
| 11033 |
dataset: function(source) { |
| 11034 |
var index = source.fill; |
| 11035 |
var chart = source.chart; |
| 11036 |
var meta = chart.getDatasetMeta(index); |
| 11037 |
var visible = meta && chart.isDatasetVisible(index); |
| 11038 |
var points = (visible && meta.dataset._children) || []; |
| 11039 |
var length = points.length || 0; |
| 11040 |
|
| 11041 |
return !length ? null : function(point, i) { |
| 11042 |
return (i < length && points[i]._view) || null; |
| 11043 |
}; |
| 11044 |
}, |
| 11045 |
|
| 11046 |
boundary: function(source) { |
| 11047 |
var boundary = source.boundary; |
| 11048 |
var x = boundary ? boundary.x : null; |
| 11049 |
var y = boundary ? boundary.y : null; |
| 11050 |
|
| 11051 |
return function(point) { |
| 11052 |
return { |
| 11053 |
x: x === null ? point.x : x, |
| 11054 |
y: y === null ? point.y : y, |
| 11055 |
}; |
| 11056 |
}; |
| 11057 |
} |
| 11058 |
}; |
| 11059 |
|
| 11060 |
// @todo if (fill[0] === '#') |
| 11061 |
function decodeFill(el, index, count) { |
| 11062 |
var model = el._model || {}; |
| 11063 |
var fill = model.fill; |
| 11064 |
var target; |
| 11065 |
|
| 11066 |
if (fill === undefined) { |
| 11067 |
fill = !!model.backgroundColor; |
| 11068 |
} |
| 11069 |
|
| 11070 |
if (fill === false || fill === null) { |
| 11071 |
return false; |
| 11072 |
} |
| 11073 |
|
| 11074 |
if (fill === true) { |
| 11075 |
return 'origin'; |
| 11076 |
} |
| 11077 |
|
| 11078 |
target = parseFloat(fill, 10); |
| 11079 |
if (isFinite(target) && Math.floor(target) === target) { |
| 11080 |
if (fill[0] === '-' || fill[0] === '+') { |
| 11081 |
target = index + target; |
| 11082 |
} |
| 11083 |
|
| 11084 |
if (target === index || target < 0 || target >= count) { |
| 11085 |
return false; |
| 11086 |
} |
| 11087 |
|
| 11088 |
return target; |
| 11089 |
} |
| 11090 |
|
| 11091 |
switch (fill) { |
| 11092 |
// compatibility |
| 11093 |
case 'bottom': |
| 11094 |
return 'start'; |
| 11095 |
case 'top': |
| 11096 |
return 'end'; |
| 11097 |
case 'zero': |
| 11098 |
return 'origin'; |
| 11099 |
// supported boundaries |
| 11100 |
case 'origin': |
| 11101 |
case 'start': |
| 11102 |
case 'end': |
| 11103 |
return fill; |
| 11104 |
// invalid fill values |
| 11105 |
default: |
| 11106 |
return false; |
| 11107 |
} |
| 11108 |
} |
| 11109 |
|
| 11110 |
function computeBoundary(source) { |
| 11111 |
var model = source.el._model || {}; |
| 11112 |
var scale = source.el._scale || {}; |
| 11113 |
var fill = source.fill; |
| 11114 |
var target = null; |
| 11115 |
var horizontal; |
| 11116 |
|
| 11117 |
if (isFinite(fill)) { |
| 11118 |
return null; |
| 11119 |
} |
| 11120 |
|
| 11121 |
// Backward compatibility: until v3, we still need to support boundary values set on |
| 11122 |
// the model (scaleTop, scaleBottom and scaleZero) because some external plugins and |
| 11123 |
// controllers might still use it (e.g. the Smith chart). |
| 11124 |
|
| 11125 |
if (fill === 'start') { |
| 11126 |
target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; |
| 11127 |
} else if (fill === 'end') { |
| 11128 |
target = model.scaleTop === undefined ? scale.top : model.scaleTop; |
| 11129 |
} else if (model.scaleZero !== undefined) { |
| 11130 |
target = model.scaleZero; |
| 11131 |
} else if (scale.getBasePosition) { |
| 11132 |
target = scale.getBasePosition(); |
| 11133 |
} else if (scale.getBasePixel) { |
| 11134 |
target = scale.getBasePixel(); |
| 11135 |
} |
| 11136 |
|
| 11137 |
if (target !== undefined && target !== null) { |
| 11138 |
if (target.x !== undefined && target.y !== undefined) { |
| 11139 |
return target; |
| 11140 |
} |
| 11141 |
|
| 11142 |
if (typeof target === 'number' && isFinite(target)) { |
| 11143 |
horizontal = scale.isHorizontal(); |
| 11144 |
return { |
| 11145 |
x: horizontal ? target : null, |
| 11146 |
y: horizontal ? null : target |
| 11147 |
}; |
| 11148 |
} |
| 11149 |
} |
| 11150 |
|
| 11151 |
return null; |
| 11152 |
} |
| 11153 |
|
| 11154 |
function resolveTarget(sources, index, propagate) { |
| 11155 |
var source = sources[index]; |
| 11156 |
var fill = source.fill; |
| 11157 |
var visited = [index]; |
| 11158 |
var target; |
| 11159 |
|
| 11160 |
if (!propagate) { |
| 11161 |
return fill; |
| 11162 |
} |
| 11163 |
|
| 11164 |
while (fill !== false && visited.indexOf(fill) === -1) { |
| 11165 |
if (!isFinite(fill)) { |
| 11166 |
return fill; |
| 11167 |
} |
| 11168 |
|
| 11169 |
target = sources[fill]; |
| 11170 |
if (!target) { |
| 11171 |
return false; |
| 11172 |
} |
| 11173 |
|
| 11174 |
if (target.visible) { |
| 11175 |
return fill; |
| 11176 |
} |
| 11177 |
|
| 11178 |
visited.push(fill); |
| 11179 |
fill = target.fill; |
| 11180 |
} |
| 11181 |
|
| 11182 |
return false; |
| 11183 |
} |
| 11184 |
|
| 11185 |
function createMapper(source) { |
| 11186 |
var fill = source.fill; |
| 11187 |
var type = 'dataset'; |
| 11188 |
|
| 11189 |
if (fill === false) { |
| 11190 |
return null; |
| 11191 |
} |
| 11192 |
|
| 11193 |
if (!isFinite(fill)) { |
| 11194 |
type = 'boundary'; |
| 11195 |
} |
| 11196 |
|
| 11197 |
return mappers[type](source); |
| 11198 |
} |
| 11199 |
|
| 11200 |
function isDrawable(point) { |
| 11201 |
return point && !point.skip; |
| 11202 |
} |
| 11203 |
|
| 11204 |
function drawArea(ctx, curve0, curve1, len0, len1) { |
| 11205 |
var i; |
| 11206 |
|
| 11207 |
if (!len0 || !len1) { |
| 11208 |
return; |
| 11209 |
} |
| 11210 |
|
| 11211 |
// building first area curve (normal) |
| 11212 |
ctx.moveTo(curve0[0].x, curve0[0].y); |
| 11213 |
for (i = 1; i < len0; ++i) { |
| 11214 |
helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); |
| 11215 |
} |
| 11216 |
|
| 11217 |
// joining the two area curves |
| 11218 |
ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); |
| 11219 |
|
| 11220 |
// building opposite area curve (reverse) |
| 11221 |
for (i = len1 - 1; i > 0; --i) { |
| 11222 |
helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); |
| 11223 |
} |
| 11224 |
} |
| 11225 |
|
| 11226 |
function doFill(ctx, points, mapper, view, color, loop) { |
| 11227 |
var count = points.length; |
| 11228 |
var span = view.spanGaps; |
| 11229 |
var curve0 = []; |
| 11230 |
var curve1 = []; |
| 11231 |
var len0 = 0; |
| 11232 |
var len1 = 0; |
| 11233 |
var i, ilen, index, p0, p1, d0, d1; |
| 11234 |
|
| 11235 |
ctx.beginPath(); |
| 11236 |
|
| 11237 |
for (i = 0, ilen = (count + !!loop); i < ilen; ++i) { |
| 11238 |
index = i % count; |
| 11239 |
p0 = points[index]._view; |
| 11240 |
p1 = mapper(p0, index, view); |
| 11241 |
d0 = isDrawable(p0); |
| 11242 |
d1 = isDrawable(p1); |
| 11243 |
|
| 11244 |
if (d0 && d1) { |
| 11245 |
len0 = curve0.push(p0); |
| 11246 |
len1 = curve1.push(p1); |
| 11247 |
} else if (len0 && len1) { |
| 11248 |
if (!span) { |
| 11249 |
drawArea(ctx, curve0, curve1, len0, len1); |
| 11250 |
len0 = len1 = 0; |
| 11251 |
curve0 = []; |
| 11252 |
curve1 = []; |
| 11253 |
} else { |
| 11254 |
if (d0) { |
| 11255 |
curve0.push(p0); |
| 11256 |
} |
| 11257 |
if (d1) { |
| 11258 |
curve1.push(p1); |
| 11259 |
} |
| 11260 |
} |
| 11261 |
} |
| 11262 |
} |
| 11263 |
|
| 11264 |
drawArea(ctx, curve0, curve1, len0, len1); |
| 11265 |
|
| 11266 |
ctx.closePath(); |
| 11267 |
ctx.fillStyle = color; |
| 11268 |
ctx.fill(); |
| 11269 |
} |
| 11270 |
|
| 11271 |
return { |
| 11272 |
id: 'filler', |
| 11273 |
|
| 11274 |
afterDatasetsUpdate: function(chart, options) { |
| 11275 |
var count = (chart.data.datasets || []).length; |
| 11276 |
var propagate = options.propagate; |
| 11277 |
var sources = []; |
| 11278 |
var meta, i, el, source; |
| 11279 |
|
| 11280 |
for (i = 0; i < count; ++i) { |
| 11281 |
meta = chart.getDatasetMeta(i); |
| 11282 |
el = meta.dataset; |
| 11283 |
source = null; |
| 11284 |
|
| 11285 |
if (el && el._model && el instanceof elements.Line) { |
| 11286 |
source = { |
| 11287 |
visible: chart.isDatasetVisible(i), |
| 11288 |
fill: decodeFill(el, i, count), |
| 11289 |
chart: chart, |
| 11290 |
el: el |
| 11291 |
}; |
| 11292 |
} |
| 11293 |
|
| 11294 |
meta.$filler = source; |
| 11295 |
sources.push(source); |
| 11296 |
} |
| 11297 |
|
| 11298 |
for (i = 0; i < count; ++i) { |
| 11299 |
source = sources[i]; |
| 11300 |
if (!source) { |
| 11301 |
continue; |
| 11302 |
} |
| 11303 |
|
| 11304 |
source.fill = resolveTarget(sources, i, propagate); |
| 11305 |
source.boundary = computeBoundary(source); |
| 11306 |
source.mapper = createMapper(source); |
| 11307 |
} |
| 11308 |
}, |
| 11309 |
|
| 11310 |
beforeDatasetDraw: function(chart, args) { |
| 11311 |
var meta = args.meta.$filler; |
| 11312 |
if (!meta) { |
| 11313 |
return; |
| 11314 |
} |
| 11315 |
|
| 11316 |
var ctx = chart.ctx; |
| 11317 |
var el = meta.el; |
| 11318 |
var view = el._view; |
| 11319 |
var points = el._children || []; |
| 11320 |
var mapper = meta.mapper; |
| 11321 |
var color = view.backgroundColor || defaults.global.defaultColor; |
| 11322 |
|
| 11323 |
if (mapper && color && points.length) { |
| 11324 |
helpers.canvas.clipArea(ctx, chart.chartArea); |
| 11325 |
doFill(ctx, points, mapper, view, color, el._loop); |
| 11326 |
helpers.canvas.unclipArea(ctx); |
| 11327 |
} |
| 11328 |
} |
| 11329 |
}; |
| 11330 |
}; |
| 11331 |
|
| 11332 |
},{"25":25,"40":40,"45":45}],50:[function(require,module,exports){ |
| 11333 |
'use strict'; |
| 11334 |
|
| 11335 |
var defaults = require(25); |
| 11336 |
var Element = require(26); |
| 11337 |
var helpers = require(45); |
| 11338 |
|
| 11339 |
defaults._set('global', { |
| 11340 |
legend: { |
| 11341 |
display: true, |
| 11342 |
position: 'top', |
| 11343 |
fullWidth: true, |
| 11344 |
reverse: false, |
| 11345 |
weight: 1000, |
| 11346 |
|
| 11347 |
// a callback that will handle |
| 11348 |
onClick: function(e, legendItem) { |
| 11349 |
var index = legendItem.datasetIndex; |
| 11350 |
var ci = this.chart; |
| 11351 |
var meta = ci.getDatasetMeta(index); |
| 11352 |
|
| 11353 |
// See controller.isDatasetVisible comment |
| 11354 |
meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; |
| 11355 |
|
| 11356 |
// We hid a dataset ... rerender the chart |
| 11357 |
ci.update(); |
| 11358 |
}, |
| 11359 |
|
| 11360 |
onHover: null, |
| 11361 |
|
| 11362 |
labels: { |
| 11363 |
boxWidth: 40, |
| 11364 |
padding: 10, |
| 11365 |
// Generates labels shown in the legend |
| 11366 |
// Valid properties to return: |
| 11367 |
// text : text to display |
| 11368 |
// fillStyle : fill of coloured box |
| 11369 |
// strokeStyle: stroke of coloured box |
| 11370 |
// hidden : if this legend item refers to a hidden item |
| 11371 |
// lineCap : cap style for line |
| 11372 |
// lineDash |
| 11373 |
// lineDashOffset : |
| 11374 |
// lineJoin : |
| 11375 |
// lineWidth : |
| 11376 |
generateLabels: function(chart) { |
| 11377 |
var data = chart.data; |
| 11378 |
return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { |
| 11379 |
return { |
| 11380 |
text: dataset.label, |
| 11381 |
fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), |
| 11382 |
hidden: !chart.isDatasetVisible(i), |
| 11383 |
lineCap: dataset.borderCapStyle, |
| 11384 |
lineDash: dataset.borderDash, |
| 11385 |
lineDashOffset: dataset.borderDashOffset, |
| 11386 |
lineJoin: dataset.borderJoinStyle, |
| 11387 |
lineWidth: dataset.borderWidth, |
| 11388 |
strokeStyle: dataset.borderColor, |
| 11389 |
pointStyle: dataset.pointStyle, |
| 11390 |
|
| 11391 |
// Below is extra data used for toggling the datasets |
| 11392 |
datasetIndex: i |
| 11393 |
}; |
| 11394 |
}, this) : []; |
| 11395 |
} |
| 11396 |
} |
| 11397 |
}, |
| 11398 |
|
| 11399 |
legendCallback: function(chart) { |
| 11400 |
var text = []; |
| 11401 |
text.push('<ul class="' + chart.id + '-legend">'); |
| 11402 |
for (var i = 0; i < chart.data.datasets.length; i++) { |
| 11403 |
text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>'); |
| 11404 |
if (chart.data.datasets[i].label) { |
| 11405 |
text.push(chart.data.datasets[i].label); |
| 11406 |
} |
| 11407 |
text.push('</li>'); |
| 11408 |
} |
| 11409 |
text.push('</ul>'); |
| 11410 |
return text.join(''); |
| 11411 |
} |
| 11412 |
}); |
| 11413 |
|
| 11414 |
module.exports = function(Chart) { |
| 11415 |
|
| 11416 |
var layout = Chart.layoutService; |
| 11417 |
var noop = helpers.noop; |
| 11418 |
|
| 11419 |
/** |
| 11420 |
* Helper function to get the box width based on the usePointStyle option |
| 11421 |
* @param labelopts {Object} the label options on the legend |
| 11422 |
* @param fontSize {Number} the label font size |
| 11423 |
* @return {Number} width of the color box area |
| 11424 |
*/ |
| 11425 |
function getBoxWidth(labelOpts, fontSize) { |
| 11426 |
return labelOpts.usePointStyle ? |
| 11427 |
fontSize * Math.SQRT2 : |
| 11428 |
labelOpts.boxWidth; |
| 11429 |
} |
| 11430 |
|
| 11431 |
Chart.Legend = Element.extend({ |
| 11432 |
|
| 11433 |
initialize: function(config) { |
| 11434 |
helpers.extend(this, config); |
| 11435 |
|
| 11436 |
// Contains hit boxes for each dataset (in dataset order) |
| 11437 |
this.legendHitBoxes = []; |
| 11438 |
|
| 11439 |
// Are we in doughnut mode which has a different data type |
| 11440 |
this.doughnutMode = false; |
| 11441 |
}, |
| 11442 |
|
| 11443 |
// These methods are ordered by lifecycle. Utilities then follow. |
| 11444 |
// Any function defined here is inherited by all legend types. |
| 11445 |
// Any function can be extended by the legend type |
| 11446 |
|
| 11447 |
beforeUpdate: noop, |
| 11448 |
update: function(maxWidth, maxHeight, margins) { |
| 11449 |
var me = this; |
| 11450 |
|
| 11451 |
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) |
| 11452 |
me.beforeUpdate(); |
| 11453 |
|
| 11454 |
// Absorb the master measurements |
| 11455 |
me.maxWidth = maxWidth; |
| 11456 |
me.maxHeight = maxHeight; |
| 11457 |
me.margins = margins; |
| 11458 |
|
| 11459 |
// Dimensions |
| 11460 |
me.beforeSetDimensions(); |
| 11461 |
me.setDimensions(); |
| 11462 |
me.afterSetDimensions(); |
| 11463 |
// Labels |
| 11464 |
me.beforeBuildLabels(); |
| 11465 |
me.buildLabels(); |
| 11466 |
me.afterBuildLabels(); |
| 11467 |
|
| 11468 |
// Fit |
| 11469 |
me.beforeFit(); |
| 11470 |
me.fit(); |
| 11471 |
me.afterFit(); |
| 11472 |
// |
| 11473 |
me.afterUpdate(); |
| 11474 |
|
| 11475 |
return me.minSize; |
| 11476 |
}, |
| 11477 |
afterUpdate: noop, |
| 11478 |
|
| 11479 |
// |
| 11480 |
|
| 11481 |
beforeSetDimensions: noop, |
| 11482 |
setDimensions: function() { |
| 11483 |
var me = this; |
| 11484 |
// Set the unconstrained dimension before label rotation |
| 11485 |
if (me.isHorizontal()) { |
| 11486 |
// Reset position before calculating rotation |
| 11487 |
me.width = me.maxWidth; |
| 11488 |
me.left = 0; |
| 11489 |
me.right = me.width; |
| 11490 |
} else { |
| 11491 |
me.height = me.maxHeight; |
| 11492 |
|
| 11493 |
// Reset position before calculating rotation |
| 11494 |
me.top = 0; |
| 11495 |
me.bottom = me.height; |
| 11496 |
} |
| 11497 |
|
| 11498 |
// Reset padding |
| 11499 |
me.paddingLeft = 0; |
| 11500 |
me.paddingTop = 0; |
| 11501 |
me.paddingRight = 0; |
| 11502 |
me.paddingBottom = 0; |
| 11503 |
|
| 11504 |
// Reset minSize |
| 11505 |
me.minSize = { |
| 11506 |
width: 0, |
| 11507 |
height: 0 |
| 11508 |
}; |
| 11509 |
}, |
| 11510 |
afterSetDimensions: noop, |
| 11511 |
|
| 11512 |
// |
| 11513 |
|
| 11514 |
beforeBuildLabels: noop, |
| 11515 |
buildLabels: function() { |
| 11516 |
var me = this; |
| 11517 |
var labelOpts = me.options.labels || {}; |
| 11518 |
var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || []; |
| 11519 |
|
| 11520 |
if (labelOpts.filter) { |
| 11521 |
legendItems = legendItems.filter(function(item) { |
| 11522 |
return labelOpts.filter(item, me.chart.data); |
| 11523 |
}); |
| 11524 |
} |
| 11525 |
|
| 11526 |
if (me.options.reverse) { |
| 11527 |
legendItems.reverse(); |
| 11528 |
} |
| 11529 |
|
| 11530 |
me.legendItems = legendItems; |
| 11531 |
}, |
| 11532 |
afterBuildLabels: noop, |
| 11533 |
|
| 11534 |
// |
| 11535 |
|
| 11536 |
beforeFit: noop, |
| 11537 |
fit: function() { |
| 11538 |
var me = this; |
| 11539 |
var opts = me.options; |
| 11540 |
var labelOpts = opts.labels; |
| 11541 |
var display = opts.display; |
| 11542 |
|
| 11543 |
var ctx = me.ctx; |
| 11544 |
|
| 11545 |
var globalDefault = defaults.global; |
| 11546 |
var valueOrDefault = helpers.valueOrDefault; |
| 11547 |
var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); |
| 11548 |
var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); |
| 11549 |
var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); |
| 11550 |
var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); |
| 11551 |
|
| 11552 |
// Reset hit boxes |
| 11553 |
var hitboxes = me.legendHitBoxes = []; |
| 11554 |
|
| 11555 |
var minSize = me.minSize; |
| 11556 |
var isHorizontal = me.isHorizontal(); |
| 11557 |
|
| 11558 |
if (isHorizontal) { |
| 11559 |
minSize.width = me.maxWidth; // fill all the width |
| 11560 |
minSize.height = display ? 10 : 0; |
| 11561 |
} else { |
| 11562 |
minSize.width = display ? 10 : 0; |
| 11563 |
minSize.height = me.maxHeight; // fill all the height |
| 11564 |
} |
| 11565 |
|
| 11566 |
// Increase sizes here |
| 11567 |
if (display) { |
| 11568 |
ctx.font = labelFont; |
| 11569 |
|
| 11570 |
if (isHorizontal) { |
| 11571 |
// Labels |
| 11572 |
|
| 11573 |
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one |
| 11574 |
var lineWidths = me.lineWidths = [0]; |
| 11575 |
var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; |
| 11576 |
|
| 11577 |
ctx.textAlign = 'left'; |
| 11578 |
ctx.textBaseline = 'top'; |
| 11579 |
|
| 11580 |
helpers.each(me.legendItems, function(legendItem, i) { |
| 11581 |
var boxWidth = getBoxWidth(labelOpts, fontSize); |
| 11582 |
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; |
| 11583 |
|
| 11584 |
if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { |
| 11585 |
totalHeight += fontSize + (labelOpts.padding); |
| 11586 |
lineWidths[lineWidths.length] = me.left; |
| 11587 |
} |
| 11588 |
|
| 11589 |
// Store the hitbox width and height here. Final position will be updated in `draw` |
| 11590 |
hitboxes[i] = { |
| 11591 |
left: 0, |
| 11592 |
top: 0, |
| 11593 |
width: width, |
| 11594 |
height: fontSize |
| 11595 |
}; |
| 11596 |
|
| 11597 |
lineWidths[lineWidths.length - 1] += width + labelOpts.padding; |
| 11598 |
}); |
| 11599 |
|
| 11600 |
minSize.height += totalHeight; |
| 11601 |
|
| 11602 |
} else { |
| 11603 |
var vPadding = labelOpts.padding; |
| 11604 |
var columnWidths = me.columnWidths = []; |
| 11605 |
var totalWidth = labelOpts.padding; |
| 11606 |
var currentColWidth = 0; |
| 11607 |
var currentColHeight = 0; |
| 11608 |
var itemHeight = fontSize + vPadding; |
| 11609 |
|
| 11610 |
helpers.each(me.legendItems, function(legendItem, i) { |
| 11611 |
var boxWidth = getBoxWidth(labelOpts, fontSize); |
| 11612 |
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; |
| 11613 |
|
| 11614 |
// If too tall, go to new column |
| 11615 |
if (currentColHeight + itemHeight > minSize.height) { |
| 11616 |
totalWidth += currentColWidth + labelOpts.padding; |
| 11617 |
columnWidths.push(currentColWidth); // previous column width |
| 11618 |
|
| 11619 |
currentColWidth = 0; |
| 11620 |
currentColHeight = 0; |
| 11621 |
} |
| 11622 |
|
| 11623 |
// Get max width |
| 11624 |
currentColWidth = Math.max(currentColWidth, itemWidth); |
| 11625 |
currentColHeight += itemHeight; |
| 11626 |
|
| 11627 |
// Store the hitbox width and height here. Final position will be updated in `draw` |
| 11628 |
hitboxes[i] = { |
| 11629 |
left: 0, |
| 11630 |
top: 0, |
| 11631 |
width: itemWidth, |
| 11632 |
height: fontSize |
| 11633 |
}; |
| 11634 |
}); |
| 11635 |
|
| 11636 |
totalWidth += currentColWidth; |
| 11637 |
columnWidths.push(currentColWidth); |
| 11638 |
minSize.width += totalWidth; |
| 11639 |
} |
| 11640 |
} |
| 11641 |
|
| 11642 |
me.width = minSize.width; |
| 11643 |
me.height = minSize.height; |
| 11644 |
}, |
| 11645 |
afterFit: noop, |
| 11646 |
|
| 11647 |
// Shared Methods |
| 11648 |
isHorizontal: function() { |
| 11649 |
return this.options.position === 'top' || this.options.position === 'bottom'; |
| 11650 |
}, |
| 11651 |
|
| 11652 |
// Actually draw the legend on the canvas |
| 11653 |
draw: function() { |
| 11654 |
var me = this; |
| 11655 |
var opts = me.options; |
| 11656 |
var labelOpts = opts.labels; |
| 11657 |
var globalDefault = defaults.global; |
| 11658 |
var lineDefault = globalDefault.elements.line; |
| 11659 |
var legendWidth = me.width; |
| 11660 |
var lineWidths = me.lineWidths; |
| 11661 |
|
| 11662 |
if (opts.display) { |
| 11663 |
var ctx = me.ctx; |
| 11664 |
var valueOrDefault = helpers.valueOrDefault; |
| 11665 |
var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor); |
| 11666 |
var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); |
| 11667 |
var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); |
| 11668 |
var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); |
| 11669 |
var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); |
| 11670 |
var cursor; |
| 11671 |
|
| 11672 |
// Canvas setup |
| 11673 |
ctx.textAlign = 'left'; |
| 11674 |
ctx.textBaseline = 'middle'; |
| 11675 |
ctx.lineWidth = 0.5; |
| 11676 |
ctx.strokeStyle = fontColor; // for strikethrough effect |
| 11677 |
ctx.fillStyle = fontColor; // render in correct colour |
| 11678 |
ctx.font = labelFont; |
| 11679 |
|
| 11680 |
var boxWidth = getBoxWidth(labelOpts, fontSize); |
| 11681 |
var hitboxes = me.legendHitBoxes; |
| 11682 |
|
| 11683 |
// current position |
| 11684 |
var drawLegendBox = function(x, y, legendItem) { |
| 11685 |
if (isNaN(boxWidth) || boxWidth <= 0) { |
| 11686 |
return; |
| 11687 |
} |
| 11688 |
|
| 11689 |
// Set the ctx for the box |
| 11690 |
ctx.save(); |
| 11691 |
|
| 11692 |
ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor); |
| 11693 |
ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); |
| 11694 |
ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); |
| 11695 |
ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); |
| 11696 |
ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth); |
| 11697 |
ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); |
| 11698 |
var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); |
| 11699 |
|
| 11700 |
if (ctx.setLineDash) { |
| 11701 |
// IE 9 and 10 do not support line dash |
| 11702 |
ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash)); |
| 11703 |
} |
| 11704 |
|
| 11705 |
if (opts.labels && opts.labels.usePointStyle) { |
| 11706 |
// Recalculate x and y for drawPoint() because its expecting |
| 11707 |
// x and y to be center of figure (instead of top left) |
| 11708 |
var radius = fontSize * Math.SQRT2 / 2; |
| 11709 |
var offSet = radius / Math.SQRT2; |
| 11710 |
var centerX = x + offSet; |
| 11711 |
var centerY = y + offSet; |
| 11712 |
|
| 11713 |
// Draw pointStyle as legend symbol |
| 11714 |
helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); |
| 11715 |
} else { |
| 11716 |
// Draw box as legend symbol |
| 11717 |
if (!isLineWidthZero) { |
| 11718 |
ctx.strokeRect(x, y, boxWidth, fontSize); |
| 11719 |
} |
| 11720 |
ctx.fillRect(x, y, boxWidth, fontSize); |
| 11721 |
} |
| 11722 |
|
| 11723 |
ctx.restore(); |
| 11724 |
}; |
| 11725 |
var fillText = function(x, y, legendItem, textWidth) { |
| 11726 |
var halfFontSize = fontSize / 2; |
| 11727 |
var xLeft = boxWidth + halfFontSize + x; |
| 11728 |
var yMiddle = y + halfFontSize; |
| 11729 |
|
| 11730 |
ctx.fillText(legendItem.text, xLeft, yMiddle); |
| 11731 |
|
| 11732 |
if (legendItem.hidden) { |
| 11733 |
// Strikethrough the text if hidden |
| 11734 |
ctx.beginPath(); |
| 11735 |
ctx.lineWidth = 2; |
| 11736 |
ctx.moveTo(xLeft, yMiddle); |
| 11737 |
ctx.lineTo(xLeft + textWidth, yMiddle); |
| 11738 |
ctx.stroke(); |
| 11739 |
} |
| 11740 |
}; |
| 11741 |
|
| 11742 |
// Horizontal |
| 11743 |
var isHorizontal = me.isHorizontal(); |
| 11744 |
if (isHorizontal) { |
| 11745 |
cursor = { |
| 11746 |
x: me.left + ((legendWidth - lineWidths[0]) / 2), |
| 11747 |
y: me.top + labelOpts.padding, |
| 11748 |
line: 0 |
| 11749 |
}; |
| 11750 |
} else { |
| 11751 |
cursor = { |
| 11752 |
x: me.left + labelOpts.padding, |
| 11753 |
y: me.top + labelOpts.padding, |
| 11754 |
line: 0 |
| 11755 |
}; |
| 11756 |
} |
| 11757 |
|
| 11758 |
var itemHeight = fontSize + labelOpts.padding; |
| 11759 |
helpers.each(me.legendItems, function(legendItem, i) { |
| 11760 |
var textWidth = ctx.measureText(legendItem.text).width; |
| 11761 |
var width = boxWidth + (fontSize / 2) + textWidth; |
| 11762 |
var x = cursor.x; |
| 11763 |
var y = cursor.y; |
| 11764 |
|
| 11765 |
if (isHorizontal) { |
| 11766 |
if (x + width >= legendWidth) { |
| 11767 |
y = cursor.y += itemHeight; |
| 11768 |
cursor.line++; |
| 11769 |
x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); |
| 11770 |
} |
| 11771 |
} else if (y + itemHeight > me.bottom) { |
| 11772 |
x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; |
| 11773 |
y = cursor.y = me.top + labelOpts.padding; |
| 11774 |
cursor.line++; |
| 11775 |
} |
| 11776 |
|
| 11777 |
drawLegendBox(x, y, legendItem); |
| 11778 |
|
| 11779 |
hitboxes[i].left = x; |
| 11780 |
hitboxes[i].top = y; |
| 11781 |
|
| 11782 |
// Fill the actual label |
| 11783 |
fillText(x, y, legendItem, textWidth); |
| 11784 |
|
| 11785 |
if (isHorizontal) { |
| 11786 |
cursor.x += width + (labelOpts.padding); |
| 11787 |
} else { |
| 11788 |
cursor.y += itemHeight; |
| 11789 |
} |
| 11790 |
|
| 11791 |
}); |
| 11792 |
} |
| 11793 |
}, |
| 11794 |
|
| 11795 |
/** |
| 11796 |
* Handle an event |
| 11797 |
* @private |
| 11798 |
* @param {IEvent} event - The event to handle |
| 11799 |
* @return {Boolean} true if a change occured |
| 11800 |
*/ |
| 11801 |
handleEvent: function(e) { |
| 11802 |
var me = this; |
| 11803 |
var opts = me.options; |
| 11804 |
var type = e.type === 'mouseup' ? 'click' : e.type; |
| 11805 |
var changed = false; |
| 11806 |
|
| 11807 |
if (type === 'mousemove') { |
| 11808 |
if (!opts.onHover) { |
| 11809 |
return; |
| 11810 |
} |
| 11811 |
} else if (type === 'click') { |
| 11812 |
if (!opts.onClick) { |
| 11813 |
return; |
| 11814 |
} |
| 11815 |
} else { |
| 11816 |
return; |
| 11817 |
} |
| 11818 |
|
| 11819 |
// Chart event already has relative position in it |
| 11820 |
var x = e.x; |
| 11821 |
var y = e.y; |
| 11822 |
|
| 11823 |
if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { |
| 11824 |
// See if we are touching one of the dataset boxes |
| 11825 |
var lh = me.legendHitBoxes; |
| 11826 |
for (var i = 0; i < lh.length; ++i) { |
| 11827 |
var hitBox = lh[i]; |
| 11828 |
|
| 11829 |
if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { |
| 11830 |
// Touching an element |
| 11831 |
if (type === 'click') { |
| 11832 |
// use e.native for backwards compatibility |
| 11833 |
opts.onClick.call(me, e.native, me.legendItems[i]); |
| 11834 |
changed = true; |
| 11835 |
break; |
| 11836 |
} else if (type === 'mousemove') { |
| 11837 |
// use e.native for backwards compatibility |
| 11838 |
opts.onHover.call(me, e.native, me.legendItems[i]); |
| 11839 |
changed = true; |
| 11840 |
break; |
| 11841 |
} |
| 11842 |
} |
| 11843 |
} |
| 11844 |
} |
| 11845 |
|
| 11846 |
return changed; |
| 11847 |
} |
| 11848 |
}); |
| 11849 |
|
| 11850 |
function createNewLegendAndAttach(chart, legendOpts) { |
| 11851 |
var legend = new Chart.Legend({ |
| 11852 |
ctx: chart.ctx, |
| 11853 |
options: legendOpts, |
| 11854 |
chart: chart |
| 11855 |
}); |
| 11856 |
|
| 11857 |
layout.configure(chart, legend, legendOpts); |
| 11858 |
layout.addBox(chart, legend); |
| 11859 |
chart.legend = legend; |
| 11860 |
} |
| 11861 |
|
| 11862 |
return { |
| 11863 |
id: 'legend', |
| 11864 |
|
| 11865 |
beforeInit: function(chart) { |
| 11866 |
var legendOpts = chart.options.legend; |
| 11867 |
|
| 11868 |
if (legendOpts) { |
| 11869 |
createNewLegendAndAttach(chart, legendOpts); |
| 11870 |
} |
| 11871 |
}, |
| 11872 |
|
| 11873 |
beforeUpdate: function(chart) { |
| 11874 |
var legendOpts = chart.options.legend; |
| 11875 |
var legend = chart.legend; |
| 11876 |
|
| 11877 |
if (legendOpts) { |
| 11878 |
helpers.mergeIf(legendOpts, defaults.global.legend); |
| 11879 |
|
| 11880 |
if (legend) { |
| 11881 |
layout.configure(chart, legend, legendOpts); |
| 11882 |
legend.options = legendOpts; |
| 11883 |
} else { |
| 11884 |
createNewLegendAndAttach(chart, legendOpts); |
| 11885 |
} |
| 11886 |
} else if (legend) { |
| 11887 |
layout.removeBox(chart, legend); |
| 11888 |
delete chart.legend; |
| 11889 |
} |
| 11890 |
}, |
| 11891 |
|
| 11892 |
afterEvent: function(chart, e) { |
| 11893 |
var legend = chart.legend; |
| 11894 |
if (legend) { |
| 11895 |
legend.handleEvent(e); |
| 11896 |
} |
| 11897 |
} |
| 11898 |
}; |
| 11899 |
}; |
| 11900 |
|
| 11901 |
},{"25":25,"26":26,"45":45}],51:[function(require,module,exports){ |
| 11902 |
'use strict'; |
| 11903 |
|
| 11904 |
var defaults = require(25); |
| 11905 |
var Element = require(26); |
| 11906 |
var helpers = require(45); |
| 11907 |
|
| 11908 |
defaults._set('global', { |
| 11909 |
title: { |
| 11910 |
display: false, |
| 11911 |
fontStyle: 'bold', |
| 11912 |
fullWidth: true, |
| 11913 |
lineHeight: 1.2, |
| 11914 |
padding: 10, |
| 11915 |
position: 'top', |
| 11916 |
text: '', |
| 11917 |
weight: 2000 // by default greater than legend (1000) to be above |
| 11918 |
} |
| 11919 |
}); |
| 11920 |
|
| 11921 |
module.exports = function(Chart) { |
| 11922 |
|
| 11923 |
var layout = Chart.layoutService; |
| 11924 |
var noop = helpers.noop; |
| 11925 |
|
| 11926 |
Chart.Title = Element.extend({ |
| 11927 |
initialize: function(config) { |
| 11928 |
var me = this; |
| 11929 |
helpers.extend(me, config); |
| 11930 |
|
| 11931 |
// Contains hit boxes for each dataset (in dataset order) |
| 11932 |
me.legendHitBoxes = []; |
| 11933 |
}, |
| 11934 |
|
| 11935 |
// These methods are ordered by lifecycle. Utilities then follow. |
| 11936 |
|
| 11937 |
beforeUpdate: noop, |
| 11938 |
update: function(maxWidth, maxHeight, margins) { |
| 11939 |
var me = this; |
| 11940 |
|
| 11941 |
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) |
| 11942 |
me.beforeUpdate(); |
| 11943 |
|
| 11944 |
// Absorb the master measurements |
| 11945 |
me.maxWidth = maxWidth; |
| 11946 |
me.maxHeight = maxHeight; |
| 11947 |
me.margins = margins; |
| 11948 |
|
| 11949 |
// Dimensions |
| 11950 |
me.beforeSetDimensions(); |
| 11951 |
me.setDimensions(); |
| 11952 |
me.afterSetDimensions(); |
| 11953 |
// Labels |
| 11954 |
me.beforeBuildLabels(); |
| 11955 |
me.buildLabels(); |
| 11956 |
me.afterBuildLabels(); |
| 11957 |
|
| 11958 |
// Fit |
| 11959 |
me.beforeFit(); |
| 11960 |
me.fit(); |
| 11961 |
me.afterFit(); |
| 11962 |
// |
| 11963 |
me.afterUpdate(); |
| 11964 |
|
| 11965 |
return me.minSize; |
| 11966 |
|
| 11967 |
}, |
| 11968 |
afterUpdate: noop, |
| 11969 |
|
| 11970 |
// |
| 11971 |
|
| 11972 |
beforeSetDimensions: noop, |
| 11973 |
setDimensions: function() { |
| 11974 |
var me = this; |
| 11975 |
// Set the unconstrained dimension before label rotation |
| 11976 |
if (me.isHorizontal()) { |
| 11977 |
// Reset position before calculating rotation |
| 11978 |
me.width = me.maxWidth; |
| 11979 |
me.left = 0; |
| 11980 |
me.right = me.width; |
| 11981 |
} else { |
| 11982 |
me.height = me.maxHeight; |
| 11983 |
|
| 11984 |
// Reset position before calculating rotation |
| 11985 |
me.top = 0; |
| 11986 |
me.bottom = me.height; |
| 11987 |
} |
| 11988 |
|
| 11989 |
// Reset padding |
| 11990 |
me.paddingLeft = 0; |
| 11991 |
me.paddingTop = 0; |
| 11992 |
me.paddingRight = 0; |
| 11993 |
me.paddingBottom = 0; |
| 11994 |
|
| 11995 |
// Reset minSize |
| 11996 |
me.minSize = { |
| 11997 |
width: 0, |
| 11998 |
height: 0 |
| 11999 |
}; |
| 12000 |
}, |
| 12001 |
afterSetDimensions: noop, |
| 12002 |
|
| 12003 |
// |
| 12004 |
|
| 12005 |
beforeBuildLabels: noop, |
| 12006 |
buildLabels: noop, |
| 12007 |
afterBuildLabels: noop, |
| 12008 |
|
| 12009 |
// |
| 12010 |
|
| 12011 |
beforeFit: noop, |
| 12012 |
fit: function() { |
| 12013 |
var me = this; |
| 12014 |
var valueOrDefault = helpers.valueOrDefault; |
| 12015 |
var opts = me.options; |
| 12016 |
var display = opts.display; |
| 12017 |
var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize); |
| 12018 |
var minSize = me.minSize; |
| 12019 |
var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1; |
| 12020 |
var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); |
| 12021 |
var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0; |
| 12022 |
|
| 12023 |
if (me.isHorizontal()) { |
| 12024 |
minSize.width = me.maxWidth; // fill all the width |
| 12025 |
minSize.height = textSize; |
| 12026 |
} else { |
| 12027 |
minSize.width = textSize; |
| 12028 |
minSize.height = me.maxHeight; // fill all the height |
| 12029 |
} |
| 12030 |
|
| 12031 |
me.width = minSize.width; |
| 12032 |
me.height = minSize.height; |
| 12033 |
|
| 12034 |
}, |
| 12035 |
afterFit: noop, |
| 12036 |
|
| 12037 |
// Shared Methods |
| 12038 |
isHorizontal: function() { |
| 12039 |
var pos = this.options.position; |
| 12040 |
return pos === 'top' || pos === 'bottom'; |
| 12041 |
}, |
| 12042 |
|
| 12043 |
// Actually draw the title block on the canvas |
| 12044 |
draw: function() { |
| 12045 |
var me = this; |
| 12046 |
var ctx = me.ctx; |
| 12047 |
var valueOrDefault = helpers.valueOrDefault; |
| 12048 |
var opts = me.options; |
| 12049 |
var globalDefaults = defaults.global; |
| 12050 |
|
| 12051 |
if (opts.display) { |
| 12052 |
var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize); |
| 12053 |
var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle); |
| 12054 |
var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily); |
| 12055 |
var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); |
| 12056 |
var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); |
| 12057 |
var offset = lineHeight / 2 + opts.padding; |
| 12058 |
var rotation = 0; |
| 12059 |
var top = me.top; |
| 12060 |
var left = me.left; |
| 12061 |
var bottom = me.bottom; |
| 12062 |
var right = me.right; |
| 12063 |
var maxWidth, titleX, titleY; |
| 12064 |
|
| 12065 |
ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour |
| 12066 |
ctx.font = titleFont; |
| 12067 |
|
| 12068 |
// Horizontal |
| 12069 |
if (me.isHorizontal()) { |
| 12070 |
titleX = left + ((right - left) / 2); // midpoint of the width |
| 12071 |
titleY = top + offset; |
| 12072 |
maxWidth = right - left; |
| 12073 |
} else { |
| 12074 |
titleX = opts.position === 'left' ? left + offset : right - offset; |
| 12075 |
titleY = top + ((bottom - top) / 2); |
| 12076 |
maxWidth = bottom - top; |
| 12077 |
rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); |
| 12078 |
} |
| 12079 |
|
| 12080 |
ctx.save(); |
| 12081 |
ctx.translate(titleX, titleY); |
| 12082 |
ctx.rotate(rotation); |
| 12083 |
ctx.textAlign = 'center'; |
| 12084 |
ctx.textBaseline = 'middle'; |
| 12085 |
|
| 12086 |
var text = opts.text; |
| 12087 |
if (helpers.isArray(text)) { |
| 12088 |
var y = 0; |
| 12089 |
for (var i = 0; i < text.length; ++i) { |
| 12090 |
ctx.fillText(text[i], 0, y, maxWidth); |
| 12091 |
y += lineHeight; |
| 12092 |
} |
| 12093 |
} else { |
| 12094 |
ctx.fillText(text, 0, 0, maxWidth); |
| 12095 |
} |
| 12096 |
|
| 12097 |
ctx.restore(); |
| 12098 |
} |
| 12099 |
} |
| 12100 |
}); |
| 12101 |
|
| 12102 |
function createNewTitleBlockAndAttach(chart, titleOpts) { |
| 12103 |
var title = new Chart.Title({ |
| 12104 |
ctx: chart.ctx, |
| 12105 |
options: titleOpts, |
| 12106 |
chart: chart |
| 12107 |
}); |
| 12108 |
|
| 12109 |
layout.configure(chart, title, titleOpts); |
| 12110 |
layout.addBox(chart, title); |
| 12111 |
chart.titleBlock = title; |
| 12112 |
} |
| 12113 |
|
| 12114 |
return { |
| 12115 |
id: 'title', |
| 12116 |
|
| 12117 |
beforeInit: function(chart) { |
| 12118 |
var titleOpts = chart.options.title; |
| 12119 |
|
| 12120 |
if (titleOpts) { |
| 12121 |
createNewTitleBlockAndAttach(chart, titleOpts); |
| 12122 |
} |
| 12123 |
}, |
| 12124 |
|
| 12125 |
beforeUpdate: function(chart) { |
| 12126 |
var titleOpts = chart.options.title; |
| 12127 |
var titleBlock = chart.titleBlock; |
| 12128 |
|
| 12129 |
if (titleOpts) { |
| 12130 |
helpers.mergeIf(titleOpts, defaults.global.title); |
| 12131 |
|
| 12132 |
if (titleBlock) { |
| 12133 |
layout.configure(chart, titleBlock, titleOpts); |
| 12134 |
titleBlock.options = titleOpts; |
| 12135 |
} else { |
| 12136 |
createNewTitleBlockAndAttach(chart, titleOpts); |
| 12137 |
} |
| 12138 |
} else if (titleBlock) { |
| 12139 |
Chart.layoutService.removeBox(chart, titleBlock); |
| 12140 |
delete chart.titleBlock; |
| 12141 |
} |
| 12142 |
} |
| 12143 |
}; |
| 12144 |
}; |
| 12145 |
|
| 12146 |
},{"25":25,"26":26,"45":45}],52:[function(require,module,exports){ |
| 12147 |
'use strict'; |
| 12148 |
|
| 12149 |
module.exports = function(Chart) { |
| 12150 |
|
| 12151 |
// Default config for a category scale |
| 12152 |
var defaultConfig = { |
| 12153 |
position: 'bottom' |
| 12154 |
}; |
| 12155 |
|
| 12156 |
var DatasetScale = Chart.Scale.extend({ |
| 12157 |
/** |
| 12158 |
* Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those |
| 12159 |
* else fall back to data.labels |
| 12160 |
* @private |
| 12161 |
*/ |
| 12162 |
getLabels: function() { |
| 12163 |
var data = this.chart.data; |
| 12164 |
return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; |
| 12165 |
}, |
| 12166 |
|
| 12167 |
determineDataLimits: function() { |
| 12168 |
var me = this; |
| 12169 |
var labels = me.getLabels(); |
| 12170 |
me.minIndex = 0; |
| 12171 |
me.maxIndex = labels.length - 1; |
| 12172 |
var findIndex; |
| 12173 |
|
| 12174 |
if (me.options.ticks.min !== undefined) { |
| 12175 |
// user specified min value |
| 12176 |
findIndex = labels.indexOf(me.options.ticks.min); |
| 12177 |
me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; |
| 12178 |
} |
| 12179 |
|
| 12180 |
if (me.options.ticks.max !== undefined) { |
| 12181 |
// user specified max value |
| 12182 |
findIndex = labels.indexOf(me.options.ticks.max); |
| 12183 |
me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; |
| 12184 |
} |
| 12185 |
|
| 12186 |
me.min = labels[me.minIndex]; |
| 12187 |
me.max = labels[me.maxIndex]; |
| 12188 |
}, |
| 12189 |
|
| 12190 |
buildTicks: function() { |
| 12191 |
var me = this; |
| 12192 |
var labels = me.getLabels(); |
| 12193 |
// If we are viewing some subset of labels, slice the original array |
| 12194 |
me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); |
| 12195 |
}, |
| 12196 |
|
| 12197 |
getLabelForIndex: function(index, datasetIndex) { |
| 12198 |
var me = this; |
| 12199 |
var data = me.chart.data; |
| 12200 |
var isHorizontal = me.isHorizontal(); |
| 12201 |
|
| 12202 |
if (data.yLabels && !isHorizontal) { |
| 12203 |
return me.getRightValue(data.datasets[datasetIndex].data[index]); |
| 12204 |
} |
| 12205 |
return me.ticks[index - me.minIndex]; |
| 12206 |
}, |
| 12207 |
|
| 12208 |
// Used to get data value locations. Value can either be an index or a numerical value |
| 12209 |
getPixelForValue: function(value, index) { |
| 12210 |
var me = this; |
| 12211 |
var offset = me.options.offset; |
| 12212 |
// 1 is added because we need the length but we have the indexes |
| 12213 |
var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1); |
| 12214 |
|
| 12215 |
// If value is a data object, then index is the index in the data array, |
| 12216 |
// not the index of the scale. We need to change that. |
| 12217 |
var valueCategory; |
| 12218 |
if (value !== undefined && value !== null) { |
| 12219 |
valueCategory = me.isHorizontal() ? value.x : value.y; |
| 12220 |
} |
| 12221 |
if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { |
| 12222 |
var labels = me.getLabels(); |
| 12223 |
value = valueCategory || value; |
| 12224 |
var idx = labels.indexOf(value); |
| 12225 |
index = idx !== -1 ? idx : index; |
| 12226 |
} |
| 12227 |
|
| 12228 |
if (me.isHorizontal()) { |
| 12229 |
var valueWidth = me.width / offsetAmt; |
| 12230 |
var widthOffset = (valueWidth * (index - me.minIndex)); |
| 12231 |
|
| 12232 |
if (offset) { |
| 12233 |
widthOffset += (valueWidth / 2); |
| 12234 |
} |
| 12235 |
|
| 12236 |
return me.left + Math.round(widthOffset); |
| 12237 |
} |
| 12238 |
var valueHeight = me.height / offsetAmt; |
| 12239 |
var heightOffset = (valueHeight * (index - me.minIndex)); |
| 12240 |
|
| 12241 |
if (offset) { |
| 12242 |
heightOffset += (valueHeight / 2); |
| 12243 |
} |
| 12244 |
|
| 12245 |
return me.top + Math.round(heightOffset); |
| 12246 |
}, |
| 12247 |
getPixelForTick: function(index) { |
| 12248 |
return this.getPixelForValue(this.ticks[index], index + this.minIndex, null); |
| 12249 |
}, |
| 12250 |
getValueForPixel: function(pixel) { |
| 12251 |
var me = this; |
| 12252 |
var offset = me.options.offset; |
| 12253 |
var value; |
| 12254 |
var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1); |
| 12255 |
var horz = me.isHorizontal(); |
| 12256 |
var valueDimension = (horz ? me.width : me.height) / offsetAmt; |
| 12257 |
|
| 12258 |
pixel -= horz ? me.left : me.top; |
| 12259 |
|
| 12260 |
if (offset) { |
| 12261 |
pixel -= (valueDimension / 2); |
| 12262 |
} |
| 12263 |
|
| 12264 |
if (pixel <= 0) { |
| 12265 |
value = 0; |
| 12266 |
} else { |
| 12267 |
value = Math.round(pixel / valueDimension); |
| 12268 |
} |
| 12269 |
|
| 12270 |
return value + me.minIndex; |
| 12271 |
}, |
| 12272 |
getBasePixel: function() { |
| 12273 |
return this.bottom; |
| 12274 |
} |
| 12275 |
}); |
| 12276 |
|
| 12277 |
Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig); |
| 12278 |
|
| 12279 |
}; |
| 12280 |
|
| 12281 |
},{}],53:[function(require,module,exports){ |
| 12282 |
'use strict'; |
| 12283 |
|
| 12284 |
var defaults = require(25); |
| 12285 |
var helpers = require(45); |
| 12286 |
var Ticks = require(34); |
| 12287 |
|
| 12288 |
module.exports = function(Chart) { |
| 12289 |
|
| 12290 |
var defaultConfig = { |
| 12291 |
position: 'left', |
| 12292 |
ticks: { |
| 12293 |
callback: Ticks.formatters.linear |
| 12294 |
} |
| 12295 |
}; |
| 12296 |
|
| 12297 |
var LinearScale = Chart.LinearScaleBase.extend({ |
| 12298 |
|
| 12299 |
determineDataLimits: function() { |
| 12300 |
var me = this; |
| 12301 |
var opts = me.options; |
| 12302 |
var chart = me.chart; |
| 12303 |
var data = chart.data; |
| 12304 |
var datasets = data.datasets; |
| 12305 |
var isHorizontal = me.isHorizontal(); |
| 12306 |
var DEFAULT_MIN = 0; |
| 12307 |
var DEFAULT_MAX = 1; |
| 12308 |
|
| 12309 |
function IDMatches(meta) { |
| 12310 |
return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; |
| 12311 |
} |
| 12312 |
|
| 12313 |
// First Calculate the range |
| 12314 |
me.min = null; |
| 12315 |
me.max = null; |
| 12316 |
|
| 12317 |
var hasStacks = opts.stacked; |
| 12318 |
if (hasStacks === undefined) { |
| 12319 |
helpers.each(datasets, function(dataset, datasetIndex) { |
| 12320 |
if (hasStacks) { |
| 12321 |
return; |
| 12322 |
} |
| 12323 |
|
| 12324 |
var meta = chart.getDatasetMeta(datasetIndex); |
| 12325 |
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && |
| 12326 |
meta.stack !== undefined) { |
| 12327 |
hasStacks = true; |
| 12328 |
} |
| 12329 |
}); |
| 12330 |
} |
| 12331 |
|
| 12332 |
if (opts.stacked || hasStacks) { |
| 12333 |
var valuesPerStack = {}; |
| 12334 |
|
| 12335 |
helpers.each(datasets, function(dataset, datasetIndex) { |
| 12336 |
var meta = chart.getDatasetMeta(datasetIndex); |
| 12337 |
var key = [ |
| 12338 |
meta.type, |
| 12339 |
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined |
| 12340 |
((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), |
| 12341 |
meta.stack |
| 12342 |
].join('.'); |
| 12343 |
|
| 12344 |
if (valuesPerStack[key] === undefined) { |
| 12345 |
valuesPerStack[key] = { |
| 12346 |
positiveValues: [], |
| 12347 |
negativeValues: [] |
| 12348 |
}; |
| 12349 |
} |
| 12350 |
|
| 12351 |
// Store these per type |
| 12352 |
var positiveValues = valuesPerStack[key].positiveValues; |
| 12353 |
var negativeValues = valuesPerStack[key].negativeValues; |
| 12354 |
|
| 12355 |
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { |
| 12356 |
helpers.each(dataset.data, function(rawValue, index) { |
| 12357 |
var value = +me.getRightValue(rawValue); |
| 12358 |
if (isNaN(value) || meta.data[index].hidden) { |
| 12359 |
return; |
| 12360 |
} |
| 12361 |
|
| 12362 |
positiveValues[index] = positiveValues[index] || 0; |
| 12363 |
negativeValues[index] = negativeValues[index] || 0; |
| 12364 |
|
| 12365 |
if (opts.relativePoints) { |
| 12366 |
positiveValues[index] = 100; |
| 12367 |
} else if (value < 0) { |
| 12368 |
negativeValues[index] += value; |
| 12369 |
} else { |
| 12370 |
positiveValues[index] += value; |
| 12371 |
} |
| 12372 |
}); |
| 12373 |
} |
| 12374 |
}); |
| 12375 |
|
| 12376 |
helpers.each(valuesPerStack, function(valuesForType) { |
| 12377 |
var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); |
| 12378 |
var minVal = helpers.min(values); |
| 12379 |
var maxVal = helpers.max(values); |
| 12380 |
me.min = me.min === null ? minVal : Math.min(me.min, minVal); |
| 12381 |
me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); |
| 12382 |
}); |
| 12383 |
|
| 12384 |
} else { |
| 12385 |
helpers.each(datasets, function(dataset, datasetIndex) { |
| 12386 |
var meta = chart.getDatasetMeta(datasetIndex); |
| 12387 |
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { |
| 12388 |
helpers.each(dataset.data, function(rawValue, index) { |
| 12389 |
var value = +me.getRightValue(rawValue); |
| 12390 |
if (isNaN(value) || meta.data[index].hidden) { |
| 12391 |
return; |
| 12392 |
} |
| 12393 |
|
| 12394 |
if (me.min === null) { |
| 12395 |
me.min = value; |
| 12396 |
} else if (value < me.min) { |
| 12397 |
me.min = value; |
| 12398 |
} |
| 12399 |
|
| 12400 |
if (me.max === null) { |
| 12401 |
me.max = value; |
| 12402 |
} else if (value > me.max) { |
| 12403 |
me.max = value; |
| 12404 |
} |
| 12405 |
}); |
| 12406 |
} |
| 12407 |
}); |
| 12408 |
} |
| 12409 |
|
| 12410 |
me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; |
| 12411 |
me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; |
| 12412 |
|
| 12413 |
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero |
| 12414 |
this.handleTickRangeOptions(); |
| 12415 |
}, |
| 12416 |
getTickLimit: function() { |
| 12417 |
var maxTicks; |
| 12418 |
var me = this; |
| 12419 |
var tickOpts = me.options.ticks; |
| 12420 |
|
| 12421 |
if (me.isHorizontal()) { |
| 12422 |
maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); |
| 12423 |
} else { |
| 12424 |
// The factor of 2 used to scale the font size has been experimentally determined. |
| 12425 |
var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); |
| 12426 |
maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); |
| 12427 |
} |
| 12428 |
|
| 12429 |
return maxTicks; |
| 12430 |
}, |
| 12431 |
// Called after the ticks are built. We need |
| 12432 |
handleDirectionalChanges: function() { |
| 12433 |
if (!this.isHorizontal()) { |
| 12434 |
// We are in a vertical orientation. The top value is the highest. So reverse the array |
| 12435 |
this.ticks.reverse(); |
| 12436 |
} |
| 12437 |
}, |
| 12438 |
getLabelForIndex: function(index, datasetIndex) { |
| 12439 |
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); |
| 12440 |
}, |
| 12441 |
// Utils |
| 12442 |
getPixelForValue: function(value) { |
| 12443 |
// This must be called after fit has been run so that |
| 12444 |
// this.left, this.top, this.right, and this.bottom have been defined |
| 12445 |
var me = this; |
| 12446 |
var start = me.start; |
| 12447 |
|
| 12448 |
var rightValue = +me.getRightValue(value); |
| 12449 |
var pixel; |
| 12450 |
var range = me.end - start; |
| 12451 |
|
| 12452 |
if (me.isHorizontal()) { |
| 12453 |
pixel = me.left + (me.width / range * (rightValue - start)); |
| 12454 |
return Math.round(pixel); |
| 12455 |
} |
| 12456 |
|
| 12457 |
pixel = me.bottom - (me.height / range * (rightValue - start)); |
| 12458 |
return Math.round(pixel); |
| 12459 |
}, |
| 12460 |
getValueForPixel: function(pixel) { |
| 12461 |
var me = this; |
| 12462 |
var isHorizontal = me.isHorizontal(); |
| 12463 |
var innerDimension = isHorizontal ? me.width : me.height; |
| 12464 |
var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; |
| 12465 |
return me.start + ((me.end - me.start) * offset); |
| 12466 |
}, |
| 12467 |
getPixelForTick: function(index) { |
| 12468 |
return this.getPixelForValue(this.ticksAsNumbers[index]); |
| 12469 |
} |
| 12470 |
}); |
| 12471 |
Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig); |
| 12472 |
|
| 12473 |
}; |
| 12474 |
|
| 12475 |
},{"25":25,"34":34,"45":45}],54:[function(require,module,exports){ |
| 12476 |
'use strict'; |
| 12477 |
|
| 12478 |
var helpers = require(45); |
| 12479 |
var Ticks = require(34); |
| 12480 |
|
| 12481 |
module.exports = function(Chart) { |
| 12482 |
|
| 12483 |
var noop = helpers.noop; |
| 12484 |
|
| 12485 |
Chart.LinearScaleBase = Chart.Scale.extend({ |
| 12486 |
getRightValue: function(value) { |
| 12487 |
if (typeof value === 'string') { |
| 12488 |
return +value; |
| 12489 |
} |
| 12490 |
return Chart.Scale.prototype.getRightValue.call(this, value); |
| 12491 |
}, |
| 12492 |
|
| 12493 |
handleTickRangeOptions: function() { |
| 12494 |
var me = this; |
| 12495 |
var opts = me.options; |
| 12496 |
var tickOpts = opts.ticks; |
| 12497 |
|
| 12498 |
// If we are forcing it to begin at 0, but 0 will already be rendered on the chart, |
| 12499 |
// do nothing since that would make the chart weird. If the user really wants a weird chart |
| 12500 |
// axis, they can manually override it |
| 12501 |
if (tickOpts.beginAtZero) { |
| 12502 |
var minSign = helpers.sign(me.min); |
| 12503 |
var maxSign = helpers.sign(me.max); |
| 12504 |
|
| 12505 |
if (minSign < 0 && maxSign < 0) { |
| 12506 |
// move the top up to 0 |
| 12507 |
me.max = 0; |
| 12508 |
} else if (minSign > 0 && maxSign > 0) { |
| 12509 |
// move the bottom down to 0 |
| 12510 |
me.min = 0; |
| 12511 |
} |
| 12512 |
} |
| 12513 |
|
| 12514 |
var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; |
| 12515 |
var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; |
| 12516 |
|
| 12517 |
if (tickOpts.min !== undefined) { |
| 12518 |
me.min = tickOpts.min; |
| 12519 |
} else if (tickOpts.suggestedMin !== undefined) { |
| 12520 |
if (me.min === null) { |
| 12521 |
me.min = tickOpts.suggestedMin; |
| 12522 |
} else { |
| 12523 |
me.min = Math.min(me.min, tickOpts.suggestedMin); |
| 12524 |
} |
| 12525 |
} |
| 12526 |
|
| 12527 |
if (tickOpts.max !== undefined) { |
| 12528 |
me.max = tickOpts.max; |
| 12529 |
} else if (tickOpts.suggestedMax !== undefined) { |
| 12530 |
if (me.max === null) { |
| 12531 |
me.max = tickOpts.suggestedMax; |
| 12532 |
} else { |
| 12533 |
me.max = Math.max(me.max, tickOpts.suggestedMax); |
| 12534 |
} |
| 12535 |
} |
| 12536 |
|
| 12537 |
if (setMin !== setMax) { |
| 12538 |
// We set the min or the max but not both. |
| 12539 |
// So ensure that our range is good |
| 12540 |
// Inverted or 0 length range can happen when |
| 12541 |
// ticks.min is set, and no datasets are visible |
| 12542 |
if (me.min >= me.max) { |
| 12543 |
if (setMin) { |
| 12544 |
me.max = me.min + 1; |
| 12545 |
} else { |
| 12546 |
me.min = me.max - 1; |
| 12547 |
} |
| 12548 |
} |
| 12549 |
} |
| 12550 |
|
| 12551 |
if (me.min === me.max) { |
| 12552 |
me.max++; |
| 12553 |
|
| 12554 |
if (!tickOpts.beginAtZero) { |
| 12555 |
me.min--; |
| 12556 |
} |
| 12557 |
} |
| 12558 |
}, |
| 12559 |
getTickLimit: noop, |
| 12560 |
handleDirectionalChanges: noop, |
| 12561 |
|
| 12562 |
buildTicks: function() { |
| 12563 |
var me = this; |
| 12564 |
var opts = me.options; |
| 12565 |
var tickOpts = opts.ticks; |
| 12566 |
|
| 12567 |
// Figure out what the max number of ticks we can support it is based on the size of |
| 12568 |
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50 |
| 12569 |
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on |
| 12570 |
// the graph. Make sure we always have at least 2 ticks |
| 12571 |
var maxTicks = me.getTickLimit(); |
| 12572 |
maxTicks = Math.max(2, maxTicks); |
| 12573 |
|
| 12574 |
var numericGeneratorOptions = { |
| 12575 |
maxTicks: maxTicks, |
| 12576 |
min: tickOpts.min, |
| 12577 |
max: tickOpts.max, |
| 12578 |
stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) |
| 12579 |
}; |
| 12580 |
var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me); |
| 12581 |
|
| 12582 |
me.handleDirectionalChanges(); |
| 12583 |
|
| 12584 |
// At this point, we need to update our max and min given the tick values since we have expanded the |
| 12585 |
// range of the scale |
| 12586 |
me.max = helpers.max(ticks); |
| 12587 |
me.min = helpers.min(ticks); |
| 12588 |
|
| 12589 |
if (tickOpts.reverse) { |
| 12590 |
ticks.reverse(); |
| 12591 |
|
| 12592 |
me.start = me.max; |
| 12593 |
me.end = me.min; |
| 12594 |
} else { |
| 12595 |
me.start = me.min; |
| 12596 |
me.end = me.max; |
| 12597 |
} |
| 12598 |
}, |
| 12599 |
convertTicksToLabels: function() { |
| 12600 |
var me = this; |
| 12601 |
me.ticksAsNumbers = me.ticks.slice(); |
| 12602 |
me.zeroLineIndex = me.ticks.indexOf(0); |
| 12603 |
|
| 12604 |
Chart.Scale.prototype.convertTicksToLabels.call(me); |
| 12605 |
} |
| 12606 |
}); |
| 12607 |
}; |
| 12608 |
|
| 12609 |
},{"34":34,"45":45}],55:[function(require,module,exports){ |
| 12610 |
'use strict'; |
| 12611 |
|
| 12612 |
var helpers = require(45); |
| 12613 |
var Ticks = require(34); |
| 12614 |
|
| 12615 |
module.exports = function(Chart) { |
| 12616 |
|
| 12617 |
var defaultConfig = { |
| 12618 |
position: 'left', |
| 12619 |
|
| 12620 |
// label settings |
| 12621 |
ticks: { |
| 12622 |
callback: Ticks.formatters.logarithmic |
| 12623 |
} |
| 12624 |
}; |
| 12625 |
|
| 12626 |
var LogarithmicScale = Chart.Scale.extend({ |
| 12627 |
determineDataLimits: function() { |
| 12628 |
var me = this; |
| 12629 |
var opts = me.options; |
| 12630 |
var tickOpts = opts.ticks; |
| 12631 |
var chart = me.chart; |
| 12632 |
var data = chart.data; |
| 12633 |
var datasets = data.datasets; |
| 12634 |
var valueOrDefault = helpers.valueOrDefault; |
| 12635 |
var isHorizontal = me.isHorizontal(); |
| 12636 |
function IDMatches(meta) { |
| 12637 |
return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; |
| 12638 |
} |
| 12639 |
|
| 12640 |
// Calculate Range |
| 12641 |
me.min = null; |
| 12642 |
me.max = null; |
| 12643 |
me.minNotZero = null; |
| 12644 |
|
| 12645 |
var hasStacks = opts.stacked; |
| 12646 |
if (hasStacks === undefined) { |
| 12647 |
helpers.each(datasets, function(dataset, datasetIndex) { |
| 12648 |
if (hasStacks) { |
| 12649 |
return; |
| 12650 |
} |
| 12651 |
|
| 12652 |
var meta = chart.getDatasetMeta(datasetIndex); |
| 12653 |
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && |
| 12654 |
meta.stack !== undefined) { |
| 12655 |
hasStacks = true; |
| 12656 |
} |
| 12657 |
}); |
| 12658 |
} |
| 12659 |
|
| 12660 |
if (opts.stacked || hasStacks) { |
| 12661 |
var valuesPerStack = {}; |
| 12662 |
|
| 12663 |
helpers.each(datasets, function(dataset, datasetIndex) { |
| 12664 |
var meta = chart.getDatasetMeta(datasetIndex); |
| 12665 |
var key = [ |
| 12666 |
meta.type, |
| 12667 |
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined |
| 12668 |
((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), |
| 12669 |
meta.stack |
| 12670 |
].join('.'); |
| 12671 |
|
| 12672 |
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { |
| 12673 |
if (valuesPerStack[key] === undefined) { |
| 12674 |
valuesPerStack[key] = []; |
| 12675 |
} |
| 12676 |
|
| 12677 |
helpers.each(dataset.data, function(rawValue, index) { |
| 12678 |
var values = valuesPerStack[key]; |
| 12679 |
var value = +me.getRightValue(rawValue); |
| 12680 |
if (isNaN(value) || meta.data[index].hidden) { |
| 12681 |
return; |
| 12682 |
} |
| 12683 |
|
| 12684 |
values[index] = values[index] || 0; |
| 12685 |
|
| 12686 |
if (opts.relativePoints) { |
| 12687 |
values[index] = 100; |
| 12688 |
} else { |
| 12689 |
// Don't need to split positive and negative since the log scale can't handle a 0 crossing |
| 12690 |
values[index] += value; |
| 12691 |
} |
| 12692 |
}); |
| 12693 |
} |
| 12694 |
}); |
| 12695 |
|
| 12696 |
helpers.each(valuesPerStack, function(valuesForType) { |
| 12697 |
var minVal = helpers.min(valuesForType); |
| 12698 |
var maxVal = helpers.max(valuesForType); |
| 12699 |
me.min = me.min === null ? minVal : Math.min(me.min, minVal); |
| 12700 |
me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); |
| 12701 |
}); |
| 12702 |
|
| 12703 |
} else { |
| 12704 |
helpers.each(datasets, function(dataset, datasetIndex) { |
| 12705 |
var meta = chart.getDatasetMeta(datasetIndex); |
| 12706 |
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { |
| 12707 |
helpers.each(dataset.data, function(rawValue, index) { |
| 12708 |
var value = +me.getRightValue(rawValue); |
| 12709 |
if (isNaN(value) || meta.data[index].hidden) { |
| 12710 |
return; |
| 12711 |
} |
| 12712 |
|
| 12713 |
if (me.min === null) { |
| 12714 |
me.min = value; |
| 12715 |
} else if (value < me.min) { |
| 12716 |
me.min = value; |
| 12717 |
} |
| 12718 |
|
| 12719 |
if (me.max === null) { |
| 12720 |
me.max = value; |
| 12721 |
} else if (value > me.max) { |
| 12722 |
me.max = value; |
| 12723 |
} |
| 12724 |
|
| 12725 |
if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { |
| 12726 |
me.minNotZero = value; |
| 12727 |
} |
| 12728 |
}); |
| 12729 |
} |
| 12730 |
}); |
| 12731 |
} |
| 12732 |
|
| 12733 |
me.min = valueOrDefault(tickOpts.min, me.min); |
| 12734 |
me.max = valueOrDefault(tickOpts.max, me.max); |
| 12735 |
|
| 12736 |
if (me.min === me.max) { |
| 12737 |
if (me.min !== 0 && me.min !== null) { |
| 12738 |
me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); |
| 12739 |
me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); |
| 12740 |
} else { |
| 12741 |
me.min = 1; |
| 12742 |
me.max = 10; |
| 12743 |
} |
| 12744 |
} |
| 12745 |
}, |
| 12746 |
buildTicks: function() { |
| 12747 |
var me = this; |
| 12748 |
var opts = me.options; |
| 12749 |
var tickOpts = opts.ticks; |
| 12750 |
|
| 12751 |
var generationOptions = { |
| 12752 |
min: tickOpts.min, |
| 12753 |
max: tickOpts.max |
| 12754 |
}; |
| 12755 |
var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me); |
| 12756 |
|
| 12757 |
if (!me.isHorizontal()) { |
| 12758 |
// We are in a vertical orientation. The top value is the highest. So reverse the array |
| 12759 |
ticks.reverse(); |
| 12760 |
} |
| 12761 |
|
| 12762 |
// At this point, we need to update our max and min given the tick values since we have expanded the |
| 12763 |
// range of the scale |
| 12764 |
me.max = helpers.max(ticks); |
| 12765 |
me.min = helpers.min(ticks); |
| 12766 |
|
| 12767 |
if (tickOpts.reverse) { |
| 12768 |
ticks.reverse(); |
| 12769 |
|
| 12770 |
me.start = me.max; |
| 12771 |
me.end = me.min; |
| 12772 |
} else { |
| 12773 |
me.start = me.min; |
| 12774 |
me.end = me.max; |
| 12775 |
} |
| 12776 |
}, |
| 12777 |
convertTicksToLabels: function() { |
| 12778 |
this.tickValues = this.ticks.slice(); |
| 12779 |
|
| 12780 |
Chart.Scale.prototype.convertTicksToLabels.call(this); |
| 12781 |
}, |
| 12782 |
// Get the correct tooltip label |
| 12783 |
getLabelForIndex: function(index, datasetIndex) { |
| 12784 |
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); |
| 12785 |
}, |
| 12786 |
getPixelForTick: function(index) { |
| 12787 |
return this.getPixelForValue(this.tickValues[index]); |
| 12788 |
}, |
| 12789 |
getPixelForValue: function(value) { |
| 12790 |
var me = this; |
| 12791 |
var start = me.start; |
| 12792 |
var newVal = +me.getRightValue(value); |
| 12793 |
var opts = me.options; |
| 12794 |
var tickOpts = opts.ticks; |
| 12795 |
var innerDimension, pixel, range; |
| 12796 |
|
| 12797 |
if (me.isHorizontal()) { |
| 12798 |
range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0 |
| 12799 |
if (newVal === 0) { |
| 12800 |
pixel = me.left; |
| 12801 |
} else { |
| 12802 |
innerDimension = me.width; |
| 12803 |
pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); |
| 12804 |
} |
| 12805 |
} else { |
| 12806 |
// Bottom - top since pixels increase downward on a screen |
| 12807 |
innerDimension = me.height; |
| 12808 |
if (start === 0 && !tickOpts.reverse) { |
| 12809 |
range = helpers.log10(me.end) - helpers.log10(me.minNotZero); |
| 12810 |
if (newVal === start) { |
| 12811 |
pixel = me.bottom; |
| 12812 |
} else if (newVal === me.minNotZero) { |
| 12813 |
pixel = me.bottom - innerDimension * 0.02; |
| 12814 |
} else { |
| 12815 |
pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero))); |
| 12816 |
} |
| 12817 |
} else if (me.end === 0 && tickOpts.reverse) { |
| 12818 |
range = helpers.log10(me.start) - helpers.log10(me.minNotZero); |
| 12819 |
if (newVal === me.end) { |
| 12820 |
pixel = me.top; |
| 12821 |
} else if (newVal === me.minNotZero) { |
| 12822 |
pixel = me.top + innerDimension * 0.02; |
| 12823 |
} else { |
| 12824 |
pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero))); |
| 12825 |
} |
| 12826 |
} else if (newVal === 0) { |
| 12827 |
pixel = tickOpts.reverse ? me.top : me.bottom; |
| 12828 |
} else { |
| 12829 |
range = helpers.log10(me.end) - helpers.log10(start); |
| 12830 |
innerDimension = me.height; |
| 12831 |
pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); |
| 12832 |
} |
| 12833 |
} |
| 12834 |
return pixel; |
| 12835 |
}, |
| 12836 |
getValueForPixel: function(pixel) { |
| 12837 |
var me = this; |
| 12838 |
var range = helpers.log10(me.end) - helpers.log10(me.start); |
| 12839 |
var value, innerDimension; |
| 12840 |
|
| 12841 |
if (me.isHorizontal()) { |
| 12842 |
innerDimension = me.width; |
| 12843 |
value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension); |
| 12844 |
} else { // todo: if start === 0 |
| 12845 |
innerDimension = me.height; |
| 12846 |
value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start; |
| 12847 |
} |
| 12848 |
return value; |
| 12849 |
} |
| 12850 |
}); |
| 12851 |
Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig); |
| 12852 |
|
| 12853 |
}; |
| 12854 |
|
| 12855 |
},{"34":34,"45":45}],56:[function(require,module,exports){ |
| 12856 |
'use strict'; |
| 12857 |
|
| 12858 |
var defaults = require(25); |
| 12859 |
var helpers = require(45); |
| 12860 |
var Ticks = require(34); |
| 12861 |
|
| 12862 |
module.exports = function(Chart) { |
| 12863 |
|
| 12864 |
var globalDefaults = defaults.global; |
| 12865 |
|
| 12866 |
var defaultConfig = { |
| 12867 |
display: true, |
| 12868 |
|
| 12869 |
// Boolean - Whether to animate scaling the chart from the centre |
| 12870 |
animate: true, |
| 12871 |
position: 'chartArea', |
| 12872 |
|
| 12873 |
angleLines: { |
| 12874 |
display: true, |
| 12875 |
color: 'rgba(0, 0, 0, 0.1)', |
| 12876 |
lineWidth: 1 |
| 12877 |
}, |
| 12878 |
|
| 12879 |
gridLines: { |
| 12880 |
circular: false |
| 12881 |
}, |
| 12882 |
|
| 12883 |
// label settings |
| 12884 |
ticks: { |
| 12885 |
// Boolean - Show a backdrop to the scale label |
| 12886 |
showLabelBackdrop: true, |
| 12887 |
|
| 12888 |
// String - The colour of the label backdrop |
| 12889 |
backdropColor: 'rgba(255,255,255,0.75)', |
| 12890 |
|
| 12891 |
// Number - The backdrop padding above & below the label in pixels |
| 12892 |
backdropPaddingY: 2, |
| 12893 |
|
| 12894 |
// Number - The backdrop padding to the side of the label in pixels |
| 12895 |
backdropPaddingX: 2, |
| 12896 |
|
| 12897 |
callback: Ticks.formatters.linear |
| 12898 |
}, |
| 12899 |
|
| 12900 |
pointLabels: { |
| 12901 |
// Boolean - if true, show point labels |
| 12902 |
display: true, |
| 12903 |
|
| 12904 |
// Number - Point label font size in pixels |
| 12905 |
fontSize: 10, |
| 12906 |
|
| 12907 |
// Function - Used to convert point labels |
| 12908 |
callback: function(label) { |
| 12909 |
return label; |
| 12910 |
} |
| 12911 |
} |
| 12912 |
}; |
| 12913 |
|
| 12914 |
function getValueCount(scale) { |
| 12915 |
var opts = scale.options; |
| 12916 |
return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; |
| 12917 |
} |
| 12918 |
|
| 12919 |
function getPointLabelFontOptions(scale) { |
| 12920 |
var pointLabelOptions = scale.options.pointLabels; |
| 12921 |
var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize); |
| 12922 |
var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle); |
| 12923 |
var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily); |
| 12924 |
var font = helpers.fontString(fontSize, fontStyle, fontFamily); |
| 12925 |
|
| 12926 |
return { |
| 12927 |
size: fontSize, |
| 12928 |
style: fontStyle, |
| 12929 |
family: fontFamily, |
| 12930 |
font: font |
| 12931 |
}; |
| 12932 |
} |
| 12933 |
|
| 12934 |
function measureLabelSize(ctx, fontSize, label) { |
| 12935 |
if (helpers.isArray(label)) { |
| 12936 |
return { |
| 12937 |
w: helpers.longestText(ctx, ctx.font, label), |
| 12938 |
h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize) |
| 12939 |
}; |
| 12940 |
} |
| 12941 |
|
| 12942 |
return { |
| 12943 |
w: ctx.measureText(label).width, |
| 12944 |
h: fontSize |
| 12945 |
}; |
| 12946 |
} |
| 12947 |
|
| 12948 |
function determineLimits(angle, pos, size, min, max) { |
| 12949 |
if (angle === min || angle === max) { |
| 12950 |
return { |
| 12951 |
start: pos - (size / 2), |
| 12952 |
end: pos + (size / 2) |
| 12953 |
}; |
| 12954 |
} else if (angle < min || angle > max) { |
| 12955 |
return { |
| 12956 |
start: pos - size - 5, |
| 12957 |
end: pos |
| 12958 |
}; |
| 12959 |
} |
| 12960 |
|
| 12961 |
return { |
| 12962 |
start: pos, |
| 12963 |
end: pos + size + 5 |
| 12964 |
}; |
| 12965 |
} |
| 12966 |
|
| 12967 |
/** |
| 12968 |
* Helper function to fit a radial linear scale with point labels |
| 12969 |
*/ |
| 12970 |
function fitWithPointLabels(scale) { |
| 12971 |
/* |
| 12972 |
* Right, this is really confusing and there is a lot of maths going on here |
| 12973 |
* The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 |
| 12974 |
* |
| 12975 |
* Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif |
| 12976 |
* |
| 12977 |
* Solution: |
| 12978 |
* |
| 12979 |
* We assume the radius of the polygon is half the size of the canvas at first |
| 12980 |
* at each index we check if the text overlaps. |
| 12981 |
* |
| 12982 |
* Where it does, we store that angle and that index. |
| 12983 |
* |
| 12984 |
* After finding the largest index and angle we calculate how much we need to remove |
| 12985 |
* from the shape radius to move the point inwards by that x. |
| 12986 |
* |
| 12987 |
* We average the left and right distances to get the maximum shape radius that can fit in the box |
| 12988 |
* along with labels. |
| 12989 |
* |
| 12990 |
* Once we have that, we can find the centre point for the chart, by taking the x text protrusion |
| 12991 |
* on each side, removing that from the size, halving it and adding the left x protrusion width. |
| 12992 |
* |
| 12993 |
* This will mean we have a shape fitted to the canvas, as large as it can be with the labels |
| 12994 |
* and position it in the most space efficient manner |
| 12995 |
* |
| 12996 |
* https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif |
| 12997 |
*/ |
| 12998 |
|
| 12999 |
var plFont = getPointLabelFontOptions(scale); |
| 13000 |
|
| 13001 |
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. |
| 13002 |
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points |
| 13003 |
var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); |
| 13004 |
var furthestLimits = { |
| 13005 |
r: scale.width, |
| 13006 |
l: 0, |
| 13007 |
t: scale.height, |
| 13008 |
b: 0 |
| 13009 |
}; |
| 13010 |
var furthestAngles = {}; |
| 13011 |
var i, textSize, pointPosition; |
| 13012 |
|
| 13013 |
scale.ctx.font = plFont.font; |
| 13014 |
scale._pointLabelSizes = []; |
| 13015 |
|
| 13016 |
var valueCount = getValueCount(scale); |
| 13017 |
for (i = 0; i < valueCount; i++) { |
| 13018 |
pointPosition = scale.getPointPosition(i, largestPossibleRadius); |
| 13019 |
textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || ''); |
| 13020 |
scale._pointLabelSizes[i] = textSize; |
| 13021 |
|
| 13022 |
// Add quarter circle to make degree 0 mean top of circle |
| 13023 |
var angleRadians = scale.getIndexAngle(i); |
| 13024 |
var angle = helpers.toDegrees(angleRadians) % 360; |
| 13025 |
var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); |
| 13026 |
var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); |
| 13027 |
|
| 13028 |
if (hLimits.start < furthestLimits.l) { |
| 13029 |
furthestLimits.l = hLimits.start; |
| 13030 |
furthestAngles.l = angleRadians; |
| 13031 |
} |
| 13032 |
|
| 13033 |
if (hLimits.end > furthestLimits.r) { |
| 13034 |
furthestLimits.r = hLimits.end; |
| 13035 |
furthestAngles.r = angleRadians; |
| 13036 |
} |
| 13037 |
|
| 13038 |
if (vLimits.start < furthestLimits.t) { |
| 13039 |
furthestLimits.t = vLimits.start; |
| 13040 |
furthestAngles.t = angleRadians; |
| 13041 |
} |
| 13042 |
|
| 13043 |
if (vLimits.end > furthestLimits.b) { |
| 13044 |
furthestLimits.b = vLimits.end; |
| 13045 |
furthestAngles.b = angleRadians; |
| 13046 |
} |
| 13047 |
} |
| 13048 |
|
| 13049 |
scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles); |
| 13050 |
} |
| 13051 |
|
| 13052 |
/** |
| 13053 |
* Helper function to fit a radial linear scale with no point labels |
| 13054 |
*/ |
| 13055 |
function fit(scale) { |
| 13056 |
var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); |
| 13057 |
scale.drawingArea = Math.round(largestPossibleRadius); |
| 13058 |
scale.setCenterPoint(0, 0, 0, 0); |
| 13059 |
} |
| 13060 |
|
| 13061 |
function getTextAlignForAngle(angle) { |
| 13062 |
if (angle === 0 || angle === 180) { |
| 13063 |
return 'center'; |
| 13064 |
} else if (angle < 180) { |
| 13065 |
return 'left'; |
| 13066 |
} |
| 13067 |
|
| 13068 |
return 'right'; |
| 13069 |
} |
| 13070 |
|
| 13071 |
function fillText(ctx, text, position, fontSize) { |
| 13072 |
if (helpers.isArray(text)) { |
| 13073 |
var y = position.y; |
| 13074 |
var spacing = 1.5 * fontSize; |
| 13075 |
|
| 13076 |
for (var i = 0; i < text.length; ++i) { |
| 13077 |
ctx.fillText(text[i], position.x, y); |
| 13078 |
y += spacing; |
| 13079 |
} |
| 13080 |
} else { |
| 13081 |
ctx.fillText(text, position.x, position.y); |
| 13082 |
} |
| 13083 |
} |
| 13084 |
|
| 13085 |
function adjustPointPositionForLabelHeight(angle, textSize, position) { |
| 13086 |
if (angle === 90 || angle === 270) { |
| 13087 |
position.y -= (textSize.h / 2); |
| 13088 |
} else if (angle > 270 || angle < 90) { |
| 13089 |
position.y -= textSize.h; |
| 13090 |
} |
| 13091 |
} |
| 13092 |
|
| 13093 |
function drawPointLabels(scale) { |
| 13094 |
var ctx = scale.ctx; |
| 13095 |
var valueOrDefault = helpers.valueOrDefault; |
| 13096 |
var opts = scale.options; |
| 13097 |
var angleLineOpts = opts.angleLines; |
| 13098 |
var pointLabelOpts = opts.pointLabels; |
| 13099 |
|
| 13100 |
ctx.lineWidth = angleLineOpts.lineWidth; |
| 13101 |
ctx.strokeStyle = angleLineOpts.color; |
| 13102 |
|
| 13103 |
var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); |
| 13104 |
|
| 13105 |
// Point Label Font |
| 13106 |
var plFont = getPointLabelFontOptions(scale); |
| 13107 |
|
| 13108 |
ctx.textBaseline = 'top'; |
| 13109 |
|
| 13110 |
for (var i = getValueCount(scale) - 1; i >= 0; i--) { |
| 13111 |
if (angleLineOpts.display) { |
| 13112 |
var outerPosition = scale.getPointPosition(i, outerDistance); |
| 13113 |
ctx.beginPath(); |
| 13114 |
ctx.moveTo(scale.xCenter, scale.yCenter); |
| 13115 |
ctx.lineTo(outerPosition.x, outerPosition.y); |
| 13116 |
ctx.stroke(); |
| 13117 |
ctx.closePath(); |
| 13118 |
} |
| 13119 |
|
| 13120 |
if (pointLabelOpts.display) { |
| 13121 |
// Extra 3px out for some label spacing |
| 13122 |
var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5); |
| 13123 |
|
| 13124 |
// Keep this in loop since we may support array properties here |
| 13125 |
var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor); |
| 13126 |
ctx.font = plFont.font; |
| 13127 |
ctx.fillStyle = pointLabelFontColor; |
| 13128 |
|
| 13129 |
var angleRadians = scale.getIndexAngle(i); |
| 13130 |
var angle = helpers.toDegrees(angleRadians); |
| 13131 |
ctx.textAlign = getTextAlignForAngle(angle); |
| 13132 |
adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); |
| 13133 |
fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size); |
| 13134 |
} |
| 13135 |
} |
| 13136 |
} |
| 13137 |
|
| 13138 |
function drawRadiusLine(scale, gridLineOpts, radius, index) { |
| 13139 |
var ctx = scale.ctx; |
| 13140 |
ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); |
| 13141 |
ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); |
| 13142 |
|
| 13143 |
if (scale.options.gridLines.circular) { |
| 13144 |
// Draw circular arcs between the points |
| 13145 |
ctx.beginPath(); |
| 13146 |
ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); |
| 13147 |
ctx.closePath(); |
| 13148 |
ctx.stroke(); |
| 13149 |
} else { |
| 13150 |
// Draw straight lines connecting each index |
| 13151 |
var valueCount = getValueCount(scale); |
| 13152 |
|
| 13153 |
if (valueCount === 0) { |
| 13154 |
return; |
| 13155 |
} |
| 13156 |
|
| 13157 |
ctx.beginPath(); |
| 13158 |
var pointPosition = scale.getPointPosition(0, radius); |
| 13159 |
ctx.moveTo(pointPosition.x, pointPosition.y); |
| 13160 |
|
| 13161 |
for (var i = 1; i < valueCount; i++) { |
| 13162 |
pointPosition = scale.getPointPosition(i, radius); |
| 13163 |
ctx.lineTo(pointPosition.x, pointPosition.y); |
| 13164 |
} |
| 13165 |
|
| 13166 |
ctx.closePath(); |
| 13167 |
ctx.stroke(); |
| 13168 |
} |
| 13169 |
} |
| 13170 |
|
| 13171 |
function numberOrZero(param) { |
| 13172 |
return helpers.isNumber(param) ? param : 0; |
| 13173 |
} |
| 13174 |
|
| 13175 |
var LinearRadialScale = Chart.LinearScaleBase.extend({ |
| 13176 |
setDimensions: function() { |
| 13177 |
var me = this; |
| 13178 |
var opts = me.options; |
| 13179 |
var tickOpts = opts.ticks; |
| 13180 |
// Set the unconstrained dimension before label rotation |
| 13181 |
me.width = me.maxWidth; |
| 13182 |
me.height = me.maxHeight; |
| 13183 |
me.xCenter = Math.round(me.width / 2); |
| 13184 |
me.yCenter = Math.round(me.height / 2); |
| 13185 |
|
| 13186 |
var minSize = helpers.min([me.height, me.width]); |
| 13187 |
var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); |
| 13188 |
me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); |
| 13189 |
}, |
| 13190 |
determineDataLimits: function() { |
| 13191 |
var me = this; |
| 13192 |
var chart = me.chart; |
| 13193 |
var min = Number.POSITIVE_INFINITY; |
| 13194 |
var max = Number.NEGATIVE_INFINITY; |
| 13195 |
|
| 13196 |
helpers.each(chart.data.datasets, function(dataset, datasetIndex) { |
| 13197 |
if (chart.isDatasetVisible(datasetIndex)) { |
| 13198 |
var meta = chart.getDatasetMeta(datasetIndex); |
| 13199 |
|
| 13200 |
helpers.each(dataset.data, function(rawValue, index) { |
| 13201 |
var value = +me.getRightValue(rawValue); |
| 13202 |
if (isNaN(value) || meta.data[index].hidden) { |
| 13203 |
return; |
| 13204 |
} |
| 13205 |
|
| 13206 |
min = Math.min(value, min); |
| 13207 |
max = Math.max(value, max); |
| 13208 |
}); |
| 13209 |
} |
| 13210 |
}); |
| 13211 |
|
| 13212 |
me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); |
| 13213 |
me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); |
| 13214 |
|
| 13215 |
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero |
| 13216 |
me.handleTickRangeOptions(); |
| 13217 |
}, |
| 13218 |
getTickLimit: function() { |
| 13219 |
var tickOpts = this.options.ticks; |
| 13220 |
var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); |
| 13221 |
return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); |
| 13222 |
}, |
| 13223 |
convertTicksToLabels: function() { |
| 13224 |
var me = this; |
| 13225 |
|
| 13226 |
Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); |
| 13227 |
|
| 13228 |
// Point labels |
| 13229 |
me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); |
| 13230 |
}, |
| 13231 |
getLabelForIndex: function(index, datasetIndex) { |
| 13232 |
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); |
| 13233 |
}, |
| 13234 |
fit: function() { |
| 13235 |
if (this.options.pointLabels.display) { |
| 13236 |
fitWithPointLabels(this); |
| 13237 |
} else { |
| 13238 |
fit(this); |
| 13239 |
} |
| 13240 |
}, |
| 13241 |
/** |
| 13242 |
* Set radius reductions and determine new radius and center point |
| 13243 |
* @private |
| 13244 |
*/ |
| 13245 |
setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { |
| 13246 |
var me = this; |
| 13247 |
var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); |
| 13248 |
var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); |
| 13249 |
var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); |
| 13250 |
var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b); |
| 13251 |
|
| 13252 |
radiusReductionLeft = numberOrZero(radiusReductionLeft); |
| 13253 |
radiusReductionRight = numberOrZero(radiusReductionRight); |
| 13254 |
radiusReductionTop = numberOrZero(radiusReductionTop); |
| 13255 |
radiusReductionBottom = numberOrZero(radiusReductionBottom); |
| 13256 |
|
| 13257 |
me.drawingArea = Math.min( |
| 13258 |
Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), |
| 13259 |
Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); |
| 13260 |
me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); |
| 13261 |
}, |
| 13262 |
setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { |
| 13263 |
var me = this; |
| 13264 |
var maxRight = me.width - rightMovement - me.drawingArea; |
| 13265 |
var maxLeft = leftMovement + me.drawingArea; |
| 13266 |
var maxTop = topMovement + me.drawingArea; |
| 13267 |
var maxBottom = me.height - bottomMovement - me.drawingArea; |
| 13268 |
|
| 13269 |
me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); |
| 13270 |
me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top); |
| 13271 |
}, |
| 13272 |
|
| 13273 |
getIndexAngle: function(index) { |
| 13274 |
var angleMultiplier = (Math.PI * 2) / getValueCount(this); |
| 13275 |
var startAngle = this.chart.options && this.chart.options.startAngle ? |
| 13276 |
this.chart.options.startAngle : |
| 13277 |
0; |
| 13278 |
|
| 13279 |
var startAngleRadians = startAngle * Math.PI * 2 / 360; |
| 13280 |
|
| 13281 |
// Start from the top instead of right, so remove a quarter of the circle |
| 13282 |
return index * angleMultiplier + startAngleRadians; |
| 13283 |
}, |
| 13284 |
getDistanceFromCenterForValue: function(value) { |
| 13285 |
var me = this; |
| 13286 |
|
| 13287 |
if (value === null) { |
| 13288 |
return 0; // null always in center |
| 13289 |
} |
| 13290 |
|
| 13291 |
// Take into account half font size + the yPadding of the top value |
| 13292 |
var scalingFactor = me.drawingArea / (me.max - me.min); |
| 13293 |
if (me.options.ticks.reverse) { |
| 13294 |
return (me.max - value) * scalingFactor; |
| 13295 |
} |
| 13296 |
return (value - me.min) * scalingFactor; |
| 13297 |
}, |
| 13298 |
getPointPosition: function(index, distanceFromCenter) { |
| 13299 |
var me = this; |
| 13300 |
var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); |
| 13301 |
return { |
| 13302 |
x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, |
| 13303 |
y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter |
| 13304 |
}; |
| 13305 |
}, |
| 13306 |
getPointPositionForValue: function(index, value) { |
| 13307 |
return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); |
| 13308 |
}, |
| 13309 |
|
| 13310 |
getBasePosition: function() { |
| 13311 |
var me = this; |
| 13312 |
var min = me.min; |
| 13313 |
var max = me.max; |
| 13314 |
|
| 13315 |
return me.getPointPositionForValue(0, |
| 13316 |
me.beginAtZero ? 0 : |
| 13317 |
min < 0 && max < 0 ? max : |
| 13318 |
min > 0 && max > 0 ? min : |
| 13319 |
0); |
| 13320 |
}, |
| 13321 |
|
| 13322 |
draw: function() { |
| 13323 |
var me = this; |
| 13324 |
var opts = me.options; |
| 13325 |
var gridLineOpts = opts.gridLines; |
| 13326 |
var tickOpts = opts.ticks; |
| 13327 |
var valueOrDefault = helpers.valueOrDefault; |
| 13328 |
|
| 13329 |
if (opts.display) { |
| 13330 |
var ctx = me.ctx; |
| 13331 |
var startAngle = this.getIndexAngle(0); |
| 13332 |
|
| 13333 |
// Tick Font |
| 13334 |
var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); |
| 13335 |
var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); |
| 13336 |
var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); |
| 13337 |
var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); |
| 13338 |
|
| 13339 |
helpers.each(me.ticks, function(label, index) { |
| 13340 |
// Don't draw a centre value (if it is minimum) |
| 13341 |
if (index > 0 || tickOpts.reverse) { |
| 13342 |
var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); |
| 13343 |
|
| 13344 |
// Draw circular lines around the scale |
| 13345 |
if (gridLineOpts.display && index !== 0) { |
| 13346 |
drawRadiusLine(me, gridLineOpts, yCenterOffset, index); |
| 13347 |
} |
| 13348 |
|
| 13349 |
if (tickOpts.display) { |
| 13350 |
var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); |
| 13351 |
ctx.font = tickLabelFont; |
| 13352 |
|
| 13353 |
ctx.save(); |
| 13354 |
ctx.translate(me.xCenter, me.yCenter); |
| 13355 |
ctx.rotate(startAngle); |
| 13356 |
|
| 13357 |
if (tickOpts.showLabelBackdrop) { |
| 13358 |
var labelWidth = ctx.measureText(label).width; |
| 13359 |
ctx.fillStyle = tickOpts.backdropColor; |
| 13360 |
ctx.fillRect( |
| 13361 |
-labelWidth / 2 - tickOpts.backdropPaddingX, |
| 13362 |
-yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY, |
| 13363 |
labelWidth + tickOpts.backdropPaddingX * 2, |
| 13364 |
tickFontSize + tickOpts.backdropPaddingY * 2 |
| 13365 |
); |
| 13366 |
} |
| 13367 |
|
| 13368 |
ctx.textAlign = 'center'; |
| 13369 |
ctx.textBaseline = 'middle'; |
| 13370 |
ctx.fillStyle = tickFontColor; |
| 13371 |
ctx.fillText(label, 0, -yCenterOffset); |
| 13372 |
ctx.restore(); |
| 13373 |
} |
| 13374 |
} |
| 13375 |
}); |
| 13376 |
|
| 13377 |
if (opts.angleLines.display || opts.pointLabels.display) { |
| 13378 |
drawPointLabels(me); |
| 13379 |
} |
| 13380 |
} |
| 13381 |
} |
| 13382 |
}); |
| 13383 |
Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig); |
| 13384 |
|
| 13385 |
}; |
| 13386 |
|
| 13387 |
},{"25":25,"34":34,"45":45}],57:[function(require,module,exports){ |
| 13388 |
/* global window: false */ |
| 13389 |
'use strict'; |
| 13390 |
|
| 13391 |
var moment = require(1); |
| 13392 |
moment = typeof moment === 'function' ? moment : window.moment; |
| 13393 |
|
| 13394 |
var defaults = require(25); |
| 13395 |
var helpers = require(45); |
| 13396 |
|
| 13397 |
// Integer constants are from the ES6 spec. |
| 13398 |
var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; |
| 13399 |
var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; |
| 13400 |
|
| 13401 |
var INTERVALS = { |
| 13402 |
millisecond: { |
| 13403 |
common: true, |
| 13404 |
size: 1, |
| 13405 |
steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] |
| 13406 |
}, |
| 13407 |
second: { |
| 13408 |
common: true, |
| 13409 |
size: 1000, |
| 13410 |
steps: [1, 2, 5, 10, 30] |
| 13411 |
}, |
| 13412 |
minute: { |
| 13413 |
common: true, |
| 13414 |
size: 60000, |
| 13415 |
steps: [1, 2, 5, 10, 30] |
| 13416 |
}, |
| 13417 |
hour: { |
| 13418 |
common: true, |
| 13419 |
size: 3600000, |
| 13420 |
steps: [1, 2, 3, 6, 12] |
| 13421 |
}, |
| 13422 |
day: { |
| 13423 |
common: true, |
| 13424 |
size: 86400000, |
| 13425 |
steps: [1, 2, 5] |
| 13426 |
}, |
| 13427 |
week: { |
| 13428 |
common: false, |
| 13429 |
size: 604800000, |
| 13430 |
steps: [1, 2, 3, 4] |
| 13431 |
}, |
| 13432 |
month: { |
| 13433 |
common: true, |
| 13434 |
size: 2.628e9, |
| 13435 |
steps: [1, 2, 3] |
| 13436 |
}, |
| 13437 |
quarter: { |
| 13438 |
common: false, |
| 13439 |
size: 7.884e9, |
| 13440 |
steps: [1, 2, 3, 4] |
| 13441 |
}, |
| 13442 |
year: { |
| 13443 |
common: true, |
| 13444 |
size: 3.154e10 |
| 13445 |
} |
| 13446 |
}; |
| 13447 |
|
| 13448 |
var UNITS = Object.keys(INTERVALS); |
| 13449 |
|
| 13450 |
function sorter(a, b) { |
| 13451 |
return a - b; |
| 13452 |
} |
| 13453 |
|
| 13454 |
function arrayUnique(items) { |
| 13455 |
var hash = {}; |
| 13456 |
var out = []; |
| 13457 |
var i, ilen, item; |
| 13458 |
|
| 13459 |
for (i = 0, ilen = items.length; i < ilen; ++i) { |
| 13460 |
item = items[i]; |
| 13461 |
if (!hash[item]) { |
| 13462 |
hash[item] = true; |
| 13463 |
out.push(item); |
| 13464 |
} |
| 13465 |
} |
| 13466 |
|
| 13467 |
return out; |
| 13468 |
} |
| 13469 |
|
| 13470 |
/** |
| 13471 |
* Returns an array of {time, pos} objects used to interpolate a specific `time` or position |
| 13472 |
* (`pos`) on the scale, by searching entries before and after the requested value. `pos` is |
| 13473 |
* a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other |
| 13474 |
* extremity (left + width or top + height). Note that it would be more optimized to directly |
| 13475 |
* store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need |
| 13476 |
* to create the lookup table. The table ALWAYS contains at least two items: min and max. |
| 13477 |
* |
| 13478 |
* @param {Number[]} timestamps - timestamps sorted from lowest to highest. |
| 13479 |
* @param {String} distribution - If 'linear', timestamps will be spread linearly along the min |
| 13480 |
* and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. |
| 13481 |
* If 'series', timestamps will be positioned at the same distance from each other. In this |
| 13482 |
* case, only timestamps that break the time linearity are registered, meaning that in the |
| 13483 |
* best case, all timestamps are linear, the table contains only min and max. |
| 13484 |
*/ |
| 13485 |
function buildLookupTable(timestamps, min, max, distribution) { |
| 13486 |
if (distribution === 'linear' || !timestamps.length) { |
| 13487 |
return [ |
| 13488 |
{time: min, pos: 0}, |
| 13489 |
{time: max, pos: 1} |
| 13490 |
]; |
| 13491 |
} |
| 13492 |
|
| 13493 |
var table = []; |
| 13494 |
var items = [min]; |
| 13495 |
var i, ilen, prev, curr, next; |
| 13496 |
|
| 13497 |
for (i = 0, ilen = timestamps.length; i < ilen; ++i) { |
| 13498 |
curr = timestamps[i]; |
| 13499 |
if (curr > min && curr < max) { |
| 13500 |
items.push(curr); |
| 13501 |
} |
| 13502 |
} |
| 13503 |
|
| 13504 |
items.push(max); |
| 13505 |
|
| 13506 |
for (i = 0, ilen = items.length; i < ilen; ++i) { |
| 13507 |
next = items[i + 1]; |
| 13508 |
prev = items[i - 1]; |
| 13509 |
curr = items[i]; |
| 13510 |
|
| 13511 |
// only add points that breaks the scale linearity |
| 13512 |
if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { |
| 13513 |
table.push({time: curr, pos: i / (ilen - 1)}); |
| 13514 |
} |
| 13515 |
} |
| 13516 |
|
| 13517 |
return table; |
| 13518 |
} |
| 13519 |
|
| 13520 |
// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ |
| 13521 |
function lookup(table, key, value) { |
| 13522 |
var lo = 0; |
| 13523 |
var hi = table.length - 1; |
| 13524 |
var mid, i0, i1; |
| 13525 |
|
| 13526 |
while (lo >= 0 && lo <= hi) { |
| 13527 |
mid = (lo + hi) >> 1; |
| 13528 |
i0 = table[mid - 1] || null; |
| 13529 |
i1 = table[mid]; |
| 13530 |
|
| 13531 |
if (!i0) { |
| 13532 |
// given value is outside table (before first item) |
| 13533 |
return {lo: null, hi: i1}; |
| 13534 |
} else if (i1[key] < value) { |
| 13535 |
lo = mid + 1; |
| 13536 |
} else if (i0[key] > value) { |
| 13537 |
hi = mid - 1; |
| 13538 |
} else { |
| 13539 |
return {lo: i0, hi: i1}; |
| 13540 |
} |
| 13541 |
} |
| 13542 |
|
| 13543 |
// given value is outside table (after last item) |
| 13544 |
return {lo: i1, hi: null}; |
| 13545 |
} |
| 13546 |
|
| 13547 |
/** |
| 13548 |
* Linearly interpolates the given source `value` using the table items `skey` values and |
| 13549 |
* returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') |
| 13550 |
* returns the position for a timestamp equal to 42. If value is out of bounds, values at |
| 13551 |
* index [0, 1] or [n - 1, n] are used for the interpolation. |
| 13552 |
*/ |
| 13553 |
function interpolate(table, skey, sval, tkey) { |
| 13554 |
var range = lookup(table, skey, sval); |
| 13555 |
|
| 13556 |
// Note: the lookup table ALWAYS contains at least 2 items (min and max) |
| 13557 |
var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; |
| 13558 |
var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; |
| 13559 |
|
| 13560 |
var span = next[skey] - prev[skey]; |
| 13561 |
var ratio = span ? (sval - prev[skey]) / span : 0; |
| 13562 |
var offset = (next[tkey] - prev[tkey]) * ratio; |
| 13563 |
|
| 13564 |
return prev[tkey] + offset; |
| 13565 |
} |
| 13566 |
|
| 13567 |
/** |
| 13568 |
* Convert the given value to a moment object using the given time options. |
| 13569 |
* @see http://momentjs.com/docs/#/parsing/ |
| 13570 |
*/ |
| 13571 |
function momentify(value, options) { |
| 13572 |
var parser = options.parser; |
| 13573 |
var format = options.parser || options.format; |
| 13574 |
|
| 13575 |
if (typeof parser === 'function') { |
| 13576 |
return parser(value); |
| 13577 |
} |
| 13578 |
|
| 13579 |
if (typeof value === 'string' && typeof format === 'string') { |
| 13580 |
return moment(value, format); |
| 13581 |
} |
| 13582 |
|
| 13583 |
if (!(value instanceof moment)) { |
| 13584 |
value = moment(value); |
| 13585 |
} |
| 13586 |
|
| 13587 |
if (value.isValid()) { |
| 13588 |
return value; |
| 13589 |
} |
| 13590 |
|
| 13591 |
// Labels are in an incompatible moment format and no `parser` has been provided. |
| 13592 |
// The user might still use the deprecated `format` option to convert his inputs. |
| 13593 |
if (typeof format === 'function') { |
| 13594 |
return format(value); |
| 13595 |
} |
| 13596 |
|
| 13597 |
return value; |
| 13598 |
} |
| 13599 |
|
| 13600 |
function parse(input, scale) { |
| 13601 |
if (helpers.isNullOrUndef(input)) { |
| 13602 |
return null; |
| 13603 |
} |
| 13604 |
|
| 13605 |
var options = scale.options.time; |
| 13606 |
var value = momentify(scale.getRightValue(input), options); |
| 13607 |
if (!value.isValid()) { |
| 13608 |
return null; |
| 13609 |
} |
| 13610 |
|
| 13611 |
if (options.round) { |
| 13612 |
value.startOf(options.round); |
| 13613 |
} |
| 13614 |
|
| 13615 |
return value.valueOf(); |
| 13616 |
} |
| 13617 |
|
| 13618 |
/** |
| 13619 |
* Returns the number of unit to skip to be able to display up to `capacity` number of ticks |
| 13620 |
* in `unit` for the given `min` / `max` range and respecting the interval steps constraints. |
| 13621 |
*/ |
| 13622 |
function determineStepSize(min, max, unit, capacity) { |
| 13623 |
var range = max - min; |
| 13624 |
var interval = INTERVALS[unit]; |
| 13625 |
var milliseconds = interval.size; |
| 13626 |
var steps = interval.steps; |
| 13627 |
var i, ilen, factor; |
| 13628 |
|
| 13629 |
if (!steps) { |
| 13630 |
return Math.ceil(range / ((capacity || 1) * milliseconds)); |
| 13631 |
} |
| 13632 |
|
| 13633 |
for (i = 0, ilen = steps.length; i < ilen; ++i) { |
| 13634 |
factor = steps[i]; |
| 13635 |
if (Math.ceil(range / (milliseconds * factor)) <= capacity) { |
| 13636 |
break; |
| 13637 |
} |
| 13638 |
} |
| 13639 |
|
| 13640 |
return factor; |
| 13641 |
} |
| 13642 |
|
| 13643 |
/** |
| 13644 |
* Figures out what unit results in an appropriate number of auto-generated ticks |
| 13645 |
*/ |
| 13646 |
function determineUnitForAutoTicks(minUnit, min, max, capacity) { |
| 13647 |
var ilen = UNITS.length; |
| 13648 |
var i, interval, factor; |
| 13649 |
|
| 13650 |
for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { |
| 13651 |
interval = INTERVALS[UNITS[i]]; |
| 13652 |
factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER; |
| 13653 |
|
| 13654 |
if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { |
| 13655 |
return UNITS[i]; |
| 13656 |
} |
| 13657 |
} |
| 13658 |
|
| 13659 |
return UNITS[ilen - 1]; |
| 13660 |
} |
| 13661 |
|
| 13662 |
/** |
| 13663 |
* Figures out what unit to format a set of ticks with |
| 13664 |
*/ |
| 13665 |
function determineUnitForFormatting(ticks, minUnit, min, max) { |
| 13666 |
var duration = moment.duration(moment(max).diff(moment(min))); |
| 13667 |
var ilen = UNITS.length; |
| 13668 |
var i, unit; |
| 13669 |
|
| 13670 |
for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) { |
| 13671 |
unit = UNITS[i]; |
| 13672 |
if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) { |
| 13673 |
return unit; |
| 13674 |
} |
| 13675 |
} |
| 13676 |
|
| 13677 |
return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; |
| 13678 |
} |
| 13679 |
|
| 13680 |
function determineMajorUnit(unit) { |
| 13681 |
for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { |
| 13682 |
if (INTERVALS[UNITS[i]].common) { |
| 13683 |
return UNITS[i]; |
| 13684 |
} |
| 13685 |
} |
| 13686 |
} |
| 13687 |
|
| 13688 |
/** |
| 13689 |
* Generates a maximum of `capacity` timestamps between min and max, rounded to the |
| 13690 |
* `minor` unit, aligned on the `major` unit and using the given scale time `options`. |
| 13691 |
* Important: this method can return ticks outside the min and max range, it's the |
| 13692 |
* responsibility of the calling code to clamp values if needed. |
| 13693 |
*/ |
| 13694 |
function generate(min, max, capacity, options) { |
| 13695 |
var timeOpts = options.time; |
| 13696 |
var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); |
| 13697 |
var major = determineMajorUnit(minor); |
| 13698 |
var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize); |
| 13699 |
var weekday = minor === 'week' ? timeOpts.isoWeekday : false; |
| 13700 |
var majorTicksEnabled = options.ticks.major.enabled; |
| 13701 |
var interval = INTERVALS[minor]; |
| 13702 |
var first = moment(min); |
| 13703 |
var last = moment(max); |
| 13704 |
var ticks = []; |
| 13705 |
var time; |
| 13706 |
|
| 13707 |
if (!stepSize) { |
| 13708 |
stepSize = determineStepSize(min, max, minor, capacity); |
| 13709 |
} |
| 13710 |
|
| 13711 |
// For 'week' unit, handle the first day of week option |
| 13712 |
if (weekday) { |
| 13713 |
first = first.isoWeekday(weekday); |
| 13714 |
last = last.isoWeekday(weekday); |
| 13715 |
} |
| 13716 |
|
| 13717 |
// Align first/last ticks on unit |
| 13718 |
first = first.startOf(weekday ? 'day' : minor); |
| 13719 |
last = last.startOf(weekday ? 'day' : minor); |
| 13720 |
|
| 13721 |
// Make sure that the last tick include max |
| 13722 |
if (last < max) { |
| 13723 |
last.add(1, minor); |
| 13724 |
} |
| 13725 |
|
| 13726 |
time = moment(first); |
| 13727 |
|
| 13728 |
if (majorTicksEnabled && major && !weekday && !timeOpts.round) { |
| 13729 |
// Align the first tick on the previous `minor` unit aligned on the `major` unit: |
| 13730 |
// we first aligned time on the previous `major` unit then add the number of full |
| 13731 |
// stepSize there is between first and the previous major time. |
| 13732 |
time.startOf(major); |
| 13733 |
time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor); |
| 13734 |
} |
| 13735 |
|
| 13736 |
for (; time < last; time.add(stepSize, minor)) { |
| 13737 |
ticks.push(+time); |
| 13738 |
} |
| 13739 |
|
| 13740 |
ticks.push(+time); |
| 13741 |
|
| 13742 |
return ticks; |
| 13743 |
} |
| 13744 |
|
| 13745 |
/** |
| 13746 |
* Returns the right and left offsets from edges in the form of {left, right}. |
| 13747 |
* Offsets are added when the `offset` option is true. |
| 13748 |
*/ |
| 13749 |
function computeOffsets(table, ticks, min, max, options) { |
| 13750 |
var left = 0; |
| 13751 |
var right = 0; |
| 13752 |
var upper, lower; |
| 13753 |
|
| 13754 |
if (options.offset && ticks.length) { |
| 13755 |
if (!options.time.min) { |
| 13756 |
upper = ticks.length > 1 ? ticks[1] : max; |
| 13757 |
lower = ticks[0]; |
| 13758 |
left = ( |
| 13759 |
interpolate(table, 'time', upper, 'pos') - |
| 13760 |
interpolate(table, 'time', lower, 'pos') |
| 13761 |
) / 2; |
| 13762 |
} |
| 13763 |
if (!options.time.max) { |
| 13764 |
upper = ticks[ticks.length - 1]; |
| 13765 |
lower = ticks.length > 1 ? ticks[ticks.length - 2] : min; |
| 13766 |
right = ( |
| 13767 |
interpolate(table, 'time', upper, 'pos') - |
| 13768 |
interpolate(table, 'time', lower, 'pos') |
| 13769 |
) / 2; |
| 13770 |
} |
| 13771 |
} |
| 13772 |
|
| 13773 |
return {left: left, right: right}; |
| 13774 |
} |
| 13775 |
|
| 13776 |
function ticksFromTimestamps(values, majorUnit) { |
| 13777 |
var ticks = []; |
| 13778 |
var i, ilen, value, major; |
| 13779 |
|
| 13780 |
for (i = 0, ilen = values.length; i < ilen; ++i) { |
| 13781 |
value = values[i]; |
| 13782 |
major = majorUnit ? value === +moment(value).startOf(majorUnit) : false; |
| 13783 |
|
| 13784 |
ticks.push({ |
| 13785 |
value: value, |
| 13786 |
major: major |
| 13787 |
}); |
| 13788 |
} |
| 13789 |
|
| 13790 |
return ticks; |
| 13791 |
} |
| 13792 |
|
| 13793 |
module.exports = function(Chart) { |
| 13794 |
|
| 13795 |
var defaultConfig = { |
| 13796 |
position: 'bottom', |
| 13797 |
|
| 13798 |
/** |
| 13799 |
* Data distribution along the scale: |
| 13800 |
* - 'linear': data are spread according to their time (distances can vary), |
| 13801 |
* - 'series': data are spread at the same distance from each other. |
| 13802 |
* @see https://github.com/chartjs/Chart.js/pull/4507 |
| 13803 |
* @since 2.7.0 |
| 13804 |
*/ |
| 13805 |
distribution: 'linear', |
| 13806 |
|
| 13807 |
/** |
| 13808 |
* Scale boundary strategy (bypassed by min/max time options) |
| 13809 |
* - `data`: make sure data are fully visible, ticks outside are removed |
| 13810 |
* - `ticks`: make sure ticks are fully visible, data outside are truncated |
| 13811 |
* @see https://github.com/chartjs/Chart.js/pull/4556 |
| 13812 |
* @since 2.7.0 |
| 13813 |
*/ |
| 13814 |
bounds: 'data', |
| 13815 |
|
| 13816 |
time: { |
| 13817 |
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 |
| 13818 |
format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ |
| 13819 |
unit: false, // false == automatic or override with week, month, year, etc. |
| 13820 |
round: false, // none, or override with week, month, year, etc. |
| 13821 |
displayFormat: false, // DEPRECATED |
| 13822 |
isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ |
| 13823 |
minUnit: 'millisecond', |
| 13824 |
|
| 13825 |
// defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ |
| 13826 |
displayFormats: { |
| 13827 |
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, |
| 13828 |
second: 'h:mm:ss a', // 11:20:01 AM |
| 13829 |
minute: 'h:mm a', // 11:20 AM |
| 13830 |
hour: 'hA', // 5PM |
| 13831 |
day: 'MMM D', // Sep 4 |
| 13832 |
week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? |
| 13833 |
month: 'MMM YYYY', // Sept 2015 |
| 13834 |
quarter: '[Q]Q - YYYY', // Q3 |
| 13835 |
year: 'YYYY' // 2015 |
| 13836 |
}, |
| 13837 |
}, |
| 13838 |
ticks: { |
| 13839 |
autoSkip: false, |
| 13840 |
|
| 13841 |
/** |
| 13842 |
* Ticks generation input values: |
| 13843 |
* - 'auto': generates "optimal" ticks based on scale size and time options. |
| 13844 |
* - 'data': generates ticks from data (including labels from data {t|x|y} objects). |
| 13845 |
* - 'labels': generates ticks from user given `data.labels` values ONLY. |
| 13846 |
* @see https://github.com/chartjs/Chart.js/pull/4507 |
| 13847 |
* @since 2.7.0 |
| 13848 |
*/ |
| 13849 |
source: 'auto', |
| 13850 |
|
| 13851 |
major: { |
| 13852 |
enabled: false |
| 13853 |
} |
| 13854 |
} |
| 13855 |
}; |
| 13856 |
|
| 13857 |
var TimeScale = Chart.Scale.extend({ |
| 13858 |
initialize: function() { |
| 13859 |
if (!moment) { |
| 13860 |
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'); |
| 13861 |
} |
| 13862 |
|
| 13863 |
this.mergeTicksOptions(); |
| 13864 |
|
| 13865 |
Chart.Scale.prototype.initialize.call(this); |
| 13866 |
}, |
| 13867 |
|
| 13868 |
update: function() { |
| 13869 |
var me = this; |
| 13870 |
var options = me.options; |
| 13871 |
|
| 13872 |
// DEPRECATIONS: output a message only one time per update |
| 13873 |
if (options.time && options.time.format) { |
| 13874 |
console.warn('options.time.format is deprecated and replaced by options.time.parser.'); |
| 13875 |
} |
| 13876 |
|
| 13877 |
return Chart.Scale.prototype.update.apply(me, arguments); |
| 13878 |
}, |
| 13879 |
|
| 13880 |
/** |
| 13881 |
* Allows data to be referenced via 't' attribute |
| 13882 |
*/ |
| 13883 |
getRightValue: function(rawValue) { |
| 13884 |
if (rawValue && rawValue.t !== undefined) { |
| 13885 |
rawValue = rawValue.t; |
| 13886 |
} |
| 13887 |
return Chart.Scale.prototype.getRightValue.call(this, rawValue); |
| 13888 |
}, |
| 13889 |
|
| 13890 |
determineDataLimits: function() { |
| 13891 |
var me = this; |
| 13892 |
var chart = me.chart; |
| 13893 |
var timeOpts = me.options.time; |
| 13894 |
var min = MAX_INTEGER; |
| 13895 |
var max = MIN_INTEGER; |
| 13896 |
var timestamps = []; |
| 13897 |
var datasets = []; |
| 13898 |
var labels = []; |
| 13899 |
var i, j, ilen, jlen, data, timestamp; |
| 13900 |
|
| 13901 |
// Convert labels to timestamps |
| 13902 |
for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) { |
| 13903 |
labels.push(parse(chart.data.labels[i], me)); |
| 13904 |
} |
| 13905 |
|
| 13906 |
// Convert data to timestamps |
| 13907 |
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { |
| 13908 |
if (chart.isDatasetVisible(i)) { |
| 13909 |
data = chart.data.datasets[i].data; |
| 13910 |
|
| 13911 |
// Let's consider that all data have the same format. |
| 13912 |
if (helpers.isObject(data[0])) { |
| 13913 |
datasets[i] = []; |
| 13914 |
|
| 13915 |
for (j = 0, jlen = data.length; j < jlen; ++j) { |
| 13916 |
timestamp = parse(data[j], me); |
| 13917 |
timestamps.push(timestamp); |
| 13918 |
datasets[i][j] = timestamp; |
| 13919 |
} |
| 13920 |
} else { |
| 13921 |
timestamps.push.apply(timestamps, labels); |
| 13922 |
datasets[i] = labels.slice(0); |
| 13923 |
} |
| 13924 |
} else { |
| 13925 |
datasets[i] = []; |
| 13926 |
} |
| 13927 |
} |
| 13928 |
|
| 13929 |
if (labels.length) { |
| 13930 |
// Sort labels **after** data have been converted |
| 13931 |
labels = arrayUnique(labels).sort(sorter); |
| 13932 |
min = Math.min(min, labels[0]); |
| 13933 |
max = Math.max(max, labels[labels.length - 1]); |
| 13934 |
} |
| 13935 |
|
| 13936 |
if (timestamps.length) { |
| 13937 |
timestamps = arrayUnique(timestamps).sort(sorter); |
| 13938 |
min = Math.min(min, timestamps[0]); |
| 13939 |
max = Math.max(max, timestamps[timestamps.length - 1]); |
| 13940 |
} |
| 13941 |
|
| 13942 |
min = parse(timeOpts.min, me) || min; |
| 13943 |
max = parse(timeOpts.max, me) || max; |
| 13944 |
|
| 13945 |
// In case there is no valid min/max, let's use today limits |
| 13946 |
min = min === MAX_INTEGER ? +moment().startOf('day') : min; |
| 13947 |
max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max; |
| 13948 |
|
| 13949 |
// Make sure that max is strictly higher than min (required by the lookup table) |
| 13950 |
me.min = Math.min(min, max); |
| 13951 |
me.max = Math.max(min + 1, max); |
| 13952 |
|
| 13953 |
// PRIVATE |
| 13954 |
me._horizontal = me.isHorizontal(); |
| 13955 |
me._table = []; |
| 13956 |
me._timestamps = { |
| 13957 |
data: timestamps, |
| 13958 |
datasets: datasets, |
| 13959 |
labels: labels |
| 13960 |
}; |
| 13961 |
}, |
| 13962 |
|
| 13963 |
buildTicks: function() { |
| 13964 |
var me = this; |
| 13965 |
var min = me.min; |
| 13966 |
var max = me.max; |
| 13967 |
var options = me.options; |
| 13968 |
var timeOpts = options.time; |
| 13969 |
var timestamps = []; |
| 13970 |
var ticks = []; |
| 13971 |
var i, ilen, timestamp; |
| 13972 |
|
| 13973 |
switch (options.ticks.source) { |
| 13974 |
case 'data': |
| 13975 |
timestamps = me._timestamps.data; |
| 13976 |
break; |
| 13977 |
case 'labels': |
| 13978 |
timestamps = me._timestamps.labels; |
| 13979 |
break; |
| 13980 |
case 'auto': |
| 13981 |
default: |
| 13982 |
timestamps = generate(min, max, me.getLabelCapacity(min), options); |
| 13983 |
} |
| 13984 |
|
| 13985 |
if (options.bounds === 'ticks' && timestamps.length) { |
| 13986 |
min = timestamps[0]; |
| 13987 |
max = timestamps[timestamps.length - 1]; |
| 13988 |
} |
| 13989 |
|
| 13990 |
// Enforce limits with user min/max options |
| 13991 |
min = parse(timeOpts.min, me) || min; |
| 13992 |
max = parse(timeOpts.max, me) || max; |
| 13993 |
|
| 13994 |
// Remove ticks outside the min/max range |
| 13995 |
for (i = 0, ilen = timestamps.length; i < ilen; ++i) { |
| 13996 |
timestamp = timestamps[i]; |
| 13997 |
if (timestamp >= min && timestamp <= max) { |
| 13998 |
ticks.push(timestamp); |
| 13999 |
} |
| 14000 |
} |
| 14001 |
|
| 14002 |
me.min = min; |
| 14003 |
me.max = max; |
| 14004 |
|
| 14005 |
// PRIVATE |
| 14006 |
me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); |
| 14007 |
me._majorUnit = determineMajorUnit(me._unit); |
| 14008 |
me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); |
| 14009 |
me._offsets = computeOffsets(me._table, ticks, min, max, options); |
| 14010 |
|
| 14011 |
return ticksFromTimestamps(ticks, me._majorUnit); |
| 14012 |
}, |
| 14013 |
|
| 14014 |
getLabelForIndex: function(index, datasetIndex) { |
| 14015 |
var me = this; |
| 14016 |
var data = me.chart.data; |
| 14017 |
var timeOpts = me.options.time; |
| 14018 |
var label = data.labels && index < data.labels.length ? data.labels[index] : ''; |
| 14019 |
var value = data.datasets[datasetIndex].data[index]; |
| 14020 |
|
| 14021 |
if (helpers.isObject(value)) { |
| 14022 |
label = me.getRightValue(value); |
| 14023 |
} |
| 14024 |
if (timeOpts.tooltipFormat) { |
| 14025 |
label = momentify(label, timeOpts).format(timeOpts.tooltipFormat); |
| 14026 |
} |
| 14027 |
|
| 14028 |
return label; |
| 14029 |
}, |
| 14030 |
|
| 14031 |
/** |
| 14032 |
* Function to format an individual tick mark |
| 14033 |
* @private |
| 14034 |
*/ |
| 14035 |
tickFormatFunction: function(tick, index, ticks, formatOverride) { |
| 14036 |
var me = this; |
| 14037 |
var options = me.options; |
| 14038 |
var time = tick.valueOf(); |
| 14039 |
var formats = options.time.displayFormats; |
| 14040 |
var minorFormat = formats[me._unit]; |
| 14041 |
var majorUnit = me._majorUnit; |
| 14042 |
var majorFormat = formats[majorUnit]; |
| 14043 |
var majorTime = tick.clone().startOf(majorUnit).valueOf(); |
| 14044 |
var majorTickOpts = options.ticks.major; |
| 14045 |
var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; |
| 14046 |
var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); |
| 14047 |
var tickOpts = major ? majorTickOpts : options.ticks.minor; |
| 14048 |
var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); |
| 14049 |
|
| 14050 |
return formatter ? formatter(label, index, ticks) : label; |
| 14051 |
}, |
| 14052 |
|
| 14053 |
convertTicksToLabels: function(ticks) { |
| 14054 |
var labels = []; |
| 14055 |
var i, ilen; |
| 14056 |
|
| 14057 |
for (i = 0, ilen = ticks.length; i < ilen; ++i) { |
| 14058 |
labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks)); |
| 14059 |
} |
| 14060 |
|
| 14061 |
return labels; |
| 14062 |
}, |
| 14063 |
|
| 14064 |
/** |
| 14065 |
* @private |
| 14066 |
*/ |
| 14067 |
getPixelForOffset: function(time) { |
| 14068 |
var me = this; |
| 14069 |
var size = me._horizontal ? me.width : me.height; |
| 14070 |
var start = me._horizontal ? me.left : me.top; |
| 14071 |
var pos = interpolate(me._table, 'time', time, 'pos'); |
| 14072 |
|
| 14073 |
return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right); |
| 14074 |
}, |
| 14075 |
|
| 14076 |
getPixelForValue: function(value, index, datasetIndex) { |
| 14077 |
var me = this; |
| 14078 |
var time = null; |
| 14079 |
|
| 14080 |
if (index !== undefined && datasetIndex !== undefined) { |
| 14081 |
time = me._timestamps.datasets[datasetIndex][index]; |
| 14082 |
} |
| 14083 |
|
| 14084 |
if (time === null) { |
| 14085 |
time = parse(value, me); |
| 14086 |
} |
| 14087 |
|
| 14088 |
if (time !== null) { |
| 14089 |
return me.getPixelForOffset(time); |
| 14090 |
} |
| 14091 |
}, |
| 14092 |
|
| 14093 |
getPixelForTick: function(index) { |
| 14094 |
var ticks = this.getTicks(); |
| 14095 |
return index >= 0 && index < ticks.length ? |
| 14096 |
this.getPixelForOffset(ticks[index].value) : |
| 14097 |
null; |
| 14098 |
}, |
| 14099 |
|
| 14100 |
getValueForPixel: function(pixel) { |
| 14101 |
var me = this; |
| 14102 |
var size = me._horizontal ? me.width : me.height; |
| 14103 |
var start = me._horizontal ? me.left : me.top; |
| 14104 |
var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right; |
| 14105 |
var time = interpolate(me._table, 'pos', pos, 'time'); |
| 14106 |
|
| 14107 |
return moment(time); |
| 14108 |
}, |
| 14109 |
|
| 14110 |
/** |
| 14111 |
* Crude approximation of what the label width might be |
| 14112 |
* @private |
| 14113 |
*/ |
| 14114 |
getLabelWidth: function(label) { |
| 14115 |
var me = this; |
| 14116 |
var ticksOpts = me.options.ticks; |
| 14117 |
var tickLabelWidth = me.ctx.measureText(label).width; |
| 14118 |
var angle = helpers.toRadians(ticksOpts.maxRotation); |
| 14119 |
var cosRotation = Math.cos(angle); |
| 14120 |
var sinRotation = Math.sin(angle); |
| 14121 |
var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); |
| 14122 |
|
| 14123 |
return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); |
| 14124 |
}, |
| 14125 |
|
| 14126 |
/** |
| 14127 |
* @private |
| 14128 |
*/ |
| 14129 |
getLabelCapacity: function(exampleTime) { |
| 14130 |
var me = this; |
| 14131 |
|
| 14132 |
var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation |
| 14133 |
|
| 14134 |
var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride); |
| 14135 |
var tickLabelWidth = me.getLabelWidth(exampleLabel); |
| 14136 |
var innerWidth = me.isHorizontal() ? me.width : me.height; |
| 14137 |
|
| 14138 |
return Math.floor(innerWidth / tickLabelWidth); |
| 14139 |
} |
| 14140 |
}); |
| 14141 |
|
| 14142 |
Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig); |
| 14143 |
}; |
| 14144 |
|
| 14145 |
},{"1":1,"25":25,"45":45}]},{},[7])(7) |
| 14146 |
}); |
| 14147 |
|