Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1 | /*! |
| 2 | * Chart.js |
| 3 | * http://chartjs.org/ |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4 | * Version: 2.2.1 |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 5 | * |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 6 | * Copyright 2016 Nick Downie |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 7 | * Released under the MIT license |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 8 | * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 9 | */ |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 10 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 11 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 12 | },{}],2:[function(require,module,exports){ |
| 13 | /* MIT license */
|
| 14 | var colorNames = require(6);
|
| 15 |
|
| 16 | module.exports = {
|
| 17 | getRgba: getRgba,
|
| 18 | getHsla: getHsla,
|
| 19 | getRgb: getRgb,
|
| 20 | getHsl: getHsl,
|
| 21 | getHwb: getHwb,
|
| 22 | getAlpha: getAlpha,
|
| 23 |
|
| 24 | hexString: hexString,
|
| 25 | rgbString: rgbString,
|
| 26 | rgbaString: rgbaString,
|
| 27 | percentString: percentString,
|
| 28 | percentaString: percentaString,
|
| 29 | hslString: hslString,
|
| 30 | hslaString: hslaString,
|
| 31 | hwbString: hwbString,
|
| 32 | keyword: keyword
|
| 33 | }
|
| 34 |
|
| 35 | function getRgba(string) {
|
| 36 | if (!string) {
|
| 37 | return;
|
| 38 | }
|
| 39 | var abbr = /^#([a-fA-F0-9]{3})$/,
|
| 40 | hex = /^#([a-fA-F0-9]{6})$/,
|
| 41 | rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
|
| 42 | per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
|
| 43 | keyword = /(\w+)/;
|
| 44 |
|
| 45 | var rgb = [0, 0, 0],
|
| 46 | a = 1,
|
| 47 | match = string.match(abbr);
|
| 48 | if (match) {
|
| 49 | match = match[1];
|
| 50 | for (var i = 0; i < rgb.length; i++) {
|
| 51 | rgb[i] = parseInt(match[i] + match[i], 16);
|
| 52 | }
|
| 53 | }
|
| 54 | else if (match = string.match(hex)) {
|
| 55 | match = match[1];
|
| 56 | for (var i = 0; i < rgb.length; i++) {
|
| 57 | rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
|
| 58 | }
|
| 59 | }
|
| 60 | else if (match = string.match(rgba)) {
|
| 61 | for (var i = 0; i < rgb.length; i++) {
|
| 62 | rgb[i] = parseInt(match[i + 1]);
|
| 63 | }
|
| 64 | a = parseFloat(match[4]);
|
| 65 | }
|
| 66 | else if (match = string.match(per)) {
|
| 67 | for (var i = 0; i < rgb.length; i++) {
|
| 68 | rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
|
| 69 | }
|
| 70 | a = parseFloat(match[4]);
|
| 71 | }
|
| 72 | else if (match = string.match(keyword)) {
|
| 73 | if (match[1] == "transparent") {
|
| 74 | return [0, 0, 0, 0];
|
| 75 | }
|
| 76 | rgb = colorNames[match[1]];
|
| 77 | if (!rgb) {
|
| 78 | return;
|
| 79 | }
|
| 80 | }
|
| 81 |
|
| 82 | for (var i = 0; i < rgb.length; i++) {
|
| 83 | rgb[i] = scale(rgb[i], 0, 255);
|
| 84 | }
|
| 85 | if (!a && a != 0) {
|
| 86 | a = 1;
|
| 87 | }
|
| 88 | else {
|
| 89 | a = scale(a, 0, 1);
|
| 90 | }
|
| 91 | rgb[3] = a;
|
| 92 | return rgb;
|
| 93 | }
|
| 94 |
|
| 95 | function getHsla(string) {
|
| 96 | if (!string) {
|
| 97 | return;
|
| 98 | }
|
| 99 | var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
|
| 100 | var match = string.match(hsl);
|
| 101 | if (match) {
|
| 102 | var alpha = parseFloat(match[4]);
|
| 103 | var h = scale(parseInt(match[1]), 0, 360),
|
| 104 | s = scale(parseFloat(match[2]), 0, 100),
|
| 105 | l = scale(parseFloat(match[3]), 0, 100),
|
| 106 | a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
|
| 107 | return [h, s, l, a];
|
| 108 | }
|
| 109 | }
|
| 110 |
|
| 111 | function getHwb(string) {
|
| 112 | if (!string) {
|
| 113 | return;
|
| 114 | }
|
| 115 | var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
|
| 116 | var match = string.match(hwb);
|
| 117 | if (match) {
|
| 118 | var alpha = parseFloat(match[4]);
|
| 119 | var h = scale(parseInt(match[1]), 0, 360),
|
| 120 | w = scale(parseFloat(match[2]), 0, 100),
|
| 121 | b = scale(parseFloat(match[3]), 0, 100),
|
| 122 | a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
|
| 123 | return [h, w, b, a];
|
| 124 | }
|
| 125 | }
|
| 126 |
|
| 127 | function getRgb(string) {
|
| 128 | var rgba = getRgba(string);
|
| 129 | return rgba && rgba.slice(0, 3);
|
| 130 | }
|
| 131 |
|
| 132 | function getHsl(string) {
|
| 133 | var hsla = getHsla(string);
|
| 134 | return hsla && hsla.slice(0, 3);
|
| 135 | }
|
| 136 |
|
| 137 | function getAlpha(string) {
|
| 138 | var vals = getRgba(string);
|
| 139 | if (vals) {
|
| 140 | return vals[3];
|
| 141 | }
|
| 142 | else if (vals = getHsla(string)) {
|
| 143 | return vals[3];
|
| 144 | }
|
| 145 | else if (vals = getHwb(string)) {
|
| 146 | return vals[3];
|
| 147 | }
|
| 148 | }
|
| 149 |
|
| 150 | // generators
|
| 151 | function hexString(rgb) {
|
| 152 | return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
|
| 153 | + hexDouble(rgb[2]);
|
| 154 | }
|
| 155 |
|
| 156 | function rgbString(rgba, alpha) {
|
| 157 | if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
|
| 158 | return rgbaString(rgba, alpha);
|
| 159 | }
|
| 160 | return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
|
| 161 | }
|
| 162 |
|
| 163 | function rgbaString(rgba, alpha) {
|
| 164 | if (alpha === undefined) {
|
| 165 | alpha = (rgba[3] !== undefined ? rgba[3] : 1);
|
| 166 | }
|
| 167 | return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
|
| 168 | + ", " + alpha + ")";
|
| 169 | }
|
| 170 |
|
| 171 | function percentString(rgba, alpha) {
|
| 172 | if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
|
| 173 | return percentaString(rgba, alpha);
|
| 174 | }
|
| 175 | var r = Math.round(rgba[0]/255 * 100),
|
| 176 | g = Math.round(rgba[1]/255 * 100),
|
| 177 | b = Math.round(rgba[2]/255 * 100);
|
| 178 |
|
| 179 | return "rgb(" + r + "%, " + g + "%, " + b + "%)";
|
| 180 | }
|
| 181 |
|
| 182 | function percentaString(rgba, alpha) {
|
| 183 | var r = Math.round(rgba[0]/255 * 100),
|
| 184 | g = Math.round(rgba[1]/255 * 100),
|
| 185 | b = Math.round(rgba[2]/255 * 100);
|
| 186 | return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
|
| 187 | }
|
| 188 |
|
| 189 | function hslString(hsla, alpha) {
|
| 190 | if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
|
| 191 | return hslaString(hsla, alpha);
|
| 192 | }
|
| 193 | return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
|
| 194 | }
|
| 195 |
|
| 196 | function hslaString(hsla, alpha) {
|
| 197 | if (alpha === undefined) {
|
| 198 | alpha = (hsla[3] !== undefined ? hsla[3] : 1);
|
| 199 | }
|
| 200 | return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
|
| 201 | + alpha + ")";
|
| 202 | }
|
| 203 |
|
| 204 | // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
|
| 205 | // (hwb have alpha optional & 1 is default value)
|
| 206 | function hwbString(hwb, alpha) {
|
| 207 | if (alpha === undefined) {
|
| 208 | alpha = (hwb[3] !== undefined ? hwb[3] : 1);
|
| 209 | }
|
| 210 | return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
|
| 211 | + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
|
| 212 | }
|
| 213 |
|
| 214 | function keyword(rgb) {
|
| 215 | return reverseNames[rgb.slice(0, 3)];
|
| 216 | }
|
| 217 |
|
| 218 | // helpers
|
| 219 | function scale(num, min, max) {
|
| 220 | return Math.min(Math.max(min, num), max);
|
| 221 | }
|
| 222 |
|
| 223 | function hexDouble(num) {
|
| 224 | var str = num.toString(16).toUpperCase();
|
| 225 | return (str.length < 2) ? "0" + str : str;
|
| 226 | }
|
| 227 |
|
| 228 |
|
| 229 | //create a list of reverse color names
|
| 230 | var reverseNames = {};
|
| 231 | for (var name in colorNames) {
|
| 232 | reverseNames[colorNames[name]] = name;
|
| 233 | }
|
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 234 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 235 | },{"6":6}],3:[function(require,module,exports){ |
| 236 | /* MIT license */
|
| 237 | var convert = require(5);
|
| 238 | var string = require(2);
|
| 239 |
|
| 240 | var Color = function (obj) {
|
| 241 | if (obj instanceof Color) {
|
| 242 | return obj;
|
| 243 | }
|
| 244 | if (!(this instanceof Color)) {
|
| 245 | return new Color(obj);
|
| 246 | }
|
| 247 |
|
| 248 | this.values = {
|
| 249 | rgb: [0, 0, 0],
|
| 250 | hsl: [0, 0, 0],
|
| 251 | hsv: [0, 0, 0],
|
| 252 | hwb: [0, 0, 0],
|
| 253 | cmyk: [0, 0, 0, 0],
|
| 254 | alpha: 1
|
| 255 | };
|
| 256 |
|
| 257 | // parse Color() argument
|
| 258 | var vals;
|
| 259 | if (typeof obj === 'string') {
|
| 260 | vals = string.getRgba(obj);
|
| 261 | if (vals) {
|
| 262 | this.setValues('rgb', vals);
|
| 263 | } else if (vals = string.getHsla(obj)) {
|
| 264 | this.setValues('hsl', vals);
|
| 265 | } else if (vals = string.getHwb(obj)) {
|
| 266 | this.setValues('hwb', vals);
|
| 267 | } else {
|
| 268 | throw new Error('Unable to parse color from string "' + obj + '"');
|
| 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 | } else {
|
| 283 | throw new Error('Unable to parse color from object ' + JSON.stringify(obj));
|
| 284 | }
|
| 285 | }
|
| 286 | };
|
| 287 |
|
| 288 | Color.prototype = {
|
| 289 | rgb: function () {
|
| 290 | return this.setSpace('rgb', arguments);
|
| 291 | },
|
| 292 | hsl: function () {
|
| 293 | return this.setSpace('hsl', arguments);
|
| 294 | },
|
| 295 | hsv: function () {
|
| 296 | return this.setSpace('hsv', arguments);
|
| 297 | },
|
| 298 | hwb: function () {
|
| 299 | return this.setSpace('hwb', arguments);
|
| 300 | },
|
| 301 | cmyk: function () {
|
| 302 | return this.setSpace('cmyk', arguments);
|
| 303 | },
|
| 304 |
|
| 305 | rgbArray: function () {
|
| 306 | return this.values.rgb;
|
| 307 | },
|
| 308 | hslArray: function () {
|
| 309 | return this.values.hsl;
|
| 310 | },
|
| 311 | hsvArray: function () {
|
| 312 | return this.values.hsv;
|
| 313 | },
|
| 314 | hwbArray: function () {
|
| 315 | var values = this.values;
|
| 316 | if (values.alpha !== 1) {
|
| 317 | return values.hwb.concat([values.alpha]);
|
| 318 | }
|
| 319 | return values.hwb;
|
| 320 | },
|
| 321 | cmykArray: function () {
|
| 322 | return this.values.cmyk;
|
| 323 | },
|
| 324 | rgbaArray: function () {
|
| 325 | var values = this.values;
|
| 326 | return values.rgb.concat([values.alpha]);
|
| 327 | },
|
| 328 | hslaArray: function () {
|
| 329 | var values = this.values;
|
| 330 | return values.hsl.concat([values.alpha]);
|
| 331 | },
|
| 332 | alpha: function (val) {
|
| 333 | if (val === undefined) {
|
| 334 | return this.values.alpha;
|
| 335 | }
|
| 336 | this.setValues('alpha', val);
|
| 337 | return this;
|
| 338 | },
|
| 339 |
|
| 340 | red: function (val) {
|
| 341 | return this.setChannel('rgb', 0, val);
|
| 342 | },
|
| 343 | green: function (val) {
|
| 344 | return this.setChannel('rgb', 1, val);
|
| 345 | },
|
| 346 | blue: function (val) {
|
| 347 | return this.setChannel('rgb', 2, val);
|
| 348 | },
|
| 349 | hue: function (val) {
|
| 350 | if (val) {
|
| 351 | val %= 360;
|
| 352 | val = val < 0 ? 360 + val : val;
|
| 353 | }
|
| 354 | return this.setChannel('hsl', 0, val);
|
| 355 | },
|
| 356 | saturation: function (val) {
|
| 357 | return this.setChannel('hsl', 1, val);
|
| 358 | },
|
| 359 | lightness: function (val) {
|
| 360 | return this.setChannel('hsl', 2, val);
|
| 361 | },
|
| 362 | saturationv: function (val) {
|
| 363 | return this.setChannel('hsv', 1, val);
|
| 364 | },
|
| 365 | whiteness: function (val) {
|
| 366 | return this.setChannel('hwb', 1, val);
|
| 367 | },
|
| 368 | blackness: function (val) {
|
| 369 | return this.setChannel('hwb', 2, val);
|
| 370 | },
|
| 371 | value: function (val) {
|
| 372 | return this.setChannel('hsv', 2, val);
|
| 373 | },
|
| 374 | cyan: function (val) {
|
| 375 | return this.setChannel('cmyk', 0, val);
|
| 376 | },
|
| 377 | magenta: function (val) {
|
| 378 | return this.setChannel('cmyk', 1, val);
|
| 379 | },
|
| 380 | yellow: function (val) {
|
| 381 | return this.setChannel('cmyk', 2, val);
|
| 382 | },
|
| 383 | black: function (val) {
|
| 384 | return this.setChannel('cmyk', 3, val);
|
| 385 | },
|
| 386 |
|
| 387 | hexString: function () {
|
| 388 | return string.hexString(this.values.rgb);
|
| 389 | },
|
| 390 | rgbString: function () {
|
| 391 | return string.rgbString(this.values.rgb, this.values.alpha);
|
| 392 | },
|
| 393 | rgbaString: function () {
|
| 394 | return string.rgbaString(this.values.rgb, this.values.alpha);
|
| 395 | },
|
| 396 | percentString: function () {
|
| 397 | return string.percentString(this.values.rgb, this.values.alpha);
|
| 398 | },
|
| 399 | hslString: function () {
|
| 400 | return string.hslString(this.values.hsl, this.values.alpha);
|
| 401 | },
|
| 402 | hslaString: function () {
|
| 403 | return string.hslaString(this.values.hsl, this.values.alpha);
|
| 404 | },
|
| 405 | hwbString: function () {
|
| 406 | return string.hwbString(this.values.hwb, this.values.alpha);
|
| 407 | },
|
| 408 | keyword: function () {
|
| 409 | return string.keyword(this.values.rgb, this.values.alpha);
|
| 410 | },
|
| 411 |
|
| 412 | rgbNumber: function () {
|
| 413 | var rgb = this.values.rgb;
|
| 414 | return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
| 415 | },
|
| 416 |
|
| 417 | luminosity: function () {
|
| 418 | // http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
| 419 | var rgb = this.values.rgb;
|
| 420 | var lum = [];
|
| 421 | for (var i = 0; i < rgb.length; i++) {
|
| 422 | var chan = rgb[i] / 255;
|
| 423 | lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
|
| 424 | }
|
| 425 | return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
|
| 426 | },
|
| 427 |
|
| 428 | contrast: function (color2) {
|
| 429 | // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
| 430 | var lum1 = this.luminosity();
|
| 431 | var lum2 = color2.luminosity();
|
| 432 | if (lum1 > lum2) {
|
| 433 | return (lum1 + 0.05) / (lum2 + 0.05);
|
| 434 | }
|
| 435 | return (lum2 + 0.05) / (lum1 + 0.05);
|
| 436 | },
|
| 437 |
|
| 438 | level: function (color2) {
|
| 439 | var contrastRatio = this.contrast(color2);
|
| 440 | if (contrastRatio >= 7.1) {
|
| 441 | return 'AAA';
|
| 442 | }
|
| 443 |
|
| 444 | return (contrastRatio >= 4.5) ? 'AA' : '';
|
| 445 | },
|
| 446 |
|
| 447 | dark: function () {
|
| 448 | // YIQ equation from http://24ways.org/2010/calculating-color-contrast
|
| 449 | var rgb = this.values.rgb;
|
| 450 | var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
|
| 451 | return yiq < 128;
|
| 452 | },
|
| 453 |
|
| 454 | light: function () {
|
| 455 | return !this.dark();
|
| 456 | },
|
| 457 |
|
| 458 | negate: function () {
|
| 459 | var rgb = [];
|
| 460 | for (var i = 0; i < 3; i++) {
|
| 461 | rgb[i] = 255 - this.values.rgb[i];
|
| 462 | }
|
| 463 | this.setValues('rgb', rgb);
|
| 464 | return this;
|
| 465 | },
|
| 466 |
|
| 467 | lighten: function (ratio) {
|
| 468 | var hsl = this.values.hsl;
|
| 469 | hsl[2] += hsl[2] * ratio;
|
| 470 | this.setValues('hsl', hsl);
|
| 471 | return this;
|
| 472 | },
|
| 473 |
|
| 474 | darken: function (ratio) {
|
| 475 | var hsl = this.values.hsl;
|
| 476 | hsl[2] -= hsl[2] * ratio;
|
| 477 | this.setValues('hsl', hsl);
|
| 478 | return this;
|
| 479 | },
|
| 480 |
|
| 481 | saturate: function (ratio) {
|
| 482 | var hsl = this.values.hsl;
|
| 483 | hsl[1] += hsl[1] * ratio;
|
| 484 | this.setValues('hsl', hsl);
|
| 485 | return this;
|
| 486 | },
|
| 487 |
|
| 488 | desaturate: function (ratio) {
|
| 489 | var hsl = this.values.hsl;
|
| 490 | hsl[1] -= hsl[1] * ratio;
|
| 491 | this.setValues('hsl', hsl);
|
| 492 | return this;
|
| 493 | },
|
| 494 |
|
| 495 | whiten: function (ratio) {
|
| 496 | var hwb = this.values.hwb;
|
| 497 | hwb[1] += hwb[1] * ratio;
|
| 498 | this.setValues('hwb', hwb);
|
| 499 | return this;
|
| 500 | },
|
| 501 |
|
| 502 | blacken: function (ratio) {
|
| 503 | var hwb = this.values.hwb;
|
| 504 | hwb[2] += hwb[2] * ratio;
|
| 505 | this.setValues('hwb', hwb);
|
| 506 | return this;
|
| 507 | },
|
| 508 |
|
| 509 | greyscale: function () {
|
| 510 | var rgb = this.values.rgb;
|
| 511 | // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
|
| 512 | var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
|
| 513 | this.setValues('rgb', [val, val, val]);
|
| 514 | return this;
|
| 515 | },
|
| 516 |
|
| 517 | clearer: function (ratio) {
|
| 518 | var alpha = this.values.alpha;
|
| 519 | this.setValues('alpha', alpha - (alpha * ratio));
|
| 520 | return this;
|
| 521 | },
|
| 522 |
|
| 523 | opaquer: function (ratio) {
|
| 524 | var alpha = this.values.alpha;
|
| 525 | this.setValues('alpha', alpha + (alpha * ratio));
|
| 526 | return this;
|
| 527 | },
|
| 528 |
|
| 529 | rotate: function (degrees) {
|
| 530 | var hsl = this.values.hsl;
|
| 531 | var hue = (hsl[0] + degrees) % 360;
|
| 532 | hsl[0] = hue < 0 ? 360 + hue : hue;
|
| 533 | this.setValues('hsl', hsl);
|
| 534 | return this;
|
| 535 | },
|
| 536 |
|
| 537 | /**
|
| 538 | * Ported from sass implementation in C
|
| 539 | * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
|
| 540 | */
|
| 541 | mix: function (mixinColor, weight) {
|
| 542 | var color1 = this;
|
| 543 | var color2 = mixinColor;
|
| 544 | var p = weight === undefined ? 0.5 : weight;
|
| 545 |
|
| 546 | var w = 2 * p - 1;
|
| 547 | var a = color1.alpha() - color2.alpha();
|
| 548 |
|
| 549 | var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
|
| 550 | var w2 = 1 - w1;
|
| 551 |
|
| 552 | return this
|
| 553 | .rgb(
|
| 554 | w1 * color1.red() + w2 * color2.red(),
|
| 555 | w1 * color1.green() + w2 * color2.green(),
|
| 556 | w1 * color1.blue() + w2 * color2.blue()
|
| 557 | )
|
| 558 | .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
|
| 559 | },
|
| 560 |
|
| 561 | toJSON: function () {
|
| 562 | return this.rgb();
|
| 563 | },
|
| 564 |
|
| 565 | clone: function () {
|
| 566 | // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
|
| 567 | // making the final build way to big to embed in Chart.js. So let's do it manually,
|
| 568 | // assuming that values to clone are 1 dimension arrays containing only numbers,
|
| 569 | // except 'alpha' which is a number.
|
| 570 | var result = new Color();
|
| 571 | var source = this.values;
|
| 572 | var target = result.values;
|
| 573 | var value, type;
|
| 574 |
|
| 575 | for (var prop in source) {
|
| 576 | if (source.hasOwnProperty(prop)) {
|
| 577 | value = source[prop];
|
| 578 | type = ({}).toString.call(value);
|
| 579 | if (type === '[object Array]') {
|
| 580 | target[prop] = value.slice(0);
|
| 581 | } else if (type === '[object Number]') {
|
| 582 | target[prop] = value;
|
| 583 | } else {
|
| 584 | console.error('unexpected color value:', value);
|
| 585 | }
|
| 586 | }
|
| 587 | }
|
| 588 |
|
| 589 | return result;
|
| 590 | }
|
| 591 | };
|
| 592 |
|
| 593 | Color.prototype.spaces = {
|
| 594 | rgb: ['red', 'green', 'blue'],
|
| 595 | hsl: ['hue', 'saturation', 'lightness'],
|
| 596 | hsv: ['hue', 'saturation', 'value'],
|
| 597 | hwb: ['hue', 'whiteness', 'blackness'],
|
| 598 | cmyk: ['cyan', 'magenta', 'yellow', 'black']
|
| 599 | };
|
| 600 |
|
| 601 | Color.prototype.maxes = {
|
| 602 | rgb: [255, 255, 255],
|
| 603 | hsl: [360, 100, 100],
|
| 604 | hsv: [360, 100, 100],
|
| 605 | hwb: [360, 100, 100],
|
| 606 | cmyk: [100, 100, 100, 100]
|
| 607 | };
|
| 608 |
|
| 609 | Color.prototype.getValues = function (space) {
|
| 610 | var values = this.values;
|
| 611 | var vals = {};
|
| 612 |
|
| 613 | for (var i = 0; i < space.length; i++) {
|
| 614 | vals[space.charAt(i)] = values[space][i];
|
| 615 | }
|
| 616 |
|
| 617 | if (values.alpha !== 1) {
|
| 618 | vals.a = values.alpha;
|
| 619 | }
|
| 620 |
|
| 621 | // {r: 255, g: 255, b: 255, a: 0.4}
|
| 622 | return vals;
|
| 623 | };
|
| 624 |
|
| 625 | Color.prototype.setValues = function (space, vals) {
|
| 626 | var values = this.values;
|
| 627 | var spaces = this.spaces;
|
| 628 | var maxes = this.maxes;
|
| 629 | var alpha = 1;
|
| 630 | var i;
|
| 631 |
|
| 632 | if (space === 'alpha') {
|
| 633 | alpha = vals;
|
| 634 | } else if (vals.length) {
|
| 635 | // [10, 10, 10]
|
| 636 | values[space] = vals.slice(0, space.length);
|
| 637 | alpha = vals[space.length];
|
| 638 | } else if (vals[space.charAt(0)] !== undefined) {
|
| 639 | // {r: 10, g: 10, b: 10}
|
| 640 | for (i = 0; i < space.length; i++) {
|
| 641 | values[space][i] = vals[space.charAt(i)];
|
| 642 | }
|
| 643 |
|
| 644 | alpha = vals.a;
|
| 645 | } else if (vals[spaces[space][0]] !== undefined) {
|
| 646 | // {red: 10, green: 10, blue: 10}
|
| 647 | var chans = spaces[space];
|
| 648 |
|
| 649 | for (i = 0; i < space.length; i++) {
|
| 650 | values[space][i] = vals[chans[i]];
|
| 651 | }
|
| 652 |
|
| 653 | alpha = vals.alpha;
|
| 654 | }
|
| 655 |
|
| 656 | values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
|
| 657 |
|
| 658 | if (space === 'alpha') {
|
| 659 | return false;
|
| 660 | }
|
| 661 |
|
| 662 | var capped;
|
| 663 |
|
| 664 | // cap values of the space prior converting all values
|
| 665 | for (i = 0; i < space.length; i++) {
|
| 666 | capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
|
| 667 | values[space][i] = Math.round(capped);
|
| 668 | }
|
| 669 |
|
| 670 | // convert to all the other color spaces
|
| 671 | for (var sname in spaces) {
|
| 672 | if (sname !== space) {
|
| 673 | values[sname] = convert[space][sname](values[space]);
|
| 674 | }
|
| 675 | }
|
| 676 |
|
| 677 | return true;
|
| 678 | };
|
| 679 |
|
| 680 | Color.prototype.setSpace = function (space, args) {
|
| 681 | var vals = args[0];
|
| 682 |
|
| 683 | if (vals === undefined) {
|
| 684 | // color.rgb()
|
| 685 | return this.getValues(space);
|
| 686 | }
|
| 687 |
|
| 688 | // color.rgb(10, 10, 10)
|
| 689 | if (typeof vals === 'number') {
|
| 690 | vals = Array.prototype.slice.call(args);
|
| 691 | }
|
| 692 |
|
| 693 | this.setValues(space, vals);
|
| 694 | return this;
|
| 695 | };
|
| 696 |
|
| 697 | Color.prototype.setChannel = function (space, index, val) {
|
| 698 | var svalues = this.values[space];
|
| 699 | if (val === undefined) {
|
| 700 | // color.red()
|
| 701 | return svalues[index];
|
| 702 | } else if (val === svalues[index]) {
|
| 703 | // color.red(color.red())
|
| 704 | return this;
|
| 705 | }
|
| 706 |
|
| 707 | // color.red(100)
|
| 708 | svalues[index] = val;
|
| 709 | this.setValues(space, svalues);
|
| 710 |
|
| 711 | return this;
|
| 712 | };
|
| 713 |
|
| 714 | if (typeof window !== 'undefined') {
|
| 715 | window.Color = Color;
|
| 716 | }
|
| 717 |
|
| 718 | module.exports = Color;
|
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 719 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 720 | },{"2":2,"5":5}],4:[function(require,module,exports){ |
| 721 | /* MIT license */ |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 722 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 723 | module.exports = { |
| 724 | rgb2hsl: rgb2hsl, |
| 725 | rgb2hsv: rgb2hsv, |
| 726 | rgb2hwb: rgb2hwb, |
| 727 | rgb2cmyk: rgb2cmyk, |
| 728 | rgb2keyword: rgb2keyword, |
| 729 | rgb2xyz: rgb2xyz, |
| 730 | rgb2lab: rgb2lab, |
| 731 | rgb2lch: rgb2lch, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 732 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 733 | hsl2rgb: hsl2rgb, |
| 734 | hsl2hsv: hsl2hsv, |
| 735 | hsl2hwb: hsl2hwb, |
| 736 | hsl2cmyk: hsl2cmyk, |
| 737 | hsl2keyword: hsl2keyword, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 738 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 739 | hsv2rgb: hsv2rgb, |
| 740 | hsv2hsl: hsv2hsl, |
| 741 | hsv2hwb: hsv2hwb, |
| 742 | hsv2cmyk: hsv2cmyk, |
| 743 | hsv2keyword: hsv2keyword, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 744 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 745 | hwb2rgb: hwb2rgb, |
| 746 | hwb2hsl: hwb2hsl, |
| 747 | hwb2hsv: hwb2hsv, |
| 748 | hwb2cmyk: hwb2cmyk, |
| 749 | hwb2keyword: hwb2keyword, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 750 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 751 | cmyk2rgb: cmyk2rgb, |
| 752 | cmyk2hsl: cmyk2hsl, |
| 753 | cmyk2hsv: cmyk2hsv, |
| 754 | cmyk2hwb: cmyk2hwb, |
| 755 | cmyk2keyword: cmyk2keyword, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 756 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 757 | keyword2rgb: keyword2rgb, |
| 758 | keyword2hsl: keyword2hsl, |
| 759 | keyword2hsv: keyword2hsv, |
| 760 | keyword2hwb: keyword2hwb, |
| 761 | keyword2cmyk: keyword2cmyk, |
| 762 | keyword2lab: keyword2lab, |
| 763 | keyword2xyz: keyword2xyz, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 764 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 765 | xyz2rgb: xyz2rgb, |
| 766 | xyz2lab: xyz2lab, |
| 767 | xyz2lch: xyz2lch, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 768 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 769 | lab2xyz: lab2xyz, |
| 770 | lab2rgb: lab2rgb, |
| 771 | lab2lch: lab2lch, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 772 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 773 | lch2lab: lch2lab, |
| 774 | lch2xyz: lch2xyz, |
| 775 | lch2rgb: lch2rgb |
| 776 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 777 | |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 778 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 779 | function rgb2hsl(rgb) { |
| 780 | var r = rgb[0]/255, |
| 781 | g = rgb[1]/255, |
| 782 | b = rgb[2]/255, |
| 783 | min = Math.min(r, g, b), |
| 784 | max = Math.max(r, g, b), |
| 785 | delta = max - min, |
| 786 | h, s, l; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 787 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 788 | if (max == min) |
| 789 | h = 0; |
| 790 | else if (r == max) |
| 791 | h = (g - b) / delta; |
| 792 | else if (g == max) |
| 793 | h = 2 + (b - r) / delta; |
| 794 | else if (b == max) |
| 795 | h = 4 + (r - g)/ delta; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 796 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 797 | h = Math.min(h * 60, 360); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 798 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 799 | if (h < 0) |
| 800 | h += 360; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 801 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 802 | l = (min + max) / 2; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 803 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 804 | if (max == min) |
| 805 | s = 0; |
| 806 | else if (l <= 0.5) |
| 807 | s = delta / (max + min); |
| 808 | else |
| 809 | s = delta / (2 - max - min); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 810 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 811 | return [h, s * 100, l * 100]; |
| 812 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 813 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 814 | function rgb2hsv(rgb) { |
| 815 | var r = rgb[0], |
| 816 | g = rgb[1], |
| 817 | b = rgb[2], |
| 818 | min = Math.min(r, g, b), |
| 819 | max = Math.max(r, g, b), |
| 820 | delta = max - min, |
| 821 | h, s, v; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 822 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 823 | if (max == 0) |
| 824 | s = 0; |
| 825 | else |
| 826 | s = (delta/max * 1000)/10; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 827 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 828 | if (max == min) |
| 829 | h = 0; |
| 830 | else if (r == max) |
| 831 | h = (g - b) / delta; |
| 832 | else if (g == max) |
| 833 | h = 2 + (b - r) / delta; |
| 834 | else if (b == max) |
| 835 | h = 4 + (r - g) / delta; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 836 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 837 | h = Math.min(h * 60, 360); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 838 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 839 | if (h < 0) |
| 840 | h += 360; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 841 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 842 | v = ((max / 255) * 1000) / 10; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 843 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 844 | return [h, s, v]; |
| 845 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 846 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 847 | function rgb2hwb(rgb) { |
| 848 | var r = rgb[0], |
| 849 | g = rgb[1], |
| 850 | b = rgb[2], |
| 851 | h = rgb2hsl(rgb)[0], |
| 852 | w = 1/255 * Math.min(r, Math.min(g, b)), |
| 853 | b = 1 - 1/255 * Math.max(r, Math.max(g, b)); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 854 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 855 | return [h, w * 100, b * 100]; |
| 856 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 857 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 858 | function rgb2cmyk(rgb) { |
| 859 | var r = rgb[0] / 255, |
| 860 | g = rgb[1] / 255, |
| 861 | b = rgb[2] / 255, |
| 862 | c, m, y, k; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 863 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 864 | k = Math.min(1 - r, 1 - g, 1 - b); |
| 865 | c = (1 - r - k) / (1 - k) || 0; |
| 866 | m = (1 - g - k) / (1 - k) || 0; |
| 867 | y = (1 - b - k) / (1 - k) || 0; |
| 868 | return [c * 100, m * 100, y * 100, k * 100]; |
| 869 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 870 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 871 | function rgb2keyword(rgb) { |
| 872 | return reverseKeywords[JSON.stringify(rgb)]; |
| 873 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 874 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 875 | function rgb2xyz(rgb) { |
| 876 | var r = rgb[0] / 255, |
| 877 | g = rgb[1] / 255, |
| 878 | b = rgb[2] / 255; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 879 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 880 | // assume sRGB |
| 881 | r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); |
| 882 | g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); |
| 883 | b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 884 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 885 | var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); |
| 886 | var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); |
| 887 | var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 888 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 889 | return [x * 100, y *100, z * 100]; |
| 890 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 891 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 892 | function rgb2lab(rgb) { |
| 893 | var xyz = rgb2xyz(rgb), |
| 894 | x = xyz[0], |
| 895 | y = xyz[1], |
| 896 | z = xyz[2], |
| 897 | l, a, b; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 898 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 899 | x /= 95.047; |
| 900 | y /= 100; |
| 901 | z /= 108.883; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 902 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 903 | x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); |
| 904 | y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); |
| 905 | z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 906 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 907 | l = (116 * y) - 16; |
| 908 | a = 500 * (x - y); |
| 909 | b = 200 * (y - z); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 910 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 911 | return [l, a, b]; |
| 912 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 913 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 914 | function rgb2lch(args) { |
| 915 | return lab2lch(rgb2lab(args)); |
| 916 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 917 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 918 | function hsl2rgb(hsl) { |
| 919 | var h = hsl[0] / 360, |
| 920 | s = hsl[1] / 100, |
| 921 | l = hsl[2] / 100, |
| 922 | t1, t2, t3, rgb, val; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 923 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 924 | if (s == 0) { |
| 925 | val = l * 255; |
| 926 | return [val, val, val]; |
| 927 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 928 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 929 | if (l < 0.5) |
| 930 | t2 = l * (1 + s); |
| 931 | else |
| 932 | t2 = l + s - l * s; |
| 933 | t1 = 2 * l - t2; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 934 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 935 | rgb = [0, 0, 0]; |
| 936 | for (var i = 0; i < 3; i++) { |
| 937 | t3 = h + 1 / 3 * - (i - 1); |
| 938 | t3 < 0 && t3++; |
| 939 | t3 > 1 && t3--; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 940 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 941 | if (6 * t3 < 1) |
| 942 | val = t1 + (t2 - t1) * 6 * t3; |
| 943 | else if (2 * t3 < 1) |
| 944 | val = t2; |
| 945 | else if (3 * t3 < 2) |
| 946 | val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; |
| 947 | else |
| 948 | val = t1; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 949 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 950 | rgb[i] = val * 255; |
| 951 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 952 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 953 | return rgb; |
| 954 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 955 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 956 | function hsl2hsv(hsl) { |
| 957 | var h = hsl[0], |
| 958 | s = hsl[1] / 100, |
| 959 | l = hsl[2] / 100, |
| 960 | sv, v; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 961 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 962 | if(l === 0) { |
| 963 | // no need to do calc on black |
| 964 | // also avoids divide by 0 error |
| 965 | return [0, 0, 0]; |
| 966 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 967 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 968 | l *= 2; |
| 969 | s *= (l <= 1) ? l : 2 - l; |
| 970 | v = (l + s) / 2; |
| 971 | sv = (2 * s) / (l + s); |
| 972 | return [h, sv * 100, v * 100]; |
| 973 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 974 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 975 | function hsl2hwb(args) { |
| 976 | return rgb2hwb(hsl2rgb(args)); |
| 977 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 978 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 979 | function hsl2cmyk(args) { |
| 980 | return rgb2cmyk(hsl2rgb(args)); |
| 981 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 982 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 983 | function hsl2keyword(args) { |
| 984 | return rgb2keyword(hsl2rgb(args)); |
| 985 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 986 | |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 987 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 988 | function hsv2rgb(hsv) { |
| 989 | var h = hsv[0] / 60, |
| 990 | s = hsv[1] / 100, |
| 991 | v = hsv[2] / 100, |
| 992 | hi = Math.floor(h) % 6; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 993 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 994 | var f = h - Math.floor(h), |
| 995 | p = 255 * v * (1 - s), |
| 996 | q = 255 * v * (1 - (s * f)), |
| 997 | t = 255 * v * (1 - (s * (1 - f))), |
| 998 | v = 255 * v; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 999 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1000 | switch(hi) { |
| 1001 | case 0: |
| 1002 | return [v, t, p]; |
| 1003 | case 1: |
| 1004 | return [q, v, p]; |
| 1005 | case 2: |
| 1006 | return [p, v, t]; |
| 1007 | case 3: |
| 1008 | return [p, q, v]; |
| 1009 | case 4: |
| 1010 | return [t, p, v]; |
| 1011 | case 5: |
| 1012 | return [v, p, q]; |
| 1013 | } |
| 1014 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1015 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1016 | function hsv2hsl(hsv) { |
| 1017 | var h = hsv[0], |
| 1018 | s = hsv[1] / 100, |
| 1019 | v = hsv[2] / 100, |
| 1020 | sl, l; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1021 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1022 | l = (2 - s) * v; |
| 1023 | sl = s * v; |
| 1024 | sl /= (l <= 1) ? l : 2 - l; |
| 1025 | sl = sl || 0; |
| 1026 | l /= 2; |
| 1027 | return [h, sl * 100, l * 100]; |
| 1028 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1029 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1030 | function hsv2hwb(args) { |
| 1031 | return rgb2hwb(hsv2rgb(args)) |
| 1032 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1033 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1034 | function hsv2cmyk(args) { |
| 1035 | return rgb2cmyk(hsv2rgb(args)); |
| 1036 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1037 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1038 | function hsv2keyword(args) { |
| 1039 | return rgb2keyword(hsv2rgb(args)); |
| 1040 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1041 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1042 | // http://dev.w3.org/csswg/css-color/#hwb-to-rgb |
| 1043 | function hwb2rgb(hwb) { |
| 1044 | var h = hwb[0] / 360, |
| 1045 | wh = hwb[1] / 100, |
| 1046 | bl = hwb[2] / 100, |
| 1047 | ratio = wh + bl, |
| 1048 | i, v, f, n; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1049 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1050 | // wh + bl cant be > 1 |
| 1051 | if (ratio > 1) { |
| 1052 | wh /= ratio; |
| 1053 | bl /= ratio; |
| 1054 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1055 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1056 | i = Math.floor(6 * h); |
| 1057 | v = 1 - bl; |
| 1058 | f = 6 * h - i; |
| 1059 | if ((i & 0x01) != 0) { |
| 1060 | f = 1 - f; |
| 1061 | } |
| 1062 | n = wh + f * (v - wh); // linear interpolation |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1063 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1064 | switch (i) { |
| 1065 | default: |
| 1066 | case 6: |
| 1067 | case 0: r = v; g = n; b = wh; break; |
| 1068 | case 1: r = n; g = v; b = wh; break; |
| 1069 | case 2: r = wh; g = v; b = n; break; |
| 1070 | case 3: r = wh; g = n; b = v; break; |
| 1071 | case 4: r = n; g = wh; b = v; break; |
| 1072 | case 5: r = v; g = wh; b = n; break; |
| 1073 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1074 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1075 | return [r * 255, g * 255, b * 255]; |
| 1076 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1077 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1078 | function hwb2hsl(args) { |
| 1079 | return rgb2hsl(hwb2rgb(args)); |
| 1080 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1081 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1082 | function hwb2hsv(args) { |
| 1083 | return rgb2hsv(hwb2rgb(args)); |
| 1084 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1085 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1086 | function hwb2cmyk(args) { |
| 1087 | return rgb2cmyk(hwb2rgb(args)); |
| 1088 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1089 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1090 | function hwb2keyword(args) { |
| 1091 | return rgb2keyword(hwb2rgb(args)); |
| 1092 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1093 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1094 | function cmyk2rgb(cmyk) { |
| 1095 | var c = cmyk[0] / 100, |
| 1096 | m = cmyk[1] / 100, |
| 1097 | y = cmyk[2] / 100, |
| 1098 | k = cmyk[3] / 100, |
| 1099 | r, g, b; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1100 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1101 | r = 1 - Math.min(1, c * (1 - k) + k); |
| 1102 | g = 1 - Math.min(1, m * (1 - k) + k); |
| 1103 | b = 1 - Math.min(1, y * (1 - k) + k); |
| 1104 | return [r * 255, g * 255, b * 255]; |
| 1105 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1106 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1107 | function cmyk2hsl(args) { |
| 1108 | return rgb2hsl(cmyk2rgb(args)); |
| 1109 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1110 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1111 | function cmyk2hsv(args) { |
| 1112 | return rgb2hsv(cmyk2rgb(args)); |
| 1113 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1114 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1115 | function cmyk2hwb(args) { |
| 1116 | return rgb2hwb(cmyk2rgb(args)); |
| 1117 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1118 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1119 | function cmyk2keyword(args) { |
| 1120 | return rgb2keyword(cmyk2rgb(args)); |
| 1121 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1122 | |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1123 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1124 | function xyz2rgb(xyz) { |
| 1125 | var x = xyz[0] / 100, |
| 1126 | y = xyz[1] / 100, |
| 1127 | z = xyz[2] / 100, |
| 1128 | r, g, b; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1129 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1130 | r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); |
| 1131 | g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); |
| 1132 | b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1133 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1134 | // assume sRGB |
| 1135 | r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) |
| 1136 | : r = (r * 12.92); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1137 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1138 | g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) |
| 1139 | : g = (g * 12.92); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1140 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1141 | b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) |
| 1142 | : b = (b * 12.92); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1143 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1144 | r = Math.min(Math.max(0, r), 1); |
| 1145 | g = Math.min(Math.max(0, g), 1); |
| 1146 | b = Math.min(Math.max(0, b), 1); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1147 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1148 | return [r * 255, g * 255, b * 255]; |
| 1149 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1150 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1151 | function xyz2lab(xyz) { |
| 1152 | var x = xyz[0], |
| 1153 | y = xyz[1], |
| 1154 | z = xyz[2], |
| 1155 | l, a, b; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1156 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1157 | x /= 95.047; |
| 1158 | y /= 100; |
| 1159 | z /= 108.883; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1160 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1161 | x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); |
| 1162 | y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); |
| 1163 | z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1164 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1165 | l = (116 * y) - 16; |
| 1166 | a = 500 * (x - y); |
| 1167 | b = 200 * (y - z); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1168 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1169 | return [l, a, b]; |
| 1170 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1171 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1172 | function xyz2lch(args) { |
| 1173 | return lab2lch(xyz2lab(args)); |
| 1174 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1175 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1176 | function lab2xyz(lab) { |
| 1177 | var l = lab[0], |
| 1178 | a = lab[1], |
| 1179 | b = lab[2], |
| 1180 | x, y, z, y2; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1181 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1182 | if (l <= 8) { |
| 1183 | y = (l * 100) / 903.3; |
| 1184 | y2 = (7.787 * (y / 100)) + (16 / 116); |
| 1185 | } else { |
| 1186 | y = 100 * Math.pow((l + 16) / 116, 3); |
| 1187 | y2 = Math.pow(y / 100, 1/3); |
| 1188 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1189 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1190 | x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1191 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1192 | z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1193 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1194 | return [x, y, z]; |
| 1195 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1196 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1197 | function lab2lch(lab) { |
| 1198 | var l = lab[0], |
| 1199 | a = lab[1], |
| 1200 | b = lab[2], |
| 1201 | hr, h, c; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1202 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1203 | hr = Math.atan2(b, a); |
| 1204 | h = hr * 360 / 2 / Math.PI; |
| 1205 | if (h < 0) { |
| 1206 | h += 360; |
| 1207 | } |
| 1208 | c = Math.sqrt(a * a + b * b); |
| 1209 | return [l, c, h]; |
| 1210 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1211 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1212 | function lab2rgb(args) { |
| 1213 | return xyz2rgb(lab2xyz(args)); |
| 1214 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1215 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1216 | function lch2lab(lch) { |
| 1217 | var l = lch[0], |
| 1218 | c = lch[1], |
| 1219 | h = lch[2], |
| 1220 | a, b, hr; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1221 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1222 | hr = h / 360 * 2 * Math.PI; |
| 1223 | a = c * Math.cos(hr); |
| 1224 | b = c * Math.sin(hr); |
| 1225 | return [l, a, b]; |
| 1226 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1227 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1228 | function lch2xyz(args) { |
| 1229 | return lab2xyz(lch2lab(args)); |
| 1230 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1231 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1232 | function lch2rgb(args) { |
| 1233 | return lab2rgb(lch2lab(args)); |
| 1234 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1235 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1236 | function keyword2rgb(keyword) { |
| 1237 | return cssKeywords[keyword]; |
| 1238 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1239 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1240 | function keyword2hsl(args) { |
| 1241 | return rgb2hsl(keyword2rgb(args)); |
| 1242 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1243 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1244 | function keyword2hsv(args) { |
| 1245 | return rgb2hsv(keyword2rgb(args)); |
| 1246 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1247 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1248 | function keyword2hwb(args) { |
| 1249 | return rgb2hwb(keyword2rgb(args)); |
| 1250 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1251 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1252 | function keyword2cmyk(args) { |
| 1253 | return rgb2cmyk(keyword2rgb(args)); |
| 1254 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1255 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1256 | function keyword2lab(args) { |
| 1257 | return rgb2lab(keyword2rgb(args)); |
| 1258 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1259 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1260 | function keyword2xyz(args) { |
| 1261 | return rgb2xyz(keyword2rgb(args)); |
| 1262 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1263 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1264 | var cssKeywords = { |
| 1265 | aliceblue: [240,248,255], |
| 1266 | antiquewhite: [250,235,215], |
| 1267 | aqua: [0,255,255], |
| 1268 | aquamarine: [127,255,212], |
| 1269 | azure: [240,255,255], |
| 1270 | beige: [245,245,220], |
| 1271 | bisque: [255,228,196], |
| 1272 | black: [0,0,0], |
| 1273 | blanchedalmond: [255,235,205], |
| 1274 | blue: [0,0,255], |
| 1275 | blueviolet: [138,43,226], |
| 1276 | brown: [165,42,42], |
| 1277 | burlywood: [222,184,135], |
| 1278 | cadetblue: [95,158,160], |
| 1279 | chartreuse: [127,255,0], |
| 1280 | chocolate: [210,105,30], |
| 1281 | coral: [255,127,80], |
| 1282 | cornflowerblue: [100,149,237], |
| 1283 | cornsilk: [255,248,220], |
| 1284 | crimson: [220,20,60], |
| 1285 | cyan: [0,255,255], |
| 1286 | darkblue: [0,0,139], |
| 1287 | darkcyan: [0,139,139], |
| 1288 | darkgoldenrod: [184,134,11], |
| 1289 | darkgray: [169,169,169], |
| 1290 | darkgreen: [0,100,0], |
| 1291 | darkgrey: [169,169,169], |
| 1292 | darkkhaki: [189,183,107], |
| 1293 | darkmagenta: [139,0,139], |
| 1294 | darkolivegreen: [85,107,47], |
| 1295 | darkorange: [255,140,0], |
| 1296 | darkorchid: [153,50,204], |
| 1297 | darkred: [139,0,0], |
| 1298 | darksalmon: [233,150,122], |
| 1299 | darkseagreen: [143,188,143], |
| 1300 | darkslateblue: [72,61,139], |
| 1301 | darkslategray: [47,79,79], |
| 1302 | darkslategrey: [47,79,79], |
| 1303 | darkturquoise: [0,206,209], |
| 1304 | darkviolet: [148,0,211], |
| 1305 | deeppink: [255,20,147], |
| 1306 | deepskyblue: [0,191,255], |
| 1307 | dimgray: [105,105,105], |
| 1308 | dimgrey: [105,105,105], |
| 1309 | dodgerblue: [30,144,255], |
| 1310 | firebrick: [178,34,34], |
| 1311 | floralwhite: [255,250,240], |
| 1312 | forestgreen: [34,139,34], |
| 1313 | fuchsia: [255,0,255], |
| 1314 | gainsboro: [220,220,220], |
| 1315 | ghostwhite: [248,248,255], |
| 1316 | gold: [255,215,0], |
| 1317 | goldenrod: [218,165,32], |
| 1318 | gray: [128,128,128], |
| 1319 | green: [0,128,0], |
| 1320 | greenyellow: [173,255,47], |
| 1321 | grey: [128,128,128], |
| 1322 | honeydew: [240,255,240], |
| 1323 | hotpink: [255,105,180], |
| 1324 | indianred: [205,92,92], |
| 1325 | indigo: [75,0,130], |
| 1326 | ivory: [255,255,240], |
| 1327 | khaki: [240,230,140], |
| 1328 | lavender: [230,230,250], |
| 1329 | lavenderblush: [255,240,245], |
| 1330 | lawngreen: [124,252,0], |
| 1331 | lemonchiffon: [255,250,205], |
| 1332 | lightblue: [173,216,230], |
| 1333 | lightcoral: [240,128,128], |
| 1334 | lightcyan: [224,255,255], |
| 1335 | lightgoldenrodyellow: [250,250,210], |
| 1336 | lightgray: [211,211,211], |
| 1337 | lightgreen: [144,238,144], |
| 1338 | lightgrey: [211,211,211], |
| 1339 | lightpink: [255,182,193], |
| 1340 | lightsalmon: [255,160,122], |
| 1341 | lightseagreen: [32,178,170], |
| 1342 | lightskyblue: [135,206,250], |
| 1343 | lightslategray: [119,136,153], |
| 1344 | lightslategrey: [119,136,153], |
| 1345 | lightsteelblue: [176,196,222], |
| 1346 | lightyellow: [255,255,224], |
| 1347 | lime: [0,255,0], |
| 1348 | limegreen: [50,205,50], |
| 1349 | linen: [250,240,230], |
| 1350 | magenta: [255,0,255], |
| 1351 | maroon: [128,0,0], |
| 1352 | mediumaquamarine: [102,205,170], |
| 1353 | mediumblue: [0,0,205], |
| 1354 | mediumorchid: [186,85,211], |
| 1355 | mediumpurple: [147,112,219], |
| 1356 | mediumseagreen: [60,179,113], |
| 1357 | mediumslateblue: [123,104,238], |
| 1358 | mediumspringgreen: [0,250,154], |
| 1359 | mediumturquoise: [72,209,204], |
| 1360 | mediumvioletred: [199,21,133], |
| 1361 | midnightblue: [25,25,112], |
| 1362 | mintcream: [245,255,250], |
| 1363 | mistyrose: [255,228,225], |
| 1364 | moccasin: [255,228,181], |
| 1365 | navajowhite: [255,222,173], |
| 1366 | navy: [0,0,128], |
| 1367 | oldlace: [253,245,230], |
| 1368 | olive: [128,128,0], |
| 1369 | olivedrab: [107,142,35], |
| 1370 | orange: [255,165,0], |
| 1371 | orangered: [255,69,0], |
| 1372 | orchid: [218,112,214], |
| 1373 | palegoldenrod: [238,232,170], |
| 1374 | palegreen: [152,251,152], |
| 1375 | paleturquoise: [175,238,238], |
| 1376 | palevioletred: [219,112,147], |
| 1377 | papayawhip: [255,239,213], |
| 1378 | peachpuff: [255,218,185], |
| 1379 | peru: [205,133,63], |
| 1380 | pink: [255,192,203], |
| 1381 | plum: [221,160,221], |
| 1382 | powderblue: [176,224,230], |
| 1383 | purple: [128,0,128], |
| 1384 | rebeccapurple: [102, 51, 153], |
| 1385 | red: [255,0,0], |
| 1386 | rosybrown: [188,143,143], |
| 1387 | royalblue: [65,105,225], |
| 1388 | saddlebrown: [139,69,19], |
| 1389 | salmon: [250,128,114], |
| 1390 | sandybrown: [244,164,96], |
| 1391 | seagreen: [46,139,87], |
| 1392 | seashell: [255,245,238], |
| 1393 | sienna: [160,82,45], |
| 1394 | silver: [192,192,192], |
| 1395 | skyblue: [135,206,235], |
| 1396 | slateblue: [106,90,205], |
| 1397 | slategray: [112,128,144], |
| 1398 | slategrey: [112,128,144], |
| 1399 | snow: [255,250,250], |
| 1400 | springgreen: [0,255,127], |
| 1401 | steelblue: [70,130,180], |
| 1402 | tan: [210,180,140], |
| 1403 | teal: [0,128,128], |
| 1404 | thistle: [216,191,216], |
| 1405 | tomato: [255,99,71], |
| 1406 | turquoise: [64,224,208], |
| 1407 | violet: [238,130,238], |
| 1408 | wheat: [245,222,179], |
| 1409 | white: [255,255,255], |
| 1410 | whitesmoke: [245,245,245], |
| 1411 | yellow: [255,255,0], |
| 1412 | yellowgreen: [154,205,50] |
| 1413 | }; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1414 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1415 | var reverseKeywords = {}; |
| 1416 | for (var key in cssKeywords) { |
| 1417 | reverseKeywords[JSON.stringify(cssKeywords[key])] = key; |
| 1418 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1419 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1420 | },{}],5:[function(require,module,exports){ |
| 1421 | var conversions = require(4); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1422 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1423 | var convert = function() { |
| 1424 | return new Converter(); |
| 1425 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1426 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1427 | for (var func in conversions) { |
| 1428 | // export Raw versions |
| 1429 | convert[func + "Raw"] = (function(func) { |
| 1430 | // accept array or plain args |
| 1431 | return function(arg) { |
| 1432 | if (typeof arg == "number") |
| 1433 | arg = Array.prototype.slice.call(arguments); |
| 1434 | return conversions[func](arg); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1435 | } |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1436 | })(func); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1437 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1438 | var pair = /(\w+)2(\w+)/.exec(func), |
| 1439 | from = pair[1], |
| 1440 | to = pair[2]; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1441 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1442 | // export rgb2hsl and ["rgb"]["hsl"] |
| 1443 | convert[from] = convert[from] || {}; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1444 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1445 | convert[from][to] = convert[func] = (function(func) { |
| 1446 | return function(arg) { |
| 1447 | if (typeof arg == "number") |
| 1448 | arg = Array.prototype.slice.call(arguments); |
| 1449 | |
| 1450 | var val = conversions[func](arg); |
| 1451 | if (typeof val == "string" || val === undefined) |
| 1452 | return val; // keyword |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1453 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1454 | for (var i = 0; i < val.length; i++) |
| 1455 | val[i] = Math.round(val[i]); |
| 1456 | return val; |
| 1457 | } |
| 1458 | })(func); |
| 1459 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1460 | |
| 1461 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1462 | /* Converter does lazy conversion and caching */ |
| 1463 | var Converter = function() { |
| 1464 | this.convs = {}; |
| 1465 | }; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1466 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1467 | /* Either get the values for a space or |
| 1468 | set the values for a space, depending on args */ |
| 1469 | Converter.prototype.routeSpace = function(space, args) { |
| 1470 | var values = args[0]; |
| 1471 | if (values === undefined) { |
| 1472 | // color.rgb() |
| 1473 | return this.getValues(space); |
| 1474 | } |
| 1475 | // color.rgb(10, 10, 10) |
| 1476 | if (typeof values == "number") { |
| 1477 | values = Array.prototype.slice.call(args); |
| 1478 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1479 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1480 | return this.setValues(space, values); |
| 1481 | }; |
| 1482 | |
| 1483 | /* Set the values for a space, invalidating cache */ |
| 1484 | Converter.prototype.setValues = function(space, values) { |
| 1485 | this.space = space; |
| 1486 | this.convs = {}; |
| 1487 | this.convs[space] = values; |
| 1488 | return this; |
| 1489 | }; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1490 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1491 | /* Get the values for a space. If there's already |
| 1492 | a conversion for the space, fetch it, otherwise |
| 1493 | compute it */ |
| 1494 | Converter.prototype.getValues = function(space) { |
| 1495 | var vals = this.convs[space]; |
| 1496 | if (!vals) { |
| 1497 | var fspace = this.space, |
| 1498 | from = this.convs[fspace]; |
| 1499 | vals = convert[fspace][space](from); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1500 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1501 | this.convs[space] = vals; |
| 1502 | } |
| 1503 | return vals; |
| 1504 | }; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1505 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1506 | ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { |
| 1507 | Converter.prototype[space] = function(vals) { |
| 1508 | return this.routeSpace(space, arguments); |
| 1509 | } |
| 1510 | }); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1511 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1512 | module.exports = convert; |
| 1513 | },{"4":4}],6:[function(require,module,exports){ |
| 1514 | module.exports = {
|
| 1515 | "aliceblue": [240, 248, 255],
|
| 1516 | "antiquewhite": [250, 235, 215],
|
| 1517 | "aqua": [0, 255, 255],
|
| 1518 | "aquamarine": [127, 255, 212],
|
| 1519 | "azure": [240, 255, 255],
|
| 1520 | "beige": [245, 245, 220],
|
| 1521 | "bisque": [255, 228, 196],
|
| 1522 | "black": [0, 0, 0],
|
| 1523 | "blanchedalmond": [255, 235, 205],
|
| 1524 | "blue": [0, 0, 255],
|
| 1525 | "blueviolet": [138, 43, 226],
|
| 1526 | "brown": [165, 42, 42],
|
| 1527 | "burlywood": [222, 184, 135],
|
| 1528 | "cadetblue": [95, 158, 160],
|
| 1529 | "chartreuse": [127, 255, 0],
|
| 1530 | "chocolate": [210, 105, 30],
|
| 1531 | "coral": [255, 127, 80],
|
| 1532 | "cornflowerblue": [100, 149, 237],
|
| 1533 | "cornsilk": [255, 248, 220],
|
| 1534 | "crimson": [220, 20, 60],
|
| 1535 | "cyan": [0, 255, 255],
|
| 1536 | "darkblue": [0, 0, 139],
|
| 1537 | "darkcyan": [0, 139, 139],
|
| 1538 | "darkgoldenrod": [184, 134, 11],
|
| 1539 | "darkgray": [169, 169, 169],
|
| 1540 | "darkgreen": [0, 100, 0],
|
| 1541 | "darkgrey": [169, 169, 169],
|
| 1542 | "darkkhaki": [189, 183, 107],
|
| 1543 | "darkmagenta": [139, 0, 139],
|
| 1544 | "darkolivegreen": [85, 107, 47],
|
| 1545 | "darkorange": [255, 140, 0],
|
| 1546 | "darkorchid": [153, 50, 204],
|
| 1547 | "darkred": [139, 0, 0],
|
| 1548 | "darksalmon": [233, 150, 122],
|
| 1549 | "darkseagreen": [143, 188, 143],
|
| 1550 | "darkslateblue": [72, 61, 139],
|
| 1551 | "darkslategray": [47, 79, 79],
|
| 1552 | "darkslategrey": [47, 79, 79],
|
| 1553 | "darkturquoise": [0, 206, 209],
|
| 1554 | "darkviolet": [148, 0, 211],
|
| 1555 | "deeppink": [255, 20, 147],
|
| 1556 | "deepskyblue": [0, 191, 255],
|
| 1557 | "dimgray": [105, 105, 105],
|
| 1558 | "dimgrey": [105, 105, 105],
|
| 1559 | "dodgerblue": [30, 144, 255],
|
| 1560 | "firebrick": [178, 34, 34],
|
| 1561 | "floralwhite": [255, 250, 240],
|
| 1562 | "forestgreen": [34, 139, 34],
|
| 1563 | "fuchsia": [255, 0, 255],
|
| 1564 | "gainsboro": [220, 220, 220],
|
| 1565 | "ghostwhite": [248, 248, 255],
|
| 1566 | "gold": [255, 215, 0],
|
| 1567 | "goldenrod": [218, 165, 32],
|
| 1568 | "gray": [128, 128, 128],
|
| 1569 | "green": [0, 128, 0],
|
| 1570 | "greenyellow": [173, 255, 47],
|
| 1571 | "grey": [128, 128, 128],
|
| 1572 | "honeydew": [240, 255, 240],
|
| 1573 | "hotpink": [255, 105, 180],
|
| 1574 | "indianred": [205, 92, 92],
|
| 1575 | "indigo": [75, 0, 130],
|
| 1576 | "ivory": [255, 255, 240],
|
| 1577 | "khaki": [240, 230, 140],
|
| 1578 | "lavender": [230, 230, 250],
|
| 1579 | "lavenderblush": [255, 240, 245],
|
| 1580 | "lawngreen": [124, 252, 0],
|
| 1581 | "lemonchiffon": [255, 250, 205],
|
| 1582 | "lightblue": [173, 216, 230],
|
| 1583 | "lightcoral": [240, 128, 128],
|
| 1584 | "lightcyan": [224, 255, 255],
|
| 1585 | "lightgoldenrodyellow": [250, 250, 210],
|
| 1586 | "lightgray": [211, 211, 211],
|
| 1587 | "lightgreen": [144, 238, 144],
|
| 1588 | "lightgrey": [211, 211, 211],
|
| 1589 | "lightpink": [255, 182, 193],
|
| 1590 | "lightsalmon": [255, 160, 122],
|
| 1591 | "lightseagreen": [32, 178, 170],
|
| 1592 | "lightskyblue": [135, 206, 250],
|
| 1593 | "lightslategray": [119, 136, 153],
|
| 1594 | "lightslategrey": [119, 136, 153],
|
| 1595 | "lightsteelblue": [176, 196, 222],
|
| 1596 | "lightyellow": [255, 255, 224],
|
| 1597 | "lime": [0, 255, 0],
|
| 1598 | "limegreen": [50, 205, 50],
|
| 1599 | "linen": [250, 240, 230],
|
| 1600 | "magenta": [255, 0, 255],
|
| 1601 | "maroon": [128, 0, 0],
|
| 1602 | "mediumaquamarine": [102, 205, 170],
|
| 1603 | "mediumblue": [0, 0, 205],
|
| 1604 | "mediumorchid": [186, 85, 211],
|
| 1605 | "mediumpurple": [147, 112, 219],
|
| 1606 | "mediumseagreen": [60, 179, 113],
|
| 1607 | "mediumslateblue": [123, 104, 238],
|
| 1608 | "mediumspringgreen": [0, 250, 154],
|
| 1609 | "mediumturquoise": [72, 209, 204],
|
| 1610 | "mediumvioletred": [199, 21, 133],
|
| 1611 | "midnightblue": [25, 25, 112],
|
| 1612 | "mintcream": [245, 255, 250],
|
| 1613 | "mistyrose": [255, 228, 225],
|
| 1614 | "moccasin": [255, 228, 181],
|
| 1615 | "navajowhite": [255, 222, 173],
|
| 1616 | "navy": [0, 0, 128],
|
| 1617 | "oldlace": [253, 245, 230],
|
| 1618 | "olive": [128, 128, 0],
|
| 1619 | "olivedrab": [107, 142, 35],
|
| 1620 | "orange": [255, 165, 0],
|
| 1621 | "orangered": [255, 69, 0],
|
| 1622 | "orchid": [218, 112, 214],
|
| 1623 | "palegoldenrod": [238, 232, 170],
|
| 1624 | "palegreen": [152, 251, 152],
|
| 1625 | "paleturquoise": [175, 238, 238],
|
| 1626 | "palevioletred": [219, 112, 147],
|
| 1627 | "papayawhip": [255, 239, 213],
|
| 1628 | "peachpuff": [255, 218, 185],
|
| 1629 | "peru": [205, 133, 63],
|
| 1630 | "pink": [255, 192, 203],
|
| 1631 | "plum": [221, 160, 221],
|
| 1632 | "powderblue": [176, 224, 230],
|
| 1633 | "purple": [128, 0, 128],
|
| 1634 | "rebeccapurple": [102, 51, 153],
|
| 1635 | "red": [255, 0, 0],
|
| 1636 | "rosybrown": [188, 143, 143],
|
| 1637 | "royalblue": [65, 105, 225],
|
| 1638 | "saddlebrown": [139, 69, 19],
|
| 1639 | "salmon": [250, 128, 114],
|
| 1640 | "sandybrown": [244, 164, 96],
|
| 1641 | "seagreen": [46, 139, 87],
|
| 1642 | "seashell": [255, 245, 238],
|
| 1643 | "sienna": [160, 82, 45],
|
| 1644 | "silver": [192, 192, 192],
|
| 1645 | "skyblue": [135, 206, 235],
|
| 1646 | "slateblue": [106, 90, 205],
|
| 1647 | "slategray": [112, 128, 144],
|
| 1648 | "slategrey": [112, 128, 144],
|
| 1649 | "snow": [255, 250, 250],
|
| 1650 | "springgreen": [0, 255, 127],
|
| 1651 | "steelblue": [70, 130, 180],
|
| 1652 | "tan": [210, 180, 140],
|
| 1653 | "teal": [0, 128, 128],
|
| 1654 | "thistle": [216, 191, 216],
|
| 1655 | "tomato": [255, 99, 71],
|
| 1656 | "turquoise": [64, 224, 208],
|
| 1657 | "violet": [238, 130, 238],
|
| 1658 | "wheat": [245, 222, 179],
|
| 1659 | "white": [255, 255, 255],
|
| 1660 | "whitesmoke": [245, 245, 245],
|
| 1661 | "yellow": [255, 255, 0],
|
| 1662 | "yellowgreen": [154, 205, 50]
|
| 1663 | }; |
| 1664 | },{}],7:[function(require,module,exports){ |
| 1665 | /** |
| 1666 | * @namespace Chart |
| 1667 | */ |
| 1668 | var Chart = require(27)(); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1669 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1670 | require(26)(Chart); |
| 1671 | require(22)(Chart); |
| 1672 | require(25)(Chart); |
| 1673 | require(21)(Chart); |
| 1674 | require(23)(Chart); |
| 1675 | require(24)(Chart); |
| 1676 | require(28)(Chart); |
| 1677 | require(32)(Chart); |
| 1678 | require(30)(Chart); |
| 1679 | require(31)(Chart); |
| 1680 | require(33)(Chart); |
| 1681 | require(29)(Chart); |
| 1682 | require(34)(Chart); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1683 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1684 | require(35)(Chart); |
| 1685 | require(36)(Chart); |
| 1686 | require(37)(Chart); |
| 1687 | require(38)(Chart); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1688 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1689 | require(41)(Chart); |
| 1690 | require(39)(Chart); |
| 1691 | require(40)(Chart); |
| 1692 | require(42)(Chart); |
| 1693 | require(43)(Chart); |
| 1694 | require(44)(Chart); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 1695 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1696 | // Controllers must be loaded after elements |
| 1697 | // See Chart.core.datasetController.dataElementType |
| 1698 | require(15)(Chart); |
| 1699 | require(16)(Chart); |
| 1700 | require(17)(Chart); |
| 1701 | require(18)(Chart); |
| 1702 | require(19)(Chart); |
| 1703 | require(20)(Chart); |
Jian Li | 82101d9 | 2016-05-04 12:00:46 -0700 | [diff] [blame] | 1704 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 1705 | require(8)(Chart); |
| 1706 | require(9)(Chart); |
| 1707 | require(10)(Chart); |
| 1708 | require(11)(Chart); |
| 1709 | require(12)(Chart); |
| 1710 | require(13)(Chart); |
| 1711 | require(14)(Chart); |
| 1712 | |
| 1713 | window.Chart = module.exports = Chart; |
| 1714 | |
| 1715 | },{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"8":8,"9":9}],8:[function(require,module,exports){ |
| 1716 | "use strict"; |
| 1717 | |
| 1718 | module.exports = function(Chart) { |
| 1719 | |
| 1720 | Chart.Bar = function(context, config) { |
| 1721 | config.type = 'bar'; |
| 1722 | |
| 1723 | return new Chart(context, config); |
| 1724 | }; |
| 1725 | |
| 1726 | }; |
| 1727 | },{}],9:[function(require,module,exports){ |
| 1728 | "use strict"; |
| 1729 | |
| 1730 | module.exports = function(Chart) { |
| 1731 | |
| 1732 | Chart.Bubble = function(context, config) { |
| 1733 | config.type = 'bubble'; |
| 1734 | return new Chart(context, config); |
| 1735 | }; |
| 1736 | |
| 1737 | }; |
| 1738 | },{}],10:[function(require,module,exports){ |
| 1739 | "use strict"; |
| 1740 | |
| 1741 | module.exports = function(Chart) { |
| 1742 | |
| 1743 | Chart.Doughnut = function(context, config) { |
| 1744 | config.type = 'doughnut'; |
| 1745 | |
| 1746 | return new Chart(context, config); |
| 1747 | }; |
| 1748 | |
| 1749 | }; |
| 1750 | },{}],11:[function(require,module,exports){ |
| 1751 | "use strict"; |
| 1752 | |
| 1753 | module.exports = function(Chart) { |
| 1754 | |
| 1755 | Chart.Line = function(context, config) { |
| 1756 | config.type = 'line'; |
| 1757 | |
| 1758 | return new Chart(context, config); |
| 1759 | }; |
| 1760 | |
| 1761 | }; |
| 1762 | },{}],12:[function(require,module,exports){ |
| 1763 | "use strict"; |
| 1764 | |
| 1765 | module.exports = function(Chart) { |
| 1766 | |
| 1767 | Chart.PolarArea = function(context, config) { |
| 1768 | config.type = 'polarArea'; |
| 1769 | |
| 1770 | return new Chart(context, config); |
| 1771 | }; |
| 1772 | |
| 1773 | }; |
| 1774 | },{}],13:[function(require,module,exports){ |
| 1775 | "use strict"; |
| 1776 | |
| 1777 | module.exports = function(Chart) { |
| 1778 | |
| 1779 | Chart.Radar = function(context, config) { |
| 1780 | config.options = Chart.helpers.configMerge({ aspectRatio: 1 }, config.options); |
| 1781 | config.type = 'radar'; |
| 1782 | |
| 1783 | return new Chart(context, config); |
| 1784 | }; |
| 1785 | |
| 1786 | }; |
| 1787 | |
| 1788 | },{}],14:[function(require,module,exports){ |
| 1789 | "use strict"; |
| 1790 | |
| 1791 | module.exports = function(Chart) { |
| 1792 | |
| 1793 | var defaultConfig = { |
| 1794 | hover: { |
| 1795 | mode: 'single' |
| 1796 | }, |
| 1797 | |
| 1798 | scales: { |
| 1799 | xAxes: [{ |
| 1800 | type: "linear", // scatter should not use a category axis |
| 1801 | position: "bottom", |
| 1802 | id: "x-axis-1" // need an ID so datasets can reference the scale |
| 1803 | }], |
| 1804 | yAxes: [{ |
| 1805 | type: "linear", |
| 1806 | position: "left", |
| 1807 | id: "y-axis-1" |
| 1808 | }] |
| 1809 | }, |
| 1810 | |
| 1811 | tooltips: { |
| 1812 | callbacks: { |
| 1813 | title: function() { |
| 1814 | // Title doesn't make sense for scatter since we format the data as a point |
| 1815 | return ''; |
| 1816 | }, |
| 1817 | label: function(tooltipItem) { |
| 1818 | return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')'; |
| 1819 | } |
| 1820 | } |
| 1821 | } |
| 1822 | }; |
| 1823 | |
| 1824 | // Register the default config for this type |
| 1825 | Chart.defaults.scatter = defaultConfig; |
| 1826 | |
| 1827 | // Scatter charts use line controllers |
| 1828 | Chart.controllers.scatter = Chart.controllers.line; |
| 1829 | |
| 1830 | Chart.Scatter = function(context, config) { |
| 1831 | config.type = 'scatter'; |
| 1832 | return new Chart(context, config); |
| 1833 | }; |
| 1834 | |
| 1835 | }; |
| 1836 | },{}],15:[function(require,module,exports){ |
| 1837 | "use strict"; |
| 1838 | |
| 1839 | module.exports = function(Chart) { |
| 1840 | |
| 1841 | var helpers = Chart.helpers; |
| 1842 | |
| 1843 | Chart.defaults.bar = { |
| 1844 | hover: { |
| 1845 | mode: "label" |
| 1846 | }, |
| 1847 | |
| 1848 | scales: { |
| 1849 | xAxes: [{ |
| 1850 | type: "category", |
| 1851 | |
| 1852 | // Specific to Bar Controller |
| 1853 | categoryPercentage: 0.8, |
| 1854 | barPercentage: 0.9, |
| 1855 | |
| 1856 | // grid line settings |
| 1857 | gridLines: { |
| 1858 | offsetGridLines: true |
| 1859 | } |
| 1860 | }], |
| 1861 | yAxes: [{ |
| 1862 | type: "linear" |
| 1863 | }] |
| 1864 | } |
| 1865 | }; |
| 1866 | |
| 1867 | Chart.controllers.bar = Chart.DatasetController.extend({ |
| 1868 | |
| 1869 | dataElementType: Chart.elements.Rectangle, |
| 1870 | |
| 1871 | initialize: function(chart, datasetIndex) { |
| 1872 | Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); |
| 1873 | |
| 1874 | // Use this to indicate that this is a bar dataset. |
| 1875 | this.getMeta().bar = true; |
| 1876 | }, |
| 1877 | |
| 1878 | // Get the number of datasets that display bars. We use this to correctly calculate the bar width |
| 1879 | getBarCount: function() { |
| 1880 | var me = this; |
| 1881 | var barCount = 0; |
| 1882 | helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { |
| 1883 | var meta = me.chart.getDatasetMeta(datasetIndex); |
| 1884 | if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) { |
| 1885 | ++barCount; |
| 1886 | } |
| 1887 | }, me); |
| 1888 | return barCount; |
| 1889 | }, |
| 1890 | |
| 1891 | update: function(reset) { |
| 1892 | var me = this; |
| 1893 | helpers.each(me.getMeta().data, function(rectangle, index) { |
| 1894 | me.updateElement(rectangle, index, reset); |
| 1895 | }, me); |
| 1896 | }, |
| 1897 | |
| 1898 | updateElement: function(rectangle, index, reset) { |
| 1899 | var me = this; |
| 1900 | var meta = me.getMeta(); |
| 1901 | var xScale = me.getScaleForId(meta.xAxisID); |
| 1902 | var yScale = me.getScaleForId(meta.yAxisID); |
| 1903 | var scaleBase = yScale.getBasePixel(); |
| 1904 | var rectangleElementOptions = me.chart.options.elements.rectangle; |
| 1905 | var custom = rectangle.custom || {}; |
| 1906 | var dataset = me.getDataset(); |
| 1907 | |
| 1908 | helpers.extend(rectangle, { |
| 1909 | // Utility |
| 1910 | _xScale: xScale, |
| 1911 | _yScale: yScale, |
| 1912 | _datasetIndex: me.index, |
| 1913 | _index: index, |
| 1914 | |
| 1915 | // Desired view properties |
| 1916 | _model: { |
| 1917 | x: me.calculateBarX(index, me.index), |
| 1918 | y: reset ? scaleBase : me.calculateBarY(index, me.index), |
| 1919 | |
| 1920 | // Tooltip |
| 1921 | label: me.chart.data.labels[index], |
| 1922 | datasetLabel: dataset.label, |
| 1923 | |
| 1924 | // Appearance |
| 1925 | base: reset ? scaleBase : me.calculateBarBase(me.index, index), |
| 1926 | width: me.calculateBarWidth(index), |
| 1927 | backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), |
| 1928 | borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, |
| 1929 | borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), |
| 1930 | borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) |
| 1931 | } |
| 1932 | }); |
| 1933 | rectangle.pivot(); |
| 1934 | }, |
| 1935 | |
| 1936 | calculateBarBase: function(datasetIndex, index) { |
| 1937 | var me = this; |
| 1938 | var meta = me.getMeta(); |
| 1939 | var yScale = me.getScaleForId(meta.yAxisID); |
| 1940 | var base = 0; |
| 1941 | |
| 1942 | if (yScale.options.stacked) { |
| 1943 | var chart = me.chart; |
| 1944 | var datasets = chart.data.datasets; |
| 1945 | var value = Number(datasets[datasetIndex].data[index]); |
| 1946 | |
| 1947 | for (var i = 0; i < datasetIndex; i++) { |
| 1948 | var currentDs = datasets[i]; |
| 1949 | var currentDsMeta = chart.getDatasetMeta(i); |
| 1950 | if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { |
| 1951 | var currentVal = Number(currentDs.data[index]); |
| 1952 | base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); |
| 1953 | } |
| 1954 | } |
| 1955 | |
| 1956 | return yScale.getPixelForValue(base); |
| 1957 | } |
| 1958 | |
| 1959 | return yScale.getBasePixel(); |
| 1960 | }, |
| 1961 | |
| 1962 | getRuler: function(index) { |
| 1963 | var me = this; |
| 1964 | var meta = me.getMeta(); |
| 1965 | var xScale = me.getScaleForId(meta.xAxisID); |
| 1966 | var datasetCount = me.getBarCount(); |
| 1967 | |
| 1968 | var tickWidth; |
| 1969 | |
| 1970 | if (xScale.options.type === 'category') { |
| 1971 | tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index); |
| 1972 | } else { |
| 1973 | // Average width |
| 1974 | tickWidth = xScale.width / xScale.ticks.length; |
| 1975 | } |
| 1976 | var categoryWidth = tickWidth * xScale.options.categoryPercentage; |
| 1977 | var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2; |
| 1978 | var fullBarWidth = categoryWidth / datasetCount; |
| 1979 | |
| 1980 | if (xScale.ticks.length !== me.chart.data.labels.length) { |
| 1981 | var perc = xScale.ticks.length / me.chart.data.labels.length; |
| 1982 | fullBarWidth = fullBarWidth * perc; |
| 1983 | } |
| 1984 | |
| 1985 | var barWidth = fullBarWidth * xScale.options.barPercentage; |
| 1986 | var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); |
| 1987 | |
| 1988 | return { |
| 1989 | datasetCount: datasetCount, |
| 1990 | tickWidth: tickWidth, |
| 1991 | categoryWidth: categoryWidth, |
| 1992 | categorySpacing: categorySpacing, |
| 1993 | fullBarWidth: fullBarWidth, |
| 1994 | barWidth: barWidth, |
| 1995 | barSpacing: barSpacing |
| 1996 | }; |
| 1997 | }, |
| 1998 | |
| 1999 | calculateBarWidth: function(index) { |
| 2000 | var xScale = this.getScaleForId(this.getMeta().xAxisID); |
| 2001 | if (xScale.options.barThickness) { |
| 2002 | return xScale.options.barThickness; |
| 2003 | } |
| 2004 | var ruler = this.getRuler(index); |
| 2005 | return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth; |
| 2006 | }, |
| 2007 | |
| 2008 | // Get bar index from the given dataset index accounting for the fact that not all bars are visible |
| 2009 | getBarIndex: function(datasetIndex) { |
| 2010 | var barIndex = 0; |
| 2011 | var meta, j; |
| 2012 | |
| 2013 | for (j = 0; j < datasetIndex; ++j) { |
| 2014 | meta = this.chart.getDatasetMeta(j); |
| 2015 | if (meta.bar && this.chart.isDatasetVisible(j)) { |
| 2016 | ++barIndex; |
| 2017 | } |
| 2018 | } |
| 2019 | |
| 2020 | return barIndex; |
| 2021 | }, |
| 2022 | |
| 2023 | calculateBarX: function(index, datasetIndex) { |
| 2024 | var me = this; |
| 2025 | var meta = me.getMeta(); |
| 2026 | var xScale = me.getScaleForId(meta.xAxisID); |
| 2027 | var barIndex = me.getBarIndex(datasetIndex); |
| 2028 | |
| 2029 | var ruler = me.getRuler(index); |
| 2030 | var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); |
| 2031 | leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0; |
| 2032 | |
| 2033 | if (xScale.options.stacked) { |
| 2034 | return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing; |
| 2035 | } |
| 2036 | |
| 2037 | return leftTick + |
| 2038 | (ruler.barWidth / 2) + |
| 2039 | ruler.categorySpacing + |
| 2040 | (ruler.barWidth * barIndex) + |
| 2041 | (ruler.barSpacing / 2) + |
| 2042 | (ruler.barSpacing * barIndex); |
| 2043 | }, |
| 2044 | |
| 2045 | calculateBarY: function(index, datasetIndex) { |
| 2046 | var me = this; |
| 2047 | var meta = me.getMeta(); |
| 2048 | var yScale = me.getScaleForId(meta.yAxisID); |
| 2049 | var value = Number(me.getDataset().data[index]); |
| 2050 | |
| 2051 | if (yScale.options.stacked) { |
| 2052 | |
| 2053 | var sumPos = 0, |
| 2054 | sumNeg = 0; |
| 2055 | |
| 2056 | for (var i = 0; i < datasetIndex; i++) { |
| 2057 | var ds = me.chart.data.datasets[i]; |
| 2058 | var dsMeta = me.chart.getDatasetMeta(i); |
| 2059 | if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i)) { |
| 2060 | var stackedVal = Number(ds.data[index]); |
| 2061 | if (stackedVal < 0) { |
| 2062 | sumNeg += stackedVal || 0; |
| 2063 | } else { |
| 2064 | sumPos += stackedVal || 0; |
| 2065 | } |
| 2066 | } |
| 2067 | } |
| 2068 | |
| 2069 | if (value < 0) { |
| 2070 | return yScale.getPixelForValue(sumNeg + value); |
| 2071 | } else { |
| 2072 | return yScale.getPixelForValue(sumPos + value); |
| 2073 | } |
| 2074 | } |
| 2075 | |
| 2076 | return yScale.getPixelForValue(value); |
| 2077 | }, |
| 2078 | |
| 2079 | draw: function(ease) { |
| 2080 | var me = this; |
| 2081 | var easingDecimal = ease || 1; |
| 2082 | helpers.each(me.getMeta().data, function(rectangle, index) { |
| 2083 | var d = me.getDataset().data[index]; |
| 2084 | if (d !== null && d !== undefined && !isNaN(d)) { |
| 2085 | rectangle.transition(easingDecimal).draw(); |
| 2086 | } |
| 2087 | }, me); |
| 2088 | }, |
| 2089 | |
| 2090 | setHoverStyle: function(rectangle) { |
| 2091 | var dataset = this.chart.data.datasets[rectangle._datasetIndex]; |
| 2092 | var index = rectangle._index; |
| 2093 | |
| 2094 | var custom = rectangle.custom || {}; |
| 2095 | var model = rectangle._model; |
| 2096 | model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); |
| 2097 | model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor)); |
| 2098 | model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); |
| 2099 | }, |
| 2100 | |
| 2101 | removeHoverStyle: function(rectangle) { |
| 2102 | var dataset = this.chart.data.datasets[rectangle._datasetIndex]; |
| 2103 | var index = rectangle._index; |
| 2104 | var custom = rectangle.custom || {}; |
| 2105 | var model = rectangle._model; |
| 2106 | var rectangleElementOptions = this.chart.options.elements.rectangle; |
| 2107 | |
| 2108 | model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor); |
| 2109 | model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor); |
| 2110 | model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth); |
| 2111 | } |
| 2112 | |
| 2113 | }); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2114 | |
| 2115 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2116 | // including horizontalBar in the bar file, instead of a file of its own |
| 2117 | // it extends bar (like pie extends doughnut) |
| 2118 | Chart.defaults.horizontalBar = { |
| 2119 | hover: { |
| 2120 | mode: "label" |
| 2121 | }, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2122 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2123 | scales: { |
| 2124 | xAxes: [{ |
| 2125 | type: "linear", |
| 2126 | position: "bottom" |
| 2127 | }], |
| 2128 | yAxes: [{ |
| 2129 | position: "left", |
| 2130 | type: "category", |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2131 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2132 | // Specific to Horizontal Bar Controller |
| 2133 | categoryPercentage: 0.8, |
| 2134 | barPercentage: 0.9, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2135 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2136 | // grid line settings |
| 2137 | gridLines: { |
| 2138 | offsetGridLines: true |
| 2139 | } |
| 2140 | }] |
| 2141 | }, |
| 2142 | elements: { |
| 2143 | rectangle: { |
| 2144 | borderSkipped: 'left' |
| 2145 | } |
| 2146 | }, |
| 2147 | tooltips: { |
| 2148 | callbacks: { |
| 2149 | title: function(tooltipItems, data) { |
| 2150 | // Pick first xLabel for now |
| 2151 | var title = ''; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2152 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2153 | if (tooltipItems.length > 0) { |
| 2154 | if (tooltipItems[0].yLabel) { |
| 2155 | title = tooltipItems[0].yLabel; |
| 2156 | } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) { |
| 2157 | title = data.labels[tooltipItems[0].index]; |
| 2158 | } |
| 2159 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2160 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2161 | return title; |
| 2162 | }, |
| 2163 | label: function(tooltipItem, data) { |
| 2164 | var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; |
| 2165 | return datasetLabel + ': ' + tooltipItem.xLabel; |
| 2166 | } |
| 2167 | } |
| 2168 | } |
| 2169 | }; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2170 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2171 | Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ |
| 2172 | updateElement: function(rectangle, index, reset) { |
| 2173 | var me = this; |
| 2174 | var meta = me.getMeta(); |
| 2175 | var xScale = me.getScaleForId(meta.xAxisID); |
| 2176 | var yScale = me.getScaleForId(meta.yAxisID); |
| 2177 | var scaleBase = xScale.getBasePixel(); |
| 2178 | var custom = rectangle.custom || {}; |
| 2179 | var dataset = me.getDataset(); |
| 2180 | var rectangleElementOptions = me.chart.options.elements.rectangle; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2181 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2182 | helpers.extend(rectangle, { |
| 2183 | // Utility |
| 2184 | _xScale: xScale, |
| 2185 | _yScale: yScale, |
| 2186 | _datasetIndex: me.index, |
| 2187 | _index: index, |
| 2188 | |
| 2189 | // Desired view properties |
| 2190 | _model: { |
| 2191 | x: reset ? scaleBase : me.calculateBarX(index, me.index), |
| 2192 | y: me.calculateBarY(index, me.index), |
| 2193 | |
| 2194 | // Tooltip |
| 2195 | label: me.chart.data.labels[index], |
| 2196 | datasetLabel: dataset.label, |
| 2197 | |
| 2198 | // Appearance |
| 2199 | base: reset ? scaleBase : me.calculateBarBase(me.index, index), |
| 2200 | height: me.calculateBarHeight(index), |
| 2201 | backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), |
| 2202 | borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, |
| 2203 | borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), |
| 2204 | borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) |
| 2205 | }, |
| 2206 | |
| 2207 | draw: function () { |
| 2208 | var ctx = this._chart.ctx; |
| 2209 | var vm = this._view; |
| 2210 | |
| 2211 | var halfHeight = vm.height / 2, |
| 2212 | topY = vm.y - halfHeight, |
| 2213 | bottomY = vm.y + halfHeight, |
| 2214 | right = vm.base - (vm.base - vm.x), |
| 2215 | halfStroke = vm.borderWidth / 2; |
| 2216 | |
| 2217 | // Canvas doesn't allow us to stroke inside the width so we can |
| 2218 | // adjust the sizes to fit if we're setting a stroke on the line |
| 2219 | if (vm.borderWidth) { |
| 2220 | topY += halfStroke; |
| 2221 | bottomY -= halfStroke; |
| 2222 | right += halfStroke; |
| 2223 | } |
| 2224 | |
| 2225 | ctx.beginPath(); |
| 2226 | |
| 2227 | ctx.fillStyle = vm.backgroundColor; |
| 2228 | ctx.strokeStyle = vm.borderColor; |
| 2229 | ctx.lineWidth = vm.borderWidth; |
| 2230 | |
| 2231 | // Corner points, from bottom-left to bottom-right clockwise |
| 2232 | // | 1 2 | |
| 2233 | // | 0 3 | |
| 2234 | var corners = [ |
| 2235 | [vm.base, bottomY], |
| 2236 | [vm.base, topY], |
| 2237 | [right, topY], |
| 2238 | [right, bottomY] |
| 2239 | ]; |
| 2240 | |
| 2241 | // Find first (starting) corner with fallback to 'bottom' |
| 2242 | var borders = ['bottom', 'left', 'top', 'right']; |
| 2243 | var startCorner = borders.indexOf(vm.borderSkipped, 0); |
| 2244 | if (startCorner === -1) |
| 2245 | startCorner = 0; |
| 2246 | |
| 2247 | function cornerAt(index) { |
| 2248 | return corners[(startCorner + index) % 4]; |
| 2249 | } |
| 2250 | |
| 2251 | // Draw rectangle from 'startCorner' |
| 2252 | ctx.moveTo.apply(ctx, cornerAt(0)); |
| 2253 | for (var i = 1; i < 4; i++) |
| 2254 | ctx.lineTo.apply(ctx, cornerAt(i)); |
| 2255 | |
| 2256 | ctx.fill(); |
| 2257 | if (vm.borderWidth) { |
| 2258 | ctx.stroke(); |
| 2259 | } |
| 2260 | }, |
| 2261 | |
| 2262 | inRange: function (mouseX, mouseY) { |
| 2263 | var vm = this._view; |
| 2264 | var inRange = false; |
| 2265 | |
| 2266 | if (vm) { |
| 2267 | if (vm.x < vm.base) { |
| 2268 | inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base); |
| 2269 | } else { |
| 2270 | inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x); |
| 2271 | } |
| 2272 | } |
| 2273 | |
| 2274 | return inRange; |
| 2275 | } |
| 2276 | }); |
| 2277 | |
| 2278 | rectangle.pivot(); |
| 2279 | }, |
| 2280 | |
| 2281 | calculateBarBase: function (datasetIndex, index) { |
| 2282 | var me = this; |
| 2283 | var meta = me.getMeta(); |
| 2284 | var xScale = me.getScaleForId(meta.xAxisID); |
| 2285 | var base = 0; |
| 2286 | |
| 2287 | if (xScale.options.stacked) { |
| 2288 | var chart = me.chart; |
| 2289 | var datasets = chart.data.datasets; |
| 2290 | var value = Number(datasets[datasetIndex].data[index]); |
| 2291 | |
| 2292 | for (var i = 0; i < datasetIndex; i++) { |
| 2293 | var currentDs = datasets[i]; |
| 2294 | var currentDsMeta = chart.getDatasetMeta(i); |
| 2295 | if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i)) { |
| 2296 | var currentVal = Number(currentDs.data[index]); |
| 2297 | base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); |
| 2298 | } |
| 2299 | } |
| 2300 | |
| 2301 | return xScale.getPixelForValue(base); |
| 2302 | } |
| 2303 | |
| 2304 | return xScale.getBasePixel(); |
| 2305 | }, |
| 2306 | |
| 2307 | getRuler: function (index) { |
| 2308 | var me = this; |
| 2309 | var meta = me.getMeta(); |
| 2310 | var yScale = me.getScaleForId(meta.yAxisID); |
| 2311 | var datasetCount = me.getBarCount(); |
| 2312 | |
| 2313 | var tickHeight; |
| 2314 | if (yScale.options.type === 'category') { |
| 2315 | tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index); |
| 2316 | } else { |
| 2317 | // Average width |
| 2318 | tickHeight = yScale.width / yScale.ticks.length; |
| 2319 | } |
| 2320 | var categoryHeight = tickHeight * yScale.options.categoryPercentage; |
| 2321 | var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2; |
| 2322 | var fullBarHeight = categoryHeight / datasetCount; |
| 2323 | |
| 2324 | if (yScale.ticks.length !== me.chart.data.labels.length) { |
| 2325 | var perc = yScale.ticks.length / me.chart.data.labels.length; |
| 2326 | fullBarHeight = fullBarHeight * perc; |
| 2327 | } |
| 2328 | |
| 2329 | var barHeight = fullBarHeight * yScale.options.barPercentage; |
| 2330 | var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage); |
| 2331 | |
| 2332 | return { |
| 2333 | datasetCount: datasetCount, |
| 2334 | tickHeight: tickHeight, |
| 2335 | categoryHeight: categoryHeight, |
| 2336 | categorySpacing: categorySpacing, |
| 2337 | fullBarHeight: fullBarHeight, |
| 2338 | barHeight: barHeight, |
| 2339 | barSpacing: barSpacing, |
| 2340 | }; |
| 2341 | }, |
| 2342 | |
| 2343 | calculateBarHeight: function (index) { |
| 2344 | var me = this; |
| 2345 | var yScale = me.getScaleForId(me.getMeta().yAxisID); |
| 2346 | if (yScale.options.barThickness) { |
| 2347 | return yScale.options.barThickness; |
| 2348 | } |
| 2349 | var ruler = me.getRuler(index); |
| 2350 | return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight; |
| 2351 | }, |
| 2352 | |
| 2353 | calculateBarX: function (index, datasetIndex) { |
| 2354 | var me = this; |
| 2355 | var meta = me.getMeta(); |
| 2356 | var xScale = me.getScaleForId(meta.xAxisID); |
| 2357 | var value = Number(me.getDataset().data[index]); |
| 2358 | |
| 2359 | if (xScale.options.stacked) { |
| 2360 | |
| 2361 | var sumPos = 0, |
| 2362 | sumNeg = 0; |
| 2363 | |
| 2364 | for (var i = 0; i < datasetIndex; i++) { |
| 2365 | var ds = me.chart.data.datasets[i]; |
| 2366 | var dsMeta = me.chart.getDatasetMeta(i); |
| 2367 | if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i)) { |
| 2368 | var stackedVal = Number(ds.data[index]); |
| 2369 | if (stackedVal < 0) { |
| 2370 | sumNeg += stackedVal || 0; |
| 2371 | } else { |
| 2372 | sumPos += stackedVal || 0; |
| 2373 | } |
| 2374 | } |
| 2375 | } |
| 2376 | |
| 2377 | if (value < 0) { |
| 2378 | return xScale.getPixelForValue(sumNeg + value); |
| 2379 | } else { |
| 2380 | return xScale.getPixelForValue(sumPos + value); |
| 2381 | } |
| 2382 | } |
| 2383 | |
| 2384 | return xScale.getPixelForValue(value); |
| 2385 | }, |
| 2386 | |
| 2387 | calculateBarY: function (index, datasetIndex) { |
| 2388 | var me = this; |
| 2389 | var meta = me.getMeta(); |
| 2390 | var yScale = me.getScaleForId(meta.yAxisID); |
| 2391 | var barIndex = me.getBarIndex(datasetIndex); |
| 2392 | |
| 2393 | var ruler = me.getRuler(index); |
| 2394 | var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); |
| 2395 | topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0; |
| 2396 | |
| 2397 | if (yScale.options.stacked) { |
| 2398 | return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing; |
| 2399 | } |
| 2400 | |
| 2401 | return topTick + |
| 2402 | (ruler.barHeight / 2) + |
| 2403 | ruler.categorySpacing + |
| 2404 | (ruler.barHeight * barIndex) + |
| 2405 | (ruler.barSpacing / 2) + |
| 2406 | (ruler.barSpacing * barIndex); |
| 2407 | } |
| 2408 | }); |
| 2409 | }; |
| 2410 | |
| 2411 | },{}],16:[function(require,module,exports){ |
| 2412 | "use strict"; |
| 2413 | |
| 2414 | module.exports = function(Chart) { |
| 2415 | |
| 2416 | var helpers = Chart.helpers; |
| 2417 | |
| 2418 | Chart.defaults.bubble = { |
| 2419 | hover: { |
| 2420 | mode: "single" |
| 2421 | }, |
| 2422 | |
| 2423 | scales: { |
| 2424 | xAxes: [{ |
| 2425 | type: "linear", // bubble should probably use a linear scale by default |
| 2426 | position: "bottom", |
| 2427 | id: "x-axis-0" // need an ID so datasets can reference the scale |
| 2428 | }], |
| 2429 | yAxes: [{ |
| 2430 | type: "linear", |
| 2431 | position: "left", |
| 2432 | id: "y-axis-0" |
| 2433 | }] |
| 2434 | }, |
| 2435 | |
| 2436 | tooltips: { |
| 2437 | callbacks: { |
| 2438 | title: function() { |
| 2439 | // Title doesn't make sense for scatter since we format the data as a point |
| 2440 | return ''; |
| 2441 | }, |
| 2442 | label: function(tooltipItem, data) { |
| 2443 | var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; |
| 2444 | var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; |
| 2445 | return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')'; |
| 2446 | } |
| 2447 | } |
| 2448 | } |
| 2449 | }; |
| 2450 | |
| 2451 | Chart.controllers.bubble = Chart.DatasetController.extend({ |
| 2452 | |
| 2453 | dataElementType: Chart.elements.Point, |
| 2454 | |
| 2455 | update: function(reset) { |
| 2456 | var me = this; |
| 2457 | var meta = me.getMeta(); |
| 2458 | var points = meta.data; |
| 2459 | |
| 2460 | // Update Points |
| 2461 | helpers.each(points, function(point, index) { |
| 2462 | me.updateElement(point, index, reset); |
| 2463 | }); |
| 2464 | }, |
| 2465 | |
| 2466 | updateElement: function(point, index, reset) { |
| 2467 | var me = this; |
| 2468 | var meta = me.getMeta(); |
| 2469 | var xScale = me.getScaleForId(meta.xAxisID); |
| 2470 | var yScale = me.getScaleForId(meta.yAxisID); |
| 2471 | |
| 2472 | var custom = point.custom || {}; |
| 2473 | var dataset = me.getDataset(); |
| 2474 | var data = dataset.data[index]; |
| 2475 | var pointElementOptions = me.chart.options.elements.point; |
| 2476 | var dsIndex = me.index; |
| 2477 | |
| 2478 | helpers.extend(point, { |
| 2479 | // Utility |
| 2480 | _xScale: xScale, |
| 2481 | _yScale: yScale, |
| 2482 | _datasetIndex: dsIndex, |
| 2483 | _index: index, |
| 2484 | |
| 2485 | // Desired view properties |
| 2486 | _model: { |
| 2487 | x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex, me.chart.isCombo), |
| 2488 | y: reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex), |
| 2489 | // Appearance |
| 2490 | radius: reset ? 0 : custom.radius ? custom.radius : me.getRadius(data), |
| 2491 | |
| 2492 | // Tooltip |
| 2493 | hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) |
| 2494 | } |
| 2495 | }); |
| 2496 | |
| 2497 | // Trick to reset the styles of the point |
| 2498 | Chart.DatasetController.prototype.removeHoverStyle.call(me, point, pointElementOptions); |
| 2499 | |
| 2500 | var model = point._model; |
| 2501 | model.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y)); |
| 2502 | |
| 2503 | point.pivot(); |
| 2504 | }, |
| 2505 | |
| 2506 | getRadius: function(value) { |
| 2507 | return value.r || this.chart.options.elements.point.radius; |
| 2508 | }, |
| 2509 | |
| 2510 | setHoverStyle: function(point) { |
| 2511 | var me = this; |
| 2512 | Chart.DatasetController.prototype.setHoverStyle.call(me, point); |
| 2513 | |
| 2514 | // Radius |
| 2515 | var dataset = me.chart.data.datasets[point._datasetIndex]; |
| 2516 | var index = point._index; |
| 2517 | var custom = point.custom || {}; |
| 2518 | var model = point._model; |
| 2519 | model.radius = custom.hoverRadius ? custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, me.chart.options.elements.point.hoverRadius)) + me.getRadius(dataset.data[index]); |
| 2520 | }, |
| 2521 | |
| 2522 | removeHoverStyle: function(point) { |
| 2523 | var me = this; |
| 2524 | Chart.DatasetController.prototype.removeHoverStyle.call(me, point, me.chart.options.elements.point); |
| 2525 | |
| 2526 | var dataVal = me.chart.data.datasets[point._datasetIndex].data[point._index]; |
| 2527 | var custom = point.custom || {}; |
| 2528 | var model = point._model; |
| 2529 | |
| 2530 | model.radius = custom.radius ? custom.radius : me.getRadius(dataVal); |
| 2531 | } |
| 2532 | }); |
| 2533 | }; |
| 2534 | |
| 2535 | },{}],17:[function(require,module,exports){ |
| 2536 | "use strict"; |
| 2537 | |
| 2538 | module.exports = function(Chart) { |
| 2539 | |
| 2540 | var helpers = Chart.helpers, |
| 2541 | defaults = Chart.defaults; |
| 2542 | |
| 2543 | defaults.doughnut = { |
| 2544 | animation: { |
| 2545 | //Boolean - Whether we animate the rotation of the Doughnut |
| 2546 | animateRotate: true, |
| 2547 | //Boolean - Whether we animate scaling the Doughnut from the centre |
| 2548 | animateScale: false |
| 2549 | }, |
| 2550 | aspectRatio: 1, |
| 2551 | hover: { |
| 2552 | mode: 'single' |
| 2553 | }, |
| 2554 | legendCallback: function(chart) { |
| 2555 | var text = []; |
| 2556 | text.push('<ul class="' + chart.id + '-legend">'); |
| 2557 | |
| 2558 | var data = chart.data; |
| 2559 | var datasets = data.datasets; |
| 2560 | var labels = data.labels; |
| 2561 | |
| 2562 | if (datasets.length) { |
| 2563 | for (var i = 0; i < datasets[0].data.length; ++i) { |
| 2564 | text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); |
| 2565 | if (labels[i]) { |
| 2566 | text.push(labels[i]); |
| 2567 | } |
| 2568 | text.push('</li>'); |
| 2569 | } |
| 2570 | } |
| 2571 | |
| 2572 | text.push('</ul>'); |
| 2573 | return text.join(""); |
| 2574 | }, |
| 2575 | legend: { |
| 2576 | labels: { |
| 2577 | generateLabels: function(chart) { |
| 2578 | var data = chart.data; |
| 2579 | if (data.labels.length && data.datasets.length) { |
| 2580 | return data.labels.map(function(label, i) { |
| 2581 | var meta = chart.getDatasetMeta(0); |
| 2582 | var ds = data.datasets[0]; |
| 2583 | var arc = meta.data[i]; |
| 2584 | var custom = arc && arc.custom || {}; |
| 2585 | var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; |
| 2586 | var arcOpts = chart.options.elements.arc; |
| 2587 | var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); |
| 2588 | var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); |
| 2589 | var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); |
| 2590 | |
| 2591 | return { |
| 2592 | text: label, |
| 2593 | fillStyle: fill, |
| 2594 | strokeStyle: stroke, |
| 2595 | lineWidth: bw, |
| 2596 | hidden: isNaN(ds.data[i]) || meta.data[i].hidden, |
| 2597 | |
| 2598 | // Extra data used for toggling the correct item |
| 2599 | index: i |
| 2600 | }; |
| 2601 | }); |
| 2602 | } else { |
| 2603 | return []; |
| 2604 | } |
| 2605 | } |
| 2606 | }, |
| 2607 | |
| 2608 | onClick: function(e, legendItem) { |
| 2609 | var index = legendItem.index; |
| 2610 | var chart = this.chart; |
| 2611 | var i, ilen, meta; |
| 2612 | |
| 2613 | for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { |
| 2614 | meta = chart.getDatasetMeta(i); |
| 2615 | meta.data[index].hidden = !meta.data[index].hidden; |
| 2616 | } |
| 2617 | |
| 2618 | chart.update(); |
| 2619 | } |
| 2620 | }, |
| 2621 | |
| 2622 | //The percentage of the chart that we cut out of the middle. |
| 2623 | cutoutPercentage: 50, |
| 2624 | |
| 2625 | //The rotation of the chart, where the first data arc begins. |
| 2626 | rotation: Math.PI * -0.5, |
| 2627 | |
| 2628 | //The total circumference of the chart. |
| 2629 | circumference: Math.PI * 2.0, |
| 2630 | |
| 2631 | // Need to override these to give a nice default |
| 2632 | tooltips: { |
| 2633 | callbacks: { |
| 2634 | title: function() { |
| 2635 | return ''; |
| 2636 | }, |
| 2637 | label: function(tooltipItem, data) { |
| 2638 | return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; |
| 2639 | } |
| 2640 | } |
| 2641 | } |
| 2642 | }; |
| 2643 | |
| 2644 | defaults.pie = helpers.clone(defaults.doughnut); |
| 2645 | helpers.extend(defaults.pie, { |
| 2646 | cutoutPercentage: 0 |
| 2647 | }); |
| 2648 | |
| 2649 | |
| 2650 | Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ |
| 2651 | |
| 2652 | dataElementType: Chart.elements.Arc, |
| 2653 | |
| 2654 | linkScales: helpers.noop, |
| 2655 | |
| 2656 | // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly |
| 2657 | getRingIndex: function(datasetIndex) { |
| 2658 | var ringIndex = 0; |
| 2659 | |
| 2660 | for (var j = 0; j < datasetIndex; ++j) { |
| 2661 | if (this.chart.isDatasetVisible(j)) { |
| 2662 | ++ringIndex; |
| 2663 | } |
| 2664 | } |
| 2665 | |
| 2666 | return ringIndex; |
| 2667 | }, |
| 2668 | |
| 2669 | update: function(reset) { |
| 2670 | var me = this; |
| 2671 | var chart = me.chart, |
| 2672 | chartArea = chart.chartArea, |
| 2673 | opts = chart.options, |
| 2674 | arcOpts = opts.elements.arc, |
| 2675 | availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth, |
| 2676 | availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth, |
| 2677 | minSize = Math.min(availableWidth, availableHeight), |
| 2678 | offset = { |
| 2679 | x: 0, |
| 2680 | y: 0 |
| 2681 | }, |
| 2682 | meta = me.getMeta(), |
| 2683 | cutoutPercentage = opts.cutoutPercentage, |
| 2684 | circumference = opts.circumference; |
| 2685 | |
| 2686 | // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc |
| 2687 | if (circumference < Math.PI * 2.0) { |
| 2688 | var startAngle = opts.rotation % (Math.PI * 2.0); |
| 2689 | startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); |
| 2690 | var endAngle = startAngle + circumference; |
| 2691 | var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; |
| 2692 | var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; |
| 2693 | var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); |
| 2694 | var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); |
| 2695 | var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); |
| 2696 | var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); |
| 2697 | var cutout = cutoutPercentage / 100.0; |
| 2698 | var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))}; |
| 2699 | var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))}; |
| 2700 | var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; |
| 2701 | minSize = Math.min(availableWidth / size.width, availableHeight / size.height); |
| 2702 | offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; |
| 2703 | } |
| 2704 | chart.borderWidth = me.getMaxBorderWidth(meta.data); |
| 2705 | |
| 2706 | chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); |
| 2707 | chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0); |
| 2708 | chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); |
| 2709 | chart.offsetX = offset.x * chart.outerRadius; |
| 2710 | chart.offsetY = offset.y * chart.outerRadius; |
| 2711 | |
| 2712 | meta.total = me.calculateTotal(); |
| 2713 | |
| 2714 | me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); |
| 2715 | me.innerRadius = me.outerRadius - chart.radiusLength; |
| 2716 | |
| 2717 | helpers.each(meta.data, function(arc, index) { |
| 2718 | me.updateElement(arc, index, reset); |
| 2719 | }); |
| 2720 | }, |
| 2721 | |
| 2722 | updateElement: function(arc, index, reset) { |
| 2723 | var me = this; |
| 2724 | var chart = me.chart, |
| 2725 | chartArea = chart.chartArea, |
| 2726 | opts = chart.options, |
| 2727 | animationOpts = opts.animation, |
| 2728 | centerX = (chartArea.left + chartArea.right) / 2, |
| 2729 | centerY = (chartArea.top + chartArea.bottom) / 2, |
| 2730 | startAngle = opts.rotation, // non reset case handled later |
| 2731 | endAngle = opts.rotation, // non reset case handled later |
| 2732 | dataset = me.getDataset(), |
| 2733 | circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)), |
| 2734 | innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius, |
| 2735 | outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius, |
| 2736 | valueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; |
| 2737 | |
| 2738 | helpers.extend(arc, { |
| 2739 | // Utility |
| 2740 | _datasetIndex: me.index, |
| 2741 | _index: index, |
| 2742 | |
| 2743 | // Desired view properties |
| 2744 | _model: { |
| 2745 | x: centerX + chart.offsetX, |
| 2746 | y: centerY + chart.offsetY, |
| 2747 | startAngle: startAngle, |
| 2748 | endAngle: endAngle, |
| 2749 | circumference: circumference, |
| 2750 | outerRadius: outerRadius, |
| 2751 | innerRadius: innerRadius, |
| 2752 | label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) |
| 2753 | } |
| 2754 | }); |
| 2755 | |
| 2756 | var model = arc._model; |
| 2757 | // Resets the visual styles |
| 2758 | this.removeHoverStyle(arc); |
| 2759 | |
| 2760 | // Set correct angles if not resetting |
| 2761 | if (!reset || !animationOpts.animateRotate) { |
| 2762 | if (index === 0) { |
| 2763 | model.startAngle = opts.rotation; |
| 2764 | } else { |
| 2765 | model.startAngle = me.getMeta().data[index - 1]._model.endAngle; |
| 2766 | } |
| 2767 | |
| 2768 | model.endAngle = model.startAngle + model.circumference; |
| 2769 | } |
| 2770 | |
| 2771 | arc.pivot(); |
| 2772 | }, |
| 2773 | |
| 2774 | removeHoverStyle: function(arc) { |
| 2775 | Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); |
| 2776 | }, |
| 2777 | |
| 2778 | calculateTotal: function() { |
| 2779 | var dataset = this.getDataset(); |
| 2780 | var meta = this.getMeta(); |
| 2781 | var total = 0; |
| 2782 | var value; |
| 2783 | |
| 2784 | helpers.each(meta.data, function(element, index) { |
| 2785 | value = dataset.data[index]; |
| 2786 | if (!isNaN(value) && !element.hidden) { |
| 2787 | total += Math.abs(value); |
| 2788 | } |
| 2789 | }); |
| 2790 | |
| 2791 | /*if (total === 0) { |
| 2792 | total = NaN; |
| 2793 | }*/ |
| 2794 | |
| 2795 | return total; |
| 2796 | }, |
| 2797 | |
| 2798 | calculateCircumference: function(value) { |
| 2799 | var total = this.getMeta().total; |
| 2800 | if (total > 0 && !isNaN(value)) { |
| 2801 | return (Math.PI * 2.0) * (value / total); |
| 2802 | } else { |
| 2803 | return 0; |
| 2804 | } |
| 2805 | }, |
| 2806 | |
| 2807 | //gets the max border or hover width to properly scale pie charts |
| 2808 | getMaxBorderWidth: function (elements) { |
| 2809 | var max = 0, |
| 2810 | index = this.index, |
| 2811 | length = elements.length, |
| 2812 | borderWidth, |
| 2813 | hoverWidth; |
| 2814 | |
| 2815 | for (var i = 0; i < length; i++) { |
| 2816 | borderWidth = elements[i]._model ? elements[i]._model.borderWidth : 0; |
| 2817 | hoverWidth = elements[i]._chart ? elements[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; |
| 2818 | |
| 2819 | max = borderWidth > max ? borderWidth : max; |
| 2820 | max = hoverWidth > max ? hoverWidth : max; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2821 | } |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2822 | return max; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 2823 | } |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 2824 | }); |
| 2825 | }; |
| 2826 | |
| 2827 | },{}],18:[function(require,module,exports){ |
| 2828 | "use strict"; |
| 2829 | |
| 2830 | module.exports = function(Chart) { |
| 2831 | |
| 2832 | var helpers = Chart.helpers; |
| 2833 | |
| 2834 | Chart.defaults.line = { |
| 2835 | showLines: true, |
| 2836 | spanGaps: false, |
| 2837 | |
| 2838 | hover: { |
| 2839 | mode: "label" |
| 2840 | }, |
| 2841 | |
| 2842 | scales: { |
| 2843 | xAxes: [{ |
| 2844 | type: "category", |
| 2845 | id: 'x-axis-0' |
| 2846 | }], |
| 2847 | yAxes: [{ |
| 2848 | type: "linear", |
| 2849 | id: 'y-axis-0' |
| 2850 | }] |
| 2851 | } |
| 2852 | }; |
| 2853 | |
| 2854 | function lineEnabled(dataset, options) { |
| 2855 | return helpers.getValueOrDefault(dataset.showLine, options.showLines); |
| 2856 | } |
| 2857 | |
| 2858 | Chart.controllers.line = Chart.DatasetController.extend({ |
| 2859 | |
| 2860 | datasetElementType: Chart.elements.Line, |
| 2861 | |
| 2862 | dataElementType: Chart.elements.Point, |
| 2863 | |
| 2864 | addElementAndReset: function(index) { |
| 2865 | var me = this; |
| 2866 | var options = me.chart.options; |
| 2867 | var meta = me.getMeta(); |
| 2868 | |
| 2869 | Chart.DatasetController.prototype.addElementAndReset.call(me, index); |
| 2870 | |
| 2871 | // Make sure bezier control points are updated |
| 2872 | if (lineEnabled(me.getDataset(), options) && meta.dataset._model.tension !== 0) { |
| 2873 | me.updateBezierControlPoints(); |
| 2874 | } |
| 2875 | }, |
| 2876 | |
| 2877 | update: function(reset) { |
| 2878 | var me = this; |
| 2879 | var meta = me.getMeta(); |
| 2880 | var line = meta.dataset; |
| 2881 | var points = meta.data || []; |
| 2882 | var options = me.chart.options; |
| 2883 | var lineElementOptions = options.elements.line; |
| 2884 | var scale = me.getScaleForId(meta.yAxisID); |
| 2885 | var i, ilen, custom; |
| 2886 | var dataset = me.getDataset(); |
| 2887 | var showLine = lineEnabled(dataset, options); |
| 2888 | |
| 2889 | // Update Line |
| 2890 | if (showLine) { |
| 2891 | custom = line.custom || {}; |
| 2892 | |
| 2893 | // Compatibility: If the properties are defined with only the old name, use those values |
| 2894 | if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { |
| 2895 | dataset.lineTension = dataset.tension; |
| 2896 | } |
| 2897 | |
| 2898 | // Utility |
| 2899 | line._scale = scale; |
| 2900 | line._datasetIndex = me.index; |
| 2901 | // Data |
| 2902 | line._children = points; |
| 2903 | // Model |
| 2904 | line._model = { |
| 2905 | // Appearance |
| 2906 | // The default behavior of lines is to break at null values, according |
| 2907 | // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 |
| 2908 | // This option gives linse the ability to span gaps |
| 2909 | spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, |
| 2910 | tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension), |
| 2911 | backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), |
| 2912 | borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), |
| 2913 | borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), |
| 2914 | borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), |
| 2915 | borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), |
| 2916 | borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), |
| 2917 | borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), |
| 2918 | fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), |
| 2919 | steppedLine: custom.steppedLine ? custom.steppedLine : helpers.getValueOrDefault(dataset.steppedLine, lineElementOptions.stepped), |
| 2920 | // Scale |
| 2921 | scaleTop: scale.top, |
| 2922 | scaleBottom: scale.bottom, |
| 2923 | scaleZero: scale.getBasePixel() |
| 2924 | }; |
| 2925 | |
| 2926 | line.pivot(); |
| 2927 | } |
| 2928 | |
| 2929 | // Update Points |
| 2930 | for (i=0, ilen=points.length; i<ilen; ++i) { |
| 2931 | me.updateElement(points[i], i, reset); |
| 2932 | } |
| 2933 | |
| 2934 | if (showLine && line._model.tension !== 0) { |
| 2935 | me.updateBezierControlPoints(); |
| 2936 | } |
| 2937 | |
| 2938 | // Now pivot the point for animation |
| 2939 | for (i=0, ilen=points.length; i<ilen; ++i) { |
| 2940 | points[i].pivot(); |
| 2941 | } |
| 2942 | }, |
| 2943 | |
| 2944 | getPointBackgroundColor: function(point, index) { |
| 2945 | var backgroundColor = this.chart.options.elements.point.backgroundColor; |
| 2946 | var dataset = this.getDataset(); |
| 2947 | var custom = point.custom || {}; |
| 2948 | |
| 2949 | if (custom.backgroundColor) { |
| 2950 | backgroundColor = custom.backgroundColor; |
| 2951 | } else if (dataset.pointBackgroundColor) { |
| 2952 | backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); |
| 2953 | } else if (dataset.backgroundColor) { |
| 2954 | backgroundColor = dataset.backgroundColor; |
| 2955 | } |
| 2956 | |
| 2957 | return backgroundColor; |
| 2958 | }, |
| 2959 | |
| 2960 | getPointBorderColor: function(point, index) { |
| 2961 | var borderColor = this.chart.options.elements.point.borderColor; |
| 2962 | var dataset = this.getDataset(); |
| 2963 | var custom = point.custom || {}; |
| 2964 | |
| 2965 | if (custom.borderColor) { |
| 2966 | borderColor = custom.borderColor; |
| 2967 | } else if (dataset.pointBorderColor) { |
| 2968 | borderColor = helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); |
| 2969 | } else if (dataset.borderColor) { |
| 2970 | borderColor = dataset.borderColor; |
| 2971 | } |
| 2972 | |
| 2973 | return borderColor; |
| 2974 | }, |
| 2975 | |
| 2976 | getPointBorderWidth: function(point, index) { |
| 2977 | var borderWidth = this.chart.options.elements.point.borderWidth; |
| 2978 | var dataset = this.getDataset(); |
| 2979 | var custom = point.custom || {}; |
| 2980 | |
| 2981 | if (custom.borderWidth) { |
| 2982 | borderWidth = custom.borderWidth; |
| 2983 | } else if (dataset.pointBorderWidth) { |
| 2984 | borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); |
| 2985 | } else if (dataset.borderWidth) { |
| 2986 | borderWidth = dataset.borderWidth; |
| 2987 | } |
| 2988 | |
| 2989 | return borderWidth; |
| 2990 | }, |
| 2991 | |
| 2992 | updateElement: function(point, index, reset) { |
| 2993 | var me = this; |
| 2994 | var meta = me.getMeta(); |
| 2995 | var custom = point.custom || {}; |
| 2996 | var dataset = me.getDataset(); |
| 2997 | var datasetIndex = me.index; |
| 2998 | var value = dataset.data[index]; |
| 2999 | var yScale = me.getScaleForId(meta.yAxisID); |
| 3000 | var xScale = me.getScaleForId(meta.xAxisID); |
| 3001 | var pointOptions = me.chart.options.elements.point; |
| 3002 | var x, y; |
| 3003 | |
| 3004 | // Compatibility: If the properties are defined with only the old name, use those values |
| 3005 | if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { |
| 3006 | dataset.pointRadius = dataset.radius; |
| 3007 | } |
| 3008 | if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { |
| 3009 | dataset.pointHitRadius = dataset.hitRadius; |
| 3010 | } |
| 3011 | |
| 3012 | x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex, me.chart.isCombo); |
| 3013 | y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); |
| 3014 | |
| 3015 | // Utility |
| 3016 | point._xScale = xScale; |
| 3017 | point._yScale = yScale; |
| 3018 | point._datasetIndex = datasetIndex; |
| 3019 | point._index = index; |
| 3020 | |
| 3021 | // Desired view properties |
| 3022 | point._model = { |
| 3023 | x: x, |
| 3024 | y: y, |
| 3025 | skip: custom.skip || isNaN(x) || isNaN(y), |
| 3026 | // Appearance |
| 3027 | radius: custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), |
| 3028 | pointStyle: custom.pointStyle || helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), |
| 3029 | backgroundColor: me.getPointBackgroundColor(point, index), |
| 3030 | borderColor: me.getPointBorderColor(point, index), |
| 3031 | borderWidth: me.getPointBorderWidth(point, index), |
| 3032 | tension: meta.dataset._model ? meta.dataset._model.tension : 0, |
| 3033 | steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, |
| 3034 | // Tooltip |
| 3035 | hitRadius: custom.hitRadius || helpers.getValueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) |
| 3036 | }; |
| 3037 | }, |
| 3038 | |
| 3039 | calculatePointY: function(value, index, datasetIndex) { |
| 3040 | var me = this; |
| 3041 | var chart = me.chart; |
| 3042 | var meta = me.getMeta(); |
| 3043 | var yScale = me.getScaleForId(meta.yAxisID); |
| 3044 | var sumPos = 0; |
| 3045 | var sumNeg = 0; |
| 3046 | var i, ds, dsMeta; |
| 3047 | |
| 3048 | if (yScale.options.stacked) { |
| 3049 | for (i = 0; i < datasetIndex; i++) { |
| 3050 | ds = chart.data.datasets[i]; |
| 3051 | dsMeta = chart.getDatasetMeta(i); |
| 3052 | if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { |
| 3053 | var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); |
| 3054 | if (stackedRightValue < 0) { |
| 3055 | sumNeg += stackedRightValue || 0; |
| 3056 | } else { |
| 3057 | sumPos += stackedRightValue || 0; |
| 3058 | } |
| 3059 | } |
| 3060 | } |
| 3061 | |
| 3062 | var rightValue = Number(yScale.getRightValue(value)); |
| 3063 | if (rightValue < 0) { |
| 3064 | return yScale.getPixelForValue(sumNeg + rightValue); |
| 3065 | } else { |
| 3066 | return yScale.getPixelForValue(sumPos + rightValue); |
| 3067 | } |
| 3068 | } |
| 3069 | |
| 3070 | return yScale.getPixelForValue(value); |
| 3071 | }, |
| 3072 | |
| 3073 | updateBezierControlPoints: function() { |
| 3074 | var me = this; |
| 3075 | var meta = me.getMeta(); |
| 3076 | var area = me.chart.chartArea; |
| 3077 | |
| 3078 | // only consider points that are drawn in case the spanGaps option is ued |
| 3079 | var points = (meta.data || []).filter(function(pt) { return !pt._model.skip; }); |
| 3080 | var i, ilen, point, model, controlPoints; |
| 3081 | |
| 3082 | var needToCap = me.chart.options.elements.line.capBezierPoints; |
| 3083 | function capIfNecessary(pt, min, max) { |
| 3084 | return needToCap ? Math.max(Math.min(pt, max), min) : pt; |
| 3085 | } |
| 3086 | |
| 3087 | for (i=0, ilen=points.length; i<ilen; ++i) { |
| 3088 | point = points[i]; |
| 3089 | model = point._model; |
| 3090 | controlPoints = helpers.splineCurve( |
| 3091 | helpers.previousItem(points, i)._model, |
| 3092 | model, |
| 3093 | helpers.nextItem(points, i)._model, |
| 3094 | meta.dataset._model.tension |
| 3095 | ); |
| 3096 | |
| 3097 | model.controlPointPreviousX = capIfNecessary(controlPoints.previous.x, area.left, area.right); |
| 3098 | model.controlPointPreviousY = capIfNecessary(controlPoints.previous.y, area.top, area.bottom); |
| 3099 | model.controlPointNextX = capIfNecessary(controlPoints.next.x, area.left, area.right); |
| 3100 | model.controlPointNextY = capIfNecessary(controlPoints.next.y, area.top, area.bottom); |
| 3101 | } |
| 3102 | }, |
| 3103 | |
| 3104 | draw: function(ease) { |
| 3105 | var me = this; |
| 3106 | var meta = me.getMeta(); |
| 3107 | var points = meta.data || []; |
| 3108 | var easingDecimal = ease || 1; |
| 3109 | var i, ilen; |
| 3110 | |
| 3111 | // Transition Point Locations |
| 3112 | for (i=0, ilen=points.length; i<ilen; ++i) { |
| 3113 | points[i].transition(easingDecimal); |
| 3114 | } |
| 3115 | |
| 3116 | // Transition and Draw the line |
| 3117 | if (lineEnabled(me.getDataset(), me.chart.options)) { |
| 3118 | meta.dataset.transition(easingDecimal).draw(); |
| 3119 | } |
| 3120 | |
| 3121 | // Draw the points |
| 3122 | for (i=0, ilen=points.length; i<ilen; ++i) { |
| 3123 | points[i].draw(); |
| 3124 | } |
| 3125 | }, |
| 3126 | |
| 3127 | setHoverStyle: function(point) { |
| 3128 | // Point |
| 3129 | var dataset = this.chart.data.datasets[point._datasetIndex]; |
| 3130 | var index = point._index; |
| 3131 | var custom = point.custom || {}; |
| 3132 | var model = point._model; |
| 3133 | |
| 3134 | model.radius = custom.hoverRadius || helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); |
| 3135 | model.backgroundColor = custom.hoverBackgroundColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); |
| 3136 | model.borderColor = custom.hoverBorderColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); |
| 3137 | model.borderWidth = custom.hoverBorderWidth || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); |
| 3138 | }, |
| 3139 | |
| 3140 | removeHoverStyle: function(point) { |
| 3141 | var me = this; |
| 3142 | var dataset = me.chart.data.datasets[point._datasetIndex]; |
| 3143 | var index = point._index; |
| 3144 | var custom = point.custom || {}; |
| 3145 | var model = point._model; |
| 3146 | |
| 3147 | // Compatibility: If the properties are defined with only the old name, use those values |
| 3148 | if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { |
| 3149 | dataset.pointRadius = dataset.radius; |
| 3150 | } |
| 3151 | |
| 3152 | model.radius = custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius); |
| 3153 | model.backgroundColor = me.getPointBackgroundColor(point, index); |
| 3154 | model.borderColor = me.getPointBorderColor(point, index); |
| 3155 | model.borderWidth = me.getPointBorderWidth(point, index); |
| 3156 | } |
| 3157 | }); |
| 3158 | }; |
| 3159 | |
| 3160 | },{}],19:[function(require,module,exports){ |
| 3161 | "use strict"; |
| 3162 | |
| 3163 | module.exports = function(Chart) { |
| 3164 | |
| 3165 | var helpers = Chart.helpers; |
| 3166 | |
| 3167 | Chart.defaults.polarArea = { |
| 3168 | |
| 3169 | scale: { |
| 3170 | type: "radialLinear", |
| 3171 | lineArc: true, // so that lines are circular |
| 3172 | ticks: { |
| 3173 | beginAtZero: true |
| 3174 | } |
| 3175 | }, |
| 3176 | |
| 3177 | //Boolean - Whether to animate the rotation of the chart |
| 3178 | animation: { |
| 3179 | animateRotate: true, |
| 3180 | animateScale: true |
| 3181 | }, |
| 3182 | |
| 3183 | startAngle: -0.5 * Math.PI, |
| 3184 | aspectRatio: 1, |
| 3185 | legendCallback: function(chart) { |
| 3186 | var text = []; |
| 3187 | text.push('<ul class="' + chart.id + '-legend">'); |
| 3188 | |
| 3189 | var data = chart.data; |
| 3190 | var datasets = data.datasets; |
| 3191 | var labels = data.labels; |
| 3192 | |
| 3193 | if (datasets.length) { |
| 3194 | for (var i = 0; i < datasets[0].data.length; ++i) { |
| 3195 | text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '">'); |
| 3196 | if (labels[i]) { |
| 3197 | text.push(labels[i]); |
| 3198 | } |
| 3199 | text.push('</span></li>'); |
| 3200 | } |
| 3201 | } |
| 3202 | |
| 3203 | text.push('</ul>'); |
| 3204 | return text.join(""); |
| 3205 | }, |
| 3206 | legend: { |
| 3207 | labels: { |
| 3208 | generateLabels: function(chart) { |
| 3209 | var data = chart.data; |
| 3210 | if (data.labels.length && data.datasets.length) { |
| 3211 | return data.labels.map(function(label, i) { |
| 3212 | var meta = chart.getDatasetMeta(0); |
| 3213 | var ds = data.datasets[0]; |
| 3214 | var arc = meta.data[i]; |
| 3215 | var custom = arc.custom || {}; |
| 3216 | var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; |
| 3217 | var arcOpts = chart.options.elements.arc; |
| 3218 | var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); |
| 3219 | var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); |
| 3220 | var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); |
| 3221 | |
| 3222 | return { |
| 3223 | text: label, |
| 3224 | fillStyle: fill, |
| 3225 | strokeStyle: stroke, |
| 3226 | lineWidth: bw, |
| 3227 | hidden: isNaN(ds.data[i]) || meta.data[i].hidden, |
| 3228 | |
| 3229 | // Extra data used for toggling the correct item |
| 3230 | index: i |
| 3231 | }; |
| 3232 | }); |
| 3233 | } else { |
| 3234 | return []; |
| 3235 | } |
| 3236 | } |
| 3237 | }, |
| 3238 | |
| 3239 | onClick: function(e, legendItem) { |
| 3240 | var index = legendItem.index; |
| 3241 | var chart = this.chart; |
| 3242 | var i, ilen, meta; |
| 3243 | |
| 3244 | for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { |
| 3245 | meta = chart.getDatasetMeta(i); |
| 3246 | meta.data[index].hidden = !meta.data[index].hidden; |
| 3247 | } |
| 3248 | |
| 3249 | chart.update(); |
| 3250 | } |
| 3251 | }, |
| 3252 | |
| 3253 | // Need to override these to give a nice default |
| 3254 | tooltips: { |
| 3255 | callbacks: { |
| 3256 | title: function() { |
| 3257 | return ''; |
| 3258 | }, |
| 3259 | label: function(tooltipItem, data) { |
| 3260 | return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel; |
| 3261 | } |
| 3262 | } |
| 3263 | } |
| 3264 | }; |
| 3265 | |
| 3266 | Chart.controllers.polarArea = Chart.DatasetController.extend({ |
| 3267 | |
| 3268 | dataElementType: Chart.elements.Arc, |
| 3269 | |
| 3270 | linkScales: helpers.noop, |
| 3271 | |
| 3272 | update: function(reset) { |
| 3273 | var me = this; |
| 3274 | var chart = me.chart; |
| 3275 | var chartArea = chart.chartArea; |
| 3276 | var meta = me.getMeta(); |
| 3277 | var opts = chart.options; |
| 3278 | var arcOpts = opts.elements.arc; |
| 3279 | var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); |
| 3280 | chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); |
| 3281 | chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); |
| 3282 | chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); |
| 3283 | |
| 3284 | me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); |
| 3285 | me.innerRadius = me.outerRadius - chart.radiusLength; |
| 3286 | |
| 3287 | meta.count = me.countVisibleElements(); |
| 3288 | |
| 3289 | helpers.each(meta.data, function(arc, index) { |
| 3290 | me.updateElement(arc, index, reset); |
| 3291 | }); |
| 3292 | }, |
| 3293 | |
| 3294 | updateElement: function(arc, index, reset) { |
| 3295 | var me = this; |
| 3296 | var chart = me.chart; |
| 3297 | var dataset = me.getDataset(); |
| 3298 | var opts = chart.options; |
| 3299 | var animationOpts = opts.animation; |
| 3300 | var scale = chart.scale; |
| 3301 | var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; |
| 3302 | var labels = chart.data.labels; |
| 3303 | |
| 3304 | var circumference = me.calculateCircumference(dataset.data[index]); |
| 3305 | var centerX = scale.xCenter; |
| 3306 | var centerY = scale.yCenter; |
| 3307 | |
| 3308 | // If there is NaN data before us, we need to calculate the starting angle correctly. |
| 3309 | // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data |
| 3310 | var visibleCount = 0; |
| 3311 | var meta = me.getMeta(); |
| 3312 | for (var i = 0; i < index; ++i) { |
| 3313 | if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) { |
| 3314 | ++visibleCount; |
| 3315 | } |
| 3316 | } |
| 3317 | |
| 3318 | //var negHalfPI = -0.5 * Math.PI; |
| 3319 | var datasetStartAngle = opts.startAngle; |
| 3320 | var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); |
| 3321 | var startAngle = datasetStartAngle + (circumference * visibleCount); |
| 3322 | var endAngle = startAngle + (arc.hidden ? 0 : circumference); |
| 3323 | |
| 3324 | var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); |
| 3325 | |
| 3326 | helpers.extend(arc, { |
| 3327 | // Utility |
| 3328 | _datasetIndex: me.index, |
| 3329 | _index: index, |
| 3330 | _scale: scale, |
| 3331 | |
| 3332 | // Desired view properties |
| 3333 | _model: { |
| 3334 | x: centerX, |
| 3335 | y: centerY, |
| 3336 | innerRadius: 0, |
| 3337 | outerRadius: reset ? resetRadius : distance, |
| 3338 | startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, |
| 3339 | endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, |
| 3340 | label: getValueAtIndexOrDefault(labels, index, labels[index]) |
| 3341 | } |
| 3342 | }); |
| 3343 | |
| 3344 | // Apply border and fill style |
| 3345 | me.removeHoverStyle(arc); |
| 3346 | |
| 3347 | arc.pivot(); |
| 3348 | }, |
| 3349 | |
| 3350 | removeHoverStyle: function(arc) { |
| 3351 | Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); |
| 3352 | }, |
| 3353 | |
| 3354 | countVisibleElements: function() { |
| 3355 | var dataset = this.getDataset(); |
| 3356 | var meta = this.getMeta(); |
| 3357 | var count = 0; |
| 3358 | |
| 3359 | helpers.each(meta.data, function(element, index) { |
| 3360 | if (!isNaN(dataset.data[index]) && !element.hidden) { |
| 3361 | count++; |
| 3362 | } |
| 3363 | }); |
| 3364 | |
| 3365 | return count; |
| 3366 | }, |
| 3367 | |
| 3368 | calculateCircumference: function(value) { |
| 3369 | var count = this.getMeta().count; |
| 3370 | if (count > 0 && !isNaN(value)) { |
| 3371 | return (2 * Math.PI) / count; |
| 3372 | } else { |
| 3373 | return 0; |
| 3374 | } |
| 3375 | } |
| 3376 | }); |
| 3377 | }; |
| 3378 | |
| 3379 | },{}],20:[function(require,module,exports){ |
| 3380 | "use strict"; |
| 3381 | |
| 3382 | module.exports = function(Chart) { |
| 3383 | |
| 3384 | var helpers = Chart.helpers; |
| 3385 | |
| 3386 | Chart.defaults.radar = { |
| 3387 | scale: { |
| 3388 | type: "radialLinear" |
| 3389 | }, |
| 3390 | elements: { |
| 3391 | line: { |
| 3392 | tension: 0 // no bezier in radar |
| 3393 | } |
| 3394 | } |
| 3395 | }; |
| 3396 | |
| 3397 | Chart.controllers.radar = Chart.DatasetController.extend({ |
| 3398 | |
| 3399 | datasetElementType: Chart.elements.Line, |
| 3400 | |
| 3401 | dataElementType: Chart.elements.Point, |
| 3402 | |
| 3403 | linkScales: helpers.noop, |
| 3404 | |
| 3405 | addElementAndReset: function(index) { |
| 3406 | Chart.DatasetController.prototype.addElementAndReset.call(this, index); |
| 3407 | |
| 3408 | // Make sure bezier control points are updated |
| 3409 | this.updateBezierControlPoints(); |
| 3410 | }, |
| 3411 | |
| 3412 | update: function(reset) { |
| 3413 | var me = this; |
| 3414 | var meta = me.getMeta(); |
| 3415 | var line = meta.dataset; |
| 3416 | var points = meta.data; |
| 3417 | var custom = line.custom || {}; |
| 3418 | var dataset = me.getDataset(); |
| 3419 | var lineElementOptions = me.chart.options.elements.line; |
| 3420 | var scale = me.chart.scale; |
| 3421 | |
| 3422 | // Compatibility: If the properties are defined with only the old name, use those values |
| 3423 | if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { |
| 3424 | dataset.lineTension = dataset.tension; |
| 3425 | } |
| 3426 | |
| 3427 | helpers.extend(meta.dataset, { |
| 3428 | // Utility |
| 3429 | _datasetIndex: me.index, |
| 3430 | // Data |
| 3431 | _children: points, |
| 3432 | _loop: true, |
| 3433 | // Model |
| 3434 | _model: { |
| 3435 | // Appearance |
| 3436 | tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension), |
| 3437 | backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), |
| 3438 | borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), |
| 3439 | borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), |
| 3440 | fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), |
| 3441 | borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), |
| 3442 | borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), |
| 3443 | borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), |
| 3444 | borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), |
| 3445 | |
| 3446 | // Scale |
| 3447 | scaleTop: scale.top, |
| 3448 | scaleBottom: scale.bottom, |
| 3449 | scaleZero: scale.getBasePosition() |
| 3450 | } |
| 3451 | }); |
| 3452 | |
| 3453 | meta.dataset.pivot(); |
| 3454 | |
| 3455 | // Update Points |
| 3456 | helpers.each(points, function(point, index) { |
| 3457 | me.updateElement(point, index, reset); |
| 3458 | }, me); |
| 3459 | |
| 3460 | |
| 3461 | // Update bezier control points |
| 3462 | me.updateBezierControlPoints(); |
| 3463 | }, |
| 3464 | updateElement: function(point, index, reset) { |
| 3465 | var me = this; |
| 3466 | var custom = point.custom || {}; |
| 3467 | var dataset = me.getDataset(); |
| 3468 | var scale = me.chart.scale; |
| 3469 | var pointElementOptions = me.chart.options.elements.point; |
| 3470 | var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); |
| 3471 | |
| 3472 | helpers.extend(point, { |
| 3473 | // Utility |
| 3474 | _datasetIndex: me.index, |
| 3475 | _index: index, |
| 3476 | _scale: scale, |
| 3477 | |
| 3478 | // Desired view properties |
| 3479 | _model: { |
| 3480 | x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales |
| 3481 | y: reset ? scale.yCenter : pointPosition.y, |
| 3482 | |
| 3483 | // Appearance |
| 3484 | tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, me.chart.options.elements.line.tension), |
| 3485 | radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), |
| 3486 | backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), |
| 3487 | borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), |
| 3488 | borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), |
| 3489 | pointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), |
| 3490 | |
| 3491 | // Tooltip |
| 3492 | hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) |
| 3493 | } |
| 3494 | }); |
| 3495 | |
| 3496 | point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); |
| 3497 | }, |
| 3498 | updateBezierControlPoints: function() { |
| 3499 | var chartArea = this.chart.chartArea; |
| 3500 | var meta = this.getMeta(); |
| 3501 | |
| 3502 | helpers.each(meta.data, function(point, index) { |
| 3503 | var model = point._model; |
| 3504 | var controlPoints = helpers.splineCurve( |
| 3505 | helpers.previousItem(meta.data, index, true)._model, |
| 3506 | model, |
| 3507 | helpers.nextItem(meta.data, index, true)._model, |
| 3508 | model.tension |
| 3509 | ); |
| 3510 | |
| 3511 | // Prevent the bezier going outside of the bounds of the graph |
| 3512 | model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); |
| 3513 | model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); |
| 3514 | |
| 3515 | model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); |
| 3516 | model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); |
| 3517 | |
| 3518 | // Now pivot the point for animation |
| 3519 | point.pivot(); |
| 3520 | }); |
| 3521 | }, |
| 3522 | |
| 3523 | draw: function(ease) { |
| 3524 | var meta = this.getMeta(); |
| 3525 | var easingDecimal = ease || 1; |
| 3526 | |
| 3527 | // Transition Point Locations |
| 3528 | helpers.each(meta.data, function(point) { |
| 3529 | point.transition(easingDecimal); |
| 3530 | }); |
| 3531 | |
| 3532 | // Transition and Draw the line |
| 3533 | meta.dataset.transition(easingDecimal).draw(); |
| 3534 | |
| 3535 | // Draw the points |
| 3536 | helpers.each(meta.data, function(point) { |
| 3537 | point.draw(); |
| 3538 | }); |
| 3539 | }, |
| 3540 | |
| 3541 | setHoverStyle: function(point) { |
| 3542 | // Point |
| 3543 | var dataset = this.chart.data.datasets[point._datasetIndex]; |
| 3544 | var custom = point.custom || {}; |
| 3545 | var index = point._index; |
| 3546 | var model = point._model; |
| 3547 | |
| 3548 | model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); |
| 3549 | model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); |
| 3550 | model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); |
| 3551 | model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); |
| 3552 | }, |
| 3553 | |
| 3554 | removeHoverStyle: function(point) { |
| 3555 | var dataset = this.chart.data.datasets[point._datasetIndex]; |
| 3556 | var custom = point.custom || {}; |
| 3557 | var index = point._index; |
| 3558 | var model = point._model; |
| 3559 | var pointElementOptions = this.chart.options.elements.point; |
| 3560 | |
| 3561 | model.radius = custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, pointElementOptions.radius); |
| 3562 | model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor); |
| 3563 | model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor); |
| 3564 | model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth); |
| 3565 | } |
| 3566 | }); |
| 3567 | }; |
| 3568 | |
| 3569 | },{}],21:[function(require,module,exports){ |
| 3570 | /*global window: false */ |
| 3571 | "use strict"; |
| 3572 | |
| 3573 | module.exports = function(Chart) { |
| 3574 | |
| 3575 | var helpers = Chart.helpers; |
| 3576 | |
| 3577 | Chart.defaults.global.animation = { |
| 3578 | duration: 1000, |
| 3579 | easing: "easeOutQuart", |
| 3580 | onProgress: helpers.noop, |
| 3581 | onComplete: helpers.noop |
| 3582 | }; |
| 3583 | |
| 3584 | Chart.Animation = Chart.Element.extend({ |
| 3585 | currentStep: null, // the current animation step |
| 3586 | numSteps: 60, // default number of steps |
| 3587 | easing: "", // the easing to use for this animation |
| 3588 | render: null, // render function used by the animation service |
| 3589 | |
| 3590 | onAnimationProgress: null, // user specified callback to fire on each step of the animation |
| 3591 | onAnimationComplete: null // user specified callback to fire when the animation finishes |
| 3592 | }); |
| 3593 | |
| 3594 | Chart.animationService = { |
| 3595 | frameDuration: 17, |
| 3596 | animations: [], |
| 3597 | dropFrames: 0, |
| 3598 | request: null, |
| 3599 | addAnimation: function(chartInstance, animationObject, duration, lazy) { |
| 3600 | var me = this; |
| 3601 | |
| 3602 | if (!lazy) { |
| 3603 | chartInstance.animating = true; |
| 3604 | } |
| 3605 | |
| 3606 | for (var index = 0; index < me.animations.length; ++index) { |
| 3607 | if (me.animations[index].chartInstance === chartInstance) { |
| 3608 | // replacing an in progress animation |
| 3609 | me.animations[index].animationObject = animationObject; |
| 3610 | return; |
| 3611 | } |
| 3612 | } |
| 3613 | |
| 3614 | me.animations.push({ |
| 3615 | chartInstance: chartInstance, |
| 3616 | animationObject: animationObject |
| 3617 | }); |
| 3618 | |
| 3619 | // If there are no animations queued, manually kickstart a digest, for lack of a better word |
| 3620 | if (me.animations.length === 1) { |
| 3621 | me.requestAnimationFrame(); |
| 3622 | } |
| 3623 | }, |
| 3624 | // Cancel the animation for a given chart instance |
| 3625 | cancelAnimation: function(chartInstance) { |
| 3626 | var index = helpers.findIndex(this.animations, function(animationWrapper) { |
| 3627 | return animationWrapper.chartInstance === chartInstance; |
| 3628 | }); |
| 3629 | |
| 3630 | if (index !== -1) { |
| 3631 | this.animations.splice(index, 1); |
| 3632 | chartInstance.animating = false; |
| 3633 | } |
| 3634 | }, |
| 3635 | requestAnimationFrame: function() { |
| 3636 | var me = this; |
| 3637 | if (me.request === null) { |
| 3638 | // Skip animation frame requests until the active one is executed. |
| 3639 | // This can happen when processing mouse events, e.g. 'mousemove' |
| 3640 | // and 'mouseout' events will trigger multiple renders. |
| 3641 | me.request = helpers.requestAnimFrame.call(window, function() { |
| 3642 | me.request = null; |
| 3643 | me.startDigest(); |
| 3644 | }); |
| 3645 | } |
| 3646 | }, |
| 3647 | startDigest: function() { |
| 3648 | var me = this; |
| 3649 | |
| 3650 | var startTime = Date.now(); |
| 3651 | var framesToDrop = 0; |
| 3652 | |
| 3653 | if (me.dropFrames > 1) { |
| 3654 | framesToDrop = Math.floor(me.dropFrames); |
| 3655 | me.dropFrames = me.dropFrames % 1; |
| 3656 | } |
| 3657 | |
| 3658 | var i = 0; |
| 3659 | while (i < me.animations.length) { |
| 3660 | if (me.animations[i].animationObject.currentStep === null) { |
| 3661 | me.animations[i].animationObject.currentStep = 0; |
| 3662 | } |
| 3663 | |
| 3664 | me.animations[i].animationObject.currentStep += 1 + framesToDrop; |
| 3665 | |
| 3666 | if (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) { |
| 3667 | me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps; |
| 3668 | } |
| 3669 | |
| 3670 | me.animations[i].animationObject.render(me.animations[i].chartInstance, me.animations[i].animationObject); |
| 3671 | if (me.animations[i].animationObject.onAnimationProgress && me.animations[i].animationObject.onAnimationProgress.call) { |
| 3672 | me.animations[i].animationObject.onAnimationProgress.call(me.animations[i].chartInstance, me.animations[i]); |
| 3673 | } |
| 3674 | |
| 3675 | if (me.animations[i].animationObject.currentStep === me.animations[i].animationObject.numSteps) { |
| 3676 | if (me.animations[i].animationObject.onAnimationComplete && me.animations[i].animationObject.onAnimationComplete.call) { |
| 3677 | me.animations[i].animationObject.onAnimationComplete.call(me.animations[i].chartInstance, me.animations[i]); |
| 3678 | } |
| 3679 | |
| 3680 | // executed the last frame. Remove the animation. |
| 3681 | me.animations[i].chartInstance.animating = false; |
| 3682 | |
| 3683 | me.animations.splice(i, 1); |
| 3684 | } else { |
| 3685 | ++i; |
| 3686 | } |
| 3687 | } |
| 3688 | |
| 3689 | var endTime = Date.now(); |
| 3690 | var dropFrames = (endTime - startTime) / me.frameDuration; |
| 3691 | |
| 3692 | me.dropFrames += dropFrames; |
| 3693 | |
| 3694 | // Do we have more stuff to animate? |
| 3695 | if (me.animations.length > 0) { |
| 3696 | me.requestAnimationFrame(); |
| 3697 | } |
| 3698 | } |
| 3699 | }; |
| 3700 | }; |
| 3701 | },{}],22:[function(require,module,exports){ |
| 3702 | "use strict"; |
| 3703 | |
| 3704 | module.exports = function(Chart) { |
| 3705 | // Global Chart canvas helpers object for drawing items to canvas |
| 3706 | var helpers = Chart.canvasHelpers = {}; |
| 3707 | |
| 3708 | helpers.drawPoint = function(ctx, pointStyle, radius, x, y) { |
| 3709 | var type, edgeLength, xOffset, yOffset, height, size; |
| 3710 | |
| 3711 | if (typeof pointStyle === 'object') { |
| 3712 | type = pointStyle.toString(); |
| 3713 | if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { |
| 3714 | ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2); |
| 3715 | return; |
| 3716 | } |
| 3717 | } |
| 3718 | |
| 3719 | if (isNaN(radius) || radius <= 0) { |
| 3720 | return; |
| 3721 | } |
| 3722 | |
| 3723 | switch (pointStyle) { |
| 3724 | // Default includes circle |
| 3725 | default: |
| 3726 | ctx.beginPath(); |
| 3727 | ctx.arc(x, y, radius, 0, Math.PI * 2); |
| 3728 | ctx.closePath(); |
| 3729 | ctx.fill(); |
| 3730 | break; |
| 3731 | case 'triangle': |
| 3732 | ctx.beginPath(); |
| 3733 | edgeLength = 3 * radius / Math.sqrt(3); |
| 3734 | height = edgeLength * Math.sqrt(3) / 2; |
| 3735 | ctx.moveTo(x - edgeLength / 2, y + height / 3); |
| 3736 | ctx.lineTo(x + edgeLength / 2, y + height / 3); |
| 3737 | ctx.lineTo(x, y - 2 * height / 3); |
| 3738 | ctx.closePath(); |
| 3739 | ctx.fill(); |
| 3740 | break; |
| 3741 | case 'rect': |
| 3742 | size = 1 / Math.SQRT2 * radius; |
| 3743 | ctx.beginPath(); |
| 3744 | ctx.fillRect(x - size, y - size, 2 * size, 2 * size); |
| 3745 | ctx.strokeRect(x - size, y - size, 2 * size, 2 * size); |
| 3746 | break; |
| 3747 | case 'rectRot': |
| 3748 | size = 1 / Math.SQRT2 * radius; |
| 3749 | ctx.beginPath(); |
| 3750 | ctx.moveTo(x - size, y); |
| 3751 | ctx.lineTo(x, y + size); |
| 3752 | ctx.lineTo(x + size, y); |
| 3753 | ctx.lineTo(x, y - size); |
| 3754 | ctx.closePath(); |
| 3755 | ctx.fill(); |
| 3756 | break; |
| 3757 | case 'cross': |
| 3758 | ctx.beginPath(); |
| 3759 | ctx.moveTo(x, y + radius); |
| 3760 | ctx.lineTo(x, y - radius); |
| 3761 | ctx.moveTo(x - radius, y); |
| 3762 | ctx.lineTo(x + radius, y); |
| 3763 | ctx.closePath(); |
| 3764 | break; |
| 3765 | case 'crossRot': |
| 3766 | ctx.beginPath(); |
| 3767 | xOffset = Math.cos(Math.PI / 4) * radius; |
| 3768 | yOffset = Math.sin(Math.PI / 4) * radius; |
| 3769 | ctx.moveTo(x - xOffset, y - yOffset); |
| 3770 | ctx.lineTo(x + xOffset, y + yOffset); |
| 3771 | ctx.moveTo(x - xOffset, y + yOffset); |
| 3772 | ctx.lineTo(x + xOffset, y - yOffset); |
| 3773 | ctx.closePath(); |
| 3774 | break; |
| 3775 | case 'star': |
| 3776 | ctx.beginPath(); |
| 3777 | ctx.moveTo(x, y + radius); |
| 3778 | ctx.lineTo(x, y - radius); |
| 3779 | ctx.moveTo(x - radius, y); |
| 3780 | ctx.lineTo(x + radius, y); |
| 3781 | xOffset = Math.cos(Math.PI / 4) * radius; |
| 3782 | yOffset = Math.sin(Math.PI / 4) * radius; |
| 3783 | ctx.moveTo(x - xOffset, y - yOffset); |
| 3784 | ctx.lineTo(x + xOffset, y + yOffset); |
| 3785 | ctx.moveTo(x - xOffset, y + yOffset); |
| 3786 | ctx.lineTo(x + xOffset, y - yOffset); |
| 3787 | ctx.closePath(); |
| 3788 | break; |
| 3789 | case 'line': |
| 3790 | ctx.beginPath(); |
| 3791 | ctx.moveTo(x - radius, y); |
| 3792 | ctx.lineTo(x + radius, y); |
| 3793 | ctx.closePath(); |
| 3794 | break; |
| 3795 | case 'dash': |
| 3796 | ctx.beginPath(); |
| 3797 | ctx.moveTo(x, y); |
| 3798 | ctx.lineTo(x + radius, y); |
| 3799 | ctx.closePath(); |
| 3800 | break; |
| 3801 | } |
| 3802 | |
| 3803 | ctx.stroke(); |
| 3804 | }; |
| 3805 | }; |
| 3806 | },{}],23:[function(require,module,exports){ |
| 3807 | "use strict"; |
| 3808 | |
| 3809 | module.exports = function(Chart) { |
| 3810 | |
| 3811 | var helpers = Chart.helpers; |
| 3812 | //Create a dictionary of chart types, to allow for extension of existing types |
| 3813 | Chart.types = {}; |
| 3814 | |
| 3815 | //Store a reference to each instance - allowing us to globally resize chart instances on window resize. |
| 3816 | //Destroy method on the chart will remove the instance of the chart from this reference. |
| 3817 | Chart.instances = {}; |
| 3818 | |
| 3819 | // Controllers available for dataset visualization eg. bar, line, slice, etc. |
| 3820 | Chart.controllers = {}; |
| 3821 | |
| 3822 | /** |
| 3823 | * @class Chart.Controller |
| 3824 | * The main controller of a chart. |
| 3825 | */ |
| 3826 | Chart.Controller = function(instance) { |
| 3827 | |
| 3828 | this.chart = instance; |
| 3829 | this.config = instance.config; |
| 3830 | this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {}); |
| 3831 | this.id = helpers.uid(); |
| 3832 | |
| 3833 | Object.defineProperty(this, 'data', { |
| 3834 | get: function() { |
| 3835 | return this.config.data; |
| 3836 | } |
| 3837 | }); |
| 3838 | |
| 3839 | //Add the chart instance to the global namespace |
| 3840 | Chart.instances[this.id] = this; |
| 3841 | |
| 3842 | if (this.options.responsive) { |
| 3843 | // Silent resize before chart draws |
| 3844 | this.resize(true); |
| 3845 | } |
| 3846 | |
| 3847 | this.initialize(); |
| 3848 | |
| 3849 | return this; |
| 3850 | }; |
| 3851 | |
| 3852 | helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ { |
| 3853 | |
| 3854 | initialize: function() { |
| 3855 | var me = this; |
| 3856 | // Before init plugin notification |
| 3857 | Chart.plugins.notify('beforeInit', [me]); |
| 3858 | |
| 3859 | me.bindEvents(); |
| 3860 | |
| 3861 | // Make sure controllers are built first so that each dataset is bound to an axis before the scales |
| 3862 | // are built |
| 3863 | me.ensureScalesHaveIDs(); |
| 3864 | me.buildOrUpdateControllers(); |
| 3865 | me.buildScales(); |
| 3866 | me.updateLayout(); |
| 3867 | me.resetElements(); |
| 3868 | me.initToolTip(); |
| 3869 | me.update(); |
| 3870 | |
| 3871 | // After init plugin notification |
| 3872 | Chart.plugins.notify('afterInit', [me]); |
| 3873 | |
| 3874 | return me; |
| 3875 | }, |
| 3876 | |
| 3877 | clear: function() { |
| 3878 | helpers.clear(this.chart); |
| 3879 | return this; |
| 3880 | }, |
| 3881 | |
| 3882 | stop: function() { |
| 3883 | // Stops any current animation loop occuring |
| 3884 | Chart.animationService.cancelAnimation(this); |
| 3885 | return this; |
| 3886 | }, |
| 3887 | |
| 3888 | resize: function resize(silent) { |
| 3889 | var me = this; |
| 3890 | var chart = me.chart; |
| 3891 | var canvas = chart.canvas; |
| 3892 | var newWidth = helpers.getMaximumWidth(canvas); |
| 3893 | var aspectRatio = chart.aspectRatio; |
| 3894 | var newHeight = (me.options.maintainAspectRatio && isNaN(aspectRatio) === false && isFinite(aspectRatio) && aspectRatio !== 0) ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas); |
| 3895 | |
| 3896 | var sizeChanged = chart.width !== newWidth || chart.height !== newHeight; |
| 3897 | |
| 3898 | if (!sizeChanged) { |
| 3899 | return me; |
| 3900 | } |
| 3901 | |
| 3902 | canvas.width = chart.width = newWidth; |
| 3903 | canvas.height = chart.height = newHeight; |
| 3904 | |
| 3905 | helpers.retinaScale(chart); |
| 3906 | |
| 3907 | // Notify any plugins about the resize |
| 3908 | var newSize = { width: newWidth, height: newHeight }; |
| 3909 | Chart.plugins.notify('resize', [me, newSize]); |
| 3910 | |
| 3911 | // Notify of resize |
| 3912 | if (me.options.onResize) { |
| 3913 | me.options.onResize(me, newSize); |
| 3914 | } |
| 3915 | |
| 3916 | if (!silent) { |
| 3917 | me.stop(); |
| 3918 | me.update(me.options.responsiveAnimationDuration); |
| 3919 | } |
| 3920 | |
| 3921 | return me; |
| 3922 | }, |
| 3923 | |
| 3924 | ensureScalesHaveIDs: function() { |
| 3925 | var options = this.options; |
| 3926 | var scalesOptions = options.scales || {}; |
| 3927 | var scaleOptions = options.scale; |
| 3928 | |
| 3929 | helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { |
| 3930 | xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); |
| 3931 | }); |
| 3932 | |
| 3933 | helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { |
| 3934 | yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); |
| 3935 | }); |
| 3936 | |
| 3937 | if (scaleOptions) { |
| 3938 | scaleOptions.id = scaleOptions.id || 'scale'; |
| 3939 | } |
| 3940 | }, |
| 3941 | |
| 3942 | /** |
| 3943 | * Builds a map of scale ID to scale object for future lookup. |
| 3944 | */ |
| 3945 | buildScales: function() { |
| 3946 | var me = this; |
| 3947 | var options = me.options; |
| 3948 | var scales = me.scales = {}; |
| 3949 | var items = []; |
| 3950 | |
| 3951 | if (options.scales) { |
| 3952 | items = items.concat( |
| 3953 | (options.scales.xAxes || []).map(function(xAxisOptions) { |
| 3954 | return { options: xAxisOptions, dtype: 'category' }; }), |
| 3955 | (options.scales.yAxes || []).map(function(yAxisOptions) { |
| 3956 | return { options: yAxisOptions, dtype: 'linear' }; })); |
| 3957 | } |
| 3958 | |
| 3959 | if (options.scale) { |
| 3960 | items.push({ options: options.scale, dtype: 'radialLinear', isDefault: true }); |
| 3961 | } |
| 3962 | |
| 3963 | helpers.each(items, function(item) { |
| 3964 | var scaleOptions = item.options; |
| 3965 | var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype); |
| 3966 | var scaleClass = Chart.scaleService.getScaleConstructor(scaleType); |
| 3967 | if (!scaleClass) { |
| 3968 | return; |
| 3969 | } |
| 3970 | |
| 3971 | var scale = new scaleClass({ |
| 3972 | id: scaleOptions.id, |
| 3973 | options: scaleOptions, |
| 3974 | ctx: me.chart.ctx, |
| 3975 | chart: me |
| 3976 | }); |
| 3977 | |
| 3978 | scales[scale.id] = scale; |
| 3979 | |
| 3980 | // TODO(SB): I think we should be able to remove this custom case (options.scale) |
| 3981 | // and consider it as a regular scale part of the "scales"" map only! This would |
| 3982 | // make the logic easier and remove some useless? custom code. |
| 3983 | if (item.isDefault) { |
| 3984 | me.scale = scale; |
| 3985 | } |
| 3986 | }); |
| 3987 | |
| 3988 | Chart.scaleService.addScalesToLayout(this); |
| 3989 | }, |
| 3990 | |
| 3991 | updateLayout: function() { |
| 3992 | Chart.layoutService.update(this, this.chart.width, this.chart.height); |
| 3993 | }, |
| 3994 | |
| 3995 | buildOrUpdateControllers: function() { |
| 3996 | var me = this; |
| 3997 | var types = []; |
| 3998 | var newControllers = []; |
| 3999 | |
| 4000 | helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 4001 | var meta = me.getDatasetMeta(datasetIndex); |
| 4002 | if (!meta.type) { |
| 4003 | meta.type = dataset.type || me.config.type; |
| 4004 | } |
| 4005 | |
| 4006 | types.push(meta.type); |
| 4007 | |
| 4008 | if (meta.controller) { |
| 4009 | meta.controller.updateIndex(datasetIndex); |
| 4010 | } else { |
| 4011 | meta.controller = new Chart.controllers[meta.type](me, datasetIndex); |
| 4012 | newControllers.push(meta.controller); |
| 4013 | } |
| 4014 | }, me); |
| 4015 | |
| 4016 | if (types.length > 1) { |
| 4017 | for (var i = 1; i < types.length; i++) { |
| 4018 | if (types[i] !== types[i - 1]) { |
| 4019 | me.isCombo = true; |
| 4020 | break; |
| 4021 | } |
| 4022 | } |
| 4023 | } |
| 4024 | |
| 4025 | return newControllers; |
| 4026 | }, |
| 4027 | |
| 4028 | resetElements: function() { |
| 4029 | var me = this; |
| 4030 | helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 4031 | me.getDatasetMeta(datasetIndex).controller.reset(); |
| 4032 | }, me); |
| 4033 | }, |
| 4034 | |
| 4035 | update: function update(animationDuration, lazy) { |
| 4036 | var me = this; |
| 4037 | Chart.plugins.notify('beforeUpdate', [me]); |
| 4038 | |
| 4039 | // In case the entire data object changed |
| 4040 | me.tooltip._data = me.data; |
| 4041 | |
| 4042 | // Make sure dataset controllers are updated and new controllers are reset |
| 4043 | var newControllers = me.buildOrUpdateControllers(); |
| 4044 | |
| 4045 | // Make sure all dataset controllers have correct meta data counts |
| 4046 | helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 4047 | me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); |
| 4048 | }, me); |
| 4049 | |
| 4050 | Chart.layoutService.update(me, me.chart.width, me.chart.height); |
| 4051 | |
| 4052 | // Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages |
| 4053 | Chart.plugins.notify('afterScaleUpdate', [me]); |
| 4054 | |
| 4055 | // Can only reset the new controllers after the scales have been updated |
| 4056 | helpers.each(newControllers, function(controller) { |
| 4057 | controller.reset(); |
| 4058 | }); |
| 4059 | |
| 4060 | me.updateDatasets(); |
| 4061 | |
| 4062 | // Do this before render so that any plugins that need final scale updates can use it |
| 4063 | Chart.plugins.notify('afterUpdate', [me]); |
| 4064 | |
| 4065 | me.render(animationDuration, lazy); |
| 4066 | }, |
| 4067 | |
| 4068 | /** |
| 4069 | * @method beforeDatasetsUpdate |
| 4070 | * @description Called before all datasets are updated. If a plugin returns false, |
| 4071 | * the datasets update will be cancelled until another chart update is triggered. |
| 4072 | * @param {Object} instance the chart instance being updated. |
| 4073 | * @returns {Boolean} false to cancel the datasets update. |
| 4074 | * @memberof Chart.PluginBase |
| 4075 | * @since version 2.1.5 |
| 4076 | * @instance |
| 4077 | */ |
| 4078 | |
| 4079 | /** |
| 4080 | * @method afterDatasetsUpdate |
| 4081 | * @description Called after all datasets have been updated. Note that this |
| 4082 | * extension will not be called if the datasets update has been cancelled. |
| 4083 | * @param {Object} instance the chart instance being updated. |
| 4084 | * @memberof Chart.PluginBase |
| 4085 | * @since version 2.1.5 |
| 4086 | * @instance |
| 4087 | */ |
| 4088 | |
| 4089 | /** |
| 4090 | * Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate |
| 4091 | * extension, in which case no datasets will be updated and the afterDatasetsUpdate |
| 4092 | * notification will be skipped. |
| 4093 | * @protected |
| 4094 | * @instance |
| 4095 | */ |
| 4096 | updateDatasets: function() { |
| 4097 | var me = this; |
| 4098 | var i, ilen; |
| 4099 | |
| 4100 | if (Chart.plugins.notify('beforeDatasetsUpdate', [ me ])) { |
| 4101 | for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { |
| 4102 | me.getDatasetMeta(i).controller.update(); |
| 4103 | } |
| 4104 | |
| 4105 | Chart.plugins.notify('afterDatasetsUpdate', [ me ]); |
| 4106 | } |
| 4107 | }, |
| 4108 | |
| 4109 | render: function render(duration, lazy) { |
| 4110 | var me = this; |
| 4111 | Chart.plugins.notify('beforeRender', [me]); |
| 4112 | |
| 4113 | var animationOptions = me.options.animation; |
| 4114 | if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { |
| 4115 | var animation = new Chart.Animation(); |
| 4116 | animation.numSteps = (duration || animationOptions.duration) / 16.66; //60 fps |
| 4117 | animation.easing = animationOptions.easing; |
| 4118 | |
| 4119 | // render function |
| 4120 | animation.render = function(chartInstance, animationObject) { |
| 4121 | var easingFunction = helpers.easingEffects[animationObject.easing]; |
| 4122 | var stepDecimal = animationObject.currentStep / animationObject.numSteps; |
| 4123 | var easeDecimal = easingFunction(stepDecimal); |
| 4124 | |
| 4125 | chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); |
| 4126 | }; |
| 4127 | |
| 4128 | // user events |
| 4129 | animation.onAnimationProgress = animationOptions.onProgress; |
| 4130 | animation.onAnimationComplete = animationOptions.onComplete; |
| 4131 | |
| 4132 | Chart.animationService.addAnimation(me, animation, duration, lazy); |
| 4133 | } else { |
| 4134 | me.draw(); |
| 4135 | if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) { |
| 4136 | animationOptions.onComplete.call(me); |
| 4137 | } |
| 4138 | } |
| 4139 | return me; |
| 4140 | }, |
| 4141 | |
| 4142 | draw: function(ease) { |
| 4143 | var me = this; |
| 4144 | var easingDecimal = ease || 1; |
| 4145 | me.clear(); |
| 4146 | |
| 4147 | Chart.plugins.notify('beforeDraw', [me, easingDecimal]); |
| 4148 | |
| 4149 | // Draw all the scales |
| 4150 | helpers.each(me.boxes, function(box) { |
| 4151 | box.draw(me.chartArea); |
| 4152 | }, me); |
| 4153 | if (me.scale) { |
| 4154 | me.scale.draw(); |
| 4155 | } |
| 4156 | |
| 4157 | Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]); |
| 4158 | |
| 4159 | // Draw each dataset via its respective controller (reversed to support proper line stacking) |
| 4160 | helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 4161 | if (me.isDatasetVisible(datasetIndex)) { |
| 4162 | me.getDatasetMeta(datasetIndex).controller.draw(ease); |
| 4163 | } |
| 4164 | }, me, true); |
| 4165 | |
| 4166 | Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]); |
| 4167 | |
| 4168 | // Finally draw the tooltip |
| 4169 | me.tooltip.transition(easingDecimal).draw(); |
| 4170 | |
| 4171 | Chart.plugins.notify('afterDraw', [me, easingDecimal]); |
| 4172 | }, |
| 4173 | |
| 4174 | // Get the single element that was clicked on |
| 4175 | // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw |
| 4176 | getElementAtEvent: function(e) { |
| 4177 | var me = this; |
| 4178 | var eventPosition = helpers.getRelativePosition(e, me.chart); |
| 4179 | var elementsArray = []; |
| 4180 | |
| 4181 | helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 4182 | if (me.isDatasetVisible(datasetIndex)) { |
| 4183 | var meta = me.getDatasetMeta(datasetIndex); |
| 4184 | helpers.each(meta.data, function(element) { |
| 4185 | if (element.inRange(eventPosition.x, eventPosition.y)) { |
| 4186 | elementsArray.push(element); |
| 4187 | return elementsArray; |
| 4188 | } |
| 4189 | }); |
| 4190 | } |
| 4191 | }); |
| 4192 | |
| 4193 | return elementsArray.slice(0, 1); |
| 4194 | }, |
| 4195 | |
| 4196 | getElementsAtEvent: function(e) { |
| 4197 | var me = this; |
| 4198 | var eventPosition = helpers.getRelativePosition(e, me.chart); |
| 4199 | var elementsArray = []; |
| 4200 | |
| 4201 | var found = (function() { |
| 4202 | if (me.data.datasets) { |
| 4203 | for (var i = 0; i < me.data.datasets.length; i++) { |
| 4204 | var meta = me.getDatasetMeta(i); |
| 4205 | if (me.isDatasetVisible(i)) { |
| 4206 | for (var j = 0; j < meta.data.length; j++) { |
| 4207 | if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) { |
| 4208 | return meta.data[j]; |
| 4209 | } |
| 4210 | } |
| 4211 | } |
| 4212 | } |
| 4213 | } |
| 4214 | }).call(me); |
| 4215 | |
| 4216 | if (!found) { |
| 4217 | return elementsArray; |
| 4218 | } |
| 4219 | |
| 4220 | helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 4221 | if (me.isDatasetVisible(datasetIndex)) { |
| 4222 | var meta = me.getDatasetMeta(datasetIndex), |
| 4223 | element = meta.data[found._index]; |
| 4224 | if(element && !element._view.skip){ |
| 4225 | elementsArray.push(element); |
| 4226 | } |
| 4227 | } |
| 4228 | }, me); |
| 4229 | |
| 4230 | return elementsArray; |
| 4231 | }, |
| 4232 | |
| 4233 | getElementsAtXAxis: function(e) { |
| 4234 | var me = this; |
| 4235 | var eventPosition = helpers.getRelativePosition(e, me.chart); |
| 4236 | var elementsArray = []; |
| 4237 | |
| 4238 | var found = (function() { |
| 4239 | if (me.data.datasets) { |
| 4240 | for (var i = 0; i < me.data.datasets.length; i++) { |
| 4241 | var meta = me.getDatasetMeta(i); |
| 4242 | if (me.isDatasetVisible(i)) { |
| 4243 | for (var j = 0; j < meta.data.length; j++) { |
| 4244 | if (meta.data[j].inLabelRange(eventPosition.x, eventPosition.y)) { |
| 4245 | return meta.data[j]; |
| 4246 | } |
| 4247 | } |
| 4248 | } |
| 4249 | } |
| 4250 | } |
| 4251 | }).call(me); |
| 4252 | |
| 4253 | if (!found) { |
| 4254 | return elementsArray; |
| 4255 | } |
| 4256 | |
| 4257 | helpers.each(me.data.datasets, function(dataset, datasetIndex) { |
| 4258 | if (me.isDatasetVisible(datasetIndex)) { |
| 4259 | var meta = me.getDatasetMeta(datasetIndex); |
| 4260 | var index = helpers.findIndex(meta.data, function (it) { |
| 4261 | return found._model.x === it._model.x; |
| 4262 | }); |
| 4263 | if(index !== -1 && !meta.data[index]._view.skip) { |
| 4264 | elementsArray.push(meta.data[index]); |
| 4265 | } |
| 4266 | } |
| 4267 | }, me); |
| 4268 | |
| 4269 | return elementsArray; |
| 4270 | }, |
| 4271 | |
| 4272 | getElementsAtEventForMode: function(e, mode) { |
| 4273 | var me = this; |
| 4274 | switch (mode) { |
| 4275 | case 'single': |
| 4276 | return me.getElementAtEvent(e); |
| 4277 | case 'label': |
| 4278 | return me.getElementsAtEvent(e); |
| 4279 | case 'dataset': |
| 4280 | return me.getDatasetAtEvent(e); |
| 4281 | case 'x-axis': |
| 4282 | return me.getElementsAtXAxis(e); |
| 4283 | default: |
| 4284 | return e; |
| 4285 | } |
| 4286 | }, |
| 4287 | |
| 4288 | getDatasetAtEvent: function(e) { |
| 4289 | var elementsArray = this.getElementAtEvent(e); |
| 4290 | |
| 4291 | if (elementsArray.length > 0) { |
| 4292 | elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data; |
| 4293 | } |
| 4294 | |
| 4295 | return elementsArray; |
| 4296 | }, |
| 4297 | |
| 4298 | getDatasetMeta: function(datasetIndex) { |
| 4299 | var me = this; |
| 4300 | var dataset = me.data.datasets[datasetIndex]; |
| 4301 | if (!dataset._meta) { |
| 4302 | dataset._meta = {}; |
| 4303 | } |
| 4304 | |
| 4305 | var meta = dataset._meta[me.id]; |
| 4306 | if (!meta) { |
| 4307 | meta = dataset._meta[me.id] = { |
| 4308 | type: null, |
| 4309 | data: [], |
| 4310 | dataset: null, |
| 4311 | controller: null, |
| 4312 | hidden: null, // See isDatasetVisible() comment |
| 4313 | xAxisID: null, |
| 4314 | yAxisID: null |
| 4315 | }; |
| 4316 | } |
| 4317 | |
| 4318 | return meta; |
| 4319 | }, |
| 4320 | |
| 4321 | getVisibleDatasetCount: function() { |
| 4322 | var count = 0; |
| 4323 | for (var i = 0, ilen = this.data.datasets.length; i<ilen; ++i) { |
| 4324 | if (this.isDatasetVisible(i)) { |
| 4325 | count++; |
| 4326 | } |
| 4327 | } |
| 4328 | return count; |
| 4329 | }, |
| 4330 | |
| 4331 | isDatasetVisible: function(datasetIndex) { |
| 4332 | var meta = this.getDatasetMeta(datasetIndex); |
| 4333 | |
| 4334 | // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, |
| 4335 | // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. |
| 4336 | return typeof meta.hidden === 'boolean'? !meta.hidden : !this.data.datasets[datasetIndex].hidden; |
| 4337 | }, |
| 4338 | |
| 4339 | generateLegend: function() { |
| 4340 | return this.options.legendCallback(this); |
| 4341 | }, |
| 4342 | |
| 4343 | destroy: function() { |
| 4344 | var me = this; |
| 4345 | me.stop(); |
| 4346 | me.clear(); |
| 4347 | helpers.unbindEvents(me, me.events); |
| 4348 | helpers.removeResizeListener(me.chart.canvas.parentNode); |
| 4349 | |
| 4350 | // Reset canvas height/width attributes |
| 4351 | var canvas = me.chart.canvas; |
| 4352 | canvas.width = me.chart.width; |
| 4353 | canvas.height = me.chart.height; |
| 4354 | |
| 4355 | // if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here |
| 4356 | if (me.chart.originalDevicePixelRatio !== undefined) { |
| 4357 | me.chart.ctx.scale(1 / me.chart.originalDevicePixelRatio, 1 / me.chart.originalDevicePixelRatio); |
| 4358 | } |
| 4359 | |
| 4360 | // Reset to the old style since it may have been changed by the device pixel ratio changes |
| 4361 | canvas.style.width = me.chart.originalCanvasStyleWidth; |
| 4362 | canvas.style.height = me.chart.originalCanvasStyleHeight; |
| 4363 | |
| 4364 | Chart.plugins.notify('destroy', [me]); |
| 4365 | |
| 4366 | delete Chart.instances[me.id]; |
| 4367 | }, |
| 4368 | |
| 4369 | toBase64Image: function() { |
| 4370 | return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); |
| 4371 | }, |
| 4372 | |
| 4373 | initToolTip: function() { |
| 4374 | var me = this; |
| 4375 | me.tooltip = new Chart.Tooltip({ |
| 4376 | _chart: me.chart, |
| 4377 | _chartInstance: me, |
| 4378 | _data: me.data, |
| 4379 | _options: me.options.tooltips |
| 4380 | }, me); |
| 4381 | }, |
| 4382 | |
| 4383 | bindEvents: function() { |
| 4384 | var me = this; |
| 4385 | helpers.bindEvents(me, me.options.events, function(evt) { |
| 4386 | me.eventHandler(evt); |
| 4387 | }); |
| 4388 | }, |
| 4389 | |
| 4390 | updateHoverStyle: function(elements, mode, enabled) { |
| 4391 | var method = enabled? 'setHoverStyle' : 'removeHoverStyle'; |
| 4392 | var element, i, ilen; |
| 4393 | |
| 4394 | switch (mode) { |
| 4395 | case 'single': |
| 4396 | elements = [ elements[0] ]; |
| 4397 | break; |
| 4398 | case 'label': |
| 4399 | case 'dataset': |
| 4400 | case 'x-axis': |
| 4401 | // elements = elements; |
| 4402 | break; |
| 4403 | default: |
| 4404 | // unsupported mode |
| 4405 | return; |
| 4406 | } |
| 4407 | |
| 4408 | for (i=0, ilen=elements.length; i<ilen; ++i) { |
| 4409 | element = elements[i]; |
| 4410 | if (element) { |
| 4411 | this.getDatasetMeta(element._datasetIndex).controller[method](element); |
| 4412 | } |
| 4413 | } |
| 4414 | }, |
| 4415 | |
| 4416 | eventHandler: function eventHandler(e) { |
| 4417 | var me = this; |
| 4418 | var tooltip = me.tooltip; |
| 4419 | var options = me.options || {}; |
| 4420 | var hoverOptions = options.hover; |
| 4421 | var tooltipsOptions = options.tooltips; |
| 4422 | |
| 4423 | me.lastActive = me.lastActive || []; |
| 4424 | me.lastTooltipActive = me.lastTooltipActive || []; |
| 4425 | |
| 4426 | // Find Active Elements for hover and tooltips |
| 4427 | if (e.type === 'mouseout') { |
| 4428 | me.active = []; |
| 4429 | me.tooltipActive = []; |
| 4430 | } else { |
| 4431 | me.active = me.getElementsAtEventForMode(e, hoverOptions.mode); |
| 4432 | me.tooltipActive = me.getElementsAtEventForMode(e, tooltipsOptions.mode); |
| 4433 | } |
| 4434 | |
| 4435 | // On Hover hook |
| 4436 | if (hoverOptions.onHover) { |
| 4437 | hoverOptions.onHover.call(me, me.active); |
| 4438 | } |
| 4439 | |
| 4440 | if (e.type === 'mouseup' || e.type === 'click') { |
| 4441 | if (options.onClick) { |
| 4442 | options.onClick.call(me, e, me.active); |
| 4443 | } |
| 4444 | if (me.legend && me.legend.handleEvent) { |
| 4445 | me.legend.handleEvent(e); |
| 4446 | } |
| 4447 | } |
| 4448 | |
| 4449 | // Remove styling for last active (even if it may still be active) |
| 4450 | if (me.lastActive.length) { |
| 4451 | me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); |
| 4452 | } |
| 4453 | |
| 4454 | // Built in hover styling |
| 4455 | if (me.active.length && hoverOptions.mode) { |
| 4456 | me.updateHoverStyle(me.active, hoverOptions.mode, true); |
| 4457 | } |
| 4458 | |
| 4459 | // Built in Tooltips |
| 4460 | if (tooltipsOptions.enabled || tooltipsOptions.custom) { |
| 4461 | tooltip.initialize(); |
| 4462 | tooltip._active = me.tooltipActive; |
| 4463 | tooltip.update(true); |
| 4464 | } |
| 4465 | |
| 4466 | // Hover animations |
| 4467 | tooltip.pivot(); |
| 4468 | |
| 4469 | if (!me.animating) { |
| 4470 | // If entering, leaving, or changing elements, animate the change via pivot |
| 4471 | if (!helpers.arrayEquals(me.active, me.lastActive) || |
| 4472 | !helpers.arrayEquals(me.tooltipActive, me.lastTooltipActive)) { |
| 4473 | |
| 4474 | me.stop(); |
| 4475 | |
| 4476 | if (tooltipsOptions.enabled || tooltipsOptions.custom) { |
| 4477 | tooltip.update(true); |
| 4478 | } |
| 4479 | |
| 4480 | // We only need to render at this point. Updating will cause scales to be |
| 4481 | // recomputed generating flicker & using more memory than necessary. |
| 4482 | me.render(hoverOptions.animationDuration, true); |
| 4483 | } |
| 4484 | } |
| 4485 | |
| 4486 | // Remember Last Actives |
| 4487 | me.lastActive = me.active; |
| 4488 | me.lastTooltipActive = me.tooltipActive; |
| 4489 | return me; |
| 4490 | } |
| 4491 | }); |
| 4492 | }; |
| 4493 | |
| 4494 | },{}],24:[function(require,module,exports){ |
| 4495 | "use strict"; |
| 4496 | |
| 4497 | module.exports = function(Chart) { |
| 4498 | |
| 4499 | var helpers = Chart.helpers; |
| 4500 | var noop = helpers.noop; |
| 4501 | |
| 4502 | // Base class for all dataset controllers (line, bar, etc) |
| 4503 | Chart.DatasetController = function(chart, datasetIndex) { |
| 4504 | this.initialize.call(this, chart, datasetIndex); |
| 4505 | }; |
| 4506 | |
| 4507 | helpers.extend(Chart.DatasetController.prototype, { |
| 4508 | |
| 4509 | /** |
| 4510 | * Element type used to generate a meta dataset (e.g. Chart.element.Line). |
| 4511 | * @type {Chart.core.element} |
| 4512 | */ |
| 4513 | datasetElementType: null, |
| 4514 | |
| 4515 | /** |
| 4516 | * Element type used to generate a meta data (e.g. Chart.element.Point). |
| 4517 | * @type {Chart.core.element} |
| 4518 | */ |
| 4519 | dataElementType: null, |
| 4520 | |
| 4521 | initialize: function(chart, datasetIndex) { |
| 4522 | var me = this; |
| 4523 | me.chart = chart; |
| 4524 | me.index = datasetIndex; |
| 4525 | me.linkScales(); |
| 4526 | me.addElements(); |
| 4527 | }, |
| 4528 | |
| 4529 | updateIndex: function(datasetIndex) { |
| 4530 | this.index = datasetIndex; |
| 4531 | }, |
| 4532 | |
| 4533 | linkScales: function() { |
| 4534 | var me = this; |
| 4535 | var meta = me.getMeta(); |
| 4536 | var dataset = me.getDataset(); |
| 4537 | |
| 4538 | if (meta.xAxisID === null) { |
| 4539 | meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; |
| 4540 | } |
| 4541 | if (meta.yAxisID === null) { |
| 4542 | meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; |
| 4543 | } |
| 4544 | }, |
| 4545 | |
| 4546 | getDataset: function() { |
| 4547 | return this.chart.data.datasets[this.index]; |
| 4548 | }, |
| 4549 | |
| 4550 | getMeta: function() { |
| 4551 | return this.chart.getDatasetMeta(this.index); |
| 4552 | }, |
| 4553 | |
| 4554 | getScaleForId: function(scaleID) { |
| 4555 | return this.chart.scales[scaleID]; |
| 4556 | }, |
| 4557 | |
| 4558 | reset: function() { |
| 4559 | this.update(true); |
| 4560 | }, |
| 4561 | |
| 4562 | createMetaDataset: function() { |
| 4563 | var me = this; |
| 4564 | var type = me.datasetElementType; |
| 4565 | return type && new type({ |
| 4566 | _chart: me.chart.chart, |
| 4567 | _datasetIndex: me.index |
| 4568 | }); |
| 4569 | }, |
| 4570 | |
| 4571 | createMetaData: function(index) { |
| 4572 | var me = this; |
| 4573 | var type = me.dataElementType; |
| 4574 | return type && new type({ |
| 4575 | _chart: me.chart.chart, |
| 4576 | _datasetIndex: me.index, |
| 4577 | _index: index |
| 4578 | }); |
| 4579 | }, |
| 4580 | |
| 4581 | addElements: function() { |
| 4582 | var me = this; |
| 4583 | var meta = me.getMeta(); |
| 4584 | var data = me.getDataset().data || []; |
| 4585 | var metaData = meta.data; |
| 4586 | var i, ilen; |
| 4587 | |
| 4588 | for (i=0, ilen=data.length; i<ilen; ++i) { |
| 4589 | metaData[i] = metaData[i] || me.createMetaData(meta, i); |
| 4590 | } |
| 4591 | |
| 4592 | meta.dataset = meta.dataset || me.createMetaDataset(); |
| 4593 | }, |
| 4594 | |
| 4595 | addElementAndReset: function(index) { |
| 4596 | var me = this; |
| 4597 | var element = me.createMetaData(index); |
| 4598 | me.getMeta().data.splice(index, 0, element); |
| 4599 | me.updateElement(element, index, true); |
| 4600 | }, |
| 4601 | |
| 4602 | buildOrUpdateElements: function() { |
| 4603 | // Handle the number of data points changing |
| 4604 | var meta = this.getMeta(), |
| 4605 | md = meta.data, |
| 4606 | numData = this.getDataset().data.length, |
| 4607 | numMetaData = md.length; |
| 4608 | |
| 4609 | // Make sure that we handle number of datapoints changing |
| 4610 | if (numData < numMetaData) { |
| 4611 | // Remove excess bars for data points that have been removed |
| 4612 | md.splice(numData, numMetaData - numData); |
| 4613 | } else if (numData > numMetaData) { |
| 4614 | // Add new elements |
| 4615 | for (var index = numMetaData; index < numData; ++index) { |
| 4616 | this.addElementAndReset(index); |
| 4617 | } |
| 4618 | } |
| 4619 | }, |
| 4620 | |
| 4621 | update: noop, |
| 4622 | |
| 4623 | draw: function(ease) { |
| 4624 | var easingDecimal = ease || 1; |
| 4625 | helpers.each(this.getMeta().data, function(element) { |
| 4626 | element.transition(easingDecimal).draw(); |
| 4627 | }); |
| 4628 | }, |
| 4629 | |
| 4630 | removeHoverStyle: function(element, elementOpts) { |
| 4631 | var dataset = this.chart.data.datasets[element._datasetIndex], |
| 4632 | index = element._index, |
| 4633 | custom = element.custom || {}, |
| 4634 | valueOrDefault = helpers.getValueAtIndexOrDefault, |
| 4635 | model = element._model; |
| 4636 | |
| 4637 | model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); |
| 4638 | model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); |
| 4639 | model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); |
| 4640 | }, |
| 4641 | |
| 4642 | setHoverStyle: function(element) { |
| 4643 | var dataset = this.chart.data.datasets[element._datasetIndex], |
| 4644 | index = element._index, |
| 4645 | custom = element.custom || {}, |
| 4646 | valueOrDefault = helpers.getValueAtIndexOrDefault, |
| 4647 | getHoverColor = helpers.getHoverColor, |
| 4648 | model = element._model; |
| 4649 | |
| 4650 | model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); |
| 4651 | model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); |
| 4652 | model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); |
| 4653 | } |
| 4654 | |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4655 | }); |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4656 | |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4657 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4658 | Chart.DatasetController.extend = helpers.inherits; |
| 4659 | }; |
| 4660 | },{}],25:[function(require,module,exports){ |
| 4661 | "use strict"; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4662 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4663 | module.exports = function(Chart) { |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4664 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4665 | var helpers = Chart.helpers; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4666 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4667 | Chart.elements = {}; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4668 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4669 | Chart.Element = function(configuration) { |
| 4670 | helpers.extend(this, configuration); |
| 4671 | this.initialize.apply(this, arguments); |
| 4672 | }; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4673 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4674 | helpers.extend(Chart.Element.prototype, { |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4675 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4676 | initialize: function() { |
| 4677 | this.hidden = false; |
| 4678 | }, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4679 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4680 | pivot: function() { |
| 4681 | var me = this; |
| 4682 | if (!me._view) { |
| 4683 | me._view = helpers.clone(me._model); |
| 4684 | } |
| 4685 | me._start = helpers.clone(me._view); |
| 4686 | return me; |
| 4687 | }, |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4688 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4689 | transition: function(ease) { |
| 4690 | var me = this; |
| 4691 | |
| 4692 | if (!me._view) { |
| 4693 | me._view = helpers.clone(me._model); |
| 4694 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4695 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4696 | // No animation -> No Transition |
| 4697 | if (ease === 1) { |
| 4698 | me._view = me._model; |
| 4699 | me._start = null; |
| 4700 | return me; |
| 4701 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4702 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4703 | if (!me._start) { |
| 4704 | me.pivot(); |
| 4705 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4706 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4707 | helpers.each(me._model, function(value, key) { |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4708 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4709 | if (key[0] === '_') { |
| 4710 | // Only non-underscored properties |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 4711 | } |
| 4712 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 4713 | // Init if doesn't exist |
| 4714 | else if (!me._view.hasOwnProperty(key)) { |
| 4715 | if (typeof value === 'number' && !isNaN(me._view[key])) { |
| 4716 | me._view[key] = value * ease; |
| 4717 | } else { |
| 4718 | me._view[key] = value; |
| 4719 | } |
| 4720 | } |
| 4721 | |
| 4722 | // No unnecessary computations |
| 4723 | else if (value === me._view[key]) { |
| 4724 | // It's the same! Woohoo! |
| 4725 | } |
| 4726 | |
| 4727 | // Color transitions if possible |
| 4728 | else if (typeof value === 'string') { |
| 4729 | try { |
| 4730 | var color = helpers.color(me._model[key]).mix(helpers.color(me._start[key]), ease); |
| 4731 | me._view[key] = color.rgbString(); |
| 4732 | } catch (err) { |
| 4733 | me._view[key] = value; |
| 4734 | } |
| 4735 | } |
| 4736 | // Number transitions |
| 4737 | else if (typeof value === 'number') { |
| 4738 | var startVal = me._start[key] !== undefined && isNaN(me._start[key]) === false ? me._start[key] : 0; |
| 4739 | me._view[key] = ((me._model[key] - startVal) * ease) + startVal; |
| 4740 | } |
| 4741 | // Everything else |
| 4742 | else { |
| 4743 | me._view[key] = value; |
| 4744 | } |
| 4745 | }, me); |
| 4746 | |
| 4747 | return me; |
| 4748 | }, |
| 4749 | |
| 4750 | tooltipPosition: function() { |
| 4751 | return { |
| 4752 | x: this._model.x, |
| 4753 | y: this._model.y |
| 4754 | }; |
| 4755 | }, |
| 4756 | |
| 4757 | hasValue: function() { |
| 4758 | return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); |
| 4759 | } |
| 4760 | }); |
| 4761 | |
| 4762 | Chart.Element.extend = helpers.inherits; |
| 4763 | |
| 4764 | }; |
| 4765 | |
| 4766 | },{}],26:[function(require,module,exports){ |
| 4767 | /*global window: false */ |
| 4768 | /*global document: false */ |
| 4769 | "use strict"; |
| 4770 | |
| 4771 | var color = require(3); |
| 4772 | |
| 4773 | module.exports = function(Chart) { |
| 4774 | //Global Chart helpers object for utility methods and classes |
| 4775 | var helpers = Chart.helpers = {}; |
| 4776 | |
| 4777 | //-- Basic js utility methods |
| 4778 | helpers.each = function(loopable, callback, self, reverse) { |
| 4779 | // Check to see if null or undefined firstly. |
| 4780 | var i, len; |
| 4781 | if (helpers.isArray(loopable)) { |
| 4782 | len = loopable.length; |
| 4783 | if (reverse) { |
| 4784 | for (i = len - 1; i >= 0; i--) { |
| 4785 | callback.call(self, loopable[i], i); |
| 4786 | } |
| 4787 | } else { |
| 4788 | for (i = 0; i < len; i++) { |
| 4789 | callback.call(self, loopable[i], i); |
| 4790 | } |
| 4791 | } |
| 4792 | } else if (typeof loopable === 'object') { |
| 4793 | var keys = Object.keys(loopable); |
| 4794 | len = keys.length; |
| 4795 | for (i = 0; i < len; i++) { |
| 4796 | callback.call(self, loopable[keys[i]], keys[i]); |
| 4797 | } |
| 4798 | } |
| 4799 | }; |
| 4800 | helpers.clone = function(obj) { |
| 4801 | var objClone = {}; |
| 4802 | helpers.each(obj, function(value, key) { |
| 4803 | if (helpers.isArray(value)) { |
| 4804 | objClone[key] = value.slice(0); |
| 4805 | } else if (typeof value === 'object' && value !== null) { |
| 4806 | objClone[key] = helpers.clone(value); |
| 4807 | } else { |
| 4808 | objClone[key] = value; |
| 4809 | } |
| 4810 | }); |
| 4811 | return objClone; |
| 4812 | }; |
| 4813 | helpers.extend = function(base) { |
| 4814 | var setFn = function(value, key) { base[key] = value; }; |
| 4815 | for (var i = 1, ilen = arguments.length; i < ilen; i++) { |
| 4816 | helpers.each(arguments[i], setFn); |
| 4817 | } |
| 4818 | return base; |
| 4819 | }; |
| 4820 | // Need a special merge function to chart configs since they are now grouped |
| 4821 | helpers.configMerge = function(_base) { |
| 4822 | var base = helpers.clone(_base); |
| 4823 | helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { |
| 4824 | helpers.each(extension, function(value, key) { |
| 4825 | if (key === 'scales') { |
| 4826 | // Scale config merging is complex. Add out own function here for that |
| 4827 | base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value); |
| 4828 | |
| 4829 | } else if (key === 'scale') { |
| 4830 | // Used in polar area & radar charts since there is only one scale |
| 4831 | base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value); |
| 4832 | } else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) { |
| 4833 | // In this case we have an array of objects replacing another array. Rather than doing a strict replace, |
| 4834 | // merge. This allows easy scale option merging |
| 4835 | var baseArray = base[key]; |
| 4836 | |
| 4837 | helpers.each(value, function(valueObj, index) { |
| 4838 | |
| 4839 | if (index < baseArray.length) { |
| 4840 | if (typeof baseArray[index] === 'object' && baseArray[index] !== null && typeof valueObj === 'object' && valueObj !== null) { |
| 4841 | // Two objects are coming together. Do a merge of them. |
| 4842 | baseArray[index] = helpers.configMerge(baseArray[index], valueObj); |
| 4843 | } else { |
| 4844 | // Just overwrite in this case since there is nothing to merge |
| 4845 | baseArray[index] = valueObj; |
| 4846 | } |
| 4847 | } else { |
| 4848 | baseArray.push(valueObj); // nothing to merge |
| 4849 | } |
| 4850 | }); |
| 4851 | |
| 4852 | } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") { |
| 4853 | // If we are overwriting an object with an object, do a merge of the properties. |
| 4854 | base[key] = helpers.configMerge(base[key], value); |
| 4855 | |
| 4856 | } else { |
| 4857 | // can just overwrite the value in this case |
| 4858 | base[key] = value; |
| 4859 | } |
| 4860 | }); |
| 4861 | }); |
| 4862 | |
| 4863 | return base; |
| 4864 | }; |
| 4865 | helpers.scaleMerge = function(_base, extension) { |
| 4866 | var base = helpers.clone(_base); |
| 4867 | |
| 4868 | helpers.each(extension, function(value, key) { |
| 4869 | if (key === 'xAxes' || key === 'yAxes') { |
| 4870 | // These properties are arrays of items |
| 4871 | if (base.hasOwnProperty(key)) { |
| 4872 | helpers.each(value, function(valueObj, index) { |
| 4873 | var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); |
| 4874 | var axisDefaults = Chart.scaleService.getScaleDefaults(axisType); |
| 4875 | if (index >= base[key].length || !base[key][index].type) { |
| 4876 | base[key].push(helpers.configMerge(axisDefaults, valueObj)); |
| 4877 | } else if (valueObj.type && valueObj.type !== base[key][index].type) { |
| 4878 | // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults |
| 4879 | base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj); |
| 4880 | } else { |
| 4881 | // Type is the same |
| 4882 | base[key][index] = helpers.configMerge(base[key][index], valueObj); |
| 4883 | } |
| 4884 | }); |
| 4885 | } else { |
| 4886 | base[key] = []; |
| 4887 | helpers.each(value, function(valueObj) { |
| 4888 | var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); |
| 4889 | base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj)); |
| 4890 | }); |
| 4891 | } |
| 4892 | } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") { |
| 4893 | // If we are overwriting an object with an object, do a merge of the properties. |
| 4894 | base[key] = helpers.configMerge(base[key], value); |
| 4895 | |
| 4896 | } else { |
| 4897 | // can just overwrite the value in this case |
| 4898 | base[key] = value; |
| 4899 | } |
| 4900 | }); |
| 4901 | |
| 4902 | return base; |
| 4903 | }; |
| 4904 | helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { |
| 4905 | if (value === undefined || value === null) { |
| 4906 | return defaultValue; |
| 4907 | } |
| 4908 | |
| 4909 | if (helpers.isArray(value)) { |
| 4910 | return index < value.length ? value[index] : defaultValue; |
| 4911 | } |
| 4912 | |
| 4913 | return value; |
| 4914 | }; |
| 4915 | helpers.getValueOrDefault = function(value, defaultValue) { |
| 4916 | return value === undefined ? defaultValue : value; |
| 4917 | }; |
| 4918 | helpers.indexOf = Array.prototype.indexOf? |
| 4919 | function(array, item) { return array.indexOf(item); } : |
| 4920 | function(array, item) { |
| 4921 | for (var i = 0, ilen = array.length; i < ilen; ++i) { |
| 4922 | if (array[i] === item) { |
| 4923 | return i; |
| 4924 | } |
| 4925 | } |
| 4926 | return -1; |
| 4927 | }; |
| 4928 | helpers.where = function(collection, filterCallback) { |
| 4929 | if (helpers.isArray(collection) && Array.prototype.filter) { |
| 4930 | return collection.filter(filterCallback); |
| 4931 | } else { |
| 4932 | var filtered = []; |
| 4933 | |
| 4934 | helpers.each(collection, function(item) { |
| 4935 | if (filterCallback(item)) { |
| 4936 | filtered.push(item); |
| 4937 | } |
| 4938 | }); |
| 4939 | |
| 4940 | return filtered; |
| 4941 | } |
| 4942 | }; |
| 4943 | helpers.findIndex = Array.prototype.findIndex? |
| 4944 | function(array, callback, scope) { return array.findIndex(callback, scope); } : |
| 4945 | function(array, callback, scope) { |
| 4946 | scope = scope === undefined? array : scope; |
| 4947 | for (var i = 0, ilen = array.length; i < ilen; ++i) { |
| 4948 | if (callback.call(scope, array[i], i, array)) { |
| 4949 | return i; |
| 4950 | } |
| 4951 | } |
| 4952 | return -1; |
| 4953 | }; |
| 4954 | helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { |
| 4955 | // Default to start of the array |
| 4956 | if (startIndex === undefined || startIndex === null) { |
| 4957 | startIndex = -1; |
| 4958 | } |
| 4959 | for (var i = startIndex + 1; i < arrayToSearch.length; i++) { |
| 4960 | var currentItem = arrayToSearch[i]; |
| 4961 | if (filterCallback(currentItem)) { |
| 4962 | return currentItem; |
| 4963 | } |
| 4964 | } |
| 4965 | }; |
| 4966 | helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { |
| 4967 | // Default to end of the array |
| 4968 | if (startIndex === undefined || startIndex === null) { |
| 4969 | startIndex = arrayToSearch.length; |
| 4970 | } |
| 4971 | for (var i = startIndex - 1; i >= 0; i--) { |
| 4972 | var currentItem = arrayToSearch[i]; |
| 4973 | if (filterCallback(currentItem)) { |
| 4974 | return currentItem; |
| 4975 | } |
| 4976 | } |
| 4977 | }; |
| 4978 | helpers.inherits = function(extensions) { |
| 4979 | //Basic javascript inheritance based on the model created in Backbone.js |
| 4980 | var parent = this; |
| 4981 | var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() { |
| 4982 | return parent.apply(this, arguments); |
| 4983 | }; |
| 4984 | |
| 4985 | var Surrogate = function() { |
| 4986 | this.constructor = ChartElement; |
| 4987 | }; |
| 4988 | Surrogate.prototype = parent.prototype; |
| 4989 | ChartElement.prototype = new Surrogate(); |
| 4990 | |
| 4991 | ChartElement.extend = helpers.inherits; |
| 4992 | |
| 4993 | if (extensions) { |
| 4994 | helpers.extend(ChartElement.prototype, extensions); |
| 4995 | } |
| 4996 | |
| 4997 | ChartElement.__super__ = parent.prototype; |
| 4998 | |
| 4999 | return ChartElement; |
| 5000 | }; |
| 5001 | helpers.noop = function() {}; |
| 5002 | helpers.uid = (function() { |
| 5003 | var id = 0; |
| 5004 | return function() { |
| 5005 | return id++; |
| 5006 | }; |
| 5007 | })(); |
| 5008 | //-- Math methods |
| 5009 | helpers.isNumber = function(n) { |
| 5010 | return !isNaN(parseFloat(n)) && isFinite(n); |
| 5011 | }; |
| 5012 | helpers.almostEquals = function(x, y, epsilon) { |
| 5013 | return Math.abs(x - y) < epsilon; |
| 5014 | }; |
| 5015 | helpers.max = function(array) { |
| 5016 | return array.reduce(function(max, value) { |
| 5017 | if (!isNaN(value)) { |
| 5018 | return Math.max(max, value); |
| 5019 | } else { |
| 5020 | return max; |
| 5021 | } |
| 5022 | }, Number.NEGATIVE_INFINITY); |
| 5023 | }; |
| 5024 | helpers.min = function(array) { |
| 5025 | return array.reduce(function(min, value) { |
| 5026 | if (!isNaN(value)) { |
| 5027 | return Math.min(min, value); |
| 5028 | } else { |
| 5029 | return min; |
| 5030 | } |
| 5031 | }, Number.POSITIVE_INFINITY); |
| 5032 | }; |
| 5033 | helpers.sign = Math.sign? |
| 5034 | function(x) { return Math.sign(x); } : |
| 5035 | function(x) { |
| 5036 | x = +x; // convert to a number |
| 5037 | if (x === 0 || isNaN(x)) { |
| 5038 | return x; |
| 5039 | } |
| 5040 | return x > 0 ? 1 : -1; |
| 5041 | }; |
| 5042 | helpers.log10 = Math.log10? |
| 5043 | function(x) { return Math.log10(x); } : |
| 5044 | function(x) { |
| 5045 | return Math.log(x) / Math.LN10; |
| 5046 | }; |
| 5047 | helpers.toRadians = function(degrees) { |
| 5048 | return degrees * (Math.PI / 180); |
| 5049 | }; |
| 5050 | helpers.toDegrees = function(radians) { |
| 5051 | return radians * (180 / Math.PI); |
| 5052 | }; |
| 5053 | // Gets the angle from vertical upright to the point about a centre. |
| 5054 | helpers.getAngleFromPoint = function(centrePoint, anglePoint) { |
| 5055 | var distanceFromXCenter = anglePoint.x - centrePoint.x, |
| 5056 | distanceFromYCenter = anglePoint.y - centrePoint.y, |
| 5057 | radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); |
| 5058 | |
| 5059 | var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); |
| 5060 | |
| 5061 | if (angle < (-0.5 * Math.PI)) { |
| 5062 | angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] |
| 5063 | } |
| 5064 | |
| 5065 | return { |
| 5066 | angle: angle, |
| 5067 | distance: radialDistanceFromCenter |
| 5068 | }; |
| 5069 | }; |
| 5070 | helpers.aliasPixel = function(pixelWidth) { |
| 5071 | return (pixelWidth % 2 === 0) ? 0 : 0.5; |
| 5072 | }; |
| 5073 | helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { |
| 5074 | //Props to Rob Spencer at scaled innovation for his post on splining between points |
| 5075 | //http://scaledinnovation.com/analytics/splines/aboutSplines.html |
| 5076 | |
| 5077 | // This function must also respect "skipped" points |
| 5078 | |
| 5079 | var previous = firstPoint.skip ? middlePoint : firstPoint, |
| 5080 | current = middlePoint, |
| 5081 | next = afterPoint.skip ? middlePoint : afterPoint; |
| 5082 | |
| 5083 | var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); |
| 5084 | var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); |
| 5085 | |
| 5086 | var s01 = d01 / (d01 + d12); |
| 5087 | var s12 = d12 / (d01 + d12); |
| 5088 | |
| 5089 | // If all points are the same, s01 & s02 will be inf |
| 5090 | s01 = isNaN(s01) ? 0 : s01; |
| 5091 | s12 = isNaN(s12) ? 0 : s12; |
| 5092 | |
| 5093 | var fa = t * s01; // scaling factor for triangle Ta |
| 5094 | var fb = t * s12; |
| 5095 | |
| 5096 | return { |
| 5097 | previous: { |
| 5098 | x: current.x - fa * (next.x - previous.x), |
| 5099 | y: current.y - fa * (next.y - previous.y) |
| 5100 | }, |
| 5101 | next: { |
| 5102 | x: current.x + fb * (next.x - previous.x), |
| 5103 | y: current.y + fb * (next.y - previous.y) |
| 5104 | } |
| 5105 | }; |
| 5106 | }; |
| 5107 | helpers.nextItem = function(collection, index, loop) { |
| 5108 | if (loop) { |
| 5109 | return index >= collection.length - 1 ? collection[0] : collection[index + 1]; |
| 5110 | } |
| 5111 | |
| 5112 | return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; |
| 5113 | }; |
| 5114 | helpers.previousItem = function(collection, index, loop) { |
| 5115 | if (loop) { |
| 5116 | return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; |
| 5117 | } |
| 5118 | return index <= 0 ? collection[0] : collection[index - 1]; |
| 5119 | }; |
| 5120 | // Implementation of the nice number algorithm used in determining where axis labels will go |
| 5121 | helpers.niceNum = function(range, round) { |
| 5122 | var exponent = Math.floor(helpers.log10(range)); |
| 5123 | var fraction = range / Math.pow(10, exponent); |
| 5124 | var niceFraction; |
| 5125 | |
| 5126 | if (round) { |
| 5127 | if (fraction < 1.5) { |
| 5128 | niceFraction = 1; |
| 5129 | } else if (fraction < 3) { |
| 5130 | niceFraction = 2; |
| 5131 | } else if (fraction < 7) { |
| 5132 | niceFraction = 5; |
| 5133 | } else { |
| 5134 | niceFraction = 10; |
| 5135 | } |
| 5136 | } else { |
| 5137 | if (fraction <= 1.0) { |
| 5138 | niceFraction = 1; |
| 5139 | } else if (fraction <= 2) { |
| 5140 | niceFraction = 2; |
| 5141 | } else if (fraction <= 5) { |
| 5142 | niceFraction = 5; |
| 5143 | } else { |
| 5144 | niceFraction = 10; |
| 5145 | } |
| 5146 | } |
| 5147 | |
| 5148 | return niceFraction * Math.pow(10, exponent); |
| 5149 | }; |
| 5150 | //Easing functions adapted from Robert Penner's easing equations |
| 5151 | //http://www.robertpenner.com/easing/ |
| 5152 | var easingEffects = helpers.easingEffects = { |
| 5153 | linear: function(t) { |
| 5154 | return t; |
| 5155 | }, |
| 5156 | easeInQuad: function(t) { |
| 5157 | return t * t; |
| 5158 | }, |
| 5159 | easeOutQuad: function(t) { |
| 5160 | return -1 * t * (t - 2); |
| 5161 | }, |
| 5162 | easeInOutQuad: function(t) { |
| 5163 | if ((t /= 1 / 2) < 1) { |
| 5164 | return 1 / 2 * t * t; |
| 5165 | } |
| 5166 | return -1 / 2 * ((--t) * (t - 2) - 1); |
| 5167 | }, |
| 5168 | easeInCubic: function(t) { |
| 5169 | return t * t * t; |
| 5170 | }, |
| 5171 | easeOutCubic: function(t) { |
| 5172 | return 1 * ((t = t / 1 - 1) * t * t + 1); |
| 5173 | }, |
| 5174 | easeInOutCubic: function(t) { |
| 5175 | if ((t /= 1 / 2) < 1) { |
| 5176 | return 1 / 2 * t * t * t; |
| 5177 | } |
| 5178 | return 1 / 2 * ((t -= 2) * t * t + 2); |
| 5179 | }, |
| 5180 | easeInQuart: function(t) { |
| 5181 | return t * t * t * t; |
| 5182 | }, |
| 5183 | easeOutQuart: function(t) { |
| 5184 | return -1 * ((t = t / 1 - 1) * t * t * t - 1); |
| 5185 | }, |
| 5186 | easeInOutQuart: function(t) { |
| 5187 | if ((t /= 1 / 2) < 1) { |
| 5188 | return 1 / 2 * t * t * t * t; |
| 5189 | } |
| 5190 | return -1 / 2 * ((t -= 2) * t * t * t - 2); |
| 5191 | }, |
| 5192 | easeInQuint: function(t) { |
| 5193 | return 1 * (t /= 1) * t * t * t * t; |
| 5194 | }, |
| 5195 | easeOutQuint: function(t) { |
| 5196 | return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); |
| 5197 | }, |
| 5198 | easeInOutQuint: function(t) { |
| 5199 | if ((t /= 1 / 2) < 1) { |
| 5200 | return 1 / 2 * t * t * t * t * t; |
| 5201 | } |
| 5202 | return 1 / 2 * ((t -= 2) * t * t * t * t + 2); |
| 5203 | }, |
| 5204 | easeInSine: function(t) { |
| 5205 | return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; |
| 5206 | }, |
| 5207 | easeOutSine: function(t) { |
| 5208 | return 1 * Math.sin(t / 1 * (Math.PI / 2)); |
| 5209 | }, |
| 5210 | easeInOutSine: function(t) { |
| 5211 | return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); |
| 5212 | }, |
| 5213 | easeInExpo: function(t) { |
| 5214 | return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); |
| 5215 | }, |
| 5216 | easeOutExpo: function(t) { |
| 5217 | return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); |
| 5218 | }, |
| 5219 | easeInOutExpo: function(t) { |
| 5220 | if (t === 0) { |
| 5221 | return 0; |
| 5222 | } |
| 5223 | if (t === 1) { |
| 5224 | return 1; |
| 5225 | } |
| 5226 | if ((t /= 1 / 2) < 1) { |
| 5227 | return 1 / 2 * Math.pow(2, 10 * (t - 1)); |
| 5228 | } |
| 5229 | return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); |
| 5230 | }, |
| 5231 | easeInCirc: function(t) { |
| 5232 | if (t >= 1) { |
| 5233 | return t; |
| 5234 | } |
| 5235 | return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); |
| 5236 | }, |
| 5237 | easeOutCirc: function(t) { |
| 5238 | return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); |
| 5239 | }, |
| 5240 | easeInOutCirc: function(t) { |
| 5241 | if ((t /= 1 / 2) < 1) { |
| 5242 | return -1 / 2 * (Math.sqrt(1 - t * t) - 1); |
| 5243 | } |
| 5244 | return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); |
| 5245 | }, |
| 5246 | easeInElastic: function(t) { |
| 5247 | var s = 1.70158; |
| 5248 | var p = 0; |
| 5249 | var a = 1; |
| 5250 | if (t === 0) { |
| 5251 | return 0; |
| 5252 | } |
| 5253 | if ((t /= 1) === 1) { |
| 5254 | return 1; |
| 5255 | } |
| 5256 | if (!p) { |
| 5257 | p = 1 * 0.3; |
| 5258 | } |
| 5259 | if (a < Math.abs(1)) { |
| 5260 | a = 1; |
| 5261 | s = p / 4; |
| 5262 | } else { |
| 5263 | s = p / (2 * Math.PI) * Math.asin(1 / a); |
| 5264 | } |
| 5265 | return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); |
| 5266 | }, |
| 5267 | easeOutElastic: function(t) { |
| 5268 | var s = 1.70158; |
| 5269 | var p = 0; |
| 5270 | var a = 1; |
| 5271 | if (t === 0) { |
| 5272 | return 0; |
| 5273 | } |
| 5274 | if ((t /= 1) === 1) { |
| 5275 | return 1; |
| 5276 | } |
| 5277 | if (!p) { |
| 5278 | p = 1 * 0.3; |
| 5279 | } |
| 5280 | if (a < Math.abs(1)) { |
| 5281 | a = 1; |
| 5282 | s = p / 4; |
| 5283 | } else { |
| 5284 | s = p / (2 * Math.PI) * Math.asin(1 / a); |
| 5285 | } |
| 5286 | return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; |
| 5287 | }, |
| 5288 | easeInOutElastic: function(t) { |
| 5289 | var s = 1.70158; |
| 5290 | var p = 0; |
| 5291 | var a = 1; |
| 5292 | if (t === 0) { |
| 5293 | return 0; |
| 5294 | } |
| 5295 | if ((t /= 1 / 2) === 2) { |
| 5296 | return 1; |
| 5297 | } |
| 5298 | if (!p) { |
| 5299 | p = 1 * (0.3 * 1.5); |
| 5300 | } |
| 5301 | if (a < Math.abs(1)) { |
| 5302 | a = 1; |
| 5303 | s = p / 4; |
| 5304 | } else { |
| 5305 | s = p / (2 * Math.PI) * Math.asin(1 / a); |
| 5306 | } |
| 5307 | if (t < 1) { |
| 5308 | return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); |
| 5309 | } |
| 5310 | return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; |
| 5311 | }, |
| 5312 | easeInBack: function(t) { |
| 5313 | var s = 1.70158; |
| 5314 | return 1 * (t /= 1) * t * ((s + 1) * t - s); |
| 5315 | }, |
| 5316 | easeOutBack: function(t) { |
| 5317 | var s = 1.70158; |
| 5318 | return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); |
| 5319 | }, |
| 5320 | easeInOutBack: function(t) { |
| 5321 | var s = 1.70158; |
| 5322 | if ((t /= 1 / 2) < 1) { |
| 5323 | return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); |
| 5324 | } |
| 5325 | return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); |
| 5326 | }, |
| 5327 | easeInBounce: function(t) { |
| 5328 | return 1 - easingEffects.easeOutBounce(1 - t); |
| 5329 | }, |
| 5330 | easeOutBounce: function(t) { |
| 5331 | if ((t /= 1) < (1 / 2.75)) { |
| 5332 | return 1 * (7.5625 * t * t); |
| 5333 | } else if (t < (2 / 2.75)) { |
| 5334 | return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); |
| 5335 | } else if (t < (2.5 / 2.75)) { |
| 5336 | return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); |
| 5337 | } else { |
| 5338 | return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); |
| 5339 | } |
| 5340 | }, |
| 5341 | easeInOutBounce: function(t) { |
| 5342 | if (t < 1 / 2) { |
| 5343 | return easingEffects.easeInBounce(t * 2) * 0.5; |
| 5344 | } |
| 5345 | return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; |
| 5346 | } |
| 5347 | }; |
| 5348 | //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ |
| 5349 | helpers.requestAnimFrame = (function() { |
| 5350 | return window.requestAnimationFrame || |
| 5351 | window.webkitRequestAnimationFrame || |
| 5352 | window.mozRequestAnimationFrame || |
| 5353 | window.oRequestAnimationFrame || |
| 5354 | window.msRequestAnimationFrame || |
| 5355 | function(callback) { |
| 5356 | return window.setTimeout(callback, 1000 / 60); |
| 5357 | }; |
| 5358 | })(); |
| 5359 | helpers.cancelAnimFrame = (function() { |
| 5360 | return window.cancelAnimationFrame || |
| 5361 | window.webkitCancelAnimationFrame || |
| 5362 | window.mozCancelAnimationFrame || |
| 5363 | window.oCancelAnimationFrame || |
| 5364 | window.msCancelAnimationFrame || |
| 5365 | function(callback) { |
| 5366 | return window.clearTimeout(callback, 1000 / 60); |
| 5367 | }; |
| 5368 | })(); |
| 5369 | //-- DOM methods |
| 5370 | helpers.getRelativePosition = function(evt, chart) { |
| 5371 | var mouseX, mouseY; |
| 5372 | var e = evt.originalEvent || evt, |
| 5373 | canvas = evt.currentTarget || evt.srcElement, |
| 5374 | boundingRect = canvas.getBoundingClientRect(); |
| 5375 | |
| 5376 | var touches = e.touches; |
| 5377 | if (touches && touches.length > 0) { |
| 5378 | mouseX = touches[0].clientX; |
| 5379 | mouseY = touches[0].clientY; |
| 5380 | |
| 5381 | } else { |
| 5382 | mouseX = e.clientX; |
| 5383 | mouseY = e.clientY; |
| 5384 | } |
| 5385 | |
| 5386 | // Scale mouse coordinates into canvas coordinates |
| 5387 | // by following the pattern laid out by 'jerryj' in the comments of |
| 5388 | // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ |
| 5389 | var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); |
| 5390 | var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); |
| 5391 | var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); |
| 5392 | var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); |
| 5393 | var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; |
| 5394 | var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; |
| 5395 | |
| 5396 | // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However |
| 5397 | // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here |
| 5398 | mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); |
| 5399 | mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); |
| 5400 | |
| 5401 | return { |
| 5402 | x: mouseX, |
| 5403 | y: mouseY |
| 5404 | }; |
| 5405 | |
| 5406 | }; |
| 5407 | helpers.addEvent = function(node, eventType, method) { |
| 5408 | if (node.addEventListener) { |
| 5409 | node.addEventListener(eventType, method); |
| 5410 | } else if (node.attachEvent) { |
| 5411 | node.attachEvent("on" + eventType, method); |
| 5412 | } else { |
| 5413 | node["on" + eventType] = method; |
| 5414 | } |
| 5415 | }; |
| 5416 | helpers.removeEvent = function(node, eventType, handler) { |
| 5417 | if (node.removeEventListener) { |
| 5418 | node.removeEventListener(eventType, handler, false); |
| 5419 | } else if (node.detachEvent) { |
| 5420 | node.detachEvent("on" + eventType, handler); |
| 5421 | } else { |
| 5422 | node["on" + eventType] = helpers.noop; |
| 5423 | } |
| 5424 | }; |
| 5425 | helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) { |
| 5426 | // Create the events object if it's not already present |
| 5427 | var events = chartInstance.events = chartInstance.events || {}; |
| 5428 | |
| 5429 | helpers.each(arrayOfEvents, function(eventName) { |
| 5430 | events[eventName] = function() { |
| 5431 | handler.apply(chartInstance, arguments); |
| 5432 | }; |
| 5433 | helpers.addEvent(chartInstance.chart.canvas, eventName, events[eventName]); |
| 5434 | }); |
| 5435 | }; |
| 5436 | helpers.unbindEvents = function(chartInstance, arrayOfEvents) { |
| 5437 | var canvas = chartInstance.chart.canvas; |
| 5438 | helpers.each(arrayOfEvents, function(handler, eventName) { |
| 5439 | helpers.removeEvent(canvas, eventName, handler); |
| 5440 | }); |
| 5441 | }; |
| 5442 | |
| 5443 | // Private helper function to convert max-width/max-height values that may be percentages into a number |
| 5444 | function parseMaxStyle(styleValue, node, parentProperty) { |
| 5445 | var valueInPixels; |
| 5446 | if (typeof(styleValue) === 'string') { |
| 5447 | valueInPixels = parseInt(styleValue, 10); |
| 5448 | |
| 5449 | if (styleValue.indexOf('%') != -1) { |
| 5450 | // percentage * size in dimension |
| 5451 | valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; |
| 5452 | } |
| 5453 | } else { |
| 5454 | valueInPixels = styleValue; |
| 5455 | } |
| 5456 | |
| 5457 | return valueInPixels; |
| 5458 | } |
| 5459 | |
| 5460 | /** |
| 5461 | * Returns if the given value contains an effective constraint. |
| 5462 | * @private |
| 5463 | */ |
| 5464 | function isConstrainedValue(value) { |
| 5465 | return value !== undefined && value !== null && value !== 'none'; |
| 5466 | } |
| 5467 | |
| 5468 | // Private helper to get a constraint dimension |
| 5469 | // @param domNode : the node to check the constraint on |
| 5470 | // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) |
| 5471 | // @param percentageProperty : property of parent to use when calculating width as a percentage |
| 5472 | // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser |
| 5473 | function getConstraintDimension(domNode, maxStyle, percentageProperty) { |
| 5474 | var view = document.defaultView; |
| 5475 | var parentNode = domNode.parentNode; |
| 5476 | var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; |
| 5477 | var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; |
| 5478 | var hasCNode = isConstrainedValue(constrainedNode); |
| 5479 | var hasCContainer = isConstrainedValue(constrainedContainer); |
| 5480 | var infinity = Number.POSITIVE_INFINITY; |
| 5481 | |
| 5482 | if (hasCNode || hasCContainer) { |
| 5483 | return Math.min( |
| 5484 | hasCNode? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, |
| 5485 | hasCContainer? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); |
| 5486 | } |
| 5487 | |
| 5488 | return 'none'; |
| 5489 | } |
| 5490 | // returns Number or undefined if no constraint |
| 5491 | helpers.getConstraintWidth = function(domNode) { |
| 5492 | return getConstraintDimension(domNode, 'max-width', 'clientWidth'); |
| 5493 | }; |
| 5494 | // returns Number or undefined if no constraint |
| 5495 | helpers.getConstraintHeight = function(domNode) { |
| 5496 | return getConstraintDimension(domNode, 'max-height', 'clientHeight'); |
| 5497 | }; |
| 5498 | helpers.getMaximumWidth = function(domNode) { |
| 5499 | var container = domNode.parentNode; |
| 5500 | var padding = parseInt(helpers.getStyle(container, 'padding-left')) + parseInt(helpers.getStyle(container, 'padding-right')); |
| 5501 | var w = container.clientWidth - padding; |
| 5502 | var cw = helpers.getConstraintWidth(domNode); |
| 5503 | return isNaN(cw)? w : Math.min(w, cw); |
| 5504 | }; |
| 5505 | helpers.getMaximumHeight = function(domNode) { |
| 5506 | var container = domNode.parentNode; |
| 5507 | var padding = parseInt(helpers.getStyle(container, 'padding-top')) + parseInt(helpers.getStyle(container, 'padding-bottom')); |
| 5508 | var h = container.clientHeight - padding; |
| 5509 | var ch = helpers.getConstraintHeight(domNode); |
| 5510 | return isNaN(ch)? h : Math.min(h, ch); |
| 5511 | }; |
| 5512 | helpers.getStyle = function(el, property) { |
| 5513 | return el.currentStyle ? |
| 5514 | el.currentStyle[property] : |
| 5515 | document.defaultView.getComputedStyle(el, null).getPropertyValue(property); |
| 5516 | }; |
| 5517 | helpers.retinaScale = function(chart) { |
| 5518 | var ctx = chart.ctx; |
| 5519 | var canvas = chart.canvas; |
| 5520 | var width = canvas.width; |
| 5521 | var height = canvas.height; |
| 5522 | var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1; |
| 5523 | |
| 5524 | if (pixelRatio !== 1) { |
| 5525 | canvas.height = height * pixelRatio; |
| 5526 | canvas.width = width * pixelRatio; |
| 5527 | ctx.scale(pixelRatio, pixelRatio); |
| 5528 | |
| 5529 | // Store the device pixel ratio so that we can go backwards in `destroy`. |
| 5530 | // The devicePixelRatio changes with zoom, so there are no guarantees that it is the same |
| 5531 | // when destroy is called |
| 5532 | chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio; |
| 5533 | } |
| 5534 | |
| 5535 | canvas.style.width = width + 'px'; |
| 5536 | canvas.style.height = height + 'px'; |
| 5537 | }; |
| 5538 | //-- Canvas methods |
| 5539 | helpers.clear = function(chart) { |
| 5540 | chart.ctx.clearRect(0, 0, chart.width, chart.height); |
| 5541 | }; |
| 5542 | helpers.fontString = function(pixelSize, fontStyle, fontFamily) { |
| 5543 | return fontStyle + " " + pixelSize + "px " + fontFamily; |
| 5544 | }; |
| 5545 | helpers.longestText = function(ctx, font, arrayOfThings, cache) { |
| 5546 | cache = cache || {}; |
| 5547 | var data = cache.data = cache.data || {}; |
| 5548 | var gc = cache.garbageCollect = cache.garbageCollect || []; |
| 5549 | |
| 5550 | if (cache.font !== font) { |
| 5551 | data = cache.data = {}; |
| 5552 | gc = cache.garbageCollect = []; |
| 5553 | cache.font = font; |
| 5554 | } |
| 5555 | |
| 5556 | ctx.font = font; |
| 5557 | var longest = 0; |
| 5558 | helpers.each(arrayOfThings, function(thing) { |
| 5559 | // Undefined strings and arrays should not be measured |
| 5560 | if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { |
| 5561 | longest = helpers.measureText(ctx, data, gc, longest, thing); |
| 5562 | } else if (helpers.isArray(thing)) { |
| 5563 | // if it is an array lets measure each element |
| 5564 | // to do maybe simplify this function a bit so we can do this more recursively? |
| 5565 | helpers.each(thing, function(nestedThing) { |
| 5566 | // Undefined strings and arrays should not be measured |
| 5567 | if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { |
| 5568 | longest = helpers.measureText(ctx, data, gc, longest, nestedThing); |
| 5569 | } |
| 5570 | }); |
| 5571 | } |
| 5572 | }); |
| 5573 | |
| 5574 | var gcLen = gc.length / 2; |
| 5575 | if (gcLen > arrayOfThings.length) { |
| 5576 | for (var i = 0; i < gcLen; i++) { |
| 5577 | delete data[gc[i]]; |
| 5578 | } |
| 5579 | gc.splice(0, gcLen); |
| 5580 | } |
| 5581 | return longest; |
| 5582 | }; |
| 5583 | helpers.measureText = function (ctx, data, gc, longest, string) { |
| 5584 | var textWidth = data[string]; |
| 5585 | if (!textWidth) { |
| 5586 | textWidth = data[string] = ctx.measureText(string).width; |
| 5587 | gc.push(string); |
| 5588 | } |
| 5589 | if (textWidth > longest) { |
| 5590 | longest = textWidth; |
| 5591 | } |
| 5592 | return longest; |
| 5593 | }; |
| 5594 | helpers.numberOfLabelLines = function(arrayOfThings) { |
| 5595 | var numberOfLines = 1; |
| 5596 | helpers.each(arrayOfThings, function(thing) { |
| 5597 | if (helpers.isArray(thing)) { |
| 5598 | if (thing.length > numberOfLines) { |
| 5599 | numberOfLines = thing.length; |
| 5600 | } |
| 5601 | } |
| 5602 | }); |
| 5603 | return numberOfLines; |
| 5604 | }; |
| 5605 | helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) { |
| 5606 | ctx.beginPath(); |
| 5607 | ctx.moveTo(x + radius, y); |
| 5608 | ctx.lineTo(x + width - radius, y); |
| 5609 | ctx.quadraticCurveTo(x + width, y, x + width, y + radius); |
| 5610 | ctx.lineTo(x + width, y + height - radius); |
| 5611 | ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); |
| 5612 | ctx.lineTo(x + radius, y + height); |
| 5613 | ctx.quadraticCurveTo(x, y + height, x, y + height - radius); |
| 5614 | ctx.lineTo(x, y + radius); |
| 5615 | ctx.quadraticCurveTo(x, y, x + radius, y); |
| 5616 | ctx.closePath(); |
| 5617 | }; |
| 5618 | helpers.color = function(c) { |
| 5619 | if (!color) { |
| 5620 | console.log('Color.js not found!'); |
| 5621 | return c; |
| 5622 | } |
| 5623 | |
| 5624 | /* global CanvasGradient */ |
| 5625 | if (c instanceof CanvasGradient) { |
| 5626 | return color(Chart.defaults.global.defaultColor); |
| 5627 | } |
| 5628 | |
| 5629 | return color(c); |
| 5630 | }; |
| 5631 | helpers.addResizeListener = function(node, callback) { |
| 5632 | // Hide an iframe before the node |
| 5633 | var hiddenIframe = document.createElement('iframe'); |
| 5634 | var hiddenIframeClass = 'chartjs-hidden-iframe'; |
| 5635 | |
| 5636 | if (hiddenIframe.classlist) { |
| 5637 | // can use classlist |
| 5638 | hiddenIframe.classlist.add(hiddenIframeClass); |
| 5639 | } else { |
| 5640 | hiddenIframe.setAttribute('class', hiddenIframeClass); |
| 5641 | } |
| 5642 | |
| 5643 | // Set the style |
| 5644 | var style = hiddenIframe.style; |
| 5645 | style.width = '100%'; |
| 5646 | style.display = 'block'; |
| 5647 | style.border = 0; |
| 5648 | style.height = 0; |
| 5649 | style.margin = 0; |
| 5650 | style.position = 'absolute'; |
| 5651 | style.left = 0; |
| 5652 | style.right = 0; |
| 5653 | style.top = 0; |
| 5654 | style.bottom = 0; |
| 5655 | |
| 5656 | // Insert the iframe so that contentWindow is available |
| 5657 | node.insertBefore(hiddenIframe, node.firstChild); |
| 5658 | |
| 5659 | (hiddenIframe.contentWindow || hiddenIframe).onresize = function() { |
| 5660 | if (callback) { |
| 5661 | callback(); |
| 5662 | } |
| 5663 | }; |
| 5664 | }; |
| 5665 | helpers.removeResizeListener = function(node) { |
| 5666 | var hiddenIframe = node.querySelector('.chartjs-hidden-iframe'); |
| 5667 | |
| 5668 | // Remove the resize detect iframe |
| 5669 | if (hiddenIframe) { |
| 5670 | hiddenIframe.parentNode.removeChild(hiddenIframe); |
| 5671 | } |
| 5672 | }; |
| 5673 | helpers.isArray = Array.isArray? |
| 5674 | function(obj) { return Array.isArray(obj); } : |
| 5675 | function(obj) { |
| 5676 | return Object.prototype.toString.call(obj) === '[object Array]'; |
| 5677 | }; |
| 5678 | //! @see http://stackoverflow.com/a/14853974 |
| 5679 | helpers.arrayEquals = function(a0, a1) { |
| 5680 | var i, ilen, v0, v1; |
| 5681 | |
| 5682 | if (!a0 || !a1 || a0.length != a1.length) { |
| 5683 | return false; |
| 5684 | } |
| 5685 | |
| 5686 | for (i = 0, ilen=a0.length; i < ilen; ++i) { |
| 5687 | v0 = a0[i]; |
| 5688 | v1 = a1[i]; |
| 5689 | |
| 5690 | if (v0 instanceof Array && v1 instanceof Array) { |
| 5691 | if (!helpers.arrayEquals(v0, v1)) { |
| 5692 | return false; |
| 5693 | } |
| 5694 | } else if (v0 != v1) { |
| 5695 | // NOTE: two different object instances will never be equal: {x:20} != {x:20} |
| 5696 | return false; |
| 5697 | } |
| 5698 | } |
| 5699 | |
| 5700 | return true; |
| 5701 | }; |
| 5702 | helpers.callCallback = function(fn, args, _tArg) { |
| 5703 | if (fn && typeof fn.call === 'function') { |
| 5704 | fn.apply(_tArg, args); |
| 5705 | } |
| 5706 | }; |
| 5707 | helpers.getHoverColor = function(color) { |
| 5708 | /* global CanvasPattern */ |
| 5709 | return (color instanceof CanvasPattern) ? |
| 5710 | color : |
| 5711 | helpers.color(color).saturate(0.5).darken(0.1).rgbString(); |
| 5712 | }; |
| 5713 | }; |
| 5714 | |
| 5715 | },{"3":3}],27:[function(require,module,exports){ |
| 5716 | "use strict"; |
| 5717 | |
| 5718 | module.exports = function() { |
| 5719 | |
| 5720 | //Occupy the global variable of Chart, and create a simple base class |
| 5721 | var Chart = function(context, config) { |
| 5722 | var me = this; |
| 5723 | var helpers = Chart.helpers; |
| 5724 | me.config = config || { |
| 5725 | data: { |
| 5726 | datasets: [] |
| 5727 | } |
| 5728 | }; |
| 5729 | |
| 5730 | // Support a jQuery'd canvas element |
| 5731 | if (context.length && context[0].getContext) { |
| 5732 | context = context[0]; |
| 5733 | } |
| 5734 | |
| 5735 | // Support a canvas domnode |
| 5736 | if (context.getContext) { |
| 5737 | context = context.getContext("2d"); |
| 5738 | } |
| 5739 | |
| 5740 | me.ctx = context; |
| 5741 | me.canvas = context.canvas; |
| 5742 | |
| 5743 | context.canvas.style.display = context.canvas.style.display || 'block'; |
| 5744 | |
| 5745 | // Figure out what the size of the chart will be. |
| 5746 | // If the canvas has a specified width and height, we use those else |
| 5747 | // we look to see if the canvas node has a CSS width and height. |
| 5748 | // If there is still no height, fill the parent container |
| 5749 | me.width = context.canvas.width || parseInt(helpers.getStyle(context.canvas, 'width'), 10) || helpers.getMaximumWidth(context.canvas); |
| 5750 | me.height = context.canvas.height || parseInt(helpers.getStyle(context.canvas, 'height'), 10) || helpers.getMaximumHeight(context.canvas); |
| 5751 | |
| 5752 | me.aspectRatio = me.width / me.height; |
| 5753 | |
| 5754 | if (isNaN(me.aspectRatio) || isFinite(me.aspectRatio) === false) { |
| 5755 | // If the canvas has no size, try and figure out what the aspect ratio will be. |
| 5756 | // Some charts prefer square canvases (pie, radar, etc). If that is specified, use that |
| 5757 | // else use the canvas default ratio of 2 |
| 5758 | me.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2; |
| 5759 | } |
| 5760 | |
| 5761 | // Store the original style of the element so we can set it back |
| 5762 | me.originalCanvasStyleWidth = context.canvas.style.width; |
| 5763 | me.originalCanvasStyleHeight = context.canvas.style.height; |
| 5764 | |
| 5765 | // High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. |
| 5766 | helpers.retinaScale(me); |
| 5767 | me.controller = new Chart.Controller(me); |
| 5768 | |
| 5769 | // Always bind this so that if the responsive state changes we still work |
| 5770 | helpers.addResizeListener(context.canvas.parentNode, function() { |
| 5771 | if (me.controller && me.controller.config.options.responsive) { |
| 5772 | me.controller.resize(); |
| 5773 | } |
| 5774 | }); |
| 5775 | |
| 5776 | return me.controller ? me.controller : me; |
| 5777 | |
| 5778 | }; |
| 5779 | |
| 5780 | //Globally expose the defaults to allow for user updating/changing |
| 5781 | Chart.defaults = { |
| 5782 | global: { |
| 5783 | responsive: true, |
| 5784 | responsiveAnimationDuration: 0, |
| 5785 | maintainAspectRatio: true, |
| 5786 | events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"], |
| 5787 | hover: { |
| 5788 | onHover: null, |
| 5789 | mode: 'single', |
| 5790 | animationDuration: 400 |
| 5791 | }, |
| 5792 | onClick: null, |
| 5793 | defaultColor: 'rgba(0,0,0,0.1)', |
| 5794 | defaultFontColor: '#666', |
| 5795 | defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", |
| 5796 | defaultFontSize: 12, |
| 5797 | defaultFontStyle: 'normal', |
| 5798 | showLines: true, |
| 5799 | |
| 5800 | // Element defaults defined in element extensions |
| 5801 | elements: {}, |
| 5802 | |
| 5803 | // Legend callback string |
| 5804 | legendCallback: function(chart) { |
| 5805 | var text = []; |
| 5806 | text.push('<ul class="' + chart.id + '-legend">'); |
| 5807 | for (var i = 0; i < chart.data.datasets.length; i++) { |
| 5808 | text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>'); |
| 5809 | if (chart.data.datasets[i].label) { |
| 5810 | text.push(chart.data.datasets[i].label); |
| 5811 | } |
| 5812 | text.push('</li>'); |
| 5813 | } |
| 5814 | text.push('</ul>'); |
| 5815 | |
| 5816 | return text.join(""); |
| 5817 | } |
| 5818 | } |
| 5819 | }; |
| 5820 | |
| 5821 | Chart.Chart = Chart; |
| 5822 | |
| 5823 | return Chart; |
| 5824 | |
| 5825 | }; |
| 5826 | |
| 5827 | },{}],28:[function(require,module,exports){ |
| 5828 | "use strict"; |
| 5829 | |
| 5830 | module.exports = function(Chart) { |
| 5831 | |
| 5832 | var helpers = Chart.helpers; |
| 5833 | |
| 5834 | // The layout service is very self explanatory. It's responsible for the layout within a chart. |
| 5835 | // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need |
| 5836 | // It is this service's responsibility of carrying out that layout. |
| 5837 | Chart.layoutService = { |
| 5838 | defaults: {}, |
| 5839 | |
| 5840 | // Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins. |
| 5841 | addBox: function(chartInstance, box) { |
| 5842 | if (!chartInstance.boxes) { |
| 5843 | chartInstance.boxes = []; |
| 5844 | } |
| 5845 | chartInstance.boxes.push(box); |
| 5846 | }, |
| 5847 | |
| 5848 | removeBox: function(chartInstance, box) { |
| 5849 | if (!chartInstance.boxes) { |
| 5850 | return; |
| 5851 | } |
| 5852 | chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1); |
| 5853 | }, |
| 5854 | |
| 5855 | // The most important function |
| 5856 | update: function(chartInstance, width, height) { |
| 5857 | |
| 5858 | if (!chartInstance) { |
| 5859 | return; |
| 5860 | } |
| 5861 | |
| 5862 | var xPadding = 0; |
| 5863 | var yPadding = 0; |
| 5864 | |
| 5865 | var leftBoxes = helpers.where(chartInstance.boxes, function(box) { |
| 5866 | return box.options.position === "left"; |
| 5867 | }); |
| 5868 | var rightBoxes = helpers.where(chartInstance.boxes, function(box) { |
| 5869 | return box.options.position === "right"; |
| 5870 | }); |
| 5871 | var topBoxes = helpers.where(chartInstance.boxes, function(box) { |
| 5872 | return box.options.position === "top"; |
| 5873 | }); |
| 5874 | var bottomBoxes = helpers.where(chartInstance.boxes, function(box) { |
| 5875 | return box.options.position === "bottom"; |
| 5876 | }); |
| 5877 | |
| 5878 | // Boxes that overlay the chartarea such as the radialLinear scale |
| 5879 | var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) { |
| 5880 | return box.options.position === "chartArea"; |
| 5881 | }); |
| 5882 | |
| 5883 | // Ensure that full width boxes are at the very top / bottom |
| 5884 | topBoxes.sort(function(a, b) { |
| 5885 | return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0); |
| 5886 | }); |
| 5887 | bottomBoxes.sort(function(a, b) { |
| 5888 | return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0); |
| 5889 | }); |
| 5890 | |
| 5891 | // Essentially we now have any number of boxes on each of the 4 sides. |
| 5892 | // Our canvas looks like the following. |
| 5893 | // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and |
| 5894 | // B1 is the bottom axis |
| 5895 | // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays |
| 5896 | // These locations are single-box locations only, when trying to register a chartArea location that is already taken, |
| 5897 | // an error will be thrown. |
| 5898 | // |
| 5899 | // |----------------------------------------------------| |
| 5900 | // | T1 (Full Width) | |
| 5901 | // |----------------------------------------------------| |
| 5902 | // | | | T2 | | |
| 5903 | // | |----|-------------------------------------|----| |
| 5904 | // | | | C1 | | C2 | | |
| 5905 | // | | |----| |----| | |
| 5906 | // | | | | | |
| 5907 | // | L1 | L2 | ChartArea (C0) | R1 | |
| 5908 | // | | | | | |
| 5909 | // | | |----| |----| | |
| 5910 | // | | | C3 | | C4 | | |
| 5911 | // | |----|-------------------------------------|----| |
| 5912 | // | | | B1 | | |
| 5913 | // |----------------------------------------------------| |
| 5914 | // | B2 (Full Width) | |
| 5915 | // |----------------------------------------------------| |
| 5916 | // |
| 5917 | // What we do to find the best sizing, we do the following |
| 5918 | // 1. Determine the minimum size of the chart area. |
| 5919 | // 2. Split the remaining width equally between each vertical axis |
| 5920 | // 3. Split the remaining height equally between each horizontal axis |
| 5921 | // 4. Give each layout the maximum size it can be. The layout will return it's minimum size |
| 5922 | // 5. Adjust the sizes of each axis based on it's minimum reported size. |
| 5923 | // 6. Refit each axis |
| 5924 | // 7. Position each axis in the final location |
| 5925 | // 8. Tell the chart the final location of the chart area |
| 5926 | // 9. Tell any axes that overlay the chart area the positions of the chart area |
| 5927 | |
| 5928 | // Step 1 |
| 5929 | var chartWidth = width - (2 * xPadding); |
| 5930 | var chartHeight = height - (2 * yPadding); |
| 5931 | var chartAreaWidth = chartWidth / 2; // min 50% |
| 5932 | var chartAreaHeight = chartHeight / 2; // min 50% |
| 5933 | |
| 5934 | // Step 2 |
| 5935 | var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); |
| 5936 | |
| 5937 | // Step 3 |
| 5938 | var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); |
| 5939 | |
| 5940 | // Step 4 |
| 5941 | var maxChartAreaWidth = chartWidth; |
| 5942 | var maxChartAreaHeight = chartHeight; |
| 5943 | var minBoxSizes = []; |
| 5944 | |
| 5945 | helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); |
| 5946 | |
| 5947 | function getMinimumBoxSize(box) { |
| 5948 | var minSize; |
| 5949 | var isHorizontal = box.isHorizontal(); |
| 5950 | |
| 5951 | if (isHorizontal) { |
| 5952 | minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); |
| 5953 | maxChartAreaHeight -= minSize.height; |
| 5954 | } else { |
| 5955 | minSize = box.update(verticalBoxWidth, chartAreaHeight); |
| 5956 | maxChartAreaWidth -= minSize.width; |
| 5957 | } |
| 5958 | |
| 5959 | minBoxSizes.push({ |
| 5960 | horizontal: isHorizontal, |
| 5961 | minSize: minSize, |
| 5962 | box: box |
| 5963 | }); |
| 5964 | } |
| 5965 | |
| 5966 | // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could |
| 5967 | // be if the axes are drawn at their minimum sizes. |
| 5968 | |
| 5969 | // Steps 5 & 6 |
| 5970 | var totalLeftBoxesWidth = xPadding; |
| 5971 | var totalRightBoxesWidth = xPadding; |
| 5972 | var totalTopBoxesHeight = yPadding; |
| 5973 | var totalBottomBoxesHeight = yPadding; |
| 5974 | |
| 5975 | // Update, and calculate the left and right margins for the horizontal boxes |
| 5976 | helpers.each(leftBoxes.concat(rightBoxes), fitBox); |
| 5977 | |
| 5978 | helpers.each(leftBoxes, function(box) { |
| 5979 | totalLeftBoxesWidth += box.width; |
| 5980 | }); |
| 5981 | |
| 5982 | helpers.each(rightBoxes, function(box) { |
| 5983 | totalRightBoxesWidth += box.width; |
| 5984 | }); |
| 5985 | |
| 5986 | // Set the Left and Right margins for the horizontal boxes |
| 5987 | helpers.each(topBoxes.concat(bottomBoxes), fitBox); |
| 5988 | |
| 5989 | // Function to fit a box |
| 5990 | function fitBox(box) { |
| 5991 | var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { |
| 5992 | return minBoxSize.box === box; |
| 5993 | }); |
| 5994 | |
| 5995 | if (minBoxSize) { |
| 5996 | if (box.isHorizontal()) { |
| 5997 | var scaleMargin = { |
| 5998 | left: totalLeftBoxesWidth, |
| 5999 | right: totalRightBoxesWidth, |
| 6000 | top: 0, |
| 6001 | bottom: 0 |
| 6002 | }; |
| 6003 | |
| 6004 | // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends |
| 6005 | // on the margin. Sometimes they need to increase in size slightly |
| 6006 | box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); |
| 6007 | } else { |
| 6008 | box.update(minBoxSize.minSize.width, maxChartAreaHeight); |
| 6009 | } |
| 6010 | } |
| 6011 | } |
| 6012 | |
| 6013 | // Figure out how much margin is on the top and bottom of the vertical boxes |
| 6014 | helpers.each(topBoxes, function(box) { |
| 6015 | totalTopBoxesHeight += box.height; |
| 6016 | }); |
| 6017 | |
| 6018 | helpers.each(bottomBoxes, function(box) { |
| 6019 | totalBottomBoxesHeight += box.height; |
| 6020 | }); |
| 6021 | |
| 6022 | // Let the left layout know the final margin |
| 6023 | helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); |
| 6024 | |
| 6025 | function finalFitVerticalBox(box) { |
| 6026 | var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { |
| 6027 | return minBoxSize.box === box; |
| 6028 | }); |
| 6029 | |
| 6030 | var scaleMargin = { |
| 6031 | left: 0, |
| 6032 | right: 0, |
| 6033 | top: totalTopBoxesHeight, |
| 6034 | bottom: totalBottomBoxesHeight |
| 6035 | }; |
| 6036 | |
| 6037 | if (minBoxSize) { |
| 6038 | box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); |
| 6039 | } |
| 6040 | } |
| 6041 | |
| 6042 | // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) |
| 6043 | totalLeftBoxesWidth = xPadding; |
| 6044 | totalRightBoxesWidth = xPadding; |
| 6045 | totalTopBoxesHeight = yPadding; |
| 6046 | totalBottomBoxesHeight = yPadding; |
| 6047 | |
| 6048 | helpers.each(leftBoxes, function(box) { |
| 6049 | totalLeftBoxesWidth += box.width; |
| 6050 | }); |
| 6051 | |
| 6052 | helpers.each(rightBoxes, function(box) { |
| 6053 | totalRightBoxesWidth += box.width; |
| 6054 | }); |
| 6055 | |
| 6056 | helpers.each(topBoxes, function(box) { |
| 6057 | totalTopBoxesHeight += box.height; |
| 6058 | }); |
| 6059 | helpers.each(bottomBoxes, function(box) { |
| 6060 | totalBottomBoxesHeight += box.height; |
| 6061 | }); |
| 6062 | |
| 6063 | // Figure out if our chart area changed. This would occur if the dataset layout label rotation |
| 6064 | // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do |
| 6065 | // without calling `fit` again |
| 6066 | var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; |
| 6067 | var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; |
| 6068 | |
| 6069 | if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { |
| 6070 | helpers.each(leftBoxes, function(box) { |
| 6071 | box.height = newMaxChartAreaHeight; |
| 6072 | }); |
| 6073 | |
| 6074 | helpers.each(rightBoxes, function(box) { |
| 6075 | box.height = newMaxChartAreaHeight; |
| 6076 | }); |
| 6077 | |
| 6078 | helpers.each(topBoxes, function(box) { |
| 6079 | if (!box.options.fullWidth) { |
| 6080 | box.width = newMaxChartAreaWidth; |
| 6081 | } |
| 6082 | }); |
| 6083 | |
| 6084 | helpers.each(bottomBoxes, function(box) { |
| 6085 | if (!box.options.fullWidth) { |
| 6086 | box.width = newMaxChartAreaWidth; |
| 6087 | } |
| 6088 | }); |
| 6089 | |
| 6090 | maxChartAreaHeight = newMaxChartAreaHeight; |
| 6091 | maxChartAreaWidth = newMaxChartAreaWidth; |
| 6092 | } |
| 6093 | |
| 6094 | // Step 7 - Position the boxes |
| 6095 | var left = xPadding; |
| 6096 | var top = yPadding; |
| 6097 | |
| 6098 | helpers.each(leftBoxes.concat(topBoxes), placeBox); |
| 6099 | |
| 6100 | // Account for chart width and height |
| 6101 | left += maxChartAreaWidth; |
| 6102 | top += maxChartAreaHeight; |
| 6103 | |
| 6104 | helpers.each(rightBoxes, placeBox); |
| 6105 | helpers.each(bottomBoxes, placeBox); |
| 6106 | |
| 6107 | function placeBox(box) { |
| 6108 | if (box.isHorizontal()) { |
| 6109 | box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth; |
| 6110 | box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth; |
| 6111 | box.top = top; |
| 6112 | box.bottom = top + box.height; |
| 6113 | |
| 6114 | // Move to next point |
| 6115 | top = box.bottom; |
| 6116 | |
| 6117 | } else { |
| 6118 | |
| 6119 | box.left = left; |
| 6120 | box.right = left + box.width; |
| 6121 | box.top = totalTopBoxesHeight; |
| 6122 | box.bottom = totalTopBoxesHeight + maxChartAreaHeight; |
| 6123 | |
| 6124 | // Move to next point |
| 6125 | left = box.right; |
| 6126 | } |
| 6127 | } |
| 6128 | |
| 6129 | // Step 8 |
| 6130 | chartInstance.chartArea = { |
| 6131 | left: totalLeftBoxesWidth, |
| 6132 | top: totalTopBoxesHeight, |
| 6133 | right: totalLeftBoxesWidth + maxChartAreaWidth, |
| 6134 | bottom: totalTopBoxesHeight + maxChartAreaHeight |
| 6135 | }; |
| 6136 | |
| 6137 | // Step 9 |
| 6138 | helpers.each(chartAreaBoxes, function(box) { |
| 6139 | box.left = chartInstance.chartArea.left; |
| 6140 | box.top = chartInstance.chartArea.top; |
| 6141 | box.right = chartInstance.chartArea.right; |
| 6142 | box.bottom = chartInstance.chartArea.bottom; |
| 6143 | |
| 6144 | box.update(maxChartAreaWidth, maxChartAreaHeight); |
| 6145 | }); |
| 6146 | } |
| 6147 | }; |
| 6148 | }; |
| 6149 | |
| 6150 | },{}],29:[function(require,module,exports){ |
| 6151 | "use strict"; |
| 6152 | |
| 6153 | module.exports = function(Chart) { |
| 6154 | |
| 6155 | var helpers = Chart.helpers; |
| 6156 | var noop = helpers.noop; |
| 6157 | |
| 6158 | Chart.defaults.global.legend = { |
| 6159 | |
| 6160 | display: true, |
| 6161 | position: 'top', |
| 6162 | fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) |
| 6163 | reverse: false, |
| 6164 | |
| 6165 | // a callback that will handle |
| 6166 | onClick: function(e, legendItem) { |
| 6167 | var index = legendItem.datasetIndex; |
| 6168 | var ci = this.chart; |
| 6169 | var meta = ci.getDatasetMeta(index); |
| 6170 | |
| 6171 | // See controller.isDatasetVisible comment |
| 6172 | meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null; |
| 6173 | |
| 6174 | // We hid a dataset ... rerender the chart |
| 6175 | ci.update(); |
| 6176 | }, |
| 6177 | |
| 6178 | labels: { |
| 6179 | boxWidth: 40, |
| 6180 | padding: 10, |
| 6181 | // Generates labels shown in the legend |
| 6182 | // Valid properties to return: |
| 6183 | // text : text to display |
| 6184 | // fillStyle : fill of coloured box |
| 6185 | // strokeStyle: stroke of coloured box |
| 6186 | // hidden : if this legend item refers to a hidden item |
| 6187 | // lineCap : cap style for line |
| 6188 | // lineDash |
| 6189 | // lineDashOffset : |
| 6190 | // lineJoin : |
| 6191 | // lineWidth : |
| 6192 | generateLabels: function(chart) { |
| 6193 | var data = chart.data; |
| 6194 | return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { |
| 6195 | return { |
| 6196 | text: dataset.label, |
| 6197 | fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), |
| 6198 | hidden: !chart.isDatasetVisible(i), |
| 6199 | lineCap: dataset.borderCapStyle, |
| 6200 | lineDash: dataset.borderDash, |
| 6201 | lineDashOffset: dataset.borderDashOffset, |
| 6202 | lineJoin: dataset.borderJoinStyle, |
| 6203 | lineWidth: dataset.borderWidth, |
| 6204 | strokeStyle: dataset.borderColor, |
| 6205 | pointStyle: dataset.pointStyle, |
| 6206 | |
| 6207 | // Below is extra data used for toggling the datasets |
| 6208 | datasetIndex: i |
| 6209 | }; |
| 6210 | }, this) : []; |
| 6211 | } |
| 6212 | } |
| 6213 | }; |
| 6214 | |
| 6215 | Chart.Legend = Chart.Element.extend({ |
| 6216 | |
| 6217 | initialize: function(config) { |
| 6218 | helpers.extend(this, config); |
| 6219 | |
| 6220 | // Contains hit boxes for each dataset (in dataset order) |
| 6221 | this.legendHitBoxes = []; |
| 6222 | |
| 6223 | // Are we in doughnut mode which has a different data type |
| 6224 | this.doughnutMode = false; |
| 6225 | }, |
| 6226 | |
| 6227 | // These methods are ordered by lifecyle. Utilities then follow. |
| 6228 | // Any function defined here is inherited by all legend types. |
| 6229 | // Any function can be extended by the legend type |
| 6230 | |
| 6231 | beforeUpdate: noop, |
| 6232 | update: function(maxWidth, maxHeight, margins) { |
| 6233 | var me = this; |
| 6234 | |
| 6235 | // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) |
| 6236 | me.beforeUpdate(); |
| 6237 | |
| 6238 | // Absorb the master measurements |
| 6239 | me.maxWidth = maxWidth; |
| 6240 | me.maxHeight = maxHeight; |
| 6241 | me.margins = margins; |
| 6242 | |
| 6243 | // Dimensions |
| 6244 | me.beforeSetDimensions(); |
| 6245 | me.setDimensions(); |
| 6246 | me.afterSetDimensions(); |
| 6247 | // Labels |
| 6248 | me.beforeBuildLabels(); |
| 6249 | me.buildLabels(); |
| 6250 | me.afterBuildLabels(); |
| 6251 | |
| 6252 | // Fit |
| 6253 | me.beforeFit(); |
| 6254 | me.fit(); |
| 6255 | me.afterFit(); |
| 6256 | // |
| 6257 | me.afterUpdate(); |
| 6258 | |
| 6259 | return me.minSize; |
| 6260 | }, |
| 6261 | afterUpdate: noop, |
| 6262 | |
| 6263 | // |
| 6264 | |
| 6265 | beforeSetDimensions: noop, |
| 6266 | setDimensions: function() { |
| 6267 | var me = this; |
| 6268 | // Set the unconstrained dimension before label rotation |
| 6269 | if (me.isHorizontal()) { |
| 6270 | // Reset position before calculating rotation |
| 6271 | me.width = me.maxWidth; |
| 6272 | me.left = 0; |
| 6273 | me.right = me.width; |
| 6274 | } else { |
| 6275 | me.height = me.maxHeight; |
| 6276 | |
| 6277 | // Reset position before calculating rotation |
| 6278 | me.top = 0; |
| 6279 | me.bottom = me.height; |
| 6280 | } |
| 6281 | |
| 6282 | // Reset padding |
| 6283 | me.paddingLeft = 0; |
| 6284 | me.paddingTop = 0; |
| 6285 | me.paddingRight = 0; |
| 6286 | me.paddingBottom = 0; |
| 6287 | |
| 6288 | // Reset minSize |
| 6289 | me.minSize = { |
| 6290 | width: 0, |
| 6291 | height: 0 |
| 6292 | }; |
| 6293 | }, |
| 6294 | afterSetDimensions: noop, |
| 6295 | |
| 6296 | // |
| 6297 | |
| 6298 | beforeBuildLabels: noop, |
| 6299 | buildLabels: function() { |
| 6300 | var me = this; |
| 6301 | me.legendItems = me.options.labels.generateLabels.call(me, me.chart); |
| 6302 | if(me.options.reverse){ |
| 6303 | me.legendItems.reverse(); |
| 6304 | } |
| 6305 | }, |
| 6306 | afterBuildLabels: noop, |
| 6307 | |
| 6308 | // |
| 6309 | |
| 6310 | beforeFit: noop, |
| 6311 | fit: function() { |
| 6312 | var me = this; |
| 6313 | var opts = me.options; |
| 6314 | var labelOpts = opts.labels; |
| 6315 | var display = opts.display; |
| 6316 | |
| 6317 | var ctx = me.ctx; |
| 6318 | |
| 6319 | var globalDefault = Chart.defaults.global, |
| 6320 | itemOrDefault = helpers.getValueOrDefault, |
| 6321 | fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize), |
| 6322 | fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), |
| 6323 | fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily), |
| 6324 | labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); |
| 6325 | |
| 6326 | // Reset hit boxes |
| 6327 | var hitboxes = me.legendHitBoxes = []; |
| 6328 | |
| 6329 | var minSize = me.minSize; |
| 6330 | var isHorizontal = me.isHorizontal(); |
| 6331 | |
| 6332 | if (isHorizontal) { |
| 6333 | minSize.width = me.maxWidth; // fill all the width |
| 6334 | minSize.height = display ? 10 : 0; |
| 6335 | } else { |
| 6336 | minSize.width = display ? 10 : 0; |
| 6337 | minSize.height = me.maxHeight; // fill all the height |
| 6338 | } |
| 6339 | |
| 6340 | // Increase sizes here |
| 6341 | if (display) { |
| 6342 | ctx.font = labelFont; |
| 6343 | |
| 6344 | if (isHorizontal) { |
| 6345 | // Labels |
| 6346 | |
| 6347 | // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one |
| 6348 | var lineWidths = me.lineWidths = [0]; |
| 6349 | var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; |
| 6350 | |
| 6351 | ctx.textAlign = "left"; |
| 6352 | ctx.textBaseline = 'top'; |
| 6353 | |
| 6354 | helpers.each(me.legendItems, function(legendItem, i) { |
| 6355 | var boxWidth = labelOpts.usePointStyle ? |
| 6356 | fontSize * Math.sqrt(2) : |
| 6357 | labelOpts.boxWidth; |
| 6358 | |
| 6359 | var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; |
| 6360 | if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { |
| 6361 | totalHeight += fontSize + (labelOpts.padding); |
| 6362 | lineWidths[lineWidths.length] = me.left; |
| 6363 | } |
| 6364 | |
| 6365 | // Store the hitbox width and height here. Final position will be updated in `draw` |
| 6366 | hitboxes[i] = { |
| 6367 | left: 0, |
| 6368 | top: 0, |
| 6369 | width: width, |
| 6370 | height: fontSize |
| 6371 | }; |
| 6372 | |
| 6373 | lineWidths[lineWidths.length - 1] += width + labelOpts.padding; |
| 6374 | }); |
| 6375 | |
| 6376 | minSize.height += totalHeight; |
| 6377 | |
| 6378 | } else { |
| 6379 | var vPadding = labelOpts.padding; |
| 6380 | var columnWidths = me.columnWidths = []; |
| 6381 | var totalWidth = labelOpts.padding; |
| 6382 | var currentColWidth = 0; |
| 6383 | var currentColHeight = 0; |
| 6384 | var itemHeight = fontSize + vPadding; |
| 6385 | |
| 6386 | helpers.each(me.legendItems, function(legendItem, i) { |
| 6387 | // If usePointStyle is set, multiple boxWidth by 2 since it represents |
| 6388 | // the radius and not truly the width |
| 6389 | var boxWidth = labelOpts.usePointStyle ? 2 * labelOpts.boxWidth : labelOpts.boxWidth; |
| 6390 | |
| 6391 | var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; |
| 6392 | |
| 6393 | // If too tall, go to new column |
| 6394 | if (currentColHeight + itemHeight > minSize.height) { |
| 6395 | totalWidth += currentColWidth + labelOpts.padding; |
| 6396 | columnWidths.push(currentColWidth); // previous column width |
| 6397 | |
| 6398 | currentColWidth = 0; |
| 6399 | currentColHeight = 0; |
| 6400 | } |
| 6401 | |
| 6402 | // Get max width |
| 6403 | currentColWidth = Math.max(currentColWidth, itemWidth); |
| 6404 | currentColHeight += itemHeight; |
| 6405 | |
| 6406 | // Store the hitbox width and height here. Final position will be updated in `draw` |
| 6407 | hitboxes[i] = { |
| 6408 | left: 0, |
| 6409 | top: 0, |
| 6410 | width: itemWidth, |
| 6411 | height: fontSize |
| 6412 | }; |
| 6413 | }); |
| 6414 | |
| 6415 | totalWidth += currentColWidth; |
| 6416 | columnWidths.push(currentColWidth); |
| 6417 | minSize.width += totalWidth; |
| 6418 | } |
| 6419 | } |
| 6420 | |
| 6421 | me.width = minSize.width; |
| 6422 | me.height = minSize.height; |
| 6423 | }, |
| 6424 | afterFit: noop, |
| 6425 | |
| 6426 | // Shared Methods |
| 6427 | isHorizontal: function() { |
| 6428 | return this.options.position === "top" || this.options.position === "bottom"; |
| 6429 | }, |
| 6430 | |
| 6431 | // Actualy draw the legend on the canvas |
| 6432 | draw: function() { |
| 6433 | var me = this; |
| 6434 | var opts = me.options; |
| 6435 | var labelOpts = opts.labels; |
| 6436 | var globalDefault = Chart.defaults.global, |
| 6437 | lineDefault = globalDefault.elements.line, |
| 6438 | legendWidth = me.width, |
| 6439 | lineWidths = me.lineWidths; |
| 6440 | |
| 6441 | if (opts.display) { |
| 6442 | var ctx = me.ctx, |
| 6443 | cursor, |
| 6444 | itemOrDefault = helpers.getValueOrDefault, |
| 6445 | fontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor), |
| 6446 | fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize), |
| 6447 | fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), |
| 6448 | fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily), |
| 6449 | labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); |
| 6450 | |
| 6451 | // Canvas setup |
| 6452 | ctx.textAlign = "left"; |
| 6453 | ctx.textBaseline = 'top'; |
| 6454 | ctx.lineWidth = 0.5; |
| 6455 | ctx.strokeStyle = fontColor; // for strikethrough effect |
| 6456 | ctx.fillStyle = fontColor; // render in correct colour |
| 6457 | ctx.font = labelFont; |
| 6458 | |
| 6459 | var boxWidth = labelOpts.boxWidth, |
| 6460 | hitboxes = me.legendHitBoxes; |
| 6461 | |
| 6462 | // current position |
| 6463 | var drawLegendBox = function(x, y, legendItem) { |
| 6464 | if (isNaN(boxWidth) || boxWidth <= 0) { |
| 6465 | return; |
| 6466 | } |
| 6467 | |
| 6468 | // Set the ctx for the box |
| 6469 | ctx.save(); |
| 6470 | |
| 6471 | ctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor); |
| 6472 | ctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); |
| 6473 | ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); |
| 6474 | ctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); |
| 6475 | ctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth); |
| 6476 | ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); |
| 6477 | |
| 6478 | if (ctx.setLineDash) { |
| 6479 | // IE 9 and 10 do not support line dash |
| 6480 | ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash)); |
| 6481 | } |
| 6482 | |
| 6483 | if (opts.labels && opts.labels.usePointStyle) { |
| 6484 | // Recalulate x and y for drawPoint() because its expecting |
| 6485 | // x and y to be center of figure (instead of top left) |
| 6486 | var radius = fontSize * Math.SQRT2 / 2; |
| 6487 | var offSet = radius / Math.SQRT2; |
| 6488 | var centerX = x + offSet; |
| 6489 | var centerY = y + offSet; |
| 6490 | |
| 6491 | // Draw pointStyle as legend symbol |
| 6492 | Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); |
| 6493 | } |
| 6494 | else { |
| 6495 | // Draw box as legend symbol |
| 6496 | ctx.strokeRect(x, y, boxWidth, fontSize); |
| 6497 | ctx.fillRect(x, y, boxWidth, fontSize); |
| 6498 | } |
| 6499 | |
| 6500 | ctx.restore(); |
| 6501 | }; |
| 6502 | var fillText = function(x, y, legendItem, textWidth) { |
| 6503 | ctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y); |
| 6504 | |
| 6505 | if (legendItem.hidden) { |
| 6506 | // Strikethrough the text if hidden |
| 6507 | ctx.beginPath(); |
| 6508 | ctx.lineWidth = 2; |
| 6509 | ctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2)); |
| 6510 | ctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2)); |
| 6511 | ctx.stroke(); |
| 6512 | } |
| 6513 | }; |
| 6514 | |
| 6515 | // Horizontal |
| 6516 | var isHorizontal = me.isHorizontal(); |
| 6517 | if (isHorizontal) { |
| 6518 | cursor = { |
| 6519 | x: me.left + ((legendWidth - lineWidths[0]) / 2), |
| 6520 | y: me.top + labelOpts.padding, |
| 6521 | line: 0 |
| 6522 | }; |
| 6523 | } else { |
| 6524 | cursor = { |
| 6525 | x: me.left + labelOpts.padding, |
| 6526 | y: me.top + labelOpts.padding, |
| 6527 | line: 0 |
| 6528 | }; |
| 6529 | } |
| 6530 | |
| 6531 | var itemHeight = fontSize + labelOpts.padding; |
| 6532 | helpers.each(me.legendItems, function(legendItem, i) { |
| 6533 | var textWidth = ctx.measureText(legendItem.text).width, |
| 6534 | width = labelOpts.usePointStyle ? |
| 6535 | fontSize + (fontSize / 2) + textWidth : |
| 6536 | boxWidth + (fontSize / 2) + textWidth, |
| 6537 | x = cursor.x, |
| 6538 | y = cursor.y; |
| 6539 | |
| 6540 | if (isHorizontal) { |
| 6541 | if (x + width >= legendWidth) { |
| 6542 | y = cursor.y += itemHeight; |
| 6543 | cursor.line++; |
| 6544 | x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); |
| 6545 | } |
| 6546 | } else { |
| 6547 | if (y + itemHeight > me.bottom) { |
| 6548 | x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; |
| 6549 | y = cursor.y = me.top; |
| 6550 | cursor.line++; |
| 6551 | } |
| 6552 | } |
| 6553 | |
| 6554 | drawLegendBox(x, y, legendItem); |
| 6555 | |
| 6556 | hitboxes[i].left = x; |
| 6557 | hitboxes[i].top = y; |
| 6558 | |
| 6559 | // Fill the actual label |
| 6560 | fillText(x, y, legendItem, textWidth); |
| 6561 | |
| 6562 | if (isHorizontal) { |
| 6563 | cursor.x += width + (labelOpts.padding); |
| 6564 | } else { |
| 6565 | cursor.y += itemHeight; |
| 6566 | } |
| 6567 | |
| 6568 | }); |
| 6569 | } |
| 6570 | }, |
| 6571 | |
| 6572 | // Handle an event |
| 6573 | handleEvent: function(e) { |
| 6574 | var me = this; |
| 6575 | var position = helpers.getRelativePosition(e, me.chart.chart), |
| 6576 | x = position.x, |
| 6577 | y = position.y, |
| 6578 | opts = me.options; |
| 6579 | |
| 6580 | if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { |
| 6581 | // See if we are touching one of the dataset boxes |
| 6582 | var lh = me.legendHitBoxes; |
| 6583 | for (var i = 0; i < lh.length; ++i) { |
| 6584 | var hitBox = lh[i]; |
| 6585 | |
| 6586 | if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { |
| 6587 | // Touching an element |
| 6588 | if (opts.onClick) { |
| 6589 | opts.onClick.call(me, e, me.legendItems[i]); |
| 6590 | } |
| 6591 | break; |
| 6592 | } |
| 6593 | } |
| 6594 | } |
| 6595 | } |
| 6596 | }); |
| 6597 | |
| 6598 | // Register the legend plugin |
| 6599 | Chart.plugins.register({ |
| 6600 | beforeInit: function(chartInstance) { |
| 6601 | var opts = chartInstance.options; |
| 6602 | var legendOpts = opts.legend; |
| 6603 | |
| 6604 | if (legendOpts) { |
| 6605 | chartInstance.legend = new Chart.Legend({ |
| 6606 | ctx: chartInstance.chart.ctx, |
| 6607 | options: legendOpts, |
| 6608 | chart: chartInstance |
| 6609 | }); |
| 6610 | |
| 6611 | Chart.layoutService.addBox(chartInstance, chartInstance.legend); |
| 6612 | } |
| 6613 | } |
| 6614 | }); |
| 6615 | }; |
| 6616 | |
| 6617 | },{}],30:[function(require,module,exports){ |
| 6618 | "use strict"; |
| 6619 | |
| 6620 | module.exports = function(Chart) { |
| 6621 | |
| 6622 | var noop = Chart.helpers.noop; |
| 6623 | |
| 6624 | /** |
| 6625 | * The plugin service singleton |
| 6626 | * @namespace Chart.plugins |
| 6627 | * @since 2.1.0 |
| 6628 | */ |
| 6629 | Chart.plugins = { |
| 6630 | _plugins: [], |
| 6631 | |
| 6632 | /** |
| 6633 | * Registers the given plugin(s) if not already registered. |
| 6634 | * @param {Array|Object} plugins plugin instance(s). |
| 6635 | */ |
| 6636 | register: function(plugins) { |
| 6637 | var p = this._plugins; |
| 6638 | ([]).concat(plugins).forEach(function(plugin) { |
| 6639 | if (p.indexOf(plugin) === -1) { |
| 6640 | p.push(plugin); |
| 6641 | } |
| 6642 | }); |
| 6643 | }, |
| 6644 | |
| 6645 | /** |
| 6646 | * Unregisters the given plugin(s) only if registered. |
| 6647 | * @param {Array|Object} plugins plugin instance(s). |
| 6648 | */ |
| 6649 | unregister: function(plugins) { |
| 6650 | var p = this._plugins; |
| 6651 | ([]).concat(plugins).forEach(function(plugin) { |
| 6652 | var idx = p.indexOf(plugin); |
| 6653 | if (idx !== -1) { |
| 6654 | p.splice(idx, 1); |
| 6655 | } |
| 6656 | }); |
| 6657 | }, |
| 6658 | |
| 6659 | /** |
| 6660 | * Remove all registered p^lugins. |
| 6661 | * @since 2.1.5 |
| 6662 | */ |
| 6663 | clear: function() { |
| 6664 | this._plugins = []; |
| 6665 | }, |
| 6666 | |
| 6667 | /** |
| 6668 | * Returns the number of registered plugins? |
| 6669 | * @returns {Number} |
| 6670 | * @since 2.1.5 |
| 6671 | */ |
| 6672 | count: function() { |
| 6673 | return this._plugins.length; |
| 6674 | }, |
| 6675 | |
| 6676 | /** |
| 6677 | * Returns all registered plugin intances. |
| 6678 | * @returns {Array} array of plugin objects. |
| 6679 | * @since 2.1.5 |
| 6680 | */ |
| 6681 | getAll: function() { |
| 6682 | return this._plugins; |
| 6683 | }, |
| 6684 | |
| 6685 | /** |
| 6686 | * Calls registered plugins on the specified extension, with the given args. This |
| 6687 | * method immediately returns as soon as a plugin explicitly returns false. The |
| 6688 | * returned value can be used, for instance, to interrupt the current action. |
| 6689 | * @param {String} extension the name of the plugin method to call (e.g. 'beforeUpdate'). |
| 6690 | * @param {Array} [args] extra arguments to apply to the extension call. |
| 6691 | * @returns {Boolean} false if any of the plugins return false, else returns true. |
| 6692 | */ |
| 6693 | notify: function(extension, args) { |
| 6694 | var plugins = this._plugins; |
| 6695 | var ilen = plugins.length; |
| 6696 | var i, plugin; |
| 6697 | |
| 6698 | for (i=0; i<ilen; ++i) { |
| 6699 | plugin = plugins[i]; |
| 6700 | if (typeof plugin[extension] === 'function') { |
| 6701 | if (plugin[extension].apply(plugin, args || []) === false) { |
| 6702 | return false; |
| 6703 | } |
| 6704 | } |
| 6705 | } |
| 6706 | |
| 6707 | return true; |
| 6708 | } |
| 6709 | }; |
| 6710 | |
| 6711 | /** |
| 6712 | * Plugin extension methods. |
| 6713 | * @interface Chart.PluginBase |
| 6714 | * @since 2.1.0 |
| 6715 | */ |
| 6716 | Chart.PluginBase = Chart.Element.extend({ |
| 6717 | // Called at start of chart init |
| 6718 | beforeInit: noop, |
| 6719 | |
| 6720 | // Called at end of chart init |
| 6721 | afterInit: noop, |
| 6722 | |
| 6723 | // Called at start of update |
| 6724 | beforeUpdate: noop, |
| 6725 | |
| 6726 | // Called at end of update |
| 6727 | afterUpdate: noop, |
| 6728 | |
| 6729 | // Called at start of draw |
| 6730 | beforeDraw: noop, |
| 6731 | |
| 6732 | // Called at end of draw |
| 6733 | afterDraw: noop, |
| 6734 | |
| 6735 | // Called during destroy |
| 6736 | destroy: noop |
| 6737 | }); |
| 6738 | |
| 6739 | /** |
| 6740 | * Provided for backward compatibility, use Chart.plugins instead |
| 6741 | * @namespace Chart.pluginService |
| 6742 | * @deprecated since version 2.1.5 |
| 6743 | * @todo remove me at version 3 |
| 6744 | */ |
| 6745 | Chart.pluginService = Chart.plugins; |
| 6746 | }; |
| 6747 | |
| 6748 | },{}],31:[function(require,module,exports){ |
| 6749 | "use strict"; |
| 6750 | |
| 6751 | module.exports = function(Chart) { |
| 6752 | |
| 6753 | var helpers = Chart.helpers; |
| 6754 | |
| 6755 | Chart.defaults.scale = { |
| 6756 | display: true, |
| 6757 | position: "left", |
| 6758 | |
| 6759 | // grid line settings |
| 6760 | gridLines: { |
| 6761 | display: true, |
| 6762 | color: "rgba(0, 0, 0, 0.1)", |
| 6763 | lineWidth: 1, |
| 6764 | drawBorder: true, |
| 6765 | drawOnChartArea: true, |
| 6766 | drawTicks: true, |
| 6767 | tickMarkLength: 10, |
| 6768 | zeroLineWidth: 1, |
| 6769 | zeroLineColor: "rgba(0,0,0,0.25)", |
| 6770 | offsetGridLines: false |
| 6771 | }, |
| 6772 | |
| 6773 | // scale label |
| 6774 | scaleLabel: { |
| 6775 | // actual label |
| 6776 | labelString: '', |
| 6777 | |
| 6778 | // display property |
| 6779 | display: false |
| 6780 | }, |
| 6781 | |
| 6782 | // label settings |
| 6783 | ticks: { |
| 6784 | beginAtZero: false, |
| 6785 | minRotation: 0, |
| 6786 | maxRotation: 50, |
| 6787 | mirror: false, |
| 6788 | padding: 10, |
| 6789 | reverse: false, |
| 6790 | display: true, |
| 6791 | autoSkip: true, |
| 6792 | autoSkipPadding: 0, |
| 6793 | labelOffset: 0, |
| 6794 | // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. |
| 6795 | callback: function(value) { |
| 6796 | return helpers.isArray(value) ? value : '' + value; |
| 6797 | } |
| 6798 | } |
| 6799 | }; |
| 6800 | |
| 6801 | Chart.Scale = Chart.Element.extend({ |
| 6802 | |
| 6803 | // These methods are ordered by lifecyle. Utilities then follow. |
| 6804 | // Any function defined here is inherited by all scale types. |
| 6805 | // Any function can be extended by the scale type |
| 6806 | |
| 6807 | beforeUpdate: function() { |
| 6808 | helpers.callCallback(this.options.beforeUpdate, [this]); |
| 6809 | }, |
| 6810 | update: function(maxWidth, maxHeight, margins) { |
| 6811 | var me = this; |
| 6812 | |
| 6813 | // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) |
| 6814 | me.beforeUpdate(); |
| 6815 | |
| 6816 | // Absorb the master measurements |
| 6817 | me.maxWidth = maxWidth; |
| 6818 | me.maxHeight = maxHeight; |
| 6819 | me.margins = helpers.extend({ |
| 6820 | left: 0, |
| 6821 | right: 0, |
| 6822 | top: 0, |
| 6823 | bottom: 0 |
| 6824 | }, margins); |
| 6825 | |
| 6826 | // Dimensions |
| 6827 | me.beforeSetDimensions(); |
| 6828 | me.setDimensions(); |
| 6829 | me.afterSetDimensions(); |
| 6830 | |
| 6831 | // Data min/max |
| 6832 | me.beforeDataLimits(); |
| 6833 | me.determineDataLimits(); |
| 6834 | me.afterDataLimits(); |
| 6835 | |
| 6836 | // Ticks |
| 6837 | me.beforeBuildTicks(); |
| 6838 | me.buildTicks(); |
| 6839 | me.afterBuildTicks(); |
| 6840 | |
| 6841 | me.beforeTickToLabelConversion(); |
| 6842 | me.convertTicksToLabels(); |
| 6843 | me.afterTickToLabelConversion(); |
| 6844 | |
| 6845 | // Tick Rotation |
| 6846 | me.beforeCalculateTickRotation(); |
| 6847 | me.calculateTickRotation(); |
| 6848 | me.afterCalculateTickRotation(); |
| 6849 | // Fit |
| 6850 | me.beforeFit(); |
| 6851 | me.fit(); |
| 6852 | me.afterFit(); |
| 6853 | // |
| 6854 | me.afterUpdate(); |
| 6855 | |
| 6856 | return me.minSize; |
| 6857 | |
| 6858 | }, |
| 6859 | afterUpdate: function() { |
| 6860 | helpers.callCallback(this.options.afterUpdate, [this]); |
| 6861 | }, |
| 6862 | |
| 6863 | // |
| 6864 | |
| 6865 | beforeSetDimensions: function() { |
| 6866 | helpers.callCallback(this.options.beforeSetDimensions, [this]); |
| 6867 | }, |
| 6868 | setDimensions: function() { |
| 6869 | var me = this; |
| 6870 | // Set the unconstrained dimension before label rotation |
| 6871 | if (me.isHorizontal()) { |
| 6872 | // Reset position before calculating rotation |
| 6873 | me.width = me.maxWidth; |
| 6874 | me.left = 0; |
| 6875 | me.right = me.width; |
| 6876 | } else { |
| 6877 | me.height = me.maxHeight; |
| 6878 | |
| 6879 | // Reset position before calculating rotation |
| 6880 | me.top = 0; |
| 6881 | me.bottom = me.height; |
| 6882 | } |
| 6883 | |
| 6884 | // Reset padding |
| 6885 | me.paddingLeft = 0; |
| 6886 | me.paddingTop = 0; |
| 6887 | me.paddingRight = 0; |
| 6888 | me.paddingBottom = 0; |
| 6889 | }, |
| 6890 | afterSetDimensions: function() { |
| 6891 | helpers.callCallback(this.options.afterSetDimensions, [this]); |
| 6892 | }, |
| 6893 | |
| 6894 | // Data limits |
| 6895 | beforeDataLimits: function() { |
| 6896 | helpers.callCallback(this.options.beforeDataLimits, [this]); |
| 6897 | }, |
| 6898 | determineDataLimits: helpers.noop, |
| 6899 | afterDataLimits: function() { |
| 6900 | helpers.callCallback(this.options.afterDataLimits, [this]); |
| 6901 | }, |
| 6902 | |
| 6903 | // |
| 6904 | beforeBuildTicks: function() { |
| 6905 | helpers.callCallback(this.options.beforeBuildTicks, [this]); |
| 6906 | }, |
| 6907 | buildTicks: helpers.noop, |
| 6908 | afterBuildTicks: function() { |
| 6909 | helpers.callCallback(this.options.afterBuildTicks, [this]); |
| 6910 | }, |
| 6911 | |
| 6912 | beforeTickToLabelConversion: function() { |
| 6913 | helpers.callCallback(this.options.beforeTickToLabelConversion, [this]); |
| 6914 | }, |
| 6915 | convertTicksToLabels: function() { |
| 6916 | var me = this; |
| 6917 | // Convert ticks to strings |
| 6918 | me.ticks = me.ticks.map(function(numericalTick, index, ticks) { |
| 6919 | if (me.options.ticks.userCallback) { |
| 6920 | return me.options.ticks.userCallback(numericalTick, index, ticks); |
| 6921 | } |
| 6922 | return me.options.ticks.callback(numericalTick, index, ticks); |
| 6923 | }, |
| 6924 | me); |
| 6925 | }, |
| 6926 | afterTickToLabelConversion: function() { |
| 6927 | helpers.callCallback(this.options.afterTickToLabelConversion, [this]); |
| 6928 | }, |
| 6929 | |
| 6930 | // |
| 6931 | |
| 6932 | beforeCalculateTickRotation: function() { |
| 6933 | helpers.callCallback(this.options.beforeCalculateTickRotation, [this]); |
| 6934 | }, |
| 6935 | calculateTickRotation: function() { |
| 6936 | var me = this; |
| 6937 | var context = me.ctx; |
| 6938 | var globalDefaults = Chart.defaults.global; |
| 6939 | var optionTicks = me.options.ticks; |
| 6940 | |
| 6941 | //Get the width of each grid by calculating the difference |
| 6942 | //between x offsets between 0 and 1. |
| 6943 | var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize); |
| 6944 | var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle); |
| 6945 | var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily); |
| 6946 | var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); |
| 6947 | context.font = tickLabelFont; |
| 6948 | |
| 6949 | var firstWidth = context.measureText(me.ticks[0]).width; |
| 6950 | var lastWidth = context.measureText(me.ticks[me.ticks.length - 1]).width; |
| 6951 | var firstRotated; |
| 6952 | |
| 6953 | me.labelRotation = optionTicks.minRotation || 0; |
| 6954 | me.paddingRight = 0; |
| 6955 | me.paddingLeft = 0; |
| 6956 | |
| 6957 | if (me.options.display) { |
| 6958 | if (me.isHorizontal()) { |
| 6959 | me.paddingRight = lastWidth / 2 + 3; |
| 6960 | me.paddingLeft = firstWidth / 2 + 3; |
| 6961 | |
| 6962 | if (!me.longestTextCache) { |
| 6963 | me.longestTextCache = {}; |
| 6964 | } |
| 6965 | var originalLabelWidth = helpers.longestText(context, tickLabelFont, me.ticks, me.longestTextCache); |
| 6966 | var labelWidth = originalLabelWidth; |
| 6967 | var cosRotation; |
| 6968 | var sinRotation; |
| 6969 | |
| 6970 | // Allow 3 pixels x2 padding either side for label readability |
| 6971 | // only the index matters for a dataset scale, but we want a consistent interface between scales |
| 6972 | var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; |
| 6973 | |
| 6974 | //Max label rotation can be set or default to 90 - also act as a loop counter |
| 6975 | while (labelWidth > tickWidth && me.labelRotation < optionTicks.maxRotation) { |
| 6976 | cosRotation = Math.cos(helpers.toRadians(me.labelRotation)); |
| 6977 | sinRotation = Math.sin(helpers.toRadians(me.labelRotation)); |
| 6978 | |
| 6979 | firstRotated = cosRotation * firstWidth; |
| 6980 | |
| 6981 | // We're right aligning the text now. |
| 6982 | if (firstRotated + tickFontSize / 2 > me.yLabelWidth) { |
| 6983 | me.paddingLeft = firstRotated + tickFontSize / 2; |
| 6984 | } |
| 6985 | |
| 6986 | me.paddingRight = tickFontSize / 2; |
| 6987 | |
| 6988 | if (sinRotation * originalLabelWidth > me.maxHeight) { |
| 6989 | // go back one step |
| 6990 | me.labelRotation--; |
| 6991 | break; |
| 6992 | } |
| 6993 | |
| 6994 | me.labelRotation++; |
| 6995 | labelWidth = cosRotation * originalLabelWidth; |
| 6996 | } |
| 6997 | } |
| 6998 | } |
| 6999 | |
| 7000 | if (me.margins) { |
| 7001 | me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); |
| 7002 | me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); |
| 7003 | } |
| 7004 | }, |
| 7005 | afterCalculateTickRotation: function() { |
| 7006 | helpers.callCallback(this.options.afterCalculateTickRotation, [this]); |
| 7007 | }, |
| 7008 | |
| 7009 | // |
| 7010 | |
| 7011 | beforeFit: function() { |
| 7012 | helpers.callCallback(this.options.beforeFit, [this]); |
| 7013 | }, |
| 7014 | fit: function() { |
| 7015 | var me = this; |
| 7016 | // Reset |
| 7017 | var minSize = me.minSize = { |
| 7018 | width: 0, |
| 7019 | height: 0 |
| 7020 | }; |
| 7021 | |
| 7022 | var opts = me.options; |
| 7023 | var globalDefaults = Chart.defaults.global; |
| 7024 | var tickOpts = opts.ticks; |
| 7025 | var scaleLabelOpts = opts.scaleLabel; |
| 7026 | var display = opts.display; |
| 7027 | var isHorizontal = me.isHorizontal(); |
| 7028 | |
| 7029 | var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); |
| 7030 | var tickFontStyle = helpers.getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); |
| 7031 | var tickFontFamily = helpers.getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); |
| 7032 | var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); |
| 7033 | |
| 7034 | var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize); |
| 7035 | |
| 7036 | var tickMarkLength = opts.gridLines.tickMarkLength; |
| 7037 | |
| 7038 | // Width |
| 7039 | if (isHorizontal) { |
| 7040 | // subtract the margins to line up with the chartArea if we are a full width scale |
| 7041 | minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; |
| 7042 | } else { |
| 7043 | minSize.width = display ? tickMarkLength : 0; |
| 7044 | } |
| 7045 | |
| 7046 | // height |
| 7047 | if (isHorizontal) { |
| 7048 | minSize.height = display ? tickMarkLength : 0; |
| 7049 | } else { |
| 7050 | minSize.height = me.maxHeight; // fill all the height |
| 7051 | } |
| 7052 | |
| 7053 | // Are we showing a title for the scale? |
| 7054 | if (scaleLabelOpts.display && display) { |
| 7055 | if (isHorizontal) { |
| 7056 | minSize.height += (scaleLabelFontSize * 1.5); |
| 7057 | } else { |
| 7058 | minSize.width += (scaleLabelFontSize * 1.5); |
| 7059 | } |
| 7060 | } |
| 7061 | |
| 7062 | if (tickOpts.display && display) { |
| 7063 | // Don't bother fitting the ticks if we are not showing them |
| 7064 | if (!me.longestTextCache) { |
| 7065 | me.longestTextCache = {}; |
| 7066 | } |
| 7067 | |
| 7068 | var largestTextWidth = helpers.longestText(me.ctx, tickLabelFont, me.ticks, me.longestTextCache); |
| 7069 | var tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks); |
| 7070 | var lineSpace = tickFontSize * 0.5; |
| 7071 | |
| 7072 | if (isHorizontal) { |
| 7073 | // A horizontal axis is more constrained by the height. |
| 7074 | me.longestLabelWidth = largestTextWidth; |
| 7075 | |
| 7076 | // TODO - improve this calculation |
| 7077 | var labelHeight = (Math.sin(helpers.toRadians(me.labelRotation)) * me.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines); |
| 7078 | |
| 7079 | minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight); |
| 7080 | me.ctx.font = tickLabelFont; |
| 7081 | |
| 7082 | var firstLabelWidth = me.ctx.measureText(me.ticks[0]).width; |
| 7083 | var lastLabelWidth = me.ctx.measureText(me.ticks[me.ticks.length - 1]).width; |
| 7084 | |
| 7085 | // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated |
| 7086 | // by the font height |
| 7087 | var cosRotation = Math.cos(helpers.toRadians(me.labelRotation)); |
| 7088 | var sinRotation = Math.sin(helpers.toRadians(me.labelRotation)); |
| 7089 | me.paddingLeft = me.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges |
| 7090 | me.paddingRight = me.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated |
| 7091 | } else { |
| 7092 | // A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first |
| 7093 | var maxLabelWidth = me.maxWidth - minSize.width; |
| 7094 | |
| 7095 | // Account for padding |
| 7096 | var mirror = tickOpts.mirror; |
| 7097 | if (!mirror) { |
| 7098 | largestTextWidth += me.options.ticks.padding; |
| 7099 | } else { |
| 7100 | // If mirrored text is on the inside so don't expand |
| 7101 | largestTextWidth = 0; |
| 7102 | } |
| 7103 | |
| 7104 | if (largestTextWidth < maxLabelWidth) { |
| 7105 | // We don't need all the room |
| 7106 | minSize.width += largestTextWidth; |
| 7107 | } else { |
| 7108 | // Expand to max size |
| 7109 | minSize.width = me.maxWidth; |
| 7110 | } |
| 7111 | |
| 7112 | me.paddingTop = tickFontSize / 2; |
| 7113 | me.paddingBottom = tickFontSize / 2; |
| 7114 | } |
| 7115 | } |
| 7116 | |
| 7117 | if (me.margins) { |
| 7118 | me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); |
| 7119 | me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); |
| 7120 | me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); |
| 7121 | me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); |
| 7122 | } |
| 7123 | |
| 7124 | me.width = minSize.width; |
| 7125 | me.height = minSize.height; |
| 7126 | |
| 7127 | }, |
| 7128 | afterFit: function() { |
| 7129 | helpers.callCallback(this.options.afterFit, [this]); |
| 7130 | }, |
| 7131 | |
| 7132 | // Shared Methods |
| 7133 | isHorizontal: function() { |
| 7134 | return this.options.position === "top" || this.options.position === "bottom"; |
| 7135 | }, |
| 7136 | isFullWidth: function() { |
| 7137 | return (this.options.fullWidth); |
| 7138 | }, |
| 7139 | |
| 7140 | // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not |
| 7141 | getRightValue: function(rawValue) { |
| 7142 | // Null and undefined values first |
| 7143 | if (rawValue === null || typeof(rawValue) === 'undefined') { |
| 7144 | return NaN; |
| 7145 | } |
| 7146 | // isNaN(object) returns true, so make sure NaN is checking for a number |
| 7147 | if (typeof(rawValue) === 'number' && isNaN(rawValue)) { |
| 7148 | return NaN; |
| 7149 | } |
| 7150 | // If it is in fact an object, dive in one more level |
| 7151 | if (typeof(rawValue) === "object") { |
| 7152 | if ((rawValue instanceof Date) || (rawValue.isValid)) { |
| 7153 | return rawValue; |
| 7154 | } else { |
| 7155 | return this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y); |
| 7156 | } |
| 7157 | } |
| 7158 | |
| 7159 | // Value is good, return it |
| 7160 | return rawValue; |
| 7161 | }, |
| 7162 | |
| 7163 | // Used to get the value to display in the tooltip for the data at the given index |
| 7164 | // function getLabelForIndex(index, datasetIndex) |
| 7165 | getLabelForIndex: helpers.noop, |
| 7166 | |
| 7167 | // Used to get data value locations. Value can either be an index or a numerical value |
| 7168 | getPixelForValue: helpers.noop, |
| 7169 | |
| 7170 | // Used to get the data value from a given pixel. This is the inverse of getPixelForValue |
| 7171 | getValueForPixel: helpers.noop, |
| 7172 | |
| 7173 | // Used for tick location, should |
| 7174 | getPixelForTick: function(index, includeOffset) { |
| 7175 | var me = this; |
| 7176 | if (me.isHorizontal()) { |
| 7177 | var innerWidth = me.width - (me.paddingLeft + me.paddingRight); |
| 7178 | var tickWidth = innerWidth / Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); |
| 7179 | var pixel = (tickWidth * index) + me.paddingLeft; |
| 7180 | |
| 7181 | if (includeOffset) { |
| 7182 | pixel += tickWidth / 2; |
| 7183 | } |
| 7184 | |
| 7185 | var finalVal = me.left + Math.round(pixel); |
| 7186 | finalVal += me.isFullWidth() ? me.margins.left : 0; |
| 7187 | return finalVal; |
| 7188 | } else { |
| 7189 | var innerHeight = me.height - (me.paddingTop + me.paddingBottom); |
| 7190 | return me.top + (index * (innerHeight / (me.ticks.length - 1))); |
| 7191 | } |
| 7192 | }, |
| 7193 | |
| 7194 | // Utility for getting the pixel location of a percentage of scale |
| 7195 | getPixelForDecimal: function(decimal /*, includeOffset*/ ) { |
| 7196 | var me = this; |
| 7197 | if (me.isHorizontal()) { |
| 7198 | var innerWidth = me.width - (me.paddingLeft + me.paddingRight); |
| 7199 | var valueOffset = (innerWidth * decimal) + me.paddingLeft; |
| 7200 | |
| 7201 | var finalVal = me.left + Math.round(valueOffset); |
| 7202 | finalVal += me.isFullWidth() ? me.margins.left : 0; |
| 7203 | return finalVal; |
| 7204 | } else { |
| 7205 | return me.top + (decimal * me.height); |
| 7206 | } |
| 7207 | }, |
| 7208 | |
| 7209 | getBasePixel: function() { |
| 7210 | var me = this; |
| 7211 | var min = me.min; |
| 7212 | var max = me.max; |
| 7213 | |
| 7214 | return me.getPixelForValue( |
| 7215 | me.beginAtZero? 0: |
| 7216 | min < 0 && max < 0? max : |
| 7217 | min > 0 && max > 0? min : |
| 7218 | 0); |
| 7219 | }, |
| 7220 | |
| 7221 | // Actualy draw the scale on the canvas |
| 7222 | // @param {rectangle} chartArea : the area of the chart to draw full grid lines on |
| 7223 | draw: function(chartArea) { |
| 7224 | var me = this; |
| 7225 | var options = me.options; |
| 7226 | if (!options.display) { |
| 7227 | return; |
| 7228 | } |
| 7229 | |
| 7230 | var context = me.ctx; |
| 7231 | var globalDefaults = Chart.defaults.global; |
| 7232 | var optionTicks = options.ticks; |
| 7233 | var gridLines = options.gridLines; |
| 7234 | var scaleLabel = options.scaleLabel; |
| 7235 | |
| 7236 | var isRotated = me.labelRotation !== 0; |
| 7237 | var skipRatio; |
| 7238 | var useAutoskipper = optionTicks.autoSkip; |
| 7239 | var isHorizontal = me.isHorizontal(); |
| 7240 | |
| 7241 | // figure out the maximum number of gridlines to show |
| 7242 | var maxTicks; |
| 7243 | if (optionTicks.maxTicksLimit) { |
| 7244 | maxTicks = optionTicks.maxTicksLimit; |
| 7245 | } |
| 7246 | |
| 7247 | var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); |
| 7248 | var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize); |
| 7249 | var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle); |
| 7250 | var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily); |
| 7251 | var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); |
| 7252 | var tl = gridLines.tickMarkLength; |
| 7253 | |
| 7254 | var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); |
| 7255 | var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabel.fontSize, globalDefaults.defaultFontSize); |
| 7256 | var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabel.fontStyle, globalDefaults.defaultFontStyle); |
| 7257 | var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabel.fontFamily, globalDefaults.defaultFontFamily); |
| 7258 | var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily); |
| 7259 | |
| 7260 | var labelRotationRadians = helpers.toRadians(me.labelRotation); |
| 7261 | var cosRotation = Math.cos(labelRotationRadians); |
| 7262 | var longestRotatedLabel = me.longestLabelWidth * cosRotation; |
| 7263 | |
| 7264 | // Make sure we draw text in the correct color and font |
| 7265 | context.fillStyle = tickFontColor; |
| 7266 | |
| 7267 | var itemsToDraw = []; |
| 7268 | |
| 7269 | if (isHorizontal) { |
| 7270 | skipRatio = false; |
| 7271 | |
| 7272 | // Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation |
| 7273 | // See #2584 |
| 7274 | if (isRotated) { |
| 7275 | longestRotatedLabel /= 2; |
| 7276 | } |
| 7277 | |
| 7278 | if ((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length > (me.width - (me.paddingLeft + me.paddingRight))) { |
| 7279 | skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length) / (me.width - (me.paddingLeft + me.paddingRight))); |
| 7280 | } |
| 7281 | |
| 7282 | // if they defined a max number of optionTicks, |
| 7283 | // increase skipRatio until that number is met |
| 7284 | if (maxTicks && me.ticks.length > maxTicks) { |
| 7285 | while (!skipRatio || me.ticks.length / (skipRatio || 1) > maxTicks) { |
| 7286 | if (!skipRatio) { |
| 7287 | skipRatio = 1; |
| 7288 | } |
| 7289 | skipRatio += 1; |
| 7290 | } |
| 7291 | } |
| 7292 | |
| 7293 | if (!useAutoskipper) { |
| 7294 | skipRatio = false; |
| 7295 | } |
| 7296 | } |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 7297 | |
| 7298 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 7299 | var xTickStart = options.position === "right" ? me.left : me.right - tl; |
| 7300 | var xTickEnd = options.position === "right" ? me.left + tl : me.right; |
| 7301 | var yTickStart = options.position === "bottom" ? me.top : me.bottom - tl; |
| 7302 | var yTickEnd = options.position === "bottom" ? me.top + tl : me.bottom; |
| 7303 | |
| 7304 | helpers.each(me.ticks, function(label, index) { |
| 7305 | // If the callback returned a null or undefined value, do not draw this line |
| 7306 | if (label === undefined || label === null) { |
| 7307 | return; |
| 7308 | } |
| 7309 | |
| 7310 | var isLastTick = me.ticks.length === index + 1; |
| 7311 | |
| 7312 | // Since we always show the last tick,we need may need to hide the last shown one before |
| 7313 | var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= me.ticks.length); |
| 7314 | if (shouldSkip && !isLastTick || (label === undefined || label === null)) { |
| 7315 | return; |
| 7316 | } |
| 7317 | |
| 7318 | var lineWidth, lineColor; |
| 7319 | if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) { |
| 7320 | // Draw the first index specially |
| 7321 | lineWidth = gridLines.zeroLineWidth; |
| 7322 | lineColor = gridLines.zeroLineColor; |
| 7323 | } else { |
| 7324 | lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, index); |
| 7325 | lineColor = helpers.getValueAtIndexOrDefault(gridLines.color, index); |
| 7326 | } |
| 7327 | |
| 7328 | // Common properties |
| 7329 | var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; |
| 7330 | var textAlign, textBaseline = 'middle'; |
| 7331 | |
| 7332 | if (isHorizontal) { |
| 7333 | if (!isRotated) { |
| 7334 | textBaseline = options.position === 'top' ? 'bottom' : 'top'; |
| 7335 | } |
| 7336 | |
| 7337 | textAlign = isRotated ? 'right' : 'center'; |
| 7338 | |
| 7339 | var xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines |
| 7340 | labelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) |
| 7341 | labelY = (isRotated) ? me.top + 12 : options.position === 'top' ? me.bottom - tl : me.top + tl; |
| 7342 | |
| 7343 | tx1 = tx2 = x1 = x2 = xLineValue; |
| 7344 | ty1 = yTickStart; |
| 7345 | ty2 = yTickEnd; |
| 7346 | y1 = chartArea.top; |
| 7347 | y2 = chartArea.bottom; |
| 7348 | } else { |
| 7349 | if (options.position === 'left') { |
| 7350 | if (optionTicks.mirror) { |
| 7351 | labelX = me.right + optionTicks.padding; |
| 7352 | textAlign = 'left'; |
| 7353 | } else { |
| 7354 | labelX = me.right - optionTicks.padding; |
| 7355 | textAlign = 'right'; |
| 7356 | } |
| 7357 | } else { |
| 7358 | // right side |
| 7359 | if (optionTicks.mirror) { |
| 7360 | labelX = me.left - optionTicks.padding; |
| 7361 | textAlign = 'right'; |
| 7362 | } else { |
| 7363 | labelX = me.left + optionTicks.padding; |
| 7364 | textAlign = 'left'; |
| 7365 | } |
| 7366 | } |
| 7367 | |
| 7368 | var yLineValue = me.getPixelForTick(index); // xvalues for grid lines |
| 7369 | yLineValue += helpers.aliasPixel(lineWidth); |
| 7370 | labelY = me.getPixelForTick(index, gridLines.offsetGridLines); |
| 7371 | |
| 7372 | tx1 = xTickStart; |
| 7373 | tx2 = xTickEnd; |
| 7374 | x1 = chartArea.left; |
| 7375 | x2 = chartArea.right; |
| 7376 | ty1 = ty2 = y1 = y2 = yLineValue; |
| 7377 | } |
| 7378 | |
| 7379 | itemsToDraw.push({ |
| 7380 | tx1: tx1, |
| 7381 | ty1: ty1, |
| 7382 | tx2: tx2, |
| 7383 | ty2: ty2, |
| 7384 | x1: x1, |
| 7385 | y1: y1, |
| 7386 | x2: x2, |
| 7387 | y2: y2, |
| 7388 | labelX: labelX, |
| 7389 | labelY: labelY, |
| 7390 | glWidth: lineWidth, |
| 7391 | glColor: lineColor, |
| 7392 | rotation: -1 * labelRotationRadians, |
| 7393 | label: label, |
| 7394 | textBaseline: textBaseline, |
| 7395 | textAlign: textAlign |
| 7396 | }); |
| 7397 | }); |
| 7398 | |
| 7399 | // Draw all of the tick labels, tick marks, and grid lines at the correct places |
| 7400 | helpers.each(itemsToDraw, function(itemToDraw) { |
| 7401 | if (gridLines.display) { |
| 7402 | context.lineWidth = itemToDraw.glWidth; |
| 7403 | context.strokeStyle = itemToDraw.glColor; |
| 7404 | |
| 7405 | context.beginPath(); |
| 7406 | |
| 7407 | if (gridLines.drawTicks) { |
| 7408 | context.moveTo(itemToDraw.tx1, itemToDraw.ty1); |
| 7409 | context.lineTo(itemToDraw.tx2, itemToDraw.ty2); |
| 7410 | } |
| 7411 | |
| 7412 | if (gridLines.drawOnChartArea) { |
| 7413 | context.moveTo(itemToDraw.x1, itemToDraw.y1); |
| 7414 | context.lineTo(itemToDraw.x2, itemToDraw.y2); |
| 7415 | } |
| 7416 | |
| 7417 | context.stroke(); |
| 7418 | } |
| 7419 | |
| 7420 | if (optionTicks.display) { |
| 7421 | context.save(); |
| 7422 | context.translate(itemToDraw.labelX, itemToDraw.labelY); |
| 7423 | context.rotate(itemToDraw.rotation); |
| 7424 | context.font = tickLabelFont; |
| 7425 | context.textBaseline = itemToDraw.textBaseline; |
| 7426 | context.textAlign = itemToDraw.textAlign; |
| 7427 | |
| 7428 | var label = itemToDraw.label; |
| 7429 | if (helpers.isArray(label)) { |
| 7430 | for (var i = 0, y = 0; i < label.length; ++i) { |
| 7431 | // We just make sure the multiline element is a string here.. |
| 7432 | context.fillText('' + label[i], 0, y); |
| 7433 | // apply same lineSpacing as calculated @ L#320 |
| 7434 | y += (tickFontSize * 1.5); |
| 7435 | } |
| 7436 | } else { |
| 7437 | context.fillText(label, 0, 0); |
| 7438 | } |
| 7439 | context.restore(); |
| 7440 | } |
| 7441 | }); |
| 7442 | |
| 7443 | if (scaleLabel.display) { |
| 7444 | // Draw the scale label |
| 7445 | var scaleLabelX; |
| 7446 | var scaleLabelY; |
| 7447 | var rotation = 0; |
| 7448 | |
| 7449 | if (isHorizontal) { |
| 7450 | scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width |
| 7451 | scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFontSize / 2) : me.top + (scaleLabelFontSize / 2); |
| 7452 | } else { |
| 7453 | var isLeft = options.position === 'left'; |
| 7454 | scaleLabelX = isLeft ? me.left + (scaleLabelFontSize / 2) : me.right - (scaleLabelFontSize / 2); |
| 7455 | scaleLabelY = me.top + ((me.bottom - me.top) / 2); |
| 7456 | rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; |
| 7457 | } |
| 7458 | |
| 7459 | context.save(); |
| 7460 | context.translate(scaleLabelX, scaleLabelY); |
| 7461 | context.rotate(rotation); |
| 7462 | context.textAlign = 'center'; |
| 7463 | context.textBaseline = 'middle'; |
| 7464 | context.fillStyle = scaleLabelFontColor; // render in correct colour |
| 7465 | context.font = scaleLabelFont; |
| 7466 | context.fillText(scaleLabel.labelString, 0, 0); |
| 7467 | context.restore(); |
| 7468 | } |
| 7469 | |
| 7470 | if (gridLines.drawBorder) { |
| 7471 | // Draw the line at the edge of the axis |
| 7472 | context.lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, 0); |
| 7473 | context.strokeStyle = helpers.getValueAtIndexOrDefault(gridLines.color, 0); |
| 7474 | var x1 = me.left, |
| 7475 | x2 = me.right, |
| 7476 | y1 = me.top, |
| 7477 | y2 = me.bottom; |
| 7478 | |
| 7479 | var aliasPixel = helpers.aliasPixel(context.lineWidth); |
| 7480 | if (isHorizontal) { |
| 7481 | y1 = y2 = options.position === 'top' ? me.bottom : me.top; |
| 7482 | y1 += aliasPixel; |
| 7483 | y2 += aliasPixel; |
| 7484 | } else { |
| 7485 | x1 = x2 = options.position === 'left' ? me.right : me.left; |
| 7486 | x1 += aliasPixel; |
| 7487 | x2 += aliasPixel; |
| 7488 | } |
| 7489 | |
| 7490 | context.beginPath(); |
| 7491 | context.moveTo(x1, y1); |
| 7492 | context.lineTo(x2, y2); |
| 7493 | context.stroke(); |
| 7494 | } |
| 7495 | } |
| 7496 | }); |
| 7497 | }; |
| 7498 | |
| 7499 | },{}],32:[function(require,module,exports){ |
| 7500 | "use strict"; |
| 7501 | |
| 7502 | module.exports = function(Chart) { |
| 7503 | |
| 7504 | var helpers = Chart.helpers; |
| 7505 | |
| 7506 | Chart.scaleService = { |
| 7507 | // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then |
| 7508 | // use the new chart options to grab the correct scale |
| 7509 | constructors: {}, |
| 7510 | // Use a registration function so that we can move to an ES6 map when we no longer need to support |
| 7511 | // old browsers |
| 7512 | |
| 7513 | // Scale config defaults |
| 7514 | defaults: {}, |
| 7515 | registerScaleType: function(type, scaleConstructor, defaults) { |
| 7516 | this.constructors[type] = scaleConstructor; |
| 7517 | this.defaults[type] = helpers.clone(defaults); |
| 7518 | }, |
| 7519 | getScaleConstructor: function(type) { |
| 7520 | return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; |
| 7521 | }, |
| 7522 | getScaleDefaults: function(type) { |
| 7523 | // Return the scale defaults merged with the global settings so that we always use the latest ones |
| 7524 | return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {}; |
| 7525 | }, |
| 7526 | updateScaleDefaults: function(type, additions) { |
| 7527 | var defaults = this.defaults; |
| 7528 | if (defaults.hasOwnProperty(type)) { |
| 7529 | defaults[type] = helpers.extend(defaults[type], additions); |
| 7530 | } |
| 7531 | }, |
| 7532 | addScalesToLayout: function(chartInstance) { |
| 7533 | // Adds each scale to the chart.boxes array to be sized accordingly |
| 7534 | helpers.each(chartInstance.scales, function(scale) { |
| 7535 | Chart.layoutService.addBox(chartInstance, scale); |
| 7536 | }); |
| 7537 | } |
| 7538 | }; |
| 7539 | }; |
| 7540 | },{}],33:[function(require,module,exports){ |
| 7541 | "use strict"; |
| 7542 | |
| 7543 | module.exports = function(Chart) { |
| 7544 | |
| 7545 | var helpers = Chart.helpers; |
| 7546 | |
| 7547 | Chart.defaults.global.title = { |
| 7548 | display: false, |
| 7549 | position: 'top', |
| 7550 | fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) |
| 7551 | |
| 7552 | fontStyle: 'bold', |
| 7553 | padding: 10, |
| 7554 | |
| 7555 | // actual title |
| 7556 | text: '' |
| 7557 | }; |
| 7558 | |
| 7559 | var noop = helpers.noop; |
| 7560 | Chart.Title = Chart.Element.extend({ |
| 7561 | |
| 7562 | initialize: function(config) { |
| 7563 | var me = this; |
| 7564 | helpers.extend(me, config); |
| 7565 | me.options = helpers.configMerge(Chart.defaults.global.title, config.options); |
| 7566 | |
| 7567 | // Contains hit boxes for each dataset (in dataset order) |
| 7568 | me.legendHitBoxes = []; |
| 7569 | }, |
| 7570 | |
| 7571 | // These methods are ordered by lifecyle. Utilities then follow. |
| 7572 | |
| 7573 | beforeUpdate: function () { |
| 7574 | var chartOpts = this.chart.options; |
| 7575 | if (chartOpts && chartOpts.title) { |
| 7576 | this.options = helpers.configMerge(Chart.defaults.global.title, chartOpts.title); |
| 7577 | } |
| 7578 | }, |
| 7579 | update: function(maxWidth, maxHeight, margins) { |
| 7580 | var me = this; |
| 7581 | |
| 7582 | // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) |
| 7583 | me.beforeUpdate(); |
| 7584 | |
| 7585 | // Absorb the master measurements |
| 7586 | me.maxWidth = maxWidth; |
| 7587 | me.maxHeight = maxHeight; |
| 7588 | me.margins = margins; |
| 7589 | |
| 7590 | // Dimensions |
| 7591 | me.beforeSetDimensions(); |
| 7592 | me.setDimensions(); |
| 7593 | me.afterSetDimensions(); |
| 7594 | // Labels |
| 7595 | me.beforeBuildLabels(); |
| 7596 | me.buildLabels(); |
| 7597 | me.afterBuildLabels(); |
| 7598 | |
| 7599 | // Fit |
| 7600 | me.beforeFit(); |
| 7601 | me.fit(); |
| 7602 | me.afterFit(); |
| 7603 | // |
| 7604 | me.afterUpdate(); |
| 7605 | |
| 7606 | return me.minSize; |
| 7607 | |
| 7608 | }, |
| 7609 | afterUpdate: noop, |
| 7610 | |
| 7611 | // |
| 7612 | |
| 7613 | beforeSetDimensions: noop, |
| 7614 | setDimensions: function() { |
| 7615 | var me = this; |
| 7616 | // Set the unconstrained dimension before label rotation |
| 7617 | if (me.isHorizontal()) { |
| 7618 | // Reset position before calculating rotation |
| 7619 | me.width = me.maxWidth; |
| 7620 | me.left = 0; |
| 7621 | me.right = me.width; |
| 7622 | } else { |
| 7623 | me.height = me.maxHeight; |
| 7624 | |
| 7625 | // Reset position before calculating rotation |
| 7626 | me.top = 0; |
| 7627 | me.bottom = me.height; |
| 7628 | } |
| 7629 | |
| 7630 | // Reset padding |
| 7631 | me.paddingLeft = 0; |
| 7632 | me.paddingTop = 0; |
| 7633 | me.paddingRight = 0; |
| 7634 | me.paddingBottom = 0; |
| 7635 | |
| 7636 | // Reset minSize |
| 7637 | me.minSize = { |
| 7638 | width: 0, |
| 7639 | height: 0 |
| 7640 | }; |
| 7641 | }, |
| 7642 | afterSetDimensions: noop, |
| 7643 | |
| 7644 | // |
| 7645 | |
| 7646 | beforeBuildLabels: noop, |
| 7647 | buildLabels: noop, |
| 7648 | afterBuildLabels: noop, |
| 7649 | |
| 7650 | // |
| 7651 | |
| 7652 | beforeFit: noop, |
| 7653 | fit: function() { |
| 7654 | var me = this, |
| 7655 | valueOrDefault = helpers.getValueOrDefault, |
| 7656 | opts = me.options, |
| 7657 | globalDefaults = Chart.defaults.global, |
| 7658 | display = opts.display, |
| 7659 | fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), |
| 7660 | minSize = me.minSize; |
| 7661 | |
| 7662 | if (me.isHorizontal()) { |
| 7663 | minSize.width = me.maxWidth; // fill all the width |
| 7664 | minSize.height = display ? fontSize + (opts.padding * 2) : 0; |
| 7665 | } else { |
| 7666 | minSize.width = display ? fontSize + (opts.padding * 2) : 0; |
| 7667 | minSize.height = me.maxHeight; // fill all the height |
| 7668 | } |
| 7669 | |
| 7670 | me.width = minSize.width; |
| 7671 | me.height = minSize.height; |
| 7672 | |
| 7673 | }, |
| 7674 | afterFit: noop, |
| 7675 | |
| 7676 | // Shared Methods |
| 7677 | isHorizontal: function() { |
| 7678 | var pos = this.options.position; |
| 7679 | return pos === "top" || pos === "bottom"; |
| 7680 | }, |
| 7681 | |
| 7682 | // Actualy draw the title block on the canvas |
| 7683 | draw: function() { |
| 7684 | var me = this, |
| 7685 | ctx = me.ctx, |
| 7686 | valueOrDefault = helpers.getValueOrDefault, |
| 7687 | opts = me.options, |
| 7688 | globalDefaults = Chart.defaults.global; |
| 7689 | |
| 7690 | if (opts.display) { |
| 7691 | var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), |
| 7692 | fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle), |
| 7693 | fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily), |
| 7694 | titleFont = helpers.fontString(fontSize, fontStyle, fontFamily), |
| 7695 | rotation = 0, |
| 7696 | titleX, |
| 7697 | titleY, |
| 7698 | top = me.top, |
| 7699 | left = me.left, |
| 7700 | bottom = me.bottom, |
| 7701 | right = me.right; |
| 7702 | |
| 7703 | ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour |
| 7704 | ctx.font = titleFont; |
| 7705 | |
| 7706 | // Horizontal |
| 7707 | if (me.isHorizontal()) { |
| 7708 | titleX = left + ((right - left) / 2); // midpoint of the width |
| 7709 | titleY = top + ((bottom - top) / 2); // midpoint of the height |
| 7710 | } else { |
| 7711 | titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2); |
| 7712 | titleY = top + ((bottom - top) / 2); |
| 7713 | rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); |
| 7714 | } |
| 7715 | |
| 7716 | ctx.save(); |
| 7717 | ctx.translate(titleX, titleY); |
| 7718 | ctx.rotate(rotation); |
| 7719 | ctx.textAlign = 'center'; |
| 7720 | ctx.textBaseline = 'middle'; |
| 7721 | ctx.fillText(opts.text, 0, 0); |
| 7722 | ctx.restore(); |
| 7723 | } |
| 7724 | } |
| 7725 | }); |
| 7726 | |
| 7727 | // Register the title plugin |
| 7728 | Chart.plugins.register({ |
| 7729 | beforeInit: function(chartInstance) { |
| 7730 | var opts = chartInstance.options; |
| 7731 | var titleOpts = opts.title; |
| 7732 | |
| 7733 | if (titleOpts) { |
| 7734 | chartInstance.titleBlock = new Chart.Title({ |
| 7735 | ctx: chartInstance.chart.ctx, |
| 7736 | options: titleOpts, |
| 7737 | chart: chartInstance |
| 7738 | }); |
| 7739 | |
| 7740 | Chart.layoutService.addBox(chartInstance, chartInstance.titleBlock); |
| 7741 | } |
| 7742 | } |
| 7743 | }); |
| 7744 | }; |
| 7745 | |
| 7746 | },{}],34:[function(require,module,exports){ |
| 7747 | "use strict"; |
| 7748 | |
| 7749 | module.exports = function(Chart) { |
| 7750 | |
| 7751 | var helpers = Chart.helpers; |
| 7752 | |
| 7753 | Chart.defaults.global.tooltips = { |
| 7754 | enabled: true, |
| 7755 | custom: null, |
| 7756 | mode: 'single', |
| 7757 | backgroundColor: "rgba(0,0,0,0.8)", |
| 7758 | titleFontStyle: "bold", |
| 7759 | titleSpacing: 2, |
| 7760 | titleMarginBottom: 6, |
| 7761 | titleFontColor: "#fff", |
| 7762 | titleAlign: "left", |
| 7763 | bodySpacing: 2, |
| 7764 | bodyFontColor: "#fff", |
| 7765 | bodyAlign: "left", |
| 7766 | footerFontStyle: "bold", |
| 7767 | footerSpacing: 2, |
| 7768 | footerMarginTop: 6, |
| 7769 | footerFontColor: "#fff", |
| 7770 | footerAlign: "left", |
| 7771 | yPadding: 6, |
| 7772 | xPadding: 6, |
| 7773 | yAlign : 'center', |
| 7774 | xAlign : 'center', |
| 7775 | caretSize: 5, |
| 7776 | cornerRadius: 6, |
| 7777 | multiKeyBackground: '#fff', |
| 7778 | callbacks: { |
| 7779 | // Args are: (tooltipItems, data) |
| 7780 | beforeTitle: helpers.noop, |
| 7781 | title: function(tooltipItems, data) { |
| 7782 | // Pick first xLabel for now |
| 7783 | var title = ''; |
| 7784 | var labels = data.labels; |
| 7785 | var labelCount = labels ? labels.length : 0; |
| 7786 | |
| 7787 | if (tooltipItems.length > 0) { |
| 7788 | var item = tooltipItems[0]; |
| 7789 | |
| 7790 | if (item.xLabel) { |
| 7791 | title = item.xLabel; |
| 7792 | } else if (labelCount > 0 && item.index < labelCount) { |
| 7793 | title = labels[item.index]; |
| 7794 | } |
| 7795 | } |
| 7796 | |
| 7797 | return title; |
| 7798 | }, |
| 7799 | afterTitle: helpers.noop, |
| 7800 | |
| 7801 | // Args are: (tooltipItems, data) |
| 7802 | beforeBody: helpers.noop, |
| 7803 | |
| 7804 | // Args are: (tooltipItem, data) |
| 7805 | beforeLabel: helpers.noop, |
| 7806 | label: function(tooltipItem, data) { |
| 7807 | var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; |
| 7808 | return datasetLabel + ': ' + tooltipItem.yLabel; |
| 7809 | }, |
| 7810 | labelColor: function(tooltipItem, chartInstance) { |
| 7811 | var meta = chartInstance.getDatasetMeta(tooltipItem.datasetIndex); |
| 7812 | var activeElement = meta.data[tooltipItem.index]; |
| 7813 | var view = activeElement._view; |
| 7814 | return { |
| 7815 | borderColor: view.borderColor, |
| 7816 | backgroundColor: view.backgroundColor |
| 7817 | }; |
| 7818 | }, |
| 7819 | afterLabel: helpers.noop, |
| 7820 | |
| 7821 | // Args are: (tooltipItems, data) |
| 7822 | afterBody: helpers.noop, |
| 7823 | |
| 7824 | // Args are: (tooltipItems, data) |
| 7825 | beforeFooter: helpers.noop, |
| 7826 | footer: helpers.noop, |
| 7827 | afterFooter: helpers.noop |
| 7828 | } |
| 7829 | }; |
| 7830 | |
| 7831 | // Helper to push or concat based on if the 2nd parameter is an array or not |
| 7832 | function pushOrConcat(base, toPush) { |
| 7833 | if (toPush) { |
| 7834 | if (helpers.isArray(toPush)) { |
| 7835 | //base = base.concat(toPush); |
| 7836 | Array.prototype.push.apply(base, toPush); |
| 7837 | } else { |
| 7838 | base.push(toPush); |
| 7839 | } |
| 7840 | } |
| 7841 | |
| 7842 | return base; |
| 7843 | } |
| 7844 | |
| 7845 | function getAveragePosition(elements) { |
| 7846 | if (!elements.length) { |
| 7847 | return false; |
| 7848 | } |
| 7849 | |
| 7850 | var i, len; |
| 7851 | var xPositions = []; |
| 7852 | var yPositions = []; |
| 7853 | |
| 7854 | for (i = 0, len = elements.length; i < len; ++i) { |
| 7855 | var el = elements[i]; |
| 7856 | if (el && el.hasValue()){ |
| 7857 | var pos = el.tooltipPosition(); |
| 7858 | xPositions.push(pos.x); |
| 7859 | yPositions.push(pos.y); |
| 7860 | } |
| 7861 | } |
| 7862 | |
| 7863 | var x = 0, |
| 7864 | y = 0; |
| 7865 | for (i = 0; i < xPositions.length; ++i) { |
| 7866 | if (xPositions[ i ]) { |
| 7867 | x += xPositions[i]; |
| 7868 | y += yPositions[i]; |
| 7869 | } |
| 7870 | } |
| 7871 | |
| 7872 | return { |
| 7873 | x: Math.round(x / xPositions.length), |
| 7874 | y: Math.round(y / xPositions.length) |
| 7875 | }; |
| 7876 | } |
| 7877 | |
| 7878 | // Private helper to create a tooltip iteam model |
| 7879 | // @param element : the chart element (point, arc, bar) to create the tooltip item for |
| 7880 | // @return : new tooltip item |
| 7881 | function createTooltipItem(element) { |
| 7882 | var xScale = element._xScale; |
| 7883 | var yScale = element._yScale || element._scale; // handle radar || polarArea charts |
| 7884 | var index = element._index, |
| 7885 | datasetIndex = element._datasetIndex; |
| 7886 | |
| 7887 | return { |
| 7888 | xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', |
| 7889 | yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', |
| 7890 | index: index, |
| 7891 | datasetIndex: datasetIndex |
| 7892 | }; |
| 7893 | } |
| 7894 | |
| 7895 | Chart.Tooltip = Chart.Element.extend({ |
| 7896 | initialize: function() { |
| 7897 | var me = this; |
| 7898 | var globalDefaults = Chart.defaults.global; |
| 7899 | var tooltipOpts = me._options; |
| 7900 | var getValueOrDefault = helpers.getValueOrDefault; |
| 7901 | |
| 7902 | helpers.extend(me, { |
| 7903 | _model: { |
| 7904 | // Positioning |
| 7905 | xPadding: tooltipOpts.xPadding, |
| 7906 | yPadding: tooltipOpts.yPadding, |
| 7907 | xAlign : tooltipOpts.xAlign, |
| 7908 | yAlign : tooltipOpts.yAlign, |
| 7909 | |
| 7910 | // Body |
| 7911 | bodyFontColor: tooltipOpts.bodyFontColor, |
| 7912 | _bodyFontFamily: getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), |
| 7913 | _bodyFontStyle: getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), |
| 7914 | _bodyAlign: tooltipOpts.bodyAlign, |
| 7915 | bodyFontSize: getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), |
| 7916 | bodySpacing: tooltipOpts.bodySpacing, |
| 7917 | |
| 7918 | // Title |
| 7919 | titleFontColor: tooltipOpts.titleFontColor, |
| 7920 | _titleFontFamily: getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), |
| 7921 | _titleFontStyle: getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), |
| 7922 | titleFontSize: getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), |
| 7923 | _titleAlign: tooltipOpts.titleAlign, |
| 7924 | titleSpacing: tooltipOpts.titleSpacing, |
| 7925 | titleMarginBottom: tooltipOpts.titleMarginBottom, |
| 7926 | |
| 7927 | // Footer |
| 7928 | footerFontColor: tooltipOpts.footerFontColor, |
| 7929 | _footerFontFamily: getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), |
| 7930 | _footerFontStyle: getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), |
| 7931 | footerFontSize: getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), |
| 7932 | _footerAlign: tooltipOpts.footerAlign, |
| 7933 | footerSpacing: tooltipOpts.footerSpacing, |
| 7934 | footerMarginTop: tooltipOpts.footerMarginTop, |
| 7935 | |
| 7936 | // Appearance |
| 7937 | caretSize: tooltipOpts.caretSize, |
| 7938 | cornerRadius: tooltipOpts.cornerRadius, |
| 7939 | backgroundColor: tooltipOpts.backgroundColor, |
| 7940 | opacity: 0, |
| 7941 | legendColorBackground: tooltipOpts.multiKeyBackground |
| 7942 | } |
| 7943 | }); |
| 7944 | }, |
| 7945 | |
| 7946 | // Get the title |
| 7947 | // Args are: (tooltipItem, data) |
| 7948 | getTitle: function() { |
| 7949 | var me = this; |
| 7950 | var opts = me._options; |
| 7951 | var callbacks = opts.callbacks; |
| 7952 | |
| 7953 | var beforeTitle = callbacks.beforeTitle.apply(me, arguments), |
| 7954 | title = callbacks.title.apply(me, arguments), |
| 7955 | afterTitle = callbacks.afterTitle.apply(me, arguments); |
| 7956 | |
| 7957 | var lines = []; |
| 7958 | lines = pushOrConcat(lines, beforeTitle); |
| 7959 | lines = pushOrConcat(lines, title); |
| 7960 | lines = pushOrConcat(lines, afterTitle); |
| 7961 | |
| 7962 | return lines; |
| 7963 | }, |
| 7964 | |
| 7965 | // Args are: (tooltipItem, data) |
| 7966 | getBeforeBody: function() { |
| 7967 | var lines = this._options.callbacks.beforeBody.apply(this, arguments); |
| 7968 | return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; |
| 7969 | }, |
| 7970 | |
| 7971 | // Args are: (tooltipItem, data) |
| 7972 | getBody: function(tooltipItems, data) { |
| 7973 | var me = this; |
| 7974 | var callbacks = me._options.callbacks; |
| 7975 | var bodyItems = []; |
| 7976 | |
| 7977 | helpers.each(tooltipItems, function(tooltipItem) { |
| 7978 | var bodyItem = { |
| 7979 | before: [], |
| 7980 | lines: [], |
| 7981 | after: [] |
| 7982 | }; |
| 7983 | pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data)); |
| 7984 | pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); |
| 7985 | pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data)); |
| 7986 | |
| 7987 | bodyItems.push(bodyItem); |
| 7988 | }); |
| 7989 | |
| 7990 | return bodyItems; |
| 7991 | }, |
| 7992 | |
| 7993 | // Args are: (tooltipItem, data) |
| 7994 | getAfterBody: function() { |
| 7995 | var lines = this._options.callbacks.afterBody.apply(this, arguments); |
| 7996 | return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; |
| 7997 | }, |
| 7998 | |
| 7999 | // Get the footer and beforeFooter and afterFooter lines |
| 8000 | // Args are: (tooltipItem, data) |
| 8001 | getFooter: function() { |
| 8002 | var me = this; |
| 8003 | var callbacks = me._options.callbacks; |
| 8004 | |
| 8005 | var beforeFooter = callbacks.beforeFooter.apply(me, arguments); |
| 8006 | var footer = callbacks.footer.apply(me, arguments); |
| 8007 | var afterFooter = callbacks.afterFooter.apply(me, arguments); |
| 8008 | |
| 8009 | var lines = []; |
| 8010 | lines = pushOrConcat(lines, beforeFooter); |
| 8011 | lines = pushOrConcat(lines, footer); |
| 8012 | lines = pushOrConcat(lines, afterFooter); |
| 8013 | |
| 8014 | return lines; |
| 8015 | }, |
| 8016 | |
| 8017 | update: function(changed) { |
| 8018 | var me = this; |
| 8019 | var opts = me._options; |
| 8020 | var model = me._model; |
| 8021 | var active = me._active; |
| 8022 | |
| 8023 | var data = me._data; |
| 8024 | var chartInstance = me._chartInstance; |
| 8025 | |
| 8026 | var i, len; |
| 8027 | |
| 8028 | if (active.length) { |
| 8029 | model.opacity = 1; |
| 8030 | |
| 8031 | var labelColors = [], |
| 8032 | tooltipPosition = getAveragePosition(active); |
| 8033 | |
| 8034 | var tooltipItems = []; |
| 8035 | for (i = 0, len = active.length; i < len; ++i) { |
| 8036 | tooltipItems.push(createTooltipItem(active[i])); |
| 8037 | } |
| 8038 | |
| 8039 | // If the user provided a sorting function, use it to modify the tooltip items |
| 8040 | if (opts.itemSort) { |
| 8041 | tooltipItems = tooltipItems.sort(opts.itemSort); |
| 8042 | } |
| 8043 | |
| 8044 | // If there is more than one item, show color items |
| 8045 | if (active.length > 1) { |
| 8046 | helpers.each(tooltipItems, function(tooltipItem) { |
| 8047 | labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance)); |
| 8048 | }); |
| 8049 | } |
| 8050 | |
| 8051 | // Build the Text Lines |
| 8052 | helpers.extend(model, { |
| 8053 | title: me.getTitle(tooltipItems, data), |
| 8054 | beforeBody: me.getBeforeBody(tooltipItems, data), |
| 8055 | body: me.getBody(tooltipItems, data), |
| 8056 | afterBody: me.getAfterBody(tooltipItems, data), |
| 8057 | footer: me.getFooter(tooltipItems, data), |
| 8058 | x: Math.round(tooltipPosition.x), |
| 8059 | y: Math.round(tooltipPosition.y), |
| 8060 | caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2), |
| 8061 | labelColors: labelColors |
| 8062 | }); |
| 8063 | |
| 8064 | // We need to determine alignment of |
| 8065 | var tooltipSize = me.getTooltipSize(model); |
| 8066 | me.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas |
| 8067 | |
| 8068 | helpers.extend(model, me.getBackgroundPoint(model, tooltipSize)); |
| 8069 | } else { |
| 8070 | me._model.opacity = 0; |
| 8071 | } |
| 8072 | |
| 8073 | if (changed && opts.custom) { |
| 8074 | opts.custom.call(me, model); |
| 8075 | } |
| 8076 | |
| 8077 | return me; |
| 8078 | }, |
| 8079 | getTooltipSize: function(vm) { |
| 8080 | var ctx = this._chart.ctx; |
| 8081 | |
| 8082 | var size = { |
| 8083 | height: vm.yPadding * 2, // Tooltip Padding |
| 8084 | width: 0 |
| 8085 | }; |
| 8086 | |
| 8087 | // Count of all lines in the body |
| 8088 | var body = vm.body; |
| 8089 | var combinedBodyLength = body.reduce(function(count, bodyItem) { |
| 8090 | return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; |
| 8091 | }, 0); |
| 8092 | combinedBodyLength += vm.beforeBody.length + vm.afterBody.length; |
| 8093 | |
| 8094 | var titleLineCount = vm.title.length; |
| 8095 | var footerLineCount = vm.footer.length; |
| 8096 | var titleFontSize = vm.titleFontSize, |
| 8097 | bodyFontSize = vm.bodyFontSize, |
| 8098 | footerFontSize = vm.footerFontSize; |
| 8099 | |
| 8100 | size.height += titleLineCount * titleFontSize; // Title Lines |
| 8101 | size.height += (titleLineCount - 1) * vm.titleSpacing; // Title Line Spacing |
| 8102 | size.height += titleLineCount ? vm.titleMarginBottom : 0; // Title's bottom Margin |
| 8103 | size.height += combinedBodyLength * bodyFontSize; // Body Lines |
| 8104 | size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing |
| 8105 | size.height += footerLineCount ? vm.footerMarginTop : 0; // Footer Margin |
| 8106 | size.height += footerLineCount * (footerFontSize); // Footer Lines |
| 8107 | size.height += footerLineCount ? (footerLineCount - 1) * vm.footerSpacing : 0; // Footer Line Spacing |
| 8108 | |
| 8109 | // Title width |
| 8110 | var widthPadding = 0; |
| 8111 | var maxLineWidth = function(line) { |
| 8112 | size.width = Math.max(size.width, ctx.measureText(line).width + widthPadding); |
| 8113 | }; |
| 8114 | |
| 8115 | ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); |
| 8116 | helpers.each(vm.title, maxLineWidth); |
| 8117 | |
| 8118 | // Body width |
| 8119 | ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); |
| 8120 | helpers.each(vm.beforeBody.concat(vm.afterBody), maxLineWidth); |
| 8121 | |
| 8122 | // Body lines may include some extra width due to the color box |
| 8123 | widthPadding = body.length > 1 ? (bodyFontSize + 2) : 0; |
| 8124 | helpers.each(body, function(bodyItem) { |
| 8125 | helpers.each(bodyItem.before, maxLineWidth); |
| 8126 | helpers.each(bodyItem.lines, maxLineWidth); |
| 8127 | helpers.each(bodyItem.after, maxLineWidth); |
| 8128 | }); |
| 8129 | |
| 8130 | // Reset back to 0 |
| 8131 | widthPadding = 0; |
| 8132 | |
| 8133 | // Footer width |
| 8134 | ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); |
| 8135 | helpers.each(vm.footer, maxLineWidth); |
| 8136 | |
| 8137 | // Add padding |
| 8138 | size.width += 2 * vm.xPadding; |
| 8139 | |
| 8140 | return size; |
| 8141 | }, |
| 8142 | determineAlignment: function(size) { |
| 8143 | var me = this; |
| 8144 | var model = me._model; |
| 8145 | var chart = me._chart; |
| 8146 | var chartArea = me._chartInstance.chartArea; |
| 8147 | |
| 8148 | if (model.y < size.height) { |
| 8149 | model.yAlign = 'top'; |
| 8150 | } else if (model.y > (chart.height - size.height)) { |
| 8151 | model.yAlign = 'bottom'; |
| 8152 | } |
| 8153 | |
| 8154 | var lf, rf; // functions to determine left, right alignment |
| 8155 | var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart |
| 8156 | var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges |
| 8157 | var midX = (chartArea.left + chartArea.right) / 2; |
| 8158 | var midY = (chartArea.top + chartArea.bottom) / 2; |
| 8159 | |
| 8160 | if (model.yAlign === 'center') { |
| 8161 | lf = function(x) { |
| 8162 | return x <= midX; |
| 8163 | }; |
| 8164 | rf = function(x) { |
| 8165 | return x > midX; |
| 8166 | }; |
| 8167 | } else { |
| 8168 | lf = function(x) { |
| 8169 | return x <= (size.width / 2); |
| 8170 | }; |
| 8171 | rf = function(x) { |
| 8172 | return x >= (chart.width - (size.width / 2)); |
| 8173 | }; |
| 8174 | } |
| 8175 | |
| 8176 | olf = function(x) { |
| 8177 | return x + size.width > chart.width; |
| 8178 | }; |
| 8179 | orf = function(x) { |
| 8180 | return x - size.width < 0; |
| 8181 | }; |
| 8182 | yf = function(y) { |
| 8183 | return y <= midY ? 'top' : 'bottom'; |
| 8184 | }; |
| 8185 | |
| 8186 | if (lf(model.x)) { |
| 8187 | model.xAlign = 'left'; |
| 8188 | |
| 8189 | // Is tooltip too wide and goes over the right side of the chart.? |
| 8190 | if (olf(model.x)) { |
| 8191 | model.xAlign = 'center'; |
| 8192 | model.yAlign = yf(model.y); |
| 8193 | } |
| 8194 | } else if (rf(model.x)) { |
| 8195 | model.xAlign = 'right'; |
| 8196 | |
| 8197 | // Is tooltip too wide and goes outside left edge of canvas? |
| 8198 | if (orf(model.x)) { |
| 8199 | model.xAlign = 'center'; |
| 8200 | model.yAlign = yf(model.y); |
| 8201 | } |
| 8202 | } |
| 8203 | }, |
| 8204 | getBackgroundPoint: function(vm, size) { |
| 8205 | // Background Position |
| 8206 | var pt = { |
| 8207 | x: vm.x, |
| 8208 | y: vm.y |
| 8209 | }; |
| 8210 | |
| 8211 | var caretSize = vm.caretSize, |
| 8212 | caretPadding = vm.caretPadding, |
| 8213 | cornerRadius = vm.cornerRadius, |
| 8214 | xAlign = vm.xAlign, |
| 8215 | yAlign = vm.yAlign, |
| 8216 | paddingAndSize = caretSize + caretPadding, |
| 8217 | radiusAndPadding = cornerRadius + caretPadding; |
| 8218 | |
| 8219 | if (xAlign === 'right') { |
| 8220 | pt.x -= size.width; |
| 8221 | } else if (xAlign === 'center') { |
| 8222 | pt.x -= (size.width / 2); |
| 8223 | } |
| 8224 | |
| 8225 | if (yAlign === 'top') { |
| 8226 | pt.y += paddingAndSize; |
| 8227 | } else if (yAlign === 'bottom') { |
| 8228 | pt.y -= size.height + paddingAndSize; |
| 8229 | } else { |
| 8230 | pt.y -= (size.height / 2); |
| 8231 | } |
| 8232 | |
| 8233 | if (yAlign === 'center') { |
| 8234 | if (xAlign === 'left') { |
| 8235 | pt.x += paddingAndSize; |
| 8236 | } else if (xAlign === 'right') { |
| 8237 | pt.x -= paddingAndSize; |
| 8238 | } |
| 8239 | } else { |
| 8240 | if (xAlign === 'left') { |
| 8241 | pt.x -= radiusAndPadding; |
| 8242 | } else if (xAlign === 'right') { |
| 8243 | pt.x += radiusAndPadding; |
| 8244 | } |
| 8245 | } |
| 8246 | |
| 8247 | return pt; |
| 8248 | }, |
| 8249 | drawCaret: function(tooltipPoint, size, opacity) { |
| 8250 | var vm = this._view; |
| 8251 | var ctx = this._chart.ctx; |
| 8252 | var x1, x2, x3; |
| 8253 | var y1, y2, y3; |
| 8254 | var caretSize = vm.caretSize; |
| 8255 | var cornerRadius = vm.cornerRadius; |
| 8256 | var xAlign = vm.xAlign, |
| 8257 | yAlign = vm.yAlign; |
| 8258 | var ptX = tooltipPoint.x, |
| 8259 | ptY = tooltipPoint.y; |
| 8260 | var width = size.width, |
| 8261 | height = size.height; |
| 8262 | |
| 8263 | if (yAlign === 'center') { |
| 8264 | // Left or right side |
| 8265 | if (xAlign === 'left') { |
| 8266 | x1 = ptX; |
| 8267 | x2 = x1 - caretSize; |
| 8268 | x3 = x1; |
| 8269 | } else { |
| 8270 | x1 = ptX + width; |
| 8271 | x2 = x1 + caretSize; |
| 8272 | x3 = x1; |
| 8273 | } |
| 8274 | |
| 8275 | y2 = ptY + (height / 2); |
| 8276 | y1 = y2 - caretSize; |
| 8277 | y3 = y2 + caretSize; |
| 8278 | } else { |
| 8279 | if (xAlign === 'left') { |
| 8280 | x1 = ptX + cornerRadius; |
| 8281 | x2 = x1 + caretSize; |
| 8282 | x3 = x2 + caretSize; |
| 8283 | } else if (xAlign === 'right') { |
| 8284 | x1 = ptX + width - cornerRadius; |
| 8285 | x2 = x1 - caretSize; |
| 8286 | x3 = x2 - caretSize; |
| 8287 | } else { |
| 8288 | x2 = ptX + (width / 2); |
| 8289 | x1 = x2 - caretSize; |
| 8290 | x3 = x2 + caretSize; |
| 8291 | } |
| 8292 | |
| 8293 | if (yAlign === 'top') { |
| 8294 | y1 = ptY; |
| 8295 | y2 = y1 - caretSize; |
| 8296 | y3 = y1; |
| 8297 | } else { |
| 8298 | y1 = ptY + height; |
| 8299 | y2 = y1 + caretSize; |
| 8300 | y3 = y1; |
| 8301 | } |
| 8302 | } |
| 8303 | |
| 8304 | var bgColor = helpers.color(vm.backgroundColor); |
| 8305 | ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString(); |
| 8306 | ctx.beginPath(); |
| 8307 | ctx.moveTo(x1, y1); |
| 8308 | ctx.lineTo(x2, y2); |
| 8309 | ctx.lineTo(x3, y3); |
| 8310 | ctx.closePath(); |
| 8311 | ctx.fill(); |
| 8312 | }, |
| 8313 | drawTitle: function(pt, vm, ctx, opacity) { |
| 8314 | var title = vm.title; |
| 8315 | |
| 8316 | if (title.length) { |
| 8317 | ctx.textAlign = vm._titleAlign; |
| 8318 | ctx.textBaseline = "top"; |
| 8319 | |
| 8320 | var titleFontSize = vm.titleFontSize, |
| 8321 | titleSpacing = vm.titleSpacing; |
| 8322 | |
| 8323 | var titleFontColor = helpers.color(vm.titleFontColor); |
| 8324 | ctx.fillStyle = titleFontColor.alpha(opacity * titleFontColor.alpha()).rgbString(); |
| 8325 | ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); |
| 8326 | |
| 8327 | var i, len; |
| 8328 | for (i = 0, len = title.length; i < len; ++i) { |
| 8329 | ctx.fillText(title[i], pt.x, pt.y); |
| 8330 | pt.y += titleFontSize + titleSpacing; // Line Height and spacing |
| 8331 | |
| 8332 | if (i + 1 === title.length) { |
| 8333 | pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing |
| 8334 | } |
| 8335 | } |
| 8336 | } |
| 8337 | }, |
| 8338 | drawBody: function(pt, vm, ctx, opacity) { |
| 8339 | var bodyFontSize = vm.bodyFontSize; |
| 8340 | var bodySpacing = vm.bodySpacing; |
| 8341 | var body = vm.body; |
| 8342 | |
| 8343 | ctx.textAlign = vm._bodyAlign; |
| 8344 | ctx.textBaseline = "top"; |
| 8345 | |
| 8346 | var bodyFontColor = helpers.color(vm.bodyFontColor); |
| 8347 | var textColor = bodyFontColor.alpha(opacity * bodyFontColor.alpha()).rgbString(); |
| 8348 | ctx.fillStyle = textColor; |
| 8349 | ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); |
| 8350 | |
| 8351 | // Before Body |
| 8352 | var xLinePadding = 0; |
| 8353 | var fillLineOfText = function(line) { |
| 8354 | ctx.fillText(line, pt.x + xLinePadding, pt.y); |
| 8355 | pt.y += bodyFontSize + bodySpacing; |
| 8356 | }; |
| 8357 | |
| 8358 | // Before body lines |
| 8359 | helpers.each(vm.beforeBody, fillLineOfText); |
| 8360 | |
| 8361 | var drawColorBoxes = body.length > 1; |
| 8362 | xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; |
| 8363 | |
| 8364 | // Draw body lines now |
| 8365 | helpers.each(body, function(bodyItem, i) { |
| 8366 | helpers.each(bodyItem.before, fillLineOfText); |
| 8367 | |
| 8368 | helpers.each(bodyItem.lines, function(line) { |
| 8369 | // Draw Legend-like boxes if needed |
| 8370 | if (drawColorBoxes) { |
| 8371 | // Fill a white rect so that colours merge nicely if the opacity is < 1 |
| 8372 | ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString(); |
| 8373 | ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); |
| 8374 | |
| 8375 | // Border |
| 8376 | ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString(); |
| 8377 | ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); |
| 8378 | |
| 8379 | // Inner square |
| 8380 | ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString(); |
| 8381 | ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); |
| 8382 | |
| 8383 | ctx.fillStyle = textColor; |
| 8384 | } |
| 8385 | |
| 8386 | fillLineOfText(line); |
| 8387 | }); |
| 8388 | |
| 8389 | helpers.each(bodyItem.after, fillLineOfText); |
| 8390 | }); |
| 8391 | |
| 8392 | // Reset back to 0 for after body |
| 8393 | xLinePadding = 0; |
| 8394 | |
| 8395 | // After body lines |
| 8396 | helpers.each(vm.afterBody, fillLineOfText); |
| 8397 | pt.y -= bodySpacing; // Remove last body spacing |
| 8398 | }, |
| 8399 | drawFooter: function(pt, vm, ctx, opacity) { |
| 8400 | var footer = vm.footer; |
| 8401 | |
| 8402 | if (footer.length) { |
| 8403 | pt.y += vm.footerMarginTop; |
| 8404 | |
| 8405 | ctx.textAlign = vm._footerAlign; |
| 8406 | ctx.textBaseline = "top"; |
| 8407 | |
| 8408 | var footerFontColor = helpers.color(vm.footerFontColor); |
| 8409 | ctx.fillStyle = footerFontColor.alpha(opacity * footerFontColor.alpha()).rgbString(); |
| 8410 | ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); |
| 8411 | |
| 8412 | helpers.each(footer, function(line) { |
| 8413 | ctx.fillText(line, pt.x, pt.y); |
| 8414 | pt.y += vm.footerFontSize + vm.footerSpacing; |
| 8415 | }); |
| 8416 | } |
| 8417 | }, |
| 8418 | draw: function() { |
| 8419 | var ctx = this._chart.ctx; |
| 8420 | var vm = this._view; |
| 8421 | |
| 8422 | if (vm.opacity === 0) { |
| 8423 | return; |
| 8424 | } |
| 8425 | |
| 8426 | var tooltipSize = this.getTooltipSize(vm); |
| 8427 | var pt = { |
| 8428 | x: vm.x, |
| 8429 | y: vm.y |
| 8430 | }; |
| 8431 | |
| 8432 | // IE11/Edge does not like very small opacities, so snap to 0 |
| 8433 | var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; |
| 8434 | |
| 8435 | if (this._options.enabled) { |
| 8436 | // Draw Background |
| 8437 | var bgColor = helpers.color(vm.backgroundColor); |
| 8438 | ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString(); |
| 8439 | helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius); |
| 8440 | ctx.fill(); |
| 8441 | |
| 8442 | // Draw Caret |
| 8443 | this.drawCaret(pt, tooltipSize, opacity); |
| 8444 | |
| 8445 | // Draw Title, Body, and Footer |
| 8446 | pt.x += vm.xPadding; |
| 8447 | pt.y += vm.yPadding; |
| 8448 | |
| 8449 | // Titles |
| 8450 | this.drawTitle(pt, vm, ctx, opacity); |
| 8451 | |
| 8452 | // Body |
| 8453 | this.drawBody(pt, vm, ctx, opacity); |
| 8454 | |
| 8455 | // Footer |
| 8456 | this.drawFooter(pt, vm, ctx, opacity); |
| 8457 | } |
| 8458 | } |
| 8459 | }); |
| 8460 | }; |
| 8461 | |
| 8462 | },{}],35:[function(require,module,exports){ |
| 8463 | "use strict"; |
| 8464 | |
| 8465 | module.exports = function(Chart) { |
| 8466 | |
| 8467 | var helpers = Chart.helpers, |
| 8468 | globalOpts = Chart.defaults.global; |
| 8469 | |
| 8470 | globalOpts.elements.arc = { |
| 8471 | backgroundColor: globalOpts.defaultColor, |
| 8472 | borderColor: "#fff", |
| 8473 | borderWidth: 2 |
| 8474 | }; |
| 8475 | |
| 8476 | Chart.elements.Arc = Chart.Element.extend({ |
| 8477 | inLabelRange: function(mouseX) { |
| 8478 | var vm = this._view; |
| 8479 | |
| 8480 | if (vm) { |
| 8481 | return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); |
| 8482 | } else { |
| 8483 | return false; |
| 8484 | } |
| 8485 | }, |
| 8486 | inRange: function(chartX, chartY) { |
| 8487 | var vm = this._view; |
| 8488 | |
| 8489 | if (vm) { |
| 8490 | var pointRelativePosition = helpers.getAngleFromPoint(vm, { |
| 8491 | x: chartX, |
| 8492 | y: chartY |
| 8493 | }), |
| 8494 | angle = pointRelativePosition.angle, |
| 8495 | distance = pointRelativePosition.distance; |
| 8496 | |
| 8497 | //Sanitise angle range |
| 8498 | var startAngle = vm.startAngle; |
| 8499 | var endAngle = vm.endAngle; |
| 8500 | while (endAngle < startAngle) { |
| 8501 | endAngle += 2.0 * Math.PI; |
| 8502 | } |
| 8503 | while (angle > endAngle) { |
| 8504 | angle -= 2.0 * Math.PI; |
| 8505 | } |
| 8506 | while (angle < startAngle) { |
| 8507 | angle += 2.0 * Math.PI; |
| 8508 | } |
| 8509 | |
| 8510 | //Check if within the range of the open/close angle |
| 8511 | var betweenAngles = (angle >= startAngle && angle <= endAngle), |
| 8512 | withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); |
| 8513 | |
| 8514 | return (betweenAngles && withinRadius); |
| 8515 | } else { |
| 8516 | return false; |
| 8517 | } |
| 8518 | }, |
| 8519 | tooltipPosition: function() { |
| 8520 | var vm = this._view; |
| 8521 | |
| 8522 | var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), |
| 8523 | rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; |
| 8524 | return { |
| 8525 | x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), |
| 8526 | y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) |
| 8527 | }; |
| 8528 | }, |
| 8529 | draw: function() { |
| 8530 | |
| 8531 | var ctx = this._chart.ctx, |
| 8532 | vm = this._view, |
| 8533 | sA = vm.startAngle, |
| 8534 | eA = vm.endAngle; |
| 8535 | |
| 8536 | ctx.beginPath(); |
| 8537 | |
| 8538 | ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); |
| 8539 | ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); |
| 8540 | |
| 8541 | ctx.closePath(); |
| 8542 | ctx.strokeStyle = vm.borderColor; |
| 8543 | ctx.lineWidth = vm.borderWidth; |
| 8544 | |
| 8545 | ctx.fillStyle = vm.backgroundColor; |
| 8546 | |
| 8547 | ctx.fill(); |
| 8548 | ctx.lineJoin = 'bevel'; |
| 8549 | |
| 8550 | if (vm.borderWidth) { |
| 8551 | ctx.stroke(); |
| 8552 | } |
| 8553 | } |
| 8554 | }); |
| 8555 | }; |
| 8556 | |
| 8557 | },{}],36:[function(require,module,exports){ |
| 8558 | "use strict"; |
| 8559 | |
| 8560 | module.exports = function(Chart) { |
| 8561 | |
| 8562 | var helpers = Chart.helpers; |
| 8563 | var globalDefaults = Chart.defaults.global; |
| 8564 | |
| 8565 | Chart.defaults.global.elements.line = { |
| 8566 | tension: 0.4, |
| 8567 | backgroundColor: globalDefaults.defaultColor, |
| 8568 | borderWidth: 3, |
| 8569 | borderColor: globalDefaults.defaultColor, |
| 8570 | borderCapStyle: 'butt', |
| 8571 | borderDash: [], |
| 8572 | borderDashOffset: 0.0, |
| 8573 | borderJoinStyle: 'miter', |
| 8574 | capBezierPoints: true, |
| 8575 | fill: true // do we fill in the area between the line and its base axis |
| 8576 | }; |
| 8577 | |
| 8578 | Chart.elements.Line = Chart.Element.extend({ |
| 8579 | draw: function() { |
| 8580 | var me = this; |
| 8581 | var vm = me._view; |
| 8582 | var spanGaps = vm.spanGaps; |
| 8583 | var scaleZero = vm.scaleZero; |
| 8584 | var loop = me._loop; |
| 8585 | |
| 8586 | var ctx = me._chart.ctx; |
| 8587 | ctx.save(); |
| 8588 | |
| 8589 | // Helper function to draw a line to a point |
| 8590 | function lineToPoint(previousPoint, point) { |
| 8591 | var vm = point._view; |
| 8592 | if (point._view.steppedLine === true) { |
| 8593 | ctx.lineTo(point._view.x, previousPoint._view.y); |
| 8594 | ctx.lineTo(point._view.x, point._view.y); |
| 8595 | } else if (point._view.tension === 0) { |
| 8596 | ctx.lineTo(vm.x, vm.y); |
| 8597 | } else { |
| 8598 | ctx.bezierCurveTo( |
| 8599 | previousPoint._view.controlPointNextX, |
| 8600 | previousPoint._view.controlPointNextY, |
| 8601 | vm.controlPointPreviousX, |
| 8602 | vm.controlPointPreviousY, |
| 8603 | vm.x, |
| 8604 | vm.y |
| 8605 | ); |
| 8606 | } |
| 8607 | } |
| 8608 | |
| 8609 | var points = me._children.slice(); // clone array |
| 8610 | var lastDrawnIndex = -1; |
| 8611 | |
| 8612 | // If we are looping, adding the first point again |
| 8613 | if (loop && points.length) { |
| 8614 | points.push(points[0]); |
| 8615 | } |
| 8616 | |
| 8617 | var index, current, previous, currentVM; |
| 8618 | |
| 8619 | // Fill Line |
| 8620 | if (points.length && vm.fill) { |
| 8621 | ctx.beginPath(); |
| 8622 | |
| 8623 | for (index = 0; index < points.length; ++index) { |
| 8624 | current = points[index]; |
| 8625 | previous = helpers.previousItem(points, index); |
| 8626 | currentVM = current._view; |
| 8627 | |
| 8628 | // First point moves to it's starting position no matter what |
| 8629 | if (index === 0) { |
| 8630 | if (loop) { |
| 8631 | ctx.moveTo(scaleZero.x, scaleZero.y); |
| 8632 | } else { |
| 8633 | ctx.moveTo(currentVM.x, scaleZero); |
| 8634 | } |
| 8635 | |
| 8636 | if (!currentVM.skip) { |
| 8637 | lastDrawnIndex = index; |
| 8638 | ctx.lineTo(currentVM.x, currentVM.y); |
| 8639 | } |
| 8640 | } else { |
| 8641 | previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; |
| 8642 | |
| 8643 | if (currentVM.skip) { |
| 8644 | // Only do this if this is the first point that is skipped |
| 8645 | if (!spanGaps && lastDrawnIndex === (index - 1)) { |
| 8646 | if (loop) { |
| 8647 | ctx.lineTo(scaleZero.x, scaleZero.y); |
| 8648 | } else { |
| 8649 | ctx.lineTo(previous._view.x, scaleZero); |
| 8650 | } |
| 8651 | } |
| 8652 | } else { |
| 8653 | if (lastDrawnIndex !== (index - 1)) { |
| 8654 | // There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case. |
| 8655 | // If the first data point is NaN, then there is no real gap to skip |
| 8656 | if (spanGaps && lastDrawnIndex !== -1) { |
| 8657 | // We are spanning the gap, so simple draw a line to this point |
| 8658 | lineToPoint(previous, current); |
| 8659 | } else { |
| 8660 | if (loop) { |
| 8661 | ctx.lineTo(currentVM.x, currentVM.y); |
| 8662 | } else { |
| 8663 | ctx.lineTo(currentVM.x, scaleZero); |
| 8664 | ctx.lineTo(currentVM.x, currentVM.y); |
| 8665 | } |
| 8666 | } |
| 8667 | } else { |
| 8668 | // Line to next point |
| 8669 | lineToPoint(previous, current); |
| 8670 | } |
| 8671 | lastDrawnIndex = index; |
| 8672 | } |
| 8673 | } |
| 8674 | } |
| 8675 | |
| 8676 | if (!loop) { |
| 8677 | ctx.lineTo(points[lastDrawnIndex]._view.x, scaleZero); |
| 8678 | } |
| 8679 | |
| 8680 | ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor; |
| 8681 | ctx.closePath(); |
| 8682 | ctx.fill(); |
| 8683 | } |
| 8684 | |
| 8685 | // Stroke Line Options |
| 8686 | var globalOptionLineElements = globalDefaults.elements.line; |
| 8687 | ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; |
| 8688 | |
| 8689 | // IE 9 and 10 do not support line dash |
| 8690 | if (ctx.setLineDash) { |
| 8691 | ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); |
| 8692 | } |
| 8693 | |
| 8694 | ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; |
| 8695 | ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; |
| 8696 | ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; |
| 8697 | ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; |
| 8698 | |
| 8699 | // Stroke Line |
| 8700 | ctx.beginPath(); |
| 8701 | lastDrawnIndex = -1; |
| 8702 | |
| 8703 | for (index = 0; index < points.length; ++index) { |
| 8704 | current = points[index]; |
| 8705 | previous = helpers.previousItem(points, index); |
| 8706 | currentVM = current._view; |
| 8707 | |
| 8708 | // First point moves to it's starting position no matter what |
| 8709 | if (index === 0) { |
| 8710 | if (currentVM.skip) { |
| 8711 | |
| 8712 | } else { |
| 8713 | ctx.moveTo(currentVM.x, currentVM.y); |
| 8714 | lastDrawnIndex = index; |
| 8715 | } |
| 8716 | } else { |
| 8717 | previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; |
| 8718 | |
| 8719 | if (!currentVM.skip) { |
| 8720 | if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { |
| 8721 | // There was a gap and this is the first point after the gap |
| 8722 | ctx.moveTo(currentVM.x, currentVM.y); |
| 8723 | } else { |
| 8724 | // Line to next point |
| 8725 | lineToPoint(previous, current); |
| 8726 | } |
| 8727 | lastDrawnIndex = index; |
| 8728 | } |
| 8729 | } |
| 8730 | } |
| 8731 | |
| 8732 | ctx.stroke(); |
| 8733 | ctx.restore(); |
| 8734 | } |
| 8735 | }); |
| 8736 | }; |
| 8737 | },{}],37:[function(require,module,exports){ |
| 8738 | "use strict"; |
| 8739 | |
| 8740 | module.exports = function(Chart) { |
| 8741 | |
| 8742 | var helpers = Chart.helpers, |
| 8743 | globalOpts = Chart.defaults.global, |
| 8744 | defaultColor = globalOpts.defaultColor; |
| 8745 | |
| 8746 | globalOpts.elements.point = { |
| 8747 | radius: 3, |
| 8748 | pointStyle: 'circle', |
| 8749 | backgroundColor: defaultColor, |
| 8750 | borderWidth: 1, |
| 8751 | borderColor: defaultColor, |
| 8752 | // Hover |
| 8753 | hitRadius: 1, |
| 8754 | hoverRadius: 4, |
| 8755 | hoverBorderWidth: 1 |
| 8756 | }; |
| 8757 | |
| 8758 | Chart.elements.Point = Chart.Element.extend({ |
| 8759 | inRange: function(mouseX, mouseY) { |
| 8760 | var vm = this._view; |
| 8761 | return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; |
| 8762 | }, |
| 8763 | inLabelRange: function(mouseX) { |
| 8764 | var vm = this._view; |
| 8765 | return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; |
| 8766 | }, |
| 8767 | tooltipPosition: function() { |
| 8768 | var vm = this._view; |
| 8769 | return { |
| 8770 | x: vm.x, |
| 8771 | y: vm.y, |
| 8772 | padding: vm.radius + vm.borderWidth |
| 8773 | }; |
| 8774 | }, |
| 8775 | draw: function() { |
| 8776 | var vm = this._view; |
| 8777 | var ctx = this._chart.ctx; |
| 8778 | var pointStyle = vm.pointStyle; |
| 8779 | var radius = vm.radius; |
| 8780 | var x = vm.x; |
| 8781 | var y = vm.y; |
| 8782 | |
| 8783 | if (vm.skip) { |
| 8784 | return; |
| 8785 | } |
| 8786 | |
| 8787 | ctx.strokeStyle = vm.borderColor || defaultColor; |
| 8788 | ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth); |
| 8789 | ctx.fillStyle = vm.backgroundColor || defaultColor; |
| 8790 | |
| 8791 | Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y); |
| 8792 | } |
| 8793 | }); |
| 8794 | }; |
| 8795 | |
| 8796 | },{}],38:[function(require,module,exports){ |
| 8797 | "use strict"; |
| 8798 | |
| 8799 | module.exports = function(Chart) { |
| 8800 | |
| 8801 | var globalOpts = Chart.defaults.global; |
| 8802 | |
| 8803 | globalOpts.elements.rectangle = { |
| 8804 | backgroundColor: globalOpts.defaultColor, |
| 8805 | borderWidth: 0, |
| 8806 | borderColor: globalOpts.defaultColor, |
| 8807 | borderSkipped: 'bottom' |
| 8808 | }; |
| 8809 | |
| 8810 | Chart.elements.Rectangle = Chart.Element.extend({ |
| 8811 | draw: function() { |
| 8812 | var ctx = this._chart.ctx; |
| 8813 | var vm = this._view; |
| 8814 | |
| 8815 | var halfWidth = vm.width / 2, |
| 8816 | leftX = vm.x - halfWidth, |
| 8817 | rightX = vm.x + halfWidth, |
| 8818 | top = vm.base - (vm.base - vm.y), |
| 8819 | halfStroke = vm.borderWidth / 2; |
| 8820 | |
| 8821 | // Canvas doesn't allow us to stroke inside the width so we can |
| 8822 | // adjust the sizes to fit if we're setting a stroke on the line |
| 8823 | if (vm.borderWidth) { |
| 8824 | leftX += halfStroke; |
| 8825 | rightX -= halfStroke; |
| 8826 | top += halfStroke; |
| 8827 | } |
| 8828 | |
| 8829 | ctx.beginPath(); |
| 8830 | ctx.fillStyle = vm.backgroundColor; |
| 8831 | ctx.strokeStyle = vm.borderColor; |
| 8832 | ctx.lineWidth = vm.borderWidth; |
| 8833 | |
| 8834 | // Corner points, from bottom-left to bottom-right clockwise |
| 8835 | // | 1 2 | |
| 8836 | // | 0 3 | |
| 8837 | var corners = [ |
| 8838 | [leftX, vm.base], |
| 8839 | [leftX, top], |
| 8840 | [rightX, top], |
| 8841 | [rightX, vm.base] |
| 8842 | ]; |
| 8843 | |
| 8844 | // Find first (starting) corner with fallback to 'bottom' |
| 8845 | var borders = ['bottom', 'left', 'top', 'right']; |
| 8846 | var startCorner = borders.indexOf(vm.borderSkipped, 0); |
| 8847 | if (startCorner === -1) |
| 8848 | startCorner = 0; |
| 8849 | |
| 8850 | function cornerAt(index) { |
| 8851 | return corners[(startCorner + index) % 4]; |
| 8852 | } |
| 8853 | |
| 8854 | // Draw rectangle from 'startCorner' |
| 8855 | ctx.moveTo.apply(ctx, cornerAt(0)); |
| 8856 | for (var i = 1; i < 4; i++) |
| 8857 | ctx.lineTo.apply(ctx, cornerAt(i)); |
| 8858 | |
| 8859 | ctx.fill(); |
| 8860 | if (vm.borderWidth) { |
| 8861 | ctx.stroke(); |
| 8862 | } |
| 8863 | }, |
| 8864 | height: function() { |
| 8865 | var vm = this._view; |
| 8866 | return vm.base - vm.y; |
| 8867 | }, |
| 8868 | inRange: function(mouseX, mouseY) { |
| 8869 | var vm = this._view; |
| 8870 | return vm ? |
| 8871 | (vm.y < vm.base ? |
| 8872 | (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base) : |
| 8873 | (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y)) : |
| 8874 | false; |
| 8875 | }, |
| 8876 | inLabelRange: function(mouseX) { |
| 8877 | var vm = this._view; |
| 8878 | return vm ? (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) : false; |
| 8879 | }, |
| 8880 | tooltipPosition: function() { |
| 8881 | var vm = this._view; |
| 8882 | return { |
| 8883 | x: vm.x, |
| 8884 | y: vm.y |
| 8885 | }; |
| 8886 | } |
| 8887 | }); |
| 8888 | |
| 8889 | }; |
| 8890 | },{}],39:[function(require,module,exports){ |
| 8891 | "use strict"; |
| 8892 | |
| 8893 | module.exports = function(Chart) { |
| 8894 | |
| 8895 | var helpers = Chart.helpers; |
| 8896 | // Default config for a category scale |
| 8897 | var defaultConfig = { |
| 8898 | position: "bottom" |
| 8899 | }; |
| 8900 | |
| 8901 | var DatasetScale = Chart.Scale.extend({ |
| 8902 | /** |
| 8903 | * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use tose |
| 8904 | * else fall back to data.labels |
| 8905 | * @private |
| 8906 | */ |
| 8907 | getLabels: function() { |
| 8908 | var data = this.chart.data; |
| 8909 | return (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; |
| 8910 | }, |
| 8911 | // Implement this so that |
| 8912 | determineDataLimits: function() { |
| 8913 | var me = this; |
| 8914 | var labels = me.getLabels(); |
| 8915 | me.minIndex = 0; |
| 8916 | me.maxIndex = labels.length - 1; |
| 8917 | var findIndex; |
| 8918 | |
| 8919 | if (me.options.ticks.min !== undefined) { |
| 8920 | // user specified min value |
| 8921 | findIndex = helpers.indexOf(labels, me.options.ticks.min); |
| 8922 | me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; |
| 8923 | } |
| 8924 | |
| 8925 | if (me.options.ticks.max !== undefined) { |
| 8926 | // user specified max value |
| 8927 | findIndex = helpers.indexOf(labels, me.options.ticks.max); |
| 8928 | me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; |
| 8929 | } |
| 8930 | |
| 8931 | me.min = labels[me.minIndex]; |
| 8932 | me.max = labels[me.maxIndex]; |
| 8933 | }, |
| 8934 | |
| 8935 | buildTicks: function() { |
| 8936 | var me = this; |
| 8937 | var labels = me.getLabels(); |
| 8938 | // If we are viewing some subset of labels, slice the original array |
| 8939 | me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); |
| 8940 | }, |
| 8941 | |
| 8942 | getLabelForIndex: function(index) { |
| 8943 | return this.ticks[index]; |
| 8944 | }, |
| 8945 | |
| 8946 | // Used to get data value locations. Value can either be an index or a numerical value |
| 8947 | getPixelForValue: function(value, index, datasetIndex, includeOffset) { |
| 8948 | var me = this; |
| 8949 | // 1 is added because we need the length but we have the indexes |
| 8950 | var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); |
| 8951 | |
| 8952 | if (value !== undefined) { |
| 8953 | var labels = me.getLabels(); |
| 8954 | var idx = labels.indexOf(value); |
| 8955 | index = idx !== -1 ? idx : index; |
| 8956 | } |
| 8957 | |
| 8958 | if (me.isHorizontal()) { |
| 8959 | var innerWidth = me.width - (me.paddingLeft + me.paddingRight); |
| 8960 | var valueWidth = innerWidth / offsetAmt; |
| 8961 | var widthOffset = (valueWidth * (index - me.minIndex)) + me.paddingLeft; |
| 8962 | |
| 8963 | if (me.options.gridLines.offsetGridLines && includeOffset) { |
| 8964 | widthOffset += (valueWidth / 2); |
| 8965 | } |
| 8966 | |
| 8967 | return me.left + Math.round(widthOffset); |
| 8968 | } else { |
| 8969 | var innerHeight = me.height - (me.paddingTop + me.paddingBottom); |
| 8970 | var valueHeight = innerHeight / offsetAmt; |
| 8971 | var heightOffset = (valueHeight * (index - me.minIndex)) + me.paddingTop; |
| 8972 | |
| 8973 | if (me.options.gridLines.offsetGridLines && includeOffset) { |
| 8974 | heightOffset += (valueHeight / 2); |
| 8975 | } |
| 8976 | |
| 8977 | return me.top + Math.round(heightOffset); |
| 8978 | } |
| 8979 | }, |
| 8980 | getPixelForTick: function(index, includeOffset) { |
| 8981 | return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset); |
| 8982 | }, |
| 8983 | getValueForPixel: function(pixel) { |
| 8984 | var me = this; |
| 8985 | var value; |
| 8986 | var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); |
| 8987 | var horz = me.isHorizontal(); |
| 8988 | var innerDimension = horz ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); |
| 8989 | var valueDimension = innerDimension / offsetAmt; |
| 8990 | |
| 8991 | pixel -= horz ? me.left : me.top; |
| 8992 | |
| 8993 | if (me.options.gridLines.offsetGridLines) { |
| 8994 | pixel -= (valueDimension / 2); |
| 8995 | } |
| 8996 | pixel -= horz ? me.paddingLeft : me.paddingTop; |
| 8997 | |
| 8998 | if (pixel <= 0) { |
| 8999 | value = 0; |
| 9000 | } else { |
| 9001 | value = Math.round(pixel / valueDimension); |
| 9002 | } |
| 9003 | |
| 9004 | return value; |
| 9005 | }, |
| 9006 | getBasePixel: function() { |
| 9007 | return this.bottom; |
| 9008 | } |
| 9009 | }); |
| 9010 | |
| 9011 | Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig); |
| 9012 | |
| 9013 | }; |
| 9014 | },{}],40:[function(require,module,exports){ |
| 9015 | "use strict"; |
| 9016 | |
| 9017 | module.exports = function(Chart) { |
| 9018 | |
| 9019 | var helpers = Chart.helpers; |
| 9020 | |
| 9021 | var defaultConfig = { |
| 9022 | position: "left", |
| 9023 | ticks: { |
| 9024 | callback: function(tickValue, index, ticks) { |
| 9025 | // If we have lots of ticks, don't use the ones |
| 9026 | var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; |
| 9027 | |
| 9028 | // If we have a number like 2.5 as the delta, figure out how many decimal places we need |
| 9029 | if (Math.abs(delta) > 1) { |
| 9030 | if (tickValue !== Math.floor(tickValue)) { |
| 9031 | // not an integer |
| 9032 | delta = tickValue - Math.floor(tickValue); |
| 9033 | } |
| 9034 | } |
| 9035 | |
| 9036 | var logDelta = helpers.log10(Math.abs(delta)); |
| 9037 | var tickString = ''; |
| 9038 | |
| 9039 | if (tickValue !== 0) { |
| 9040 | var numDecimal = -1 * Math.floor(logDelta); |
| 9041 | numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places |
| 9042 | tickString = tickValue.toFixed(numDecimal); |
| 9043 | } else { |
| 9044 | tickString = '0'; // never show decimal places for 0 |
| 9045 | } |
| 9046 | |
| 9047 | return tickString; |
| 9048 | } |
| 9049 | } |
| 9050 | }; |
| 9051 | |
| 9052 | var LinearScale = Chart.LinearScaleBase.extend({ |
| 9053 | determineDataLimits: function() { |
| 9054 | var me = this; |
| 9055 | var opts = me.options; |
| 9056 | var chart = me.chart; |
| 9057 | var data = chart.data; |
| 9058 | var datasets = data.datasets; |
| 9059 | var isHorizontal = me.isHorizontal(); |
| 9060 | |
| 9061 | function IDMatches(meta) { |
| 9062 | return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; |
| 9063 | } |
| 9064 | |
| 9065 | // First Calculate the range |
| 9066 | me.min = null; |
| 9067 | me.max = null; |
| 9068 | |
| 9069 | if (opts.stacked) { |
| 9070 | var valuesPerType = {}; |
| 9071 | var hasPositiveValues = false; |
| 9072 | var hasNegativeValues = false; |
| 9073 | |
| 9074 | helpers.each(datasets, function(dataset, datasetIndex) { |
| 9075 | var meta = chart.getDatasetMeta(datasetIndex); |
| 9076 | if (valuesPerType[meta.type] === undefined) { |
| 9077 | valuesPerType[meta.type] = { |
| 9078 | positiveValues: [], |
| 9079 | negativeValues: [] |
| 9080 | }; |
| 9081 | } |
| 9082 | |
| 9083 | // Store these per type |
| 9084 | var positiveValues = valuesPerType[meta.type].positiveValues; |
| 9085 | var negativeValues = valuesPerType[meta.type].negativeValues; |
| 9086 | |
| 9087 | if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { |
| 9088 | helpers.each(dataset.data, function(rawValue, index) { |
| 9089 | var value = +me.getRightValue(rawValue); |
| 9090 | if (isNaN(value) || meta.data[index].hidden) { |
| 9091 | return; |
| 9092 | } |
| 9093 | |
| 9094 | positiveValues[index] = positiveValues[index] || 0; |
| 9095 | negativeValues[index] = negativeValues[index] || 0; |
| 9096 | |
| 9097 | if (opts.relativePoints) { |
| 9098 | positiveValues[index] = 100; |
| 9099 | } else { |
| 9100 | if (value < 0) { |
| 9101 | hasNegativeValues = true; |
| 9102 | negativeValues[index] += value; |
| 9103 | } else { |
| 9104 | hasPositiveValues = true; |
| 9105 | positiveValues[index] += value; |
| 9106 | } |
| 9107 | } |
| 9108 | }); |
| 9109 | } |
| 9110 | }); |
| 9111 | |
| 9112 | helpers.each(valuesPerType, function(valuesForType) { |
| 9113 | var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); |
| 9114 | var minVal = helpers.min(values); |
| 9115 | var maxVal = helpers.max(values); |
| 9116 | me.min = me.min === null ? minVal : Math.min(me.min, minVal); |
| 9117 | me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); |
| 9118 | }); |
| 9119 | |
| 9120 | } else { |
| 9121 | helpers.each(datasets, function(dataset, datasetIndex) { |
| 9122 | var meta = chart.getDatasetMeta(datasetIndex); |
| 9123 | if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { |
| 9124 | helpers.each(dataset.data, function(rawValue, index) { |
| 9125 | var value = +me.getRightValue(rawValue); |
| 9126 | if (isNaN(value) || meta.data[index].hidden) { |
| 9127 | return; |
| 9128 | } |
| 9129 | |
| 9130 | if (me.min === null) { |
| 9131 | me.min = value; |
| 9132 | } else if (value < me.min) { |
| 9133 | me.min = value; |
| 9134 | } |
| 9135 | |
| 9136 | if (me.max === null) { |
| 9137 | me.max = value; |
| 9138 | } else if (value > me.max) { |
| 9139 | me.max = value; |
| 9140 | } |
| 9141 | }); |
| 9142 | } |
| 9143 | }); |
| 9144 | } |
| 9145 | |
| 9146 | // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero |
| 9147 | this.handleTickRangeOptions(); |
| 9148 | }, |
| 9149 | getTickLimit: function() { |
| 9150 | var maxTicks; |
| 9151 | var me = this; |
| 9152 | var tickOpts = me.options.ticks; |
| 9153 | |
| 9154 | if (me.isHorizontal()) { |
| 9155 | maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); |
| 9156 | } else { |
| 9157 | // The factor of 2 used to scale the font size has been experimentally determined. |
| 9158 | var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize); |
| 9159 | maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); |
| 9160 | } |
| 9161 | |
| 9162 | return maxTicks; |
| 9163 | }, |
| 9164 | // Called after the ticks are built. We need |
| 9165 | handleDirectionalChanges: function() { |
| 9166 | if (!this.isHorizontal()) { |
| 9167 | // We are in a vertical orientation. The top value is the highest. So reverse the array |
| 9168 | this.ticks.reverse(); |
| 9169 | } |
| 9170 | }, |
| 9171 | getLabelForIndex: function(index, datasetIndex) { |
| 9172 | return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); |
| 9173 | }, |
| 9174 | // Utils |
| 9175 | getPixelForValue: function(value) { |
| 9176 | // This must be called after fit has been run so that |
| 9177 | // this.left, this.top, this.right, and this.bottom have been defined |
| 9178 | var me = this; |
| 9179 | var paddingLeft = me.paddingLeft; |
| 9180 | var paddingBottom = me.paddingBottom; |
| 9181 | var start = me.start; |
| 9182 | |
| 9183 | var rightValue = +me.getRightValue(value); |
| 9184 | var pixel; |
| 9185 | var innerDimension; |
| 9186 | var range = me.end - start; |
| 9187 | |
| 9188 | if (me.isHorizontal()) { |
| 9189 | innerDimension = me.width - (paddingLeft + me.paddingRight); |
| 9190 | pixel = me.left + (innerDimension / range * (rightValue - start)); |
| 9191 | return Math.round(pixel + paddingLeft); |
| 9192 | } else { |
| 9193 | innerDimension = me.height - (me.paddingTop + paddingBottom); |
| 9194 | pixel = (me.bottom - paddingBottom) - (innerDimension / range * (rightValue - start)); |
| 9195 | return Math.round(pixel); |
| 9196 | } |
| 9197 | }, |
| 9198 | getValueForPixel: function(pixel) { |
| 9199 | var me = this; |
| 9200 | var isHorizontal = me.isHorizontal(); |
| 9201 | var paddingLeft = me.paddingLeft; |
| 9202 | var paddingBottom = me.paddingBottom; |
| 9203 | var innerDimension = isHorizontal ? me.width - (paddingLeft + me.paddingRight) : me.height - (me.paddingTop + paddingBottom); |
| 9204 | var offset = (isHorizontal ? pixel - me.left - paddingLeft : me.bottom - paddingBottom - pixel) / innerDimension; |
| 9205 | return me.start + ((me.end - me.start) * offset); |
| 9206 | }, |
| 9207 | getPixelForTick: function(index) { |
| 9208 | return this.getPixelForValue(this.ticksAsNumbers[index]); |
| 9209 | } |
| 9210 | }); |
| 9211 | Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig); |
| 9212 | |
| 9213 | }; |
| 9214 | },{}],41:[function(require,module,exports){ |
| 9215 | "use strict"; |
| 9216 | |
| 9217 | module.exports = function(Chart) { |
| 9218 | |
| 9219 | var helpers = Chart.helpers, |
| 9220 | noop = helpers.noop; |
| 9221 | |
| 9222 | Chart.LinearScaleBase = Chart.Scale.extend({ |
| 9223 | handleTickRangeOptions: function() { |
| 9224 | var me = this; |
| 9225 | var opts = me.options; |
| 9226 | var tickOpts = opts.ticks; |
| 9227 | |
| 9228 | // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, |
| 9229 | // do nothing since that would make the chart weird. If the user really wants a weird chart |
| 9230 | // axis, they can manually override it |
| 9231 | if (tickOpts.beginAtZero) { |
| 9232 | var minSign = helpers.sign(me.min); |
| 9233 | var maxSign = helpers.sign(me.max); |
| 9234 | |
| 9235 | if (minSign < 0 && maxSign < 0) { |
| 9236 | // move the top up to 0 |
| 9237 | me.max = 0; |
| 9238 | } else if (minSign > 0 && maxSign > 0) { |
| 9239 | // move the botttom down to 0 |
| 9240 | me.min = 0; |
| 9241 | } |
| 9242 | } |
| 9243 | |
| 9244 | if (tickOpts.min !== undefined) { |
| 9245 | me.min = tickOpts.min; |
| 9246 | } else if (tickOpts.suggestedMin !== undefined) { |
| 9247 | me.min = Math.min(me.min, tickOpts.suggestedMin); |
| 9248 | } |
| 9249 | |
| 9250 | if (tickOpts.max !== undefined) { |
| 9251 | me.max = tickOpts.max; |
| 9252 | } else if (tickOpts.suggestedMax !== undefined) { |
| 9253 | me.max = Math.max(me.max, tickOpts.suggestedMax); |
| 9254 | } |
| 9255 | |
| 9256 | if (me.min === me.max) { |
| 9257 | me.max++; |
| 9258 | |
| 9259 | if (!tickOpts.beginAtZero) { |
| 9260 | me.min--; |
| 9261 | } |
| 9262 | } |
| 9263 | }, |
| 9264 | getTickLimit: noop, |
| 9265 | handleDirectionalChanges: noop, |
| 9266 | |
| 9267 | buildTicks: function() { |
| 9268 | var me = this; |
| 9269 | var opts = me.options; |
| 9270 | var ticks = me.ticks = []; |
| 9271 | var tickOpts = opts.ticks; |
| 9272 | var getValueOrDefault = helpers.getValueOrDefault; |
| 9273 | |
| 9274 | // Figure out what the max number of ticks we can support it is based on the size of |
| 9275 | // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 |
| 9276 | // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on |
| 9277 | // the graph |
| 9278 | |
| 9279 | var maxTicks = me.getTickLimit(); |
| 9280 | |
| 9281 | // Make sure we always have at least 2 ticks |
| 9282 | maxTicks = Math.max(2, maxTicks); |
| 9283 | |
| 9284 | // To get a "nice" value for the tick spacing, we will use the appropriately named |
| 9285 | // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks |
| 9286 | // for details. |
| 9287 | |
| 9288 | var spacing; |
| 9289 | var fixedStepSizeSet = (tickOpts.fixedStepSize && tickOpts.fixedStepSize > 0) || (tickOpts.stepSize && tickOpts.stepSize > 0); |
| 9290 | if (fixedStepSizeSet) { |
| 9291 | spacing = getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize); |
| 9292 | } else { |
| 9293 | var niceRange = helpers.niceNum(me.max - me.min, false); |
| 9294 | spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); |
| 9295 | } |
| 9296 | var niceMin = Math.floor(me.min / spacing) * spacing; |
| 9297 | var niceMax = Math.ceil(me.max / spacing) * spacing; |
| 9298 | var numSpaces = (niceMax - niceMin) / spacing; |
| 9299 | |
| 9300 | // If very close to our rounded value, use it. |
| 9301 | if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { |
| 9302 | numSpaces = Math.round(numSpaces); |
| 9303 | } else { |
| 9304 | numSpaces = Math.ceil(numSpaces); |
| 9305 | } |
| 9306 | |
| 9307 | // Put the values into the ticks array |
| 9308 | ticks.push(tickOpts.min !== undefined ? tickOpts.min : niceMin); |
| 9309 | for (var j = 1; j < numSpaces; ++j) { |
| 9310 | ticks.push(niceMin + (j * spacing)); |
| 9311 | } |
| 9312 | ticks.push(tickOpts.max !== undefined ? tickOpts.max : niceMax); |
| 9313 | |
| 9314 | me.handleDirectionalChanges(); |
| 9315 | |
| 9316 | // At this point, we need to update our max and min given the tick values since we have expanded the |
| 9317 | // range of the scale |
| 9318 | me.max = helpers.max(ticks); |
| 9319 | me.min = helpers.min(ticks); |
| 9320 | |
| 9321 | if (tickOpts.reverse) { |
| 9322 | ticks.reverse(); |
| 9323 | |
| 9324 | me.start = me.max; |
| 9325 | me.end = me.min; |
| 9326 | } else { |
| 9327 | me.start = me.min; |
| 9328 | me.end = me.max; |
| 9329 | } |
| 9330 | }, |
| 9331 | convertTicksToLabels: function() { |
| 9332 | var me = this; |
| 9333 | me.ticksAsNumbers = me.ticks.slice(); |
| 9334 | me.zeroLineIndex = me.ticks.indexOf(0); |
| 9335 | |
| 9336 | Chart.Scale.prototype.convertTicksToLabels.call(me); |
| 9337 | }, |
| 9338 | }); |
| 9339 | }; |
| 9340 | },{}],42:[function(require,module,exports){ |
| 9341 | "use strict"; |
| 9342 | |
| 9343 | module.exports = function(Chart) { |
| 9344 | |
| 9345 | var helpers = Chart.helpers; |
| 9346 | |
| 9347 | var defaultConfig = { |
| 9348 | position: "left", |
| 9349 | |
| 9350 | // label settings |
| 9351 | ticks: { |
| 9352 | callback: function(value, index, arr) { |
| 9353 | var remain = value / (Math.pow(10, Math.floor(helpers.log10(value)))); |
| 9354 | |
| 9355 | if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) { |
| 9356 | return value.toExponential(); |
| 9357 | } else { |
| 9358 | return ''; |
| 9359 | } |
| 9360 | } |
| 9361 | } |
| 9362 | }; |
| 9363 | |
| 9364 | var LogarithmicScale = Chart.Scale.extend({ |
| 9365 | determineDataLimits: function() { |
| 9366 | var me = this; |
| 9367 | var opts = me.options; |
| 9368 | var tickOpts = opts.ticks; |
| 9369 | var chart = me.chart; |
| 9370 | var data = chart.data; |
| 9371 | var datasets = data.datasets; |
| 9372 | var getValueOrDefault = helpers.getValueOrDefault; |
| 9373 | var isHorizontal = me.isHorizontal(); |
| 9374 | function IDMatches(meta) { |
| 9375 | return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; |
| 9376 | } |
| 9377 | |
| 9378 | // Calculate Range |
| 9379 | me.min = null; |
| 9380 | me.max = null; |
| 9381 | |
| 9382 | if (opts.stacked) { |
| 9383 | var valuesPerType = {}; |
| 9384 | |
| 9385 | helpers.each(datasets, function(dataset, datasetIndex) { |
| 9386 | var meta = chart.getDatasetMeta(datasetIndex); |
| 9387 | if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { |
| 9388 | if (valuesPerType[meta.type] === undefined) { |
| 9389 | valuesPerType[meta.type] = []; |
| 9390 | } |
| 9391 | |
| 9392 | helpers.each(dataset.data, function(rawValue, index) { |
| 9393 | var values = valuesPerType[meta.type]; |
| 9394 | var value = +me.getRightValue(rawValue); |
| 9395 | if (isNaN(value) || meta.data[index].hidden) { |
| 9396 | return; |
| 9397 | } |
| 9398 | |
| 9399 | values[index] = values[index] || 0; |
| 9400 | |
| 9401 | if (opts.relativePoints) { |
| 9402 | values[index] = 100; |
| 9403 | } else { |
| 9404 | // Don't need to split positive and negative since the log scale can't handle a 0 crossing |
| 9405 | values[index] += value; |
| 9406 | } |
| 9407 | }); |
| 9408 | } |
| 9409 | }); |
| 9410 | |
| 9411 | helpers.each(valuesPerType, function(valuesForType) { |
| 9412 | var minVal = helpers.min(valuesForType); |
| 9413 | var maxVal = helpers.max(valuesForType); |
| 9414 | me.min = me.min === null ? minVal : Math.min(me.min, minVal); |
| 9415 | me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); |
| 9416 | }); |
| 9417 | |
| 9418 | } else { |
| 9419 | helpers.each(datasets, function(dataset, datasetIndex) { |
| 9420 | var meta = chart.getDatasetMeta(datasetIndex); |
| 9421 | if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { |
| 9422 | helpers.each(dataset.data, function(rawValue, index) { |
| 9423 | var value = +me.getRightValue(rawValue); |
| 9424 | if (isNaN(value) || meta.data[index].hidden) { |
| 9425 | return; |
| 9426 | } |
| 9427 | |
| 9428 | if (me.min === null) { |
| 9429 | me.min = value; |
| 9430 | } else if (value < me.min) { |
| 9431 | me.min = value; |
| 9432 | } |
| 9433 | |
| 9434 | if (me.max === null) { |
| 9435 | me.max = value; |
| 9436 | } else if (value > me.max) { |
| 9437 | me.max = value; |
| 9438 | } |
| 9439 | }); |
| 9440 | } |
| 9441 | }); |
| 9442 | } |
| 9443 | |
| 9444 | me.min = getValueOrDefault(tickOpts.min, me.min); |
| 9445 | me.max = getValueOrDefault(tickOpts.max, me.max); |
| 9446 | |
| 9447 | if (me.min === me.max) { |
| 9448 | if (me.min !== 0 && me.min !== null) { |
| 9449 | me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); |
| 9450 | me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); |
| 9451 | } else { |
| 9452 | me.min = 1; |
| 9453 | me.max = 10; |
| 9454 | } |
| 9455 | } |
| 9456 | }, |
| 9457 | buildTicks: function() { |
| 9458 | var me = this; |
| 9459 | var opts = me.options; |
| 9460 | var tickOpts = opts.ticks; |
| 9461 | var getValueOrDefault = helpers.getValueOrDefault; |
| 9462 | |
| 9463 | // Reset the ticks array. Later on, we will draw a grid line at these positions |
| 9464 | // The array simply contains the numerical value of the spots where ticks will be |
| 9465 | var ticks = me.ticks = []; |
| 9466 | |
| 9467 | // Figure out what the max number of ticks we can support it is based on the size of |
| 9468 | // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 |
| 9469 | // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on |
| 9470 | // the graph |
| 9471 | |
| 9472 | var tickVal = getValueOrDefault(tickOpts.min, Math.pow(10, Math.floor(helpers.log10(me.min)))); |
| 9473 | |
| 9474 | while (tickVal < me.max) { |
| 9475 | ticks.push(tickVal); |
| 9476 | |
| 9477 | var exp = Math.floor(helpers.log10(tickVal)); |
| 9478 | var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1; |
| 9479 | |
| 9480 | if (significand === 10) { |
| 9481 | significand = 1; |
| 9482 | ++exp; |
| 9483 | } |
| 9484 | |
| 9485 | tickVal = significand * Math.pow(10, exp); |
| 9486 | } |
| 9487 | |
| 9488 | var lastTick = getValueOrDefault(tickOpts.max, tickVal); |
| 9489 | ticks.push(lastTick); |
| 9490 | |
| 9491 | if (!me.isHorizontal()) { |
| 9492 | // We are in a vertical orientation. The top value is the highest. So reverse the array |
| 9493 | ticks.reverse(); |
| 9494 | } |
| 9495 | |
| 9496 | // At this point, we need to update our max and min given the tick values since we have expanded the |
| 9497 | // range of the scale |
| 9498 | me.max = helpers.max(ticks); |
| 9499 | me.min = helpers.min(ticks); |
| 9500 | |
| 9501 | if (tickOpts.reverse) { |
| 9502 | ticks.reverse(); |
| 9503 | |
| 9504 | me.start = me.max; |
| 9505 | me.end = me.min; |
| 9506 | } else { |
| 9507 | me.start = me.min; |
| 9508 | me.end = me.max; |
| 9509 | } |
| 9510 | }, |
| 9511 | convertTicksToLabels: function() { |
| 9512 | this.tickValues = this.ticks.slice(); |
| 9513 | |
| 9514 | Chart.Scale.prototype.convertTicksToLabels.call(this); |
| 9515 | }, |
| 9516 | // Get the correct tooltip label |
| 9517 | getLabelForIndex: function(index, datasetIndex) { |
| 9518 | return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); |
| 9519 | }, |
| 9520 | getPixelForTick: function(index) { |
| 9521 | return this.getPixelForValue(this.tickValues[index]); |
| 9522 | }, |
| 9523 | getPixelForValue: function(value) { |
| 9524 | var me = this; |
| 9525 | var innerDimension; |
| 9526 | var pixel; |
| 9527 | |
| 9528 | var start = me.start; |
| 9529 | var newVal = +me.getRightValue(value); |
| 9530 | var range = helpers.log10(me.end) - helpers.log10(start); |
| 9531 | var paddingTop = me.paddingTop; |
| 9532 | var paddingBottom = me.paddingBottom; |
| 9533 | var paddingLeft = me.paddingLeft; |
| 9534 | |
| 9535 | if (me.isHorizontal()) { |
| 9536 | |
| 9537 | if (newVal === 0) { |
| 9538 | pixel = me.left + paddingLeft; |
| 9539 | } else { |
| 9540 | innerDimension = me.width - (paddingLeft + me.paddingRight); |
| 9541 | pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); |
| 9542 | pixel += paddingLeft; |
| 9543 | } |
| 9544 | } else { |
| 9545 | // Bottom - top since pixels increase downard on a screen |
| 9546 | if (newVal === 0) { |
| 9547 | pixel = me.top + paddingTop; |
| 9548 | } else { |
| 9549 | innerDimension = me.height - (paddingTop + paddingBottom); |
| 9550 | pixel = (me.bottom - paddingBottom) - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); |
| 9551 | } |
| 9552 | } |
| 9553 | |
| 9554 | return pixel; |
| 9555 | }, |
| 9556 | getValueForPixel: function(pixel) { |
| 9557 | var me = this; |
| 9558 | var range = helpers.log10(me.end) - helpers.log10(me.start); |
| 9559 | var value, innerDimension; |
| 9560 | |
| 9561 | if (me.isHorizontal()) { |
| 9562 | innerDimension = me.width - (me.paddingLeft + me.paddingRight); |
| 9563 | value = me.start * Math.pow(10, (pixel - me.left - me.paddingLeft) * range / innerDimension); |
| 9564 | } else { |
| 9565 | innerDimension = me.height - (me.paddingTop + me.paddingBottom); |
| 9566 | value = Math.pow(10, (me.bottom - me.paddingBottom - pixel) * range / innerDimension) / me.start; |
| 9567 | } |
| 9568 | |
| 9569 | return value; |
| 9570 | } |
| 9571 | }); |
| 9572 | Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig); |
| 9573 | |
| 9574 | }; |
| 9575 | },{}],43:[function(require,module,exports){ |
| 9576 | "use strict"; |
| 9577 | |
| 9578 | module.exports = function(Chart) { |
| 9579 | |
| 9580 | var helpers = Chart.helpers; |
| 9581 | var globalDefaults = Chart.defaults.global; |
| 9582 | |
| 9583 | var defaultConfig = { |
| 9584 | display: true, |
| 9585 | |
| 9586 | //Boolean - Whether to animate scaling the chart from the centre |
| 9587 | animate: true, |
| 9588 | lineArc: false, |
| 9589 | position: "chartArea", |
| 9590 | |
| 9591 | angleLines: { |
| 9592 | display: true, |
| 9593 | color: "rgba(0, 0, 0, 0.1)", |
| 9594 | lineWidth: 1 |
| 9595 | }, |
| 9596 | |
| 9597 | // label settings |
| 9598 | ticks: { |
| 9599 | //Boolean - Show a backdrop to the scale label |
| 9600 | showLabelBackdrop: true, |
| 9601 | |
| 9602 | //String - The colour of the label backdrop |
| 9603 | backdropColor: "rgba(255,255,255,0.75)", |
| 9604 | |
| 9605 | //Number - The backdrop padding above & below the label in pixels |
| 9606 | backdropPaddingY: 2, |
| 9607 | |
| 9608 | //Number - The backdrop padding to the side of the label in pixels |
| 9609 | backdropPaddingX: 2 |
| 9610 | }, |
| 9611 | |
| 9612 | pointLabels: { |
| 9613 | //Number - Point label font size in pixels |
| 9614 | fontSize: 10, |
| 9615 | |
| 9616 | //Function - Used to convert point labels |
| 9617 | callback: function(label) { |
| 9618 | return label; |
| 9619 | } |
| 9620 | } |
| 9621 | }; |
| 9622 | |
| 9623 | var LinearRadialScale = Chart.LinearScaleBase.extend({ |
| 9624 | getValueCount: function() { |
| 9625 | return this.chart.data.labels.length; |
| 9626 | }, |
| 9627 | setDimensions: function() { |
| 9628 | var me = this; |
| 9629 | var opts = me.options; |
| 9630 | var tickOpts = opts.ticks; |
| 9631 | // Set the unconstrained dimension before label rotation |
| 9632 | me.width = me.maxWidth; |
| 9633 | me.height = me.maxHeight; |
| 9634 | me.xCenter = Math.round(me.width / 2); |
| 9635 | me.yCenter = Math.round(me.height / 2); |
| 9636 | |
| 9637 | var minSize = helpers.min([me.height, me.width]); |
| 9638 | var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); |
| 9639 | me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); |
| 9640 | }, |
| 9641 | determineDataLimits: function() { |
| 9642 | var me = this; |
| 9643 | var chart = me.chart; |
| 9644 | me.min = null; |
| 9645 | me.max = null; |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 9646 | |
| 9647 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 9648 | helpers.each(chart.data.datasets, function(dataset, datasetIndex) { |
| 9649 | if (chart.isDatasetVisible(datasetIndex)) { |
| 9650 | var meta = chart.getDatasetMeta(datasetIndex); |
Jian Li | d7a5a74 | 2016-02-12 13:51:18 -0800 | [diff] [blame] | 9651 | |
Jian Li | 46770fc | 2016-08-03 02:32:45 +0900 | [diff] [blame] | 9652 | helpers.each(dataset.data, function(rawValue, index) { |
| 9653 | var value = +me.getRightValue(rawValue); |
| 9654 | if (isNaN(value) || meta.data[index].hidden) { |
| 9655 | return; |
| 9656 | } |
| 9657 | |
| 9658 | if (me.min === null) { |
| 9659 | me.min = value; |
| 9660 | } else if (value < me.min) { |
| 9661 | me.min = value; |
| 9662 | } |
| 9663 | |
| 9664 | if (me.max === null) { |
| 9665 | me.max = value; |
| 9666 | } else if (value > me.max) { |
| 9667 | me.max = value; |
| 9668 | } |
| 9669 | }); |
| 9670 | } |
| 9671 | }); |
| 9672 | |
| 9673 | // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero |
| 9674 | me.handleTickRangeOptions(); |
| 9675 | }, |
| 9676 | getTickLimit: function() { |
| 9677 | var tickOpts = this.options.ticks; |
| 9678 | var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); |
| 9679 | return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); |
| 9680 | }, |
| 9681 | convertTicksToLabels: function() { |
| 9682 | var me = this; |
| 9683 | Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); |
| 9684 | |
| 9685 | // Point labels |
| 9686 | me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); |
| 9687 | }, |
| 9688 | getLabelForIndex: function(index, datasetIndex) { |
| 9689 | return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); |
| 9690 | }, |
| 9691 | fit: function() { |
| 9692 | /* |
| 9693 | * Right, this is really confusing and there is a lot of maths going on here |
| 9694 | * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 |
| 9695 | * |
| 9696 | * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif |
| 9697 | * |
| 9698 | * Solution: |
| 9699 | * |
| 9700 | * We assume the radius of the polygon is half the size of the canvas at first |
| 9701 | * at each index we check if the text overlaps. |
| 9702 | * |
| 9703 | * Where it does, we store that angle and that index. |
| 9704 | * |
| 9705 | * After finding the largest index and angle we calculate how much we need to remove |
| 9706 | * from the shape radius to move the point inwards by that x. |
| 9707 | * |
| 9708 | * We average the left and right distances to get the maximum shape radius that can fit in the box |
| 9709 | * along with labels. |
| 9710 | * |
| 9711 | * Once we have that, we can find the centre point for the chart, by taking the x text protrusion |
| 9712 | * on each side, removing that from the size, halving it and adding the left x protrusion width. |
| 9713 | * |
| 9714 | * This will mean we have a shape fitted to the canvas, as large as it can be with the labels |
| 9715 | * and position it in the most space efficient manner |
| 9716 | * |
| 9717 | * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif |
| 9718 | */ |
| 9719 | |
| 9720 | var pointLabels = this.options.pointLabels; |
| 9721 | var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize); |
| 9722 | var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle); |
| 9723 | var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily); |
| 9724 | var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); |
| 9725 | |
| 9726 | // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. |
| 9727 | // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points |
| 9728 | var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]), |
| 9729 | pointPosition, |
| 9730 | i, |
| 9731 | textWidth, |
| 9732 | halfTextWidth, |
| 9733 | furthestRight = this.width, |
| 9734 | furthestRightIndex, |
| 9735 | furthestRightAngle, |
| 9736 | furthestLeft = 0, |
| 9737 | furthestLeftIndex, |
| 9738 | furthestLeftAngle, |
| 9739 | xProtrusionLeft, |
| 9740 | xProtrusionRight, |
| 9741 | radiusReductionRight, |
| 9742 | radiusReductionLeft; |
| 9743 | this.ctx.font = pointLabeFont; |
| 9744 | |
| 9745 | for (i = 0; i < this.getValueCount(); i++) { |
| 9746 | // 5px to space the text slightly out - similar to what we do in the draw function. |
| 9747 | pointPosition = this.getPointPosition(i, largestPossibleRadius); |
| 9748 | textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5; |
| 9749 | |
| 9750 | // Add quarter circle to make degree 0 mean top of circle |
| 9751 | var angleRadians = this.getIndexAngle(i) + (Math.PI / 2); |
| 9752 | var angle = (angleRadians * 360 / (2 * Math.PI)) % 360; |
| 9753 | |
| 9754 | if (angle === 0 || angle === 180) { |
| 9755 | // At angle 0 and 180, we're at exactly the top/bottom |
| 9756 | // of the radar chart, so text will be aligned centrally, so we'll half it and compare |
| 9757 | // w/left and right text sizes |
| 9758 | halfTextWidth = textWidth / 2; |
| 9759 | if (pointPosition.x + halfTextWidth > furthestRight) { |
| 9760 | furthestRight = pointPosition.x + halfTextWidth; |
| 9761 | furthestRightIndex = i; |
| 9762 | } |
| 9763 | if (pointPosition.x - halfTextWidth < furthestLeft) { |
| 9764 | furthestLeft = pointPosition.x - halfTextWidth; |
| 9765 | furthestLeftIndex = i; |
| 9766 | } |
| 9767 | } else if (angle < 180) { |
| 9768 | // Less than half the values means we'll left align the text |
| 9769 | if (pointPosition.x + textWidth > furthestRight) { |
| 9770 | furthestRight = pointPosition.x + textWidth; |
| 9771 | furthestRightIndex = i; |
| 9772 | } |
| 9773 | } else { |
| 9774 | // More than half the values means we'll right align the text |
| 9775 | if (pointPosition.x - textWidth < furthestLeft) { |
| 9776 | furthestLeft = pointPosition.x - textWidth; |
| 9777 | furthestLeftIndex = i; |
| 9778 | } |
| 9779 | } |
| 9780 | } |
| 9781 | |
| 9782 | xProtrusionLeft = furthestLeft; |
| 9783 | xProtrusionRight = Math.ceil(furthestRight - this.width); |
| 9784 | |
| 9785 | furthestRightAngle = this.getIndexAngle(furthestRightIndex); |
| 9786 | furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); |
| 9787 | |
| 9788 | radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); |
| 9789 | radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); |
| 9790 | |
| 9791 | // Ensure we actually need to reduce the size of the chart |
| 9792 | radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; |
| 9793 | radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; |
| 9794 | |
| 9795 | this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2); |
| 9796 | this.setCenterPoint(radiusReductionLeft, radiusReductionRight); |
| 9797 | }, |
| 9798 | setCenterPoint: function(leftMovement, rightMovement) { |
| 9799 | var me = this; |
| 9800 | var maxRight = me.width - rightMovement - me.drawingArea, |
| 9801 | maxLeft = leftMovement + me.drawingArea; |
| 9802 | |
| 9803 | me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); |
| 9804 | // Always vertically in the centre as the text height doesn't change |
| 9805 | me.yCenter = Math.round((me.height / 2) + me.top); |
| 9806 | }, |
| 9807 | |
| 9808 | getIndexAngle: function(index) { |
| 9809 | var angleMultiplier = (Math.PI * 2) / this.getValueCount(); |
| 9810 | var startAngle = this.chart.options && this.chart.options.startAngle ? |
| 9811 | this.chart.options.startAngle : |
| 9812 | 0; |
| 9813 | |
| 9814 | var startAngleRadians = startAngle * Math.PI * 2 / 360; |
| 9815 | |
| 9816 | // Start from the top instead of right, so remove a quarter of the circle |
| 9817 | return index * angleMultiplier - (Math.PI / 2) + startAngleRadians; |
| 9818 | }, |
| 9819 | getDistanceFromCenterForValue: function(value) { |
| 9820 | var me = this; |
| 9821 | |
| 9822 | if (value === null) { |
| 9823 | return 0; // null always in center |
| 9824 | } |
| 9825 | |
| 9826 | // Take into account half font size + the yPadding of the top value |
| 9827 | var scalingFactor = me.drawingArea / (me.max - me.min); |
| 9828 | if (me.options.reverse) { |
| 9829 | return (me.max - value) * scalingFactor; |
| 9830 | } else { |
| 9831 | return (value - me.min) * scalingFactor; |
| 9832 | } |
| 9833 | }, |
| 9834 | getPointPosition: function(index, distanceFromCenter) { |
| 9835 | var me = this; |
| 9836 | var thisAngle = me.getIndexAngle(index); |
| 9837 | return { |
| 9838 | x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, |
| 9839 | y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter |
| 9840 | }; |
| 9841 | }, |
| 9842 | getPointPositionForValue: function(index, value) { |
| 9843 | return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); |
| 9844 | }, |
| 9845 | |
| 9846 | getBasePosition: function() { |
| 9847 | var me = this; |
| 9848 | var min = me.min; |
| 9849 | var max = me.max; |
| 9850 | |
| 9851 | return me.getPointPositionForValue(0, |
| 9852 | me.beginAtZero? 0: |
| 9853 | min < 0 && max < 0? max : |
| 9854 | min > 0 && max > 0? min : |
| 9855 | 0); |
| 9856 | }, |
| 9857 | |
| 9858 | draw: function() { |
| 9859 | var me = this; |
| 9860 | var opts = me.options; |
| 9861 | var gridLineOpts = opts.gridLines; |
| 9862 | var tickOpts = opts.ticks; |
| 9863 | var angleLineOpts = opts.angleLines; |
| 9864 | var pointLabelOpts = opts.pointLabels; |
| 9865 | var getValueOrDefault = helpers.getValueOrDefault; |
| 9866 | |
| 9867 | if (opts.display) { |
| 9868 | var ctx = me.ctx; |
| 9869 | |
| 9870 | // Tick Font |
| 9871 | var tickFontSize = getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); |
| 9872 | var tickFontStyle = getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); |
| 9873 | var tickFontFamily = getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); |
| 9874 | var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); |
| 9875 | |
| 9876 | helpers.each(me.ticks, function(label, index) { |
| 9877 | // Don't draw a centre value (if it is minimum) |
| 9878 | if (index > 0 || opts.reverse) { |
| 9879 | var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); |
| 9880 | var yHeight = me.yCenter - yCenterOffset; |
| 9881 | |
| 9882 | // Draw circular lines around the scale |
| 9883 | if (gridLineOpts.display && index !== 0) { |
| 9884 | ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1); |
| 9885 | ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); |
| 9886 | |
| 9887 | if (opts.lineArc) { |
| 9888 | // Draw circular arcs between the points |
| 9889 | ctx.beginPath(); |
| 9890 | ctx.arc(me.xCenter, me.yCenter, yCenterOffset, 0, Math.PI * 2); |
| 9891 | ctx.closePath(); |
| 9892 | ctx.stroke(); |
| 9893 | } else { |
| 9894 | // Draw straight lines connecting each index |
| 9895 | ctx.beginPath(); |
| 9896 | for (var i = 0; i < me.getValueCount(); i++) { |
| 9897 | var pointPosition = me.getPointPosition(i, yCenterOffset); |
| 9898 | if (i === 0) { |
| 9899 | ctx.moveTo(pointPosition.x, pointPosition.y); |
| 9900 | } else { |
| 9901 | ctx.lineTo(pointPosition.x, pointPosition.y); |
| 9902 | } |
| 9903 | } |
| 9904 | ctx.closePath(); |
| 9905 | ctx.stroke(); |
| 9906 | } |
| 9907 | } |
| 9908 | |
| 9909 | if (tickOpts.display) { |
| 9910 | var tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); |
| 9911 | ctx.font = tickLabelFont; |
| 9912 | |
| 9913 | if (tickOpts.showLabelBackdrop) { |
| 9914 | var labelWidth = ctx.measureText(label).width; |
| 9915 | ctx.fillStyle = tickOpts.backdropColor; |
| 9916 | ctx.fillRect( |
| 9917 | me.xCenter - labelWidth / 2 - tickOpts.backdropPaddingX, |
| 9918 | yHeight - tickFontSize / 2 - tickOpts.backdropPaddingY, |
| 9919 | labelWidth + tickOpts.backdropPaddingX * 2, |
| 9920 | tickFontSize + tickOpts.backdropPaddingY * 2 |
| 9921 | ); |
| 9922 | } |
| 9923 | |
| 9924 | ctx.textAlign = 'center'; |
| 9925 | ctx.textBaseline = "middle"; |
| 9926 | ctx.fillStyle = tickFontColor; |
| 9927 | ctx.fillText(label, me.xCenter, yHeight); |
| 9928 | } |
| 9929 | } |
| 9930 | }); |
| 9931 | |
| 9932 | if (!opts.lineArc) { |
| 9933 | ctx.lineWidth = angleLineOpts.lineWidth; |
| 9934 | ctx.strokeStyle = angleLineOpts.color; |
| 9935 | |
| 9936 | var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max); |
| 9937 | |
| 9938 | // Point Label Font |
| 9939 | var pointLabelFontSize = getValueOrDefault(pointLabelOpts.fontSize, globalDefaults.defaultFontSize); |
| 9940 | var pointLabeFontStyle = getValueOrDefault(pointLabelOpts.fontStyle, globalDefaults.defaultFontStyle); |
| 9941 | var pointLabeFontFamily = getValueOrDefault(pointLabelOpts.fontFamily, globalDefaults.defaultFontFamily); |
| 9942 | var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); |
| 9943 | |
| 9944 | for (var i = me.getValueCount() - 1; i >= 0; i--) { |
| 9945 | if (angleLineOpts.display) { |
| 9946 | var outerPosition = me.getPointPosition(i, outerDistance); |
| 9947 | ctx.beginPath(); |
| 9948 | ctx.moveTo(me.xCenter, me.yCenter); |
| 9949 | ctx.lineTo(outerPosition.x, outerPosition.y); |
| 9950 | ctx.stroke(); |
| 9951 | ctx.closePath(); |
| 9952 | } |
| 9953 | // Extra 3px out for some label spacing |
| 9954 | var pointLabelPosition = me.getPointPosition(i, outerDistance + 5); |
| 9955 | |
| 9956 | // Keep this in loop since we may support array properties here |
| 9957 | var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor); |
| 9958 | ctx.font = pointLabeFont; |
| 9959 | ctx.fillStyle = pointLabelFontColor; |
| 9960 | |
| 9961 | var pointLabels = me.pointLabels; |
| 9962 | |
| 9963 | // Add quarter circle to make degree 0 mean top of circle |
| 9964 | var angleRadians = this.getIndexAngle(i) + (Math.PI / 2); |
| 9965 | var angle = (angleRadians * 360 / (2 * Math.PI)) % 360; |
| 9966 | |
| 9967 | if (angle === 0 || angle === 180) { |
| 9968 | ctx.textAlign = 'center'; |
| 9969 | } else if (angle < 180) { |
| 9970 | ctx.textAlign = 'left'; |
| 9971 | } else { |
| 9972 | ctx.textAlign = 'right'; |
| 9973 | } |
| 9974 | |
| 9975 | // Set the correct text baseline based on outer positioning |
| 9976 | if (angle === 90 || angle === 270) { |
| 9977 | ctx.textBaseline = 'middle'; |
| 9978 | } else if (angle > 270 || angle < 90) { |
| 9979 | ctx.textBaseline = 'bottom'; |
| 9980 | } else { |
| 9981 | ctx.textBaseline = 'top'; |
| 9982 | } |
| 9983 | |
| 9984 | ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y); |
| 9985 | } |
| 9986 | } |
| 9987 | } |
| 9988 | } |
| 9989 | }); |
| 9990 | Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig); |
| 9991 | |
| 9992 | }; |
| 9993 | |
| 9994 | },{}],44:[function(require,module,exports){ |
| 9995 | /*global window: false */ |
| 9996 | "use strict"; |
| 9997 | |
| 9998 | var moment = require(1); |
| 9999 | moment = typeof(moment) === 'function' ? moment : window.moment; |
| 10000 | |
| 10001 | module.exports = function(Chart) { |
| 10002 | |
| 10003 | var helpers = Chart.helpers; |
| 10004 | var time = { |
| 10005 | units: [{ |
| 10006 | name: 'millisecond', |
| 10007 | steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] |
| 10008 | }, { |
| 10009 | name: 'second', |
| 10010 | steps: [1, 2, 5, 10, 30] |
| 10011 | }, { |
| 10012 | name: 'minute', |
| 10013 | steps: [1, 2, 5, 10, 30] |
| 10014 | }, { |
| 10015 | name: 'hour', |
| 10016 | steps: [1, 2, 3, 6, 12] |
| 10017 | }, { |
| 10018 | name: 'day', |
| 10019 | steps: [1, 2, 5] |
| 10020 | }, { |
| 10021 | name: 'week', |
| 10022 | maxStep: 4 |
| 10023 | }, { |
| 10024 | name: 'month', |
| 10025 | maxStep: 3 |
| 10026 | }, { |
| 10027 | name: 'quarter', |
| 10028 | maxStep: 4 |
| 10029 | }, { |
| 10030 | name: 'year', |
| 10031 | maxStep: false |
| 10032 | }] |
| 10033 | }; |
| 10034 | |
| 10035 | var defaultConfig = { |
| 10036 | position: "bottom", |
| 10037 | |
| 10038 | time: { |
| 10039 | parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment |
| 10040 | format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ |
| 10041 | unit: false, // false == automatic or override with week, month, year, etc. |
| 10042 | round: false, // none, or override with week, month, year, etc. |
| 10043 | displayFormat: false, // DEPRECATED |
| 10044 | isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ |
| 10045 | |
| 10046 | // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ |
| 10047 | displayFormats: { |
| 10048 | 'millisecond': 'h:mm:ss.SSS a', // 11:20:01.123 AM, |
| 10049 | 'second': 'h:mm:ss a', // 11:20:01 AM |
| 10050 | 'minute': 'h:mm:ss a', // 11:20:01 AM |
| 10051 | 'hour': 'MMM D, hA', // Sept 4, 5PM |
| 10052 | 'day': 'll', // Sep 4 2015 |
| 10053 | 'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ? |
| 10054 | 'month': 'MMM YYYY', // Sept 2015 |
| 10055 | 'quarter': '[Q]Q - YYYY', // Q3 |
| 10056 | 'year': 'YYYY' // 2015 |
| 10057 | } |
| 10058 | }, |
| 10059 | ticks: { |
| 10060 | autoSkip: false |
| 10061 | } |
| 10062 | }; |
| 10063 | |
| 10064 | var TimeScale = Chart.Scale.extend({ |
| 10065 | initialize: function() { |
| 10066 | if (!moment) { |
| 10067 | throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); |
| 10068 | } |
| 10069 | |
| 10070 | Chart.Scale.prototype.initialize.call(this); |
| 10071 | }, |
| 10072 | getLabelMoment: function(datasetIndex, index) { |
| 10073 | if (typeof this.labelMoments[datasetIndex] != 'undefined') { |
| 10074 | return this.labelMoments[datasetIndex][index]; |
| 10075 | } |
| 10076 | |
| 10077 | return null; |
| 10078 | }, |
| 10079 | getMomentStartOf: function(tick) { |
| 10080 | var me = this; |
| 10081 | if (me.options.time.unit === 'week' && me.options.time.isoWeekday !== false) { |
| 10082 | return tick.clone().startOf('isoWeek').isoWeekday(me.options.time.isoWeekday); |
| 10083 | } else { |
| 10084 | return tick.clone().startOf(me.tickUnit); |
| 10085 | } |
| 10086 | }, |
| 10087 | determineDataLimits: function() { |
| 10088 | var me = this; |
| 10089 | me.labelMoments = []; |
| 10090 | |
| 10091 | // Only parse these once. If the dataset does not have data as x,y pairs, we will use |
| 10092 | // these |
| 10093 | var scaleLabelMoments = []; |
| 10094 | if (me.chart.data.labels && me.chart.data.labels.length > 0) { |
| 10095 | helpers.each(me.chart.data.labels, function(label) { |
| 10096 | var labelMoment = me.parseTime(label); |
| 10097 | |
| 10098 | if (labelMoment.isValid()) { |
| 10099 | if (me.options.time.round) { |
| 10100 | labelMoment.startOf(me.options.time.round); |
| 10101 | } |
| 10102 | scaleLabelMoments.push(labelMoment); |
| 10103 | } |
| 10104 | }, me); |
| 10105 | |
| 10106 | me.firstTick = moment.min.call(me, scaleLabelMoments); |
| 10107 | me.lastTick = moment.max.call(me, scaleLabelMoments); |
| 10108 | } else { |
| 10109 | me.firstTick = null; |
| 10110 | me.lastTick = null; |
| 10111 | } |
| 10112 | |
| 10113 | helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { |
| 10114 | var momentsForDataset = []; |
| 10115 | var datasetVisible = me.chart.isDatasetVisible(datasetIndex); |
| 10116 | |
| 10117 | if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) { |
| 10118 | helpers.each(dataset.data, function(value) { |
| 10119 | var labelMoment = me.parseTime(me.getRightValue(value)); |
| 10120 | |
| 10121 | if (labelMoment.isValid()) { |
| 10122 | if (me.options.time.round) { |
| 10123 | labelMoment.startOf(me.options.time.round); |
| 10124 | } |
| 10125 | momentsForDataset.push(labelMoment); |
| 10126 | |
| 10127 | if (datasetVisible) { |
| 10128 | // May have gone outside the scale ranges, make sure we keep the first and last ticks updated |
| 10129 | me.firstTick = me.firstTick !== null ? moment.min(me.firstTick, labelMoment) : labelMoment; |
| 10130 | me.lastTick = me.lastTick !== null ? moment.max(me.lastTick, labelMoment) : labelMoment; |
| 10131 | } |
| 10132 | } |
| 10133 | }, me); |
| 10134 | } else { |
| 10135 | // We have no labels. Use the ones from the scale |
| 10136 | momentsForDataset = scaleLabelMoments; |
| 10137 | } |
| 10138 | |
| 10139 | me.labelMoments.push(momentsForDataset); |
| 10140 | }, me); |
| 10141 | |
| 10142 | // Set these after we've done all the data |
| 10143 | if (me.options.time.min) { |
| 10144 | me.firstTick = me.parseTime(me.options.time.min); |
| 10145 | } |
| 10146 | |
| 10147 | if (me.options.time.max) { |
| 10148 | me.lastTick = me.parseTime(me.options.time.max); |
| 10149 | } |
| 10150 | |
| 10151 | // We will modify these, so clone for later |
| 10152 | me.firstTick = (me.firstTick || moment()).clone(); |
| 10153 | me.lastTick = (me.lastTick || moment()).clone(); |
| 10154 | }, |
| 10155 | buildTicks: function() { |
| 10156 | var me = this; |
| 10157 | |
| 10158 | me.ctx.save(); |
| 10159 | var tickFontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); |
| 10160 | var tickFontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); |
| 10161 | var tickFontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); |
| 10162 | var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); |
| 10163 | me.ctx.font = tickLabelFont; |
| 10164 | |
| 10165 | me.ticks = []; |
| 10166 | me.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step |
| 10167 | me.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc) |
| 10168 | |
| 10169 | // Set unit override if applicable |
| 10170 | if (me.options.time.unit) { |
| 10171 | me.tickUnit = me.options.time.unit || 'day'; |
| 10172 | me.displayFormat = me.options.time.displayFormats[me.tickUnit]; |
| 10173 | me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); |
| 10174 | me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, 1); |
| 10175 | } else { |
| 10176 | // Determine the smallest needed unit of the time |
| 10177 | var innerWidth = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); |
| 10178 | |
| 10179 | // Crude approximation of what the label length might be |
| 10180 | var tempFirstLabel = me.tickFormatFunction(me.firstTick, 0, []); |
| 10181 | var tickLabelWidth = me.ctx.measureText(tempFirstLabel).width; |
| 10182 | var cosRotation = Math.cos(helpers.toRadians(me.options.ticks.maxRotation)); |
| 10183 | var sinRotation = Math.sin(helpers.toRadians(me.options.ticks.maxRotation)); |
| 10184 | tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); |
| 10185 | var labelCapacity = innerWidth / (tickLabelWidth); |
| 10186 | |
| 10187 | // Start as small as possible |
| 10188 | me.tickUnit = 'millisecond'; |
| 10189 | me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); |
| 10190 | me.displayFormat = me.options.time.displayFormats[me.tickUnit]; |
| 10191 | |
| 10192 | var unitDefinitionIndex = 0; |
| 10193 | var unitDefinition = time.units[unitDefinitionIndex]; |
| 10194 | |
| 10195 | // While we aren't ideal and we don't have units left |
| 10196 | while (unitDefinitionIndex < time.units.length) { |
| 10197 | // Can we scale this unit. If `false` we can scale infinitely |
| 10198 | me.unitScale = 1; |
| 10199 | |
| 10200 | if (helpers.isArray(unitDefinition.steps) && Math.ceil(me.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) { |
| 10201 | // Use one of the prefedined steps |
| 10202 | for (var idx = 0; idx < unitDefinition.steps.length; ++idx) { |
| 10203 | if (unitDefinition.steps[idx] >= Math.ceil(me.scaleSizeInUnits / labelCapacity)) { |
| 10204 | me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, unitDefinition.steps[idx]); |
| 10205 | break; |
| 10206 | } |
| 10207 | } |
| 10208 | |
| 10209 | break; |
| 10210 | } else if ((unitDefinition.maxStep === false) || (Math.ceil(me.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) { |
| 10211 | // We have a max step. Scale this unit |
| 10212 | me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, Math.ceil(me.scaleSizeInUnits / labelCapacity)); |
| 10213 | break; |
| 10214 | } else { |
| 10215 | // Move to the next unit up |
| 10216 | ++unitDefinitionIndex; |
| 10217 | unitDefinition = time.units[unitDefinitionIndex]; |
| 10218 | |
| 10219 | me.tickUnit = unitDefinition.name; |
| 10220 | var leadingUnitBuffer = me.firstTick.diff(me.getMomentStartOf(me.firstTick), me.tickUnit, true); |
| 10221 | var trailingUnitBuffer = me.getMomentStartOf(me.lastTick.clone().add(1, me.tickUnit)).diff(me.lastTick, me.tickUnit, true); |
| 10222 | me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true) + leadingUnitBuffer + trailingUnitBuffer; |
| 10223 | me.displayFormat = me.options.time.displayFormats[unitDefinition.name]; |
| 10224 | } |
| 10225 | } |
| 10226 | } |
| 10227 | |
| 10228 | var roundedStart; |
| 10229 | |
| 10230 | // Only round the first tick if we have no hard minimum |
| 10231 | if (!me.options.time.min) { |
| 10232 | me.firstTick = me.getMomentStartOf(me.firstTick); |
| 10233 | roundedStart = me.firstTick; |
| 10234 | } else { |
| 10235 | roundedStart = me.getMomentStartOf(me.firstTick); |
| 10236 | } |
| 10237 | |
| 10238 | // Only round the last tick if we have no hard maximum |
| 10239 | if (!me.options.time.max) { |
| 10240 | var roundedEnd = me.getMomentStartOf(me.lastTick); |
| 10241 | var delta = roundedEnd.diff(me.lastTick, me.tickUnit, true); |
| 10242 | if (delta < 0) { |
| 10243 | // Do not use end of because we need me to be in the next time unit |
| 10244 | me.lastTick = me.getMomentStartOf(me.lastTick.add(1, me.tickUnit)); |
| 10245 | } else if (delta >= 0) { |
| 10246 | me.lastTick = roundedEnd; |
| 10247 | } |
| 10248 | |
| 10249 | me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); |
| 10250 | } |
| 10251 | |
| 10252 | me.smallestLabelSeparation = me.width; |
| 10253 | |
| 10254 | helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { |
| 10255 | for (var i = 1; i < me.labelMoments[datasetIndex].length; i++) { |
| 10256 | me.smallestLabelSeparation = Math.min(me.smallestLabelSeparation, me.labelMoments[datasetIndex][i].diff(me.labelMoments[datasetIndex][i - 1], me.tickUnit, true)); |
| 10257 | } |
| 10258 | }, me); |
| 10259 | |
| 10260 | // Tick displayFormat override |
| 10261 | if (me.options.time.displayFormat) { |
| 10262 | me.displayFormat = me.options.time.displayFormat; |
| 10263 | } |
| 10264 | |
| 10265 | // first tick. will have been rounded correctly if options.time.min is not specified |
| 10266 | me.ticks.push(me.firstTick.clone()); |
| 10267 | |
| 10268 | // For every unit in between the first and last moment, create a moment and add it to the ticks tick |
| 10269 | for (var i = 1; i <= me.scaleSizeInUnits; ++i) { |
| 10270 | var newTick = roundedStart.clone().add(i, me.tickUnit); |
| 10271 | |
| 10272 | // Are we greater than the max time |
| 10273 | if (me.options.time.max && newTick.diff(me.lastTick, me.tickUnit, true) >= 0) { |
| 10274 | break; |
| 10275 | } |
| 10276 | |
| 10277 | if (i % me.unitScale === 0) { |
| 10278 | me.ticks.push(newTick); |
| 10279 | } |
| 10280 | } |
| 10281 | |
| 10282 | // Always show the right tick |
| 10283 | var diff = me.ticks[me.ticks.length - 1].diff(me.lastTick, me.tickUnit); |
| 10284 | if (diff !== 0 || me.scaleSizeInUnits === 0) { |
| 10285 | // this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart |
| 10286 | // but the last tick was not rounded. |
| 10287 | if (me.options.time.max) { |
| 10288 | me.ticks.push(me.lastTick.clone()); |
| 10289 | me.scaleSizeInUnits = me.lastTick.diff(me.ticks[0], me.tickUnit, true); |
| 10290 | } else { |
| 10291 | me.ticks.push(me.lastTick.clone()); |
| 10292 | me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); |
| 10293 | } |
| 10294 | } |
| 10295 | |
| 10296 | me.ctx.restore(); |
| 10297 | }, |
| 10298 | // Get tooltip label |
| 10299 | getLabelForIndex: function(index, datasetIndex) { |
| 10300 | var me = this; |
| 10301 | var label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : ''; |
| 10302 | |
| 10303 | if (typeof me.chart.data.datasets[datasetIndex].data[0] === 'object') { |
| 10304 | label = me.getRightValue(me.chart.data.datasets[datasetIndex].data[index]); |
| 10305 | } |
| 10306 | |
| 10307 | // Format nicely |
| 10308 | if (me.options.time.tooltipFormat) { |
| 10309 | label = me.parseTime(label).format(me.options.time.tooltipFormat); |
| 10310 | } |
| 10311 | |
| 10312 | return label; |
| 10313 | }, |
| 10314 | // Function to format an individual tick mark |
| 10315 | tickFormatFunction: function(tick, index, ticks) { |
| 10316 | var formattedTick = tick.format(this.displayFormat); |
| 10317 | var tickOpts = this.options.ticks; |
| 10318 | var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); |
| 10319 | |
| 10320 | if (callback) { |
| 10321 | return callback(formattedTick, index, ticks); |
| 10322 | } else { |
| 10323 | return formattedTick; |
| 10324 | } |
| 10325 | }, |
| 10326 | convertTicksToLabels: function() { |
| 10327 | var me = this; |
| 10328 | me.tickMoments = me.ticks; |
| 10329 | me.ticks = me.ticks.map(me.tickFormatFunction, me); |
| 10330 | }, |
| 10331 | getPixelForValue: function(value, index, datasetIndex) { |
| 10332 | var me = this; |
| 10333 | if (!value || !value.isValid) { |
| 10334 | // not already a moment object |
| 10335 | value = moment(me.getRightValue(value)); |
| 10336 | } |
| 10337 | var labelMoment = value && value.isValid && value.isValid() ? value : me.getLabelMoment(datasetIndex, index); |
| 10338 | |
| 10339 | if (labelMoment) { |
| 10340 | var offset = labelMoment.diff(me.firstTick, me.tickUnit, true); |
| 10341 | |
| 10342 | var decimal = offset !== 0 ? offset / me.scaleSizeInUnits : offset; |
| 10343 | |
| 10344 | if (me.isHorizontal()) { |
| 10345 | var innerWidth = me.width - (me.paddingLeft + me.paddingRight); |
| 10346 | var valueOffset = (innerWidth * decimal) + me.paddingLeft; |
| 10347 | |
| 10348 | return me.left + Math.round(valueOffset); |
| 10349 | } else { |
| 10350 | var innerHeight = me.height - (me.paddingTop + me.paddingBottom); |
| 10351 | var heightOffset = (innerHeight * decimal) + me.paddingTop; |
| 10352 | |
| 10353 | return me.top + Math.round(heightOffset); |
| 10354 | } |
| 10355 | } |
| 10356 | }, |
| 10357 | getPixelForTick: function(index) { |
| 10358 | return this.getPixelForValue(this.tickMoments[index], null, null); |
| 10359 | }, |
| 10360 | getValueForPixel: function(pixel) { |
| 10361 | var me = this; |
| 10362 | var innerDimension = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); |
| 10363 | var offset = (pixel - (me.isHorizontal() ? me.left + me.paddingLeft : me.top + me.paddingTop)) / innerDimension; |
| 10364 | offset *= me.scaleSizeInUnits; |
| 10365 | return me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds'); |
| 10366 | }, |
| 10367 | parseTime: function(label) { |
| 10368 | var me = this; |
| 10369 | if (typeof me.options.time.parser === 'string') { |
| 10370 | return moment(label, me.options.time.parser); |
| 10371 | } |
| 10372 | if (typeof me.options.time.parser === 'function') { |
| 10373 | return me.options.time.parser(label); |
| 10374 | } |
| 10375 | // Date objects |
| 10376 | if (typeof label.getMonth === 'function' || typeof label === 'number') { |
| 10377 | return moment(label); |
| 10378 | } |
| 10379 | // Moment support |
| 10380 | if (label.isValid && label.isValid()) { |
| 10381 | return label; |
| 10382 | } |
| 10383 | // Custom parsing (return an instance of moment) |
| 10384 | if (typeof me.options.time.format !== 'string' && me.options.time.format.call) { |
| 10385 | console.warn("options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale"); |
| 10386 | return me.options.time.format(label); |
| 10387 | } |
| 10388 | // Moment format parsing |
| 10389 | return moment(label, me.options.time.format); |
| 10390 | } |
| 10391 | }); |
| 10392 | Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig); |
| 10393 | |
| 10394 | }; |
| 10395 | |
| 10396 | },{"1":1}]},{},[7])(7) |
| 10397 | }); |