PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 1.9.8
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v1.9.8
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / assets / js / chart.js

chart.js in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 1.9.8, at assets/js/chart.js

14,147 lines 388.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*!
2 * Chart.js
3 * http://chartjs.org/
4 * Version: 2.7.1
5 *
6 * Copyright 2017 Nick Downie
7 * Released under the MIT license
8 * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
9 */
10 window.imagify = window.imagify || {};
11 (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window.imagify}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
12
13 },{}],2:[function(require,module,exports){
14 /* MIT license */
15 var colorNames = require(6);
16
17 module.exports = {
18 getRgba: getRgba,
19 getHsla: getHsla,
20 getRgb: getRgb,
21 getHsl: getHsl,
22 getHwb: getHwb,
23 getAlpha: getAlpha,
24
25 hexString: hexString,
26 rgbString: rgbString,
27 rgbaString: rgbaString,
28 percentString: percentString,
29 percentaString: percentaString,
30 hslString: hslString,
31 hslaString: hslaString,
32 hwbString: hwbString,
33 keyword: keyword
34 }
35
36 function getRgba(string) {
37 if (!string) {
38 return;
39 }
40 var abbr = /^#([a-fA-F0-9]{3})$/i,
41 hex = /^#([a-fA-F0-9]{6})$/i,
42 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
43 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
44 keyword = /(\w+)/;
45
46 var rgb = [0, 0, 0],
47 a = 1,
48 match = string.match(abbr);
49 if (match) {
50 match = match[1];
51 for (var i = 0; i < rgb.length; i++) {
52 rgb[i] = parseInt(match[i] + match[i], 16);
53 }
54 }
55 else if (match = string.match(hex)) {
56 match = match[1];
57 for (var i = 0; i < rgb.length; i++) {
58 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
59 }
60 }
61 else if (match = string.match(rgba)) {
62 for (var i = 0; i < rgb.length; i++) {
63 rgb[i] = parseInt(match[i + 1]);
64 }
65 a = parseFloat(match[4]);
66 }
67 else if (match = string.match(per)) {
68 for (var i = 0; i < rgb.length; i++) {
69 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
70 }
71 a = parseFloat(match[4]);
72 }
73 else if (match = string.match(keyword)) {
74 if (match[1] == "transparent") {
75 return [0, 0, 0, 0];
76 }
77 rgb = colorNames[match[1]];
78 if (!rgb) {
79 return;
80 }
81 }
82
83 for (var i = 0; i < rgb.length; i++) {
84 rgb[i] = scale(rgb[i], 0, 255);
85 }
86 if (!a && a != 0) {
87 a = 1;
88 }
89 else {
90 a = scale(a, 0, 1);
91 }
92 rgb[3] = a;
93 return rgb;
94 }
95
96 function getHsla(string) {
97 if (!string) {
98 return;
99 }
100 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
101 var match = string.match(hsl);
102 if (match) {
103 var alpha = parseFloat(match[4]);
104 var h = scale(parseInt(match[1]), 0, 360),
105 s = scale(parseFloat(match[2]), 0, 100),
106 l = scale(parseFloat(match[3]), 0, 100),
107 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
108 return [h, s, l, a];
109 }
110 }
111
112 function getHwb(string) {
113 if (!string) {
114 return;
115 }
116 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
117 var match = string.match(hwb);
118 if (match) {
119 var alpha = parseFloat(match[4]);
120 var h = scale(parseInt(match[1]), 0, 360),
121 w = scale(parseFloat(match[2]), 0, 100),
122 b = scale(parseFloat(match[3]), 0, 100),
123 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
124 return [h, w, b, a];
125 }
126 }
127
128 function getRgb(string) {
129 var rgba = getRgba(string);
130 return rgba && rgba.slice(0, 3);
131 }
132
133 function getHsl(string) {
134 var hsla = getHsla(string);
135 return hsla && hsla.slice(0, 3);
136 }
137
138 function getAlpha(string) {
139 var vals = getRgba(string);
140 if (vals) {
141 return vals[3];
142 }
143 else if (vals = getHsla(string)) {
144 return vals[3];
145 }
146 else if (vals = getHwb(string)) {
147 return vals[3];
148 }
149 }
150
151 // generators
152 function hexString(rgb) {
153 return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
154 + hexDouble(rgb[2]);
155 }
156
157 function rgbString(rgba, alpha) {
158 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
159 return rgbaString(rgba, alpha);
160 }
161 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
162 }
163
164 function rgbaString(rgba, alpha) {
165 if (alpha === undefined) {
166 alpha = (rgba[3] !== undefined ? rgba[3] : 1);
167 }
168 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
169 + ", " + alpha + ")";
170 }
171
172 function percentString(rgba, alpha) {
173 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
174 return percentaString(rgba, alpha);
175 }
176 var r = Math.round(rgba[0]/255 * 100),
177 g = Math.round(rgba[1]/255 * 100),
178 b = Math.round(rgba[2]/255 * 100);
179
180 return "rgb(" + r + "%, " + g + "%, " + b + "%)";
181 }
182
183 function percentaString(rgba, alpha) {
184 var r = Math.round(rgba[0]/255 * 100),
185 g = Math.round(rgba[1]/255 * 100),
186 b = Math.round(rgba[2]/255 * 100);
187 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
188 }
189
190 function hslString(hsla, alpha) {
191 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
192 return hslaString(hsla, alpha);
193 }
194 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
195 }
196
197 function hslaString(hsla, alpha) {
198 if (alpha === undefined) {
199 alpha = (hsla[3] !== undefined ? hsla[3] : 1);
200 }
201 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
202 + alpha + ")";
203 }
204
205 // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
206 // (hwb have alpha optional & 1 is default value)
207 function hwbString(hwb, alpha) {
208 if (alpha === undefined) {
209 alpha = (hwb[3] !== undefined ? hwb[3] : 1);
210 }
211 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
212 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
213 }
214
215 function keyword(rgb) {
216 return reverseNames[rgb.slice(0, 3)];
217 }
218
219 // helpers
220 function scale(num, min, max) {
221 return Math.min(Math.max(min, num), max);
222 }
223
224 function hexDouble(num) {
225 var str = num.toString(16).toUpperCase();
226 return (str.length < 2) ? "0" + str : str;
227 }
228
229
230 //create a list of reverse color names
231 var reverseNames = {};
232 for (var name in colorNames) {
233 reverseNames[colorNames[name]] = name;
234 }
235
236 },{"6":6}],3:[function(require,module,exports){
237 /* MIT license */
238 var convert = require(5);
239 var string = require(2);
240
241 var Color = function (obj) {
242 if (obj instanceof Color) {
243 return obj;
244 }
245 if (!(this instanceof Color)) {
246 return new Color(obj);
247 }
248
249 this.valid = false;
250 this.values = {
251 rgb: [0, 0, 0],
252 hsl: [0, 0, 0],
253 hsv: [0, 0, 0],
254 hwb: [0, 0, 0],
255 cmyk: [0, 0, 0, 0],
256 alpha: 1
257 };
258
259 // parse Color() argument
260 var vals;
261 if (typeof obj === 'string') {
262 vals = string.getRgba(obj);
263 if (vals) {
264 this.setValues('rgb', vals);
265 } else if (vals = string.getHsla(obj)) {
266 this.setValues('hsl', vals);
267 } else if (vals = string.getHwb(obj)) {
268 this.setValues('hwb', vals);
269 }
270 } else if (typeof obj === 'object') {
271 vals = obj;
272 if (vals.r !== undefined || vals.red !== undefined) {
273 this.setValues('rgb', vals);
274 } else if (vals.l !== undefined || vals.lightness !== undefined) {
275 this.setValues('hsl', vals);
276 } else if (vals.v !== undefined || vals.value !== undefined) {
277 this.setValues('hsv', vals);
278 } else if (vals.w !== undefined || vals.whiteness !== undefined) {
279 this.setValues('hwb', vals);
280 } else if (vals.c !== undefined || vals.cyan !== undefined) {
281 this.setValues('cmyk', vals);
282 }
283 }
284 };
285
286 Color.prototype = {
287 isValid: function () {
288 return this.valid;
289 },
290 rgb: function () {
291 return this.setSpace('rgb', arguments);
292 },
293 hsl: function () {
294 return this.setSpace('hsl', arguments);
295 },
296 hsv: function () {
297 return this.setSpace('hsv', arguments);
298 },
299 hwb: function () {
300 return this.setSpace('hwb', arguments);
301 },
302 cmyk: function () {
303 return this.setSpace('cmyk', arguments);
304 },
305
306 rgbArray: function () {
307 return this.values.rgb;
308 },
309 hslArray: function () {
310 return this.values.hsl;
311 },
312 hsvArray: function () {
313 return this.values.hsv;
314 },
315 hwbArray: function () {
316 var values = this.values;
317 if (values.alpha !== 1) {
318 return values.hwb.concat([values.alpha]);
319 }
320 return values.hwb;
321 },
322 cmykArray: function () {
323 return this.values.cmyk;
324 },
325 rgbaArray: function () {
326 var values = this.values;
327 return values.rgb.concat([values.alpha]);
328 },
329 hslaArray: function () {
330 var values = this.values;
331 return values.hsl.concat([values.alpha]);
332 },
333 alpha: function (val) {
334 if (val === undefined) {
335 return this.values.alpha;
336 }
337 this.setValues('alpha', val);
338 return this;
339 },
340
341 red: function (val) {
342 return this.setChannel('rgb', 0, val);
343 },
344 green: function (val) {
345 return this.setChannel('rgb', 1, val);
346 },
347 blue: function (val) {
348 return this.setChannel('rgb', 2, val);
349 },
350 hue: function (val) {
351 if (val) {
352 val %= 360;
353 val = val < 0 ? 360 + val : val;
354 }
355 return this.setChannel('hsl', 0, val);
356 },
357 saturation: function (val) {
358 return this.setChannel('hsl', 1, val);
359 },
360 lightness: function (val) {
361 return this.setChannel('hsl', 2, val);
362 },
363 saturationv: function (val) {
364 return this.setChannel('hsv', 1, val);
365 },
366 whiteness: function (val) {
367 return this.setChannel('hwb', 1, val);
368 },
369 blackness: function (val) {
370 return this.setChannel('hwb', 2, val);
371 },
372 value: function (val) {
373 return this.setChannel('hsv', 2, val);
374 },
375 cyan: function (val) {
376 return this.setChannel('cmyk', 0, val);
377 },
378 magenta: function (val) {
379 return this.setChannel('cmyk', 1, val);
380 },
381 yellow: function (val) {
382 return this.setChannel('cmyk', 2, val);
383 },
384 black: function (val) {
385 return this.setChannel('cmyk', 3, val);
386 },
387
388 hexString: function () {
389 return string.hexString(this.values.rgb);
390 },
391 rgbString: function () {
392 return string.rgbString(this.values.rgb, this.values.alpha);
393 },
394 rgbaString: function () {
395 return string.rgbaString(this.values.rgb, this.values.alpha);
396 },
397 percentString: function () {
398 return string.percentString(this.values.rgb, this.values.alpha);
399 },
400 hslString: function () {
401 return string.hslString(this.values.hsl, this.values.alpha);
402 },
403 hslaString: function () {
404 return string.hslaString(this.values.hsl, this.values.alpha);
405 },
406 hwbString: function () {
407 return string.hwbString(this.values.hwb, this.values.alpha);
408 },
409 keyword: function () {
410 return string.keyword(this.values.rgb, this.values.alpha);
411 },
412
413 rgbNumber: function () {
414 var rgb = this.values.rgb;
415 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
416 },
417
418 luminosity: function () {
419 // http://www.w3.org/TR/WCAG20/#relativeluminancedef
420 var rgb = this.values.rgb;
421 var lum = [];
422 for (var i = 0; i < rgb.length; i++) {
423 var chan = rgb[i] / 255;
424 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
425 }
426 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
427 },
428
429 contrast: function (color2) {
430 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
431 var lum1 = this.luminosity();
432 var lum2 = color2.luminosity();
433 if (lum1 > lum2) {
434 return (lum1 + 0.05) / (lum2 + 0.05);
435 }
436 return (lum2 + 0.05) / (lum1 + 0.05);
437 },
438
439 level: function (color2) {
440 var contrastRatio = this.contrast(color2);
441 if (contrastRatio >= 7.1) {
442 return 'AAA';
443 }
444
445 return (contrastRatio >= 4.5) ? 'AA' : '';
446 },
447
448 dark: function () {
449 // YIQ equation from http://24ways.org/2010/calculating-color-contrast
450 var rgb = this.values.rgb;
451 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
452 return yiq < 128;
453 },
454
455 light: function () {
456 return !this.dark();
457 },
458
459 negate: function () {
460 var rgb = [];
461 for (var i = 0; i < 3; i++) {
462 rgb[i] = 255 - this.values.rgb[i];
463 }
464 this.setValues('rgb', rgb);
465 return this;
466 },
467
468 lighten: function (ratio) {
469 var hsl = this.values.hsl;
470 hsl[2] += hsl[2] * ratio;
471 this.setValues('hsl', hsl);
472 return this;
473 },
474
475 darken: function (ratio) {
476 var hsl = this.values.hsl;
477 hsl[2] -= hsl[2] * ratio;
478 this.setValues('hsl', hsl);
479 return this;
480 },
481
482 saturate: function (ratio) {
483 var hsl = this.values.hsl;
484 hsl[1] += hsl[1] * ratio;
485 this.setValues('hsl', hsl);
486 return this;
487 },
488
489 desaturate: function (ratio) {
490 var hsl = this.values.hsl;
491 hsl[1] -= hsl[1] * ratio;
492 this.setValues('hsl', hsl);
493 return this;
494 },
495
496 whiten: function (ratio) {
497 var hwb = this.values.hwb;
498 hwb[1] += hwb[1] * ratio;
499 this.setValues('hwb', hwb);
500 return this;
501 },
502
503 blacken: function (ratio) {
504 var hwb = this.values.hwb;
505 hwb[2] += hwb[2] * ratio;
506 this.setValues('hwb', hwb);
507 return this;
508 },
509
510 greyscale: function () {
511 var rgb = this.values.rgb;
512 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
513 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
514 this.setValues('rgb', [val, val, val]);
515 return this;
516 },
517
518 clearer: function (ratio) {
519 var alpha = this.values.alpha;
520 this.setValues('alpha', alpha - (alpha * ratio));
521 return this;
522 },
523
524 opaquer: function (ratio) {
525 var alpha = this.values.alpha;
526 this.setValues('alpha', alpha + (alpha * ratio));
527 return this;
528 },
529
530 rotate: function (degrees) {
531 var hsl = this.values.hsl;
532 var hue = (hsl[0] + degrees) % 360;
533 hsl[0] = hue < 0 ? 360 + hue : hue;
534 this.setValues('hsl', hsl);
535 return this;
536 },
537
538 /**
539 * Ported from sass implementation in C
540 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
541 */
542 mix: function (mixinColor, weight) {
543 var color1 = this;
544 var color2 = mixinColor;
545 var p = weight === undefined ? 0.5 : weight;
546
547 var w = 2 * p - 1;
548 var a = color1.alpha() - color2.alpha();
549
550 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
551 var w2 = 1 - w1;
552
553 return this
554 .rgb(
555 w1 * color1.red() + w2 * color2.red(),
556 w1 * color1.green() + w2 * color2.green(),
557 w1 * color1.blue() + w2 * color2.blue()
558 )
559 .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
560 },
561
562 toJSON: function () {
563 return this.rgb();
564 },
565
566 clone: function () {
567 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
568 // making the final build way to big to embed in Chart.js. So let's do it manually,
569 // assuming that values to clone are 1 dimension arrays containing only numbers,
570 // except 'alpha' which is a number.
571 var result = new Color();
572 var source = this.values;
573 var target = result.values;
574 var value, type;
575
576 for (var prop in source) {
577 if (source.hasOwnProperty(prop)) {
578 value = source[prop];
579 type = ({}).toString.call(value);
580 if (type === '[object Array]') {
581 target[prop] = value.slice(0);
582 } else if (type === '[object Number]') {
583 target[prop] = value;
584 } else {
585 console.error('unexpected color value:', value);
586 }
587 }
588 }
589
590 return result;
591 }
592 };
593
594 Color.prototype.spaces = {
595 rgb: ['red', 'green', 'blue'],
596 hsl: ['hue', 'saturation', 'lightness'],
597 hsv: ['hue', 'saturation', 'value'],
598 hwb: ['hue', 'whiteness', 'blackness'],
599 cmyk: ['cyan', 'magenta', 'yellow', 'black']
600 };
601
602 Color.prototype.maxes = {
603 rgb: [255, 255, 255],
604 hsl: [360, 100, 100],
605 hsv: [360, 100, 100],
606 hwb: [360, 100, 100],
607 cmyk: [100, 100, 100, 100]
608 };
609
610 Color.prototype.getValues = function (space) {
611 var values = this.values;
612 var vals = {};
613
614 for (var i = 0; i < space.length; i++) {
615 vals[space.charAt(i)] = values[space][i];
616 }
617
618 if (values.alpha !== 1) {
619 vals.a = values.alpha;
620 }
621
622 // {r: 255, g: 255, b: 255, a: 0.4}
623 return vals;
624 };
625
626 Color.prototype.setValues = function (space, vals) {
627 var values = this.values;
628 var spaces = this.spaces;
629 var maxes = this.maxes;
630 var alpha = 1;
631 var i;
632
633 this.valid = true;
634
635 if (space === 'alpha') {
636 alpha = vals;
637 } else if (vals.length) {
638 // [10, 10, 10]
639 values[space] = vals.slice(0, space.length);
640 alpha = vals[space.length];
641 } else if (vals[space.charAt(0)] !== undefined) {
642 // {r: 10, g: 10, b: 10}
643 for (i = 0; i < space.length; i++) {
644 values[space][i] = vals[space.charAt(i)];
645 }
646
647 alpha = vals.a;
648 } else if (vals[spaces[space][0]] !== undefined) {
649 // {red: 10, green: 10, blue: 10}
650 var chans = spaces[space];
651
652 for (i = 0; i < space.length; i++) {
653 values[space][i] = vals[chans[i]];
654 }
655
656 alpha = vals.alpha;
657 }
658
659 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
660
661 if (space === 'alpha') {
662 return false;
663 }
664
665 var capped;
666
667 // cap values of the space prior converting all values
668 for (i = 0; i < space.length; i++) {
669 capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
670 values[space][i] = Math.round(capped);
671 }
672
673 // convert to all the other color spaces
674 for (var sname in spaces) {
675 if (sname !== space) {
676 values[sname] = convert[space][sname](values[space]);
677 }
678 }
679
680 return true;
681 };
682
683 Color.prototype.setSpace = function (space, args) {
684 var vals = args[0];
685
686 if (vals === undefined) {
687 // color.rgb()
688 return this.getValues(space);
689 }
690
691 // color.rgb(10, 10, 10)
692 if (typeof vals === 'number') {
693 vals = Array.prototype.slice.call(args);
694 }
695
696 this.setValues(space, vals);
697 return this;
698 };
699
700 Color.prototype.setChannel = function (space, index, val) {
701 var svalues = this.values[space];
702 if (val === undefined) {
703 // color.red()
704 return svalues[index];
705 } else if (val === svalues[index]) {
706 // color.red(color.red())
707 return this;
708 }
709
710 // color.red(100)
711 svalues[index] = val;
712 this.setValues(space, svalues);
713
714 return this;
715 };
716
717 if (typeof window !== 'undefined') {
718 window.imagify.Color = Color;
719 }
720
721 module.exports = Color;
722
723 },{"2":2,"5":5}],4:[function(require,module,exports){
724 /* MIT license */
725
726 module.exports = {
727 rgb2hsl: rgb2hsl,
728 rgb2hsv: rgb2hsv,
729 rgb2hwb: rgb2hwb,
730 rgb2cmyk: rgb2cmyk,
731 rgb2keyword: rgb2keyword,
732 rgb2xyz: rgb2xyz,
733 rgb2lab: rgb2lab,
734 rgb2lch: rgb2lch,
735
736 hsl2rgb: hsl2rgb,
737 hsl2hsv: hsl2hsv,
738 hsl2hwb: hsl2hwb,
739 hsl2cmyk: hsl2cmyk,
740 hsl2keyword: hsl2keyword,
741
742 hsv2rgb: hsv2rgb,
743 hsv2hsl: hsv2hsl,
744 hsv2hwb: hsv2hwb,
745 hsv2cmyk: hsv2cmyk,
746 hsv2keyword: hsv2keyword,
747
748 hwb2rgb: hwb2rgb,
749 hwb2hsl: hwb2hsl,
750 hwb2hsv: hwb2hsv,
751 hwb2cmyk: hwb2cmyk,
752 hwb2keyword: hwb2keyword,
753
754 cmyk2rgb: cmyk2rgb,
755 cmyk2hsl: cmyk2hsl,
756 cmyk2hsv: cmyk2hsv,
757 cmyk2hwb: cmyk2hwb,
758 cmyk2keyword: cmyk2keyword,
759
760 keyword2rgb: keyword2rgb,
761 keyword2hsl: keyword2hsl,
762 keyword2hsv: keyword2hsv,
763 keyword2hwb: keyword2hwb,
764 keyword2cmyk: keyword2cmyk,
765 keyword2lab: keyword2lab,
766 keyword2xyz: keyword2xyz,
767
768 xyz2rgb: xyz2rgb,
769 xyz2lab: xyz2lab,
770 xyz2lch: xyz2lch,
771
772 lab2xyz: lab2xyz,
773 lab2rgb: lab2rgb,
774 lab2lch: lab2lch,
775
776 lch2lab: lch2lab,
777 lch2xyz: lch2xyz,
778 lch2rgb: lch2rgb
779 }
780
781
782 function rgb2hsl(rgb) {
783 var r = rgb[0]/255,
784 g = rgb[1]/255,
785 b = rgb[2]/255,
786 min = Math.min(r, g, b),
787 max = Math.max(r, g, b),
788 delta = max - min,
789 h, s, l;
790
791 if (max == min)
792 h = 0;
793 else if (r == max)
794 h = (g - b) / delta;
795 else if (g == max)
796 h = 2 + (b - r) / delta;
797 else if (b == max)
798 h = 4 + (r - g)/ delta;
799
800 h = Math.min(h * 60, 360);
801
802 if (h < 0)
803 h += 360;
804
805 l = (min + max) / 2;
806
807 if (max == min)
808 s = 0;
809 else if (l <= 0.5)
810 s = delta / (max + min);
811 else
812 s = delta / (2 - max - min);
813
814 return [h, s * 100, l * 100];
815 }
816
817 function rgb2hsv(rgb) {
818 var r = rgb[0],
819 g = rgb[1],
820 b = rgb[2],
821 min = Math.min(r, g, b),
822 max = Math.max(r, g, b),
823 delta = max - min,
824 h, s, v;
825
826 if (max == 0)
827 s = 0;
828 else
829 s = (delta/max * 1000)/10;
830
831 if (max == min)
832 h = 0;
833 else if (r == max)
834 h = (g - b) / delta;
835 else if (g == max)
836 h = 2 + (b - r) / delta;
837 else if (b == max)
838 h = 4 + (r - g) / delta;
839
840 h = Math.min(h * 60, 360);
841
842 if (h < 0)
843 h += 360;
844
845 v = ((max / 255) * 1000) / 10;
846
847 return [h, s, v];
848 }
849
850 function rgb2hwb(rgb) {
851 var r = rgb[0],
852 g = rgb[1],
853 b = rgb[2],
854 h = rgb2hsl(rgb)[0],
855 w = 1/255 * Math.min(r, Math.min(g, b)),
856 b = 1 - 1/255 * Math.max(r, Math.max(g, b));
857
858 return [h, w * 100, b * 100];
859 }
860
861 function rgb2cmyk(rgb) {
862 var r = rgb[0] / 255,
863 g = rgb[1] / 255,
864 b = rgb[2] / 255,
865 c, m, y, k;
866
867 k = Math.min(1 - r, 1 - g, 1 - b);
868 c = (1 - r - k) / (1 - k) || 0;
869 m = (1 - g - k) / (1 - k) || 0;
870 y = (1 - b - k) / (1 - k) || 0;
871 return [c * 100, m * 100, y * 100, k * 100];
872 }
873
874 function rgb2keyword(rgb) {
875 return reverseKeywords[JSON.stringify(rgb)];
876 }
877
878 function rgb2xyz(rgb) {
879 var r = rgb[0] / 255,
880 g = rgb[1] / 255,
881 b = rgb[2] / 255;
882
883 // assume sRGB
884 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
885 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
886 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
887
888 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
889 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
890 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
891
892 return [x * 100, y *100, z * 100];
893 }
894
895 function rgb2lab(rgb) {
896 var xyz = rgb2xyz(rgb),
897 x = xyz[0],
898 y = xyz[1],
899 z = xyz[2],
900 l, a, b;
901
902 x /= 95.047;
903 y /= 100;
904 z /= 108.883;
905
906 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
907 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
908 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
909
910 l = (116 * y) - 16;
911 a = 500 * (x - y);
912 b = 200 * (y - z);
913
914 return [l, a, b];
915 }
916
917 function rgb2lch(args) {
918 return lab2lch(rgb2lab(args));
919 }
920
921 function hsl2rgb(hsl) {
922 var h = hsl[0] / 360,
923 s = hsl[1] / 100,
924 l = hsl[2] / 100,
925 t1, t2, t3, rgb, val;
926
927 if (s == 0) {
928 val = l * 255;
929 return [val, val, val];
930 }
931
932 if (l < 0.5)
933 t2 = l * (1 + s);
934 else
935 t2 = l + s - l * s;
936 t1 = 2 * l - t2;
937
938 rgb = [0, 0, 0];
939 for (var i = 0; i < 3; i++) {
940 t3 = h + 1 / 3 * - (i - 1);
941 t3 < 0 && t3++;
942 t3 > 1 && t3--;
943
944 if (6 * t3 < 1)
945 val = t1 + (t2 - t1) * 6 * t3;
946 else if (2 * t3 < 1)
947 val = t2;
948 else if (3 * t3 < 2)
949 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
950 else
951 val = t1;
952
953 rgb[i] = val * 255;
954 }
955
956 return rgb;
957 }
958
959 function hsl2hsv(hsl) {
960 var h = hsl[0],
961 s = hsl[1] / 100,
962 l = hsl[2] / 100,
963 sv, v;
964
965 if(l === 0) {
966 // no need to do calc on black
967 // also avoids divide by 0 error
968 return [0, 0, 0];
969 }
970
971 l *= 2;
972 s *= (l <= 1) ? l : 2 - l;
973 v = (l + s) / 2;
974 sv = (2 * s) / (l + s);
975 return [h, sv * 100, v * 100];
976 }
977
978 function hsl2hwb(args) {
979 return rgb2hwb(hsl2rgb(args));
980 }
981
982 function hsl2cmyk(args) {
983 return rgb2cmyk(hsl2rgb(args));
984 }
985
986 function hsl2keyword(args) {
987 return rgb2keyword(hsl2rgb(args));
988 }
989
990
991 function hsv2rgb(hsv) {
992 var h = hsv[0] / 60,
993 s = hsv[1] / 100,
994 v = hsv[2] / 100,
995 hi = Math.floor(h) % 6;
996
997 var f = h - Math.floor(h),
998 p = 255 * v * (1 - s),
999 q = 255 * v * (1 - (s * f)),
1000 t = 255 * v * (1 - (s * (1 - f))),
1001 v = 255 * v;
1002
1003 switch(hi) {
1004 case 0:
1005 return [v, t, p];
1006 case 1:
1007 return [q, v, p];
1008 case 2:
1009 return [p, v, t];
1010 case 3:
1011 return [p, q, v];
1012 case 4:
1013 return [t, p, v];
1014 case 5:
1015 return [v, p, q];
1016 }
1017 }
1018
1019 function hsv2hsl(hsv) {
1020 var h = hsv[0],
1021 s = hsv[1] / 100,
1022 v = hsv[2] / 100,
1023 sl, l;
1024
1025 l = (2 - s) * v;
1026 sl = s * v;
1027 sl /= (l <= 1) ? l : 2 - l;
1028 sl = sl || 0;
1029 l /= 2;
1030 return [h, sl * 100, l * 100];
1031 }
1032
1033 function hsv2hwb(args) {
1034 return rgb2hwb(hsv2rgb(args))
1035 }
1036
1037 function hsv2cmyk(args) {
1038 return rgb2cmyk(hsv2rgb(args));
1039 }
1040
1041 function hsv2keyword(args) {
1042 return rgb2keyword(hsv2rgb(args));
1043 }
1044
1045 // http://dev.w3.org/csswg/css-color/#hwb-to-rgb
1046 function hwb2rgb(hwb) {
1047 var h = hwb[0] / 360,
1048 wh = hwb[1] / 100,
1049 bl = hwb[2] / 100,
1050 ratio = wh + bl,
1051 i, v, f, n;
1052
1053 // wh + bl cant be > 1
1054 if (ratio > 1) {
1055 wh /= ratio;
1056 bl /= ratio;
1057 }
1058
1059 i = Math.floor(6 * h);
1060 v = 1 - bl;
1061 f = 6 * h - i;
1062 if ((i & 0x01) != 0) {
1063 f = 1 - f;
1064 }
1065 n = wh + f * (v - wh); // linear interpolation
1066
1067 switch (i) {
1068 default:
1069 case 6:
1070 case 0: r = v; g = n; b = wh; break;
1071 case 1: r = n; g = v; b = wh; break;
1072 case 2: r = wh; g = v; b = n; break;
1073 case 3: r = wh; g = n; b = v; break;
1074 case 4: r = n; g = wh; b = v; break;
1075 case 5: r = v; g = wh; b = n; break;
1076 }
1077
1078 return [r * 255, g * 255, b * 255];
1079 }
1080
1081 function hwb2hsl(args) {
1082 return rgb2hsl(hwb2rgb(args));
1083 }
1084
1085 function hwb2hsv(args) {
1086 return rgb2hsv(hwb2rgb(args));
1087 }
1088
1089 function hwb2cmyk(args) {
1090 return rgb2cmyk(hwb2rgb(args));
1091 }
1092
1093 function hwb2keyword(args) {
1094 return rgb2keyword(hwb2rgb(args));
1095 }
1096
1097 function cmyk2rgb(cmyk) {
1098 var c = cmyk[0] / 100,
1099 m = cmyk[1] / 100,
1100 y = cmyk[2] / 100,
1101 k = cmyk[3] / 100,
1102 r, g, b;
1103
1104 r = 1 - Math.min(1, c * (1 - k) + k);
1105 g = 1 - Math.min(1, m * (1 - k) + k);
1106 b = 1 - Math.min(1, y * (1 - k) + k);
1107 return [r * 255, g * 255, b * 255];
1108 }
1109
1110 function cmyk2hsl(args) {
1111 return rgb2hsl(cmyk2rgb(args));
1112 }
1113
1114 function cmyk2hsv(args) {
1115 return rgb2hsv(cmyk2rgb(args));
1116 }
1117
1118 function cmyk2hwb(args) {
1119 return rgb2hwb(cmyk2rgb(args));
1120 }
1121
1122 function cmyk2keyword(args) {
1123 return rgb2keyword(cmyk2rgb(args));
1124 }
1125
1126
1127 function xyz2rgb(xyz) {
1128 var x = xyz[0] / 100,
1129 y = xyz[1] / 100,
1130 z = xyz[2] / 100,
1131 r, g, b;
1132
1133 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
1134 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
1135 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
1136
1137 // assume sRGB
1138 r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
1139 : r = (r * 12.92);
1140
1141 g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
1142 : g = (g * 12.92);
1143
1144 b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
1145 : b = (b * 12.92);
1146
1147 r = Math.min(Math.max(0, r), 1);
1148 g = Math.min(Math.max(0, g), 1);
1149 b = Math.min(Math.max(0, b), 1);
1150
1151 return [r * 255, g * 255, b * 255];
1152 }
1153
1154 function xyz2lab(xyz) {
1155 var x = xyz[0],
1156 y = xyz[1],
1157 z = xyz[2],
1158 l, a, b;
1159
1160 x /= 95.047;
1161 y /= 100;
1162 z /= 108.883;
1163
1164 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
1165 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
1166 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
1167
1168 l = (116 * y) - 16;
1169 a = 500 * (x - y);
1170 b = 200 * (y - z);
1171
1172 return [l, a, b];
1173 }
1174
1175 function xyz2lch(args) {
1176 return lab2lch(xyz2lab(args));
1177 }
1178
1179 function lab2xyz(lab) {
1180 var l = lab[0],
1181 a = lab[1],
1182 b = lab[2],
1183 x, y, z, y2;
1184
1185 if (l <= 8) {
1186 y = (l * 100) / 903.3;
1187 y2 = (7.787 * (y / 100)) + (16 / 116);
1188 } else {
1189 y = 100 * Math.pow((l + 16) / 116, 3);
1190 y2 = Math.pow(y / 100, 1/3);
1191 }
1192
1193 x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
1194
1195 z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
1196
1197 return [x, y, z];
1198 }
1199
1200 function lab2lch(lab) {
1201 var l = lab[0],
1202 a = lab[1],
1203 b = lab[2],
1204 hr, h, c;
1205
1206 hr = Math.atan2(b, a);
1207 h = hr * 360 / 2 / Math.PI;
1208 if (h < 0) {
1209 h += 360;
1210 }
1211 c = Math.sqrt(a * a + b * b);
1212 return [l, c, h];
1213 }
1214
1215 function lab2rgb(args) {
1216 return xyz2rgb(lab2xyz(args));
1217 }
1218
1219 function lch2lab(lch) {
1220 var l = lch[0],
1221 c = lch[1],
1222 h = lch[2],
1223 a, b, hr;
1224
1225 hr = h / 360 * 2 * Math.PI;
1226 a = c * Math.cos(hr);
1227 b = c * Math.sin(hr);
1228 return [l, a, b];
1229 }
1230
1231 function lch2xyz(args) {
1232 return lab2xyz(lch2lab(args));
1233 }
1234
1235 function lch2rgb(args) {
1236 return lab2rgb(lch2lab(args));
1237 }
1238
1239 function keyword2rgb(keyword) {
1240 return cssKeywords[keyword];
1241 }
1242
1243 function keyword2hsl(args) {
1244 return rgb2hsl(keyword2rgb(args));
1245 }
1246
1247 function keyword2hsv(args) {
1248 return rgb2hsv(keyword2rgb(args));
1249 }
1250
1251 function keyword2hwb(args) {
1252 return rgb2hwb(keyword2rgb(args));
1253 }
1254
1255 function keyword2cmyk(args) {
1256 return rgb2cmyk(keyword2rgb(args));
1257 }
1258
1259 function keyword2lab(args) {
1260 return rgb2lab(keyword2rgb(args));
1261 }
1262
1263 function keyword2xyz(args) {
1264 return rgb2xyz(keyword2rgb(args));
1265 }
1266
1267 var cssKeywords = {
1268 aliceblue: [240,248,255],
1269 antiquewhite: [250,235,215],
1270 aqua: [0,255,255],
1271 aquamarine: [127,255,212],
1272 azure: [240,255,255],
1273 beige: [245,245,220],
1274 bisque: [255,228,196],
1275 black: [0,0,0],
1276 blanchedalmond: [255,235,205],
1277 blue: [0,0,255],
1278 blueviolet: [138,43,226],
1279 brown: [165,42,42],
1280 burlywood: [222,184,135],
1281 cadetblue: [95,158,160],
1282 chartreuse: [127,255,0],
1283 chocolate: [210,105,30],
1284 coral: [255,127,80],
1285 cornflowerblue: [100,149,237],
1286 cornsilk: [255,248,220],
1287 crimson: [220,20,60],
1288 cyan: [0,255,255],
1289 darkblue: [0,0,139],
1290 darkcyan: [0,139,139],
1291 darkgoldenrod: [184,134,11],
1292 darkgray: [169,169,169],
1293 darkgreen: [0,100,0],
1294 darkgrey: [169,169,169],
1295 darkkhaki: [189,183,107],
1296 darkmagenta: [139,0,139],
1297 darkolivegreen: [85,107,47],
1298 darkorange: [255,140,0],
1299 darkorchid: [153,50,204],
1300 darkred: [139,0,0],
1301 darksalmon: [233,150,122],
1302 darkseagreen: [143,188,143],
1303 darkslateblue: [72,61,139],
1304 darkslategray: [47,79,79],
1305 darkslategrey: [47,79,79],
1306 darkturquoise: [0,206,209],
1307 darkviolet: [148,0,211],
1308 deeppink: [255,20,147],
1309 deepskyblue: [0,191,255],
1310 dimgray: [105,105,105],
1311 dimgrey: [105,105,105],
1312 dodgerblue: [30,144,255],
1313 firebrick: [178,34,34],
1314 floralwhite: [255,250,240],
1315 forestgreen: [34,139,34],
1316 fuchsia: [255,0,255],
1317 gainsboro: [220,220,220],
1318 ghostwhite: [248,248,255],
1319 gold: [255,215,0],
1320 goldenrod: [218,165,32],
1321 gray: [128,128,128],
1322 green: [0,128,0],
1323 greenyellow: [173,255,47],
1324 grey: [128,128,128],
1325 honeydew: [240,255,240],
1326 hotpink: [255,105,180],
1327 indianred: [205,92,92],
1328 indigo: [75,0,130],
1329 ivory: [255,255,240],
1330 khaki: [240,230,140],
1331 lavender: [230,230,250],
1332 lavenderblush: [255,240,245],
1333 lawngreen: [124,252,0],
1334 lemonchiffon: [255,250,205],
1335 lightblue: [173,216,230],
1336 lightcoral: [240,128,128],
1337 lightcyan: [224,255,255],
1338 lightgoldenrodyellow: [250,250,210],
1339 lightgray: [211,211,211],
1340 lightgreen: [144,238,144],
1341 lightgrey: [211,211,211],
1342 lightpink: [255,182,193],
1343 lightsalmon: [255,160,122],
1344 lightseagreen: [32,178,170],
1345 lightskyblue: [135,206,250],
1346 lightslategray: [119,136,153],
1347 lightslategrey: [119,136,153],
1348 lightsteelblue: [176,196,222],
1349 lightyellow: [255,255,224],
1350 lime: [0,255,0],
1351 limegreen: [50,205,50],
1352 linen: [250,240,230],
1353 magenta: [255,0,255],
1354 maroon: [128,0,0],
1355 mediumaquamarine: [102,205,170],
1356 mediumblue: [0,0,205],
1357 mediumorchid: [186,85,211],
1358 mediumpurple: [147,112,219],
1359 mediumseagreen: [60,179,113],
1360 mediumslateblue: [123,104,238],
1361 mediumspringgreen: [0,250,154],
1362 mediumturquoise: [72,209,204],
1363 mediumvioletred: [199,21,133],
1364 midnightblue: [25,25,112],
1365 mintcream: [245,255,250],
1366 mistyrose: [255,228,225],
1367 moccasin: [255,228,181],
1368 navajowhite: [255,222,173],
1369 navy: [0,0,128],
1370 oldlace: [253,245,230],
1371 olive: [128,128,0],
1372 olivedrab: [107,142,35],
1373 orange: [255,165,0],
1374 orangered: [255,69,0],
1375 orchid: [218,112,214],
1376 palegoldenrod: [238,232,170],
1377 palegreen: [152,251,152],
1378 paleturquoise: [175,238,238],
1379 palevioletred: [219,112,147],
1380 papayawhip: [255,239,213],
1381 peachpuff: [255,218,185],
1382 peru: [205,133,63],
1383 pink: [255,192,203],
1384 plum: [221,160,221],
1385 powderblue: [176,224,230],
1386 purple: [128,0,128],
1387 rebeccapurple: [102, 51, 153],
1388 red: [255,0,0],
1389 rosybrown: [188,143,143],
1390 royalblue: [65,105,225],
1391 saddlebrown: [139,69,19],
1392 salmon: [250,128,114],
1393 sandybrown: [244,164,96],
1394 seagreen: [46,139,87],
1395 seashell: [255,245,238],
1396 sienna: [160,82,45],
1397 silver: [192,192,192],
1398 skyblue: [135,206,235],
1399 slateblue: [106,90,205],
1400 slategray: [112,128,144],
1401 slategrey: [112,128,144],
1402 snow: [255,250,250],
1403 springgreen: [0,255,127],
1404 steelblue: [70,130,180],
1405 tan: [210,180,140],
1406 teal: [0,128,128],
1407 thistle: [216,191,216],
1408 tomato: [255,99,71],
1409 turquoise: [64,224,208],
1410 violet: [238,130,238],
1411 wheat: [245,222,179],
1412 white: [255,255,255],
1413 whitesmoke: [245,245,245],
1414 yellow: [255,255,0],
1415 yellowgreen: [154,205,50]
1416 };
1417
1418 var reverseKeywords = {};
1419 for (var key in cssKeywords) {
1420 reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
1421 }
1422
1423 },{}],5:[function(require,module,exports){
1424 var conversions = require(4);
1425
1426 var convert = function() {
1427 return new Converter();
1428 }
1429
1430 for (var func in conversions) {
1431 // export Raw versions
1432 convert[func + "Raw"] = (function(func) {
1433 // accept array or plain args
1434 return function(arg) {
1435 if (typeof arg == "number")
1436 arg = Array.prototype.slice.call(arguments);
1437 return conversions[func](arg);
1438 }
1439 })(func);
1440
1441 var pair = /(\w+)2(\w+)/.exec(func),
1442 from = pair[1],
1443 to = pair[2];
1444
1445 // export rgb2hsl and ["rgb"]["hsl"]
1446 convert[from] = convert[from] || {};
1447
1448 convert[from][to] = convert[func] = (function(func) {
1449 return function(arg) {
1450 if (typeof arg == "number")
1451 arg = Array.prototype.slice.call(arguments);
1452
1453 var val = conversions[func](arg);
1454 if (typeof val == "string" || val === undefined)
1455 return val; // keyword
1456
1457 for (var i = 0; i < val.length; i++)
1458 val[i] = Math.round(val[i]);
1459 return val;
1460 }
1461 })(func);
1462 }
1463
1464
1465 /* Converter does lazy conversion and caching */
1466 var Converter = function() {
1467 this.convs = {};
1468 };
1469
1470 /* Either get the values for a space or
1471 set the values for a space, depending on args */
1472 Converter.prototype.routeSpace = function(space, args) {
1473 var values = args[0];
1474 if (values === undefined) {
1475 // color.rgb()
1476 return this.getValues(space);
1477 }
1478 // color.rgb(10, 10, 10)
1479 if (typeof values == "number") {
1480 values = Array.prototype.slice.call(args);
1481 }
1482
1483 return this.setValues(space, values);
1484 };
1485
1486 /* Set the values for a space, invalidating cache */
1487 Converter.prototype.setValues = function(space, values) {
1488 this.space = space;
1489 this.convs = {};
1490 this.convs[space] = values;
1491 return this;
1492 };
1493
1494 /* Get the values for a space. If there's already
1495 a conversion for the space, fetch it, otherwise
1496 compute it */
1497 Converter.prototype.getValues = function(space) {
1498 var vals = this.convs[space];
1499 if (!vals) {
1500 var fspace = this.space,
1501 from = this.convs[fspace];
1502 vals = convert[fspace][space](from);
1503
1504 this.convs[space] = vals;
1505 }
1506 return vals;
1507 };
1508
1509 ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
1510 Converter.prototype[space] = function(vals) {
1511 return this.routeSpace(space, arguments);
1512 }
1513 });
1514
1515 module.exports = convert;
1516 },{"4":4}],6:[function(require,module,exports){
1517 'use strict'
1518
1519 module.exports = {
1520 "aliceblue": [240, 248, 255],
1521 "antiquewhite": [250, 235, 215],
1522 "aqua": [0, 255, 255],
1523 "aquamarine": [127, 255, 212],
1524 "azure": [240, 255, 255],
1525 "beige": [245, 245, 220],
1526 "bisque": [255, 228, 196],
1527 "black": [0, 0, 0],
1528 "blanchedalmond": [255, 235, 205],
1529 "blue": [0, 0, 255],
1530 "blueviolet": [138, 43, 226],
1531 "brown": [165, 42, 42],
1532 "burlywood": [222, 184, 135],
1533 "cadetblue": [95, 158, 160],
1534 "chartreuse": [127, 255, 0],
1535 "chocolate": [210, 105, 30],
1536 "coral": [255, 127, 80],
1537 "cornflowerblue": [100, 149, 237],
1538 "cornsilk": [255, 248, 220],
1539 "crimson": [220, 20, 60],
1540 "cyan": [0, 255, 255],
1541 "darkblue": [0, 0, 139],
1542 "darkcyan": [0, 139, 139],
1543 "darkgoldenrod": [184, 134, 11],
1544 "darkgray": [169, 169, 169],
1545 "darkgreen": [0, 100, 0],
1546 "darkgrey": [169, 169, 169],
1547 "darkkhaki": [189, 183, 107],
1548 "darkmagenta": [139, 0, 139],
1549 "darkolivegreen": [85, 107, 47],
1550 "darkorange": [255, 140, 0],
1551 "darkorchid": [153, 50, 204],
1552 "darkred": [139, 0, 0],
1553 "darksalmon": [233, 150, 122],
1554 "darkseagreen": [143, 188, 143],
1555 "darkslateblue": [72, 61, 139],
1556 "darkslategray": [47, 79, 79],
1557 "darkslategrey": [47, 79, 79],
1558 "darkturquoise": [0, 206, 209],
1559 "darkviolet": [148, 0, 211],
1560 "deeppink": [255, 20, 147],
1561 "deepskyblue": [0, 191, 255],
1562 "dimgray": [105, 105, 105],
1563 "dimgrey": [105, 105, 105],
1564 "dodgerblue": [30, 144, 255],
1565 "firebrick": [178, 34, 34],
1566 "floralwhite": [255, 250, 240],
1567 "forestgreen": [34, 139, 34],
1568 "fuchsia": [255, 0, 255],
1569 "gainsboro": [220, 220, 220],
1570 "ghostwhite": [248, 248, 255],
1571 "gold": [255, 215, 0],
1572 "goldenrod": [218, 165, 32],
1573 "gray": [128, 128, 128],
1574 "green": [0, 128, 0],
1575 "greenyellow": [173, 255, 47],
1576 "grey": [128, 128, 128],
1577 "honeydew": [240, 255, 240],
1578 "hotpink": [255, 105, 180],
1579 "indianred": [205, 92, 92],
1580 "indigo": [75, 0, 130],
1581 "ivory": [255, 255, 240],
1582 "khaki": [240, 230, 140],
1583 "lavender": [230, 230, 250],
1584 "lavenderblush": [255, 240, 245],
1585 "lawngreen": [124, 252, 0],
1586 "lemonchiffon": [255, 250, 205],
1587 "lightblue": [173, 216, 230],
1588 "lightcoral": [240, 128, 128],
1589 "lightcyan": [224, 255, 255],
1590 "lightgoldenrodyellow": [250, 250, 210],
1591 "lightgray": [211, 211, 211],
1592 "lightgreen": [144, 238, 144],
1593 "lightgrey": [211, 211, 211],
1594 "lightpink": [255, 182, 193],
1595 "lightsalmon": [255, 160, 122],
1596 "lightseagreen": [32, 178, 170],
1597 "lightskyblue": [135, 206, 250],
1598 "lightslategray": [119, 136, 153],
1599 "lightslategrey": [119, 136, 153],
1600 "lightsteelblue": [176, 196, 222],
1601 "lightyellow": [255, 255, 224],
1602 "lime": [0, 255, 0],
1603 "limegreen": [50, 205, 50],
1604 "linen": [250, 240, 230],
1605 "magenta": [255, 0, 255],
1606 "maroon": [128, 0, 0],
1607 "mediumaquamarine": [102, 205, 170],
1608 "mediumblue": [0, 0, 205],
1609 "mediumorchid": [186, 85, 211],
1610 "mediumpurple": [147, 112, 219],
1611 "mediumseagreen": [60, 179, 113],
1612 "mediumslateblue": [123, 104, 238],
1613 "mediumspringgreen": [0, 250, 154],
1614 "mediumturquoise": [72, 209, 204],
1615 "mediumvioletred": [199, 21, 133],
1616 "midnightblue": [25, 25, 112],
1617 "mintcream": [245, 255, 250],
1618 "mistyrose": [255, 228, 225],
1619 "moccasin": [255, 228, 181],
1620 "navajowhite": [255, 222, 173],
1621 "navy": [0, 0, 128],
1622 "oldlace": [253, 245, 230],
1623 "olive": [128, 128, 0],
1624 "olivedrab": [107, 142, 35],
1625 "orange": [255, 165, 0],
1626 "orangered": [255, 69, 0],
1627 "orchid": [218, 112, 214],
1628 "palegoldenrod": [238, 232, 170],
1629 "palegreen": [152, 251, 152],
1630 "paleturquoise": [175, 238, 238],
1631 "palevioletred": [219, 112, 147],
1632 "papayawhip": [255, 239, 213],
1633 "peachpuff": [255, 218, 185],
1634 "peru": [205, 133, 63],
1635 "pink": [255, 192, 203],
1636 "plum": [221, 160, 221],
1637 "powderblue": [176, 224, 230],
1638 "purple": [128, 0, 128],
1639 "rebeccapurple": [102, 51, 153],
1640 "red": [255, 0, 0],
1641 "rosybrown": [188, 143, 143],
1642 "royalblue": [65, 105, 225],
1643 "saddlebrown": [139, 69, 19],
1644 "salmon": [250, 128, 114],
1645 "sandybrown": [244, 164, 96],
1646 "seagreen": [46, 139, 87],
1647 "seashell": [255, 245, 238],
1648 "sienna": [160, 82, 45],
1649 "silver": [192, 192, 192],
1650 "skyblue": [135, 206, 235],
1651 "slateblue": [106, 90, 205],
1652 "slategray": [112, 128, 144],
1653 "slategrey": [112, 128, 144],
1654 "snow": [255, 250, 250],
1655 "springgreen": [0, 255, 127],
1656 "steelblue": [70, 130, 180],
1657 "tan": [210, 180, 140],
1658 "teal": [0, 128, 128],
1659 "thistle": [216, 191, 216],
1660 "tomato": [255, 99, 71],
1661 "turquoise": [64, 224, 208],
1662 "violet": [238, 130, 238],
1663 "wheat": [245, 222, 179],
1664 "white": [255, 255, 255],
1665 "whitesmoke": [245, 245, 245],
1666 "yellow": [255, 255, 0],
1667 "yellowgreen": [154, 205, 50]
1668 };
1669
1670 },{}],7:[function(require,module,exports){
1671 /**
1672 * @namespace Chart
1673 */
1674 var Chart = require(29)();
1675
1676 Chart.helpers = require(45);
1677
1678 // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
1679 require(27)(Chart);
1680
1681 Chart.defaults = require(25);
1682 Chart.Element = require(26);
1683 Chart.elements = require(40);
1684 Chart.Interaction = require(28);
1685 Chart.platform = require(48);
1686
1687 require(31)(Chart);
1688 require(22)(Chart);
1689 require(23)(Chart);
1690 require(24)(Chart);
1691 require(30)(Chart);
1692 require(33)(Chart);
1693 require(32)(Chart);
1694 require(35)(Chart);
1695
1696 require(54)(Chart);
1697 require(52)(Chart);
1698 require(53)(Chart);
1699 require(55)(Chart);
1700 require(56)(Chart);
1701 require(57)(Chart);
1702
1703 // Controllers must be loaded after elements
1704 // See Chart.core.datasetController.dataElementType
1705 require(15)(Chart);
1706 require(16)(Chart);
1707 require(17)(Chart);
1708 require(18)(Chart);
1709 require(19)(Chart);
1710 require(20)(Chart);
1711 require(21)(Chart);
1712
1713 require(8)(Chart);
1714 require(9)(Chart);
1715 require(10)(Chart);
1716 require(11)(Chart);
1717 require(12)(Chart);
1718 require(13)(Chart);
1719 require(14)(Chart);
1720
1721 // Loading built-it plugins
1722 var plugins = [];
1723
1724 plugins.push(
1725 require(49)(Chart),
1726 require(50)(Chart),
1727 require(51)(Chart)
1728 );
1729
1730 Chart.plugins.register(plugins);
1731
1732 Chart.platform.initialize();
1733
1734 module.exports = Chart;
1735 if (typeof window !== 'undefined') {
1736 window.imagify.Chart = Chart;
1737 }
1738
1739 // DEPRECATIONS
1740
1741 /**
1742 * Provided for backward compatibility, use Chart.helpers.canvas instead.
1743 * @namespace Chart.canvasHelpers
1744 * @deprecated since version 2.6.0
1745 * @todo remove at version 3
1746 * @private
1747 */
1748 Chart.canvasHelpers = Chart.helpers.canvas;
1749
1750 },{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"35":35,"40":40,"45":45,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"8":8,"9":9}],8:[function(require,module,exports){
1751 'use strict';
1752
1753 module.exports = function(Chart) {
1754
1755 Chart.Bar = function(context, config) {
1756 config.type = 'bar';
1757
1758 return new Chart(context, config);
1759 };
1760
1761 };
1762
1763 },{}],9:[function(require,module,exports){
1764 'use strict';
1765
1766 module.exports = function(Chart) {
1767
1768 Chart.Bubble = function(context, config) {
1769 config.type = 'bubble';
1770 return new Chart(context, config);
1771 };
1772
1773 };
1774
1775 },{}],10:[function(require,module,exports){
1776 'use strict';
1777
1778 module.exports = function(Chart) {
1779
1780 Chart.Doughnut = function(context, config) {
1781 config.type = 'doughnut';
1782
1783 return new Chart(context, config);
1784 };
1785
1786 };
1787
1788 },{}],11:[function(require,module,exports){
1789 'use strict';
1790
1791 module.exports = function(Chart) {
1792
1793 Chart.Line = function(context, config) {
1794 config.type = 'line';
1795
1796 return new Chart(context, config);
1797 };
1798
1799 };
1800
1801 },{}],12:[function(require,module,exports){
1802 'use strict';
1803
1804 module.exports = function(Chart) {
1805
1806 Chart.PolarArea = function(context, config) {
1807 config.type = 'polarArea';
1808
1809 return new Chart(context, config);
1810 };
1811
1812 };
1813
1814 },{}],13:[function(require,module,exports){
1815 'use strict';
1816
1817 module.exports = function(Chart) {
1818
1819 Chart.Radar = function(context, config) {
1820 config.type = 'radar';
1821
1822 return new Chart(context, config);
1823 };
1824
1825 };
1826
1827 },{}],14:[function(require,module,exports){
1828 'use strict';
1829
1830 module.exports = function(Chart) {
1831 Chart.Scatter = function(context, config) {
1832 config.type = 'scatter';
1833 return new Chart(context, config);
1834 };
1835 };
1836
1837 },{}],15:[function(require,module,exports){
1838 'use strict';
1839
1840 var defaults = require(25);
1841 var elements = require(40);
1842 var helpers = require(45);
1843
1844 defaults._set('bar', {
1845 hover: {
1846 mode: 'label'
1847 },
1848
1849 scales: {
1850 xAxes: [{
1851 type: 'category',
1852
1853 // Specific to Bar Controller
1854 categoryPercentage: 0.8,
1855 barPercentage: 0.9,
1856
1857 // offset settings
1858 offset: true,
1859
1860 // grid line settings
1861 gridLines: {
1862 offsetGridLines: true
1863 }
1864 }],
1865
1866 yAxes: [{
1867 type: 'linear'
1868 }]
1869 }
1870 });
1871
1872 defaults._set('horizontalBar', {
1873 hover: {
1874 mode: 'index',
1875 axis: 'y'
1876 },
1877
1878 scales: {
1879 xAxes: [{
1880 type: 'linear',
1881 position: 'bottom'
1882 }],
1883
1884 yAxes: [{
1885 position: 'left',
1886 type: 'category',
1887
1888 // Specific to Horizontal Bar Controller
1889 categoryPercentage: 0.8,
1890 barPercentage: 0.9,
1891
1892 // offset settings
1893 offset: true,
1894
1895 // grid line settings
1896 gridLines: {
1897 offsetGridLines: true
1898 }
1899 }]
1900 },
1901
1902 elements: {
1903 rectangle: {
1904 borderSkipped: 'left'
1905 }
1906 },
1907
1908 tooltips: {
1909 callbacks: {
1910 title: function(item, data) {
1911 // Pick first xLabel for now
1912 var title = '';
1913
1914 if (item.length > 0) {
1915 if (item[0].yLabel) {
1916 title = item[0].yLabel;
1917 } else if (data.labels.length > 0 && item[0].index < data.labels.length) {
1918 title = data.labels[item[0].index];
1919 }
1920 }
1921
1922 return title;
1923 },
1924
1925 label: function(item, data) {
1926 var datasetLabel = data.datasets[item.datasetIndex].label || '';
1927 return datasetLabel + ': ' + item.xLabel;
1928 }
1929 },
1930 mode: 'index',
1931 axis: 'y'
1932 }
1933 });
1934
1935 module.exports = function(Chart) {
1936
1937 Chart.controllers.bar = Chart.DatasetController.extend({
1938
1939 dataElementType: elements.Rectangle,
1940
1941 initialize: function() {
1942 var me = this;
1943 var meta;
1944
1945 Chart.DatasetController.prototype.initialize.apply(me, arguments);
1946
1947 meta = me.getMeta();
1948 meta.stack = me.getDataset().stack;
1949 meta.bar = true;
1950 },
1951
1952 update: function(reset) {
1953 var me = this;
1954 var rects = me.getMeta().data;
1955 var i, ilen;
1956
1957 me._ruler = me.getRuler();
1958
1959 for (i = 0, ilen = rects.length; i < ilen; ++i) {
1960 me.updateElement(rects[i], i, reset);
1961 }
1962 },
1963
1964 updateElement: function(rectangle, index, reset) {
1965 var me = this;
1966 var chart = me.chart;
1967 var meta = me.getMeta();
1968 var dataset = me.getDataset();
1969 var custom = rectangle.custom || {};
1970 var rectangleOptions = chart.options.elements.rectangle;
1971
1972 rectangle._xScale = me.getScaleForId(meta.xAxisID);
1973 rectangle._yScale = me.getScaleForId(meta.yAxisID);
1974 rectangle._datasetIndex = me.index;
1975 rectangle._index = index;
1976
1977 rectangle._model = {
1978 datasetLabel: dataset.label,
1979 label: chart.data.labels[index],
1980 borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped,
1981 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor),
1982 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor),
1983 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth)
1984 };
1985
1986 me.updateElementGeometry(rectangle, index, reset);
1987
1988 rectangle.pivot();
1989 },
1990
1991 /**
1992 * @private
1993 */
1994 updateElementGeometry: function(rectangle, index, reset) {
1995 var me = this;
1996 var model = rectangle._model;
1997 var vscale = me.getValueScale();
1998 var base = vscale.getBasePixel();
1999 var horizontal = vscale.isHorizontal();
2000 var ruler = me._ruler || me.getRuler();
2001 var vpixels = me.calculateBarValuePixels(me.index, index);
2002 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
2003
2004 model.horizontal = horizontal;
2005 model.base = reset ? base : vpixels.base;
2006 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
2007 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
2008 model.height = horizontal ? ipixels.size : undefined;
2009 model.width = horizontal ? undefined : ipixels.size;
2010 },
2011
2012 /**
2013 * @private
2014 */
2015 getValueScaleId: function() {
2016 return this.getMeta().yAxisID;
2017 },
2018
2019 /**
2020 * @private
2021 */
2022 getIndexScaleId: function() {
2023 return this.getMeta().xAxisID;
2024 },
2025
2026 /**
2027 * @private
2028 */
2029 getValueScale: function() {
2030 return this.getScaleForId(this.getValueScaleId());
2031 },
2032
2033 /**
2034 * @private
2035 */
2036 getIndexScale: function() {
2037 return this.getScaleForId(this.getIndexScaleId());
2038 },
2039
2040 /**
2041 * Returns the effective number of stacks based on groups and bar visibility.
2042 * @private
2043 */
2044 getStackCount: function(last) {
2045 var me = this;
2046 var chart = me.chart;
2047 var scale = me.getIndexScale();
2048 var stacked = scale.options.stacked;
2049 var ilen = last === undefined ? chart.data.datasets.length : last + 1;
2050 var stacks = [];
2051 var i, meta;
2052
2053 for (i = 0; i < ilen; ++i) {
2054 meta = chart.getDatasetMeta(i);
2055 if (meta.bar && chart.isDatasetVisible(i) &&
2056 (stacked === false ||
2057 (stacked === true && stacks.indexOf(meta.stack) === -1) ||
2058 (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
2059 stacks.push(meta.stack);
2060 }
2061 }
2062
2063 return stacks.length;
2064 },
2065
2066 /**
2067 * Returns the stack index for the given dataset based on groups and bar visibility.
2068 * @private
2069 */
2070 getStackIndex: function(datasetIndex) {
2071 return this.getStackCount(datasetIndex) - 1;
2072 },
2073
2074 /**
2075 * @private
2076 */
2077 getRuler: function() {
2078 var me = this;
2079 var scale = me.getIndexScale();
2080 var stackCount = me.getStackCount();
2081 var datasetIndex = me.index;
2082 var pixels = [];
2083 var isHorizontal = scale.isHorizontal();
2084 var start = isHorizontal ? scale.left : scale.top;
2085 var end = start + (isHorizontal ? scale.width : scale.height);
2086 var i, ilen;
2087
2088 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
2089 pixels.push(scale.getPixelForValue(null, i, datasetIndex));
2090 }
2091
2092 return {
2093 pixels: pixels,
2094 start: start,
2095 end: end,
2096 stackCount: stackCount,
2097 scale: scale
2098 };
2099 },
2100
2101 /**
2102 * Note: pixel values are not clamped to the scale area.
2103 * @private
2104 */
2105 calculateBarValuePixels: function(datasetIndex, index) {
2106 var me = this;
2107 var chart = me.chart;
2108 var meta = me.getMeta();
2109 var scale = me.getValueScale();
2110 var datasets = chart.data.datasets;
2111 var value = scale.getRightValue(datasets[datasetIndex].data[index]);
2112 var stacked = scale.options.stacked;
2113 var stack = meta.stack;
2114 var start = 0;
2115 var i, imeta, ivalue, base, head, size;
2116
2117 if (stacked || (stacked === undefined && stack !== undefined)) {
2118 for (i = 0; i < datasetIndex; ++i) {
2119 imeta = chart.getDatasetMeta(i);
2120
2121 if (imeta.bar &&
2122 imeta.stack === stack &&
2123 imeta.controller.getValueScaleId() === scale.id &&
2124 chart.isDatasetVisible(i)) {
2125
2126 ivalue = scale.getRightValue(datasets[i].data[index]);
2127 if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
2128 start += ivalue;
2129 }
2130 }
2131 }
2132 }
2133
2134 base = scale.getPixelForValue(start);
2135 head = scale.getPixelForValue(start + value);
2136 size = (head - base) / 2;
2137
2138 return {
2139 size: size,
2140 base: base,
2141 head: head,
2142 center: head + size / 2
2143 };
2144 },
2145
2146 /**
2147 * @private
2148 */
2149 calculateBarIndexPixels: function(datasetIndex, index, ruler) {
2150 var me = this;
2151 var options = ruler.scale.options;
2152 var stackIndex = me.getStackIndex(datasetIndex);
2153 var pixels = ruler.pixels;
2154 var base = pixels[index];
2155 var length = pixels.length;
2156 var start = ruler.start;
2157 var end = ruler.end;
2158 var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size;
2159
2160 if (length === 1) {
2161 leftSampleSize = base > start ? base - start : end - base;
2162 rightSampleSize = base < end ? end - base : base - start;
2163 } else {
2164 if (index > 0) {
2165 leftSampleSize = (base - pixels[index - 1]) / 2;
2166 if (index === length - 1) {
2167 rightSampleSize = leftSampleSize;
2168 }
2169 }
2170 if (index < length - 1) {
2171 rightSampleSize = (pixels[index + 1] - base) / 2;
2172 if (index === 0) {
2173 leftSampleSize = rightSampleSize;
2174 }
2175 }
2176 }
2177
2178 leftCategorySize = leftSampleSize * options.categoryPercentage;
2179 rightCategorySize = rightSampleSize * options.categoryPercentage;
2180 fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount;
2181 size = fullBarSize * options.barPercentage;
2182
2183 size = Math.min(
2184 helpers.valueOrDefault(options.barThickness, size),
2185 helpers.valueOrDefault(options.maxBarThickness, Infinity));
2186
2187 base -= leftCategorySize;
2188 base += fullBarSize * stackIndex;
2189 base += (fullBarSize - size) / 2;
2190
2191 return {
2192 size: size,
2193 base: base,
2194 head: base + size,
2195 center: base + size / 2
2196 };
2197 },
2198
2199 draw: function() {
2200 var me = this;
2201 var chart = me.chart;
2202 var scale = me.getValueScale();
2203 var rects = me.getMeta().data;
2204 var dataset = me.getDataset();
2205 var ilen = rects.length;
2206 var i = 0;
2207
2208 helpers.canvas.clipArea(chart.ctx, chart.chartArea);
2209
2210 for (; i < ilen; ++i) {
2211 if (!isNaN(scale.getRightValue(dataset.data[i]))) {
2212 rects[i].draw();
2213 }
2214 }
2215
2216 helpers.canvas.unclipArea(chart.ctx);
2217 },
2218
2219 setHoverStyle: function(rectangle) {
2220 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2221 var index = rectangle._index;
2222 var custom = rectangle.custom || {};
2223 var model = rectangle._model;
2224
2225 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
2226 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
2227 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
2228 },
2229
2230 removeHoverStyle: function(rectangle) {
2231 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2232 var index = rectangle._index;
2233 var custom = rectangle.custom || {};
2234 var model = rectangle._model;
2235 var rectangleElementOptions = this.chart.options.elements.rectangle;
2236
2237 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
2238 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
2239 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
2240 }
2241 });
2242
2243 Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
2244 /**
2245 * @private
2246 */
2247 getValueScaleId: function() {
2248 return this.getMeta().xAxisID;
2249 },
2250
2251 /**
2252 * @private
2253 */
2254 getIndexScaleId: function() {
2255 return this.getMeta().yAxisID;
2256 }
2257 });
2258 };
2259
2260 },{"25":25,"40":40,"45":45}],16:[function(require,module,exports){
2261 'use strict';
2262
2263 var defaults = require(25);
2264 var elements = require(40);
2265 var helpers = require(45);
2266
2267 defaults._set('bubble', {
2268 hover: {
2269 mode: 'single'
2270 },
2271
2272 scales: {
2273 xAxes: [{
2274 type: 'linear', // bubble should probably use a linear scale by default
2275 position: 'bottom',
2276 id: 'x-axis-0' // need an ID so datasets can reference the scale
2277 }],
2278 yAxes: [{
2279 type: 'linear',
2280 position: 'left',
2281 id: 'y-axis-0'
2282 }]
2283 },
2284
2285 tooltips: {
2286 callbacks: {
2287 title: function() {
2288 // Title doesn't make sense for scatter since we format the data as a point
2289 return '';
2290 },
2291 label: function(item, data) {
2292 var datasetLabel = data.datasets[item.datasetIndex].label || '';
2293 var dataPoint = data.datasets[item.datasetIndex].data[item.index];
2294 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
2295 }
2296 }
2297 }
2298 });
2299
2300
2301 module.exports = function(Chart) {
2302
2303 Chart.controllers.bubble = Chart.DatasetController.extend({
2304 /**
2305 * @protected
2306 */
2307 dataElementType: elements.Point,
2308
2309 /**
2310 * @protected
2311 */
2312 update: function(reset) {
2313 var me = this;
2314 var meta = me.getMeta();
2315 var points = meta.data;
2316
2317 // Update Points
2318 helpers.each(points, function(point, index) {
2319 me.updateElement(point, index, reset);
2320 });
2321 },
2322
2323 /**
2324 * @protected
2325 */
2326 updateElement: function(point, index, reset) {
2327 var me = this;
2328 var meta = me.getMeta();
2329 var custom = point.custom || {};
2330 var xScale = me.getScaleForId(meta.xAxisID);
2331 var yScale = me.getScaleForId(meta.yAxisID);
2332 var options = me._resolveElementOptions(point, index);
2333 var data = me.getDataset().data[index];
2334 var dsIndex = me.index;
2335
2336 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
2337 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
2338
2339 point._xScale = xScale;
2340 point._yScale = yScale;
2341 point._options = options;
2342 point._datasetIndex = dsIndex;
2343 point._index = index;
2344 point._model = {
2345 backgroundColor: options.backgroundColor,
2346 borderColor: options.borderColor,
2347 borderWidth: options.borderWidth,
2348 hitRadius: options.hitRadius,
2349 pointStyle: options.pointStyle,
2350 radius: reset ? 0 : options.radius,
2351 skip: custom.skip || isNaN(x) || isNaN(y),
2352 x: x,
2353 y: y,
2354 };
2355
2356 point.pivot();
2357 },
2358
2359 /**
2360 * @protected
2361 */
2362 setHoverStyle: function(point) {
2363 var model = point._model;
2364 var options = point._options;
2365
2366 model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
2367 model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
2368 model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
2369 model.radius = options.radius + options.hoverRadius;
2370 },
2371
2372 /**
2373 * @protected
2374 */
2375 removeHoverStyle: function(point) {
2376 var model = point._model;
2377 var options = point._options;
2378
2379 model.backgroundColor = options.backgroundColor;
2380 model.borderColor = options.borderColor;
2381 model.borderWidth = options.borderWidth;
2382 model.radius = options.radius;
2383 },
2384
2385 /**
2386 * @private
2387 */
2388 _resolveElementOptions: function(point, index) {
2389 var me = this;
2390 var chart = me.chart;
2391 var datasets = chart.data.datasets;
2392 var dataset = datasets[me.index];
2393 var custom = point.custom || {};
2394 var options = chart.options.elements.point;
2395 var resolve = helpers.options.resolve;
2396 var data = dataset.data[index];
2397 var values = {};
2398 var i, ilen, key;
2399
2400 // Scriptable options
2401 var context = {
2402 chart: chart,
2403 dataIndex: index,
2404 dataset: dataset,
2405 datasetIndex: me.index
2406 };
2407
2408 var keys = [
2409 'backgroundColor',
2410 'borderColor',
2411 'borderWidth',
2412 'hoverBackgroundColor',
2413 'hoverBorderColor',
2414 'hoverBorderWidth',
2415 'hoverRadius',
2416 'hitRadius',
2417 'pointStyle'
2418 ];
2419
2420 for (i = 0, ilen = keys.length; i < ilen; ++i) {
2421 key = keys[i];
2422 values[key] = resolve([
2423 custom[key],
2424 dataset[key],
2425 options[key]
2426 ], context, index);
2427 }
2428
2429 // Custom radius resolution
2430 values.radius = resolve([
2431 custom.radius,
2432 data ? data.r : undefined,
2433 dataset.radius,
2434 options.radius
2435 ], context, index);
2436
2437 return values;
2438 }
2439 });
2440 };
2441
2442 },{"25":25,"40":40,"45":45}],17:[function(require,module,exports){
2443 'use strict';
2444
2445 var defaults = require(25);
2446 var elements = require(40);
2447 var helpers = require(45);
2448
2449 defaults._set('doughnut', {
2450 animation: {
2451 // Boolean - Whether we animate the rotation of the Doughnut
2452 animateRotate: true,
2453 // Boolean - Whether we animate scaling the Doughnut from the centre
2454 animateScale: false
2455 },
2456 hover: {
2457 mode: 'single'
2458 },
2459 legendCallback: function(chart) {
2460 var text = [];
2461 text.push('<ul class="' + chart.id + '-legend">');
2462
2463 var data = chart.data;
2464 var datasets = data.datasets;
2465 var labels = data.labels;
2466
2467 if (datasets.length) {
2468 for (var i = 0; i < datasets[0].data.length; ++i) {
2469 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
2470 if (labels[i]) {
2471 text.push(labels[i]);
2472 }
2473 text.push('</li>');
2474 }
2475 }
2476
2477 text.push('</ul>');
2478 return text.join('');
2479 },
2480 legend: {
2481 labels: {
2482 generateLabels: function(chart) {
2483 var data = chart.data;
2484 if (data.labels.length && data.datasets.length) {
2485 return data.labels.map(function(label, i) {
2486 var meta = chart.getDatasetMeta(0);
2487 var ds = data.datasets[0];
2488 var arc = meta.data[i];
2489 var custom = arc && arc.custom || {};
2490 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2491 var arcOpts = chart.options.elements.arc;
2492 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
2493 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
2494 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
2495
2496 return {
2497 text: label,
2498 fillStyle: fill,
2499 strokeStyle: stroke,
2500 lineWidth: bw,
2501 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
2502
2503 // Extra data used for toggling the correct item
2504 index: i
2505 };
2506 });
2507 }
2508 return [];
2509 }
2510 },
2511
2512 onClick: function(e, legendItem) {
2513 var index = legendItem.index;
2514 var chart = this.chart;
2515 var i, ilen, meta;
2516
2517 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
2518 meta = chart.getDatasetMeta(i);
2519 // toggle visibility of index if exists
2520 if (meta.data[index]) {
2521 meta.data[index].hidden = !meta.data[index].hidden;
2522 }
2523 }
2524
2525 chart.update();
2526 }
2527 },
2528
2529 // The percentage of the chart that we cut out of the middle.
2530 cutoutPercentage: 50,
2531
2532 // The rotation of the chart, where the first data arc begins.
2533 rotation: Math.PI * -0.5,
2534
2535 // The total circumference of the chart.
2536 circumference: Math.PI * 2.0,
2537
2538 // Need to override these to give a nice default
2539 tooltips: {
2540 callbacks: {
2541 title: function() {
2542 return '';
2543 },
2544 label: function(tooltipItem, data) {
2545 var dataLabel = data.labels[tooltipItem.index];
2546 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2547
2548 if (helpers.isArray(dataLabel)) {
2549 // show value on first line of multiline label
2550 // need to clone because we are changing the value
2551 dataLabel = dataLabel.slice();
2552 dataLabel[0] += value;
2553 } else {
2554 dataLabel += value;
2555 }
2556
2557 return dataLabel;
2558 }
2559 }
2560 }
2561 });
2562
2563 defaults._set('pie', helpers.clone(defaults.doughnut));
2564 defaults._set('pie', {
2565 cutoutPercentage: 0
2566 });
2567
2568 module.exports = function(Chart) {
2569
2570 Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
2571
2572 dataElementType: elements.Arc,
2573
2574 linkScales: helpers.noop,
2575
2576 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
2577 getRingIndex: function(datasetIndex) {
2578 var ringIndex = 0;
2579
2580 for (var j = 0; j < datasetIndex; ++j) {
2581 if (this.chart.isDatasetVisible(j)) {
2582 ++ringIndex;
2583 }
2584 }
2585
2586 return ringIndex;
2587 },
2588
2589 update: function(reset) {
2590 var me = this;
2591 var chart = me.chart;
2592 var chartArea = chart.chartArea;
2593 var opts = chart.options;
2594 var arcOpts = opts.elements.arc;
2595 var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
2596 var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
2597 var minSize = Math.min(availableWidth, availableHeight);
2598 var offset = {x: 0, y: 0};
2599 var meta = me.getMeta();
2600 var cutoutPercentage = opts.cutoutPercentage;
2601 var circumference = opts.circumference;
2602
2603 // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
2604 if (circumference < Math.PI * 2.0) {
2605 var startAngle = opts.rotation % (Math.PI * 2.0);
2606 startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
2607 var endAngle = startAngle + circumference;
2608 var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
2609 var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
2610 var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
2611 var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
2612 var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
2613 var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
2614 var cutout = cutoutPercentage / 100.0;
2615 var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
2616 var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
2617 var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
2618 minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
2619 offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
2620 }
2621
2622 chart.borderWidth = me.getMaxBorderWidth(meta.data);
2623 chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
2624 chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
2625 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
2626 chart.offsetX = offset.x * chart.outerRadius;
2627 chart.offsetY = offset.y * chart.outerRadius;
2628
2629 meta.total = me.calculateTotal();
2630
2631 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
2632 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
2633
2634 helpers.each(meta.data, function(arc, index) {
2635 me.updateElement(arc, index, reset);
2636 });
2637 },
2638
2639 updateElement: function(arc, index, reset) {
2640 var me = this;
2641 var chart = me.chart;
2642 var chartArea = chart.chartArea;
2643 var opts = chart.options;
2644 var animationOpts = opts.animation;
2645 var centerX = (chartArea.left + chartArea.right) / 2;
2646 var centerY = (chartArea.top + chartArea.bottom) / 2;
2647 var startAngle = opts.rotation; // non reset case handled later
2648 var endAngle = opts.rotation; // non reset case handled later
2649 var dataset = me.getDataset();
2650 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
2651 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
2652 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
2653 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2654
2655 helpers.extend(arc, {
2656 // Utility
2657 _datasetIndex: me.index,
2658 _index: index,
2659
2660 // Desired view properties
2661 _model: {
2662 x: centerX + chart.offsetX,
2663 y: centerY + chart.offsetY,
2664 startAngle: startAngle,
2665 endAngle: endAngle,
2666 circumference: circumference,
2667 outerRadius: outerRadius,
2668 innerRadius: innerRadius,
2669 label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
2670 }
2671 });
2672
2673 var model = arc._model;
2674 // Resets the visual styles
2675 this.removeHoverStyle(arc);
2676
2677 // Set correct angles if not resetting
2678 if (!reset || !animationOpts.animateRotate) {
2679 if (index === 0) {
2680 model.startAngle = opts.rotation;
2681 } else {
2682 model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
2683 }
2684
2685 model.endAngle = model.startAngle + model.circumference;
2686 }
2687
2688 arc.pivot();
2689 },
2690
2691 removeHoverStyle: function(arc) {
2692 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
2693 },
2694
2695 calculateTotal: function() {
2696 var dataset = this.getDataset();
2697 var meta = this.getMeta();
2698 var total = 0;
2699 var value;
2700
2701 helpers.each(meta.data, function(element, index) {
2702 value = dataset.data[index];
2703 if (!isNaN(value) && !element.hidden) {
2704 total += Math.abs(value);
2705 }
2706 });
2707
2708 /* if (total === 0) {
2709 total = NaN;
2710 }*/
2711
2712 return total;
2713 },
2714
2715 calculateCircumference: function(value) {
2716 var total = this.getMeta().total;
2717 if (total > 0 && !isNaN(value)) {
2718 return (Math.PI * 2.0) * (value / total);
2719 }
2720 return 0;
2721 },
2722
2723 // gets the max border or hover width to properly scale pie charts
2724 getMaxBorderWidth: function(arcs) {
2725 var max = 0;
2726 var index = this.index;
2727 var length = arcs.length;
2728 var borderWidth;
2729 var hoverWidth;
2730
2731 for (var i = 0; i < length; i++) {
2732 borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
2733 hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
2734
2735 max = borderWidth > max ? borderWidth : max;
2736 max = hoverWidth > max ? hoverWidth : max;
2737 }
2738 return max;
2739 }
2740 });
2741 };
2742
2743 },{"25":25,"40":40,"45":45}],18:[function(require,module,exports){
2744 'use strict';
2745
2746 var defaults = require(25);
2747 var elements = require(40);
2748 var helpers = require(45);
2749
2750 defaults._set('line', {
2751 showLines: true,
2752 spanGaps: false,
2753
2754 hover: {
2755 mode: 'label'
2756 },
2757
2758 scales: {
2759 xAxes: [{
2760 type: 'category',
2761 id: 'x-axis-0'
2762 }],
2763 yAxes: [{
2764 type: 'linear',
2765 id: 'y-axis-0'
2766 }]
2767 }
2768 });
2769
2770 module.exports = function(Chart) {
2771
2772 function lineEnabled(dataset, options) {
2773 return helpers.valueOrDefault(dataset.showLine, options.showLines);
2774 }
2775
2776 Chart.controllers.line = Chart.DatasetController.extend({
2777
2778 datasetElementType: elements.Line,
2779
2780 dataElementType: elements.Point,
2781
2782 update: function(reset) {
2783 var me = this;
2784 var meta = me.getMeta();
2785 var line = meta.dataset;
2786 var points = meta.data || [];
2787 var options = me.chart.options;
2788 var lineElementOptions = options.elements.line;
2789 var scale = me.getScaleForId(meta.yAxisID);
2790 var i, ilen, custom;
2791 var dataset = me.getDataset();
2792 var showLine = lineEnabled(dataset, options);
2793
2794 // Update Line
2795 if (showLine) {
2796 custom = line.custom || {};
2797
2798 // Compatibility: If the properties are defined with only the old name, use those values
2799 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
2800 dataset.lineTension = dataset.tension;
2801 }
2802
2803 // Utility
2804 line._scale = scale;
2805 line._datasetIndex = me.index;
2806 // Data
2807 line._children = points;
2808 // Model
2809 line._model = {
2810 // Appearance
2811 // The default behavior of lines is to break at null values, according
2812 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
2813 // This option gives lines the ability to span gaps
2814 spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
2815 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
2816 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
2817 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
2818 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
2819 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
2820 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
2821 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
2822 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
2823 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
2824 steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
2825 cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
2826 };
2827
2828 line.pivot();
2829 }
2830
2831 // Update Points
2832 for (i = 0, ilen = points.length; i < ilen; ++i) {
2833 me.updateElement(points[i], i, reset);
2834 }
2835
2836 if (showLine && line._model.tension !== 0) {
2837 me.updateBezierControlPoints();
2838 }
2839
2840 // Now pivot the point for animation
2841 for (i = 0, ilen = points.length; i < ilen; ++i) {
2842 points[i].pivot();
2843 }
2844 },
2845
2846 getPointBackgroundColor: function(point, index) {
2847 var backgroundColor = this.chart.options.elements.point.backgroundColor;
2848 var dataset = this.getDataset();
2849 var custom = point.custom || {};
2850
2851 if (custom.backgroundColor) {
2852 backgroundColor = custom.backgroundColor;
2853 } else if (dataset.pointBackgroundColor) {
2854 backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
2855 } else if (dataset.backgroundColor) {
2856 backgroundColor = dataset.backgroundColor;
2857 }
2858
2859 return backgroundColor;
2860 },
2861
2862 getPointBorderColor: function(point, index) {
2863 var borderColor = this.chart.options.elements.point.borderColor;
2864 var dataset = this.getDataset();
2865 var custom = point.custom || {};
2866
2867 if (custom.borderColor) {
2868 borderColor = custom.borderColor;
2869 } else if (dataset.pointBorderColor) {
2870 borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
2871 } else if (dataset.borderColor) {
2872 borderColor = dataset.borderColor;
2873 }
2874
2875 return borderColor;
2876 },
2877
2878 getPointBorderWidth: function(point, index) {
2879 var borderWidth = this.chart.options.elements.point.borderWidth;
2880 var dataset = this.getDataset();
2881 var custom = point.custom || {};
2882
2883 if (!isNaN(custom.borderWidth)) {
2884 borderWidth = custom.borderWidth;
2885 } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
2886 borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
2887 } else if (!isNaN(dataset.borderWidth)) {
2888 borderWidth = dataset.borderWidth;
2889 }
2890
2891 return borderWidth;
2892 },
2893
2894 updateElement: function(point, index, reset) {
2895 var me = this;
2896 var meta = me.getMeta();
2897 var custom = point.custom || {};
2898 var dataset = me.getDataset();
2899 var datasetIndex = me.index;
2900 var value = dataset.data[index];
2901 var yScale = me.getScaleForId(meta.yAxisID);
2902 var xScale = me.getScaleForId(meta.xAxisID);
2903 var pointOptions = me.chart.options.elements.point;
2904 var x, y;
2905
2906 // Compatibility: If the properties are defined with only the old name, use those values
2907 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
2908 dataset.pointRadius = dataset.radius;
2909 }
2910 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
2911 dataset.pointHitRadius = dataset.hitRadius;
2912 }
2913
2914 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
2915 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
2916
2917 // Utility
2918 point._xScale = xScale;
2919 point._yScale = yScale;
2920 point._datasetIndex = datasetIndex;
2921 point._index = index;
2922
2923 // Desired view properties
2924 point._model = {
2925 x: x,
2926 y: y,
2927 skip: custom.skip || isNaN(x) || isNaN(y),
2928 // Appearance
2929 radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
2930 pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
2931 backgroundColor: me.getPointBackgroundColor(point, index),
2932 borderColor: me.getPointBorderColor(point, index),
2933 borderWidth: me.getPointBorderWidth(point, index),
2934 tension: meta.dataset._model ? meta.dataset._model.tension : 0,
2935 steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
2936 // Tooltip
2937 hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
2938 };
2939 },
2940
2941 calculatePointY: function(value, index, datasetIndex) {
2942 var me = this;
2943 var chart = me.chart;
2944 var meta = me.getMeta();
2945 var yScale = me.getScaleForId(meta.yAxisID);
2946 var sumPos = 0;
2947 var sumNeg = 0;
2948 var i, ds, dsMeta;
2949
2950 if (yScale.options.stacked) {
2951 for (i = 0; i < datasetIndex; i++) {
2952 ds = chart.data.datasets[i];
2953 dsMeta = chart.getDatasetMeta(i);
2954 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
2955 var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
2956 if (stackedRightValue < 0) {
2957 sumNeg += stackedRightValue || 0;
2958 } else {
2959 sumPos += stackedRightValue || 0;
2960 }
2961 }
2962 }
2963
2964 var rightValue = Number(yScale.getRightValue(value));
2965 if (rightValue < 0) {
2966 return yScale.getPixelForValue(sumNeg + rightValue);
2967 }
2968 return yScale.getPixelForValue(sumPos + rightValue);
2969 }
2970
2971 return yScale.getPixelForValue(value);
2972 },
2973
2974 updateBezierControlPoints: function() {
2975 var me = this;
2976 var meta = me.getMeta();
2977 var area = me.chart.chartArea;
2978 var points = (meta.data || []);
2979 var i, ilen, point, model, controlPoints;
2980
2981 // Only consider points that are drawn in case the spanGaps option is used
2982 if (meta.dataset._model.spanGaps) {
2983 points = points.filter(function(pt) {
2984 return !pt._model.skip;
2985 });
2986 }
2987
2988 function capControlPoint(pt, min, max) {
2989 return Math.max(Math.min(pt, max), min);
2990 }
2991
2992 if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
2993 helpers.splineCurveMonotone(points);
2994 } else {
2995 for (i = 0, ilen = points.length; i < ilen; ++i) {
2996 point = points[i];
2997 model = point._model;
2998 controlPoints = helpers.splineCurve(
2999 helpers.previousItem(points, i)._model,
3000 model,
3001 helpers.nextItem(points, i)._model,
3002 meta.dataset._model.tension
3003 );
3004 model.controlPointPreviousX = controlPoints.previous.x;
3005 model.controlPointPreviousY = controlPoints.previous.y;
3006 model.controlPointNextX = controlPoints.next.x;
3007 model.controlPointNextY = controlPoints.next.y;
3008 }
3009 }
3010
3011 if (me.chart.options.elements.line.capBezierPoints) {
3012 for (i = 0, ilen = points.length; i < ilen; ++i) {
3013 model = points[i]._model;
3014 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
3015 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
3016 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
3017 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
3018 }
3019 }
3020 },
3021
3022 draw: function() {
3023 var me = this;
3024 var chart = me.chart;
3025 var meta = me.getMeta();
3026 var points = meta.data || [];
3027 var area = chart.chartArea;
3028 var ilen = points.length;
3029 var i = 0;
3030
3031 helpers.canvas.clipArea(chart.ctx, area);
3032
3033 if (lineEnabled(me.getDataset(), chart.options)) {
3034 meta.dataset.draw();
3035 }
3036
3037 helpers.canvas.unclipArea(chart.ctx);
3038
3039 // Draw the points
3040 for (; i < ilen; ++i) {
3041 points[i].draw(area);
3042 }
3043 },
3044
3045 setHoverStyle: function(point) {
3046 // Point
3047 var dataset = this.chart.data.datasets[point._datasetIndex];
3048 var index = point._index;
3049 var custom = point.custom || {};
3050 var model = point._model;
3051
3052 model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3053 model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3054 model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3055 model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3056 },
3057
3058 removeHoverStyle: function(point) {
3059 var me = this;
3060 var dataset = me.chart.data.datasets[point._datasetIndex];
3061 var index = point._index;
3062 var custom = point.custom || {};
3063 var model = point._model;
3064
3065 // Compatibility: If the properties are defined with only the old name, use those values
3066 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3067 dataset.pointRadius = dataset.radius;
3068 }
3069
3070 model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
3071 model.backgroundColor = me.getPointBackgroundColor(point, index);
3072 model.borderColor = me.getPointBorderColor(point, index);
3073 model.borderWidth = me.getPointBorderWidth(point, index);
3074 }
3075 });
3076 };
3077
3078 },{"25":25,"40":40,"45":45}],19:[function(require,module,exports){
3079 'use strict';
3080
3081 var defaults = require(25);
3082 var elements = require(40);
3083 var helpers = require(45);
3084
3085 defaults._set('polarArea', {
3086 scale: {
3087 type: 'radialLinear',
3088 angleLines: {
3089 display: false
3090 },
3091 gridLines: {
3092 circular: true
3093 },
3094 pointLabels: {
3095 display: false
3096 },
3097 ticks: {
3098 beginAtZero: true
3099 }
3100 },
3101
3102 // Boolean - Whether to animate the rotation of the chart
3103 animation: {
3104 animateRotate: true,
3105 animateScale: true
3106 },
3107
3108 startAngle: -0.5 * Math.PI,
3109 legendCallback: function(chart) {
3110 var text = [];
3111 text.push('<ul class="' + chart.id + '-legend">');
3112
3113 var data = chart.data;
3114 var datasets = data.datasets;
3115 var labels = data.labels;
3116
3117 if (datasets.length) {
3118 for (var i = 0; i < datasets[0].data.length; ++i) {
3119 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
3120 if (labels[i]) {
3121 text.push(labels[i]);
3122 }
3123 text.push('</li>');
3124 }
3125 }
3126
3127 text.push('</ul>');
3128 return text.join('');
3129 },
3130 legend: {
3131 labels: {
3132 generateLabels: function(chart) {
3133 var data = chart.data;
3134 if (data.labels.length && data.datasets.length) {
3135 return data.labels.map(function(label, i) {
3136 var meta = chart.getDatasetMeta(0);
3137 var ds = data.datasets[0];
3138 var arc = meta.data[i];
3139 var custom = arc.custom || {};
3140 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
3141 var arcOpts = chart.options.elements.arc;
3142 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
3143 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
3144 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
3145
3146 return {
3147 text: label,
3148 fillStyle: fill,
3149 strokeStyle: stroke,
3150 lineWidth: bw,
3151 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
3152
3153 // Extra data used for toggling the correct item
3154 index: i
3155 };
3156 });
3157 }
3158 return [];
3159 }
3160 },
3161
3162 onClick: function(e, legendItem) {
3163 var index = legendItem.index;
3164 var chart = this.chart;
3165 var i, ilen, meta;
3166
3167 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
3168 meta = chart.getDatasetMeta(i);
3169 meta.data[index].hidden = !meta.data[index].hidden;
3170 }
3171
3172 chart.update();
3173 }
3174 },
3175
3176 // Need to override these to give a nice default
3177 tooltips: {
3178 callbacks: {
3179 title: function() {
3180 return '';
3181 },
3182 label: function(item, data) {
3183 return data.labels[item.index] + ': ' + item.yLabel;
3184 }
3185 }
3186 }
3187 });
3188
3189 module.exports = function(Chart) {
3190
3191 Chart.controllers.polarArea = Chart.DatasetController.extend({
3192
3193 dataElementType: elements.Arc,
3194
3195 linkScales: helpers.noop,
3196
3197 update: function(reset) {
3198 var me = this;
3199 var chart = me.chart;
3200 var chartArea = chart.chartArea;
3201 var meta = me.getMeta();
3202 var opts = chart.options;
3203 var arcOpts = opts.elements.arc;
3204 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
3205 chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
3206 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
3207 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
3208
3209 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
3210 me.innerRadius = me.outerRadius - chart.radiusLength;
3211
3212 meta.count = me.countVisibleElements();
3213
3214 helpers.each(meta.data, function(arc, index) {
3215 me.updateElement(arc, index, reset);
3216 });
3217 },
3218
3219 updateElement: function(arc, index, reset) {
3220 var me = this;
3221 var chart = me.chart;
3222 var dataset = me.getDataset();
3223 var opts = chart.options;
3224 var animationOpts = opts.animation;
3225 var scale = chart.scale;
3226 var labels = chart.data.labels;
3227
3228 var circumference = me.calculateCircumference(dataset.data[index]);
3229 var centerX = scale.xCenter;
3230 var centerY = scale.yCenter;
3231
3232 // If there is NaN data before us, we need to calculate the starting angle correctly.
3233 // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
3234 var visibleCount = 0;
3235 var meta = me.getMeta();
3236 for (var i = 0; i < index; ++i) {
3237 if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
3238 ++visibleCount;
3239 }
3240 }
3241
3242 // var negHalfPI = -0.5 * Math.PI;
3243 var datasetStartAngle = opts.startAngle;
3244 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3245 var startAngle = datasetStartAngle + (circumference * visibleCount);
3246 var endAngle = startAngle + (arc.hidden ? 0 : circumference);
3247
3248 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3249
3250 helpers.extend(arc, {
3251 // Utility
3252 _datasetIndex: me.index,
3253 _index: index,
3254 _scale: scale,
3255
3256 // Desired view properties
3257 _model: {
3258 x: centerX,
3259 y: centerY,
3260 innerRadius: 0,
3261 outerRadius: reset ? resetRadius : distance,
3262 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
3263 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
3264 label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
3265 }
3266 });
3267
3268 // Apply border and fill style
3269 me.removeHoverStyle(arc);
3270
3271 arc.pivot();
3272 },
3273
3274 removeHoverStyle: function(arc) {
3275 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
3276 },
3277
3278 countVisibleElements: function() {
3279 var dataset = this.getDataset();
3280 var meta = this.getMeta();
3281 var count = 0;
3282
3283 helpers.each(meta.data, function(element, index) {
3284 if (!isNaN(dataset.data[index]) && !element.hidden) {
3285 count++;
3286 }
3287 });
3288
3289 return count;
3290 },
3291
3292 calculateCircumference: function(value) {
3293 var count = this.getMeta().count;
3294 if (count > 0 && !isNaN(value)) {
3295 return (2 * Math.PI) / count;
3296 }
3297 return 0;
3298 }
3299 });
3300 };
3301
3302 },{"25":25,"40":40,"45":45}],20:[function(require,module,exports){
3303 'use strict';
3304
3305 var defaults = require(25);
3306 var elements = require(40);
3307 var helpers = require(45);
3308
3309 defaults._set('radar', {
3310 scale: {
3311 type: 'radialLinear'
3312 },
3313 elements: {
3314 line: {
3315 tension: 0 // no bezier in radar
3316 }
3317 }
3318 });
3319
3320 module.exports = function(Chart) {
3321
3322 Chart.controllers.radar = Chart.DatasetController.extend({
3323
3324 datasetElementType: elements.Line,
3325
3326 dataElementType: elements.Point,
3327
3328 linkScales: helpers.noop,
3329
3330 update: function(reset) {
3331 var me = this;
3332 var meta = me.getMeta();
3333 var line = meta.dataset;
3334 var points = meta.data;
3335 var custom = line.custom || {};
3336 var dataset = me.getDataset();
3337 var lineElementOptions = me.chart.options.elements.line;
3338 var scale = me.chart.scale;
3339
3340 // Compatibility: If the properties are defined with only the old name, use those values
3341 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
3342 dataset.lineTension = dataset.tension;
3343 }
3344
3345 helpers.extend(meta.dataset, {
3346 // Utility
3347 _datasetIndex: me.index,
3348 _scale: scale,
3349 // Data
3350 _children: points,
3351 _loop: true,
3352 // Model
3353 _model: {
3354 // Appearance
3355 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
3356 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
3357 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
3358 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
3359 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
3360 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
3361 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
3362 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
3363 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
3364 }
3365 });
3366
3367 meta.dataset.pivot();
3368
3369 // Update Points
3370 helpers.each(points, function(point, index) {
3371 me.updateElement(point, index, reset);
3372 }, me);
3373
3374 // Update bezier control points
3375 me.updateBezierControlPoints();
3376 },
3377 updateElement: function(point, index, reset) {
3378 var me = this;
3379 var custom = point.custom || {};
3380 var dataset = me.getDataset();
3381 var scale = me.chart.scale;
3382 var pointElementOptions = me.chart.options.elements.point;
3383 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
3384
3385 // Compatibility: If the properties are defined with only the old name, use those values
3386 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3387 dataset.pointRadius = dataset.radius;
3388 }
3389 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
3390 dataset.pointHitRadius = dataset.hitRadius;
3391 }
3392
3393 helpers.extend(point, {
3394 // Utility
3395 _datasetIndex: me.index,
3396 _index: index,
3397 _scale: scale,
3398
3399 // Desired view properties
3400 _model: {
3401 x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
3402 y: reset ? scale.yCenter : pointPosition.y,
3403
3404 // Appearance
3405 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
3406 radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
3407 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
3408 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
3409 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
3410 pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
3411
3412 // Tooltip
3413 hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
3414 }
3415 });
3416
3417 point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
3418 },
3419 updateBezierControlPoints: function() {
3420 var chartArea = this.chart.chartArea;
3421 var meta = this.getMeta();
3422
3423 helpers.each(meta.data, function(point, index) {
3424 var model = point._model;
3425 var controlPoints = helpers.splineCurve(
3426 helpers.previousItem(meta.data, index, true)._model,
3427 model,
3428 helpers.nextItem(meta.data, index, true)._model,
3429 model.tension
3430 );
3431
3432 // Prevent the bezier going outside of the bounds of the graph
3433 model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
3434 model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
3435
3436 model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
3437 model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
3438
3439 // Now pivot the point for animation
3440 point.pivot();
3441 });
3442 },
3443
3444 setHoverStyle: function(point) {
3445 // Point
3446 var dataset = this.chart.data.datasets[point._datasetIndex];
3447 var custom = point.custom || {};
3448 var index = point._index;
3449 var model = point._model;
3450
3451 model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3452 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3453 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3454 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3455 },
3456
3457 removeHoverStyle: function(point) {
3458 var dataset = this.chart.data.datasets[point._datasetIndex];
3459 var custom = point.custom || {};
3460 var index = point._index;
3461 var model = point._model;
3462 var pointElementOptions = this.chart.options.elements.point;
3463
3464 model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius);
3465 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
3466 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
3467 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
3468 }
3469 });
3470 };
3471
3472 },{"25":25,"40":40,"45":45}],21:[function(require,module,exports){
3473 'use strict';
3474
3475 var defaults = require(25);
3476
3477 defaults._set('scatter', {
3478 hover: {
3479 mode: 'single'
3480 },
3481
3482 scales: {
3483 xAxes: [{
3484 id: 'x-axis-1', // need an ID so datasets can reference the scale
3485 type: 'linear', // scatter should not use a category axis
3486 position: 'bottom'
3487 }],
3488 yAxes: [{
3489 id: 'y-axis-1',
3490 type: 'linear',
3491 position: 'left'
3492 }]
3493 },
3494
3495 showLines: false,
3496
3497 tooltips: {
3498 callbacks: {
3499 title: function() {
3500 return ''; // doesn't make sense for scatter since data are formatted as a point
3501 },
3502 label: function(item) {
3503 return '(' + item.xLabel + ', ' + item.yLabel + ')';
3504 }
3505 }
3506 }
3507 });
3508
3509 module.exports = function(Chart) {
3510
3511 // Scatter charts use line controllers
3512 Chart.controllers.scatter = Chart.controllers.line;
3513
3514 };
3515
3516 },{"25":25}],22:[function(require,module,exports){
3517 /* global window: false */
3518 'use strict';
3519
3520 var defaults = require(25);
3521 var Element = require(26);
3522 var helpers = require(45);
3523
3524 defaults._set('global', {
3525 animation: {
3526 duration: 1000,
3527 easing: 'easeOutQuart',
3528 onProgress: helpers.noop,
3529 onComplete: helpers.noop
3530 }
3531 });
3532
3533 module.exports = function(Chart) {
3534
3535 Chart.Animation = Element.extend({
3536 chart: null, // the animation associated chart instance
3537 currentStep: 0, // the current animation step
3538 numSteps: 60, // default number of steps
3539 easing: '', // the easing to use for this animation
3540 render: null, // render function used by the animation service
3541
3542 onAnimationProgress: null, // user specified callback to fire on each step of the animation
3543 onAnimationComplete: null, // user specified callback to fire when the animation finishes
3544 });
3545
3546 Chart.animationService = {
3547 frameDuration: 17,
3548 animations: [],
3549 dropFrames: 0,
3550 request: null,
3551
3552 /**
3553 * @param {Chart} chart - The chart to animate.
3554 * @param {Chart.Animation} animation - The animation that we will animate.
3555 * @param {Number} duration - The animation duration in ms.
3556 * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
3557 */
3558 addAnimation: function(chart, animation, duration, lazy) {
3559 var animations = this.animations;
3560 var i, ilen;
3561
3562 animation.chart = chart;
3563
3564 if (!lazy) {
3565 chart.animating = true;
3566 }
3567
3568 for (i = 0, ilen = animations.length; i < ilen; ++i) {
3569 if (animations[i].chart === chart) {
3570 animations[i] = animation;
3571 return;
3572 }
3573 }
3574
3575 animations.push(animation);
3576
3577 // If there are no animations queued, manually kickstart a digest, for lack of a better word
3578 if (animations.length === 1) {
3579 this.requestAnimationFrame();
3580 }
3581 },
3582
3583 cancelAnimation: function(chart) {
3584 var index = helpers.findIndex(this.animations, function(animation) {
3585 return animation.chart === chart;
3586 });
3587
3588 if (index !== -1) {
3589 this.animations.splice(index, 1);
3590 chart.animating = false;
3591 }
3592 },
3593
3594 requestAnimationFrame: function() {
3595 var me = this;
3596 if (me.request === null) {
3597 // Skip animation frame requests until the active one is executed.
3598 // This can happen when processing mouse events, e.g. 'mousemove'
3599 // and 'mouseout' events will trigger multiple renders.
3600 me.request = helpers.requestAnimFrame.call(window, function() {
3601 me.request = null;
3602 me.startDigest();
3603 });
3604 }
3605 },
3606
3607 /**
3608 * @private
3609 */
3610 startDigest: function() {
3611 var me = this;
3612 var startTime = Date.now();
3613 var framesToDrop = 0;
3614
3615 if (me.dropFrames > 1) {
3616 framesToDrop = Math.floor(me.dropFrames);
3617 me.dropFrames = me.dropFrames % 1;
3618 }
3619
3620 me.advance(1 + framesToDrop);
3621
3622 var endTime = Date.now();
3623
3624 me.dropFrames += (endTime - startTime) / me.frameDuration;
3625
3626 // Do we have more stuff to animate?
3627 if (me.animations.length > 0) {
3628 me.requestAnimationFrame();
3629 }
3630 },
3631
3632 /**
3633 * @private
3634 */
3635 advance: function(count) {
3636 var animations = this.animations;
3637 var animation, chart;
3638 var i = 0;
3639
3640 while (i < animations.length) {
3641 animation = animations[i];
3642 chart = animation.chart;
3643
3644 animation.currentStep = (animation.currentStep || 0) + count;
3645 animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
3646
3647 helpers.callback(animation.render, [chart, animation], chart);
3648 helpers.callback(animation.onAnimationProgress, [animation], chart);
3649
3650 if (animation.currentStep >= animation.numSteps) {
3651 helpers.callback(animation.onAnimationComplete, [animation], chart);
3652 chart.animating = false;
3653 animations.splice(i, 1);
3654 } else {
3655 ++i;
3656 }
3657 }
3658 }
3659 };
3660
3661 /**
3662 * Provided for backward compatibility, use Chart.Animation instead
3663 * @prop Chart.Animation#animationObject
3664 * @deprecated since version 2.6.0
3665 * @todo remove at version 3
3666 */
3667 Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
3668 get: function() {
3669 return this;
3670 }
3671 });
3672
3673 /**
3674 * Provided for backward compatibility, use Chart.Animation#chart instead
3675 * @prop Chart.Animation#chartInstance
3676 * @deprecated since version 2.6.0
3677 * @todo remove at version 3
3678 */
3679 Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
3680 get: function() {
3681 return this.chart;
3682 },
3683 set: function(value) {
3684 this.chart = value;
3685 }
3686 });
3687
3688 };
3689
3690 },{"25":25,"26":26,"45":45}],23:[function(require,module,exports){
3691 'use strict';
3692
3693 var defaults = require(25);
3694 var helpers = require(45);
3695 var Interaction = require(28);
3696 var platform = require(48);
3697
3698 module.exports = function(Chart) {
3699 var plugins = Chart.plugins;
3700
3701 // Create a dictionary of chart types, to allow for extension of existing types
3702 Chart.types = {};
3703
3704 // Store a reference to each instance - allowing us to globally resize chart instances on window resize.
3705 // Destroy method on the chart will remove the instance of the chart from this reference.
3706 Chart.instances = {};
3707
3708 // Controllers available for dataset visualization eg. bar, line, slice, etc.
3709 Chart.controllers = {};
3710
3711 /**
3712 * Initializes the given config with global and chart default values.
3713 */
3714 function initConfig(config) {
3715 config = config || {};
3716
3717 // Do NOT use configMerge() for the data object because this method merges arrays
3718 // and so would change references to labels and datasets, preventing data updates.
3719 var data = config.data = config.data || {};
3720 data.datasets = data.datasets || [];
3721 data.labels = data.labels || [];
3722
3723 config.options = helpers.configMerge(
3724 defaults.global,
3725 defaults[config.type],
3726 config.options || {});
3727
3728 return config;
3729 }
3730
3731 /**
3732 * Updates the config of the chart
3733 * @param chart {Chart} chart to update the options for
3734 */
3735 function updateConfig(chart) {
3736 var newOptions = chart.options;
3737
3738 // Update Scale(s) with options
3739 if (newOptions.scale) {
3740 chart.scale.options = newOptions.scale;
3741 } else if (newOptions.scales) {
3742 newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) {
3743 chart.scales[scaleOptions.id].options = scaleOptions;
3744 });
3745 }
3746
3747 // Tooltip
3748 chart.tooltip._options = newOptions.tooltips;
3749 }
3750
3751 function positionIsHorizontal(position) {
3752 return position === 'top' || position === 'bottom';
3753 }
3754
3755 helpers.extend(Chart.prototype, /** @lends Chart */ {
3756 /**
3757 * @private
3758 */
3759 construct: function(item, config) {
3760 var me = this;
3761
3762 config = initConfig(config);
3763
3764 var context = platform.acquireContext(item, config);
3765 var canvas = context && context.canvas;
3766 var height = canvas && canvas.height;
3767 var width = canvas && canvas.width;
3768
3769 me.id = helpers.uid();
3770 me.ctx = context;
3771 me.canvas = canvas;
3772 me.config = config;
3773 me.width = width;
3774 me.height = height;
3775 me.aspectRatio = height ? width / height : null;
3776 me.options = config.options;
3777 me._bufferedRender = false;
3778
3779 /**
3780 * Provided for backward compatibility, Chart and Chart.Controller have been merged,
3781 * the "instance" still need to be defined since it might be called from plugins.
3782 * @prop Chart#chart
3783 * @deprecated since version 2.6.0
3784 * @todo remove at version 3
3785 * @private
3786 */
3787 me.chart = me;
3788 me.controller = me; // chart.chart.controller #inception
3789
3790 // Add the chart instance to the global namespace
3791 Chart.instances[me.id] = me;
3792
3793 // Define alias to the config data: `chart.data === chart.config.data`
3794 Object.defineProperty(me, 'data', {
3795 get: function() {
3796 return me.config.data;
3797 },
3798 set: function(value) {
3799 me.config.data = value;
3800 }
3801 });
3802
3803 if (!context || !canvas) {
3804 // The given item is not a compatible context2d element, let's return before finalizing
3805 // the chart initialization but after setting basic chart / controller properties that
3806 // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
3807 // https://github.com/chartjs/Chart.js/issues/2807
3808 console.error("Failed to create chart: can't acquire context from the given item");
3809 return;
3810 }
3811
3812 me.initialize();
3813 me.update();
3814 },
3815
3816 /**
3817 * @private
3818 */
3819 initialize: function() {
3820 var me = this;
3821
3822 // Before init plugin notification
3823 plugins.notify(me, 'beforeInit');
3824
3825 helpers.retinaScale(me, me.options.devicePixelRatio);
3826
3827 me.bindEvents();
3828
3829 if (me.options.responsive) {
3830 // Initial resize before chart draws (must be silent to preserve initial animations).
3831 me.resize(true);
3832 }
3833
3834 // Make sure scales have IDs and are built before we build any controllers.
3835 me.ensureScalesHaveIDs();
3836 me.buildScales();
3837 me.initToolTip();
3838
3839 // After init plugin notification
3840 plugins.notify(me, 'afterInit');
3841
3842 return me;
3843 },
3844
3845 clear: function() {
3846 helpers.canvas.clear(this);
3847 return this;
3848 },
3849
3850 stop: function() {
3851 // Stops any current animation loop occurring
3852 Chart.animationService.cancelAnimation(this);
3853 return this;
3854 },
3855
3856 resize: function(silent) {
3857 var me = this;
3858 var options = me.options;
3859 var canvas = me.canvas;
3860 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
3861
3862 // the canvas render width and height will be casted to integers so make sure that
3863 // the canvas display style uses the same integer values to avoid blurring effect.
3864
3865 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased
3866 var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
3867 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
3868
3869 if (me.width === newWidth && me.height === newHeight) {
3870 return;
3871 }
3872
3873 canvas.width = me.width = newWidth;
3874 canvas.height = me.height = newHeight;
3875 canvas.style.width = newWidth + 'px';
3876 canvas.style.height = newHeight + 'px';
3877
3878 helpers.retinaScale(me, options.devicePixelRatio);
3879
3880 if (!silent) {
3881 // Notify any plugins about the resize
3882 var newSize = {width: newWidth, height: newHeight};
3883 plugins.notify(me, 'resize', [newSize]);
3884
3885 // Notify of resize
3886 if (me.options.onResize) {
3887 me.options.onResize(me, newSize);
3888 }
3889
3890 me.stop();
3891 me.update(me.options.responsiveAnimationDuration);
3892 }
3893 },
3894
3895 ensureScalesHaveIDs: function() {
3896 var options = this.options;
3897 var scalesOptions = options.scales || {};
3898 var scaleOptions = options.scale;
3899
3900 helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
3901 xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
3902 });
3903
3904 helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
3905 yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
3906 });
3907
3908 if (scaleOptions) {
3909 scaleOptions.id = scaleOptions.id || 'scale';
3910 }
3911 },
3912
3913 /**
3914 * Builds a map of scale ID to scale object for future lookup.
3915 */
3916 buildScales: function() {
3917 var me = this;
3918 var options = me.options;
3919 var scales = me.scales = {};
3920 var items = [];
3921
3922 if (options.scales) {
3923 items = items.concat(
3924 (options.scales.xAxes || []).map(function(xAxisOptions) {
3925 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
3926 }),
3927 (options.scales.yAxes || []).map(function(yAxisOptions) {
3928 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
3929 })
3930 );
3931 }
3932
3933 if (options.scale) {
3934 items.push({
3935 options: options.scale,
3936 dtype: 'radialLinear',
3937 isDefault: true,
3938 dposition: 'chartArea'
3939 });
3940 }
3941
3942 helpers.each(items, function(item) {
3943 var scaleOptions = item.options;
3944 var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype);
3945 var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
3946 if (!scaleClass) {
3947 return;
3948 }
3949
3950 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
3951 scaleOptions.position = item.dposition;
3952 }
3953
3954 var scale = new scaleClass({
3955 id: scaleOptions.id,
3956 options: scaleOptions,
3957 ctx: me.ctx,
3958 chart: me
3959 });
3960
3961 scales[scale.id] = scale;
3962 scale.mergeTicksOptions();
3963
3964 // TODO(SB): I think we should be able to remove this custom case (options.scale)
3965 // and consider it as a regular scale part of the "scales"" map only! This would
3966 // make the logic easier and remove some useless? custom code.
3967 if (item.isDefault) {
3968 me.scale = scale;
3969 }
3970 });
3971
3972 Chart.scaleService.addScalesToLayout(this);
3973 },
3974
3975 buildOrUpdateControllers: function() {
3976 var me = this;
3977 var types = [];
3978 var newControllers = [];
3979
3980 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
3981 var meta = me.getDatasetMeta(datasetIndex);
3982 var type = dataset.type || me.config.type;
3983
3984 if (meta.type && meta.type !== type) {
3985 me.destroyDatasetMeta(datasetIndex);
3986 meta = me.getDatasetMeta(datasetIndex);
3987 }
3988 meta.type = type;
3989
3990 types.push(meta.type);
3991
3992 if (meta.controller) {
3993 meta.controller.updateIndex(datasetIndex);
3994 } else {
3995 var ControllerClass = Chart.controllers[meta.type];
3996 if (ControllerClass === undefined) {
3997 throw new Error('"' + meta.type + '" is not a chart type.');
3998 }
3999
4000 meta.controller = new ControllerClass(me, datasetIndex);
4001 newControllers.push(meta.controller);
4002 }
4003 }, me);
4004
4005 return newControllers;
4006 },
4007
4008 /**
4009 * Reset the elements of all datasets
4010 * @private
4011 */
4012 resetElements: function() {
4013 var me = this;
4014 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4015 me.getDatasetMeta(datasetIndex).controller.reset();
4016 }, me);
4017 },
4018
4019 /**
4020 * Resets the chart back to it's state before the initial animation
4021 */
4022 reset: function() {
4023 this.resetElements();
4024 this.tooltip.initialize();
4025 },
4026
4027 update: function(config) {
4028 var me = this;
4029
4030 if (!config || typeof config !== 'object') {
4031 // backwards compatibility
4032 config = {
4033 duration: config,
4034 lazy: arguments[1]
4035 };
4036 }
4037
4038 updateConfig(me);
4039
4040 if (plugins.notify(me, 'beforeUpdate') === false) {
4041 return;
4042 }
4043
4044 // In case the entire data object changed
4045 me.tooltip._data = me.data;
4046
4047 // Make sure dataset controllers are updated and new controllers are reset
4048 var newControllers = me.buildOrUpdateControllers();
4049
4050 // Make sure all dataset controllers have correct meta data counts
4051 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4052 me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
4053 }, me);
4054
4055 me.updateLayout();
4056
4057 // Can only reset the new controllers after the scales have been updated
4058 helpers.each(newControllers, function(controller) {
4059 controller.reset();
4060 });
4061
4062 me.updateDatasets();
4063
4064 // Need to reset tooltip in case it is displayed with elements that are removed
4065 // after update.
4066 me.tooltip.initialize();
4067
4068 // Last active contains items that were previously in the tooltip.
4069 // When we reset the tooltip, we need to clear it
4070 me.lastActive = [];
4071
4072 // Do this before render so that any plugins that need final scale updates can use it
4073 plugins.notify(me, 'afterUpdate');
4074
4075 if (me._bufferedRender) {
4076 me._bufferedRequest = {
4077 duration: config.duration,
4078 easing: config.easing,
4079 lazy: config.lazy
4080 };
4081 } else {
4082 me.render(config);
4083 }
4084 },
4085
4086 /**
4087 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
4088 * hook, in which case, plugins will not be called on `afterLayout`.
4089 * @private
4090 */
4091 updateLayout: function() {
4092 var me = this;
4093
4094 if (plugins.notify(me, 'beforeLayout') === false) {
4095 return;
4096 }
4097
4098 Chart.layoutService.update(this, this.width, this.height);
4099
4100 /**
4101 * Provided for backward compatibility, use `afterLayout` instead.
4102 * @method IPlugin#afterScaleUpdate
4103 * @deprecated since version 2.5.0
4104 * @todo remove at version 3
4105 * @private
4106 */
4107 plugins.notify(me, 'afterScaleUpdate');
4108 plugins.notify(me, 'afterLayout');
4109 },
4110
4111 /**
4112 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
4113 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
4114 * @private
4115 */
4116 updateDatasets: function() {
4117 var me = this;
4118
4119 if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
4120 return;
4121 }
4122
4123 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4124 me.updateDataset(i);
4125 }
4126
4127 plugins.notify(me, 'afterDatasetsUpdate');
4128 },
4129
4130 /**
4131 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
4132 * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
4133 * @private
4134 */
4135 updateDataset: function(index) {
4136 var me = this;
4137 var meta = me.getDatasetMeta(index);
4138 var args = {
4139 meta: meta,
4140 index: index
4141 };
4142
4143 if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
4144 return;
4145 }
4146
4147 meta.controller.update();
4148
4149 plugins.notify(me, 'afterDatasetUpdate', [args]);
4150 },
4151
4152 render: function(config) {
4153 var me = this;
4154
4155 if (!config || typeof config !== 'object') {
4156 // backwards compatibility
4157 config = {
4158 duration: config,
4159 lazy: arguments[1]
4160 };
4161 }
4162
4163 var duration = config.duration;
4164 var lazy = config.lazy;
4165
4166 if (plugins.notify(me, 'beforeRender') === false) {
4167 return;
4168 }
4169
4170 var animationOptions = me.options.animation;
4171 var onComplete = function(animation) {
4172 plugins.notify(me, 'afterRender');
4173 helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
4174 };
4175
4176 if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
4177 var animation = new Chart.Animation({
4178 numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
4179 easing: config.easing || animationOptions.easing,
4180
4181 render: function(chart, animationObject) {
4182 var easingFunction = helpers.easing.effects[animationObject.easing];
4183 var currentStep = animationObject.currentStep;
4184 var stepDecimal = currentStep / animationObject.numSteps;
4185
4186 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
4187 },
4188
4189 onAnimationProgress: animationOptions.onProgress,
4190 onAnimationComplete: onComplete
4191 });
4192
4193 Chart.animationService.addAnimation(me, animation, duration, lazy);
4194 } else {
4195 me.draw();
4196
4197 // See https://github.com/chartjs/Chart.js/issues/3781
4198 onComplete(new Chart.Animation({numSteps: 0, chart: me}));
4199 }
4200
4201 return me;
4202 },
4203
4204 draw: function(easingValue) {
4205 var me = this;
4206
4207 me.clear();
4208
4209 if (helpers.isNullOrUndef(easingValue)) {
4210 easingValue = 1;
4211 }
4212
4213 me.transition(easingValue);
4214
4215 if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
4216 return;
4217 }
4218
4219 // Draw all the scales
4220 helpers.each(me.boxes, function(box) {
4221 box.draw(me.chartArea);
4222 }, me);
4223
4224 if (me.scale) {
4225 me.scale.draw();
4226 }
4227
4228 me.drawDatasets(easingValue);
4229 me._drawTooltip(easingValue);
4230
4231 plugins.notify(me, 'afterDraw', [easingValue]);
4232 },
4233
4234 /**
4235 * @private
4236 */
4237 transition: function(easingValue) {
4238 var me = this;
4239
4240 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
4241 if (me.isDatasetVisible(i)) {
4242 me.getDatasetMeta(i).controller.transition(easingValue);
4243 }
4244 }
4245
4246 me.tooltip.transition(easingValue);
4247 },
4248
4249 /**
4250 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
4251 * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
4252 * @private
4253 */
4254 drawDatasets: function(easingValue) {
4255 var me = this;
4256
4257 if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
4258 return;
4259 }
4260
4261 // Draw datasets reversed to support proper line stacking
4262 for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
4263 if (me.isDatasetVisible(i)) {
4264 me.drawDataset(i, easingValue);
4265 }
4266 }
4267
4268 plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
4269 },
4270
4271 /**
4272 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
4273 * hook, in which case, plugins will not be called on `afterDatasetDraw`.
4274 * @private
4275 */
4276 drawDataset: function(index, easingValue) {
4277 var me = this;
4278 var meta = me.getDatasetMeta(index);
4279 var args = {
4280 meta: meta,
4281 index: index,
4282 easingValue: easingValue
4283 };
4284
4285 if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
4286 return;
4287 }
4288
4289 meta.controller.draw(easingValue);
4290
4291 plugins.notify(me, 'afterDatasetDraw', [args]);
4292 },
4293
4294 /**
4295 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
4296 * hook, in which case, plugins will not be called on `afterTooltipDraw`.
4297 * @private
4298 */
4299 _drawTooltip: function(easingValue) {
4300 var me = this;
4301 var tooltip = me.tooltip;
4302 var args = {
4303 tooltip: tooltip,
4304 easingValue: easingValue
4305 };
4306
4307 if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
4308 return;
4309 }
4310
4311 tooltip.draw();
4312
4313 plugins.notify(me, 'afterTooltipDraw', [args]);
4314 },
4315
4316 // Get the single element that was clicked on
4317 // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
4318 getElementAtEvent: function(e) {
4319 return Interaction.modes.single(this, e);
4320 },
4321
4322 getElementsAtEvent: function(e) {
4323 return Interaction.modes.label(this, e, {intersect: true});
4324 },
4325
4326 getElementsAtXAxis: function(e) {
4327 return Interaction.modes['x-axis'](this, e, {intersect: true});
4328 },
4329
4330 getElementsAtEventForMode: function(e, mode, options) {
4331 var method = Interaction.modes[mode];
4332 if (typeof method === 'function') {
4333 return method(this, e, options);
4334 }
4335
4336 return [];
4337 },
4338
4339 getDatasetAtEvent: function(e) {
4340 return Interaction.modes.dataset(this, e, {intersect: true});
4341 },
4342
4343 getDatasetMeta: function(datasetIndex) {
4344 var me = this;
4345 var dataset = me.data.datasets[datasetIndex];
4346 if (!dataset._meta) {
4347 dataset._meta = {};
4348 }
4349
4350 var meta = dataset._meta[me.id];
4351 if (!meta) {
4352 meta = dataset._meta[me.id] = {
4353 type: null,
4354 data: [],
4355 dataset: null,
4356 controller: null,
4357 hidden: null, // See isDatasetVisible() comment
4358 xAxisID: null,
4359 yAxisID: null
4360 };
4361 }
4362
4363 return meta;
4364 },
4365
4366 getVisibleDatasetCount: function() {
4367 var count = 0;
4368 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
4369 if (this.isDatasetVisible(i)) {
4370 count++;
4371 }
4372 }
4373 return count;
4374 },
4375
4376 isDatasetVisible: function(datasetIndex) {
4377 var meta = this.getDatasetMeta(datasetIndex);
4378
4379 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
4380 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
4381 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
4382 },
4383
4384 generateLegend: function() {
4385 return this.options.legendCallback(this);
4386 },
4387
4388 /**
4389 * @private
4390 */
4391 destroyDatasetMeta: function(datasetIndex) {
4392 var id = this.id;
4393 var dataset = this.data.datasets[datasetIndex];
4394 var meta = dataset._meta && dataset._meta[id];
4395
4396 if (meta) {
4397 meta.controller.destroy();
4398 delete dataset._meta[id];
4399 }
4400 },
4401
4402 destroy: function() {
4403 var me = this;
4404 var canvas = me.canvas;
4405 var i, ilen;
4406
4407 me.stop();
4408
4409 // dataset controllers need to cleanup associated data
4410 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4411 me.destroyDatasetMeta(i);
4412 }
4413
4414 if (canvas) {
4415 me.unbindEvents();
4416 helpers.canvas.clear(me);
4417 platform.releaseContext(me.ctx);
4418 me.canvas = null;
4419 me.ctx = null;
4420 }
4421
4422 plugins.notify(me, 'destroy');
4423
4424 delete Chart.instances[me.id];
4425 },
4426
4427 toBase64Image: function() {
4428 return this.canvas.toDataURL.apply(this.canvas, arguments);
4429 },
4430
4431 initToolTip: function() {
4432 var me = this;
4433 me.tooltip = new Chart.Tooltip({
4434 _chart: me,
4435 _chartInstance: me, // deprecated, backward compatibility
4436 _data: me.data,
4437 _options: me.options.tooltips
4438 }, me);
4439 },
4440
4441 /**
4442 * @private
4443 */
4444 bindEvents: function() {
4445 var me = this;
4446 var listeners = me._listeners = {};
4447 var listener = function() {
4448 me.eventHandler.apply(me, arguments);
4449 };
4450
4451 helpers.each(me.options.events, function(type) {
4452 platform.addEventListener(me, type, listener);
4453 listeners[type] = listener;
4454 });
4455
4456 // Elements used to detect size change should not be injected for non responsive charts.
4457 // See https://github.com/chartjs/Chart.js/issues/2210
4458 if (me.options.responsive) {
4459 listener = function() {
4460 me.resize();
4461 };
4462
4463 platform.addEventListener(me, 'resize', listener);
4464 listeners.resize = listener;
4465 }
4466 },
4467
4468 /**
4469 * @private
4470 */
4471 unbindEvents: function() {
4472 var me = this;
4473 var listeners = me._listeners;
4474 if (!listeners) {
4475 return;
4476 }
4477
4478 delete me._listeners;
4479 helpers.each(listeners, function(listener, type) {
4480 platform.removeEventListener(me, type, listener);
4481 });
4482 },
4483
4484 updateHoverStyle: function(elements, mode, enabled) {
4485 var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
4486 var element, i, ilen;
4487
4488 for (i = 0, ilen = elements.length; i < ilen; ++i) {
4489 element = elements[i];
4490 if (element) {
4491 this.getDatasetMeta(element._datasetIndex).controller[method](element);
4492 }
4493 }
4494 },
4495
4496 /**
4497 * @private
4498 */
4499 eventHandler: function(e) {
4500 var me = this;
4501 var tooltip = me.tooltip;
4502
4503 if (plugins.notify(me, 'beforeEvent', [e]) === false) {
4504 return;
4505 }
4506
4507 // Buffer any update calls so that renders do not occur
4508 me._bufferedRender = true;
4509 me._bufferedRequest = null;
4510
4511 var changed = me.handleEvent(e);
4512 changed |= tooltip && tooltip.handleEvent(e);
4513
4514 plugins.notify(me, 'afterEvent', [e]);
4515
4516 var bufferedRequest = me._bufferedRequest;
4517 if (bufferedRequest) {
4518 // If we have an update that was triggered, we need to do a normal render
4519 me.render(bufferedRequest);
4520 } else if (changed && !me.animating) {
4521 // If entering, leaving, or changing elements, animate the change via pivot
4522 me.stop();
4523
4524 // We only need to render at this point. Updating will cause scales to be
4525 // recomputed generating flicker & using more memory than necessary.
4526 me.render(me.options.hover.animationDuration, true);
4527 }
4528
4529 me._bufferedRender = false;
4530 me._bufferedRequest = null;
4531
4532 return me;
4533 },
4534
4535 /**
4536 * Handle an event
4537 * @private
4538 * @param {IEvent} event the event to handle
4539 * @return {Boolean} true if the chart needs to re-render
4540 */
4541 handleEvent: function(e) {
4542 var me = this;
4543 var options = me.options || {};
4544 var hoverOptions = options.hover;
4545 var changed = false;
4546
4547 me.lastActive = me.lastActive || [];
4548
4549 // Find Active Elements for hover and tooltips
4550 if (e.type === 'mouseout') {
4551 me.active = [];
4552 } else {
4553 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
4554 }
4555
4556 // Invoke onHover hook
4557 // Need to call with native event here to not break backwards compatibility
4558 helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
4559
4560 if (e.type === 'mouseup' || e.type === 'click') {
4561 if (options.onClick) {
4562 // Use e.native here for backwards compatibility
4563 options.onClick.call(me, e.native, me.active);
4564 }
4565 }
4566
4567 // Remove styling for last active (even if it may still be active)
4568 if (me.lastActive.length) {
4569 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
4570 }
4571
4572 // Built in hover styling
4573 if (me.active.length && hoverOptions.mode) {
4574 me.updateHoverStyle(me.active, hoverOptions.mode, true);
4575 }
4576
4577 changed = !helpers.arrayEquals(me.active, me.lastActive);
4578
4579 // Remember Last Actives
4580 me.lastActive = me.active;
4581
4582 return changed;
4583 }
4584 });
4585
4586 /**
4587 * Provided for backward compatibility, use Chart instead.
4588 * @class Chart.Controller
4589 * @deprecated since version 2.6.0
4590 * @todo remove at version 3
4591 * @private
4592 */
4593 Chart.Controller = Chart;
4594 };
4595
4596 },{"25":25,"28":28,"45":45,"48":48}],24:[function(require,module,exports){
4597 'use strict';
4598
4599 var helpers = require(45);
4600
4601 module.exports = function(Chart) {
4602
4603 var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
4604
4605 /**
4606 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
4607 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
4608 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
4609 */
4610 function listenArrayEvents(array, listener) {
4611 if (array._chartjs) {
4612 array._chartjs.listeners.push(listener);
4613 return;
4614 }
4615
4616 Object.defineProperty(array, '_chartjs', {
4617 configurable: true,
4618 enumerable: false,
4619 value: {
4620 listeners: [listener]
4621 }
4622 });
4623
4624 arrayEvents.forEach(function(key) {
4625 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
4626 var base = array[key];
4627
4628 Object.defineProperty(array, key, {
4629 configurable: true,
4630 enumerable: false,
4631 value: function() {
4632 var args = Array.prototype.slice.call(arguments);
4633 var res = base.apply(this, args);
4634
4635 helpers.each(array._chartjs.listeners, function(object) {
4636 if (typeof object[method] === 'function') {
4637 object[method].apply(object, args);
4638 }
4639 });
4640
4641 return res;
4642 }
4643 });
4644 });
4645 }
4646
4647 /**
4648 * Removes the given array event listener and cleanup extra attached properties (such as
4649 * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
4650 */
4651 function unlistenArrayEvents(array, listener) {
4652 var stub = array._chartjs;
4653 if (!stub) {
4654 return;
4655 }
4656
4657 var listeners = stub.listeners;
4658 var index = listeners.indexOf(listener);
4659 if (index !== -1) {
4660 listeners.splice(index, 1);
4661 }
4662
4663 if (listeners.length > 0) {
4664 return;
4665 }
4666
4667 arrayEvents.forEach(function(key) {
4668 delete array[key];
4669 });
4670
4671 delete array._chartjs;
4672 }
4673
4674 // Base class for all dataset controllers (line, bar, etc)
4675 Chart.DatasetController = function(chart, datasetIndex) {
4676 this.initialize(chart, datasetIndex);
4677 };
4678
4679 helpers.extend(Chart.DatasetController.prototype, {
4680
4681 /**
4682 * Element type used to generate a meta dataset (e.g. Chart.element.Line).
4683 * @type {Chart.core.element}
4684 */
4685 datasetElementType: null,
4686
4687 /**
4688 * Element type used to generate a meta data (e.g. Chart.element.Point).
4689 * @type {Chart.core.element}
4690 */
4691 dataElementType: null,
4692
4693 initialize: function(chart, datasetIndex) {
4694 var me = this;
4695 me.chart = chart;
4696 me.index = datasetIndex;
4697 me.linkScales();
4698 me.addElements();
4699 },
4700
4701 updateIndex: function(datasetIndex) {
4702 this.index = datasetIndex;
4703 },
4704
4705 linkScales: function() {
4706 var me = this;
4707 var meta = me.getMeta();
4708 var dataset = me.getDataset();
4709
4710 if (meta.xAxisID === null) {
4711 meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
4712 }
4713 if (meta.yAxisID === null) {
4714 meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
4715 }
4716 },
4717
4718 getDataset: function() {
4719 return this.chart.data.datasets[this.index];
4720 },
4721
4722 getMeta: function() {
4723 return this.chart.getDatasetMeta(this.index);
4724 },
4725
4726 getScaleForId: function(scaleID) {
4727 return this.chart.scales[scaleID];
4728 },
4729
4730 reset: function() {
4731 this.update(true);
4732 },
4733
4734 /**
4735 * @private
4736 */
4737 destroy: function() {
4738 if (this._data) {
4739 unlistenArrayEvents(this._data, this);
4740 }
4741 },
4742
4743 createMetaDataset: function() {
4744 var me = this;
4745 var type = me.datasetElementType;
4746 return type && new type({
4747 _chart: me.chart,
4748 _datasetIndex: me.index
4749 });
4750 },
4751
4752 createMetaData: function(index) {
4753 var me = this;
4754 var type = me.dataElementType;
4755 return type && new type({
4756 _chart: me.chart,
4757 _datasetIndex: me.index,
4758 _index: index
4759 });
4760 },
4761
4762 addElements: function() {
4763 var me = this;
4764 var meta = me.getMeta();
4765 var data = me.getDataset().data || [];
4766 var metaData = meta.data;
4767 var i, ilen;
4768
4769 for (i = 0, ilen = data.length; i < ilen; ++i) {
4770 metaData[i] = metaData[i] || me.createMetaData(i);
4771 }
4772
4773 meta.dataset = meta.dataset || me.createMetaDataset();
4774 },
4775
4776 addElementAndReset: function(index) {
4777 var element = this.createMetaData(index);
4778 this.getMeta().data.splice(index, 0, element);
4779 this.updateElement(element, index, true);
4780 },
4781
4782 buildOrUpdateElements: function() {
4783 var me = this;
4784 var dataset = me.getDataset();
4785 var data = dataset.data || (dataset.data = []);
4786
4787 // In order to correctly handle data addition/deletion animation (an thus simulate
4788 // real-time charts), we need to monitor these data modifications and synchronize
4789 // the internal meta data accordingly.
4790 if (me._data !== data) {
4791 if (me._data) {
4792 // This case happens when the user replaced the data array instance.
4793 unlistenArrayEvents(me._data, me);
4794 }
4795
4796 listenArrayEvents(data, me);
4797 me._data = data;
4798 }
4799
4800 // Re-sync meta data in case the user replaced the data array or if we missed
4801 // any updates and so make sure that we handle number of datapoints changing.
4802 me.resyncElements();
4803 },
4804
4805 update: helpers.noop,
4806
4807 transition: function(easingValue) {
4808 var meta = this.getMeta();
4809 var elements = meta.data || [];
4810 var ilen = elements.length;
4811 var i = 0;
4812
4813 for (; i < ilen; ++i) {
4814 elements[i].transition(easingValue);
4815 }
4816
4817 if (meta.dataset) {
4818 meta.dataset.transition(easingValue);
4819 }
4820 },
4821
4822 draw: function() {
4823 var meta = this.getMeta();
4824 var elements = meta.data || [];
4825 var ilen = elements.length;
4826 var i = 0;
4827
4828 if (meta.dataset) {
4829 meta.dataset.draw();
4830 }
4831
4832 for (; i < ilen; ++i) {
4833 elements[i].draw();
4834 }
4835 },
4836
4837 removeHoverStyle: function(element, elementOpts) {
4838 var dataset = this.chart.data.datasets[element._datasetIndex];
4839 var index = element._index;
4840 var custom = element.custom || {};
4841 var valueOrDefault = helpers.valueAtIndexOrDefault;
4842 var model = element._model;
4843
4844 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
4845 model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
4846 model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
4847 },
4848
4849 setHoverStyle: function(element) {
4850 var dataset = this.chart.data.datasets[element._datasetIndex];
4851 var index = element._index;
4852 var custom = element.custom || {};
4853 var valueOrDefault = helpers.valueAtIndexOrDefault;
4854 var getHoverColor = helpers.getHoverColor;
4855 var model = element._model;
4856
4857 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
4858 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
4859 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
4860 },
4861
4862 /**
4863 * @private
4864 */
4865 resyncElements: function() {
4866 var me = this;
4867 var meta = me.getMeta();
4868 var data = me.getDataset().data;
4869 var numMeta = meta.data.length;
4870 var numData = data.length;
4871
4872 if (numData < numMeta) {
4873 meta.data.splice(numData, numMeta - numData);
4874 } else if (numData > numMeta) {
4875 me.insertElements(numMeta, numData - numMeta);
4876 }
4877 },
4878
4879 /**
4880 * @private
4881 */
4882 insertElements: function(start, count) {
4883 for (var i = 0; i < count; ++i) {
4884 this.addElementAndReset(start + i);
4885 }
4886 },
4887
4888 /**
4889 * @private
4890 */
4891 onDataPush: function() {
4892 this.insertElements(this.getDataset().data.length - 1, arguments.length);
4893 },
4894
4895 /**
4896 * @private
4897 */
4898 onDataPop: function() {
4899 this.getMeta().data.pop();
4900 },
4901
4902 /**
4903 * @private
4904 */
4905 onDataShift: function() {
4906 this.getMeta().data.shift();
4907 },
4908
4909 /**
4910 * @private
4911 */
4912 onDataSplice: function(start, count) {
4913 this.getMeta().data.splice(start, count);
4914 this.insertElements(start, arguments.length - 2);
4915 },
4916
4917 /**
4918 * @private
4919 */
4920 onDataUnshift: function() {
4921 this.insertElements(0, arguments.length);
4922 }
4923 });
4924
4925 Chart.DatasetController.extend = helpers.inherits;
4926 };
4927
4928 },{"45":45}],25:[function(require,module,exports){
4929 'use strict';
4930
4931 var helpers = require(45);
4932
4933 module.exports = {
4934 /**
4935 * @private
4936 */
4937 _set: function(scope, values) {
4938 return helpers.merge(this[scope] || (this[scope] = {}), values);
4939 }
4940 };
4941
4942 },{"45":45}],26:[function(require,module,exports){
4943 'use strict';
4944
4945 var color = require(3);
4946 var helpers = require(45);
4947
4948 function interpolate(start, view, model, ease) {
4949 var keys = Object.keys(model);
4950 var i, ilen, key, actual, origin, target, type, c0, c1;
4951
4952 for (i = 0, ilen = keys.length; i < ilen; ++i) {
4953 key = keys[i];
4954
4955 target = model[key];
4956
4957 // if a value is added to the model after pivot() has been called, the view
4958 // doesn't contain it, so let's initialize the view to the target value.
4959 if (!view.hasOwnProperty(key)) {
4960 view[key] = target;
4961 }
4962
4963 actual = view[key];
4964
4965 if (actual === target || key[0] === '_') {
4966 continue;
4967 }
4968
4969 if (!start.hasOwnProperty(key)) {
4970 start[key] = actual;
4971 }
4972
4973 origin = start[key];
4974
4975 type = typeof target;
4976
4977 if (type === typeof origin) {
4978 if (type === 'string') {
4979 c0 = color(origin);
4980 if (c0.valid) {
4981 c1 = color(target);
4982 if (c1.valid) {
4983 view[key] = c1.mix(c0, ease).rgbString();
4984 continue;
4985 }
4986 }
4987 } else if (type === 'number' && isFinite(origin) && isFinite(target)) {
4988 view[key] = origin + (target - origin) * ease;
4989 continue;
4990 }
4991 }
4992
4993 view[key] = target;
4994 }
4995 }
4996
4997 var Element = function(configuration) {
4998 helpers.extend(this, configuration);
4999 this.initialize.apply(this, arguments);
5000 };
5001
5002 helpers.extend(Element.prototype, {
5003
5004 initialize: function() {
5005 this.hidden = false;
5006 },
5007
5008 pivot: function() {
5009 var me = this;
5010 if (!me._view) {
5011 me._view = helpers.clone(me._model);
5012 }
5013 me._start = {};
5014 return me;
5015 },
5016
5017 transition: function(ease) {
5018 var me = this;
5019 var model = me._model;
5020 var start = me._start;
5021 var view = me._view;
5022
5023 // No animation -> No Transition
5024 if (!model || ease === 1) {
5025 me._view = model;
5026 me._start = null;
5027 return me;
5028 }
5029
5030 if (!view) {
5031 view = me._view = {};
5032 }
5033
5034 if (!start) {
5035 start = me._start = {};
5036 }
5037
5038 interpolate(start, view, model, ease);
5039
5040 return me;
5041 },
5042
5043 tooltipPosition: function() {
5044 return {
5045 x: this._model.x,
5046 y: this._model.y
5047 };
5048 },
5049
5050 hasValue: function() {
5051 return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
5052 }
5053 });
5054
5055 Element.extend = helpers.inherits;
5056
5057 module.exports = Element;
5058
5059 },{"3":3,"45":45}],27:[function(require,module,exports){
5060 /* global window: false */
5061 /* global document: false */
5062 'use strict';
5063
5064 var color = require(3);
5065 var defaults = require(25);
5066 var helpers = require(45);
5067
5068 module.exports = function(Chart) {
5069
5070 // -- Basic js utility methods
5071
5072 helpers.configMerge = function(/* objects ... */) {
5073 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5074 merger: function(key, target, source, options) {
5075 var tval = target[key] || {};
5076 var sval = source[key];
5077
5078 if (key === 'scales') {
5079 // scale config merging is complex. Add our own function here for that
5080 target[key] = helpers.scaleMerge(tval, sval);
5081 } else if (key === 'scale') {
5082 // used in polar area & radar charts since there is only one scale
5083 target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]);
5084 } else {
5085 helpers._merger(key, target, source, options);
5086 }
5087 }
5088 });
5089 };
5090
5091 helpers.scaleMerge = function(/* objects ... */) {
5092 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5093 merger: function(key, target, source, options) {
5094 if (key === 'xAxes' || key === 'yAxes') {
5095 var slen = source[key].length;
5096 var i, type, scale;
5097
5098 if (!target[key]) {
5099 target[key] = [];
5100 }
5101
5102 for (i = 0; i < slen; ++i) {
5103 scale = source[key][i];
5104 type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear');
5105
5106 if (i >= target[key].length) {
5107 target[key].push({});
5108 }
5109
5110 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
5111 // new/untyped scale or type changed: let's apply the new defaults
5112 // then merge source scale to correctly overwrite the defaults.
5113 helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]);
5114 } else {
5115 // scales type are the same
5116 helpers.merge(target[key][i], scale);
5117 }
5118 }
5119 } else {
5120 helpers._merger(key, target, source, options);
5121 }
5122 }
5123 });
5124 };
5125
5126 helpers.where = function(collection, filterCallback) {
5127 if (helpers.isArray(collection) && Array.prototype.filter) {
5128 return collection.filter(filterCallback);
5129 }
5130 var filtered = [];
5131
5132 helpers.each(collection, function(item) {
5133 if (filterCallback(item)) {
5134 filtered.push(item);
5135 }
5136 });
5137
5138 return filtered;
5139 };
5140 helpers.findIndex = Array.prototype.findIndex ?
5141 function(array, callback, scope) {
5142 return array.findIndex(callback, scope);
5143 } :
5144 function(array, callback, scope) {
5145 scope = scope === undefined ? array : scope;
5146 for (var i = 0, ilen = array.length; i < ilen; ++i) {
5147 if (callback.call(scope, array[i], i, array)) {
5148 return i;
5149 }
5150 }
5151 return -1;
5152 };
5153 helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
5154 // Default to start of the array
5155 if (helpers.isNullOrUndef(startIndex)) {
5156 startIndex = -1;
5157 }
5158 for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
5159 var currentItem = arrayToSearch[i];
5160 if (filterCallback(currentItem)) {
5161 return currentItem;
5162 }
5163 }
5164 };
5165 helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
5166 // Default to end of the array
5167 if (helpers.isNullOrUndef(startIndex)) {
5168 startIndex = arrayToSearch.length;
5169 }
5170 for (var i = startIndex - 1; i >= 0; i--) {
5171 var currentItem = arrayToSearch[i];
5172 if (filterCallback(currentItem)) {
5173 return currentItem;
5174 }
5175 }
5176 };
5177
5178 // -- Math methods
5179 helpers.isNumber = function(n) {
5180 return !isNaN(parseFloat(n)) && isFinite(n);
5181 };
5182 helpers.almostEquals = function(x, y, epsilon) {
5183 return Math.abs(x - y) < epsilon;
5184 };
5185 helpers.almostWhole = function(x, epsilon) {
5186 var rounded = Math.round(x);
5187 return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
5188 };
5189 helpers.max = function(array) {
5190 return array.reduce(function(max, value) {
5191 if (!isNaN(value)) {
5192 return Math.max(max, value);
5193 }
5194 return max;
5195 }, Number.NEGATIVE_INFINITY);
5196 };
5197 helpers.min = function(array) {
5198 return array.reduce(function(min, value) {
5199 if (!isNaN(value)) {
5200 return Math.min(min, value);
5201 }
5202 return min;
5203 }, Number.POSITIVE_INFINITY);
5204 };
5205 helpers.sign = Math.sign ?
5206 function(x) {
5207 return Math.sign(x);
5208 } :
5209 function(x) {
5210 x = +x; // convert to a number
5211 if (x === 0 || isNaN(x)) {
5212 return x;
5213 }
5214 return x > 0 ? 1 : -1;
5215 };
5216 helpers.log10 = Math.log10 ?
5217 function(x) {
5218 return Math.log10(x);
5219 } :
5220 function(x) {
5221 return Math.log(x) / Math.LN10;
5222 };
5223 helpers.toRadians = function(degrees) {
5224 return degrees * (Math.PI / 180);
5225 };
5226 helpers.toDegrees = function(radians) {
5227 return radians * (180 / Math.PI);
5228 };
5229 // Gets the angle from vertical upright to the point about a centre.
5230 helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
5231 var distanceFromXCenter = anglePoint.x - centrePoint.x;
5232 var distanceFromYCenter = anglePoint.y - centrePoint.y;
5233 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
5234
5235 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
5236
5237 if (angle < (-0.5 * Math.PI)) {
5238 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
5239 }
5240
5241 return {
5242 angle: angle,
5243 distance: radialDistanceFromCenter
5244 };
5245 };
5246 helpers.distanceBetweenPoints = function(pt1, pt2) {
5247 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
5248 };
5249 helpers.aliasPixel = function(pixelWidth) {
5250 return (pixelWidth % 2 === 0) ? 0 : 0.5;
5251 };
5252 helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
5253 // Props to Rob Spencer at scaled innovation for his post on splining between points
5254 // http://scaledinnovation.com/analytics/splines/aboutSplines.html
5255
5256 // This function must also respect "skipped" points
5257
5258 var previous = firstPoint.skip ? middlePoint : firstPoint;
5259 var current = middlePoint;
5260 var next = afterPoint.skip ? middlePoint : afterPoint;
5261
5262 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
5263 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
5264
5265 var s01 = d01 / (d01 + d12);
5266 var s12 = d12 / (d01 + d12);
5267
5268 // If all points are the same, s01 & s02 will be inf
5269 s01 = isNaN(s01) ? 0 : s01;
5270 s12 = isNaN(s12) ? 0 : s12;
5271
5272 var fa = t * s01; // scaling factor for triangle Ta
5273 var fb = t * s12;
5274
5275 return {
5276 previous: {
5277 x: current.x - fa * (next.x - previous.x),
5278 y: current.y - fa * (next.y - previous.y)
5279 },
5280 next: {
5281 x: current.x + fb * (next.x - previous.x),
5282 y: current.y + fb * (next.y - previous.y)
5283 }
5284 };
5285 };
5286 helpers.EPSILON = Number.EPSILON || 1e-14;
5287 helpers.splineCurveMonotone = function(points) {
5288 // This function calculates Bézier control points in a similar way than |splineCurve|,
5289 // but preserves monotonicity of the provided data and ensures no local extremums are added
5290 // between the dataset discrete points due to the interpolation.
5291 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
5292
5293 var pointsWithTangents = (points || []).map(function(point) {
5294 return {
5295 model: point._model,
5296 deltaK: 0,
5297 mK: 0
5298 };
5299 });
5300
5301 // Calculate slopes (deltaK) and initialize tangents (mK)
5302 var pointsLen = pointsWithTangents.length;
5303 var i, pointBefore, pointCurrent, pointAfter;
5304 for (i = 0; i < pointsLen; ++i) {
5305 pointCurrent = pointsWithTangents[i];
5306 if (pointCurrent.model.skip) {
5307 continue;
5308 }
5309
5310 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5311 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5312 if (pointAfter && !pointAfter.model.skip) {
5313 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
5314
5315 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
5316 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
5317 }
5318
5319 if (!pointBefore || pointBefore.model.skip) {
5320 pointCurrent.mK = pointCurrent.deltaK;
5321 } else if (!pointAfter || pointAfter.model.skip) {
5322 pointCurrent.mK = pointBefore.deltaK;
5323 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
5324 pointCurrent.mK = 0;
5325 } else {
5326 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
5327 }
5328 }
5329
5330 // Adjust tangents to ensure monotonic properties
5331 var alphaK, betaK, tauK, squaredMagnitude;
5332 for (i = 0; i < pointsLen - 1; ++i) {
5333 pointCurrent = pointsWithTangents[i];
5334 pointAfter = pointsWithTangents[i + 1];
5335 if (pointCurrent.model.skip || pointAfter.model.skip) {
5336 continue;
5337 }
5338
5339 if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
5340 pointCurrent.mK = pointAfter.mK = 0;
5341 continue;
5342 }
5343
5344 alphaK = pointCurrent.mK / pointCurrent.deltaK;
5345 betaK = pointAfter.mK / pointCurrent.deltaK;
5346 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
5347 if (squaredMagnitude <= 9) {
5348 continue;
5349 }
5350
5351 tauK = 3 / Math.sqrt(squaredMagnitude);
5352 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
5353 pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
5354 }
5355
5356 // Compute control points
5357 var deltaX;
5358 for (i = 0; i < pointsLen; ++i) {
5359 pointCurrent = pointsWithTangents[i];
5360 if (pointCurrent.model.skip) {
5361 continue;
5362 }
5363
5364 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5365 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5366 if (pointBefore && !pointBefore.model.skip) {
5367 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
5368 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
5369 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
5370 }
5371 if (pointAfter && !pointAfter.model.skip) {
5372 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
5373 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
5374 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
5375 }
5376 }
5377 };
5378 helpers.nextItem = function(collection, index, loop) {
5379 if (loop) {
5380 return index >= collection.length - 1 ? collection[0] : collection[index + 1];
5381 }
5382 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
5383 };
5384 helpers.previousItem = function(collection, index, loop) {
5385 if (loop) {
5386 return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
5387 }
5388 return index <= 0 ? collection[0] : collection[index - 1];
5389 };
5390 // Implementation of the nice number algorithm used in determining where axis labels will go
5391 helpers.niceNum = function(range, round) {
5392 var exponent = Math.floor(helpers.log10(range));
5393 var fraction = range / Math.pow(10, exponent);
5394 var niceFraction;
5395
5396 if (round) {
5397 if (fraction < 1.5) {
5398 niceFraction = 1;
5399 } else if (fraction < 3) {
5400 niceFraction = 2;
5401 } else if (fraction < 7) {
5402 niceFraction = 5;
5403 } else {
5404 niceFraction = 10;
5405 }
5406 } else if (fraction <= 1.0) {
5407 niceFraction = 1;
5408 } else if (fraction <= 2) {
5409 niceFraction = 2;
5410 } else if (fraction <= 5) {
5411 niceFraction = 5;
5412 } else {
5413 niceFraction = 10;
5414 }
5415
5416 return niceFraction * Math.pow(10, exponent);
5417 };
5418 // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
5419 helpers.requestAnimFrame = (function() {
5420 if (typeof window === 'undefined') {
5421 return function(callback) {
5422 callback();
5423 };
5424 }
5425 return window.requestAnimationFrame ||
5426 window.webkitRequestAnimationFrame ||
5427 window.mozRequestAnimationFrame ||
5428 window.oRequestAnimationFrame ||
5429 window.msRequestAnimationFrame ||
5430 function(callback) {
5431 return window.setTimeout(callback, 1000 / 60);
5432 };
5433 }());
5434 // -- DOM methods
5435 helpers.getRelativePosition = function(evt, chart) {
5436 var mouseX, mouseY;
5437 var e = evt.originalEvent || evt;
5438 var canvas = evt.currentTarget || evt.srcElement;
5439 var boundingRect = canvas.getBoundingClientRect();
5440
5441 var touches = e.touches;
5442 if (touches && touches.length > 0) {
5443 mouseX = touches[0].clientX;
5444 mouseY = touches[0].clientY;
5445
5446 } else {
5447 mouseX = e.clientX;
5448 mouseY = e.clientY;
5449 }
5450
5451 // Scale mouse coordinates into canvas coordinates
5452 // by following the pattern laid out by 'jerryj' in the comments of
5453 // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
5454 var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
5455 var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
5456 var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
5457 var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
5458 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
5459 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
5460
5461 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
5462 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
5463 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
5464 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
5465
5466 return {
5467 x: mouseX,
5468 y: mouseY
5469 };
5470
5471 };
5472
5473 // Private helper function to convert max-width/max-height values that may be percentages into a number
5474 function parseMaxStyle(styleValue, node, parentProperty) {
5475 var valueInPixels;
5476 if (typeof styleValue === 'string') {
5477 valueInPixels = parseInt(styleValue, 10);
5478
5479 if (styleValue.indexOf('%') !== -1) {
5480 // percentage * size in dimension
5481 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
5482 }
5483 } else {
5484 valueInPixels = styleValue;
5485 }
5486
5487 return valueInPixels;
5488 }
5489
5490 /**
5491 * Returns if the given value contains an effective constraint.
5492 * @private
5493 */
5494 function isConstrainedValue(value) {
5495 return value !== undefined && value !== null && value !== 'none';
5496 }
5497
5498 // Private helper to get a constraint dimension
5499 // @param domNode : the node to check the constraint on
5500 // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
5501 // @param percentageProperty : property of parent to use when calculating width as a percentage
5502 // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
5503 function getConstraintDimension(domNode, maxStyle, percentageProperty) {
5504 var view = document.defaultView;
5505 var parentNode = domNode.parentNode;
5506 var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
5507 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
5508 var hasCNode = isConstrainedValue(constrainedNode);
5509 var hasCContainer = isConstrainedValue(constrainedContainer);
5510 var infinity = Number.POSITIVE_INFINITY;
5511
5512 if (hasCNode || hasCContainer) {
5513 return Math.min(
5514 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
5515 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
5516 }
5517
5518 return 'none';
5519 }
5520 // returns Number or undefined if no constraint
5521 helpers.getConstraintWidth = function(domNode) {
5522 return getConstraintDimension(domNode, 'max-width', 'clientWidth');
5523 };
5524 // returns Number or undefined if no constraint
5525 helpers.getConstraintHeight = function(domNode) {
5526 return getConstraintDimension(domNode, 'max-height', 'clientHeight');
5527 };
5528 helpers.getMaximumWidth = function(domNode) {
5529 var container = domNode.parentNode;
5530 if (!container) {
5531 return domNode.clientWidth;
5532 }
5533
5534 var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
5535 var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
5536 var w = container.clientWidth - paddingLeft - paddingRight;
5537 var cw = helpers.getConstraintWidth(domNode);
5538 return isNaN(cw) ? w : Math.min(w, cw);
5539 };
5540 helpers.getMaximumHeight = function(domNode) {
5541 var container = domNode.parentNode;
5542 if (!container) {
5543 return domNode.clientHeight;
5544 }
5545
5546 var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
5547 var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
5548 var h = container.clientHeight - paddingTop - paddingBottom;
5549 var ch = helpers.getConstraintHeight(domNode);
5550 return isNaN(ch) ? h : Math.min(h, ch);
5551 };
5552 helpers.getStyle = function(el, property) {
5553 return el.currentStyle ?
5554 el.currentStyle[property] :
5555 document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
5556 };
5557 helpers.retinaScale = function(chart, forceRatio) {
5558 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1;
5559 if (pixelRatio === 1) {
5560 return;
5561 }
5562
5563 var canvas = chart.canvas;
5564 var height = chart.height;
5565 var width = chart.width;
5566
5567 canvas.height = height * pixelRatio;
5568 canvas.width = width * pixelRatio;
5569 chart.ctx.scale(pixelRatio, pixelRatio);
5570
5571 // If no style has been set on the canvas, the render size is used as display size,
5572 // making the chart visually bigger, so let's enforce it to the "correct" values.
5573 // See https://github.com/chartjs/Chart.js/issues/3575
5574 canvas.style.height = height + 'px';
5575 canvas.style.width = width + 'px';
5576 };
5577 // -- Canvas methods
5578 helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
5579 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
5580 };
5581 helpers.longestText = function(ctx, font, arrayOfThings, cache) {
5582 cache = cache || {};
5583 var data = cache.data = cache.data || {};
5584 var gc = cache.garbageCollect = cache.garbageCollect || [];
5585
5586 if (cache.font !== font) {
5587 data = cache.data = {};
5588 gc = cache.garbageCollect = [];
5589 cache.font = font;
5590 }
5591
5592 ctx.font = font;
5593 var longest = 0;
5594 helpers.each(arrayOfThings, function(thing) {
5595 // Undefined strings and arrays should not be measured
5596 if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
5597 longest = helpers.measureText(ctx, data, gc, longest, thing);
5598 } else if (helpers.isArray(thing)) {
5599 // if it is an array lets measure each element
5600 // to do maybe simplify this function a bit so we can do this more recursively?
5601 helpers.each(thing, function(nestedThing) {
5602 // Undefined strings and arrays should not be measured
5603 if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
5604 longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
5605 }
5606 });
5607 }
5608 });
5609
5610 var gcLen = gc.length / 2;
5611 if (gcLen > arrayOfThings.length) {
5612 for (var i = 0; i < gcLen; i++) {
5613 delete data[gc[i]];
5614 }
5615 gc.splice(0, gcLen);
5616 }
5617 return longest;
5618 };
5619 helpers.measureText = function(ctx, data, gc, longest, string) {
5620 var textWidth = data[string];
5621 if (!textWidth) {
5622 textWidth = data[string] = ctx.measureText(string).width;
5623 gc.push(string);
5624 }
5625 if (textWidth > longest) {
5626 longest = textWidth;
5627 }
5628 return longest;
5629 };
5630 helpers.numberOfLabelLines = function(arrayOfThings) {
5631 var numberOfLines = 1;
5632 helpers.each(arrayOfThings, function(thing) {
5633 if (helpers.isArray(thing)) {
5634 if (thing.length > numberOfLines) {
5635 numberOfLines = thing.length;
5636 }
5637 }
5638 });
5639 return numberOfLines;
5640 };
5641
5642 helpers.color = !color ?
5643 function(value) {
5644 console.error('Color.js not found!');
5645 return value;
5646 } :
5647 function(value) {
5648 /* global CanvasGradient */
5649 if (value instanceof CanvasGradient) {
5650 value = defaults.global.defaultColor;
5651 }
5652
5653 return color(value);
5654 };
5655
5656 helpers.getHoverColor = function(colorValue) {
5657 /* global CanvasPattern */
5658 return (colorValue instanceof CanvasPattern) ?
5659 colorValue :
5660 helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
5661 };
5662 };
5663
5664 },{"25":25,"3":3,"45":45}],28:[function(require,module,exports){
5665 'use strict';
5666
5667 var helpers = require(45);
5668
5669 /**
5670 * Helper function to get relative position for an event
5671 * @param {Event|IEvent} event - The event to get the position for
5672 * @param {Chart} chart - The chart
5673 * @returns {Point} the event position
5674 */
5675 function getRelativePosition(e, chart) {
5676 if (e.native) {
5677 return {
5678 x: e.x,
5679 y: e.y
5680 };
5681 }
5682
5683 return helpers.getRelativePosition(e, chart);
5684 }
5685
5686 /**
5687 * Helper function to traverse all of the visible elements in the chart
5688 * @param chart {chart} the chart
5689 * @param handler {Function} the callback to execute for each visible item
5690 */
5691 function parseVisibleItems(chart, handler) {
5692 var datasets = chart.data.datasets;
5693 var meta, i, j, ilen, jlen;
5694
5695 for (i = 0, ilen = datasets.length; i < ilen; ++i) {
5696 if (!chart.isDatasetVisible(i)) {
5697 continue;
5698 }
5699
5700 meta = chart.getDatasetMeta(i);
5701 for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
5702 var element = meta.data[j];
5703 if (!element._view.skip) {
5704 handler(element);
5705 }
5706 }
5707 }
5708 }
5709
5710 /**
5711 * Helper function to get the items that intersect the event position
5712 * @param items {ChartElement[]} elements to filter
5713 * @param position {Point} the point to be nearest to
5714 * @return {ChartElement[]} the nearest items
5715 */
5716 function getIntersectItems(chart, position) {
5717 var elements = [];
5718
5719 parseVisibleItems(chart, function(element) {
5720 if (element.inRange(position.x, position.y)) {
5721 elements.push(element);
5722 }
5723 });
5724
5725 return elements;
5726 }
5727
5728 /**
5729 * Helper function to get the items nearest to the event position considering all visible items in teh chart
5730 * @param chart {Chart} the chart to look at elements from
5731 * @param position {Point} the point to be nearest to
5732 * @param intersect {Boolean} if true, only consider items that intersect the position
5733 * @param distanceMetric {Function} function to provide the distance between points
5734 * @return {ChartElement[]} the nearest items
5735 */
5736 function getNearestItems(chart, position, intersect, distanceMetric) {
5737 var minDistance = Number.POSITIVE_INFINITY;
5738 var nearestItems = [];
5739
5740 parseVisibleItems(chart, function(element) {
5741 if (intersect && !element.inRange(position.x, position.y)) {
5742 return;
5743 }
5744
5745 var center = element.getCenterPoint();
5746 var distance = distanceMetric(position, center);
5747
5748 if (distance < minDistance) {
5749 nearestItems = [element];
5750 minDistance = distance;
5751 } else if (distance === minDistance) {
5752 // Can have multiple items at the same distance in which case we sort by size
5753 nearestItems.push(element);
5754 }
5755 });
5756
5757 return nearestItems;
5758 }
5759
5760 /**
5761 * Get a distance metric function for two points based on the
5762 * axis mode setting
5763 * @param {String} axis the axis mode. x|y|xy
5764 */
5765 function getDistanceMetricForAxis(axis) {
5766 var useX = axis.indexOf('x') !== -1;
5767 var useY = axis.indexOf('y') !== -1;
5768
5769 return function(pt1, pt2) {
5770 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
5771 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
5772 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
5773 };
5774 }
5775
5776 function indexMode(chart, e, options) {
5777 var position = getRelativePosition(e, chart);
5778 // Default axis for index mode is 'x' to match old behaviour
5779 options.axis = options.axis || 'x';
5780 var distanceMetric = getDistanceMetricForAxis(options.axis);
5781 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5782 var elements = [];
5783
5784 if (!items.length) {
5785 return [];
5786 }
5787
5788 chart.data.datasets.forEach(function(dataset, datasetIndex) {
5789 if (chart.isDatasetVisible(datasetIndex)) {
5790 var meta = chart.getDatasetMeta(datasetIndex);
5791 var element = meta.data[items[0]._index];
5792
5793 // don't count items that are skipped (null data)
5794 if (element && !element._view.skip) {
5795 elements.push(element);
5796 }
5797 }
5798 });
5799
5800 return elements;
5801 }
5802
5803 /**
5804 * @interface IInteractionOptions
5805 */
5806 /**
5807 * If true, only consider items that intersect the point
5808 * @name IInterfaceOptions#boolean
5809 * @type Boolean
5810 */
5811
5812 /**
5813 * Contains interaction related functions
5814 * @namespace Chart.Interaction
5815 */
5816 module.exports = {
5817 // Helper function for different modes
5818 modes: {
5819 single: function(chart, e) {
5820 var position = getRelativePosition(e, chart);
5821 var elements = [];
5822
5823 parseVisibleItems(chart, function(element) {
5824 if (element.inRange(position.x, position.y)) {
5825 elements.push(element);
5826 return elements;
5827 }
5828 });
5829
5830 return elements.slice(0, 1);
5831 },
5832
5833 /**
5834 * @function Chart.Interaction.modes.label
5835 * @deprecated since version 2.4.0
5836 * @todo remove at version 3
5837 * @private
5838 */
5839 label: indexMode,
5840
5841 /**
5842 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
5843 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
5844 * @function Chart.Interaction.modes.index
5845 * @since v2.4.0
5846 * @param chart {chart} the chart we are returning items from
5847 * @param e {Event} the event we are find things at
5848 * @param options {IInteractionOptions} options to use during interaction
5849 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5850 */
5851 index: indexMode,
5852
5853 /**
5854 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
5855 * If the options.intersect is false, we find the nearest item and return the items in that dataset
5856 * @function Chart.Interaction.modes.dataset
5857 * @param chart {chart} the chart we are returning items from
5858 * @param e {Event} the event we are find things at
5859 * @param options {IInteractionOptions} options to use during interaction
5860 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5861 */
5862 dataset: function(chart, e, options) {
5863 var position = getRelativePosition(e, chart);
5864 options.axis = options.axis || 'xy';
5865 var distanceMetric = getDistanceMetricForAxis(options.axis);
5866 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5867
5868 if (items.length > 0) {
5869 items = chart.getDatasetMeta(items[0]._datasetIndex).data;
5870 }
5871
5872 return items;
5873 },
5874
5875 /**
5876 * @function Chart.Interaction.modes.x-axis
5877 * @deprecated since version 2.4.0. Use index mode and intersect == true
5878 * @todo remove at version 3
5879 * @private
5880 */
5881 'x-axis': function(chart, e) {
5882 return indexMode(chart, e, {intersect: false});
5883 },
5884
5885 /**
5886 * Point mode returns all elements that hit test based on the event position
5887 * of the event
5888 * @function Chart.Interaction.modes.intersect
5889 * @param chart {chart} the chart we are returning items from
5890 * @param e {Event} the event we are find things at
5891 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5892 */
5893 point: function(chart, e) {
5894 var position = getRelativePosition(e, chart);
5895 return getIntersectItems(chart, position);
5896 },
5897
5898 /**
5899 * nearest mode returns the element closest to the point
5900 * @function Chart.Interaction.modes.intersect
5901 * @param chart {chart} the chart we are returning items from
5902 * @param e {Event} the event we are find things at
5903 * @param options {IInteractionOptions} options to use
5904 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5905 */
5906 nearest: function(chart, e, options) {
5907 var position = getRelativePosition(e, chart);
5908 options.axis = options.axis || 'xy';
5909 var distanceMetric = getDistanceMetricForAxis(options.axis);
5910 var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric);
5911
5912 // We have multiple items at the same distance from the event. Now sort by smallest
5913 if (nearestItems.length > 1) {
5914 nearestItems.sort(function(a, b) {
5915 var sizeA = a.getArea();
5916 var sizeB = b.getArea();
5917 var ret = sizeA - sizeB;
5918
5919 if (ret === 0) {
5920 // if equal sort by dataset index
5921 ret = a._datasetIndex - b._datasetIndex;
5922 }
5923
5924 return ret;
5925 });
5926 }
5927
5928 // Return only 1 item
5929 return nearestItems.slice(0, 1);
5930 },
5931
5932 /**
5933 * x mode returns the elements that hit-test at the current x coordinate
5934 * @function Chart.Interaction.modes.x
5935 * @param chart {chart} the chart we are returning items from
5936 * @param e {Event} the event we are find things at
5937 * @param options {IInteractionOptions} options to use
5938 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5939 */
5940 x: function(chart, e, options) {
5941 var position = getRelativePosition(e, chart);
5942 var items = [];
5943 var intersectsItem = false;
5944
5945 parseVisibleItems(chart, function(element) {
5946 if (element.inXRange(position.x)) {
5947 items.push(element);
5948 }
5949
5950 if (element.inRange(position.x, position.y)) {
5951 intersectsItem = true;
5952 }
5953 });
5954
5955 // If we want to trigger on an intersect and we don't have any items
5956 // that intersect the position, return nothing
5957 if (options.intersect && !intersectsItem) {
5958 items = [];
5959 }
5960 return items;
5961 },
5962
5963 /**
5964 * y mode returns the elements that hit-test at the current y coordinate
5965 * @function Chart.Interaction.modes.y
5966 * @param chart {chart} the chart we are returning items from
5967 * @param e {Event} the event we are find things at
5968 * @param options {IInteractionOptions} options to use
5969 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5970 */
5971 y: function(chart, e, options) {
5972 var position = getRelativePosition(e, chart);
5973 var items = [];
5974 var intersectsItem = false;
5975
5976 parseVisibleItems(chart, function(element) {
5977 if (element.inYRange(position.y)) {
5978 items.push(element);
5979 }
5980
5981 if (element.inRange(position.x, position.y)) {
5982 intersectsItem = true;
5983 }
5984 });
5985
5986 // If we want to trigger on an intersect and we don't have any items
5987 // that intersect the position, return nothing
5988 if (options.intersect && !intersectsItem) {
5989 items = [];
5990 }
5991 return items;
5992 }
5993 }
5994 };
5995
5996 },{"45":45}],29:[function(require,module,exports){
5997 'use strict';
5998
5999 var defaults = require(25);
6000
6001 defaults._set('global', {
6002 responsive: true,
6003 responsiveAnimationDuration: 0,
6004 maintainAspectRatio: true,
6005 events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
6006 hover: {
6007 onHover: null,
6008 mode: 'nearest',
6009 intersect: true,
6010 animationDuration: 400
6011 },
6012 onClick: null,
6013 defaultColor: 'rgba(0,0,0,0.1)',
6014 defaultFontColor: '#666',
6015 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
6016 defaultFontSize: 12,
6017 defaultFontStyle: 'normal',
6018 showLines: true,
6019
6020 // Element defaults defined in element extensions
6021 elements: {},
6022
6023 // Layout options such as padding
6024 layout: {
6025 padding: {
6026 top: 0,
6027 right: 0,
6028 bottom: 0,
6029 left: 0
6030 }
6031 }
6032 });
6033
6034 module.exports = function() {
6035
6036 // Occupy the global variable of Chart, and create a simple base class
6037 var Chart = function(item, config) {
6038 this.construct(item, config);
6039 return this;
6040 };
6041
6042 Chart.Chart = Chart;
6043
6044 return Chart;
6045 };
6046
6047 },{"25":25}],30:[function(require,module,exports){
6048 'use strict';
6049
6050 var helpers = require(45);
6051
6052 module.exports = function(Chart) {
6053
6054 function filterByPosition(array, position) {
6055 return helpers.where(array, function(v) {
6056 return v.position === position;
6057 });
6058 }
6059
6060 function sortByWeight(array, reverse) {
6061 array.forEach(function(v, i) {
6062 v._tmpIndex_ = i;
6063 return v;
6064 });
6065 array.sort(function(a, b) {
6066 var v0 = reverse ? b : a;
6067 var v1 = reverse ? a : b;
6068 return v0.weight === v1.weight ?
6069 v0._tmpIndex_ - v1._tmpIndex_ :
6070 v0.weight - v1.weight;
6071 });
6072 array.forEach(function(v) {
6073 delete v._tmpIndex_;
6074 });
6075 }
6076
6077 /**
6078 * @interface ILayoutItem
6079 * @prop {String} position - The position of the item in the chart layout. Possible values are
6080 * 'left', 'top', 'right', 'bottom', and 'chartArea'
6081 * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
6082 * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
6083 * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
6084 * @prop {Function} update - Takes two parameters: width and height. Returns size of item
6085 * @prop {Function} getPadding - Returns an object with padding on the edges
6086 * @prop {Number} width - Width of item. Must be valid after update()
6087 * @prop {Number} height - Height of item. Must be valid after update()
6088 * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
6089 * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
6090 * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
6091 * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
6092 */
6093
6094 // The layout service is very self explanatory. It's responsible for the layout within a chart.
6095 // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
6096 // It is this service's responsibility of carrying out that layout.
6097 Chart.layoutService = {
6098 defaults: {},
6099
6100 /**
6101 * Register a box to a chart.
6102 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
6103 * @param {Chart} chart - the chart to use
6104 * @param {ILayoutItem} item - the item to add to be layed out
6105 */
6106 addBox: function(chart, item) {
6107 if (!chart.boxes) {
6108 chart.boxes = [];
6109 }
6110
6111 // initialize item with default values
6112 item.fullWidth = item.fullWidth || false;
6113 item.position = item.position || 'top';
6114 item.weight = item.weight || 0;
6115
6116 chart.boxes.push(item);
6117 },
6118
6119 /**
6120 * Remove a layoutItem from a chart
6121 * @param {Chart} chart - the chart to remove the box from
6122 * @param {Object} layoutItem - the item to remove from the layout
6123 */
6124 removeBox: function(chart, layoutItem) {
6125 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
6126 if (index !== -1) {
6127 chart.boxes.splice(index, 1);
6128 }
6129 },
6130
6131 /**
6132 * Sets (or updates) options on the given `item`.
6133 * @param {Chart} chart - the chart in which the item lives (or will be added to)
6134 * @param {Object} item - the item to configure with the given options
6135 * @param {Object} options - the new item options.
6136 */
6137 configure: function(chart, item, options) {
6138 var props = ['fullWidth', 'position', 'weight'];
6139 var ilen = props.length;
6140 var i = 0;
6141 var prop;
6142
6143 for (; i < ilen; ++i) {
6144 prop = props[i];
6145 if (options.hasOwnProperty(prop)) {
6146 item[prop] = options[prop];
6147 }
6148 }
6149 },
6150
6151 /**
6152 * Fits boxes of the given chart into the given size by having each box measure itself
6153 * then running a fitting algorithm
6154 * @param {Chart} chart - the chart
6155 * @param {Number} width - the width to fit into
6156 * @param {Number} height - the height to fit into
6157 */
6158 update: function(chart, width, height) {
6159 if (!chart) {
6160 return;
6161 }
6162
6163 var layoutOptions = chart.options.layout || {};
6164 var padding = helpers.options.toPadding(layoutOptions.padding);
6165 var leftPadding = padding.left;
6166 var rightPadding = padding.right;
6167 var topPadding = padding.top;
6168 var bottomPadding = padding.bottom;
6169
6170 var leftBoxes = filterByPosition(chart.boxes, 'left');
6171 var rightBoxes = filterByPosition(chart.boxes, 'right');
6172 var topBoxes = filterByPosition(chart.boxes, 'top');
6173 var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
6174 var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
6175
6176 // Sort boxes by weight. A higher weight is further away from the chart area
6177 sortByWeight(leftBoxes, true);
6178 sortByWeight(rightBoxes, false);
6179 sortByWeight(topBoxes, true);
6180 sortByWeight(bottomBoxes, false);
6181
6182 // Essentially we now have any number of boxes on each of the 4 sides.
6183 // Our canvas looks like the following.
6184 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
6185 // B1 is the bottom axis
6186 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
6187 // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
6188 // an error will be thrown.
6189 //
6190 // |----------------------------------------------------|
6191 // | T1 (Full Width) |
6192 // |----------------------------------------------------|
6193 // | | | T2 | |
6194 // | |----|-------------------------------------|----|
6195 // | | | C1 | | C2 | |
6196 // | | |----| |----| |
6197 // | | | | |
6198 // | L1 | L2 | ChartArea (C0) | R1 |
6199 // | | | | |
6200 // | | |----| |----| |
6201 // | | | C3 | | C4 | |
6202 // | |----|-------------------------------------|----|
6203 // | | | B1 | |
6204 // |----------------------------------------------------|
6205 // | B2 (Full Width) |
6206 // |----------------------------------------------------|
6207 //
6208 // What we do to find the best sizing, we do the following
6209 // 1. Determine the minimum size of the chart area.
6210 // 2. Split the remaining width equally between each vertical axis
6211 // 3. Split the remaining height equally between each horizontal axis
6212 // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
6213 // 5. Adjust the sizes of each axis based on it's minimum reported size.
6214 // 6. Refit each axis
6215 // 7. Position each axis in the final location
6216 // 8. Tell the chart the final location of the chart area
6217 // 9. Tell any axes that overlay the chart area the positions of the chart area
6218
6219 // Step 1
6220 var chartWidth = width - leftPadding - rightPadding;
6221 var chartHeight = height - topPadding - bottomPadding;
6222 var chartAreaWidth = chartWidth / 2; // min 50%
6223 var chartAreaHeight = chartHeight / 2; // min 50%
6224
6225 // Step 2
6226 var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
6227
6228 // Step 3
6229 var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
6230
6231 // Step 4
6232 var maxChartAreaWidth = chartWidth;
6233 var maxChartAreaHeight = chartHeight;
6234 var minBoxSizes = [];
6235
6236 function getMinimumBoxSize(box) {
6237 var minSize;
6238 var isHorizontal = box.isHorizontal();
6239
6240 if (isHorizontal) {
6241 minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
6242 maxChartAreaHeight -= minSize.height;
6243 } else {
6244 minSize = box.update(verticalBoxWidth, chartAreaHeight);
6245 maxChartAreaWidth -= minSize.width;
6246 }
6247
6248 minBoxSizes.push({
6249 horizontal: isHorizontal,
6250 minSize: minSize,
6251 box: box,
6252 });
6253 }
6254
6255 helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
6256
6257 // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
6258 var maxHorizontalLeftPadding = 0;
6259 var maxHorizontalRightPadding = 0;
6260 var maxVerticalTopPadding = 0;
6261 var maxVerticalBottomPadding = 0;
6262
6263 helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
6264 if (horizontalBox.getPadding) {
6265 var boxPadding = horizontalBox.getPadding();
6266 maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
6267 maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
6268 }
6269 });
6270
6271 helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
6272 if (verticalBox.getPadding) {
6273 var boxPadding = verticalBox.getPadding();
6274 maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
6275 maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
6276 }
6277 });
6278
6279 // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
6280 // be if the axes are drawn at their minimum sizes.
6281 // Steps 5 & 6
6282 var totalLeftBoxesWidth = leftPadding;
6283 var totalRightBoxesWidth = rightPadding;
6284 var totalTopBoxesHeight = topPadding;
6285 var totalBottomBoxesHeight = bottomPadding;
6286
6287 // Function to fit a box
6288 function fitBox(box) {
6289 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
6290 return minBox.box === box;
6291 });
6292
6293 if (minBoxSize) {
6294 if (box.isHorizontal()) {
6295 var scaleMargin = {
6296 left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
6297 right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
6298 top: 0,
6299 bottom: 0
6300 };
6301
6302 // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
6303 // on the margin. Sometimes they need to increase in size slightly
6304 box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
6305 } else {
6306 box.update(minBoxSize.minSize.width, maxChartAreaHeight);
6307 }
6308 }
6309 }
6310
6311 // Update, and calculate the left and right margins for the horizontal boxes
6312 helpers.each(leftBoxes.concat(rightBoxes), fitBox);
6313
6314 helpers.each(leftBoxes, function(box) {
6315 totalLeftBoxesWidth += box.width;
6316 });
6317
6318 helpers.each(rightBoxes, function(box) {
6319 totalRightBoxesWidth += box.width;
6320 });
6321
6322 // Set the Left and Right margins for the horizontal boxes
6323 helpers.each(topBoxes.concat(bottomBoxes), fitBox);
6324
6325 // Figure out how much margin is on the top and bottom of the vertical boxes
6326 helpers.each(topBoxes, function(box) {
6327 totalTopBoxesHeight += box.height;
6328 });
6329
6330 helpers.each(bottomBoxes, function(box) {
6331 totalBottomBoxesHeight += box.height;
6332 });
6333
6334 function finalFitVerticalBox(box) {
6335 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
6336 return minSize.box === box;
6337 });
6338
6339 var scaleMargin = {
6340 left: 0,
6341 right: 0,
6342 top: totalTopBoxesHeight,
6343 bottom: totalBottomBoxesHeight
6344 };
6345
6346 if (minBoxSize) {
6347 box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
6348 }
6349 }
6350
6351 // Let the left layout know the final margin
6352 helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
6353
6354 // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
6355 totalLeftBoxesWidth = leftPadding;
6356 totalRightBoxesWidth = rightPadding;
6357 totalTopBoxesHeight = topPadding;
6358 totalBottomBoxesHeight = bottomPadding;
6359
6360 helpers.each(leftBoxes, function(box) {
6361 totalLeftBoxesWidth += box.width;
6362 });
6363
6364 helpers.each(rightBoxes, function(box) {
6365 totalRightBoxesWidth += box.width;
6366 });
6367
6368 helpers.each(topBoxes, function(box) {
6369 totalTopBoxesHeight += box.height;
6370 });
6371 helpers.each(bottomBoxes, function(box) {
6372 totalBottomBoxesHeight += box.height;
6373 });
6374
6375 // We may be adding some padding to account for rotated x axis labels
6376 var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
6377 totalLeftBoxesWidth += leftPaddingAddition;
6378 totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
6379
6380 var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
6381 totalTopBoxesHeight += topPaddingAddition;
6382 totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
6383
6384 // Figure out if our chart area changed. This would occur if the dataset layout label rotation
6385 // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
6386 // without calling `fit` again
6387 var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
6388 var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
6389
6390 if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
6391 helpers.each(leftBoxes, function(box) {
6392 box.height = newMaxChartAreaHeight;
6393 });
6394
6395 helpers.each(rightBoxes, function(box) {
6396 box.height = newMaxChartAreaHeight;
6397 });
6398
6399 helpers.each(topBoxes, function(box) {
6400 if (!box.fullWidth) {
6401 box.width = newMaxChartAreaWidth;
6402 }
6403 });
6404
6405 helpers.each(bottomBoxes, function(box) {
6406 if (!box.fullWidth) {
6407 box.width = newMaxChartAreaWidth;
6408 }
6409 });
6410
6411 maxChartAreaHeight = newMaxChartAreaHeight;
6412 maxChartAreaWidth = newMaxChartAreaWidth;
6413 }
6414
6415 // Step 7 - Position the boxes
6416 var left = leftPadding + leftPaddingAddition;
6417 var top = topPadding + topPaddingAddition;
6418
6419 function placeBox(box) {
6420 if (box.isHorizontal()) {
6421 box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
6422 box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
6423 box.top = top;
6424 box.bottom = top + box.height;
6425
6426 // Move to next point
6427 top = box.bottom;
6428
6429 } else {
6430
6431 box.left = left;
6432 box.right = left + box.width;
6433 box.top = totalTopBoxesHeight;
6434 box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
6435
6436 // Move to next point
6437 left = box.right;
6438 }
6439 }
6440
6441 helpers.each(leftBoxes.concat(topBoxes), placeBox);
6442
6443 // Account for chart width and height
6444 left += maxChartAreaWidth;
6445 top += maxChartAreaHeight;
6446
6447 helpers.each(rightBoxes, placeBox);
6448 helpers.each(bottomBoxes, placeBox);
6449
6450 // Step 8
6451 chart.chartArea = {
6452 left: totalLeftBoxesWidth,
6453 top: totalTopBoxesHeight,
6454 right: totalLeftBoxesWidth + maxChartAreaWidth,
6455 bottom: totalTopBoxesHeight + maxChartAreaHeight
6456 };
6457
6458 // Step 9
6459 helpers.each(chartAreaBoxes, function(box) {
6460 box.left = chart.chartArea.left;
6461 box.top = chart.chartArea.top;
6462 box.right = chart.chartArea.right;
6463 box.bottom = chart.chartArea.bottom;
6464
6465 box.update(maxChartAreaWidth, maxChartAreaHeight);
6466 });
6467 }
6468 };
6469 };
6470
6471 },{"45":45}],31:[function(require,module,exports){
6472 'use strict';
6473
6474 var defaults = require(25);
6475 var Element = require(26);
6476 var helpers = require(45);
6477
6478 defaults._set('global', {
6479 plugins: {}
6480 });
6481
6482 module.exports = function(Chart) {
6483
6484 /**
6485 * The plugin service singleton
6486 * @namespace Chart.plugins
6487 * @since 2.1.0
6488 */
6489 Chart.plugins = {
6490 /**
6491 * Globally registered plugins.
6492 * @private
6493 */
6494 _plugins: [],
6495
6496 /**
6497 * This identifier is used to invalidate the descriptors cache attached to each chart
6498 * when a global plugin is registered or unregistered. In this case, the cache ID is
6499 * incremented and descriptors are regenerated during following API calls.
6500 * @private
6501 */
6502 _cacheId: 0,
6503
6504 /**
6505 * Registers the given plugin(s) if not already registered.
6506 * @param {Array|Object} plugins plugin instance(s).
6507 */
6508 register: function(plugins) {
6509 var p = this._plugins;
6510 ([]).concat(plugins).forEach(function(plugin) {
6511 if (p.indexOf(plugin) === -1) {
6512 p.push(plugin);
6513 }
6514 });
6515
6516 this._cacheId++;
6517 },
6518
6519 /**
6520 * Unregisters the given plugin(s) only if registered.
6521 * @param {Array|Object} plugins plugin instance(s).
6522 */
6523 unregister: function(plugins) {
6524 var p = this._plugins;
6525 ([]).concat(plugins).forEach(function(plugin) {
6526 var idx = p.indexOf(plugin);
6527 if (idx !== -1) {
6528 p.splice(idx, 1);
6529 }
6530 });
6531
6532 this._cacheId++;
6533 },
6534
6535 /**
6536 * Remove all registered plugins.
6537 * @since 2.1.5
6538 */
6539 clear: function() {
6540 this._plugins = [];
6541 this._cacheId++;
6542 },
6543
6544 /**
6545 * Returns the number of registered plugins?
6546 * @returns {Number}
6547 * @since 2.1.5
6548 */
6549 count: function() {
6550 return this._plugins.length;
6551 },
6552
6553 /**
6554 * Returns all registered plugin instances.
6555 * @returns {Array} array of plugin objects.
6556 * @since 2.1.5
6557 */
6558 getAll: function() {
6559 return this._plugins;
6560 },
6561
6562 /**
6563 * Calls enabled plugins for `chart` on the specified hook and with the given args.
6564 * This method immediately returns as soon as a plugin explicitly returns false. The
6565 * returned value can be used, for instance, to interrupt the current action.
6566 * @param {Object} chart - The chart instance for which plugins should be called.
6567 * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
6568 * @param {Array} [args] - Extra arguments to apply to the hook call.
6569 * @returns {Boolean} false if any of the plugins return false, else returns true.
6570 */
6571 notify: function(chart, hook, args) {
6572 var descriptors = this.descriptors(chart);
6573 var ilen = descriptors.length;
6574 var i, descriptor, plugin, params, method;
6575
6576 for (i = 0; i < ilen; ++i) {
6577 descriptor = descriptors[i];
6578 plugin = descriptor.plugin;
6579 method = plugin[hook];
6580 if (typeof method === 'function') {
6581 params = [chart].concat(args || []);
6582 params.push(descriptor.options);
6583 if (method.apply(plugin, params) === false) {
6584 return false;
6585 }
6586 }
6587 }
6588
6589 return true;
6590 },
6591
6592 /**
6593 * Returns descriptors of enabled plugins for the given chart.
6594 * @returns {Array} [{ plugin, options }]
6595 * @private
6596 */
6597 descriptors: function(chart) {
6598 var cache = chart._plugins || (chart._plugins = {});
6599 if (cache.id === this._cacheId) {
6600 return cache.descriptors;
6601 }
6602
6603 var plugins = [];
6604 var descriptors = [];
6605 var config = (chart && chart.config) || {};
6606 var options = (config.options && config.options.plugins) || {};
6607
6608 this._plugins.concat(config.plugins || []).forEach(function(plugin) {
6609 var idx = plugins.indexOf(plugin);
6610 if (idx !== -1) {
6611 return;
6612 }
6613
6614 var id = plugin.id;
6615 var opts = options[id];
6616 if (opts === false) {
6617 return;
6618 }
6619
6620 if (opts === true) {
6621 opts = helpers.clone(defaults.global.plugins[id]);
6622 }
6623
6624 plugins.push(plugin);
6625 descriptors.push({
6626 plugin: plugin,
6627 options: opts || {}
6628 });
6629 });
6630
6631 cache.descriptors = descriptors;
6632 cache.id = this._cacheId;
6633 return descriptors;
6634 }
6635 };
6636
6637 /**
6638 * Plugin extension hooks.
6639 * @interface IPlugin
6640 * @since 2.1.0
6641 */
6642 /**
6643 * @method IPlugin#beforeInit
6644 * @desc Called before initializing `chart`.
6645 * @param {Chart.Controller} chart - The chart instance.
6646 * @param {Object} options - The plugin options.
6647 */
6648 /**
6649 * @method IPlugin#afterInit
6650 * @desc Called after `chart` has been initialized and before the first update.
6651 * @param {Chart.Controller} chart - The chart instance.
6652 * @param {Object} options - The plugin options.
6653 */
6654 /**
6655 * @method IPlugin#beforeUpdate
6656 * @desc Called before updating `chart`. If any plugin returns `false`, the update
6657 * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
6658 * @param {Chart.Controller} chart - The chart instance.
6659 * @param {Object} options - The plugin options.
6660 * @returns {Boolean} `false` to cancel the chart update.
6661 */
6662 /**
6663 * @method IPlugin#afterUpdate
6664 * @desc Called after `chart` has been updated and before rendering. Note that this
6665 * hook will not be called if the chart update has been previously cancelled.
6666 * @param {Chart.Controller} chart - The chart instance.
6667 * @param {Object} options - The plugin options.
6668 */
6669 /**
6670 * @method IPlugin#beforeDatasetsUpdate
6671 * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
6672 * the datasets update is cancelled until another `update` is triggered.
6673 * @param {Chart.Controller} chart - The chart instance.
6674 * @param {Object} options - The plugin options.
6675 * @returns {Boolean} false to cancel the datasets update.
6676 * @since version 2.1.5
6677 */
6678 /**
6679 * @method IPlugin#afterDatasetsUpdate
6680 * @desc Called after the `chart` datasets have been updated. Note that this hook
6681 * will not be called if the datasets update has been previously cancelled.
6682 * @param {Chart.Controller} chart - The chart instance.
6683 * @param {Object} options - The plugin options.
6684 * @since version 2.1.5
6685 */
6686 /**
6687 * @method IPlugin#beforeDatasetUpdate
6688 * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
6689 * returns `false`, the datasets update is cancelled until another `update` is triggered.
6690 * @param {Chart} chart - The chart instance.
6691 * @param {Object} args - The call arguments.
6692 * @param {Number} args.index - The dataset index.
6693 * @param {Object} args.meta - The dataset metadata.
6694 * @param {Object} options - The plugin options.
6695 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6696 */
6697 /**
6698 * @method IPlugin#afterDatasetUpdate
6699 * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
6700 * that this hook will not be called if the datasets update has been previously cancelled.
6701 * @param {Chart} chart - The chart instance.
6702 * @param {Object} args - The call arguments.
6703 * @param {Number} args.index - The dataset index.
6704 * @param {Object} args.meta - The dataset metadata.
6705 * @param {Object} options - The plugin options.
6706 */
6707 /**
6708 * @method IPlugin#beforeLayout
6709 * @desc Called before laying out `chart`. If any plugin returns `false`,
6710 * the layout update is cancelled until another `update` is triggered.
6711 * @param {Chart.Controller} chart - The chart instance.
6712 * @param {Object} options - The plugin options.
6713 * @returns {Boolean} `false` to cancel the chart layout.
6714 */
6715 /**
6716 * @method IPlugin#afterLayout
6717 * @desc Called after the `chart` has been layed out. Note that this hook will not
6718 * be called if the layout update has been previously cancelled.
6719 * @param {Chart.Controller} chart - The chart instance.
6720 * @param {Object} options - The plugin options.
6721 */
6722 /**
6723 * @method IPlugin#beforeRender
6724 * @desc Called before rendering `chart`. If any plugin returns `false`,
6725 * the rendering is cancelled until another `render` is triggered.
6726 * @param {Chart.Controller} chart - The chart instance.
6727 * @param {Object} options - The plugin options.
6728 * @returns {Boolean} `false` to cancel the chart rendering.
6729 */
6730 /**
6731 * @method IPlugin#afterRender
6732 * @desc Called after the `chart` has been fully rendered (and animation completed). Note
6733 * that this hook will not be called if the rendering has been previously cancelled.
6734 * @param {Chart.Controller} chart - The chart instance.
6735 * @param {Object} options - The plugin options.
6736 */
6737 /**
6738 * @method IPlugin#beforeDraw
6739 * @desc Called before drawing `chart` at every animation frame specified by the given
6740 * easing value. If any plugin returns `false`, the frame drawing is cancelled until
6741 * another `render` is triggered.
6742 * @param {Chart.Controller} chart - The chart instance.
6743 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6744 * @param {Object} options - The plugin options.
6745 * @returns {Boolean} `false` to cancel the chart drawing.
6746 */
6747 /**
6748 * @method IPlugin#afterDraw
6749 * @desc Called after the `chart` has been drawn for the specific easing value. Note
6750 * that this hook will not be called if the drawing has been previously cancelled.
6751 * @param {Chart.Controller} chart - The chart instance.
6752 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6753 * @param {Object} options - The plugin options.
6754 */
6755 /**
6756 * @method IPlugin#beforeDatasetsDraw
6757 * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
6758 * the datasets drawing is cancelled until another `render` is triggered.
6759 * @param {Chart.Controller} chart - The chart instance.
6760 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6761 * @param {Object} options - The plugin options.
6762 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6763 */
6764 /**
6765 * @method IPlugin#afterDatasetsDraw
6766 * @desc Called after the `chart` datasets have been drawn. Note that this hook
6767 * will not be called if the datasets drawing has been previously cancelled.
6768 * @param {Chart.Controller} chart - The chart instance.
6769 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6770 * @param {Object} options - The plugin options.
6771 */
6772 /**
6773 * @method IPlugin#beforeDatasetDraw
6774 * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
6775 * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
6776 * is cancelled until another `render` is triggered.
6777 * @param {Chart} chart - The chart instance.
6778 * @param {Object} args - The call arguments.
6779 * @param {Number} args.index - The dataset index.
6780 * @param {Object} args.meta - The dataset metadata.
6781 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6782 * @param {Object} options - The plugin options.
6783 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6784 */
6785 /**
6786 * @method IPlugin#afterDatasetDraw
6787 * @desc Called after the `chart` datasets at the given `args.index` have been drawn
6788 * (datasets are drawn in the reverse order). Note that this hook will not be called
6789 * if the datasets drawing has been previously cancelled.
6790 * @param {Chart} chart - The chart instance.
6791 * @param {Object} args - The call arguments.
6792 * @param {Number} args.index - The dataset index.
6793 * @param {Object} args.meta - The dataset metadata.
6794 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6795 * @param {Object} options - The plugin options.
6796 */
6797 /**
6798 * @method IPlugin#beforeTooltipDraw
6799 * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
6800 * the tooltip drawing is cancelled until another `render` is triggered.
6801 * @param {Chart} chart - The chart instance.
6802 * @param {Object} args - The call arguments.
6803 * @param {Object} args.tooltip - The tooltip.
6804 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6805 * @param {Object} options - The plugin options.
6806 * @returns {Boolean} `false` to cancel the chart tooltip drawing.
6807 */
6808 /**
6809 * @method IPlugin#afterTooltipDraw
6810 * @desc Called after drawing the `tooltip`. Note that this hook will not
6811 * be called if the tooltip drawing has been previously cancelled.
6812 * @param {Chart} chart - The chart instance.
6813 * @param {Object} args - The call arguments.
6814 * @param {Object} args.tooltip - The tooltip.
6815 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6816 * @param {Object} options - The plugin options.
6817 */
6818 /**
6819 * @method IPlugin#beforeEvent
6820 * @desc Called before processing the specified `event`. If any plugin returns `false`,
6821 * the event will be discarded.
6822 * @param {Chart.Controller} chart - The chart instance.
6823 * @param {IEvent} event - The event object.
6824 * @param {Object} options - The plugin options.
6825 */
6826 /**
6827 * @method IPlugin#afterEvent
6828 * @desc Called after the `event` has been consumed. Note that this hook
6829 * will not be called if the `event` has been previously discarded.
6830 * @param {Chart.Controller} chart - The chart instance.
6831 * @param {IEvent} event - The event object.
6832 * @param {Object} options - The plugin options.
6833 */
6834 /**
6835 * @method IPlugin#resize
6836 * @desc Called after the chart as been resized.
6837 * @param {Chart.Controller} chart - The chart instance.
6838 * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
6839 * @param {Object} options - The plugin options.
6840 */
6841 /**
6842 * @method IPlugin#destroy
6843 * @desc Called after the chart as been destroyed.
6844 * @param {Chart.Controller} chart - The chart instance.
6845 * @param {Object} options - The plugin options.
6846 */
6847
6848 /**
6849 * Provided for backward compatibility, use Chart.plugins instead
6850 * @namespace Chart.pluginService
6851 * @deprecated since version 2.1.5
6852 * @todo remove at version 3
6853 * @private
6854 */
6855 Chart.pluginService = Chart.plugins;
6856
6857 /**
6858 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
6859 * effect, instead simply create/register plugins via plain JavaScript objects.
6860 * @interface Chart.PluginBase
6861 * @deprecated since version 2.5.0
6862 * @todo remove at version 3
6863 * @private
6864 */
6865 Chart.PluginBase = Element.extend({});
6866 };
6867
6868 },{"25":25,"26":26,"45":45}],32:[function(require,module,exports){
6869 'use strict';
6870
6871 var defaults = require(25);
6872 var Element = require(26);
6873 var helpers = require(45);
6874 var Ticks = require(34);
6875
6876 defaults._set('scale', {
6877 display: true,
6878 position: 'left',
6879 offset: false,
6880
6881 // grid line settings
6882 gridLines: {
6883 display: true,
6884 color: 'rgba(0, 0, 0, 0.1)',
6885 lineWidth: 1,
6886 drawBorder: true,
6887 drawOnChartArea: true,
6888 drawTicks: true,
6889 tickMarkLength: 10,
6890 zeroLineWidth: 1,
6891 zeroLineColor: 'rgba(0,0,0,0.25)',
6892 zeroLineBorderDash: [],
6893 zeroLineBorderDashOffset: 0.0,
6894 offsetGridLines: false,
6895 borderDash: [],
6896 borderDashOffset: 0.0
6897 },
6898
6899 // scale label
6900 scaleLabel: {
6901 // display property
6902 display: false,
6903
6904 // actual label
6905 labelString: '',
6906
6907 // line height
6908 lineHeight: 1.2,
6909
6910 // top/bottom padding
6911 padding: {
6912 top: 4,
6913 bottom: 4
6914 }
6915 },
6916
6917 // label settings
6918 ticks: {
6919 beginAtZero: false,
6920 minRotation: 0,
6921 maxRotation: 50,
6922 mirror: false,
6923 padding: 0,
6924 reverse: false,
6925 display: true,
6926 autoSkip: true,
6927 autoSkipPadding: 0,
6928 labelOffset: 0,
6929 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
6930 callback: Ticks.formatters.values,
6931 minor: {},
6932 major: {}
6933 }
6934 });
6935
6936 function labelsFromTicks(ticks) {
6937 var labels = [];
6938 var i, ilen;
6939
6940 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
6941 labels.push(ticks[i].label);
6942 }
6943
6944 return labels;
6945 }
6946
6947 function getLineValue(scale, index, offsetGridLines) {
6948 var lineValue = scale.getPixelForTick(index);
6949
6950 if (offsetGridLines) {
6951 if (index === 0) {
6952 lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
6953 } else {
6954 lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
6955 }
6956 }
6957 return lineValue;
6958 }
6959
6960 module.exports = function(Chart) {
6961
6962 function computeTextSize(context, tick, font) {
6963 return helpers.isArray(tick) ?
6964 helpers.longestText(context, font, tick) :
6965 context.measureText(tick).width;
6966 }
6967
6968 function parseFontOptions(options) {
6969 var valueOrDefault = helpers.valueOrDefault;
6970 var globalDefaults = defaults.global;
6971 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
6972 var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
6973 var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
6974
6975 return {
6976 size: size,
6977 style: style,
6978 family: family,
6979 font: helpers.fontString(size, style, family)
6980 };
6981 }
6982
6983 function parseLineHeight(options) {
6984 return helpers.options.toLineHeight(
6985 helpers.valueOrDefault(options.lineHeight, 1.2),
6986 helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize));
6987 }
6988
6989 Chart.Scale = Element.extend({
6990 /**
6991 * Get the padding needed for the scale
6992 * @method getPadding
6993 * @private
6994 * @returns {Padding} the necessary padding
6995 */
6996 getPadding: function() {
6997 var me = this;
6998 return {
6999 left: me.paddingLeft || 0,
7000 top: me.paddingTop || 0,
7001 right: me.paddingRight || 0,
7002 bottom: me.paddingBottom || 0
7003 };
7004 },
7005
7006 /**
7007 * Returns the scale tick objects ({label, major})
7008 * @since 2.7
7009 */
7010 getTicks: function() {
7011 return this._ticks;
7012 },
7013
7014 // These methods are ordered by lifecyle. Utilities then follow.
7015 // Any function defined here is inherited by all scale types.
7016 // Any function can be extended by the scale type
7017
7018 mergeTicksOptions: function() {
7019 var ticks = this.options.ticks;
7020 if (ticks.minor === false) {
7021 ticks.minor = {
7022 display: false
7023 };
7024 }
7025 if (ticks.major === false) {
7026 ticks.major = {
7027 display: false
7028 };
7029 }
7030 for (var key in ticks) {
7031 if (key !== 'major' && key !== 'minor') {
7032 if (typeof ticks.minor[key] === 'undefined') {
7033 ticks.minor[key] = ticks[key];
7034 }
7035 if (typeof ticks.major[key] === 'undefined') {
7036 ticks.major[key] = ticks[key];
7037 }
7038 }
7039 }
7040 },
7041 beforeUpdate: function() {
7042 helpers.callback(this.options.beforeUpdate, [this]);
7043 },
7044 update: function(maxWidth, maxHeight, margins) {
7045 var me = this;
7046 var i, ilen, labels, label, ticks, tick;
7047
7048 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
7049 me.beforeUpdate();
7050
7051 // Absorb the master measurements
7052 me.maxWidth = maxWidth;
7053 me.maxHeight = maxHeight;
7054 me.margins = helpers.extend({
7055 left: 0,
7056 right: 0,
7057 top: 0,
7058 bottom: 0
7059 }, margins);
7060 me.longestTextCache = me.longestTextCache || {};
7061
7062 // Dimensions
7063 me.beforeSetDimensions();
7064 me.setDimensions();
7065 me.afterSetDimensions();
7066
7067 // Data min/max
7068 me.beforeDataLimits();
7069 me.determineDataLimits();
7070 me.afterDataLimits();
7071
7072 // Ticks - `this.ticks` is now DEPRECATED!
7073 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
7074 // and must not be accessed directly from outside this class. `this.ticks` being
7075 // around for long time and not marked as private, we can't change its structure
7076 // without unexpected breaking changes. If you need to access the scale ticks,
7077 // use scale.getTicks() instead.
7078
7079 me.beforeBuildTicks();
7080
7081 // New implementations should return an array of objects but for BACKWARD COMPAT,
7082 // we still support no return (`this.ticks` internally set by calling this method).
7083 ticks = me.buildTicks() || [];
7084
7085 me.afterBuildTicks();
7086
7087 me.beforeTickToLabelConversion();
7088
7089 // New implementations should return the formatted tick labels but for BACKWARD
7090 // COMPAT, we still support no return (`this.ticks` internally changed by calling
7091 // this method and supposed to contain only string values).
7092 labels = me.convertTicksToLabels(ticks) || me.ticks;
7093
7094 me.afterTickToLabelConversion();
7095
7096 me.ticks = labels; // BACKWARD COMPATIBILITY
7097
7098 // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
7099
7100 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
7101 for (i = 0, ilen = labels.length; i < ilen; ++i) {
7102 label = labels[i];
7103 tick = ticks[i];
7104 if (!tick) {
7105 ticks.push(tick = {
7106 label: label,
7107 major: false
7108 });
7109 } else {
7110 tick.label = label;
7111 }
7112 }
7113
7114 me._ticks = ticks;
7115
7116 // Tick Rotation
7117 me.beforeCalculateTickRotation();
7118 me.calculateTickRotation();
7119 me.afterCalculateTickRotation();
7120 // Fit
7121 me.beforeFit();
7122 me.fit();
7123 me.afterFit();
7124 //
7125 me.afterUpdate();
7126
7127 return me.minSize;
7128
7129 },
7130 afterUpdate: function() {
7131 helpers.callback(this.options.afterUpdate, [this]);
7132 },
7133
7134 //
7135
7136 beforeSetDimensions: function() {
7137 helpers.callback(this.options.beforeSetDimensions, [this]);
7138 },
7139 setDimensions: function() {
7140 var me = this;
7141 // Set the unconstrained dimension before label rotation
7142 if (me.isHorizontal()) {
7143 // Reset position before calculating rotation
7144 me.width = me.maxWidth;
7145 me.left = 0;
7146 me.right = me.width;
7147 } else {
7148 me.height = me.maxHeight;
7149
7150 // Reset position before calculating rotation
7151 me.top = 0;
7152 me.bottom = me.height;
7153 }
7154
7155 // Reset padding
7156 me.paddingLeft = 0;
7157 me.paddingTop = 0;
7158 me.paddingRight = 0;
7159 me.paddingBottom = 0;
7160 },
7161 afterSetDimensions: function() {
7162 helpers.callback(this.options.afterSetDimensions, [this]);
7163 },
7164
7165 // Data limits
7166 beforeDataLimits: function() {
7167 helpers.callback(this.options.beforeDataLimits, [this]);
7168 },
7169 determineDataLimits: helpers.noop,
7170 afterDataLimits: function() {
7171 helpers.callback(this.options.afterDataLimits, [this]);
7172 },
7173
7174 //
7175 beforeBuildTicks: function() {
7176 helpers.callback(this.options.beforeBuildTicks, [this]);
7177 },
7178 buildTicks: helpers.noop,
7179 afterBuildTicks: function() {
7180 helpers.callback(this.options.afterBuildTicks, [this]);
7181 },
7182
7183 beforeTickToLabelConversion: function() {
7184 helpers.callback(this.options.beforeTickToLabelConversion, [this]);
7185 },
7186 convertTicksToLabels: function() {
7187 var me = this;
7188 // Convert ticks to strings
7189 var tickOpts = me.options.ticks;
7190 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
7191 },
7192 afterTickToLabelConversion: function() {
7193 helpers.callback(this.options.afterTickToLabelConversion, [this]);
7194 },
7195
7196 //
7197
7198 beforeCalculateTickRotation: function() {
7199 helpers.callback(this.options.beforeCalculateTickRotation, [this]);
7200 },
7201 calculateTickRotation: function() {
7202 var me = this;
7203 var context = me.ctx;
7204 var tickOpts = me.options.ticks;
7205 var labels = labelsFromTicks(me._ticks);
7206
7207 // Get the width of each grid by calculating the difference
7208 // between x offsets between 0 and 1.
7209 var tickFont = parseFontOptions(tickOpts);
7210 context.font = tickFont.font;
7211
7212 var labelRotation = tickOpts.minRotation || 0;
7213
7214 if (labels.length && me.options.display && me.isHorizontal()) {
7215 var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache);
7216 var labelWidth = originalLabelWidth;
7217 var cosRotation, sinRotation;
7218
7219 // Allow 3 pixels x2 padding either side for label readability
7220 var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
7221
7222 // Max label rotation can be set or default to 90 - also act as a loop counter
7223 while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
7224 var angleRadians = helpers.toRadians(labelRotation);
7225 cosRotation = Math.cos(angleRadians);
7226 sinRotation = Math.sin(angleRadians);
7227
7228 if (sinRotation * originalLabelWidth > me.maxHeight) {
7229 // go back one step
7230 labelRotation--;
7231 break;
7232 }
7233
7234 labelRotation++;
7235 labelWidth = cosRotation * originalLabelWidth;
7236 }
7237 }
7238
7239 me.labelRotation = labelRotation;
7240 },
7241 afterCalculateTickRotation: function() {
7242 helpers.callback(this.options.afterCalculateTickRotation, [this]);
7243 },
7244
7245 //
7246
7247 beforeFit: function() {
7248 helpers.callback(this.options.beforeFit, [this]);
7249 },
7250 fit: function() {
7251 var me = this;
7252 // Reset
7253 var minSize = me.minSize = {
7254 width: 0,
7255 height: 0
7256 };
7257
7258 var labels = labelsFromTicks(me._ticks);
7259
7260 var opts = me.options;
7261 var tickOpts = opts.ticks;
7262 var scaleLabelOpts = opts.scaleLabel;
7263 var gridLineOpts = opts.gridLines;
7264 var display = opts.display;
7265 var isHorizontal = me.isHorizontal();
7266
7267 var tickFont = parseFontOptions(tickOpts);
7268 var tickMarkLength = opts.gridLines.tickMarkLength;
7269
7270 // Width
7271 if (isHorizontal) {
7272 // subtract the margins to line up with the chartArea if we are a full width scale
7273 minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
7274 } else {
7275 minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7276 }
7277
7278 // height
7279 if (isHorizontal) {
7280 minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7281 } else {
7282 minSize.height = me.maxHeight; // fill all the height
7283 }
7284
7285 // Are we showing a title for the scale?
7286 if (scaleLabelOpts.display && display) {
7287 var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts);
7288 var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding);
7289 var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height;
7290
7291 if (isHorizontal) {
7292 minSize.height += deltaHeight;
7293 } else {
7294 minSize.width += deltaHeight;
7295 }
7296 }
7297
7298 // Don't bother fitting the ticks if we are not showing them
7299 if (tickOpts.display && display) {
7300 var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache);
7301 var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels);
7302 var lineSpace = tickFont.size * 0.5;
7303 var tickPadding = me.options.ticks.padding;
7304
7305 if (isHorizontal) {
7306 // A horizontal axis is more constrained by the height.
7307 me.longestLabelWidth = largestTextWidth;
7308
7309 var angleRadians = helpers.toRadians(me.labelRotation);
7310 var cosRotation = Math.cos(angleRadians);
7311 var sinRotation = Math.sin(angleRadians);
7312
7313 // TODO - improve this calculation
7314 var labelHeight = (sinRotation * largestTextWidth)
7315 + (tickFont.size * tallestLabelHeightInLines)
7316 + (lineSpace * (tallestLabelHeightInLines - 1))
7317 + lineSpace; // padding
7318
7319 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
7320
7321 me.ctx.font = tickFont.font;
7322 var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font);
7323 var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font);
7324
7325 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
7326 // which means that the right padding is dominated by the font height
7327 if (me.labelRotation !== 0) {
7328 me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges
7329 me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3;
7330 } else {
7331 me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
7332 me.paddingRight = lastLabelWidth / 2 + 3;
7333 }
7334 } else {
7335 // A vertical axis is more constrained by the width. Labels are the
7336 // dominant factor here, so get that length first and account for padding
7337 if (tickOpts.mirror) {
7338 largestTextWidth = 0;
7339 } else {
7340 // use lineSpace for consistency with horizontal axis
7341 // tickPadding is not implemented for horizontal
7342 largestTextWidth += tickPadding + lineSpace;
7343 }
7344
7345 minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
7346
7347 me.paddingTop = tickFont.size / 2;
7348 me.paddingBottom = tickFont.size / 2;
7349 }
7350 }
7351
7352 me.handleMargins();
7353
7354 me.width = minSize.width;
7355 me.height = minSize.height;
7356 },
7357
7358 /**
7359 * Handle margins and padding interactions
7360 * @private
7361 */
7362 handleMargins: function() {
7363 var me = this;
7364 if (me.margins) {
7365 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7366 me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
7367 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7368 me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
7369 }
7370 },
7371
7372 afterFit: function() {
7373 helpers.callback(this.options.afterFit, [this]);
7374 },
7375
7376 // Shared Methods
7377 isHorizontal: function() {
7378 return this.options.position === 'top' || this.options.position === 'bottom';
7379 },
7380 isFullWidth: function() {
7381 return (this.options.fullWidth);
7382 },
7383
7384 // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
7385 getRightValue: function(rawValue) {
7386 // Null and undefined values first
7387 if (helpers.isNullOrUndef(rawValue)) {
7388 return NaN;
7389 }
7390 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
7391 if (typeof rawValue === 'number' && !isFinite(rawValue)) {
7392 return NaN;
7393 }
7394 // If it is in fact an object, dive in one more level
7395 if (rawValue) {
7396 if (this.isHorizontal()) {
7397 if (rawValue.x !== undefined) {
7398 return this.getRightValue(rawValue.x);
7399 }
7400 } else if (rawValue.y !== undefined) {
7401 return this.getRightValue(rawValue.y);
7402 }
7403 }
7404
7405 // Value is good, return it
7406 return rawValue;
7407 },
7408
7409 /**
7410 * Used to get the value to display in the tooltip for the data at the given index
7411 * @param index
7412 * @param datasetIndex
7413 */
7414 getLabelForIndex: helpers.noop,
7415
7416 /**
7417 * Returns the location of the given data point. Value can either be an index or a numerical value
7418 * The coordinate (0, 0) is at the upper-left corner of the canvas
7419 * @param value
7420 * @param index
7421 * @param datasetIndex
7422 */
7423 getPixelForValue: helpers.noop,
7424
7425 /**
7426 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
7427 * The coordinate (0, 0) is at the upper-left corner of the canvas
7428 * @param pixel
7429 */
7430 getValueForPixel: helpers.noop,
7431
7432 /**
7433 * Returns the location of the tick at the given index
7434 * The coordinate (0, 0) is at the upper-left corner of the canvas
7435 */
7436 getPixelForTick: function(index) {
7437 var me = this;
7438 var offset = me.options.offset;
7439 if (me.isHorizontal()) {
7440 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7441 var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
7442 var pixel = (tickWidth * index) + me.paddingLeft;
7443
7444 if (offset) {
7445 pixel += tickWidth / 2;
7446 }
7447
7448 var finalVal = me.left + Math.round(pixel);
7449 finalVal += me.isFullWidth() ? me.margins.left : 0;
7450 return finalVal;
7451 }
7452 var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
7453 return me.top + (index * (innerHeight / (me._ticks.length - 1)));
7454 },
7455
7456 /**
7457 * Utility for getting the pixel location of a percentage of scale
7458 * The coordinate (0, 0) is at the upper-left corner of the canvas
7459 */
7460 getPixelForDecimal: function(decimal) {
7461 var me = this;
7462 if (me.isHorizontal()) {
7463 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7464 var valueOffset = (innerWidth * decimal) + me.paddingLeft;
7465
7466 var finalVal = me.left + Math.round(valueOffset);
7467 finalVal += me.isFullWidth() ? me.margins.left : 0;
7468 return finalVal;
7469 }
7470 return me.top + (decimal * me.height);
7471 },
7472
7473 /**
7474 * Returns the pixel for the minimum chart value
7475 * The coordinate (0, 0) is at the upper-left corner of the canvas
7476 */
7477 getBasePixel: function() {
7478 return this.getPixelForValue(this.getBaseValue());
7479 },
7480
7481 getBaseValue: function() {
7482 var me = this;
7483 var min = me.min;
7484 var max = me.max;
7485
7486 return me.beginAtZero ? 0 :
7487 min < 0 && max < 0 ? max :
7488 min > 0 && max > 0 ? min :
7489 0;
7490 },
7491
7492 /**
7493 * Returns a subset of ticks to be plotted to avoid overlapping labels.
7494 * @private
7495 */
7496 _autoSkip: function(ticks) {
7497 var skipRatio;
7498 var me = this;
7499 var isHorizontal = me.isHorizontal();
7500 var optionTicks = me.options.ticks.minor;
7501 var tickCount = ticks.length;
7502 var labelRotationRadians = helpers.toRadians(me.labelRotation);
7503 var cosRotation = Math.cos(labelRotationRadians);
7504 var longestRotatedLabel = me.longestLabelWidth * cosRotation;
7505 var result = [];
7506 var i, tick, shouldSkip;
7507
7508 // figure out the maximum number of gridlines to show
7509 var maxTicks;
7510 if (optionTicks.maxTicksLimit) {
7511 maxTicks = optionTicks.maxTicksLimit;
7512 }
7513
7514 if (isHorizontal) {
7515 skipRatio = false;
7516
7517 if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) {
7518 skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight)));
7519 }
7520
7521 // if they defined a max number of optionTicks,
7522 // increase skipRatio until that number is met
7523 if (maxTicks && tickCount > maxTicks) {
7524 skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks));
7525 }
7526 }
7527
7528 for (i = 0; i < tickCount; i++) {
7529 tick = ticks[i];
7530
7531 // Since we always show the last tick,we need may need to hide the last shown one before
7532 shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount);
7533 if (shouldSkip && i !== tickCount - 1) {
7534 // leave tick in place but make sure it's not displayed (#4635)
7535 delete tick.label;
7536 }
7537 result.push(tick);
7538 }
7539 return result;
7540 },
7541
7542 // Actually draw the scale on the canvas
7543 // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
7544 draw: function(chartArea) {
7545 var me = this;
7546 var options = me.options;
7547 if (!options.display) {
7548 return;
7549 }
7550
7551 var context = me.ctx;
7552 var globalDefaults = defaults.global;
7553 var optionTicks = options.ticks.minor;
7554 var optionMajorTicks = options.ticks.major || optionTicks;
7555 var gridLines = options.gridLines;
7556 var scaleLabel = options.scaleLabel;
7557
7558 var isRotated = me.labelRotation !== 0;
7559 var isHorizontal = me.isHorizontal();
7560
7561 var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
7562 var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
7563 var tickFont = parseFontOptions(optionTicks);
7564 var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
7565 var majorTickFont = parseFontOptions(optionMajorTicks);
7566
7567 var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
7568
7569 var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
7570 var scaleLabelFont = parseFontOptions(scaleLabel);
7571 var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
7572 var labelRotationRadians = helpers.toRadians(me.labelRotation);
7573
7574 var itemsToDraw = [];
7575
7576 var xTickStart = options.position === 'right' ? me.left : me.right - tl;
7577 var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
7578 var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
7579 var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
7580
7581 helpers.each(ticks, function(tick, index) {
7582 // autoskipper skipped this tick (#4635)
7583 if (helpers.isNullOrUndef(tick.label)) {
7584 return;
7585 }
7586
7587 var label = tick.label;
7588 var lineWidth, lineColor, borderDash, borderDashOffset;
7589 if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
7590 // Draw the first index specially
7591 lineWidth = gridLines.zeroLineWidth;
7592 lineColor = gridLines.zeroLineColor;
7593 borderDash = gridLines.zeroLineBorderDash;
7594 borderDashOffset = gridLines.zeroLineBorderDashOffset;
7595 } else {
7596 lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index);
7597 lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index);
7598 borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
7599 borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
7600 }
7601
7602 // Common properties
7603 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
7604 var textAlign = 'middle';
7605 var textBaseline = 'middle';
7606 var tickPadding = optionTicks.padding;
7607
7608 if (isHorizontal) {
7609 var labelYOffset = tl + tickPadding;
7610
7611 if (options.position === 'bottom') {
7612 // bottom
7613 textBaseline = !isRotated ? 'top' : 'middle';
7614 textAlign = !isRotated ? 'center' : 'right';
7615 labelY = me.top + labelYOffset;
7616 } else {
7617 // top
7618 textBaseline = !isRotated ? 'bottom' : 'middle';
7619 textAlign = !isRotated ? 'center' : 'left';
7620 labelY = me.bottom - labelYOffset;
7621 }
7622
7623 var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7624 if (xLineValue < me.left) {
7625 lineColor = 'rgba(0,0,0,0)';
7626 }
7627 xLineValue += helpers.aliasPixel(lineWidth);
7628
7629 labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
7630
7631 tx1 = tx2 = x1 = x2 = xLineValue;
7632 ty1 = yTickStart;
7633 ty2 = yTickEnd;
7634 y1 = chartArea.top;
7635 y2 = chartArea.bottom;
7636 } else {
7637 var isLeft = options.position === 'left';
7638 var labelXOffset;
7639
7640 if (optionTicks.mirror) {
7641 textAlign = isLeft ? 'left' : 'right';
7642 labelXOffset = tickPadding;
7643 } else {
7644 textAlign = isLeft ? 'right' : 'left';
7645 labelXOffset = tl + tickPadding;
7646 }
7647
7648 labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
7649
7650 var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7651 if (yLineValue < me.top) {
7652 lineColor = 'rgba(0,0,0,0)';
7653 }
7654 yLineValue += helpers.aliasPixel(lineWidth);
7655
7656 labelY = me.getPixelForTick(index) + optionTicks.labelOffset;
7657
7658 tx1 = xTickStart;
7659 tx2 = xTickEnd;
7660 x1 = chartArea.left;
7661 x2 = chartArea.right;
7662 ty1 = ty2 = y1 = y2 = yLineValue;
7663 }
7664
7665 itemsToDraw.push({
7666 tx1: tx1,
7667 ty1: ty1,
7668 tx2: tx2,
7669 ty2: ty2,
7670 x1: x1,
7671 y1: y1,
7672 x2: x2,
7673 y2: y2,
7674 labelX: labelX,
7675 labelY: labelY,
7676 glWidth: lineWidth,
7677 glColor: lineColor,
7678 glBorderDash: borderDash,
7679 glBorderDashOffset: borderDashOffset,
7680 rotation: -1 * labelRotationRadians,
7681 label: label,
7682 major: tick.major,
7683 textBaseline: textBaseline,
7684 textAlign: textAlign
7685 });
7686 });
7687
7688 // Draw all of the tick labels, tick marks, and grid lines at the correct places
7689 helpers.each(itemsToDraw, function(itemToDraw) {
7690 if (gridLines.display) {
7691 context.save();
7692 context.lineWidth = itemToDraw.glWidth;
7693 context.strokeStyle = itemToDraw.glColor;
7694 if (context.setLineDash) {
7695 context.setLineDash(itemToDraw.glBorderDash);
7696 context.lineDashOffset = itemToDraw.glBorderDashOffset;
7697 }
7698
7699 context.beginPath();
7700
7701 if (gridLines.drawTicks) {
7702 context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
7703 context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
7704 }
7705
7706 if (gridLines.drawOnChartArea) {
7707 context.moveTo(itemToDraw.x1, itemToDraw.y1);
7708 context.lineTo(itemToDraw.x2, itemToDraw.y2);
7709 }
7710
7711 context.stroke();
7712 context.restore();
7713 }
7714
7715 if (optionTicks.display) {
7716 // Make sure we draw text in the correct color and font
7717 context.save();
7718 context.translate(itemToDraw.labelX, itemToDraw.labelY);
7719 context.rotate(itemToDraw.rotation);
7720 context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
7721 context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
7722 context.textBaseline = itemToDraw.textBaseline;
7723 context.textAlign = itemToDraw.textAlign;
7724
7725 var label = itemToDraw.label;
7726 if (helpers.isArray(label)) {
7727 for (var i = 0, y = 0; i < label.length; ++i) {
7728 // We just make sure the multiline element is a string here..
7729 context.fillText('' + label[i], 0, y);
7730 // apply same lineSpacing as calculated @ L#320
7731 y += (tickFont.size * 1.5);
7732 }
7733 } else {
7734 context.fillText(label, 0, 0);
7735 }
7736 context.restore();
7737 }
7738 });
7739
7740 if (scaleLabel.display) {
7741 // Draw the scale label
7742 var scaleLabelX;
7743 var scaleLabelY;
7744 var rotation = 0;
7745 var halfLineHeight = parseLineHeight(scaleLabel) / 2;
7746
7747 if (isHorizontal) {
7748 scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
7749 scaleLabelY = options.position === 'bottom'
7750 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
7751 : me.top + halfLineHeight + scaleLabelPadding.top;
7752 } else {
7753 var isLeft = options.position === 'left';
7754 scaleLabelX = isLeft
7755 ? me.left + halfLineHeight + scaleLabelPadding.top
7756 : me.right - halfLineHeight - scaleLabelPadding.top;
7757 scaleLabelY = me.top + ((me.bottom - me.top) / 2);
7758 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
7759 }
7760
7761 context.save();
7762 context.translate(scaleLabelX, scaleLabelY);
7763 context.rotate(rotation);
7764 context.textAlign = 'center';
7765 context.textBaseline = 'middle';
7766 context.fillStyle = scaleLabelFontColor; // render in correct colour
7767 context.font = scaleLabelFont.font;
7768 context.fillText(scaleLabel.labelString, 0, 0);
7769 context.restore();
7770 }
7771
7772 if (gridLines.drawBorder) {
7773 // Draw the line at the edge of the axis
7774 context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0);
7775 context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0);
7776 var x1 = me.left;
7777 var x2 = me.right;
7778 var y1 = me.top;
7779 var y2 = me.bottom;
7780
7781 var aliasPixel = helpers.aliasPixel(context.lineWidth);
7782 if (isHorizontal) {
7783 y1 = y2 = options.position === 'top' ? me.bottom : me.top;
7784 y1 += aliasPixel;
7785 y2 += aliasPixel;
7786 } else {
7787 x1 = x2 = options.position === 'left' ? me.right : me.left;
7788 x1 += aliasPixel;
7789 x2 += aliasPixel;
7790 }
7791
7792 context.beginPath();
7793 context.moveTo(x1, y1);
7794 context.lineTo(x2, y2);
7795 context.stroke();
7796 }
7797 }
7798 });
7799 };
7800
7801 },{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){
7802 'use strict';
7803
7804 var defaults = require(25);
7805 var helpers = require(45);
7806
7807 module.exports = function(Chart) {
7808
7809 Chart.scaleService = {
7810 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
7811 // use the new chart options to grab the correct scale
7812 constructors: {},
7813 // Use a registration function so that we can move to an ES6 map when we no longer need to support
7814 // old browsers
7815
7816 // Scale config defaults
7817 defaults: {},
7818 registerScaleType: function(type, scaleConstructor, scaleDefaults) {
7819 this.constructors[type] = scaleConstructor;
7820 this.defaults[type] = helpers.clone(scaleDefaults);
7821 },
7822 getScaleConstructor: function(type) {
7823 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
7824 },
7825 getScaleDefaults: function(type) {
7826 // Return the scale defaults merged with the global settings so that we always use the latest ones
7827 return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {};
7828 },
7829 updateScaleDefaults: function(type, additions) {
7830 var me = this;
7831 if (me.defaults.hasOwnProperty(type)) {
7832 me.defaults[type] = helpers.extend(me.defaults[type], additions);
7833 }
7834 },
7835 addScalesToLayout: function(chart) {
7836 // Adds each scale to the chart.boxes array to be sized accordingly
7837 helpers.each(chart.scales, function(scale) {
7838 // Set ILayoutItem parameters for backwards compatibility
7839 scale.fullWidth = scale.options.fullWidth;
7840 scale.position = scale.options.position;
7841 scale.weight = scale.options.weight;
7842 Chart.layoutService.addBox(chart, scale);
7843 });
7844 }
7845 };
7846 };
7847
7848 },{"25":25,"45":45}],34:[function(require,module,exports){
7849 'use strict';
7850
7851 var helpers = require(45);
7852
7853 /**
7854 * Namespace to hold static tick generation functions
7855 * @namespace Chart.Ticks
7856 */
7857 module.exports = {
7858 /**
7859 * Namespace to hold generators for different types of ticks
7860 * @namespace Chart.Ticks.generators
7861 */
7862 generators: {
7863 /**
7864 * Interface for the options provided to the numeric tick generator
7865 * @interface INumericTickGenerationOptions
7866 */
7867 /**
7868 * The maximum number of ticks to display
7869 * @name INumericTickGenerationOptions#maxTicks
7870 * @type Number
7871 */
7872 /**
7873 * The distance between each tick.
7874 * @name INumericTickGenerationOptions#stepSize
7875 * @type Number
7876 * @optional
7877 */
7878 /**
7879 * Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum
7880 * @name INumericTickGenerationOptions#min
7881 * @type Number
7882 * @optional
7883 */
7884 /**
7885 * The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum
7886 * @name INumericTickGenerationOptions#max
7887 * @type Number
7888 * @optional
7889 */
7890
7891 /**
7892 * Generate a set of linear ticks
7893 * @method Chart.Ticks.generators.linear
7894 * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7895 * @param dataRange {IRange} the range of the data
7896 * @returns {Array<Number>} array of tick values
7897 */
7898 linear: function(generationOptions, dataRange) {
7899 var ticks = [];
7900 // To get a "nice" value for the tick spacing, we will use the appropriately named
7901 // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
7902 // for details.
7903
7904 var spacing;
7905 if (generationOptions.stepSize && generationOptions.stepSize > 0) {
7906 spacing = generationOptions.stepSize;
7907 } else {
7908 var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
7909 spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
7910 }
7911 var niceMin = Math.floor(dataRange.min / spacing) * spacing;
7912 var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
7913
7914 // If min, max and stepSize is set and they make an evenly spaced scale use it.
7915 if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
7916 // If very close to our whole number, use it.
7917 if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
7918 niceMin = generationOptions.min;
7919 niceMax = generationOptions.max;
7920 }
7921 }
7922
7923 var numSpaces = (niceMax - niceMin) / spacing;
7924 // If very close to our rounded value, use it.
7925 if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
7926 numSpaces = Math.round(numSpaces);
7927 } else {
7928 numSpaces = Math.ceil(numSpaces);
7929 }
7930
7931 // Put the values into the ticks array
7932 ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
7933 for (var j = 1; j < numSpaces; ++j) {
7934 ticks.push(niceMin + (j * spacing));
7935 }
7936 ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
7937
7938 return ticks;
7939 },
7940
7941 /**
7942 * Generate a set of logarithmic ticks
7943 * @method Chart.Ticks.generators.logarithmic
7944 * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7945 * @param dataRange {IRange} the range of the data
7946 * @returns {Array<Number>} array of tick values
7947 */
7948 logarithmic: function(generationOptions, dataRange) {
7949 var ticks = [];
7950 var valueOrDefault = helpers.valueOrDefault;
7951
7952 // Figure out what the max number of ticks we can support it is based on the size of
7953 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
7954 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
7955 // the graph
7956 var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
7957
7958 var endExp = Math.floor(helpers.log10(dataRange.max));
7959 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
7960 var exp, significand;
7961
7962 if (tickVal === 0) {
7963 exp = Math.floor(helpers.log10(dataRange.minNotZero));
7964 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
7965
7966 ticks.push(tickVal);
7967 tickVal = significand * Math.pow(10, exp);
7968 } else {
7969 exp = Math.floor(helpers.log10(tickVal));
7970 significand = Math.floor(tickVal / Math.pow(10, exp));
7971 }
7972
7973 do {
7974 ticks.push(tickVal);
7975
7976 ++significand;
7977 if (significand === 10) {
7978 significand = 1;
7979 ++exp;
7980 }
7981
7982 tickVal = significand * Math.pow(10, exp);
7983 } while (exp < endExp || (exp === endExp && significand < endSignificand));
7984
7985 var lastTick = valueOrDefault(generationOptions.max, tickVal);
7986 ticks.push(lastTick);
7987
7988 return ticks;
7989 }
7990 },
7991
7992 /**
7993 * Namespace to hold formatters for different types of ticks
7994 * @namespace Chart.Ticks.formatters
7995 */
7996 formatters: {
7997 /**
7998 * Formatter for value labels
7999 * @method Chart.Ticks.formatters.values
8000 * @param value the value to display
8001 * @return {String|Array} the label to display
8002 */
8003 values: function(value) {
8004 return helpers.isArray(value) ? value : '' + value;
8005 },
8006
8007 /**
8008 * Formatter for linear numeric ticks
8009 * @method Chart.Ticks.formatters.linear
8010 * @param tickValue {Number} the value to be formatted
8011 * @param index {Number} the position of the tickValue parameter in the ticks array
8012 * @param ticks {Array<Number>} the list of ticks being converted
8013 * @return {String} string representation of the tickValue parameter
8014 */
8015 linear: function(tickValue, index, ticks) {
8016 // If we have lots of ticks, don't use the ones
8017 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
8018
8019 // If we have a number like 2.5 as the delta, figure out how many decimal places we need
8020 if (Math.abs(delta) > 1) {
8021 if (tickValue !== Math.floor(tickValue)) {
8022 // not an integer
8023 delta = tickValue - Math.floor(tickValue);
8024 }
8025 }
8026
8027 var logDelta = helpers.log10(Math.abs(delta));
8028 var tickString = '';
8029
8030 if (tickValue !== 0) {
8031 var numDecimal = -1 * Math.floor(logDelta);
8032 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
8033 tickString = tickValue.toFixed(numDecimal);
8034 } else {
8035 tickString = '0'; // never show decimal places for 0
8036 }
8037
8038 return tickString;
8039 },
8040
8041 logarithmic: function(tickValue, index, ticks) {
8042 var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));
8043
8044 if (tickValue === 0) {
8045 return '0';
8046 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
8047 return tickValue.toExponential();
8048 }
8049 return '';
8050 }
8051 }
8052 };
8053
8054 },{"45":45}],35:[function(require,module,exports){
8055 'use strict';
8056
8057 var defaults = require(25);
8058 var Element = require(26);
8059 var helpers = require(45);
8060
8061 defaults._set('global', {
8062 tooltips: {
8063 enabled: true,
8064 custom: null,
8065 mode: 'nearest',
8066 position: 'average',
8067 intersect: true,
8068 backgroundColor: 'rgba(0,0,0,0.8)',
8069 titleFontStyle: 'bold',
8070 titleSpacing: 2,
8071 titleMarginBottom: 6,
8072 titleFontColor: '#fff',
8073 titleAlign: 'left',
8074 bodySpacing: 2,
8075 bodyFontColor: '#fff',
8076 bodyAlign: 'left',
8077 footerFontStyle: 'bold',
8078 footerSpacing: 2,
8079 footerMarginTop: 6,
8080 footerFontColor: '#fff',
8081 footerAlign: 'left',
8082 yPadding: 6,
8083 xPadding: 6,
8084 caretPadding: 2,
8085 caretSize: 5,
8086 cornerRadius: 6,
8087 multiKeyBackground: '#fff',
8088 displayColors: true,
8089 borderColor: 'rgba(0,0,0,0)',
8090 borderWidth: 0,
8091 callbacks: {
8092 // Args are: (tooltipItems, data)
8093 beforeTitle: helpers.noop,
8094 title: function(tooltipItems, data) {
8095 // Pick first xLabel for now
8096 var title = '';
8097 var labels = data.labels;
8098 var labelCount = labels ? labels.length : 0;
8099
8100 if (tooltipItems.length > 0) {
8101 var item = tooltipItems[0];
8102
8103 if (item.xLabel) {
8104 title = item.xLabel;
8105 } else if (labelCount > 0 && item.index < labelCount) {
8106 title = labels[item.index];
8107 }
8108 }
8109
8110 return title;
8111 },
8112 afterTitle: helpers.noop,
8113
8114 // Args are: (tooltipItems, data)
8115 beforeBody: helpers.noop,
8116
8117 // Args are: (tooltipItem, data)
8118 beforeLabel: helpers.noop,
8119 label: function(tooltipItem, data) {
8120 var label = data.datasets[tooltipItem.datasetIndex].label || '';
8121
8122 if (label) {
8123 label += ': ';
8124 }
8125 label += tooltipItem.yLabel;
8126 return label;
8127 },
8128 labelColor: function(tooltipItem, chart) {
8129 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
8130 var activeElement = meta.data[tooltipItem.index];
8131 var view = activeElement._view;
8132 return {
8133 borderColor: view.borderColor,
8134 backgroundColor: view.backgroundColor
8135 };
8136 },
8137 labelTextColor: function() {
8138 return this._options.bodyFontColor;
8139 },
8140 afterLabel: helpers.noop,
8141
8142 // Args are: (tooltipItems, data)
8143 afterBody: helpers.noop,
8144
8145 // Args are: (tooltipItems, data)
8146 beforeFooter: helpers.noop,
8147 footer: helpers.noop,
8148 afterFooter: helpers.noop
8149 }
8150 }
8151 });
8152
8153 module.exports = function(Chart) {
8154
8155 /**
8156 * Helper method to merge the opacity into a color
8157 */
8158 function mergeOpacity(colorString, opacity) {
8159 var color = helpers.color(colorString);
8160 return color.alpha(opacity * color.alpha()).rgbaString();
8161 }
8162
8163 // Helper to push or concat based on if the 2nd parameter is an array or not
8164 function pushOrConcat(base, toPush) {
8165 if (toPush) {
8166 if (helpers.isArray(toPush)) {
8167 // base = base.concat(toPush);
8168 Array.prototype.push.apply(base, toPush);
8169 } else {
8170 base.push(toPush);
8171 }
8172 }
8173
8174 return base;
8175 }
8176
8177 // Private helper to create a tooltip item model
8178 // @param element : the chart element (point, arc, bar) to create the tooltip item for
8179 // @return : new tooltip item
8180 function createTooltipItem(element) {
8181 var xScale = element._xScale;
8182 var yScale = element._yScale || element._scale; // handle radar || polarArea charts
8183 var index = element._index;
8184 var datasetIndex = element._datasetIndex;
8185
8186 return {
8187 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
8188 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
8189 index: index,
8190 datasetIndex: datasetIndex,
8191 x: element._model.x,
8192 y: element._model.y
8193 };
8194 }
8195
8196 /**
8197 * Helper to get the reset model for the tooltip
8198 * @param tooltipOpts {Object} the tooltip options
8199 */
8200 function getBaseModel(tooltipOpts) {
8201 var globalDefaults = defaults.global;
8202 var valueOrDefault = helpers.valueOrDefault;
8203
8204 return {
8205 // Positioning
8206 xPadding: tooltipOpts.xPadding,
8207 yPadding: tooltipOpts.yPadding,
8208 xAlign: tooltipOpts.xAlign,
8209 yAlign: tooltipOpts.yAlign,
8210
8211 // Body
8212 bodyFontColor: tooltipOpts.bodyFontColor,
8213 _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
8214 _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
8215 _bodyAlign: tooltipOpts.bodyAlign,
8216 bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
8217 bodySpacing: tooltipOpts.bodySpacing,
8218
8219 // Title
8220 titleFontColor: tooltipOpts.titleFontColor,
8221 _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
8222 _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
8223 titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
8224 _titleAlign: tooltipOpts.titleAlign,
8225 titleSpacing: tooltipOpts.titleSpacing,
8226 titleMarginBottom: tooltipOpts.titleMarginBottom,
8227
8228 // Footer
8229 footerFontColor: tooltipOpts.footerFontColor,
8230 _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
8231 _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
8232 footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
8233 _footerAlign: tooltipOpts.footerAlign,
8234 footerSpacing: tooltipOpts.footerSpacing,
8235 footerMarginTop: tooltipOpts.footerMarginTop,
8236
8237 // Appearance
8238 caretSize: tooltipOpts.caretSize,
8239 cornerRadius: tooltipOpts.cornerRadius,
8240 backgroundColor: tooltipOpts.backgroundColor,
8241 opacity: 0,
8242 legendColorBackground: tooltipOpts.multiKeyBackground,
8243 displayColors: tooltipOpts.displayColors,
8244 borderColor: tooltipOpts.borderColor,
8245 borderWidth: tooltipOpts.borderWidth
8246 };
8247 }
8248
8249 /**
8250 * Get the size of the tooltip
8251 */
8252 function getTooltipSize(tooltip, model) {
8253 var ctx = tooltip._chart.ctx;
8254
8255 var height = model.yPadding * 2; // Tooltip Padding
8256 var width = 0;
8257
8258 // Count of all lines in the body
8259 var body = model.body;
8260 var combinedBodyLength = body.reduce(function(count, bodyItem) {
8261 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
8262 }, 0);
8263 combinedBodyLength += model.beforeBody.length + model.afterBody.length;
8264
8265 var titleLineCount = model.title.length;
8266 var footerLineCount = model.footer.length;
8267 var titleFontSize = model.titleFontSize;
8268 var bodyFontSize = model.bodyFontSize;
8269 var footerFontSize = model.footerFontSize;
8270
8271 height += titleLineCount * titleFontSize; // Title Lines
8272 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
8273 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
8274 height += combinedBodyLength * bodyFontSize; // Body Lines
8275 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
8276 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
8277 height += footerLineCount * (footerFontSize); // Footer Lines
8278 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
8279
8280 // Title width
8281 var widthPadding = 0;
8282 var maxLineWidth = function(line) {
8283 width = Math.max(width, ctx.measureText(line).width + widthPadding);
8284 };
8285
8286 ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
8287 helpers.each(model.title, maxLineWidth);
8288
8289 // Body width
8290 ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
8291 helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
8292
8293 // Body lines may include some extra width due to the color box
8294 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
8295 helpers.each(body, function(bodyItem) {
8296 helpers.each(bodyItem.before, maxLineWidth);
8297 helpers.each(bodyItem.lines, maxLineWidth);
8298 helpers.each(bodyItem.after, maxLineWidth);
8299 });
8300
8301 // Reset back to 0
8302 widthPadding = 0;
8303
8304 // Footer width
8305 ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
8306 helpers.each(model.footer, maxLineWidth);
8307
8308 // Add padding
8309 width += 2 * model.xPadding;
8310
8311 return {
8312 width: width,
8313 height: height
8314 };
8315 }
8316
8317 /**
8318 * Helper to get the alignment of a tooltip given the size
8319 */
8320 function determineAlignment(tooltip, size) {
8321 var model = tooltip._model;
8322 var chart = tooltip._chart;
8323 var chartArea = tooltip._chart.chartArea;
8324 var xAlign = 'center';
8325 var yAlign = 'center';
8326
8327 if (model.y < size.height) {
8328 yAlign = 'top';
8329 } else if (model.y > (chart.height - size.height)) {
8330 yAlign = 'bottom';
8331 }
8332
8333 var lf, rf; // functions to determine left, right alignment
8334 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
8335 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
8336 var midX = (chartArea.left + chartArea.right) / 2;
8337 var midY = (chartArea.top + chartArea.bottom) / 2;
8338
8339 if (yAlign === 'center') {
8340 lf = function(x) {
8341 return x <= midX;
8342 };
8343 rf = function(x) {
8344 return x > midX;
8345 };
8346 } else {
8347 lf = function(x) {
8348 return x <= (size.width / 2);
8349 };
8350 rf = function(x) {
8351 return x >= (chart.width - (size.width / 2));
8352 };
8353 }
8354
8355 olf = function(x) {
8356 return x + size.width > chart.width;
8357 };
8358 orf = function(x) {
8359 return x - size.width < 0;
8360 };
8361 yf = function(y) {
8362 return y <= midY ? 'top' : 'bottom';
8363 };
8364
8365 if (lf(model.x)) {
8366 xAlign = 'left';
8367
8368 // Is tooltip too wide and goes over the right side of the chart.?
8369 if (olf(model.x)) {
8370 xAlign = 'center';
8371 yAlign = yf(model.y);
8372 }
8373 } else if (rf(model.x)) {
8374 xAlign = 'right';
8375
8376 // Is tooltip too wide and goes outside left edge of canvas?
8377 if (orf(model.x)) {
8378 xAlign = 'center';
8379 yAlign = yf(model.y);
8380 }
8381 }
8382
8383 var opts = tooltip._options;
8384 return {
8385 xAlign: opts.xAlign ? opts.xAlign : xAlign,
8386 yAlign: opts.yAlign ? opts.yAlign : yAlign
8387 };
8388 }
8389
8390 /**
8391 * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
8392 */
8393 function getBackgroundPoint(vm, size, alignment) {
8394 // Background Position
8395 var x = vm.x;
8396 var y = vm.y;
8397
8398 var caretSize = vm.caretSize;
8399 var caretPadding = vm.caretPadding;
8400 var cornerRadius = vm.cornerRadius;
8401 var xAlign = alignment.xAlign;
8402 var yAlign = alignment.yAlign;
8403 var paddingAndSize = caretSize + caretPadding;
8404 var radiusAndPadding = cornerRadius + caretPadding;
8405
8406 if (xAlign === 'right') {
8407 x -= size.width;
8408 } else if (xAlign === 'center') {
8409 x -= (size.width / 2);
8410 }
8411
8412 if (yAlign === 'top') {
8413 y += paddingAndSize;
8414 } else if (yAlign === 'bottom') {
8415 y -= size.height + paddingAndSize;
8416 } else {
8417 y -= (size.height / 2);
8418 }
8419
8420 if (yAlign === 'center') {
8421 if (xAlign === 'left') {
8422 x += paddingAndSize;
8423 } else if (xAlign === 'right') {
8424 x -= paddingAndSize;
8425 }
8426 } else if (xAlign === 'left') {
8427 x -= radiusAndPadding;
8428 } else if (xAlign === 'right') {
8429 x += radiusAndPadding;
8430 }
8431
8432 return {
8433 x: x,
8434 y: y
8435 };
8436 }
8437
8438 Chart.Tooltip = Element.extend({
8439 initialize: function() {
8440 this._model = getBaseModel(this._options);
8441 this._lastActive = [];
8442 },
8443
8444 // Get the title
8445 // Args are: (tooltipItem, data)
8446 getTitle: function() {
8447 var me = this;
8448 var opts = me._options;
8449 var callbacks = opts.callbacks;
8450
8451 var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
8452 var title = callbacks.title.apply(me, arguments);
8453 var afterTitle = callbacks.afterTitle.apply(me, arguments);
8454
8455 var lines = [];
8456 lines = pushOrConcat(lines, beforeTitle);
8457 lines = pushOrConcat(lines, title);
8458 lines = pushOrConcat(lines, afterTitle);
8459
8460 return lines;
8461 },
8462
8463 // Args are: (tooltipItem, data)
8464 getBeforeBody: function() {
8465 var lines = this._options.callbacks.beforeBody.apply(this, arguments);
8466 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8467 },
8468
8469 // Args are: (tooltipItem, data)
8470 getBody: function(tooltipItems, data) {
8471 var me = this;
8472 var callbacks = me._options.callbacks;
8473 var bodyItems = [];
8474
8475 helpers.each(tooltipItems, function(tooltipItem) {
8476 var bodyItem = {
8477 before: [],
8478 lines: [],
8479 after: []
8480 };
8481 pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
8482 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
8483 pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
8484
8485 bodyItems.push(bodyItem);
8486 });
8487
8488 return bodyItems;
8489 },
8490
8491 // Args are: (tooltipItem, data)
8492 getAfterBody: function() {
8493 var lines = this._options.callbacks.afterBody.apply(this, arguments);
8494 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8495 },
8496
8497 // Get the footer and beforeFooter and afterFooter lines
8498 // Args are: (tooltipItem, data)
8499 getFooter: function() {
8500 var me = this;
8501 var callbacks = me._options.callbacks;
8502
8503 var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
8504 var footer = callbacks.footer.apply(me, arguments);
8505 var afterFooter = callbacks.afterFooter.apply(me, arguments);
8506
8507 var lines = [];
8508 lines = pushOrConcat(lines, beforeFooter);
8509 lines = pushOrConcat(lines, footer);
8510 lines = pushOrConcat(lines, afterFooter);
8511
8512 return lines;
8513 },
8514
8515 update: function(changed) {
8516 var me = this;
8517 var opts = me._options;
8518
8519 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
8520 // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time
8521 // which breaks any animations.
8522 var existingModel = me._model;
8523 var model = me._model = getBaseModel(opts);
8524 var active = me._active;
8525
8526 var data = me._data;
8527
8528 // In the case where active.length === 0 we need to keep these at existing values for good animations
8529 var alignment = {
8530 xAlign: existingModel.xAlign,
8531 yAlign: existingModel.yAlign
8532 };
8533 var backgroundPoint = {
8534 x: existingModel.x,
8535 y: existingModel.y
8536 };
8537 var tooltipSize = {
8538 width: existingModel.width,
8539 height: existingModel.height
8540 };
8541 var tooltipPosition = {
8542 x: existingModel.caretX,
8543 y: existingModel.caretY
8544 };
8545
8546 var i, len;
8547
8548 if (active.length) {
8549 model.opacity = 1;
8550
8551 var labelColors = [];
8552 var labelTextColors = [];
8553 tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition);
8554
8555 var tooltipItems = [];
8556 for (i = 0, len = active.length; i < len; ++i) {
8557 tooltipItems.push(createTooltipItem(active[i]));
8558 }
8559
8560 // If the user provided a filter function, use it to modify the tooltip items
8561 if (opts.filter) {
8562 tooltipItems = tooltipItems.filter(function(a) {
8563 return opts.filter(a, data);
8564 });
8565 }
8566
8567 // If the user provided a sorting function, use it to modify the tooltip items
8568 if (opts.itemSort) {
8569 tooltipItems = tooltipItems.sort(function(a, b) {
8570 return opts.itemSort(a, b, data);
8571 });
8572 }
8573
8574 // Determine colors for boxes
8575 helpers.each(tooltipItems, function(tooltipItem) {
8576 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
8577 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
8578 });
8579
8580
8581 // Build the Text Lines
8582 model.title = me.getTitle(tooltipItems, data);
8583 model.beforeBody = me.getBeforeBody(tooltipItems, data);
8584 model.body = me.getBody(tooltipItems, data);
8585 model.afterBody = me.getAfterBody(tooltipItems, data);
8586 model.footer = me.getFooter(tooltipItems, data);
8587
8588 // Initial positioning and colors
8589 model.x = Math.round(tooltipPosition.x);
8590 model.y = Math.round(tooltipPosition.y);
8591 model.caretPadding = opts.caretPadding;
8592 model.labelColors = labelColors;
8593 model.labelTextColors = labelTextColors;
8594
8595 // data points
8596 model.dataPoints = tooltipItems;
8597
8598 // We need to determine alignment of the tooltip
8599 tooltipSize = getTooltipSize(this, model);
8600 alignment = determineAlignment(this, tooltipSize);
8601 // Final Size and Position
8602 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment);
8603 } else {
8604 model.opacity = 0;
8605 }
8606
8607 model.xAlign = alignment.xAlign;
8608 model.yAlign = alignment.yAlign;
8609 model.x = backgroundPoint.x;
8610 model.y = backgroundPoint.y;
8611 model.width = tooltipSize.width;
8612 model.height = tooltipSize.height;
8613
8614 // Point where the caret on the tooltip points to
8615 model.caretX = tooltipPosition.x;
8616 model.caretY = tooltipPosition.y;
8617
8618 me._model = model;
8619
8620 if (changed && opts.custom) {
8621 opts.custom.call(me, model);
8622 }
8623
8624 return me;
8625 },
8626 drawCaret: function(tooltipPoint, size) {
8627 var ctx = this._chart.ctx;
8628 var vm = this._view;
8629 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
8630
8631 ctx.lineTo(caretPosition.x1, caretPosition.y1);
8632 ctx.lineTo(caretPosition.x2, caretPosition.y2);
8633 ctx.lineTo(caretPosition.x3, caretPosition.y3);
8634 },
8635 getCaretPosition: function(tooltipPoint, size, vm) {
8636 var x1, x2, x3, y1, y2, y3;
8637 var caretSize = vm.caretSize;
8638 var cornerRadius = vm.cornerRadius;
8639 var xAlign = vm.xAlign;
8640 var yAlign = vm.yAlign;
8641 var ptX = tooltipPoint.x;
8642 var ptY = tooltipPoint.y;
8643 var width = size.width;
8644 var height = size.height;
8645
8646 if (yAlign === 'center') {
8647 y2 = ptY + (height / 2);
8648
8649 if (xAlign === 'left') {
8650 x1 = ptX;
8651 x2 = x1 - caretSize;
8652 x3 = x1;
8653
8654 y1 = y2 + caretSize;
8655 y3 = y2 - caretSize;
8656 } else {
8657 x1 = ptX + width;
8658 x2 = x1 + caretSize;
8659 x3 = x1;
8660
8661 y1 = y2 - caretSize;
8662 y3 = y2 + caretSize;
8663 }
8664 } else {
8665 if (xAlign === 'left') {
8666 x2 = ptX + cornerRadius + (caretSize);
8667 x1 = x2 - caretSize;
8668 x3 = x2 + caretSize;
8669 } else if (xAlign === 'right') {
8670 x2 = ptX + width - cornerRadius - caretSize;
8671 x1 = x2 - caretSize;
8672 x3 = x2 + caretSize;
8673 } else {
8674 x2 = ptX + (width / 2);
8675 x1 = x2 - caretSize;
8676 x3 = x2 + caretSize;
8677 }
8678 if (yAlign === 'top') {
8679 y1 = ptY;
8680 y2 = y1 - caretSize;
8681 y3 = y1;
8682 } else {
8683 y1 = ptY + height;
8684 y2 = y1 + caretSize;
8685 y3 = y1;
8686 // invert drawing order
8687 var tmp = x3;
8688 x3 = x1;
8689 x1 = tmp;
8690 }
8691 }
8692 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
8693 },
8694 drawTitle: function(pt, vm, ctx, opacity) {
8695 var title = vm.title;
8696
8697 if (title.length) {
8698 ctx.textAlign = vm._titleAlign;
8699 ctx.textBaseline = 'top';
8700
8701 var titleFontSize = vm.titleFontSize;
8702 var titleSpacing = vm.titleSpacing;
8703
8704 ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
8705 ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8706
8707 var i, len;
8708 for (i = 0, len = title.length; i < len; ++i) {
8709 ctx.fillText(title[i], pt.x, pt.y);
8710 pt.y += titleFontSize + titleSpacing; // Line Height and spacing
8711
8712 if (i + 1 === title.length) {
8713 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
8714 }
8715 }
8716 }
8717 },
8718 drawBody: function(pt, vm, ctx, opacity) {
8719 var bodyFontSize = vm.bodyFontSize;
8720 var bodySpacing = vm.bodySpacing;
8721 var body = vm.body;
8722
8723 ctx.textAlign = vm._bodyAlign;
8724 ctx.textBaseline = 'top';
8725 ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8726
8727 // Before Body
8728 var xLinePadding = 0;
8729 var fillLineOfText = function(line) {
8730 ctx.fillText(line, pt.x + xLinePadding, pt.y);
8731 pt.y += bodyFontSize + bodySpacing;
8732 };
8733
8734 // Before body lines
8735 ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
8736 helpers.each(vm.beforeBody, fillLineOfText);
8737
8738 var drawColorBoxes = vm.displayColors;
8739 xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
8740
8741 // Draw body lines now
8742 helpers.each(body, function(bodyItem, i) {
8743 var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
8744 ctx.fillStyle = textColor;
8745 helpers.each(bodyItem.before, fillLineOfText);
8746
8747 helpers.each(bodyItem.lines, function(line) {
8748 // Draw Legend-like boxes if needed
8749 if (drawColorBoxes) {
8750 // Fill a white rect so that colours merge nicely if the opacity is < 1
8751 ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
8752 ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8753
8754 // Border
8755 ctx.lineWidth = 1;
8756 ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
8757 ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8758
8759 // Inner square
8760 ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
8761 ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
8762 ctx.fillStyle = textColor;
8763 }
8764
8765 fillLineOfText(line);
8766 });
8767
8768 helpers.each(bodyItem.after, fillLineOfText);
8769 });
8770
8771 // Reset back to 0 for after body
8772 xLinePadding = 0;
8773
8774 // After body lines
8775 helpers.each(vm.afterBody, fillLineOfText);
8776 pt.y -= bodySpacing; // Remove last body spacing
8777 },
8778 drawFooter: function(pt, vm, ctx, opacity) {
8779 var footer = vm.footer;
8780
8781 if (footer.length) {
8782 pt.y += vm.footerMarginTop;
8783
8784 ctx.textAlign = vm._footerAlign;
8785 ctx.textBaseline = 'top';
8786
8787 ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
8788 ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8789
8790 helpers.each(footer, function(line) {
8791 ctx.fillText(line, pt.x, pt.y);
8792 pt.y += vm.footerFontSize + vm.footerSpacing;
8793 });
8794 }
8795 },
8796 drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
8797 ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
8798 ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
8799 ctx.lineWidth = vm.borderWidth;
8800 var xAlign = vm.xAlign;
8801 var yAlign = vm.yAlign;
8802 var x = pt.x;
8803 var y = pt.y;
8804 var width = tooltipSize.width;
8805 var height = tooltipSize.height;
8806 var radius = vm.cornerRadius;
8807
8808 ctx.beginPath();
8809 ctx.moveTo(x + radius, y);
8810 if (yAlign === 'top') {
8811 this.drawCaret(pt, tooltipSize);
8812 }
8813 ctx.lineTo(x + width - radius, y);
8814 ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
8815 if (yAlign === 'center' && xAlign === 'right') {
8816 this.drawCaret(pt, tooltipSize);
8817 }
8818 ctx.lineTo(x + width, y + height - radius);
8819 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
8820 if (yAlign === 'bottom') {
8821 this.drawCaret(pt, tooltipSize);
8822 }
8823 ctx.lineTo(x + radius, y + height);
8824 ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
8825 if (yAlign === 'center' && xAlign === 'left') {
8826 this.drawCaret(pt, tooltipSize);
8827 }
8828 ctx.lineTo(x, y + radius);
8829 ctx.quadraticCurveTo(x, y, x + radius, y);
8830 ctx.closePath();
8831
8832 ctx.fill();
8833
8834 if (vm.borderWidth > 0) {
8835 ctx.stroke();
8836 }
8837 },
8838 draw: function() {
8839 var ctx = this._chart.ctx;
8840 var vm = this._view;
8841
8842 if (vm.opacity === 0) {
8843 return;
8844 }
8845
8846 var tooltipSize = {
8847 width: vm.width,
8848 height: vm.height
8849 };
8850 var pt = {
8851 x: vm.x,
8852 y: vm.y
8853 };
8854
8855 // IE11/Edge does not like very small opacities, so snap to 0
8856 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
8857
8858 // Truthy/falsey value for empty tooltip
8859 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
8860
8861 if (this._options.enabled && hasTooltipContent) {
8862 // Draw Background
8863 this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
8864
8865 // Draw Title, Body, and Footer
8866 pt.x += vm.xPadding;
8867 pt.y += vm.yPadding;
8868
8869 // Titles
8870 this.drawTitle(pt, vm, ctx, opacity);
8871
8872 // Body
8873 this.drawBody(pt, vm, ctx, opacity);
8874
8875 // Footer
8876 this.drawFooter(pt, vm, ctx, opacity);
8877 }
8878 },
8879
8880 /**
8881 * Handle an event
8882 * @private
8883 * @param {IEvent} event - The event to handle
8884 * @returns {Boolean} true if the tooltip changed
8885 */
8886 handleEvent: function(e) {
8887 var me = this;
8888 var options = me._options;
8889 var changed = false;
8890
8891 me._lastActive = me._lastActive || [];
8892
8893 // Find Active Elements for tooltips
8894 if (e.type === 'mouseout') {
8895 me._active = [];
8896 } else {
8897 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
8898 }
8899
8900 // Remember Last Actives
8901 changed = !helpers.arrayEquals(me._active, me._lastActive);
8902
8903 // If tooltip didn't change, do not handle the target event
8904 if (!changed) {
8905 return false;
8906 }
8907
8908 me._lastActive = me._active;
8909
8910 if (options.enabled || options.custom) {
8911 me._eventPosition = {
8912 x: e.x,
8913 y: e.y
8914 };
8915
8916 var model = me._model;
8917 me.update(true);
8918 me.pivot();
8919
8920 // See if our tooltip position changed
8921 changed |= (model.x !== me._model.x) || (model.y !== me._model.y);
8922 }
8923
8924 return changed;
8925 }
8926 });
8927
8928 /**
8929 * @namespace Chart.Tooltip.positioners
8930 */
8931 Chart.Tooltip.positioners = {
8932 /**
8933 * Average mode places the tooltip at the average position of the elements shown
8934 * @function Chart.Tooltip.positioners.average
8935 * @param elements {ChartElement[]} the elements being displayed in the tooltip
8936 * @returns {Point} tooltip position
8937 */
8938 average: function(elements) {
8939 if (!elements.length) {
8940 return false;
8941 }
8942
8943 var i, len;
8944 var x = 0;
8945 var y = 0;
8946 var count = 0;
8947
8948 for (i = 0, len = elements.length; i < len; ++i) {
8949 var el = elements[i];
8950 if (el && el.hasValue()) {
8951 var pos = el.tooltipPosition();
8952 x += pos.x;
8953 y += pos.y;
8954 ++count;
8955 }
8956 }
8957
8958 return {
8959 x: Math.round(x / count),
8960 y: Math.round(y / count)
8961 };
8962 },
8963
8964 /**
8965 * Gets the tooltip position nearest of the item nearest to the event position
8966 * @function Chart.Tooltip.positioners.nearest
8967 * @param elements {Chart.Element[]} the tooltip elements
8968 * @param eventPosition {Point} the position of the event in canvas coordinates
8969 * @returns {Point} the tooltip position
8970 */
8971 nearest: function(elements, eventPosition) {
8972 var x = eventPosition.x;
8973 var y = eventPosition.y;
8974 var minDistance = Number.POSITIVE_INFINITY;
8975 var i, len, nearestElement;
8976
8977 for (i = 0, len = elements.length; i < len; ++i) {
8978 var el = elements[i];
8979 if (el && el.hasValue()) {
8980 var center = el.getCenterPoint();
8981 var d = helpers.distanceBetweenPoints(eventPosition, center);
8982
8983 if (d < minDistance) {
8984 minDistance = d;
8985 nearestElement = el;
8986 }
8987 }
8988 }
8989
8990 if (nearestElement) {
8991 var tp = nearestElement.tooltipPosition();
8992 x = tp.x;
8993 y = tp.y;
8994 }
8995
8996 return {
8997 x: x,
8998 y: y
8999 };
9000 }
9001 };
9002 };
9003
9004 },{"25":25,"26":26,"45":45}],36:[function(require,module,exports){
9005 'use strict';
9006
9007 var defaults = require(25);
9008 var Element = require(26);
9009 var helpers = require(45);
9010
9011 defaults._set('global', {
9012 elements: {
9013 arc: {
9014 backgroundColor: defaults.global.defaultColor,
9015 borderColor: '#fff',
9016 borderWidth: 2
9017 }
9018 }
9019 });
9020
9021 module.exports = Element.extend({
9022 inLabelRange: function(mouseX) {
9023 var vm = this._view;
9024
9025 if (vm) {
9026 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
9027 }
9028 return false;
9029 },
9030
9031 inRange: function(chartX, chartY) {
9032 var vm = this._view;
9033
9034 if (vm) {
9035 var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
9036 var angle = pointRelativePosition.angle;
9037 var distance = pointRelativePosition.distance;
9038
9039 // Sanitise angle range
9040 var startAngle = vm.startAngle;
9041 var endAngle = vm.endAngle;
9042 while (endAngle < startAngle) {
9043 endAngle += 2.0 * Math.PI;
9044 }
9045 while (angle > endAngle) {
9046 angle -= 2.0 * Math.PI;
9047 }
9048 while (angle < startAngle) {
9049 angle += 2.0 * Math.PI;
9050 }
9051
9052 // Check if within the range of the open/close angle
9053 var betweenAngles = (angle >= startAngle && angle <= endAngle);
9054 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
9055
9056 return (betweenAngles && withinRadius);
9057 }
9058 return false;
9059 },
9060
9061 getCenterPoint: function() {
9062 var vm = this._view;
9063 var halfAngle = (vm.startAngle + vm.endAngle) / 2;
9064 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
9065 return {
9066 x: vm.x + Math.cos(halfAngle) * halfRadius,
9067 y: vm.y + Math.sin(halfAngle) * halfRadius
9068 };
9069 },
9070
9071 getArea: function() {
9072 var vm = this._view;
9073 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
9074 },
9075
9076 tooltipPosition: function() {
9077 var vm = this._view;
9078 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
9079 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
9080
9081 return {
9082 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
9083 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
9084 };
9085 },
9086
9087 draw: function() {
9088 var ctx = this._chart.ctx;
9089 var vm = this._view;
9090 var sA = vm.startAngle;
9091 var eA = vm.endAngle;
9092
9093 ctx.beginPath();
9094
9095 ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
9096 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
9097
9098 ctx.closePath();
9099 ctx.strokeStyle = vm.borderColor;
9100 ctx.lineWidth = vm.borderWidth;
9101
9102 ctx.fillStyle = vm.backgroundColor;
9103
9104 ctx.fill();
9105 ctx.lineJoin = 'bevel';
9106
9107 if (vm.borderWidth) {
9108 ctx.stroke();
9109 }
9110 }
9111 });
9112
9113 },{"25":25,"26":26,"45":45}],37:[function(require,module,exports){
9114 'use strict';
9115
9116 var defaults = require(25);
9117 var Element = require(26);
9118 var helpers = require(45);
9119
9120 var globalDefaults = defaults.global;
9121
9122 defaults._set('global', {
9123 elements: {
9124 line: {
9125 tension: 0.4,
9126 backgroundColor: globalDefaults.defaultColor,
9127 borderWidth: 3,
9128 borderColor: globalDefaults.defaultColor,
9129 borderCapStyle: 'butt',
9130 borderDash: [],
9131 borderDashOffset: 0.0,
9132 borderJoinStyle: 'miter',
9133 capBezierPoints: true,
9134 fill: true, // do we fill in the area between the line and its base axis
9135 }
9136 }
9137 });
9138
9139 module.exports = Element.extend({
9140 draw: function() {
9141 var me = this;
9142 var vm = me._view;
9143 var ctx = me._chart.ctx;
9144 var spanGaps = vm.spanGaps;
9145 var points = me._children.slice(); // clone array
9146 var globalOptionLineElements = globalDefaults.elements.line;
9147 var lastDrawnIndex = -1;
9148 var index, current, previous, currentVM;
9149
9150 // If we are looping, adding the first point again
9151 if (me._loop && points.length) {
9152 points.push(points[0]);
9153 }
9154
9155 ctx.save();
9156
9157 // Stroke Line Options
9158 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
9159
9160 // IE 9 and 10 do not support line dash
9161 if (ctx.setLineDash) {
9162 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
9163 }
9164
9165 ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
9166 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
9167 ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
9168 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
9169
9170 // Stroke Line
9171 ctx.beginPath();
9172 lastDrawnIndex = -1;
9173
9174 for (index = 0; index < points.length; ++index) {
9175 current = points[index];
9176 previous = helpers.previousItem(points, index);
9177 currentVM = current._view;
9178
9179 // First point moves to it's starting position no matter what
9180 if (index === 0) {
9181 if (!currentVM.skip) {
9182 ctx.moveTo(currentVM.x, currentVM.y);
9183 lastDrawnIndex = index;
9184 }
9185 } else {
9186 previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
9187
9188 if (!currentVM.skip) {
9189 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
9190 // There was a gap and this is the first point after the gap
9191 ctx.moveTo(currentVM.x, currentVM.y);
9192 } else {
9193 // Line to next point
9194 helpers.canvas.lineTo(ctx, previous._view, current._view);
9195 }
9196 lastDrawnIndex = index;
9197 }
9198 }
9199 }
9200
9201 ctx.stroke();
9202 ctx.restore();
9203 }
9204 });
9205
9206 },{"25":25,"26":26,"45":45}],38:[function(require,module,exports){
9207 'use strict';
9208
9209 var defaults = require(25);
9210 var Element = require(26);
9211 var helpers = require(45);
9212
9213 var defaultColor = defaults.global.defaultColor;
9214
9215 defaults._set('global', {
9216 elements: {
9217 point: {
9218 radius: 3,
9219 pointStyle: 'circle',
9220 backgroundColor: defaultColor,
9221 borderColor: defaultColor,
9222 borderWidth: 1,
9223 // Hover
9224 hitRadius: 1,
9225 hoverRadius: 4,
9226 hoverBorderWidth: 1
9227 }
9228 }
9229 });
9230
9231 function xRange(mouseX) {
9232 var vm = this._view;
9233 return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9234 }
9235
9236 function yRange(mouseY) {
9237 var vm = this._view;
9238 return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9239 }
9240
9241 module.exports = Element.extend({
9242 inRange: function(mouseX, mouseY) {
9243 var vm = this._view;
9244 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
9245 },
9246
9247 inLabelRange: xRange,
9248 inXRange: xRange,
9249 inYRange: yRange,
9250
9251 getCenterPoint: function() {
9252 var vm = this._view;
9253 return {
9254 x: vm.x,
9255 y: vm.y
9256 };
9257 },
9258
9259 getArea: function() {
9260 return Math.PI * Math.pow(this._view.radius, 2);
9261 },
9262
9263 tooltipPosition: function() {
9264 var vm = this._view;
9265 return {
9266 x: vm.x,
9267 y: vm.y,
9268 padding: vm.radius + vm.borderWidth
9269 };
9270 },
9271
9272 draw: function(chartArea) {
9273 var vm = this._view;
9274 var model = this._model;
9275 var ctx = this._chart.ctx;
9276 var pointStyle = vm.pointStyle;
9277 var radius = vm.radius;
9278 var x = vm.x;
9279 var y = vm.y;
9280 var color = helpers.color;
9281 var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
9282 var ratio = 0;
9283
9284 if (vm.skip) {
9285 return;
9286 }
9287
9288 ctx.strokeStyle = vm.borderColor || defaultColor;
9289 ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth);
9290 ctx.fillStyle = vm.backgroundColor || defaultColor;
9291
9292 // Cliping for Points.
9293 // going out from inner charArea?
9294 if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) {
9295 // Point fade out
9296 if (model.x < chartArea.left) {
9297 ratio = (x - model.x) / (chartArea.left - model.x);
9298 } else if (chartArea.right * errMargin < model.x) {
9299 ratio = (model.x - x) / (model.x - chartArea.right);
9300 } else if (model.y < chartArea.top) {
9301 ratio = (y - model.y) / (chartArea.top - model.y);
9302 } else if (chartArea.bottom * errMargin < model.y) {
9303 ratio = (model.y - y) / (model.y - chartArea.bottom);
9304 }
9305 ratio = Math.round(ratio * 100) / 100;
9306 ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();
9307 ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
9308 }
9309
9310 helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y);
9311 }
9312 });
9313
9314 },{"25":25,"26":26,"45":45}],39:[function(require,module,exports){
9315 'use strict';
9316
9317 var defaults = require(25);
9318 var Element = require(26);
9319
9320 defaults._set('global', {
9321 elements: {
9322 rectangle: {
9323 backgroundColor: defaults.global.defaultColor,
9324 borderColor: defaults.global.defaultColor,
9325 borderSkipped: 'bottom',
9326 borderWidth: 0
9327 }
9328 }
9329 });
9330
9331 function isVertical(bar) {
9332 return bar._view.width !== undefined;
9333 }
9334
9335 /**
9336 * Helper function to get the bounds of the bar regardless of the orientation
9337 * @param bar {Chart.Element.Rectangle} the bar
9338 * @return {Bounds} bounds of the bar
9339 * @private
9340 */
9341 function getBarBounds(bar) {
9342 var vm = bar._view;
9343 var x1, x2, y1, y2;
9344
9345 if (isVertical(bar)) {
9346 // vertical
9347 var halfWidth = vm.width / 2;
9348 x1 = vm.x - halfWidth;
9349 x2 = vm.x + halfWidth;
9350 y1 = Math.min(vm.y, vm.base);
9351 y2 = Math.max(vm.y, vm.base);
9352 } else {
9353 // horizontal bar
9354 var halfHeight = vm.height / 2;
9355 x1 = Math.min(vm.x, vm.base);
9356 x2 = Math.max(vm.x, vm.base);
9357 y1 = vm.y - halfHeight;
9358 y2 = vm.y + halfHeight;
9359 }
9360
9361 return {
9362 left: x1,
9363 top: y1,
9364 right: x2,
9365 bottom: y2
9366 };
9367 }
9368
9369 module.exports = Element.extend({
9370 draw: function() {
9371 var ctx = this._chart.ctx;
9372 var vm = this._view;
9373 var left, right, top, bottom, signX, signY, borderSkipped;
9374 var borderWidth = vm.borderWidth;
9375
9376 if (!vm.horizontal) {
9377 // bar
9378 left = vm.x - vm.width / 2;
9379 right = vm.x + vm.width / 2;
9380 top = vm.y;
9381 bottom = vm.base;
9382 signX = 1;
9383 signY = bottom > top ? 1 : -1;
9384 borderSkipped = vm.borderSkipped || 'bottom';
9385 } else {
9386 // horizontal bar
9387 left = vm.base;
9388 right = vm.x;
9389 top = vm.y - vm.height / 2;
9390 bottom = vm.y + vm.height / 2;
9391 signX = right > left ? 1 : -1;
9392 signY = 1;
9393 borderSkipped = vm.borderSkipped || 'left';
9394 }
9395
9396 // Canvas doesn't allow us to stroke inside the width so we can
9397 // adjust the sizes to fit if we're setting a stroke on the line
9398 if (borderWidth) {
9399 // borderWidth shold be less than bar width and bar height.
9400 var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
9401 borderWidth = borderWidth > barSize ? barSize : borderWidth;
9402 var halfStroke = borderWidth / 2;
9403 // Adjust borderWidth when bar top position is near vm.base(zero).
9404 var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
9405 var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
9406 var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
9407 var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
9408 // not become a vertical line?
9409 if (borderLeft !== borderRight) {
9410 top = borderTop;
9411 bottom = borderBottom;
9412 }
9413 // not become a horizontal line?
9414 if (borderTop !== borderBottom) {
9415 left = borderLeft;
9416 right = borderRight;
9417 }
9418 }
9419
9420 ctx.beginPath();
9421 ctx.fillStyle = vm.backgroundColor;
9422 ctx.strokeStyle = vm.borderColor;
9423 ctx.lineWidth = borderWidth;
9424
9425 // Corner points, from bottom-left to bottom-right clockwise
9426 // | 1 2 |
9427 // | 0 3 |
9428 var corners = [
9429 [left, bottom],
9430 [left, top],
9431 [right, top],
9432 [right, bottom]
9433 ];
9434
9435 // Find first (starting) corner with fallback to 'bottom'
9436 var borders = ['bottom', 'left', 'top', 'right'];
9437 var startCorner = borders.indexOf(borderSkipped, 0);
9438 if (startCorner === -1) {
9439 startCorner = 0;
9440 }
9441
9442 function cornerAt(index) {
9443 return corners[(startCorner + index) % 4];
9444 }
9445
9446 // Draw rectangle from 'startCorner'
9447 var corner = cornerAt(0);
9448 ctx.moveTo(corner[0], corner[1]);
9449
9450 for (var i = 1; i < 4; i++) {
9451 corner = cornerAt(i);
9452 ctx.lineTo(corner[0], corner[1]);
9453 }
9454
9455 ctx.fill();
9456 if (borderWidth) {
9457 ctx.stroke();
9458 }
9459 },
9460
9461 height: function() {
9462 var vm = this._view;
9463 return vm.base - vm.y;
9464 },
9465
9466 inRange: function(mouseX, mouseY) {
9467 var inRange = false;
9468
9469 if (this._view) {
9470 var bounds = getBarBounds(this);
9471 inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
9472 }
9473
9474 return inRange;
9475 },
9476
9477 inLabelRange: function(mouseX, mouseY) {
9478 var me = this;
9479 if (!me._view) {
9480 return false;
9481 }
9482
9483 var inRange = false;
9484 var bounds = getBarBounds(me);
9485
9486 if (isVertical(me)) {
9487 inRange = mouseX >= bounds.left && mouseX <= bounds.right;
9488 } else {
9489 inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
9490 }
9491
9492 return inRange;
9493 },
9494
9495 inXRange: function(mouseX) {
9496 var bounds = getBarBounds(this);
9497 return mouseX >= bounds.left && mouseX <= bounds.right;
9498 },
9499
9500 inYRange: function(mouseY) {
9501 var bounds = getBarBounds(this);
9502 return mouseY >= bounds.top && mouseY <= bounds.bottom;
9503 },
9504
9505 getCenterPoint: function() {
9506 var vm = this._view;
9507 var x, y;
9508 if (isVertical(this)) {
9509 x = vm.x;
9510 y = (vm.y + vm.base) / 2;
9511 } else {
9512 x = (vm.x + vm.base) / 2;
9513 y = vm.y;
9514 }
9515
9516 return {x: x, y: y};
9517 },
9518
9519 getArea: function() {
9520 var vm = this._view;
9521 return vm.width * Math.abs(vm.y - vm.base);
9522 },
9523
9524 tooltipPosition: function() {
9525 var vm = this._view;
9526 return {
9527 x: vm.x,
9528 y: vm.y
9529 };
9530 }
9531 });
9532
9533 },{"25":25,"26":26}],40:[function(require,module,exports){
9534 'use strict';
9535
9536 module.exports = {};
9537 module.exports.Arc = require(36);
9538 module.exports.Line = require(37);
9539 module.exports.Point = require(38);
9540 module.exports.Rectangle = require(39);
9541
9542 },{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){
9543 'use strict';
9544
9545 var helpers = require(42);
9546
9547 /**
9548 * @namespace Chart.helpers.canvas
9549 */
9550 var exports = module.exports = {
9551 /**
9552 * Clears the entire canvas associated to the given `chart`.
9553 * @param {Chart} chart - The chart for which to clear the canvas.
9554 */
9555 clear: function(chart) {
9556 chart.ctx.clearRect(0, 0, chart.width, chart.height);
9557 },
9558
9559 /**
9560 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
9561 * given size (width, height) and the same `radius` for all corners.
9562 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
9563 * @param {Number} x - The x axis of the coordinate for the rectangle starting point.
9564 * @param {Number} y - The y axis of the coordinate for the rectangle starting point.
9565 * @param {Number} width - The rectangle's width.
9566 * @param {Number} height - The rectangle's height.
9567 * @param {Number} radius - The rounded amount (in pixels) for the four corners.
9568 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
9569 */
9570 roundedRect: function(ctx, x, y, width, height, radius) {
9571 if (radius) {
9572 var rx = Math.min(radius, width / 2);
9573 var ry = Math.min(radius, height / 2);
9574
9575 ctx.moveTo(x + rx, y);
9576 ctx.lineTo(x + width - rx, y);
9577 ctx.quadraticCurveTo(x + width, y, x + width, y + ry);
9578 ctx.lineTo(x + width, y + height - ry);
9579 ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
9580 ctx.lineTo(x + rx, y + height);
9581 ctx.quadraticCurveTo(x, y + height, x, y + height - ry);
9582 ctx.lineTo(x, y + ry);
9583 ctx.quadraticCurveTo(x, y, x + rx, y);
9584 } else {
9585 ctx.rect(x, y, width, height);
9586 }
9587 },
9588
9589 drawPoint: function(ctx, style, radius, x, y) {
9590 var type, edgeLength, xOffset, yOffset, height, size;
9591
9592 if (style && typeof style === 'object') {
9593 type = style.toString();
9594 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
9595 ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
9596 return;
9597 }
9598 }
9599
9600 if (isNaN(radius) || radius <= 0) {
9601 return;
9602 }
9603
9604 switch (style) {
9605 // Default includes circle
9606 default:
9607 ctx.beginPath();
9608 ctx.arc(x, y, radius, 0, Math.PI * 2);
9609 ctx.closePath();
9610 ctx.fill();
9611 break;
9612 case 'triangle':
9613 ctx.beginPath();
9614 edgeLength = 3 * radius / Math.sqrt(3);
9615 height = edgeLength * Math.sqrt(3) / 2;
9616 ctx.moveTo(x - edgeLength / 2, y + height / 3);
9617 ctx.lineTo(x + edgeLength / 2, y + height / 3);
9618 ctx.lineTo(x, y - 2 * height / 3);
9619 ctx.closePath();
9620 ctx.fill();
9621 break;
9622 case 'rect':
9623 size = 1 / Math.SQRT2 * radius;
9624 ctx.beginPath();
9625 ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
9626 ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
9627 break;
9628 case 'rectRounded':
9629 var offset = radius / Math.SQRT2;
9630 var leftX = x - offset;
9631 var topY = y - offset;
9632 var sideSize = Math.SQRT2 * radius;
9633 ctx.beginPath();
9634 this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2);
9635 ctx.closePath();
9636 ctx.fill();
9637 break;
9638 case 'rectRot':
9639 size = 1 / Math.SQRT2 * radius;
9640 ctx.beginPath();
9641 ctx.moveTo(x - size, y);
9642 ctx.lineTo(x, y + size);
9643 ctx.lineTo(x + size, y);
9644 ctx.lineTo(x, y - size);
9645 ctx.closePath();
9646 ctx.fill();
9647 break;
9648 case 'cross':
9649 ctx.beginPath();
9650 ctx.moveTo(x, y + radius);
9651 ctx.lineTo(x, y - radius);
9652 ctx.moveTo(x - radius, y);
9653 ctx.lineTo(x + radius, y);
9654 ctx.closePath();
9655 break;
9656 case 'crossRot':
9657 ctx.beginPath();
9658 xOffset = Math.cos(Math.PI / 4) * radius;
9659 yOffset = Math.sin(Math.PI / 4) * radius;
9660 ctx.moveTo(x - xOffset, y - yOffset);
9661 ctx.lineTo(x + xOffset, y + yOffset);
9662 ctx.moveTo(x - xOffset, y + yOffset);
9663 ctx.lineTo(x + xOffset, y - yOffset);
9664 ctx.closePath();
9665 break;
9666 case 'star':
9667 ctx.beginPath();
9668 ctx.moveTo(x, y + radius);
9669 ctx.lineTo(x, y - radius);
9670 ctx.moveTo(x - radius, y);
9671 ctx.lineTo(x + radius, y);
9672 xOffset = Math.cos(Math.PI / 4) * radius;
9673 yOffset = Math.sin(Math.PI / 4) * radius;
9674 ctx.moveTo(x - xOffset, y - yOffset);
9675 ctx.lineTo(x + xOffset, y + yOffset);
9676 ctx.moveTo(x - xOffset, y + yOffset);
9677 ctx.lineTo(x + xOffset, y - yOffset);
9678 ctx.closePath();
9679 break;
9680 case 'line':
9681 ctx.beginPath();
9682 ctx.moveTo(x - radius, y);
9683 ctx.lineTo(x + radius, y);
9684 ctx.closePath();
9685 break;
9686 case 'dash':
9687 ctx.beginPath();
9688 ctx.moveTo(x, y);
9689 ctx.lineTo(x + radius, y);
9690 ctx.closePath();
9691 break;
9692 }
9693
9694 ctx.stroke();
9695 },
9696
9697 clipArea: function(ctx, area) {
9698 ctx.save();
9699 ctx.beginPath();
9700 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
9701 ctx.clip();
9702 },
9703
9704 unclipArea: function(ctx) {
9705 ctx.restore();
9706 },
9707
9708 lineTo: function(ctx, previous, target, flip) {
9709 if (target.steppedLine) {
9710 if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) {
9711 ctx.lineTo(previous.x, target.y);
9712 } else {
9713 ctx.lineTo(target.x, previous.y);
9714 }
9715 ctx.lineTo(target.x, target.y);
9716 return;
9717 }
9718
9719 if (!target.tension) {
9720 ctx.lineTo(target.x, target.y);
9721 return;
9722 }
9723
9724 ctx.bezierCurveTo(
9725 flip ? previous.controlPointPreviousX : previous.controlPointNextX,
9726 flip ? previous.controlPointPreviousY : previous.controlPointNextY,
9727 flip ? target.controlPointNextX : target.controlPointPreviousX,
9728 flip ? target.controlPointNextY : target.controlPointPreviousY,
9729 target.x,
9730 target.y);
9731 }
9732 };
9733
9734 // DEPRECATIONS
9735
9736 /**
9737 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
9738 * @namespace Chart.helpers.clear
9739 * @deprecated since version 2.7.0
9740 * @todo remove at version 3
9741 * @private
9742 */
9743 helpers.clear = exports.clear;
9744
9745 /**
9746 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
9747 * @namespace Chart.helpers.drawRoundedRectangle
9748 * @deprecated since version 2.7.0
9749 * @todo remove at version 3
9750 * @private
9751 */
9752 helpers.drawRoundedRectangle = function(ctx) {
9753 ctx.beginPath();
9754 exports.roundedRect.apply(exports, arguments);
9755 ctx.closePath();
9756 };
9757
9758 },{"42":42}],42:[function(require,module,exports){
9759 'use strict';
9760
9761 /**
9762 * @namespace Chart.helpers
9763 */
9764 var helpers = {
9765 /**
9766 * An empty function that can be used, for example, for optional callback.
9767 */
9768 noop: function() {},
9769
9770 /**
9771 * Returns a unique id, sequentially generated from a global variable.
9772 * @returns {Number}
9773 * @function
9774 */
9775 uid: (function() {
9776 var id = 0;
9777 return function() {
9778 return id++;
9779 };
9780 }()),
9781
9782 /**
9783 * Returns true if `value` is neither null nor undefined, else returns false.
9784 * @param {*} value - The value to test.
9785 * @returns {Boolean}
9786 * @since 2.7.0
9787 */
9788 isNullOrUndef: function(value) {
9789 return value === null || typeof value === 'undefined';
9790 },
9791
9792 /**
9793 * Returns true if `value` is an array, else returns false.
9794 * @param {*} value - The value to test.
9795 * @returns {Boolean}
9796 * @function
9797 */
9798 isArray: Array.isArray ? Array.isArray : function(value) {
9799 return Object.prototype.toString.call(value) === '[object Array]';
9800 },
9801
9802 /**
9803 * Returns true if `value` is an object (excluding null), else returns false.
9804 * @param {*} value - The value to test.
9805 * @returns {Boolean}
9806 * @since 2.7.0
9807 */
9808 isObject: function(value) {
9809 return value !== null && Object.prototype.toString.call(value) === '[object Object]';
9810 },
9811
9812 /**
9813 * Returns `value` if defined, else returns `defaultValue`.
9814 * @param {*} value - The value to return if defined.
9815 * @param {*} defaultValue - The value to return if `value` is undefined.
9816 * @returns {*}
9817 */
9818 valueOrDefault: function(value, defaultValue) {
9819 return typeof value === 'undefined' ? defaultValue : value;
9820 },
9821
9822 /**
9823 * Returns value at the given `index` in array if defined, else returns `defaultValue`.
9824 * @param {Array} value - The array to lookup for value at `index`.
9825 * @param {Number} index - The index in `value` to lookup for value.
9826 * @param {*} defaultValue - The value to return if `value[index]` is undefined.
9827 * @returns {*}
9828 */
9829 valueAtIndexOrDefault: function(value, index, defaultValue) {
9830 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
9831 },
9832
9833 /**
9834 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
9835 * value returned by `fn`. If `fn` is not a function, this method returns undefined.
9836 * @param {Function} fn - The function to call.
9837 * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
9838 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9839 * @returns {*}
9840 */
9841 callback: function(fn, args, thisArg) {
9842 if (fn && typeof fn.call === 'function') {
9843 return fn.apply(thisArg, args);
9844 }
9845 },
9846
9847 /**
9848 * Note(SB) for performance sake, this method should only be used when loopable type
9849 * is unknown or in none intensive code (not called often and small loopable). Else
9850 * it's preferable to use a regular for() loop and save extra function calls.
9851 * @param {Object|Array} loopable - The object or array to be iterated.
9852 * @param {Function} fn - The function to call for each item.
9853 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9854 * @param {Boolean} [reverse] - If true, iterates backward on the loopable.
9855 */
9856 each: function(loopable, fn, thisArg, reverse) {
9857 var i, len, keys;
9858 if (helpers.isArray(loopable)) {
9859 len = loopable.length;
9860 if (reverse) {
9861 for (i = len - 1; i >= 0; i--) {
9862 fn.call(thisArg, loopable[i], i);
9863 }
9864 } else {
9865 for (i = 0; i < len; i++) {
9866 fn.call(thisArg, loopable[i], i);
9867 }
9868 }
9869 } else if (helpers.isObject(loopable)) {
9870 keys = Object.keys(loopable);
9871 len = keys.length;
9872 for (i = 0; i < len; i++) {
9873 fn.call(thisArg, loopable[keys[i]], keys[i]);
9874 }
9875 }
9876 },
9877
9878 /**
9879 * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
9880 * @see http://stackoverflow.com/a/14853974
9881 * @param {Array} a0 - The array to compare
9882 * @param {Array} a1 - The array to compare
9883 * @returns {Boolean}
9884 */
9885 arrayEquals: function(a0, a1) {
9886 var i, ilen, v0, v1;
9887
9888 if (!a0 || !a1 || a0.length !== a1.length) {
9889 return false;
9890 }
9891
9892 for (i = 0, ilen = a0.length; i < ilen; ++i) {
9893 v0 = a0[i];
9894 v1 = a1[i];
9895
9896 if (v0 instanceof Array && v1 instanceof Array) {
9897 if (!helpers.arrayEquals(v0, v1)) {
9898 return false;
9899 }
9900 } else if (v0 !== v1) {
9901 // NOTE: two different object instances will never be equal: {x:20} != {x:20}
9902 return false;
9903 }
9904 }
9905
9906 return true;
9907 },
9908
9909 /**
9910 * Returns a deep copy of `source` without keeping references on objects and arrays.
9911 * @param {*} source - The value to clone.
9912 * @returns {*}
9913 */
9914 clone: function(source) {
9915 if (helpers.isArray(source)) {
9916 return source.map(helpers.clone);
9917 }
9918
9919 if (helpers.isObject(source)) {
9920 var target = {};
9921 var keys = Object.keys(source);
9922 var klen = keys.length;
9923 var k = 0;
9924
9925 for (; k < klen; ++k) {
9926 target[keys[k]] = helpers.clone(source[keys[k]]);
9927 }
9928
9929 return target;
9930 }
9931
9932 return source;
9933 },
9934
9935 /**
9936 * The default merger when Chart.helpers.merge is called without merger option.
9937 * Note(SB): this method is also used by configMerge and scaleMerge as fallback.
9938 * @private
9939 */
9940 _merger: function(key, target, source, options) {
9941 var tval = target[key];
9942 var sval = source[key];
9943
9944 if (helpers.isObject(tval) && helpers.isObject(sval)) {
9945 helpers.merge(tval, sval, options);
9946 } else {
9947 target[key] = helpers.clone(sval);
9948 }
9949 },
9950
9951 /**
9952 * Merges source[key] in target[key] only if target[key] is undefined.
9953 * @private
9954 */
9955 _mergerIf: function(key, target, source) {
9956 var tval = target[key];
9957 var sval = source[key];
9958
9959 if (helpers.isObject(tval) && helpers.isObject(sval)) {
9960 helpers.mergeIf(tval, sval);
9961 } else if (!target.hasOwnProperty(key)) {
9962 target[key] = helpers.clone(sval);
9963 }
9964 },
9965
9966 /**
9967 * Recursively deep copies `source` properties into `target` with the given `options`.
9968 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
9969 * @param {Object} target - The target object in which all sources are merged into.
9970 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
9971 * @param {Object} [options] - Merging options:
9972 * @param {Function} [options.merger] - The merge method (key, target, source, options)
9973 * @returns {Object} The `target` object.
9974 */
9975 merge: function(target, source, options) {
9976 var sources = helpers.isArray(source) ? source : [source];
9977 var ilen = sources.length;
9978 var merge, i, keys, klen, k;
9979
9980 if (!helpers.isObject(target)) {
9981 return target;
9982 }
9983
9984 options = options || {};
9985 merge = options.merger || helpers._merger;
9986
9987 for (i = 0; i < ilen; ++i) {
9988 source = sources[i];
9989 if (!helpers.isObject(source)) {
9990 continue;
9991 }
9992
9993 keys = Object.keys(source);
9994 for (k = 0, klen = keys.length; k < klen; ++k) {
9995 merge(keys[k], target, source, options);
9996 }
9997 }
9998
9999 return target;
10000 },
10001
10002 /**
10003 * Recursively deep copies `source` properties into `target` *only* if not defined in target.
10004 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
10005 * @param {Object} target - The target object in which all sources are merged into.
10006 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
10007 * @returns {Object} The `target` object.
10008 */
10009 mergeIf: function(target, source) {
10010 return helpers.merge(target, source, {merger: helpers._mergerIf});
10011 },
10012
10013 /**
10014 * Applies the contents of two or more objects together into the first object.
10015 * @param {Object} target - The target object in which all objects are merged into.
10016 * @param {Object} arg1 - Object containing additional properties to merge in target.
10017 * @param {Object} argN - Additional objects containing properties to merge in target.
10018 * @returns {Object} The `target` object.
10019 */
10020 extend: function(target) {
10021 var setFn = function(value, key) {
10022 target[key] = value;
10023 };
10024 for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
10025 helpers.each(arguments[i], setFn);
10026 }
10027 return target;
10028 },
10029
10030 /**
10031 * Basic javascript inheritance based on the model created in Backbone.js
10032 */
10033 inherits: function(extensions) {
10034 var me = this;
10035 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
10036 return me.apply(this, arguments);
10037 };
10038
10039 var Surrogate = function() {
10040 this.constructor = ChartElement;
10041 };
10042
10043 Surrogate.prototype = me.prototype;
10044 ChartElement.prototype = new Surrogate();
10045 ChartElement.extend = helpers.inherits;
10046
10047 if (extensions) {
10048 helpers.extend(ChartElement.prototype, extensions);
10049 }
10050
10051 ChartElement.__super__ = me.prototype;
10052 return ChartElement;
10053 }
10054 };
10055
10056 module.exports = helpers;
10057
10058 // DEPRECATIONS
10059
10060 /**
10061 * Provided for backward compatibility, use Chart.helpers.callback instead.
10062 * @function Chart.helpers.callCallback
10063 * @deprecated since version 2.6.0
10064 * @todo remove at version 3
10065 * @private
10066 */
10067 helpers.callCallback = helpers.callback;
10068
10069 /**
10070 * Provided for backward compatibility, use Array.prototype.indexOf instead.
10071 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
10072 * @function Chart.helpers.indexOf
10073 * @deprecated since version 2.7.0
10074 * @todo remove at version 3
10075 * @private
10076 */
10077 helpers.indexOf = function(array, item, fromIndex) {
10078 return Array.prototype.indexOf.call(array, item, fromIndex);
10079 };
10080
10081 /**
10082 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
10083 * @function Chart.helpers.getValueOrDefault
10084 * @deprecated since version 2.7.0
10085 * @todo remove at version 3
10086 * @private
10087 */
10088 helpers.getValueOrDefault = helpers.valueOrDefault;
10089
10090 /**
10091 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
10092 * @function Chart.helpers.getValueAtIndexOrDefault
10093 * @deprecated since version 2.7.0
10094 * @todo remove at version 3
10095 * @private
10096 */
10097 helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
10098
10099 },{}],43:[function(require,module,exports){
10100 'use strict';
10101
10102 var helpers = require(42);
10103
10104 /**
10105 * Easing functions adapted from Robert Penner's easing equations.
10106 * @namespace Chart.helpers.easingEffects
10107 * @see http://www.robertpenner.com/easing/
10108 */
10109 var effects = {
10110 linear: function(t) {
10111 return t;
10112 },
10113
10114 easeInQuad: function(t) {
10115 return t * t;
10116 },
10117
10118 easeOutQuad: function(t) {
10119 return -t * (t - 2);
10120 },
10121
10122 easeInOutQuad: function(t) {
10123 if ((t /= 0.5) < 1) {
10124 return 0.5 * t * t;
10125 }
10126 return -0.5 * ((--t) * (t - 2) - 1);
10127 },
10128
10129 easeInCubic: function(t) {
10130 return t * t * t;
10131 },
10132
10133 easeOutCubic: function(t) {
10134 return (t = t - 1) * t * t + 1;
10135 },
10136
10137 easeInOutCubic: function(t) {
10138 if ((t /= 0.5) < 1) {
10139 return 0.5 * t * t * t;
10140 }
10141 return 0.5 * ((t -= 2) * t * t + 2);
10142 },
10143
10144 easeInQuart: function(t) {
10145 return t * t * t * t;
10146 },
10147
10148 easeOutQuart: function(t) {
10149 return -((t = t - 1) * t * t * t - 1);
10150 },
10151
10152 easeInOutQuart: function(t) {
10153 if ((t /= 0.5) < 1) {
10154 return 0.5 * t * t * t * t;
10155 }
10156 return -0.5 * ((t -= 2) * t * t * t - 2);
10157 },
10158
10159 easeInQuint: function(t) {
10160 return t * t * t * t * t;
10161 },
10162
10163 easeOutQuint: function(t) {
10164 return (t = t - 1) * t * t * t * t + 1;
10165 },
10166
10167 easeInOutQuint: function(t) {
10168 if ((t /= 0.5) < 1) {
10169 return 0.5 * t * t * t * t * t;
10170 }
10171 return 0.5 * ((t -= 2) * t * t * t * t + 2);
10172 },
10173
10174 easeInSine: function(t) {
10175 return -Math.cos(t * (Math.PI / 2)) + 1;
10176 },
10177
10178 easeOutSine: function(t) {
10179 return Math.sin(t * (Math.PI / 2));
10180 },
10181
10182 easeInOutSine: function(t) {
10183 return -0.5 * (Math.cos(Math.PI * t) - 1);
10184 },
10185
10186 easeInExpo: function(t) {
10187 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
10188 },
10189
10190 easeOutExpo: function(t) {
10191 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
10192 },
10193
10194 easeInOutExpo: function(t) {
10195 if (t === 0) {
10196 return 0;
10197 }
10198 if (t === 1) {
10199 return 1;
10200 }
10201 if ((t /= 0.5) < 1) {
10202 return 0.5 * Math.pow(2, 10 * (t - 1));
10203 }
10204 return 0.5 * (-Math.pow(2, -10 * --t) + 2);
10205 },
10206
10207 easeInCirc: function(t) {
10208 if (t >= 1) {
10209 return t;
10210 }
10211 return -(Math.sqrt(1 - t * t) - 1);
10212 },
10213
10214 easeOutCirc: function(t) {
10215 return Math.sqrt(1 - (t = t - 1) * t);
10216 },
10217
10218 easeInOutCirc: function(t) {
10219 if ((t /= 0.5) < 1) {
10220 return -0.5 * (Math.sqrt(1 - t * t) - 1);
10221 }
10222 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
10223 },
10224
10225 easeInElastic: function(t) {
10226 var s = 1.70158;
10227 var p = 0;
10228 var a = 1;
10229 if (t === 0) {
10230 return 0;
10231 }
10232 if (t === 1) {
10233 return 1;
10234 }
10235 if (!p) {
10236 p = 0.3;
10237 }
10238 if (a < 1) {
10239 a = 1;
10240 s = p / 4;
10241 } else {
10242 s = p / (2 * Math.PI) * Math.asin(1 / a);
10243 }
10244 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10245 },
10246
10247 easeOutElastic: function(t) {
10248 var s = 1.70158;
10249 var p = 0;
10250 var a = 1;
10251 if (t === 0) {
10252 return 0;
10253 }
10254 if (t === 1) {
10255 return 1;
10256 }
10257 if (!p) {
10258 p = 0.3;
10259 }
10260 if (a < 1) {
10261 a = 1;
10262 s = p / 4;
10263 } else {
10264 s = p / (2 * Math.PI) * Math.asin(1 / a);
10265 }
10266 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
10267 },
10268
10269 easeInOutElastic: function(t) {
10270 var s = 1.70158;
10271 var p = 0;
10272 var a = 1;
10273 if (t === 0) {
10274 return 0;
10275 }
10276 if ((t /= 0.5) === 2) {
10277 return 1;
10278 }
10279 if (!p) {
10280 p = 0.45;
10281 }
10282 if (a < 1) {
10283 a = 1;
10284 s = p / 4;
10285 } else {
10286 s = p / (2 * Math.PI) * Math.asin(1 / a);
10287 }
10288 if (t < 1) {
10289 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10290 }
10291 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
10292 },
10293 easeInBack: function(t) {
10294 var s = 1.70158;
10295 return t * t * ((s + 1) * t - s);
10296 },
10297
10298 easeOutBack: function(t) {
10299 var s = 1.70158;
10300 return (t = t - 1) * t * ((s + 1) * t + s) + 1;
10301 },
10302
10303 easeInOutBack: function(t) {
10304 var s = 1.70158;
10305 if ((t /= 0.5) < 1) {
10306 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
10307 }
10308 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
10309 },
10310
10311 easeInBounce: function(t) {
10312 return 1 - effects.easeOutBounce(1 - t);
10313 },
10314
10315 easeOutBounce: function(t) {
10316 if (t < (1 / 2.75)) {
10317 return 7.5625 * t * t;
10318 }
10319 if (t < (2 / 2.75)) {
10320 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
10321 }
10322 if (t < (2.5 / 2.75)) {
10323 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
10324 }
10325 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
10326 },
10327
10328 easeInOutBounce: function(t) {
10329 if (t < 0.5) {
10330 return effects.easeInBounce(t * 2) * 0.5;
10331 }
10332 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
10333 }
10334 };
10335
10336 module.exports = {
10337 effects: effects
10338 };
10339
10340 // DEPRECATIONS
10341
10342 /**
10343 * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
10344 * @function Chart.helpers.easingEffects
10345 * @deprecated since version 2.7.0
10346 * @todo remove at version 3
10347 * @private
10348 */
10349 helpers.easingEffects = effects;
10350
10351 },{"42":42}],44:[function(require,module,exports){
10352 'use strict';
10353
10354 var helpers = require(42);
10355
10356 /**
10357 * @alias Chart.helpers.options
10358 * @namespace
10359 */
10360 module.exports = {
10361 /**
10362 * Converts the given line height `value` in pixels for a specific font `size`.
10363 * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
10364 * @param {Number} size - The font size (in pixels) used to resolve relative `value`.
10365 * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid).
10366 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
10367 * @since 2.7.0
10368 */
10369 toLineHeight: function(value, size) {
10370 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
10371 if (!matches || matches[1] === 'normal') {
10372 return size * 1.2;
10373 }
10374
10375 value = +matches[2];
10376
10377 switch (matches[3]) {
10378 case 'px':
10379 return value;
10380 case '%':
10381 value /= 100;
10382 break;
10383 default:
10384 break;
10385 }
10386
10387 return size * value;
10388 },
10389
10390 /**
10391 * Converts the given value into a padding object with pre-computed width/height.
10392 * @param {Number|Object} value - If a number, set the value to all TRBL component,
10393 * else, if and object, use defined properties and sets undefined ones to 0.
10394 * @returns {Object} The padding values (top, right, bottom, left, width, height)
10395 * @since 2.7.0
10396 */
10397 toPadding: function(value) {
10398 var t, r, b, l;
10399
10400 if (helpers.isObject(value)) {
10401 t = +value.top || 0;
10402 r = +value.right || 0;
10403 b = +value.bottom || 0;
10404 l = +value.left || 0;
10405 } else {
10406 t = r = b = l = +value || 0;
10407 }
10408
10409 return {
10410 top: t,
10411 right: r,
10412 bottom: b,
10413 left: l,
10414 height: t + b,
10415 width: l + r
10416 };
10417 },
10418
10419 /**
10420 * Evaluates the given `inputs` sequentially and returns the first defined value.
10421 * @param {Array[]} inputs - An array of values, falling back to the last value.
10422 * @param {Object} [context] - If defined and the current value is a function, the value
10423 * is called with `context` as first argument and the result becomes the new input.
10424 * @param {Number} [index] - If defined and the current value is an array, the value
10425 * at `index` become the new input.
10426 * @since 2.7.0
10427 */
10428 resolve: function(inputs, context, index) {
10429 var i, ilen, value;
10430
10431 for (i = 0, ilen = inputs.length; i < ilen; ++i) {
10432 value = inputs[i];
10433 if (value === undefined) {
10434 continue;
10435 }
10436 if (context !== undefined && typeof value === 'function') {
10437 value = value(context);
10438 }
10439 if (index !== undefined && helpers.isArray(value)) {
10440 value = value[index];
10441 }
10442 if (value !== undefined) {
10443 return value;
10444 }
10445 }
10446 }
10447 };
10448
10449 },{"42":42}],45:[function(require,module,exports){
10450 'use strict';
10451
10452 module.exports = require(42);
10453 module.exports.easing = require(43);
10454 module.exports.canvas = require(41);
10455 module.exports.options = require(44);
10456
10457 },{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){
10458 /**
10459 * Platform fallback implementation (minimal).
10460 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
10461 */
10462
10463 module.exports = {
10464 acquireContext: function(item) {
10465 if (item && item.canvas) {
10466 // Support for any object associated to a canvas (including a context2d)
10467 item = item.canvas;
10468 }
10469
10470 return item && item.getContext('2d') || null;
10471 }
10472 };
10473
10474 },{}],47:[function(require,module,exports){
10475 /**
10476 * Chart.Platform implementation for targeting a web browser
10477 */
10478
10479 'use strict';
10480
10481 var helpers = require(45);
10482
10483 var EXPANDO_KEY = '$chartjs';
10484 var CSS_PREFIX = 'chartjs-';
10485 var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
10486 var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
10487 var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
10488
10489 /**
10490 * DOM event types -> Chart.js event types.
10491 * Note: only events with different types are mapped.
10492 * @see https://developer.mozilla.org/en-US/docs/Web/Events
10493 */
10494 var EVENT_TYPES = {
10495 touchstart: 'mousedown',
10496 touchmove: 'mousemove',
10497 touchend: 'mouseup',
10498 pointerenter: 'mouseenter',
10499 pointerdown: 'mousedown',
10500 pointermove: 'mousemove',
10501 pointerup: 'mouseup',
10502 pointerleave: 'mouseout',
10503 pointerout: 'mouseout'
10504 };
10505
10506 /**
10507 * The "used" size is the final value of a dimension property after all calculations have
10508 * been performed. This method uses the computed style of `element` but returns undefined
10509 * if the computed style is not expressed in pixels. That can happen in some cases where
10510 * `element` has a size relative to its parent and this last one is not yet displayed,
10511 * for example because of `display: none` on a parent node.
10512 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
10513 * @returns {Number} Size in pixels or undefined if unknown.
10514 */
10515 function readUsedSize(element, property) {
10516 var value = helpers.getStyle(element, property);
10517 var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
10518 return matches ? Number(matches[1]) : undefined;
10519 }
10520
10521 /**
10522 * Initializes the canvas style and render size without modifying the canvas display size,
10523 * since responsiveness is handled by the controller.resize() method. The config is used
10524 * to determine the aspect ratio to apply in case no explicit height has been specified.
10525 */
10526 function initCanvas(canvas, config) {
10527 var style = canvas.style;
10528
10529 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
10530 // returns null or '' if no explicit value has been set to the canvas attribute.
10531 var renderHeight = canvas.getAttribute('height');
10532 var renderWidth = canvas.getAttribute('width');
10533
10534 // Chart.js modifies some canvas values that we want to restore on destroy
10535 canvas[EXPANDO_KEY] = {
10536 initial: {
10537 height: renderHeight,
10538 width: renderWidth,
10539 style: {
10540 display: style.display,
10541 height: style.height,
10542 width: style.width
10543 }
10544 }
10545 };
10546
10547 // Force canvas to display as block to avoid extra space caused by inline
10548 // elements, which would interfere with the responsive resize process.
10549 // https://github.com/chartjs/Chart.js/issues/2538
10550 style.display = style.display || 'block';
10551
10552 if (renderWidth === null || renderWidth === '') {
10553 var displayWidth = readUsedSize(canvas, 'width');
10554 if (displayWidth !== undefined) {
10555 canvas.width = displayWidth;
10556 }
10557 }
10558
10559 if (renderHeight === null || renderHeight === '') {
10560 if (canvas.style.height === '') {
10561 // If no explicit render height and style height, let's apply the aspect ratio,
10562 // which one can be specified by the user but also by charts as default option
10563 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
10564 canvas.height = canvas.width / (config.options.aspectRatio || 2);
10565 } else {
10566 var displayHeight = readUsedSize(canvas, 'height');
10567 if (displayWidth !== undefined) {
10568 canvas.height = displayHeight;
10569 }
10570 }
10571 }
10572
10573 return canvas;
10574 }
10575
10576 /**
10577 * Detects support for options object argument in addEventListener.
10578 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
10579 * @private
10580 */
10581 var supportsEventListenerOptions = (function() {
10582 var supports = false;
10583 try {
10584 var options = Object.defineProperty({}, 'passive', {
10585 get: function() {
10586 supports = true;
10587 }
10588 });
10589 window.addEventListener('e', null, options);
10590 } catch (e) {
10591 // continue regardless of error
10592 }
10593 return supports;
10594 }());
10595
10596 // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
10597 // https://github.com/chartjs/Chart.js/issues/4287
10598 var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
10599
10600 function addEventListener(node, type, listener) {
10601 node.addEventListener(type, listener, eventListenerOptions);
10602 }
10603
10604 function removeEventListener(node, type, listener) {
10605 node.removeEventListener(type, listener, eventListenerOptions);
10606 }
10607
10608 function createEvent(type, chart, x, y, nativeEvent) {
10609 return {
10610 type: type,
10611 chart: chart,
10612 native: nativeEvent || null,
10613 x: x !== undefined ? x : null,
10614 y: y !== undefined ? y : null,
10615 };
10616 }
10617
10618 function fromNativeEvent(event, chart) {
10619 var type = EVENT_TYPES[event.type] || event.type;
10620 var pos = helpers.getRelativePosition(event, chart);
10621 return createEvent(type, chart, pos.x, pos.y, event);
10622 }
10623
10624 function throttled(fn, thisArg) {
10625 var ticking = false;
10626 var args = [];
10627
10628 return function() {
10629 args = Array.prototype.slice.call(arguments);
10630 thisArg = thisArg || this;
10631
10632 if (!ticking) {
10633 ticking = true;
10634 helpers.requestAnimFrame.call(window, function() {
10635 ticking = false;
10636 fn.apply(thisArg, args);
10637 });
10638 }
10639 };
10640 }
10641
10642 // Implementation based on https://github.com/marcj/css-element-queries
10643 function createResizer(handler) {
10644 var resizer = document.createElement('div');
10645 var cls = CSS_PREFIX + 'size-monitor';
10646 var maxSize = 1000000;
10647 var style =
10648 'position:absolute;' +
10649 'left:0;' +
10650 'top:0;' +
10651 'right:0;' +
10652 'bottom:0;' +
10653 'overflow:hidden;' +
10654 'pointer-events:none;' +
10655 'visibility:hidden;' +
10656 'z-index:-1;';
10657
10658 resizer.style.cssText = style;
10659 resizer.className = cls;
10660 resizer.innerHTML =
10661 '<div class="' + cls + '-expand" style="' + style + '">' +
10662 '<div style="' +
10663 'position:absolute;' +
10664 'width:' + maxSize + 'px;' +
10665 'height:' + maxSize + 'px;' +
10666 'left:0;' +
10667 'top:0">' +
10668 '</div>' +
10669 '</div>' +
10670 '<div class="' + cls + '-shrink" style="' + style + '">' +
10671 '<div style="' +
10672 'position:absolute;' +
10673 'width:200%;' +
10674 'height:200%;' +
10675 'left:0; ' +
10676 'top:0">' +
10677 '</div>' +
10678 '</div>';
10679
10680 var expand = resizer.childNodes[0];
10681 var shrink = resizer.childNodes[1];
10682
10683 resizer._reset = function() {
10684 expand.scrollLeft = maxSize;
10685 expand.scrollTop = maxSize;
10686 shrink.scrollLeft = maxSize;
10687 shrink.scrollTop = maxSize;
10688 };
10689 var onScroll = function() {
10690 resizer._reset();
10691 handler();
10692 };
10693
10694 addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
10695 addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
10696
10697 return resizer;
10698 }
10699
10700 // https://davidwalsh.name/detect-node-insertion
10701 function watchForRender(node, handler) {
10702 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10703 var proxy = expando.renderProxy = function(e) {
10704 if (e.animationName === CSS_RENDER_ANIMATION) {
10705 handler();
10706 }
10707 };
10708
10709 helpers.each(ANIMATION_START_EVENTS, function(type) {
10710 addEventListener(node, type, proxy);
10711 });
10712
10713 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
10714 // is removed then added back immediately (same animation frame?). Accessing the
10715 // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
10716 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
10717 // https://github.com/chartjs/Chart.js/issues/4737
10718 expando.reflow = !!node.offsetParent;
10719
10720 node.classList.add(CSS_RENDER_MONITOR);
10721 }
10722
10723 function unwatchForRender(node) {
10724 var expando = node[EXPANDO_KEY] || {};
10725 var proxy = expando.renderProxy;
10726
10727 if (proxy) {
10728 helpers.each(ANIMATION_START_EVENTS, function(type) {
10729 removeEventListener(node, type, proxy);
10730 });
10731
10732 delete expando.renderProxy;
10733 }
10734
10735 node.classList.remove(CSS_RENDER_MONITOR);
10736 }
10737
10738 function addResizeListener(node, listener, chart) {
10739 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10740
10741 // Let's keep track of this added resizer and thus avoid DOM query when removing it.
10742 var resizer = expando.resizer = createResizer(throttled(function() {
10743 if (expando.resizer) {
10744 return listener(createEvent('resize', chart));
10745 }
10746 }));
10747
10748 // The resizer needs to be attached to the node parent, so we first need to be
10749 // sure that `node` is attached to the DOM before injecting the resizer element.
10750 watchForRender(node, function() {
10751 if (expando.resizer) {
10752 var container = node.parentNode;
10753 if (container && container !== resizer.parentNode) {
10754 container.insertBefore(resizer, container.firstChild);
10755 }
10756
10757 // The container size might have changed, let's reset the resizer state.
10758 resizer._reset();
10759 }
10760 });
10761 }
10762
10763 function removeResizeListener(node) {
10764 var expando = node[EXPANDO_KEY] || {};
10765 var resizer = expando.resizer;
10766
10767 delete expando.resizer;
10768 unwatchForRender(node);
10769
10770 if (resizer && resizer.parentNode) {
10771 resizer.parentNode.removeChild(resizer);
10772 }
10773 }
10774
10775 function injectCSS(platform, css) {
10776 // http://stackoverflow.com/q/3922139
10777 var style = platform._style || document.createElement('style');
10778 if (!platform._style) {
10779 platform._style = style;
10780 css = '/* Chart.js */\n' + css;
10781 style.setAttribute('type', 'text/css');
10782 document.getElementsByTagName('head')[0].appendChild(style);
10783 }
10784
10785 style.appendChild(document.createTextNode(css));
10786 }
10787
10788 module.exports = {
10789 /**
10790 * This property holds whether this platform is enabled for the current environment.
10791 * Currently used by platform.js to select the proper implementation.
10792 * @private
10793 */
10794 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
10795
10796 initialize: function() {
10797 var keyframes = 'from{opacity:0.99}to{opacity:1}';
10798
10799 injectCSS(this,
10800 // DOM rendering detection
10801 // https://davidwalsh.name/detect-node-insertion
10802 '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10803 '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10804 '.' + CSS_RENDER_MONITOR + '{' +
10805 '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10806 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10807 '}'
10808 );
10809 },
10810
10811 acquireContext: function(item, config) {
10812 if (typeof item === 'string') {
10813 item = document.getElementById(item);
10814 } else if (item.length) {
10815 // Support for array based queries (such as jQuery)
10816 item = item[0];
10817 }
10818
10819 if (item && item.canvas) {
10820 // Support for any object associated to a canvas (including a context2d)
10821 item = item.canvas;
10822 }
10823
10824 // To prevent canvas fingerprinting, some add-ons undefine the getContext
10825 // method, for example: https://github.com/kkapsner/CanvasBlocker
10826 // https://github.com/chartjs/Chart.js/issues/2807
10827 var context = item && item.getContext && item.getContext('2d');
10828
10829 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
10830 // inside an iframe or when running in a protected environment. We could guess the
10831 // types from their toString() value but let's keep things flexible and assume it's
10832 // a sufficient condition if the item has a context2D which has item as `canvas`.
10833 // https://github.com/chartjs/Chart.js/issues/3887
10834 // https://github.com/chartjs/Chart.js/issues/4102
10835 // https://github.com/chartjs/Chart.js/issues/4152
10836 if (context && context.canvas === item) {
10837 initCanvas(item, config);
10838 return context;
10839 }
10840
10841 return null;
10842 },
10843
10844 releaseContext: function(context) {
10845 var canvas = context.canvas;
10846 if (!canvas[EXPANDO_KEY]) {
10847 return;
10848 }
10849
10850 var initial = canvas[EXPANDO_KEY].initial;
10851 ['height', 'width'].forEach(function(prop) {
10852 var value = initial[prop];
10853 if (helpers.isNullOrUndef(value)) {
10854 canvas.removeAttribute(prop);
10855 } else {
10856 canvas.setAttribute(prop, value);
10857 }
10858 });
10859
10860 helpers.each(initial.style || {}, function(value, key) {
10861 canvas.style[key] = value;
10862 });
10863
10864 // The canvas render size might have been changed (and thus the state stack discarded),
10865 // we can't use save() and restore() to restore the initial state. So make sure that at
10866 // least the canvas context is reset to the default state by setting the canvas width.
10867 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
10868 canvas.width = canvas.width;
10869
10870 delete canvas[EXPANDO_KEY];
10871 },
10872
10873 addEventListener: function(chart, type, listener) {
10874 var canvas = chart.canvas;
10875 if (type === 'resize') {
10876 // Note: the resize event is not supported on all browsers.
10877 addResizeListener(canvas, listener, chart);
10878 return;
10879 }
10880
10881 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
10882 var proxies = expando.proxies || (expando.proxies = {});
10883 var proxy = proxies[chart.id + '_' + type] = function(event) {
10884 listener(fromNativeEvent(event, chart));
10885 };
10886
10887 addEventListener(canvas, type, proxy);
10888 },
10889
10890 removeEventListener: function(chart, type, listener) {
10891 var canvas = chart.canvas;
10892 if (type === 'resize') {
10893 // Note: the resize event is not supported on all browsers.
10894 removeResizeListener(canvas, listener);
10895 return;
10896 }
10897
10898 var expando = listener[EXPANDO_KEY] || {};
10899 var proxies = expando.proxies || {};
10900 var proxy = proxies[chart.id + '_' + type];
10901 if (!proxy) {
10902 return;
10903 }
10904
10905 removeEventListener(canvas, type, proxy);
10906 }
10907 };
10908
10909 // DEPRECATIONS
10910
10911 /**
10912 * Provided for backward compatibility, use EventTarget.addEventListener instead.
10913 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10914 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
10915 * @function Chart.helpers.addEvent
10916 * @deprecated since version 2.7.0
10917 * @todo remove at version 3
10918 * @private
10919 */
10920 helpers.addEvent = addEventListener;
10921
10922 /**
10923 * Provided for backward compatibility, use EventTarget.removeEventListener instead.
10924 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10925 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
10926 * @function Chart.helpers.removeEvent
10927 * @deprecated since version 2.7.0
10928 * @todo remove at version 3
10929 * @private
10930 */
10931 helpers.removeEvent = removeEventListener;
10932
10933 },{"45":45}],48:[function(require,module,exports){
10934 'use strict';
10935
10936 var helpers = require(45);
10937 var basic = require(46);
10938 var dom = require(47);
10939
10940 // @TODO Make possible to select another platform at build time.
10941 var implementation = dom._enabled ? dom : basic;
10942
10943 /**
10944 * @namespace Chart.platform
10945 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
10946 * @since 2.4.0
10947 */
10948 module.exports = helpers.extend({
10949 /**
10950 * @since 2.7.0
10951 */
10952 initialize: function() {},
10953
10954 /**
10955 * Called at chart construction time, returns a context2d instance implementing
10956 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
10957 * @param {*} item - The native item from which to acquire context (platform specific)
10958 * @param {Object} options - The chart options
10959 * @returns {CanvasRenderingContext2D} context2d instance
10960 */
10961 acquireContext: function() {},
10962
10963 /**
10964 * Called at chart destruction time, releases any resources associated to the context
10965 * previously returned by the acquireContext() method.
10966 * @param {CanvasRenderingContext2D} context - The context2d instance
10967 * @returns {Boolean} true if the method succeeded, else false
10968 */
10969 releaseContext: function() {},
10970
10971 /**
10972 * Registers the specified listener on the given chart.
10973 * @param {Chart} chart - Chart from which to listen for event
10974 * @param {String} type - The ({@link IEvent}) type to listen for
10975 * @param {Function} listener - Receives a notification (an object that implements
10976 * the {@link IEvent} interface) when an event of the specified type occurs.
10977 */
10978 addEventListener: function() {},
10979
10980 /**
10981 * Removes the specified listener previously registered with addEventListener.
10982 * @param {Chart} chart -Chart from which to remove the listener
10983 * @param {String} type - The ({@link IEvent}) type to remove
10984 * @param {Function} listener - The listener function to remove from the event target.
10985 */
10986 removeEventListener: function() {}
10987
10988 }, implementation);
10989
10990 /**
10991 * @interface IPlatform
10992 * Allows abstracting platform dependencies away from the chart
10993 * @borrows Chart.platform.acquireContext as acquireContext
10994 * @borrows Chart.platform.releaseContext as releaseContext
10995 * @borrows Chart.platform.addEventListener as addEventListener
10996 * @borrows Chart.platform.removeEventListener as removeEventListener
10997 */
10998
10999 /**
11000 * @interface IEvent
11001 * @prop {String} type - The event type name, possible values are:
11002 * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',
11003 * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'
11004 * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')
11005 * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)
11006 * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)
11007 */
11008
11009 },{"45":45,"46":46,"47":47}],49:[function(require,module,exports){
11010 /**
11011 * Plugin based on discussion from the following Chart.js issues:
11012 * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569
11013 * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897
11014 */
11015
11016 'use strict';
11017
11018 var defaults = require(25);
11019 var elements = require(40);
11020 var helpers = require(45);
11021
11022 defaults._set('global', {
11023 plugins: {
11024 filler: {
11025 propagate: true
11026 }
11027 }
11028 });
11029
11030 module.exports = function() {
11031
11032 var mappers = {
11033 dataset: function(source) {
11034 var index = source.fill;
11035 var chart = source.chart;
11036 var meta = chart.getDatasetMeta(index);
11037 var visible = meta && chart.isDatasetVisible(index);
11038 var points = (visible && meta.dataset._children) || [];
11039 var length = points.length || 0;
11040
11041 return !length ? null : function(point, i) {
11042 return (i < length && points[i]._view) || null;
11043 };
11044 },
11045
11046 boundary: function(source) {
11047 var boundary = source.boundary;
11048 var x = boundary ? boundary.x : null;
11049 var y = boundary ? boundary.y : null;
11050
11051 return function(point) {
11052 return {
11053 x: x === null ? point.x : x,
11054 y: y === null ? point.y : y,
11055 };
11056 };
11057 }
11058 };
11059
11060 // @todo if (fill[0] === '#')
11061 function decodeFill(el, index, count) {
11062 var model = el._model || {};
11063 var fill = model.fill;
11064 var target;
11065
11066 if (fill === undefined) {
11067 fill = !!model.backgroundColor;
11068 }
11069
11070 if (fill === false || fill === null) {
11071 return false;
11072 }
11073
11074 if (fill === true) {
11075 return 'origin';
11076 }
11077
11078 target = parseFloat(fill, 10);
11079 if (isFinite(target) && Math.floor(target) === target) {
11080 if (fill[0] === '-' || fill[0] === '+') {
11081 target = index + target;
11082 }
11083
11084 if (target === index || target < 0 || target >= count) {
11085 return false;
11086 }
11087
11088 return target;
11089 }
11090
11091 switch (fill) {
11092 // compatibility
11093 case 'bottom':
11094 return 'start';
11095 case 'top':
11096 return 'end';
11097 case 'zero':
11098 return 'origin';
11099 // supported boundaries
11100 case 'origin':
11101 case 'start':
11102 case 'end':
11103 return fill;
11104 // invalid fill values
11105 default:
11106 return false;
11107 }
11108 }
11109
11110 function computeBoundary(source) {
11111 var model = source.el._model || {};
11112 var scale = source.el._scale || {};
11113 var fill = source.fill;
11114 var target = null;
11115 var horizontal;
11116
11117 if (isFinite(fill)) {
11118 return null;
11119 }
11120
11121 // Backward compatibility: until v3, we still need to support boundary values set on
11122 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
11123 // controllers might still use it (e.g. the Smith chart).
11124
11125 if (fill === 'start') {
11126 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
11127 } else if (fill === 'end') {
11128 target = model.scaleTop === undefined ? scale.top : model.scaleTop;
11129 } else if (model.scaleZero !== undefined) {
11130 target = model.scaleZero;
11131 } else if (scale.getBasePosition) {
11132 target = scale.getBasePosition();
11133 } else if (scale.getBasePixel) {
11134 target = scale.getBasePixel();
11135 }
11136
11137 if (target !== undefined && target !== null) {
11138 if (target.x !== undefined && target.y !== undefined) {
11139 return target;
11140 }
11141
11142 if (typeof target === 'number' && isFinite(target)) {
11143 horizontal = scale.isHorizontal();
11144 return {
11145 x: horizontal ? target : null,
11146 y: horizontal ? null : target
11147 };
11148 }
11149 }
11150
11151 return null;
11152 }
11153
11154 function resolveTarget(sources, index, propagate) {
11155 var source = sources[index];
11156 var fill = source.fill;
11157 var visited = [index];
11158 var target;
11159
11160 if (!propagate) {
11161 return fill;
11162 }
11163
11164 while (fill !== false && visited.indexOf(fill) === -1) {
11165 if (!isFinite(fill)) {
11166 return fill;
11167 }
11168
11169 target = sources[fill];
11170 if (!target) {
11171 return false;
11172 }
11173
11174 if (target.visible) {
11175 return fill;
11176 }
11177
11178 visited.push(fill);
11179 fill = target.fill;
11180 }
11181
11182 return false;
11183 }
11184
11185 function createMapper(source) {
11186 var fill = source.fill;
11187 var type = 'dataset';
11188
11189 if (fill === false) {
11190 return null;
11191 }
11192
11193 if (!isFinite(fill)) {
11194 type = 'boundary';
11195 }
11196
11197 return mappers[type](source);
11198 }
11199
11200 function isDrawable(point) {
11201 return point && !point.skip;
11202 }
11203
11204 function drawArea(ctx, curve0, curve1, len0, len1) {
11205 var i;
11206
11207 if (!len0 || !len1) {
11208 return;
11209 }
11210
11211 // building first area curve (normal)
11212 ctx.moveTo(curve0[0].x, curve0[0].y);
11213 for (i = 1; i < len0; ++i) {
11214 helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
11215 }
11216
11217 // joining the two area curves
11218 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
11219
11220 // building opposite area curve (reverse)
11221 for (i = len1 - 1; i > 0; --i) {
11222 helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
11223 }
11224 }
11225
11226 function doFill(ctx, points, mapper, view, color, loop) {
11227 var count = points.length;
11228 var span = view.spanGaps;
11229 var curve0 = [];
11230 var curve1 = [];
11231 var len0 = 0;
11232 var len1 = 0;
11233 var i, ilen, index, p0, p1, d0, d1;
11234
11235 ctx.beginPath();
11236
11237 for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
11238 index = i % count;
11239 p0 = points[index]._view;
11240 p1 = mapper(p0, index, view);
11241 d0 = isDrawable(p0);
11242 d1 = isDrawable(p1);
11243
11244 if (d0 && d1) {
11245 len0 = curve0.push(p0);
11246 len1 = curve1.push(p1);
11247 } else if (len0 && len1) {
11248 if (!span) {
11249 drawArea(ctx, curve0, curve1, len0, len1);
11250 len0 = len1 = 0;
11251 curve0 = [];
11252 curve1 = [];
11253 } else {
11254 if (d0) {
11255 curve0.push(p0);
11256 }
11257 if (d1) {
11258 curve1.push(p1);
11259 }
11260 }
11261 }
11262 }
11263
11264 drawArea(ctx, curve0, curve1, len0, len1);
11265
11266 ctx.closePath();
11267 ctx.fillStyle = color;
11268 ctx.fill();
11269 }
11270
11271 return {
11272 id: 'filler',
11273
11274 afterDatasetsUpdate: function(chart, options) {
11275 var count = (chart.data.datasets || []).length;
11276 var propagate = options.propagate;
11277 var sources = [];
11278 var meta, i, el, source;
11279
11280 for (i = 0; i < count; ++i) {
11281 meta = chart.getDatasetMeta(i);
11282 el = meta.dataset;
11283 source = null;
11284
11285 if (el && el._model && el instanceof elements.Line) {
11286 source = {
11287 visible: chart.isDatasetVisible(i),
11288 fill: decodeFill(el, i, count),
11289 chart: chart,
11290 el: el
11291 };
11292 }
11293
11294 meta.$filler = source;
11295 sources.push(source);
11296 }
11297
11298 for (i = 0; i < count; ++i) {
11299 source = sources[i];
11300 if (!source) {
11301 continue;
11302 }
11303
11304 source.fill = resolveTarget(sources, i, propagate);
11305 source.boundary = computeBoundary(source);
11306 source.mapper = createMapper(source);
11307 }
11308 },
11309
11310 beforeDatasetDraw: function(chart, args) {
11311 var meta = args.meta.$filler;
11312 if (!meta) {
11313 return;
11314 }
11315
11316 var ctx = chart.ctx;
11317 var el = meta.el;
11318 var view = el._view;
11319 var points = el._children || [];
11320 var mapper = meta.mapper;
11321 var color = view.backgroundColor || defaults.global.defaultColor;
11322
11323 if (mapper && color && points.length) {
11324 helpers.canvas.clipArea(ctx, chart.chartArea);
11325 doFill(ctx, points, mapper, view, color, el._loop);
11326 helpers.canvas.unclipArea(ctx);
11327 }
11328 }
11329 };
11330 };
11331
11332 },{"25":25,"40":40,"45":45}],50:[function(require,module,exports){
11333 'use strict';
11334
11335 var defaults = require(25);
11336 var Element = require(26);
11337 var helpers = require(45);
11338
11339 defaults._set('global', {
11340 legend: {
11341 display: true,
11342 position: 'top',
11343 fullWidth: true,
11344 reverse: false,
11345 weight: 1000,
11346
11347 // a callback that will handle
11348 onClick: function(e, legendItem) {
11349 var index = legendItem.datasetIndex;
11350 var ci = this.chart;
11351 var meta = ci.getDatasetMeta(index);
11352
11353 // See controller.isDatasetVisible comment
11354 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
11355
11356 // We hid a dataset ... rerender the chart
11357 ci.update();
11358 },
11359
11360 onHover: null,
11361
11362 labels: {
11363 boxWidth: 40,
11364 padding: 10,
11365 // Generates labels shown in the legend
11366 // Valid properties to return:
11367 // text : text to display
11368 // fillStyle : fill of coloured box
11369 // strokeStyle: stroke of coloured box
11370 // hidden : if this legend item refers to a hidden item
11371 // lineCap : cap style for line
11372 // lineDash
11373 // lineDashOffset :
11374 // lineJoin :
11375 // lineWidth :
11376 generateLabels: function(chart) {
11377 var data = chart.data;
11378 return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
11379 return {
11380 text: dataset.label,
11381 fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
11382 hidden: !chart.isDatasetVisible(i),
11383 lineCap: dataset.borderCapStyle,
11384 lineDash: dataset.borderDash,
11385 lineDashOffset: dataset.borderDashOffset,
11386 lineJoin: dataset.borderJoinStyle,
11387 lineWidth: dataset.borderWidth,
11388 strokeStyle: dataset.borderColor,
11389 pointStyle: dataset.pointStyle,
11390
11391 // Below is extra data used for toggling the datasets
11392 datasetIndex: i
11393 };
11394 }, this) : [];
11395 }
11396 }
11397 },
11398
11399 legendCallback: function(chart) {
11400 var text = [];
11401 text.push('<ul class="' + chart.id + '-legend">');
11402 for (var i = 0; i < chart.data.datasets.length; i++) {
11403 text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
11404 if (chart.data.datasets[i].label) {
11405 text.push(chart.data.datasets[i].label);
11406 }
11407 text.push('</li>');
11408 }
11409 text.push('</ul>');
11410 return text.join('');
11411 }
11412 });
11413
11414 module.exports = function(Chart) {
11415
11416 var layout = Chart.layoutService;
11417 var noop = helpers.noop;
11418
11419 /**
11420 * Helper function to get the box width based on the usePointStyle option
11421 * @param labelopts {Object} the label options on the legend
11422 * @param fontSize {Number} the label font size
11423 * @return {Number} width of the color box area
11424 */
11425 function getBoxWidth(labelOpts, fontSize) {
11426 return labelOpts.usePointStyle ?
11427 fontSize * Math.SQRT2 :
11428 labelOpts.boxWidth;
11429 }
11430
11431 Chart.Legend = Element.extend({
11432
11433 initialize: function(config) {
11434 helpers.extend(this, config);
11435
11436 // Contains hit boxes for each dataset (in dataset order)
11437 this.legendHitBoxes = [];
11438
11439 // Are we in doughnut mode which has a different data type
11440 this.doughnutMode = false;
11441 },
11442
11443 // These methods are ordered by lifecycle. Utilities then follow.
11444 // Any function defined here is inherited by all legend types.
11445 // Any function can be extended by the legend type
11446
11447 beforeUpdate: noop,
11448 update: function(maxWidth, maxHeight, margins) {
11449 var me = this;
11450
11451 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11452 me.beforeUpdate();
11453
11454 // Absorb the master measurements
11455 me.maxWidth = maxWidth;
11456 me.maxHeight = maxHeight;
11457 me.margins = margins;
11458
11459 // Dimensions
11460 me.beforeSetDimensions();
11461 me.setDimensions();
11462 me.afterSetDimensions();
11463 // Labels
11464 me.beforeBuildLabels();
11465 me.buildLabels();
11466 me.afterBuildLabels();
11467
11468 // Fit
11469 me.beforeFit();
11470 me.fit();
11471 me.afterFit();
11472 //
11473 me.afterUpdate();
11474
11475 return me.minSize;
11476 },
11477 afterUpdate: noop,
11478
11479 //
11480
11481 beforeSetDimensions: noop,
11482 setDimensions: function() {
11483 var me = this;
11484 // Set the unconstrained dimension before label rotation
11485 if (me.isHorizontal()) {
11486 // Reset position before calculating rotation
11487 me.width = me.maxWidth;
11488 me.left = 0;
11489 me.right = me.width;
11490 } else {
11491 me.height = me.maxHeight;
11492
11493 // Reset position before calculating rotation
11494 me.top = 0;
11495 me.bottom = me.height;
11496 }
11497
11498 // Reset padding
11499 me.paddingLeft = 0;
11500 me.paddingTop = 0;
11501 me.paddingRight = 0;
11502 me.paddingBottom = 0;
11503
11504 // Reset minSize
11505 me.minSize = {
11506 width: 0,
11507 height: 0
11508 };
11509 },
11510 afterSetDimensions: noop,
11511
11512 //
11513
11514 beforeBuildLabels: noop,
11515 buildLabels: function() {
11516 var me = this;
11517 var labelOpts = me.options.labels || {};
11518 var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
11519
11520 if (labelOpts.filter) {
11521 legendItems = legendItems.filter(function(item) {
11522 return labelOpts.filter(item, me.chart.data);
11523 });
11524 }
11525
11526 if (me.options.reverse) {
11527 legendItems.reverse();
11528 }
11529
11530 me.legendItems = legendItems;
11531 },
11532 afterBuildLabels: noop,
11533
11534 //
11535
11536 beforeFit: noop,
11537 fit: function() {
11538 var me = this;
11539 var opts = me.options;
11540 var labelOpts = opts.labels;
11541 var display = opts.display;
11542
11543 var ctx = me.ctx;
11544
11545 var globalDefault = defaults.global;
11546 var valueOrDefault = helpers.valueOrDefault;
11547 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11548 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11549 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11550 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11551
11552 // Reset hit boxes
11553 var hitboxes = me.legendHitBoxes = [];
11554
11555 var minSize = me.minSize;
11556 var isHorizontal = me.isHorizontal();
11557
11558 if (isHorizontal) {
11559 minSize.width = me.maxWidth; // fill all the width
11560 minSize.height = display ? 10 : 0;
11561 } else {
11562 minSize.width = display ? 10 : 0;
11563 minSize.height = me.maxHeight; // fill all the height
11564 }
11565
11566 // Increase sizes here
11567 if (display) {
11568 ctx.font = labelFont;
11569
11570 if (isHorizontal) {
11571 // Labels
11572
11573 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
11574 var lineWidths = me.lineWidths = [0];
11575 var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
11576
11577 ctx.textAlign = 'left';
11578 ctx.textBaseline = 'top';
11579
11580 helpers.each(me.legendItems, function(legendItem, i) {
11581 var boxWidth = getBoxWidth(labelOpts, fontSize);
11582 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11583
11584 if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
11585 totalHeight += fontSize + (labelOpts.padding);
11586 lineWidths[lineWidths.length] = me.left;
11587 }
11588
11589 // Store the hitbox width and height here. Final position will be updated in `draw`
11590 hitboxes[i] = {
11591 left: 0,
11592 top: 0,
11593 width: width,
11594 height: fontSize
11595 };
11596
11597 lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
11598 });
11599
11600 minSize.height += totalHeight;
11601
11602 } else {
11603 var vPadding = labelOpts.padding;
11604 var columnWidths = me.columnWidths = [];
11605 var totalWidth = labelOpts.padding;
11606 var currentColWidth = 0;
11607 var currentColHeight = 0;
11608 var itemHeight = fontSize + vPadding;
11609
11610 helpers.each(me.legendItems, function(legendItem, i) {
11611 var boxWidth = getBoxWidth(labelOpts, fontSize);
11612 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11613
11614 // If too tall, go to new column
11615 if (currentColHeight + itemHeight > minSize.height) {
11616 totalWidth += currentColWidth + labelOpts.padding;
11617 columnWidths.push(currentColWidth); // previous column width
11618
11619 currentColWidth = 0;
11620 currentColHeight = 0;
11621 }
11622
11623 // Get max width
11624 currentColWidth = Math.max(currentColWidth, itemWidth);
11625 currentColHeight += itemHeight;
11626
11627 // Store the hitbox width and height here. Final position will be updated in `draw`
11628 hitboxes[i] = {
11629 left: 0,
11630 top: 0,
11631 width: itemWidth,
11632 height: fontSize
11633 };
11634 });
11635
11636 totalWidth += currentColWidth;
11637 columnWidths.push(currentColWidth);
11638 minSize.width += totalWidth;
11639 }
11640 }
11641
11642 me.width = minSize.width;
11643 me.height = minSize.height;
11644 },
11645 afterFit: noop,
11646
11647 // Shared Methods
11648 isHorizontal: function() {
11649 return this.options.position === 'top' || this.options.position === 'bottom';
11650 },
11651
11652 // Actually draw the legend on the canvas
11653 draw: function() {
11654 var me = this;
11655 var opts = me.options;
11656 var labelOpts = opts.labels;
11657 var globalDefault = defaults.global;
11658 var lineDefault = globalDefault.elements.line;
11659 var legendWidth = me.width;
11660 var lineWidths = me.lineWidths;
11661
11662 if (opts.display) {
11663 var ctx = me.ctx;
11664 var valueOrDefault = helpers.valueOrDefault;
11665 var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
11666 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11667 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11668 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11669 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11670 var cursor;
11671
11672 // Canvas setup
11673 ctx.textAlign = 'left';
11674 ctx.textBaseline = 'middle';
11675 ctx.lineWidth = 0.5;
11676 ctx.strokeStyle = fontColor; // for strikethrough effect
11677 ctx.fillStyle = fontColor; // render in correct colour
11678 ctx.font = labelFont;
11679
11680 var boxWidth = getBoxWidth(labelOpts, fontSize);
11681 var hitboxes = me.legendHitBoxes;
11682
11683 // current position
11684 var drawLegendBox = function(x, y, legendItem) {
11685 if (isNaN(boxWidth) || boxWidth <= 0) {
11686 return;
11687 }
11688
11689 // Set the ctx for the box
11690 ctx.save();
11691
11692 ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
11693 ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
11694 ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
11695 ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
11696 ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
11697 ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
11698 var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
11699
11700 if (ctx.setLineDash) {
11701 // IE 9 and 10 do not support line dash
11702 ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
11703 }
11704
11705 if (opts.labels && opts.labels.usePointStyle) {
11706 // Recalculate x and y for drawPoint() because its expecting
11707 // x and y to be center of figure (instead of top left)
11708 var radius = fontSize * Math.SQRT2 / 2;
11709 var offSet = radius / Math.SQRT2;
11710 var centerX = x + offSet;
11711 var centerY = y + offSet;
11712
11713 // Draw pointStyle as legend symbol
11714 helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
11715 } else {
11716 // Draw box as legend symbol
11717 if (!isLineWidthZero) {
11718 ctx.strokeRect(x, y, boxWidth, fontSize);
11719 }
11720 ctx.fillRect(x, y, boxWidth, fontSize);
11721 }
11722
11723 ctx.restore();
11724 };
11725 var fillText = function(x, y, legendItem, textWidth) {
11726 var halfFontSize = fontSize / 2;
11727 var xLeft = boxWidth + halfFontSize + x;
11728 var yMiddle = y + halfFontSize;
11729
11730 ctx.fillText(legendItem.text, xLeft, yMiddle);
11731
11732 if (legendItem.hidden) {
11733 // Strikethrough the text if hidden
11734 ctx.beginPath();
11735 ctx.lineWidth = 2;
11736 ctx.moveTo(xLeft, yMiddle);
11737 ctx.lineTo(xLeft + textWidth, yMiddle);
11738 ctx.stroke();
11739 }
11740 };
11741
11742 // Horizontal
11743 var isHorizontal = me.isHorizontal();
11744 if (isHorizontal) {
11745 cursor = {
11746 x: me.left + ((legendWidth - lineWidths[0]) / 2),
11747 y: me.top + labelOpts.padding,
11748 line: 0
11749 };
11750 } else {
11751 cursor = {
11752 x: me.left + labelOpts.padding,
11753 y: me.top + labelOpts.padding,
11754 line: 0
11755 };
11756 }
11757
11758 var itemHeight = fontSize + labelOpts.padding;
11759 helpers.each(me.legendItems, function(legendItem, i) {
11760 var textWidth = ctx.measureText(legendItem.text).width;
11761 var width = boxWidth + (fontSize / 2) + textWidth;
11762 var x = cursor.x;
11763 var y = cursor.y;
11764
11765 if (isHorizontal) {
11766 if (x + width >= legendWidth) {
11767 y = cursor.y += itemHeight;
11768 cursor.line++;
11769 x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
11770 }
11771 } else if (y + itemHeight > me.bottom) {
11772 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
11773 y = cursor.y = me.top + labelOpts.padding;
11774 cursor.line++;
11775 }
11776
11777 drawLegendBox(x, y, legendItem);
11778
11779 hitboxes[i].left = x;
11780 hitboxes[i].top = y;
11781
11782 // Fill the actual label
11783 fillText(x, y, legendItem, textWidth);
11784
11785 if (isHorizontal) {
11786 cursor.x += width + (labelOpts.padding);
11787 } else {
11788 cursor.y += itemHeight;
11789 }
11790
11791 });
11792 }
11793 },
11794
11795 /**
11796 * Handle an event
11797 * @private
11798 * @param {IEvent} event - The event to handle
11799 * @return {Boolean} true if a change occured
11800 */
11801 handleEvent: function(e) {
11802 var me = this;
11803 var opts = me.options;
11804 var type = e.type === 'mouseup' ? 'click' : e.type;
11805 var changed = false;
11806
11807 if (type === 'mousemove') {
11808 if (!opts.onHover) {
11809 return;
11810 }
11811 } else if (type === 'click') {
11812 if (!opts.onClick) {
11813 return;
11814 }
11815 } else {
11816 return;
11817 }
11818
11819 // Chart event already has relative position in it
11820 var x = e.x;
11821 var y = e.y;
11822
11823 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
11824 // See if we are touching one of the dataset boxes
11825 var lh = me.legendHitBoxes;
11826 for (var i = 0; i < lh.length; ++i) {
11827 var hitBox = lh[i];
11828
11829 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
11830 // Touching an element
11831 if (type === 'click') {
11832 // use e.native for backwards compatibility
11833 opts.onClick.call(me, e.native, me.legendItems[i]);
11834 changed = true;
11835 break;
11836 } else if (type === 'mousemove') {
11837 // use e.native for backwards compatibility
11838 opts.onHover.call(me, e.native, me.legendItems[i]);
11839 changed = true;
11840 break;
11841 }
11842 }
11843 }
11844 }
11845
11846 return changed;
11847 }
11848 });
11849
11850 function createNewLegendAndAttach(chart, legendOpts) {
11851 var legend = new Chart.Legend({
11852 ctx: chart.ctx,
11853 options: legendOpts,
11854 chart: chart
11855 });
11856
11857 layout.configure(chart, legend, legendOpts);
11858 layout.addBox(chart, legend);
11859 chart.legend = legend;
11860 }
11861
11862 return {
11863 id: 'legend',
11864
11865 beforeInit: function(chart) {
11866 var legendOpts = chart.options.legend;
11867
11868 if (legendOpts) {
11869 createNewLegendAndAttach(chart, legendOpts);
11870 }
11871 },
11872
11873 beforeUpdate: function(chart) {
11874 var legendOpts = chart.options.legend;
11875 var legend = chart.legend;
11876
11877 if (legendOpts) {
11878 helpers.mergeIf(legendOpts, defaults.global.legend);
11879
11880 if (legend) {
11881 layout.configure(chart, legend, legendOpts);
11882 legend.options = legendOpts;
11883 } else {
11884 createNewLegendAndAttach(chart, legendOpts);
11885 }
11886 } else if (legend) {
11887 layout.removeBox(chart, legend);
11888 delete chart.legend;
11889 }
11890 },
11891
11892 afterEvent: function(chart, e) {
11893 var legend = chart.legend;
11894 if (legend) {
11895 legend.handleEvent(e);
11896 }
11897 }
11898 };
11899 };
11900
11901 },{"25":25,"26":26,"45":45}],51:[function(require,module,exports){
11902 'use strict';
11903
11904 var defaults = require(25);
11905 var Element = require(26);
11906 var helpers = require(45);
11907
11908 defaults._set('global', {
11909 title: {
11910 display: false,
11911 fontStyle: 'bold',
11912 fullWidth: true,
11913 lineHeight: 1.2,
11914 padding: 10,
11915 position: 'top',
11916 text: '',
11917 weight: 2000 // by default greater than legend (1000) to be above
11918 }
11919 });
11920
11921 module.exports = function(Chart) {
11922
11923 var layout = Chart.layoutService;
11924 var noop = helpers.noop;
11925
11926 Chart.Title = Element.extend({
11927 initialize: function(config) {
11928 var me = this;
11929 helpers.extend(me, config);
11930
11931 // Contains hit boxes for each dataset (in dataset order)
11932 me.legendHitBoxes = [];
11933 },
11934
11935 // These methods are ordered by lifecycle. Utilities then follow.
11936
11937 beforeUpdate: noop,
11938 update: function(maxWidth, maxHeight, margins) {
11939 var me = this;
11940
11941 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11942 me.beforeUpdate();
11943
11944 // Absorb the master measurements
11945 me.maxWidth = maxWidth;
11946 me.maxHeight = maxHeight;
11947 me.margins = margins;
11948
11949 // Dimensions
11950 me.beforeSetDimensions();
11951 me.setDimensions();
11952 me.afterSetDimensions();
11953 // Labels
11954 me.beforeBuildLabels();
11955 me.buildLabels();
11956 me.afterBuildLabels();
11957
11958 // Fit
11959 me.beforeFit();
11960 me.fit();
11961 me.afterFit();
11962 //
11963 me.afterUpdate();
11964
11965 return me.minSize;
11966
11967 },
11968 afterUpdate: noop,
11969
11970 //
11971
11972 beforeSetDimensions: noop,
11973 setDimensions: function() {
11974 var me = this;
11975 // Set the unconstrained dimension before label rotation
11976 if (me.isHorizontal()) {
11977 // Reset position before calculating rotation
11978 me.width = me.maxWidth;
11979 me.left = 0;
11980 me.right = me.width;
11981 } else {
11982 me.height = me.maxHeight;
11983
11984 // Reset position before calculating rotation
11985 me.top = 0;
11986 me.bottom = me.height;
11987 }
11988
11989 // Reset padding
11990 me.paddingLeft = 0;
11991 me.paddingTop = 0;
11992 me.paddingRight = 0;
11993 me.paddingBottom = 0;
11994
11995 // Reset minSize
11996 me.minSize = {
11997 width: 0,
11998 height: 0
11999 };
12000 },
12001 afterSetDimensions: noop,
12002
12003 //
12004
12005 beforeBuildLabels: noop,
12006 buildLabels: noop,
12007 afterBuildLabels: noop,
12008
12009 //
12010
12011 beforeFit: noop,
12012 fit: function() {
12013 var me = this;
12014 var valueOrDefault = helpers.valueOrDefault;
12015 var opts = me.options;
12016 var display = opts.display;
12017 var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
12018 var minSize = me.minSize;
12019 var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
12020 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12021 var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
12022
12023 if (me.isHorizontal()) {
12024 minSize.width = me.maxWidth; // fill all the width
12025 minSize.height = textSize;
12026 } else {
12027 minSize.width = textSize;
12028 minSize.height = me.maxHeight; // fill all the height
12029 }
12030
12031 me.width = minSize.width;
12032 me.height = minSize.height;
12033
12034 },
12035 afterFit: noop,
12036
12037 // Shared Methods
12038 isHorizontal: function() {
12039 var pos = this.options.position;
12040 return pos === 'top' || pos === 'bottom';
12041 },
12042
12043 // Actually draw the title block on the canvas
12044 draw: function() {
12045 var me = this;
12046 var ctx = me.ctx;
12047 var valueOrDefault = helpers.valueOrDefault;
12048 var opts = me.options;
12049 var globalDefaults = defaults.global;
12050
12051 if (opts.display) {
12052 var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
12053 var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
12054 var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
12055 var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
12056 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12057 var offset = lineHeight / 2 + opts.padding;
12058 var rotation = 0;
12059 var top = me.top;
12060 var left = me.left;
12061 var bottom = me.bottom;
12062 var right = me.right;
12063 var maxWidth, titleX, titleY;
12064
12065 ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
12066 ctx.font = titleFont;
12067
12068 // Horizontal
12069 if (me.isHorizontal()) {
12070 titleX = left + ((right - left) / 2); // midpoint of the width
12071 titleY = top + offset;
12072 maxWidth = right - left;
12073 } else {
12074 titleX = opts.position === 'left' ? left + offset : right - offset;
12075 titleY = top + ((bottom - top) / 2);
12076 maxWidth = bottom - top;
12077 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
12078 }
12079
12080 ctx.save();
12081 ctx.translate(titleX, titleY);
12082 ctx.rotate(rotation);
12083 ctx.textAlign = 'center';
12084 ctx.textBaseline = 'middle';
12085
12086 var text = opts.text;
12087 if (helpers.isArray(text)) {
12088 var y = 0;
12089 for (var i = 0; i < text.length; ++i) {
12090 ctx.fillText(text[i], 0, y, maxWidth);
12091 y += lineHeight;
12092 }
12093 } else {
12094 ctx.fillText(text, 0, 0, maxWidth);
12095 }
12096
12097 ctx.restore();
12098 }
12099 }
12100 });
12101
12102 function createNewTitleBlockAndAttach(chart, titleOpts) {
12103 var title = new Chart.Title({
12104 ctx: chart.ctx,
12105 options: titleOpts,
12106 chart: chart
12107 });
12108
12109 layout.configure(chart, title, titleOpts);
12110 layout.addBox(chart, title);
12111 chart.titleBlock = title;
12112 }
12113
12114 return {
12115 id: 'title',
12116
12117 beforeInit: function(chart) {
12118 var titleOpts = chart.options.title;
12119
12120 if (titleOpts) {
12121 createNewTitleBlockAndAttach(chart, titleOpts);
12122 }
12123 },
12124
12125 beforeUpdate: function(chart) {
12126 var titleOpts = chart.options.title;
12127 var titleBlock = chart.titleBlock;
12128
12129 if (titleOpts) {
12130 helpers.mergeIf(titleOpts, defaults.global.title);
12131
12132 if (titleBlock) {
12133 layout.configure(chart, titleBlock, titleOpts);
12134 titleBlock.options = titleOpts;
12135 } else {
12136 createNewTitleBlockAndAttach(chart, titleOpts);
12137 }
12138 } else if (titleBlock) {
12139 Chart.layoutService.removeBox(chart, titleBlock);
12140 delete chart.titleBlock;
12141 }
12142 }
12143 };
12144 };
12145
12146 },{"25":25,"26":26,"45":45}],52:[function(require,module,exports){
12147 'use strict';
12148
12149 module.exports = function(Chart) {
12150
12151 // Default config for a category scale
12152 var defaultConfig = {
12153 position: 'bottom'
12154 };
12155
12156 var DatasetScale = Chart.Scale.extend({
12157 /**
12158 * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
12159 * else fall back to data.labels
12160 * @private
12161 */
12162 getLabels: function() {
12163 var data = this.chart.data;
12164 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
12165 },
12166
12167 determineDataLimits: function() {
12168 var me = this;
12169 var labels = me.getLabels();
12170 me.minIndex = 0;
12171 me.maxIndex = labels.length - 1;
12172 var findIndex;
12173
12174 if (me.options.ticks.min !== undefined) {
12175 // user specified min value
12176 findIndex = labels.indexOf(me.options.ticks.min);
12177 me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
12178 }
12179
12180 if (me.options.ticks.max !== undefined) {
12181 // user specified max value
12182 findIndex = labels.indexOf(me.options.ticks.max);
12183 me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
12184 }
12185
12186 me.min = labels[me.minIndex];
12187 me.max = labels[me.maxIndex];
12188 },
12189
12190 buildTicks: function() {
12191 var me = this;
12192 var labels = me.getLabels();
12193 // If we are viewing some subset of labels, slice the original array
12194 me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
12195 },
12196
12197 getLabelForIndex: function(index, datasetIndex) {
12198 var me = this;
12199 var data = me.chart.data;
12200 var isHorizontal = me.isHorizontal();
12201
12202 if (data.yLabels && !isHorizontal) {
12203 return me.getRightValue(data.datasets[datasetIndex].data[index]);
12204 }
12205 return me.ticks[index - me.minIndex];
12206 },
12207
12208 // Used to get data value locations. Value can either be an index or a numerical value
12209 getPixelForValue: function(value, index) {
12210 var me = this;
12211 var offset = me.options.offset;
12212 // 1 is added because we need the length but we have the indexes
12213 var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
12214
12215 // If value is a data object, then index is the index in the data array,
12216 // not the index of the scale. We need to change that.
12217 var valueCategory;
12218 if (value !== undefined && value !== null) {
12219 valueCategory = me.isHorizontal() ? value.x : value.y;
12220 }
12221 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
12222 var labels = me.getLabels();
12223 value = valueCategory || value;
12224 var idx = labels.indexOf(value);
12225 index = idx !== -1 ? idx : index;
12226 }
12227
12228 if (me.isHorizontal()) {
12229 var valueWidth = me.width / offsetAmt;
12230 var widthOffset = (valueWidth * (index - me.minIndex));
12231
12232 if (offset) {
12233 widthOffset += (valueWidth / 2);
12234 }
12235
12236 return me.left + Math.round(widthOffset);
12237 }
12238 var valueHeight = me.height / offsetAmt;
12239 var heightOffset = (valueHeight * (index - me.minIndex));
12240
12241 if (offset) {
12242 heightOffset += (valueHeight / 2);
12243 }
12244
12245 return me.top + Math.round(heightOffset);
12246 },
12247 getPixelForTick: function(index) {
12248 return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
12249 },
12250 getValueForPixel: function(pixel) {
12251 var me = this;
12252 var offset = me.options.offset;
12253 var value;
12254 var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
12255 var horz = me.isHorizontal();
12256 var valueDimension = (horz ? me.width : me.height) / offsetAmt;
12257
12258 pixel -= horz ? me.left : me.top;
12259
12260 if (offset) {
12261 pixel -= (valueDimension / 2);
12262 }
12263
12264 if (pixel <= 0) {
12265 value = 0;
12266 } else {
12267 value = Math.round(pixel / valueDimension);
12268 }
12269
12270 return value + me.minIndex;
12271 },
12272 getBasePixel: function() {
12273 return this.bottom;
12274 }
12275 });
12276
12277 Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
12278
12279 };
12280
12281 },{}],53:[function(require,module,exports){
12282 'use strict';
12283
12284 var defaults = require(25);
12285 var helpers = require(45);
12286 var Ticks = require(34);
12287
12288 module.exports = function(Chart) {
12289
12290 var defaultConfig = {
12291 position: 'left',
12292 ticks: {
12293 callback: Ticks.formatters.linear
12294 }
12295 };
12296
12297 var LinearScale = Chart.LinearScaleBase.extend({
12298
12299 determineDataLimits: function() {
12300 var me = this;
12301 var opts = me.options;
12302 var chart = me.chart;
12303 var data = chart.data;
12304 var datasets = data.datasets;
12305 var isHorizontal = me.isHorizontal();
12306 var DEFAULT_MIN = 0;
12307 var DEFAULT_MAX = 1;
12308
12309 function IDMatches(meta) {
12310 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12311 }
12312
12313 // First Calculate the range
12314 me.min = null;
12315 me.max = null;
12316
12317 var hasStacks = opts.stacked;
12318 if (hasStacks === undefined) {
12319 helpers.each(datasets, function(dataset, datasetIndex) {
12320 if (hasStacks) {
12321 return;
12322 }
12323
12324 var meta = chart.getDatasetMeta(datasetIndex);
12325 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12326 meta.stack !== undefined) {
12327 hasStacks = true;
12328 }
12329 });
12330 }
12331
12332 if (opts.stacked || hasStacks) {
12333 var valuesPerStack = {};
12334
12335 helpers.each(datasets, function(dataset, datasetIndex) {
12336 var meta = chart.getDatasetMeta(datasetIndex);
12337 var key = [
12338 meta.type,
12339 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12340 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12341 meta.stack
12342 ].join('.');
12343
12344 if (valuesPerStack[key] === undefined) {
12345 valuesPerStack[key] = {
12346 positiveValues: [],
12347 negativeValues: []
12348 };
12349 }
12350
12351 // Store these per type
12352 var positiveValues = valuesPerStack[key].positiveValues;
12353 var negativeValues = valuesPerStack[key].negativeValues;
12354
12355 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12356 helpers.each(dataset.data, function(rawValue, index) {
12357 var value = +me.getRightValue(rawValue);
12358 if (isNaN(value) || meta.data[index].hidden) {
12359 return;
12360 }
12361
12362 positiveValues[index] = positiveValues[index] || 0;
12363 negativeValues[index] = negativeValues[index] || 0;
12364
12365 if (opts.relativePoints) {
12366 positiveValues[index] = 100;
12367 } else if (value < 0) {
12368 negativeValues[index] += value;
12369 } else {
12370 positiveValues[index] += value;
12371 }
12372 });
12373 }
12374 });
12375
12376 helpers.each(valuesPerStack, function(valuesForType) {
12377 var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
12378 var minVal = helpers.min(values);
12379 var maxVal = helpers.max(values);
12380 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12381 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12382 });
12383
12384 } else {
12385 helpers.each(datasets, function(dataset, datasetIndex) {
12386 var meta = chart.getDatasetMeta(datasetIndex);
12387 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12388 helpers.each(dataset.data, function(rawValue, index) {
12389 var value = +me.getRightValue(rawValue);
12390 if (isNaN(value) || meta.data[index].hidden) {
12391 return;
12392 }
12393
12394 if (me.min === null) {
12395 me.min = value;
12396 } else if (value < me.min) {
12397 me.min = value;
12398 }
12399
12400 if (me.max === null) {
12401 me.max = value;
12402 } else if (value > me.max) {
12403 me.max = value;
12404 }
12405 });
12406 }
12407 });
12408 }
12409
12410 me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
12411 me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
12412
12413 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
12414 this.handleTickRangeOptions();
12415 },
12416 getTickLimit: function() {
12417 var maxTicks;
12418 var me = this;
12419 var tickOpts = me.options.ticks;
12420
12421 if (me.isHorizontal()) {
12422 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
12423 } else {
12424 // The factor of 2 used to scale the font size has been experimentally determined.
12425 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize);
12426 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
12427 }
12428
12429 return maxTicks;
12430 },
12431 // Called after the ticks are built. We need
12432 handleDirectionalChanges: function() {
12433 if (!this.isHorizontal()) {
12434 // We are in a vertical orientation. The top value is the highest. So reverse the array
12435 this.ticks.reverse();
12436 }
12437 },
12438 getLabelForIndex: function(index, datasetIndex) {
12439 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12440 },
12441 // Utils
12442 getPixelForValue: function(value) {
12443 // This must be called after fit has been run so that
12444 // this.left, this.top, this.right, and this.bottom have been defined
12445 var me = this;
12446 var start = me.start;
12447
12448 var rightValue = +me.getRightValue(value);
12449 var pixel;
12450 var range = me.end - start;
12451
12452 if (me.isHorizontal()) {
12453 pixel = me.left + (me.width / range * (rightValue - start));
12454 return Math.round(pixel);
12455 }
12456
12457 pixel = me.bottom - (me.height / range * (rightValue - start));
12458 return Math.round(pixel);
12459 },
12460 getValueForPixel: function(pixel) {
12461 var me = this;
12462 var isHorizontal = me.isHorizontal();
12463 var innerDimension = isHorizontal ? me.width : me.height;
12464 var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
12465 return me.start + ((me.end - me.start) * offset);
12466 },
12467 getPixelForTick: function(index) {
12468 return this.getPixelForValue(this.ticksAsNumbers[index]);
12469 }
12470 });
12471 Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
12472
12473 };
12474
12475 },{"25":25,"34":34,"45":45}],54:[function(require,module,exports){
12476 'use strict';
12477
12478 var helpers = require(45);
12479 var Ticks = require(34);
12480
12481 module.exports = function(Chart) {
12482
12483 var noop = helpers.noop;
12484
12485 Chart.LinearScaleBase = Chart.Scale.extend({
12486 getRightValue: function(value) {
12487 if (typeof value === 'string') {
12488 return +value;
12489 }
12490 return Chart.Scale.prototype.getRightValue.call(this, value);
12491 },
12492
12493 handleTickRangeOptions: function() {
12494 var me = this;
12495 var opts = me.options;
12496 var tickOpts = opts.ticks;
12497
12498 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
12499 // do nothing since that would make the chart weird. If the user really wants a weird chart
12500 // axis, they can manually override it
12501 if (tickOpts.beginAtZero) {
12502 var minSign = helpers.sign(me.min);
12503 var maxSign = helpers.sign(me.max);
12504
12505 if (minSign < 0 && maxSign < 0) {
12506 // move the top up to 0
12507 me.max = 0;
12508 } else if (minSign > 0 && maxSign > 0) {
12509 // move the bottom down to 0
12510 me.min = 0;
12511 }
12512 }
12513
12514 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
12515 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
12516
12517 if (tickOpts.min !== undefined) {
12518 me.min = tickOpts.min;
12519 } else if (tickOpts.suggestedMin !== undefined) {
12520 if (me.min === null) {
12521 me.min = tickOpts.suggestedMin;
12522 } else {
12523 me.min = Math.min(me.min, tickOpts.suggestedMin);
12524 }
12525 }
12526
12527 if (tickOpts.max !== undefined) {
12528 me.max = tickOpts.max;
12529 } else if (tickOpts.suggestedMax !== undefined) {
12530 if (me.max === null) {
12531 me.max = tickOpts.suggestedMax;
12532 } else {
12533 me.max = Math.max(me.max, tickOpts.suggestedMax);
12534 }
12535 }
12536
12537 if (setMin !== setMax) {
12538 // We set the min or the max but not both.
12539 // So ensure that our range is good
12540 // Inverted or 0 length range can happen when
12541 // ticks.min is set, and no datasets are visible
12542 if (me.min >= me.max) {
12543 if (setMin) {
12544 me.max = me.min + 1;
12545 } else {
12546 me.min = me.max - 1;
12547 }
12548 }
12549 }
12550
12551 if (me.min === me.max) {
12552 me.max++;
12553
12554 if (!tickOpts.beginAtZero) {
12555 me.min--;
12556 }
12557 }
12558 },
12559 getTickLimit: noop,
12560 handleDirectionalChanges: noop,
12561
12562 buildTicks: function() {
12563 var me = this;
12564 var opts = me.options;
12565 var tickOpts = opts.ticks;
12566
12567 // Figure out what the max number of ticks we can support it is based on the size of
12568 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
12569 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
12570 // the graph. Make sure we always have at least 2 ticks
12571 var maxTicks = me.getTickLimit();
12572 maxTicks = Math.max(2, maxTicks);
12573
12574 var numericGeneratorOptions = {
12575 maxTicks: maxTicks,
12576 min: tickOpts.min,
12577 max: tickOpts.max,
12578 stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
12579 };
12580 var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me);
12581
12582 me.handleDirectionalChanges();
12583
12584 // At this point, we need to update our max and min given the tick values since we have expanded the
12585 // range of the scale
12586 me.max = helpers.max(ticks);
12587 me.min = helpers.min(ticks);
12588
12589 if (tickOpts.reverse) {
12590 ticks.reverse();
12591
12592 me.start = me.max;
12593 me.end = me.min;
12594 } else {
12595 me.start = me.min;
12596 me.end = me.max;
12597 }
12598 },
12599 convertTicksToLabels: function() {
12600 var me = this;
12601 me.ticksAsNumbers = me.ticks.slice();
12602 me.zeroLineIndex = me.ticks.indexOf(0);
12603
12604 Chart.Scale.prototype.convertTicksToLabels.call(me);
12605 }
12606 });
12607 };
12608
12609 },{"34":34,"45":45}],55:[function(require,module,exports){
12610 'use strict';
12611
12612 var helpers = require(45);
12613 var Ticks = require(34);
12614
12615 module.exports = function(Chart) {
12616
12617 var defaultConfig = {
12618 position: 'left',
12619
12620 // label settings
12621 ticks: {
12622 callback: Ticks.formatters.logarithmic
12623 }
12624 };
12625
12626 var LogarithmicScale = Chart.Scale.extend({
12627 determineDataLimits: function() {
12628 var me = this;
12629 var opts = me.options;
12630 var tickOpts = opts.ticks;
12631 var chart = me.chart;
12632 var data = chart.data;
12633 var datasets = data.datasets;
12634 var valueOrDefault = helpers.valueOrDefault;
12635 var isHorizontal = me.isHorizontal();
12636 function IDMatches(meta) {
12637 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12638 }
12639
12640 // Calculate Range
12641 me.min = null;
12642 me.max = null;
12643 me.minNotZero = null;
12644
12645 var hasStacks = opts.stacked;
12646 if (hasStacks === undefined) {
12647 helpers.each(datasets, function(dataset, datasetIndex) {
12648 if (hasStacks) {
12649 return;
12650 }
12651
12652 var meta = chart.getDatasetMeta(datasetIndex);
12653 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12654 meta.stack !== undefined) {
12655 hasStacks = true;
12656 }
12657 });
12658 }
12659
12660 if (opts.stacked || hasStacks) {
12661 var valuesPerStack = {};
12662
12663 helpers.each(datasets, function(dataset, datasetIndex) {
12664 var meta = chart.getDatasetMeta(datasetIndex);
12665 var key = [
12666 meta.type,
12667 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12668 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12669 meta.stack
12670 ].join('.');
12671
12672 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12673 if (valuesPerStack[key] === undefined) {
12674 valuesPerStack[key] = [];
12675 }
12676
12677 helpers.each(dataset.data, function(rawValue, index) {
12678 var values = valuesPerStack[key];
12679 var value = +me.getRightValue(rawValue);
12680 if (isNaN(value) || meta.data[index].hidden) {
12681 return;
12682 }
12683
12684 values[index] = values[index] || 0;
12685
12686 if (opts.relativePoints) {
12687 values[index] = 100;
12688 } else {
12689 // Don't need to split positive and negative since the log scale can't handle a 0 crossing
12690 values[index] += value;
12691 }
12692 });
12693 }
12694 });
12695
12696 helpers.each(valuesPerStack, function(valuesForType) {
12697 var minVal = helpers.min(valuesForType);
12698 var maxVal = helpers.max(valuesForType);
12699 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12700 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12701 });
12702
12703 } else {
12704 helpers.each(datasets, function(dataset, datasetIndex) {
12705 var meta = chart.getDatasetMeta(datasetIndex);
12706 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12707 helpers.each(dataset.data, function(rawValue, index) {
12708 var value = +me.getRightValue(rawValue);
12709 if (isNaN(value) || meta.data[index].hidden) {
12710 return;
12711 }
12712
12713 if (me.min === null) {
12714 me.min = value;
12715 } else if (value < me.min) {
12716 me.min = value;
12717 }
12718
12719 if (me.max === null) {
12720 me.max = value;
12721 } else if (value > me.max) {
12722 me.max = value;
12723 }
12724
12725 if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
12726 me.minNotZero = value;
12727 }
12728 });
12729 }
12730 });
12731 }
12732
12733 me.min = valueOrDefault(tickOpts.min, me.min);
12734 me.max = valueOrDefault(tickOpts.max, me.max);
12735
12736 if (me.min === me.max) {
12737 if (me.min !== 0 && me.min !== null) {
12738 me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
12739 me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
12740 } else {
12741 me.min = 1;
12742 me.max = 10;
12743 }
12744 }
12745 },
12746 buildTicks: function() {
12747 var me = this;
12748 var opts = me.options;
12749 var tickOpts = opts.ticks;
12750
12751 var generationOptions = {
12752 min: tickOpts.min,
12753 max: tickOpts.max
12754 };
12755 var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me);
12756
12757 if (!me.isHorizontal()) {
12758 // We are in a vertical orientation. The top value is the highest. So reverse the array
12759 ticks.reverse();
12760 }
12761
12762 // At this point, we need to update our max and min given the tick values since we have expanded the
12763 // range of the scale
12764 me.max = helpers.max(ticks);
12765 me.min = helpers.min(ticks);
12766
12767 if (tickOpts.reverse) {
12768 ticks.reverse();
12769
12770 me.start = me.max;
12771 me.end = me.min;
12772 } else {
12773 me.start = me.min;
12774 me.end = me.max;
12775 }
12776 },
12777 convertTicksToLabels: function() {
12778 this.tickValues = this.ticks.slice();
12779
12780 Chart.Scale.prototype.convertTicksToLabels.call(this);
12781 },
12782 // Get the correct tooltip label
12783 getLabelForIndex: function(index, datasetIndex) {
12784 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12785 },
12786 getPixelForTick: function(index) {
12787 return this.getPixelForValue(this.tickValues[index]);
12788 },
12789 getPixelForValue: function(value) {
12790 var me = this;
12791 var start = me.start;
12792 var newVal = +me.getRightValue(value);
12793 var opts = me.options;
12794 var tickOpts = opts.ticks;
12795 var innerDimension, pixel, range;
12796
12797 if (me.isHorizontal()) {
12798 range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
12799 if (newVal === 0) {
12800 pixel = me.left;
12801 } else {
12802 innerDimension = me.width;
12803 pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12804 }
12805 } else {
12806 // Bottom - top since pixels increase downward on a screen
12807 innerDimension = me.height;
12808 if (start === 0 && !tickOpts.reverse) {
12809 range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
12810 if (newVal === start) {
12811 pixel = me.bottom;
12812 } else if (newVal === me.minNotZero) {
12813 pixel = me.bottom - innerDimension * 0.02;
12814 } else {
12815 pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12816 }
12817 } else if (me.end === 0 && tickOpts.reverse) {
12818 range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
12819 if (newVal === me.end) {
12820 pixel = me.top;
12821 } else if (newVal === me.minNotZero) {
12822 pixel = me.top + innerDimension * 0.02;
12823 } else {
12824 pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12825 }
12826 } else if (newVal === 0) {
12827 pixel = tickOpts.reverse ? me.top : me.bottom;
12828 } else {
12829 range = helpers.log10(me.end) - helpers.log10(start);
12830 innerDimension = me.height;
12831 pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12832 }
12833 }
12834 return pixel;
12835 },
12836 getValueForPixel: function(pixel) {
12837 var me = this;
12838 var range = helpers.log10(me.end) - helpers.log10(me.start);
12839 var value, innerDimension;
12840
12841 if (me.isHorizontal()) {
12842 innerDimension = me.width;
12843 value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);
12844 } else { // todo: if start === 0
12845 innerDimension = me.height;
12846 value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;
12847 }
12848 return value;
12849 }
12850 });
12851 Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
12852
12853 };
12854
12855 },{"34":34,"45":45}],56:[function(require,module,exports){
12856 'use strict';
12857
12858 var defaults = require(25);
12859 var helpers = require(45);
12860 var Ticks = require(34);
12861
12862 module.exports = function(Chart) {
12863
12864 var globalDefaults = defaults.global;
12865
12866 var defaultConfig = {
12867 display: true,
12868
12869 // Boolean - Whether to animate scaling the chart from the centre
12870 animate: true,
12871 position: 'chartArea',
12872
12873 angleLines: {
12874 display: true,
12875 color: 'rgba(0, 0, 0, 0.1)',
12876 lineWidth: 1
12877 },
12878
12879 gridLines: {
12880 circular: false
12881 },
12882
12883 // label settings
12884 ticks: {
12885 // Boolean - Show a backdrop to the scale label
12886 showLabelBackdrop: true,
12887
12888 // String - The colour of the label backdrop
12889 backdropColor: 'rgba(255,255,255,0.75)',
12890
12891 // Number - The backdrop padding above & below the label in pixels
12892 backdropPaddingY: 2,
12893
12894 // Number - The backdrop padding to the side of the label in pixels
12895 backdropPaddingX: 2,
12896
12897 callback: Ticks.formatters.linear
12898 },
12899
12900 pointLabels: {
12901 // Boolean - if true, show point labels
12902 display: true,
12903
12904 // Number - Point label font size in pixels
12905 fontSize: 10,
12906
12907 // Function - Used to convert point labels
12908 callback: function(label) {
12909 return label;
12910 }
12911 }
12912 };
12913
12914 function getValueCount(scale) {
12915 var opts = scale.options;
12916 return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
12917 }
12918
12919 function getPointLabelFontOptions(scale) {
12920 var pointLabelOptions = scale.options.pointLabels;
12921 var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
12922 var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
12923 var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
12924 var font = helpers.fontString(fontSize, fontStyle, fontFamily);
12925
12926 return {
12927 size: fontSize,
12928 style: fontStyle,
12929 family: fontFamily,
12930 font: font
12931 };
12932 }
12933
12934 function measureLabelSize(ctx, fontSize, label) {
12935 if (helpers.isArray(label)) {
12936 return {
12937 w: helpers.longestText(ctx, ctx.font, label),
12938 h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
12939 };
12940 }
12941
12942 return {
12943 w: ctx.measureText(label).width,
12944 h: fontSize
12945 };
12946 }
12947
12948 function determineLimits(angle, pos, size, min, max) {
12949 if (angle === min || angle === max) {
12950 return {
12951 start: pos - (size / 2),
12952 end: pos + (size / 2)
12953 };
12954 } else if (angle < min || angle > max) {
12955 return {
12956 start: pos - size - 5,
12957 end: pos
12958 };
12959 }
12960
12961 return {
12962 start: pos,
12963 end: pos + size + 5
12964 };
12965 }
12966
12967 /**
12968 * Helper function to fit a radial linear scale with point labels
12969 */
12970 function fitWithPointLabels(scale) {
12971 /*
12972 * Right, this is really confusing and there is a lot of maths going on here
12973 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
12974 *
12975 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
12976 *
12977 * Solution:
12978 *
12979 * We assume the radius of the polygon is half the size of the canvas at first
12980 * at each index we check if the text overlaps.
12981 *
12982 * Where it does, we store that angle and that index.
12983 *
12984 * After finding the largest index and angle we calculate how much we need to remove
12985 * from the shape radius to move the point inwards by that x.
12986 *
12987 * We average the left and right distances to get the maximum shape radius that can fit in the box
12988 * along with labels.
12989 *
12990 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
12991 * on each side, removing that from the size, halving it and adding the left x protrusion width.
12992 *
12993 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
12994 * and position it in the most space efficient manner
12995 *
12996 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
12997 */
12998
12999 var plFont = getPointLabelFontOptions(scale);
13000
13001 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
13002 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
13003 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13004 var furthestLimits = {
13005 r: scale.width,
13006 l: 0,
13007 t: scale.height,
13008 b: 0
13009 };
13010 var furthestAngles = {};
13011 var i, textSize, pointPosition;
13012
13013 scale.ctx.font = plFont.font;
13014 scale._pointLabelSizes = [];
13015
13016 var valueCount = getValueCount(scale);
13017 for (i = 0; i < valueCount; i++) {
13018 pointPosition = scale.getPointPosition(i, largestPossibleRadius);
13019 textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
13020 scale._pointLabelSizes[i] = textSize;
13021
13022 // Add quarter circle to make degree 0 mean top of circle
13023 var angleRadians = scale.getIndexAngle(i);
13024 var angle = helpers.toDegrees(angleRadians) % 360;
13025 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
13026 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
13027
13028 if (hLimits.start < furthestLimits.l) {
13029 furthestLimits.l = hLimits.start;
13030 furthestAngles.l = angleRadians;
13031 }
13032
13033 if (hLimits.end > furthestLimits.r) {
13034 furthestLimits.r = hLimits.end;
13035 furthestAngles.r = angleRadians;
13036 }
13037
13038 if (vLimits.start < furthestLimits.t) {
13039 furthestLimits.t = vLimits.start;
13040 furthestAngles.t = angleRadians;
13041 }
13042
13043 if (vLimits.end > furthestLimits.b) {
13044 furthestLimits.b = vLimits.end;
13045 furthestAngles.b = angleRadians;
13046 }
13047 }
13048
13049 scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
13050 }
13051
13052 /**
13053 * Helper function to fit a radial linear scale with no point labels
13054 */
13055 function fit(scale) {
13056 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13057 scale.drawingArea = Math.round(largestPossibleRadius);
13058 scale.setCenterPoint(0, 0, 0, 0);
13059 }
13060
13061 function getTextAlignForAngle(angle) {
13062 if (angle === 0 || angle === 180) {
13063 return 'center';
13064 } else if (angle < 180) {
13065 return 'left';
13066 }
13067
13068 return 'right';
13069 }
13070
13071 function fillText(ctx, text, position, fontSize) {
13072 if (helpers.isArray(text)) {
13073 var y = position.y;
13074 var spacing = 1.5 * fontSize;
13075
13076 for (var i = 0; i < text.length; ++i) {
13077 ctx.fillText(text[i], position.x, y);
13078 y += spacing;
13079 }
13080 } else {
13081 ctx.fillText(text, position.x, position.y);
13082 }
13083 }
13084
13085 function adjustPointPositionForLabelHeight(angle, textSize, position) {
13086 if (angle === 90 || angle === 270) {
13087 position.y -= (textSize.h / 2);
13088 } else if (angle > 270 || angle < 90) {
13089 position.y -= textSize.h;
13090 }
13091 }
13092
13093 function drawPointLabels(scale) {
13094 var ctx = scale.ctx;
13095 var valueOrDefault = helpers.valueOrDefault;
13096 var opts = scale.options;
13097 var angleLineOpts = opts.angleLines;
13098 var pointLabelOpts = opts.pointLabels;
13099
13100 ctx.lineWidth = angleLineOpts.lineWidth;
13101 ctx.strokeStyle = angleLineOpts.color;
13102
13103 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
13104
13105 // Point Label Font
13106 var plFont = getPointLabelFontOptions(scale);
13107
13108 ctx.textBaseline = 'top';
13109
13110 for (var i = getValueCount(scale) - 1; i >= 0; i--) {
13111 if (angleLineOpts.display) {
13112 var outerPosition = scale.getPointPosition(i, outerDistance);
13113 ctx.beginPath();
13114 ctx.moveTo(scale.xCenter, scale.yCenter);
13115 ctx.lineTo(outerPosition.x, outerPosition.y);
13116 ctx.stroke();
13117 ctx.closePath();
13118 }
13119
13120 if (pointLabelOpts.display) {
13121 // Extra 3px out for some label spacing
13122 var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
13123
13124 // Keep this in loop since we may support array properties here
13125 var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
13126 ctx.font = plFont.font;
13127 ctx.fillStyle = pointLabelFontColor;
13128
13129 var angleRadians = scale.getIndexAngle(i);
13130 var angle = helpers.toDegrees(angleRadians);
13131 ctx.textAlign = getTextAlignForAngle(angle);
13132 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
13133 fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
13134 }
13135 }
13136 }
13137
13138 function drawRadiusLine(scale, gridLineOpts, radius, index) {
13139 var ctx = scale.ctx;
13140 ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1);
13141 ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
13142
13143 if (scale.options.gridLines.circular) {
13144 // Draw circular arcs between the points
13145 ctx.beginPath();
13146 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
13147 ctx.closePath();
13148 ctx.stroke();
13149 } else {
13150 // Draw straight lines connecting each index
13151 var valueCount = getValueCount(scale);
13152
13153 if (valueCount === 0) {
13154 return;
13155 }
13156
13157 ctx.beginPath();
13158 var pointPosition = scale.getPointPosition(0, radius);
13159 ctx.moveTo(pointPosition.x, pointPosition.y);
13160
13161 for (var i = 1; i < valueCount; i++) {
13162 pointPosition = scale.getPointPosition(i, radius);
13163 ctx.lineTo(pointPosition.x, pointPosition.y);
13164 }
13165
13166 ctx.closePath();
13167 ctx.stroke();
13168 }
13169 }
13170
13171 function numberOrZero(param) {
13172 return helpers.isNumber(param) ? param : 0;
13173 }
13174
13175 var LinearRadialScale = Chart.LinearScaleBase.extend({
13176 setDimensions: function() {
13177 var me = this;
13178 var opts = me.options;
13179 var tickOpts = opts.ticks;
13180 // Set the unconstrained dimension before label rotation
13181 me.width = me.maxWidth;
13182 me.height = me.maxHeight;
13183 me.xCenter = Math.round(me.width / 2);
13184 me.yCenter = Math.round(me.height / 2);
13185
13186 var minSize = helpers.min([me.height, me.width]);
13187 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13188 me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
13189 },
13190 determineDataLimits: function() {
13191 var me = this;
13192 var chart = me.chart;
13193 var min = Number.POSITIVE_INFINITY;
13194 var max = Number.NEGATIVE_INFINITY;
13195
13196 helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
13197 if (chart.isDatasetVisible(datasetIndex)) {
13198 var meta = chart.getDatasetMeta(datasetIndex);
13199
13200 helpers.each(dataset.data, function(rawValue, index) {
13201 var value = +me.getRightValue(rawValue);
13202 if (isNaN(value) || meta.data[index].hidden) {
13203 return;
13204 }
13205
13206 min = Math.min(value, min);
13207 max = Math.max(value, max);
13208 });
13209 }
13210 });
13211
13212 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
13213 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
13214
13215 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13216 me.handleTickRangeOptions();
13217 },
13218 getTickLimit: function() {
13219 var tickOpts = this.options.ticks;
13220 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13221 return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
13222 },
13223 convertTicksToLabels: function() {
13224 var me = this;
13225
13226 Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
13227
13228 // Point labels
13229 me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
13230 },
13231 getLabelForIndex: function(index, datasetIndex) {
13232 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
13233 },
13234 fit: function() {
13235 if (this.options.pointLabels.display) {
13236 fitWithPointLabels(this);
13237 } else {
13238 fit(this);
13239 }
13240 },
13241 /**
13242 * Set radius reductions and determine new radius and center point
13243 * @private
13244 */
13245 setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
13246 var me = this;
13247 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
13248 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
13249 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
13250 var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
13251
13252 radiusReductionLeft = numberOrZero(radiusReductionLeft);
13253 radiusReductionRight = numberOrZero(radiusReductionRight);
13254 radiusReductionTop = numberOrZero(radiusReductionTop);
13255 radiusReductionBottom = numberOrZero(radiusReductionBottom);
13256
13257 me.drawingArea = Math.min(
13258 Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
13259 Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
13260 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
13261 },
13262 setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
13263 var me = this;
13264 var maxRight = me.width - rightMovement - me.drawingArea;
13265 var maxLeft = leftMovement + me.drawingArea;
13266 var maxTop = topMovement + me.drawingArea;
13267 var maxBottom = me.height - bottomMovement - me.drawingArea;
13268
13269 me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
13270 me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
13271 },
13272
13273 getIndexAngle: function(index) {
13274 var angleMultiplier = (Math.PI * 2) / getValueCount(this);
13275 var startAngle = this.chart.options && this.chart.options.startAngle ?
13276 this.chart.options.startAngle :
13277 0;
13278
13279 var startAngleRadians = startAngle * Math.PI * 2 / 360;
13280
13281 // Start from the top instead of right, so remove a quarter of the circle
13282 return index * angleMultiplier + startAngleRadians;
13283 },
13284 getDistanceFromCenterForValue: function(value) {
13285 var me = this;
13286
13287 if (value === null) {
13288 return 0; // null always in center
13289 }
13290
13291 // Take into account half font size + the yPadding of the top value
13292 var scalingFactor = me.drawingArea / (me.max - me.min);
13293 if (me.options.ticks.reverse) {
13294 return (me.max - value) * scalingFactor;
13295 }
13296 return (value - me.min) * scalingFactor;
13297 },
13298 getPointPosition: function(index, distanceFromCenter) {
13299 var me = this;
13300 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
13301 return {
13302 x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
13303 y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
13304 };
13305 },
13306 getPointPositionForValue: function(index, value) {
13307 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
13308 },
13309
13310 getBasePosition: function() {
13311 var me = this;
13312 var min = me.min;
13313 var max = me.max;
13314
13315 return me.getPointPositionForValue(0,
13316 me.beginAtZero ? 0 :
13317 min < 0 && max < 0 ? max :
13318 min > 0 && max > 0 ? min :
13319 0);
13320 },
13321
13322 draw: function() {
13323 var me = this;
13324 var opts = me.options;
13325 var gridLineOpts = opts.gridLines;
13326 var tickOpts = opts.ticks;
13327 var valueOrDefault = helpers.valueOrDefault;
13328
13329 if (opts.display) {
13330 var ctx = me.ctx;
13331 var startAngle = this.getIndexAngle(0);
13332
13333 // Tick Font
13334 var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13335 var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
13336 var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
13337 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
13338
13339 helpers.each(me.ticks, function(label, index) {
13340 // Don't draw a centre value (if it is minimum)
13341 if (index > 0 || tickOpts.reverse) {
13342 var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13343
13344 // Draw circular lines around the scale
13345 if (gridLineOpts.display && index !== 0) {
13346 drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
13347 }
13348
13349 if (tickOpts.display) {
13350 var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
13351 ctx.font = tickLabelFont;
13352
13353 ctx.save();
13354 ctx.translate(me.xCenter, me.yCenter);
13355 ctx.rotate(startAngle);
13356
13357 if (tickOpts.showLabelBackdrop) {
13358 var labelWidth = ctx.measureText(label).width;
13359 ctx.fillStyle = tickOpts.backdropColor;
13360 ctx.fillRect(
13361 -labelWidth / 2 - tickOpts.backdropPaddingX,
13362 -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY,
13363 labelWidth + tickOpts.backdropPaddingX * 2,
13364 tickFontSize + tickOpts.backdropPaddingY * 2
13365 );
13366 }
13367
13368 ctx.textAlign = 'center';
13369 ctx.textBaseline = 'middle';
13370 ctx.fillStyle = tickFontColor;
13371 ctx.fillText(label, 0, -yCenterOffset);
13372 ctx.restore();
13373 }
13374 }
13375 });
13376
13377 if (opts.angleLines.display || opts.pointLabels.display) {
13378 drawPointLabels(me);
13379 }
13380 }
13381 }
13382 });
13383 Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
13384
13385 };
13386
13387 },{"25":25,"34":34,"45":45}],57:[function(require,module,exports){
13388 /* global window: false */
13389 'use strict';
13390
13391 var moment = require(1);
13392 moment = typeof moment === 'function' ? moment : window.moment;
13393
13394 var defaults = require(25);
13395 var helpers = require(45);
13396
13397 // Integer constants are from the ES6 spec.
13398 var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
13399 var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
13400
13401 var INTERVALS = {
13402 millisecond: {
13403 common: true,
13404 size: 1,
13405 steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
13406 },
13407 second: {
13408 common: true,
13409 size: 1000,
13410 steps: [1, 2, 5, 10, 30]
13411 },
13412 minute: {
13413 common: true,
13414 size: 60000,
13415 steps: [1, 2, 5, 10, 30]
13416 },
13417 hour: {
13418 common: true,
13419 size: 3600000,
13420 steps: [1, 2, 3, 6, 12]
13421 },
13422 day: {
13423 common: true,
13424 size: 86400000,
13425 steps: [1, 2, 5]
13426 },
13427 week: {
13428 common: false,
13429 size: 604800000,
13430 steps: [1, 2, 3, 4]
13431 },
13432 month: {
13433 common: true,
13434 size: 2.628e9,
13435 steps: [1, 2, 3]
13436 },
13437 quarter: {
13438 common: false,
13439 size: 7.884e9,
13440 steps: [1, 2, 3, 4]
13441 },
13442 year: {
13443 common: true,
13444 size: 3.154e10
13445 }
13446 };
13447
13448 var UNITS = Object.keys(INTERVALS);
13449
13450 function sorter(a, b) {
13451 return a - b;
13452 }
13453
13454 function arrayUnique(items) {
13455 var hash = {};
13456 var out = [];
13457 var i, ilen, item;
13458
13459 for (i = 0, ilen = items.length; i < ilen; ++i) {
13460 item = items[i];
13461 if (!hash[item]) {
13462 hash[item] = true;
13463 out.push(item);
13464 }
13465 }
13466
13467 return out;
13468 }
13469
13470 /**
13471 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
13472 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
13473 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
13474 * extremity (left + width or top + height). Note that it would be more optimized to directly
13475 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
13476 * to create the lookup table. The table ALWAYS contains at least two items: min and max.
13477 *
13478 * @param {Number[]} timestamps - timestamps sorted from lowest to highest.
13479 * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
13480 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
13481 * If 'series', timestamps will be positioned at the same distance from each other. In this
13482 * case, only timestamps that break the time linearity are registered, meaning that in the
13483 * best case, all timestamps are linear, the table contains only min and max.
13484 */
13485 function buildLookupTable(timestamps, min, max, distribution) {
13486 if (distribution === 'linear' || !timestamps.length) {
13487 return [
13488 {time: min, pos: 0},
13489 {time: max, pos: 1}
13490 ];
13491 }
13492
13493 var table = [];
13494 var items = [min];
13495 var i, ilen, prev, curr, next;
13496
13497 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13498 curr = timestamps[i];
13499 if (curr > min && curr < max) {
13500 items.push(curr);
13501 }
13502 }
13503
13504 items.push(max);
13505
13506 for (i = 0, ilen = items.length; i < ilen; ++i) {
13507 next = items[i + 1];
13508 prev = items[i - 1];
13509 curr = items[i];
13510
13511 // only add points that breaks the scale linearity
13512 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
13513 table.push({time: curr, pos: i / (ilen - 1)});
13514 }
13515 }
13516
13517 return table;
13518 }
13519
13520 // @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
13521 function lookup(table, key, value) {
13522 var lo = 0;
13523 var hi = table.length - 1;
13524 var mid, i0, i1;
13525
13526 while (lo >= 0 && lo <= hi) {
13527 mid = (lo + hi) >> 1;
13528 i0 = table[mid - 1] || null;
13529 i1 = table[mid];
13530
13531 if (!i0) {
13532 // given value is outside table (before first item)
13533 return {lo: null, hi: i1};
13534 } else if (i1[key] < value) {
13535 lo = mid + 1;
13536 } else if (i0[key] > value) {
13537 hi = mid - 1;
13538 } else {
13539 return {lo: i0, hi: i1};
13540 }
13541 }
13542
13543 // given value is outside table (after last item)
13544 return {lo: i1, hi: null};
13545 }
13546
13547 /**
13548 * Linearly interpolates the given source `value` using the table items `skey` values and
13549 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
13550 * returns the position for a timestamp equal to 42. If value is out of bounds, values at
13551 * index [0, 1] or [n - 1, n] are used for the interpolation.
13552 */
13553 function interpolate(table, skey, sval, tkey) {
13554 var range = lookup(table, skey, sval);
13555
13556 // Note: the lookup table ALWAYS contains at least 2 items (min and max)
13557 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
13558 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
13559
13560 var span = next[skey] - prev[skey];
13561 var ratio = span ? (sval - prev[skey]) / span : 0;
13562 var offset = (next[tkey] - prev[tkey]) * ratio;
13563
13564 return prev[tkey] + offset;
13565 }
13566
13567 /**
13568 * Convert the given value to a moment object using the given time options.
13569 * @see http://momentjs.com/docs/#/parsing/
13570 */
13571 function momentify(value, options) {
13572 var parser = options.parser;
13573 var format = options.parser || options.format;
13574
13575 if (typeof parser === 'function') {
13576 return parser(value);
13577 }
13578
13579 if (typeof value === 'string' && typeof format === 'string') {
13580 return moment(value, format);
13581 }
13582
13583 if (!(value instanceof moment)) {
13584 value = moment(value);
13585 }
13586
13587 if (value.isValid()) {
13588 return value;
13589 }
13590
13591 // Labels are in an incompatible moment format and no `parser` has been provided.
13592 // The user might still use the deprecated `format` option to convert his inputs.
13593 if (typeof format === 'function') {
13594 return format(value);
13595 }
13596
13597 return value;
13598 }
13599
13600 function parse(input, scale) {
13601 if (helpers.isNullOrUndef(input)) {
13602 return null;
13603 }
13604
13605 var options = scale.options.time;
13606 var value = momentify(scale.getRightValue(input), options);
13607 if (!value.isValid()) {
13608 return null;
13609 }
13610
13611 if (options.round) {
13612 value.startOf(options.round);
13613 }
13614
13615 return value.valueOf();
13616 }
13617
13618 /**
13619 * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
13620 * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
13621 */
13622 function determineStepSize(min, max, unit, capacity) {
13623 var range = max - min;
13624 var interval = INTERVALS[unit];
13625 var milliseconds = interval.size;
13626 var steps = interval.steps;
13627 var i, ilen, factor;
13628
13629 if (!steps) {
13630 return Math.ceil(range / ((capacity || 1) * milliseconds));
13631 }
13632
13633 for (i = 0, ilen = steps.length; i < ilen; ++i) {
13634 factor = steps[i];
13635 if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
13636 break;
13637 }
13638 }
13639
13640 return factor;
13641 }
13642
13643 /**
13644 * Figures out what unit results in an appropriate number of auto-generated ticks
13645 */
13646 function determineUnitForAutoTicks(minUnit, min, max, capacity) {
13647 var ilen = UNITS.length;
13648 var i, interval, factor;
13649
13650 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
13651 interval = INTERVALS[UNITS[i]];
13652 factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
13653
13654 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
13655 return UNITS[i];
13656 }
13657 }
13658
13659 return UNITS[ilen - 1];
13660 }
13661
13662 /**
13663 * Figures out what unit to format a set of ticks with
13664 */
13665 function determineUnitForFormatting(ticks, minUnit, min, max) {
13666 var duration = moment.duration(moment(max).diff(moment(min)));
13667 var ilen = UNITS.length;
13668 var i, unit;
13669
13670 for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
13671 unit = UNITS[i];
13672 if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) {
13673 return unit;
13674 }
13675 }
13676
13677 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
13678 }
13679
13680 function determineMajorUnit(unit) {
13681 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
13682 if (INTERVALS[UNITS[i]].common) {
13683 return UNITS[i];
13684 }
13685 }
13686 }
13687
13688 /**
13689 * Generates a maximum of `capacity` timestamps between min and max, rounded to the
13690 * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
13691 * Important: this method can return ticks outside the min and max range, it's the
13692 * responsibility of the calling code to clamp values if needed.
13693 */
13694 function generate(min, max, capacity, options) {
13695 var timeOpts = options.time;
13696 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
13697 var major = determineMajorUnit(minor);
13698 var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
13699 var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
13700 var majorTicksEnabled = options.ticks.major.enabled;
13701 var interval = INTERVALS[minor];
13702 var first = moment(min);
13703 var last = moment(max);
13704 var ticks = [];
13705 var time;
13706
13707 if (!stepSize) {
13708 stepSize = determineStepSize(min, max, minor, capacity);
13709 }
13710
13711 // For 'week' unit, handle the first day of week option
13712 if (weekday) {
13713 first = first.isoWeekday(weekday);
13714 last = last.isoWeekday(weekday);
13715 }
13716
13717 // Align first/last ticks on unit
13718 first = first.startOf(weekday ? 'day' : minor);
13719 last = last.startOf(weekday ? 'day' : minor);
13720
13721 // Make sure that the last tick include max
13722 if (last < max) {
13723 last.add(1, minor);
13724 }
13725
13726 time = moment(first);
13727
13728 if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
13729 // Align the first tick on the previous `minor` unit aligned on the `major` unit:
13730 // we first aligned time on the previous `major` unit then add the number of full
13731 // stepSize there is between first and the previous major time.
13732 time.startOf(major);
13733 time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
13734 }
13735
13736 for (; time < last; time.add(stepSize, minor)) {
13737 ticks.push(+time);
13738 }
13739
13740 ticks.push(+time);
13741
13742 return ticks;
13743 }
13744
13745 /**
13746 * Returns the right and left offsets from edges in the form of {left, right}.
13747 * Offsets are added when the `offset` option is true.
13748 */
13749 function computeOffsets(table, ticks, min, max, options) {
13750 var left = 0;
13751 var right = 0;
13752 var upper, lower;
13753
13754 if (options.offset && ticks.length) {
13755 if (!options.time.min) {
13756 upper = ticks.length > 1 ? ticks[1] : max;
13757 lower = ticks[0];
13758 left = (
13759 interpolate(table, 'time', upper, 'pos') -
13760 interpolate(table, 'time', lower, 'pos')
13761 ) / 2;
13762 }
13763 if (!options.time.max) {
13764 upper = ticks[ticks.length - 1];
13765 lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
13766 right = (
13767 interpolate(table, 'time', upper, 'pos') -
13768 interpolate(table, 'time', lower, 'pos')
13769 ) / 2;
13770 }
13771 }
13772
13773 return {left: left, right: right};
13774 }
13775
13776 function ticksFromTimestamps(values, majorUnit) {
13777 var ticks = [];
13778 var i, ilen, value, major;
13779
13780 for (i = 0, ilen = values.length; i < ilen; ++i) {
13781 value = values[i];
13782 major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
13783
13784 ticks.push({
13785 value: value,
13786 major: major
13787 });
13788 }
13789
13790 return ticks;
13791 }
13792
13793 module.exports = function(Chart) {
13794
13795 var defaultConfig = {
13796 position: 'bottom',
13797
13798 /**
13799 * Data distribution along the scale:
13800 * - 'linear': data are spread according to their time (distances can vary),
13801 * - 'series': data are spread at the same distance from each other.
13802 * @see https://github.com/chartjs/Chart.js/pull/4507
13803 * @since 2.7.0
13804 */
13805 distribution: 'linear',
13806
13807 /**
13808 * Scale boundary strategy (bypassed by min/max time options)
13809 * - `data`: make sure data are fully visible, ticks outside are removed
13810 * - `ticks`: make sure ticks are fully visible, data outside are truncated
13811 * @see https://github.com/chartjs/Chart.js/pull/4556
13812 * @since 2.7.0
13813 */
13814 bounds: 'data',
13815
13816 time: {
13817 parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
13818 format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
13819 unit: false, // false == automatic or override with week, month, year, etc.
13820 round: false, // none, or override with week, month, year, etc.
13821 displayFormat: false, // DEPRECATED
13822 isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
13823 minUnit: 'millisecond',
13824
13825 // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
13826 displayFormats: {
13827 millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
13828 second: 'h:mm:ss a', // 11:20:01 AM
13829 minute: 'h:mm a', // 11:20 AM
13830 hour: 'hA', // 5PM
13831 day: 'MMM D', // Sep 4
13832 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
13833 month: 'MMM YYYY', // Sept 2015
13834 quarter: '[Q]Q - YYYY', // Q3
13835 year: 'YYYY' // 2015
13836 },
13837 },
13838 ticks: {
13839 autoSkip: false,
13840
13841 /**
13842 * Ticks generation input values:
13843 * - 'auto': generates "optimal" ticks based on scale size and time options.
13844 * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
13845 * - 'labels': generates ticks from user given `data.labels` values ONLY.
13846 * @see https://github.com/chartjs/Chart.js/pull/4507
13847 * @since 2.7.0
13848 */
13849 source: 'auto',
13850
13851 major: {
13852 enabled: false
13853 }
13854 }
13855 };
13856
13857 var TimeScale = Chart.Scale.extend({
13858 initialize: function() {
13859 if (!moment) {
13860 throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
13861 }
13862
13863 this.mergeTicksOptions();
13864
13865 Chart.Scale.prototype.initialize.call(this);
13866 },
13867
13868 update: function() {
13869 var me = this;
13870 var options = me.options;
13871
13872 // DEPRECATIONS: output a message only one time per update
13873 if (options.time && options.time.format) {
13874 console.warn('options.time.format is deprecated and replaced by options.time.parser.');
13875 }
13876
13877 return Chart.Scale.prototype.update.apply(me, arguments);
13878 },
13879
13880 /**
13881 * Allows data to be referenced via 't' attribute
13882 */
13883 getRightValue: function(rawValue) {
13884 if (rawValue && rawValue.t !== undefined) {
13885 rawValue = rawValue.t;
13886 }
13887 return Chart.Scale.prototype.getRightValue.call(this, rawValue);
13888 },
13889
13890 determineDataLimits: function() {
13891 var me = this;
13892 var chart = me.chart;
13893 var timeOpts = me.options.time;
13894 var min = MAX_INTEGER;
13895 var max = MIN_INTEGER;
13896 var timestamps = [];
13897 var datasets = [];
13898 var labels = [];
13899 var i, j, ilen, jlen, data, timestamp;
13900
13901 // Convert labels to timestamps
13902 for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) {
13903 labels.push(parse(chart.data.labels[i], me));
13904 }
13905
13906 // Convert data to timestamps
13907 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
13908 if (chart.isDatasetVisible(i)) {
13909 data = chart.data.datasets[i].data;
13910
13911 // Let's consider that all data have the same format.
13912 if (helpers.isObject(data[0])) {
13913 datasets[i] = [];
13914
13915 for (j = 0, jlen = data.length; j < jlen; ++j) {
13916 timestamp = parse(data[j], me);
13917 timestamps.push(timestamp);
13918 datasets[i][j] = timestamp;
13919 }
13920 } else {
13921 timestamps.push.apply(timestamps, labels);
13922 datasets[i] = labels.slice(0);
13923 }
13924 } else {
13925 datasets[i] = [];
13926 }
13927 }
13928
13929 if (labels.length) {
13930 // Sort labels **after** data have been converted
13931 labels = arrayUnique(labels).sort(sorter);
13932 min = Math.min(min, labels[0]);
13933 max = Math.max(max, labels[labels.length - 1]);
13934 }
13935
13936 if (timestamps.length) {
13937 timestamps = arrayUnique(timestamps).sort(sorter);
13938 min = Math.min(min, timestamps[0]);
13939 max = Math.max(max, timestamps[timestamps.length - 1]);
13940 }
13941
13942 min = parse(timeOpts.min, me) || min;
13943 max = parse(timeOpts.max, me) || max;
13944
13945 // In case there is no valid min/max, let's use today limits
13946 min = min === MAX_INTEGER ? +moment().startOf('day') : min;
13947 max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max;
13948
13949 // Make sure that max is strictly higher than min (required by the lookup table)
13950 me.min = Math.min(min, max);
13951 me.max = Math.max(min + 1, max);
13952
13953 // PRIVATE
13954 me._horizontal = me.isHorizontal();
13955 me._table = [];
13956 me._timestamps = {
13957 data: timestamps,
13958 datasets: datasets,
13959 labels: labels
13960 };
13961 },
13962
13963 buildTicks: function() {
13964 var me = this;
13965 var min = me.min;
13966 var max = me.max;
13967 var options = me.options;
13968 var timeOpts = options.time;
13969 var timestamps = [];
13970 var ticks = [];
13971 var i, ilen, timestamp;
13972
13973 switch (options.ticks.source) {
13974 case 'data':
13975 timestamps = me._timestamps.data;
13976 break;
13977 case 'labels':
13978 timestamps = me._timestamps.labels;
13979 break;
13980 case 'auto':
13981 default:
13982 timestamps = generate(min, max, me.getLabelCapacity(min), options);
13983 }
13984
13985 if (options.bounds === 'ticks' && timestamps.length) {
13986 min = timestamps[0];
13987 max = timestamps[timestamps.length - 1];
13988 }
13989
13990 // Enforce limits with user min/max options
13991 min = parse(timeOpts.min, me) || min;
13992 max = parse(timeOpts.max, me) || max;
13993
13994 // Remove ticks outside the min/max range
13995 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13996 timestamp = timestamps[i];
13997 if (timestamp >= min && timestamp <= max) {
13998 ticks.push(timestamp);
13999 }
14000 }
14001
14002 me.min = min;
14003 me.max = max;
14004
14005 // PRIVATE
14006 me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max);
14007 me._majorUnit = determineMajorUnit(me._unit);
14008 me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
14009 me._offsets = computeOffsets(me._table, ticks, min, max, options);
14010
14011 return ticksFromTimestamps(ticks, me._majorUnit);
14012 },
14013
14014 getLabelForIndex: function(index, datasetIndex) {
14015 var me = this;
14016 var data = me.chart.data;
14017 var timeOpts = me.options.time;
14018 var label = data.labels && index < data.labels.length ? data.labels[index] : '';
14019 var value = data.datasets[datasetIndex].data[index];
14020
14021 if (helpers.isObject(value)) {
14022 label = me.getRightValue(value);
14023 }
14024 if (timeOpts.tooltipFormat) {
14025 label = momentify(label, timeOpts).format(timeOpts.tooltipFormat);
14026 }
14027
14028 return label;
14029 },
14030
14031 /**
14032 * Function to format an individual tick mark
14033 * @private
14034 */
14035 tickFormatFunction: function(tick, index, ticks, formatOverride) {
14036 var me = this;
14037 var options = me.options;
14038 var time = tick.valueOf();
14039 var formats = options.time.displayFormats;
14040 var minorFormat = formats[me._unit];
14041 var majorUnit = me._majorUnit;
14042 var majorFormat = formats[majorUnit];
14043 var majorTime = tick.clone().startOf(majorUnit).valueOf();
14044 var majorTickOpts = options.ticks.major;
14045 var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
14046 var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat);
14047 var tickOpts = major ? majorTickOpts : options.ticks.minor;
14048 var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback);
14049
14050 return formatter ? formatter(label, index, ticks) : label;
14051 },
14052
14053 convertTicksToLabels: function(ticks) {
14054 var labels = [];
14055 var i, ilen;
14056
14057 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
14058 labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
14059 }
14060
14061 return labels;
14062 },
14063
14064 /**
14065 * @private
14066 */
14067 getPixelForOffset: function(time) {
14068 var me = this;
14069 var size = me._horizontal ? me.width : me.height;
14070 var start = me._horizontal ? me.left : me.top;
14071 var pos = interpolate(me._table, 'time', time, 'pos');
14072
14073 return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
14074 },
14075
14076 getPixelForValue: function(value, index, datasetIndex) {
14077 var me = this;
14078 var time = null;
14079
14080 if (index !== undefined && datasetIndex !== undefined) {
14081 time = me._timestamps.datasets[datasetIndex][index];
14082 }
14083
14084 if (time === null) {
14085 time = parse(value, me);
14086 }
14087
14088 if (time !== null) {
14089 return me.getPixelForOffset(time);
14090 }
14091 },
14092
14093 getPixelForTick: function(index) {
14094 var ticks = this.getTicks();
14095 return index >= 0 && index < ticks.length ?
14096 this.getPixelForOffset(ticks[index].value) :
14097 null;
14098 },
14099
14100 getValueForPixel: function(pixel) {
14101 var me = this;
14102 var size = me._horizontal ? me.width : me.height;
14103 var start = me._horizontal ? me.left : me.top;
14104 var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
14105 var time = interpolate(me._table, 'pos', pos, 'time');
14106
14107 return moment(time);
14108 },
14109
14110 /**
14111 * Crude approximation of what the label width might be
14112 * @private
14113 */
14114 getLabelWidth: function(label) {
14115 var me = this;
14116 var ticksOpts = me.options.ticks;
14117 var tickLabelWidth = me.ctx.measureText(label).width;
14118 var angle = helpers.toRadians(ticksOpts.maxRotation);
14119 var cosRotation = Math.cos(angle);
14120 var sinRotation = Math.sin(angle);
14121 var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
14122
14123 return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
14124 },
14125
14126 /**
14127 * @private
14128 */
14129 getLabelCapacity: function(exampleTime) {
14130 var me = this;
14131
14132 var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
14133
14134 var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride);
14135 var tickLabelWidth = me.getLabelWidth(exampleLabel);
14136 var innerWidth = me.isHorizontal() ? me.width : me.height;
14137
14138 return Math.floor(innerWidth / tickLabelWidth);
14139 }
14140 });
14141
14142 Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
14143 };
14144
14145 },{"1":1,"25":25,"45":45}]},{},[7])(7)
14146 });
14147