· 8 years ago · Jan 03, 2018, 03:50 PM
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(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
11
12},{}],2:[function(require,module,exports){
13/* MIT license */
14var colorNames = require(6);
15
16module.exports = {
17 getRgba: getRgba,
18 getHsla: getHsla,
19 getRgb: getRgb,
20 getHsl: getHsl,
21 getHwb: getHwb,
22 getAlpha: getAlpha,
23
24 hexString: hexString,
25 rgbString: rgbString,
26 rgbaString: rgbaString,
27 percentString: percentString,
28 percentaString: percentaString,
29 hslString: hslString,
30 hslaString: hslaString,
31 hwbString: hwbString,
32 keyword: keyword
33}
34
35function getRgba(string) {
36 if (!string) {
37 return;
38 }
39 var abbr = /^#([a-fA-F0-9]{3})$/i,
40 hex = /^#([a-fA-F0-9]{6})$/i,
41 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
42 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
43 keyword = /(\w+)/;
44
45 var rgb = [0, 0, 0],
46 a = 1,
47 match = string.match(abbr);
48 if (match) {
49 match = match[1];
50 for (var i = 0; i < rgb.length; i++) {
51 rgb[i] = parseInt(match[i] + match[i], 16);
52 }
53 }
54 else if (match = string.match(hex)) {
55 match = match[1];
56 for (var i = 0; i < rgb.length; i++) {
57 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
58 }
59 }
60 else if (match = string.match(rgba)) {
61 for (var i = 0; i < rgb.length; i++) {
62 rgb[i] = parseInt(match[i + 1]);
63 }
64 a = parseFloat(match[4]);
65 }
66 else if (match = string.match(per)) {
67 for (var i = 0; i < rgb.length; i++) {
68 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
69 }
70 a = parseFloat(match[4]);
71 }
72 else if (match = string.match(keyword)) {
73 if (match[1] == "transparent") {
74 return [0, 0, 0, 0];
75 }
76 rgb = colorNames[match[1]];
77 if (!rgb) {
78 return;
79 }
80 }
81
82 for (var i = 0; i < rgb.length; i++) {
83 rgb[i] = scale(rgb[i], 0, 255);
84 }
85 if (!a && a != 0) {
86 a = 1;
87 }
88 else {
89 a = scale(a, 0, 1);
90 }
91 rgb[3] = a;
92 return rgb;
93}
94
95function getHsla(string) {
96 if (!string) {
97 return;
98 }
99 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
100 var match = string.match(hsl);
101 if (match) {
102 var alpha = parseFloat(match[4]);
103 var h = scale(parseInt(match[1]), 0, 360),
104 s = scale(parseFloat(match[2]), 0, 100),
105 l = scale(parseFloat(match[3]), 0, 100),
106 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
107 return [h, s, l, a];
108 }
109}
110
111function getHwb(string) {
112 if (!string) {
113 return;
114 }
115 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
116 var match = string.match(hwb);
117 if (match) {
118 var alpha = parseFloat(match[4]);
119 var h = scale(parseInt(match[1]), 0, 360),
120 w = scale(parseFloat(match[2]), 0, 100),
121 b = scale(parseFloat(match[3]), 0, 100),
122 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
123 return [h, w, b, a];
124 }
125}
126
127function getRgb(string) {
128 var rgba = getRgba(string);
129 return rgba && rgba.slice(0, 3);
130}
131
132function getHsl(string) {
133 var hsla = getHsla(string);
134 return hsla && hsla.slice(0, 3);
135}
136
137function getAlpha(string) {
138 var vals = getRgba(string);
139 if (vals) {
140 return vals[3];
141 }
142 else if (vals = getHsla(string)) {
143 return vals[3];
144 }
145 else if (vals = getHwb(string)) {
146 return vals[3];
147 }
148}
149
150// generators
151function hexString(rgb) {
152 return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
153 + hexDouble(rgb[2]);
154}
155
156function rgbString(rgba, alpha) {
157 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
158 return rgbaString(rgba, alpha);
159 }
160 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
161}
162
163function rgbaString(rgba, alpha) {
164 if (alpha === undefined) {
165 alpha = (rgba[3] !== undefined ? rgba[3] : 1);
166 }
167 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
168 + ", " + alpha + ")";
169}
170
171function percentString(rgba, alpha) {
172 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
173 return percentaString(rgba, alpha);
174 }
175 var r = Math.round(rgba[0]/255 * 100),
176 g = Math.round(rgba[1]/255 * 100),
177 b = Math.round(rgba[2]/255 * 100);
178
179 return "rgb(" + r + "%, " + g + "%, " + b + "%)";
180}
181
182function percentaString(rgba, alpha) {
183 var r = Math.round(rgba[0]/255 * 100),
184 g = Math.round(rgba[1]/255 * 100),
185 b = Math.round(rgba[2]/255 * 100);
186 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
187}
188
189function hslString(hsla, alpha) {
190 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
191 return hslaString(hsla, alpha);
192 }
193 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
194}
195
196function hslaString(hsla, alpha) {
197 if (alpha === undefined) {
198 alpha = (hsla[3] !== undefined ? hsla[3] : 1);
199 }
200 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
201 + alpha + ")";
202}
203
204// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
205// (hwb have alpha optional & 1 is default value)
206function hwbString(hwb, alpha) {
207 if (alpha === undefined) {
208 alpha = (hwb[3] !== undefined ? hwb[3] : 1);
209 }
210 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
211 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
212}
213
214function keyword(rgb) {
215 return reverseNames[rgb.slice(0, 3)];
216}
217
218// helpers
219function scale(num, min, max) {
220 return Math.min(Math.max(min, num), max);
221}
222
223function hexDouble(num) {
224 var str = num.toString(16).toUpperCase();
225 return (str.length < 2) ? "0" + str : str;
226}
227
228
229//create a list of reverse color names
230var reverseNames = {};
231for (var name in colorNames) {
232 reverseNames[colorNames[name]] = name;
233}
234
235},{"6":6}],3:[function(require,module,exports){
236/* MIT license */
237var convert = require(5);
238var string = require(2);
239
240var Color = function (obj) {
241 if (obj instanceof Color) {
242 return obj;
243 }
244 if (!(this instanceof Color)) {
245 return new Color(obj);
246 }
247
248 this.valid = false;
249 this.values = {
250 rgb: [0, 0, 0],
251 hsl: [0, 0, 0],
252 hsv: [0, 0, 0],
253 hwb: [0, 0, 0],
254 cmyk: [0, 0, 0, 0],
255 alpha: 1
256 };
257
258 // parse Color() argument
259 var vals;
260 if (typeof obj === 'string') {
261 vals = string.getRgba(obj);
262 if (vals) {
263 this.setValues('rgb', vals);
264 } else if (vals = string.getHsla(obj)) {
265 this.setValues('hsl', vals);
266 } else if (vals = string.getHwb(obj)) {
267 this.setValues('hwb', vals);
268 }
269 } else if (typeof obj === 'object') {
270 vals = obj;
271 if (vals.r !== undefined || vals.red !== undefined) {
272 this.setValues('rgb', vals);
273 } else if (vals.l !== undefined || vals.lightness !== undefined) {
274 this.setValues('hsl', vals);
275 } else if (vals.v !== undefined || vals.value !== undefined) {
276 this.setValues('hsv', vals);
277 } else if (vals.w !== undefined || vals.whiteness !== undefined) {
278 this.setValues('hwb', vals);
279 } else if (vals.c !== undefined || vals.cyan !== undefined) {
280 this.setValues('cmyk', vals);
281 }
282 }
283};
284
285Color.prototype = {
286 isValid: function () {
287 return this.valid;
288 },
289 rgb: function () {
290 return this.setSpace('rgb', arguments);
291 },
292 hsl: function () {
293 return this.setSpace('hsl', arguments);
294 },
295 hsv: function () {
296 return this.setSpace('hsv', arguments);
297 },
298 hwb: function () {
299 return this.setSpace('hwb', arguments);
300 },
301 cmyk: function () {
302 return this.setSpace('cmyk', arguments);
303 },
304
305 rgbArray: function () {
306 return this.values.rgb;
307 },
308 hslArray: function () {
309 return this.values.hsl;
310 },
311 hsvArray: function () {
312 return this.values.hsv;
313 },
314 hwbArray: function () {
315 var values = this.values;
316 if (values.alpha !== 1) {
317 return values.hwb.concat([values.alpha]);
318 }
319 return values.hwb;
320 },
321 cmykArray: function () {
322 return this.values.cmyk;
323 },
324 rgbaArray: function () {
325 var values = this.values;
326 return values.rgb.concat([values.alpha]);
327 },
328 hslaArray: function () {
329 var values = this.values;
330 return values.hsl.concat([values.alpha]);
331 },
332 alpha: function (val) {
333 if (val === undefined) {
334 return this.values.alpha;
335 }
336 this.setValues('alpha', val);
337 return this;
338 },
339
340 red: function (val) {
341 return this.setChannel('rgb', 0, val);
342 },
343 green: function (val) {
344 return this.setChannel('rgb', 1, val);
345 },
346 blue: function (val) {
347 return this.setChannel('rgb', 2, val);
348 },
349 hue: function (val) {
350 if (val) {
351 val %= 360;
352 val = val < 0 ? 360 + val : val;
353 }
354 return this.setChannel('hsl', 0, val);
355 },
356 saturation: function (val) {
357 return this.setChannel('hsl', 1, val);
358 },
359 lightness: function (val) {
360 return this.setChannel('hsl', 2, val);
361 },
362 saturationv: function (val) {
363 return this.setChannel('hsv', 1, val);
364 },
365 whiteness: function (val) {
366 return this.setChannel('hwb', 1, val);
367 },
368 blackness: function (val) {
369 return this.setChannel('hwb', 2, val);
370 },
371 value: function (val) {
372 return this.setChannel('hsv', 2, val);
373 },
374 cyan: function (val) {
375 return this.setChannel('cmyk', 0, val);
376 },
377 magenta: function (val) {
378 return this.setChannel('cmyk', 1, val);
379 },
380 yellow: function (val) {
381 return this.setChannel('cmyk', 2, val);
382 },
383 black: function (val) {
384 return this.setChannel('cmyk', 3, val);
385 },
386
387 hexString: function () {
388 return string.hexString(this.values.rgb);
389 },
390 rgbString: function () {
391 return string.rgbString(this.values.rgb, this.values.alpha);
392 },
393 rgbaString: function () {
394 return string.rgbaString(this.values.rgb, this.values.alpha);
395 },
396 percentString: function () {
397 return string.percentString(this.values.rgb, this.values.alpha);
398 },
399 hslString: function () {
400 return string.hslString(this.values.hsl, this.values.alpha);
401 },
402 hslaString: function () {
403 return string.hslaString(this.values.hsl, this.values.alpha);
404 },
405 hwbString: function () {
406 return string.hwbString(this.values.hwb, this.values.alpha);
407 },
408 keyword: function () {
409 return string.keyword(this.values.rgb, this.values.alpha);
410 },
411
412 rgbNumber: function () {
413 var rgb = this.values.rgb;
414 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
415 },
416
417 luminosity: function () {
418 // http://www.w3.org/TR/WCAG20/#relativeluminancedef
419 var rgb = this.values.rgb;
420 var lum = [];
421 for (var i = 0; i < rgb.length; i++) {
422 var chan = rgb[i] / 255;
423 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
424 }
425 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
426 },
427
428 contrast: function (color2) {
429 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
430 var lum1 = this.luminosity();
431 var lum2 = color2.luminosity();
432 if (lum1 > lum2) {
433 return (lum1 + 0.05) / (lum2 + 0.05);
434 }
435 return (lum2 + 0.05) / (lum1 + 0.05);
436 },
437
438 level: function (color2) {
439 var contrastRatio = this.contrast(color2);
440 if (contrastRatio >= 7.1) {
441 return 'AAA';
442 }
443
444 return (contrastRatio >= 4.5) ? 'AA' : '';
445 },
446
447 dark: function () {
448 // YIQ equation from http://24ways.org/2010/calculating-color-contrast
449 var rgb = this.values.rgb;
450 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
451 return yiq < 128;
452 },
453
454 light: function () {
455 return !this.dark();
456 },
457
458 negate: function () {
459 var rgb = [];
460 for (var i = 0; i < 3; i++) {
461 rgb[i] = 255 - this.values.rgb[i];
462 }
463 this.setValues('rgb', rgb);
464 return this;
465 },
466
467 lighten: function (ratio) {
468 var hsl = this.values.hsl;
469 hsl[2] += hsl[2] * ratio;
470 this.setValues('hsl', hsl);
471 return this;
472 },
473
474 darken: function (ratio) {
475 var hsl = this.values.hsl;
476 hsl[2] -= hsl[2] * ratio;
477 this.setValues('hsl', hsl);
478 return this;
479 },
480
481 saturate: function (ratio) {
482 var hsl = this.values.hsl;
483 hsl[1] += hsl[1] * ratio;
484 this.setValues('hsl', hsl);
485 return this;
486 },
487
488 desaturate: function (ratio) {
489 var hsl = this.values.hsl;
490 hsl[1] -= hsl[1] * ratio;
491 this.setValues('hsl', hsl);
492 return this;
493 },
494
495 whiten: function (ratio) {
496 var hwb = this.values.hwb;
497 hwb[1] += hwb[1] * ratio;
498 this.setValues('hwb', hwb);
499 return this;
500 },
501
502 blacken: function (ratio) {
503 var hwb = this.values.hwb;
504 hwb[2] += hwb[2] * ratio;
505 this.setValues('hwb', hwb);
506 return this;
507 },
508
509 greyscale: function () {
510 var rgb = this.values.rgb;
511 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
512 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
513 this.setValues('rgb', [val, val, val]);
514 return this;
515 },
516
517 clearer: function (ratio) {
518 var alpha = this.values.alpha;
519 this.setValues('alpha', alpha - (alpha * ratio));
520 return this;
521 },
522
523 opaquer: function (ratio) {
524 var alpha = this.values.alpha;
525 this.setValues('alpha', alpha + (alpha * ratio));
526 return this;
527 },
528
529 rotate: function (degrees) {
530 var hsl = this.values.hsl;
531 var hue = (hsl[0] + degrees) % 360;
532 hsl[0] = hue < 0 ? 360 + hue : hue;
533 this.setValues('hsl', hsl);
534 return this;
535 },
536
537 /**
538 * Ported from sass implementation in C
539 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
540 */
541 mix: function (mixinColor, weight) {
542 var color1 = this;
543 var color2 = mixinColor;
544 var p = weight === undefined ? 0.5 : weight;
545
546 var w = 2 * p - 1;
547 var a = color1.alpha() - color2.alpha();
548
549 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
550 var w2 = 1 - w1;
551
552 return this
553 .rgb(
554 w1 * color1.red() + w2 * color2.red(),
555 w1 * color1.green() + w2 * color2.green(),
556 w1 * color1.blue() + w2 * color2.blue()
557 )
558 .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
559 },
560
561 toJSON: function () {
562 return this.rgb();
563 },
564
565 clone: function () {
566 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
567 // making the final build way to big to embed in Chart.js. So let's do it manually,
568 // assuming that values to clone are 1 dimension arrays containing only numbers,
569 // except 'alpha' which is a number.
570 var result = new Color();
571 var source = this.values;
572 var target = result.values;
573 var value, type;
574
575 for (var prop in source) {
576 if (source.hasOwnProperty(prop)) {
577 value = source[prop];
578 type = ({}).toString.call(value);
579 if (type === '[object Array]') {
580 target[prop] = value.slice(0);
581 } else if (type === '[object Number]') {
582 target[prop] = value;
583 } else {
584 console.error('unexpected color value:', value);
585 }
586 }
587 }
588
589 return result;
590 }
591};
592
593Color.prototype.spaces = {
594 rgb: ['red', 'green', 'blue'],
595 hsl: ['hue', 'saturation', 'lightness'],
596 hsv: ['hue', 'saturation', 'value'],
597 hwb: ['hue', 'whiteness', 'blackness'],
598 cmyk: ['cyan', 'magenta', 'yellow', 'black']
599};
600
601Color.prototype.maxes = {
602 rgb: [255, 255, 255],
603 hsl: [360, 100, 100],
604 hsv: [360, 100, 100],
605 hwb: [360, 100, 100],
606 cmyk: [100, 100, 100, 100]
607};
608
609Color.prototype.getValues = function (space) {
610 var values = this.values;
611 var vals = {};
612
613 for (var i = 0; i < space.length; i++) {
614 vals[space.charAt(i)] = values[space][i];
615 }
616
617 if (values.alpha !== 1) {
618 vals.a = values.alpha;
619 }
620
621 // {r: 255, g: 255, b: 255, a: 0.4}
622 return vals;
623};
624
625Color.prototype.setValues = function (space, vals) {
626 var values = this.values;
627 var spaces = this.spaces;
628 var maxes = this.maxes;
629 var alpha = 1;
630 var i;
631
632 this.valid = true;
633
634 if (space === 'alpha') {
635 alpha = vals;
636 } else if (vals.length) {
637 // [10, 10, 10]
638 values[space] = vals.slice(0, space.length);
639 alpha = vals[space.length];
640 } else if (vals[space.charAt(0)] !== undefined) {
641 // {r: 10, g: 10, b: 10}
642 for (i = 0; i < space.length; i++) {
643 values[space][i] = vals[space.charAt(i)];
644 }
645
646 alpha = vals.a;
647 } else if (vals[spaces[space][0]] !== undefined) {
648 // {red: 10, green: 10, blue: 10}
649 var chans = spaces[space];
650
651 for (i = 0; i < space.length; i++) {
652 values[space][i] = vals[chans[i]];
653 }
654
655 alpha = vals.alpha;
656 }
657
658 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
659
660 if (space === 'alpha') {
661 return false;
662 }
663
664 var capped;
665
666 // cap values of the space prior converting all values
667 for (i = 0; i < space.length; i++) {
668 capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
669 values[space][i] = Math.round(capped);
670 }
671
672 // convert to all the other color spaces
673 for (var sname in spaces) {
674 if (sname !== space) {
675 values[sname] = convert[space][sname](values[space]);
676 }
677 }
678
679 return true;
680};
681
682Color.prototype.setSpace = function (space, args) {
683 var vals = args[0];
684
685 if (vals === undefined) {
686 // color.rgb()
687 return this.getValues(space);
688 }
689
690 // color.rgb(10, 10, 10)
691 if (typeof vals === 'number') {
692 vals = Array.prototype.slice.call(args);
693 }
694
695 this.setValues(space, vals);
696 return this;
697};
698
699Color.prototype.setChannel = function (space, index, val) {
700 var svalues = this.values[space];
701 if (val === undefined) {
702 // color.red()
703 return svalues[index];
704 } else if (val === svalues[index]) {
705 // color.red(color.red())
706 return this;
707 }
708
709 // color.red(100)
710 svalues[index] = val;
711 this.setValues(space, svalues);
712
713 return this;
714};
715
716if (typeof window !== 'undefined') {
717 window.Color = Color;
718}
719
720module.exports = Color;
721
722},{"2":2,"5":5}],4:[function(require,module,exports){
723/* MIT license */
724
725module.exports = {
726 rgb2hsl: rgb2hsl,
727 rgb2hsv: rgb2hsv,
728 rgb2hwb: rgb2hwb,
729 rgb2cmyk: rgb2cmyk,
730 rgb2keyword: rgb2keyword,
731 rgb2xyz: rgb2xyz,
732 rgb2lab: rgb2lab,
733 rgb2lch: rgb2lch,
734
735 hsl2rgb: hsl2rgb,
736 hsl2hsv: hsl2hsv,
737 hsl2hwb: hsl2hwb,
738 hsl2cmyk: hsl2cmyk,
739 hsl2keyword: hsl2keyword,
740
741 hsv2rgb: hsv2rgb,
742 hsv2hsl: hsv2hsl,
743 hsv2hwb: hsv2hwb,
744 hsv2cmyk: hsv2cmyk,
745 hsv2keyword: hsv2keyword,
746
747 hwb2rgb: hwb2rgb,
748 hwb2hsl: hwb2hsl,
749 hwb2hsv: hwb2hsv,
750 hwb2cmyk: hwb2cmyk,
751 hwb2keyword: hwb2keyword,
752
753 cmyk2rgb: cmyk2rgb,
754 cmyk2hsl: cmyk2hsl,
755 cmyk2hsv: cmyk2hsv,
756 cmyk2hwb: cmyk2hwb,
757 cmyk2keyword: cmyk2keyword,
758
759 keyword2rgb: keyword2rgb,
760 keyword2hsl: keyword2hsl,
761 keyword2hsv: keyword2hsv,
762 keyword2hwb: keyword2hwb,
763 keyword2cmyk: keyword2cmyk,
764 keyword2lab: keyword2lab,
765 keyword2xyz: keyword2xyz,
766
767 xyz2rgb: xyz2rgb,
768 xyz2lab: xyz2lab,
769 xyz2lch: xyz2lch,
770
771 lab2xyz: lab2xyz,
772 lab2rgb: lab2rgb,
773 lab2lch: lab2lch,
774
775 lch2lab: lch2lab,
776 lch2xyz: lch2xyz,
777 lch2rgb: lch2rgb
778}
779
780
781function rgb2hsl(rgb) {
782 var r = rgb[0]/255,
783 g = rgb[1]/255,
784 b = rgb[2]/255,
785 min = Math.min(r, g, b),
786 max = Math.max(r, g, b),
787 delta = max - min,
788 h, s, l;
789
790 if (max == min)
791 h = 0;
792 else if (r == max)
793 h = (g - b) / delta;
794 else if (g == max)
795 h = 2 + (b - r) / delta;
796 else if (b == max)
797 h = 4 + (r - g)/ delta;
798
799 h = Math.min(h * 60, 360);
800
801 if (h < 0)
802 h += 360;
803
804 l = (min + max) / 2;
805
806 if (max == min)
807 s = 0;
808 else if (l <= 0.5)
809 s = delta / (max + min);
810 else
811 s = delta / (2 - max - min);
812
813 return [h, s * 100, l * 100];
814}
815
816function rgb2hsv(rgb) {
817 var r = rgb[0],
818 g = rgb[1],
819 b = rgb[2],
820 min = Math.min(r, g, b),
821 max = Math.max(r, g, b),
822 delta = max - min,
823 h, s, v;
824
825 if (max == 0)
826 s = 0;
827 else
828 s = (delta/max * 1000)/10;
829
830 if (max == min)
831 h = 0;
832 else if (r == max)
833 h = (g - b) / delta;
834 else if (g == max)
835 h = 2 + (b - r) / delta;
836 else if (b == max)
837 h = 4 + (r - g) / delta;
838
839 h = Math.min(h * 60, 360);
840
841 if (h < 0)
842 h += 360;
843
844 v = ((max / 255) * 1000) / 10;
845
846 return [h, s, v];
847}
848
849function rgb2hwb(rgb) {
850 var r = rgb[0],
851 g = rgb[1],
852 b = rgb[2],
853 h = rgb2hsl(rgb)[0],
854 w = 1/255 * Math.min(r, Math.min(g, b)),
855 b = 1 - 1/255 * Math.max(r, Math.max(g, b));
856
857 return [h, w * 100, b * 100];
858}
859
860function rgb2cmyk(rgb) {
861 var r = rgb[0] / 255,
862 g = rgb[1] / 255,
863 b = rgb[2] / 255,
864 c, m, y, k;
865
866 k = Math.min(1 - r, 1 - g, 1 - b);
867 c = (1 - r - k) / (1 - k) || 0;
868 m = (1 - g - k) / (1 - k) || 0;
869 y = (1 - b - k) / (1 - k) || 0;
870 return [c * 100, m * 100, y * 100, k * 100];
871}
872
873function rgb2keyword(rgb) {
874 return reverseKeywords[JSON.stringify(rgb)];
875}
876
877function rgb2xyz(rgb) {
878 var r = rgb[0] / 255,
879 g = rgb[1] / 255,
880 b = rgb[2] / 255;
881
882 // assume sRGB
883 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
884 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
885 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
886
887 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
888 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
889 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
890
891 return [x * 100, y *100, z * 100];
892}
893
894function rgb2lab(rgb) {
895 var xyz = rgb2xyz(rgb),
896 x = xyz[0],
897 y = xyz[1],
898 z = xyz[2],
899 l, a, b;
900
901 x /= 95.047;
902 y /= 100;
903 z /= 108.883;
904
905 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
906 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
907 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
908
909 l = (116 * y) - 16;
910 a = 500 * (x - y);
911 b = 200 * (y - z);
912
913 return [l, a, b];
914}
915
916function rgb2lch(args) {
917 return lab2lch(rgb2lab(args));
918}
919
920function hsl2rgb(hsl) {
921 var h = hsl[0] / 360,
922 s = hsl[1] / 100,
923 l = hsl[2] / 100,
924 t1, t2, t3, rgb, val;
925
926 if (s == 0) {
927 val = l * 255;
928 return [val, val, val];
929 }
930
931 if (l < 0.5)
932 t2 = l * (1 + s);
933 else
934 t2 = l + s - l * s;
935 t1 = 2 * l - t2;
936
937 rgb = [0, 0, 0];
938 for (var i = 0; i < 3; i++) {
939 t3 = h + 1 / 3 * - (i - 1);
940 t3 < 0 && t3++;
941 t3 > 1 && t3--;
942
943 if (6 * t3 < 1)
944 val = t1 + (t2 - t1) * 6 * t3;
945 else if (2 * t3 < 1)
946 val = t2;
947 else if (3 * t3 < 2)
948 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
949 else
950 val = t1;
951
952 rgb[i] = val * 255;
953 }
954
955 return rgb;
956}
957
958function hsl2hsv(hsl) {
959 var h = hsl[0],
960 s = hsl[1] / 100,
961 l = hsl[2] / 100,
962 sv, v;
963
964 if(l === 0) {
965 // no need to do calc on black
966 // also avoids divide by 0 error
967 return [0, 0, 0];
968 }
969
970 l *= 2;
971 s *= (l <= 1) ? l : 2 - l;
972 v = (l + s) / 2;
973 sv = (2 * s) / (l + s);
974 return [h, sv * 100, v * 100];
975}
976
977function hsl2hwb(args) {
978 return rgb2hwb(hsl2rgb(args));
979}
980
981function hsl2cmyk(args) {
982 return rgb2cmyk(hsl2rgb(args));
983}
984
985function hsl2keyword(args) {
986 return rgb2keyword(hsl2rgb(args));
987}
988
989
990function hsv2rgb(hsv) {
991 var h = hsv[0] / 60,
992 s = hsv[1] / 100,
993 v = hsv[2] / 100,
994 hi = Math.floor(h) % 6;
995
996 var f = h - Math.floor(h),
997 p = 255 * v * (1 - s),
998 q = 255 * v * (1 - (s * f)),
999 t = 255 * v * (1 - (s * (1 - f))),
1000 v = 255 * v;
1001
1002 switch(hi) {
1003 case 0:
1004 return [v, t, p];
1005 case 1:
1006 return [q, v, p];
1007 case 2:
1008 return [p, v, t];
1009 case 3:
1010 return [p, q, v];
1011 case 4:
1012 return [t, p, v];
1013 case 5:
1014 return [v, p, q];
1015 }
1016}
1017
1018function hsv2hsl(hsv) {
1019 var h = hsv[0],
1020 s = hsv[1] / 100,
1021 v = hsv[2] / 100,
1022 sl, l;
1023
1024 l = (2 - s) * v;
1025 sl = s * v;
1026 sl /= (l <= 1) ? l : 2 - l;
1027 sl = sl || 0;
1028 l /= 2;
1029 return [h, sl * 100, l * 100];
1030}
1031
1032function hsv2hwb(args) {
1033 return rgb2hwb(hsv2rgb(args))
1034}
1035
1036function hsv2cmyk(args) {
1037 return rgb2cmyk(hsv2rgb(args));
1038}
1039
1040function hsv2keyword(args) {
1041 return rgb2keyword(hsv2rgb(args));
1042}
1043
1044// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
1045function hwb2rgb(hwb) {
1046 var h = hwb[0] / 360,
1047 wh = hwb[1] / 100,
1048 bl = hwb[2] / 100,
1049 ratio = wh + bl,
1050 i, v, f, n;
1051
1052 // wh + bl cant be > 1
1053 if (ratio > 1) {
1054 wh /= ratio;
1055 bl /= ratio;
1056 }
1057
1058 i = Math.floor(6 * h);
1059 v = 1 - bl;
1060 f = 6 * h - i;
1061 if ((i & 0x01) != 0) {
1062 f = 1 - f;
1063 }
1064 n = wh + f * (v - wh); // linear interpolation
1065
1066 switch (i) {
1067 default:
1068 case 6:
1069 case 0: r = v; g = n; b = wh; break;
1070 case 1: r = n; g = v; b = wh; break;
1071 case 2: r = wh; g = v; b = n; break;
1072 case 3: r = wh; g = n; b = v; break;
1073 case 4: r = n; g = wh; b = v; break;
1074 case 5: r = v; g = wh; b = n; break;
1075 }
1076
1077 return [r * 255, g * 255, b * 255];
1078}
1079
1080function hwb2hsl(args) {
1081 return rgb2hsl(hwb2rgb(args));
1082}
1083
1084function hwb2hsv(args) {
1085 return rgb2hsv(hwb2rgb(args));
1086}
1087
1088function hwb2cmyk(args) {
1089 return rgb2cmyk(hwb2rgb(args));
1090}
1091
1092function hwb2keyword(args) {
1093 return rgb2keyword(hwb2rgb(args));
1094}
1095
1096function cmyk2rgb(cmyk) {
1097 var c = cmyk[0] / 100,
1098 m = cmyk[1] / 100,
1099 y = cmyk[2] / 100,
1100 k = cmyk[3] / 100,
1101 r, g, b;
1102
1103 r = 1 - Math.min(1, c * (1 - k) + k);
1104 g = 1 - Math.min(1, m * (1 - k) + k);
1105 b = 1 - Math.min(1, y * (1 - k) + k);
1106 return [r * 255, g * 255, b * 255];
1107}
1108
1109function cmyk2hsl(args) {
1110 return rgb2hsl(cmyk2rgb(args));
1111}
1112
1113function cmyk2hsv(args) {
1114 return rgb2hsv(cmyk2rgb(args));
1115}
1116
1117function cmyk2hwb(args) {
1118 return rgb2hwb(cmyk2rgb(args));
1119}
1120
1121function cmyk2keyword(args) {
1122 return rgb2keyword(cmyk2rgb(args));
1123}
1124
1125
1126function xyz2rgb(xyz) {
1127 var x = xyz[0] / 100,
1128 y = xyz[1] / 100,
1129 z = xyz[2] / 100,
1130 r, g, b;
1131
1132 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
1133 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
1134 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
1135
1136 // assume sRGB
1137 r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
1138 : r = (r * 12.92);
1139
1140 g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
1141 : g = (g * 12.92);
1142
1143 b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
1144 : b = (b * 12.92);
1145
1146 r = Math.min(Math.max(0, r), 1);
1147 g = Math.min(Math.max(0, g), 1);
1148 b = Math.min(Math.max(0, b), 1);
1149
1150 return [r * 255, g * 255, b * 255];
1151}
1152
1153function xyz2lab(xyz) {
1154 var x = xyz[0],
1155 y = xyz[1],
1156 z = xyz[2],
1157 l, a, b;
1158
1159 x /= 95.047;
1160 y /= 100;
1161 z /= 108.883;
1162
1163 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
1164 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
1165 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
1166
1167 l = (116 * y) - 16;
1168 a = 500 * (x - y);
1169 b = 200 * (y - z);
1170
1171 return [l, a, b];
1172}
1173
1174function xyz2lch(args) {
1175 return lab2lch(xyz2lab(args));
1176}
1177
1178function lab2xyz(lab) {
1179 var l = lab[0],
1180 a = lab[1],
1181 b = lab[2],
1182 x, y, z, y2;
1183
1184 if (l <= 8) {
1185 y = (l * 100) / 903.3;
1186 y2 = (7.787 * (y / 100)) + (16 / 116);
1187 } else {
1188 y = 100 * Math.pow((l + 16) / 116, 3);
1189 y2 = Math.pow(y / 100, 1/3);
1190 }
1191
1192 x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
1193
1194 z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
1195
1196 return [x, y, z];
1197}
1198
1199function lab2lch(lab) {
1200 var l = lab[0],
1201 a = lab[1],
1202 b = lab[2],
1203 hr, h, c;
1204
1205 hr = Math.atan2(b, a);
1206 h = hr * 360 / 2 / Math.PI;
1207 if (h < 0) {
1208 h += 360;
1209 }
1210 c = Math.sqrt(a * a + b * b);
1211 return [l, c, h];
1212}
1213
1214function lab2rgb(args) {
1215 return xyz2rgb(lab2xyz(args));
1216}
1217
1218function lch2lab(lch) {
1219 var l = lch[0],
1220 c = lch[1],
1221 h = lch[2],
1222 a, b, hr;
1223
1224 hr = h / 360 * 2 * Math.PI;
1225 a = c * Math.cos(hr);
1226 b = c * Math.sin(hr);
1227 return [l, a, b];
1228}
1229
1230function lch2xyz(args) {
1231 return lab2xyz(lch2lab(args));
1232}
1233
1234function lch2rgb(args) {
1235 return lab2rgb(lch2lab(args));
1236}
1237
1238function keyword2rgb(keyword) {
1239 return cssKeywords[keyword];
1240}
1241
1242function keyword2hsl(args) {
1243 return rgb2hsl(keyword2rgb(args));
1244}
1245
1246function keyword2hsv(args) {
1247 return rgb2hsv(keyword2rgb(args));
1248}
1249
1250function keyword2hwb(args) {
1251 return rgb2hwb(keyword2rgb(args));
1252}
1253
1254function keyword2cmyk(args) {
1255 return rgb2cmyk(keyword2rgb(args));
1256}
1257
1258function keyword2lab(args) {
1259 return rgb2lab(keyword2rgb(args));
1260}
1261
1262function keyword2xyz(args) {
1263 return rgb2xyz(keyword2rgb(args));
1264}
1265
1266var cssKeywords = {
1267 aliceblue: [240,248,255],
1268 antiquewhite: [250,235,215],
1269 aqua: [0,255,255],
1270 aquamarine: [127,255,212],
1271 azure: [240,255,255],
1272 beige: [245,245,220],
1273 bisque: [255,228,196],
1274 black: [0,0,0],
1275 blanchedalmond: [255,235,205],
1276 blue: [0,0,255],
1277 blueviolet: [138,43,226],
1278 brown: [165,42,42],
1279 burlywood: [222,184,135],
1280 cadetblue: [95,158,160],
1281 chartreuse: [127,255,0],
1282 chocolate: [210,105,30],
1283 coral: [255,127,80],
1284 cornflowerblue: [100,149,237],
1285 cornsilk: [255,248,220],
1286 crimson: [220,20,60],
1287 cyan: [0,255,255],
1288 darkblue: [0,0,139],
1289 darkcyan: [0,139,139],
1290 darkgoldenrod: [184,134,11],
1291 darkgray: [169,169,169],
1292 darkgreen: [0,100,0],
1293 darkgrey: [169,169,169],
1294 darkkhaki: [189,183,107],
1295 darkmagenta: [139,0,139],
1296 darkolivegreen: [85,107,47],
1297 darkorange: [255,140,0],
1298 darkorchid: [153,50,204],
1299 darkred: [139,0,0],
1300 darksalmon: [233,150,122],
1301 darkseagreen: [143,188,143],
1302 darkslateblue: [72,61,139],
1303 darkslategray: [47,79,79],
1304 darkslategrey: [47,79,79],
1305 darkturquoise: [0,206,209],
1306 darkviolet: [148,0,211],
1307 deeppink: [255,20,147],
1308 deepskyblue: [0,191,255],
1309 dimgray: [105,105,105],
1310 dimgrey: [105,105,105],
1311 dodgerblue: [30,144,255],
1312 firebrick: [178,34,34],
1313 floralwhite: [255,250,240],
1314 forestgreen: [34,139,34],
1315 fuchsia: [255,0,255],
1316 gainsboro: [220,220,220],
1317 ghostwhite: [248,248,255],
1318 gold: [255,215,0],
1319 goldenrod: [218,165,32],
1320 gray: [128,128,128],
1321 green: [0,128,0],
1322 greenyellow: [173,255,47],
1323 grey: [128,128,128],
1324 honeydew: [240,255,240],
1325 hotpink: [255,105,180],
1326 indianred: [205,92,92],
1327 indigo: [75,0,130],
1328 ivory: [255,255,240],
1329 khaki: [240,230,140],
1330 lavender: [230,230,250],
1331 lavenderblush: [255,240,245],
1332 lawngreen: [124,252,0],
1333 lemonchiffon: [255,250,205],
1334 lightblue: [173,216,230],
1335 lightcoral: [240,128,128],
1336 lightcyan: [224,255,255],
1337 lightgoldenrodyellow: [250,250,210],
1338 lightgray: [211,211,211],
1339 lightgreen: [144,238,144],
1340 lightgrey: [211,211,211],
1341 lightpink: [255,182,193],
1342 lightsalmon: [255,160,122],
1343 lightseagreen: [32,178,170],
1344 lightskyblue: [135,206,250],
1345 lightslategray: [119,136,153],
1346 lightslategrey: [119,136,153],
1347 lightsteelblue: [176,196,222],
1348 lightyellow: [255,255,224],
1349 lime: [0,255,0],
1350 limegreen: [50,205,50],
1351 linen: [250,240,230],
1352 magenta: [255,0,255],
1353 maroon: [128,0,0],
1354 mediumaquamarine: [102,205,170],
1355 mediumblue: [0,0,205],
1356 mediumorchid: [186,85,211],
1357 mediumpurple: [147,112,219],
1358 mediumseagreen: [60,179,113],
1359 mediumslateblue: [123,104,238],
1360 mediumspringgreen: [0,250,154],
1361 mediumturquoise: [72,209,204],
1362 mediumvioletred: [199,21,133],
1363 midnightblue: [25,25,112],
1364 mintcream: [245,255,250],
1365 mistyrose: [255,228,225],
1366 moccasin: [255,228,181],
1367 navajowhite: [255,222,173],
1368 navy: [0,0,128],
1369 oldlace: [253,245,230],
1370 olive: [128,128,0],
1371 olivedrab: [107,142,35],
1372 orange: [255,165,0],
1373 orangered: [255,69,0],
1374 orchid: [218,112,214],
1375 palegoldenrod: [238,232,170],
1376 palegreen: [152,251,152],
1377 paleturquoise: [175,238,238],
1378 palevioletred: [219,112,147],
1379 papayawhip: [255,239,213],
1380 peachpuff: [255,218,185],
1381 peru: [205,133,63],
1382 pink: [255,192,203],
1383 plum: [221,160,221],
1384 powderblue: [176,224,230],
1385 purple: [128,0,128],
1386 rebeccapurple: [102, 51, 153],
1387 red: [255,0,0],
1388 rosybrown: [188,143,143],
1389 royalblue: [65,105,225],
1390 saddlebrown: [139,69,19],
1391 salmon: [250,128,114],
1392 sandybrown: [244,164,96],
1393 seagreen: [46,139,87],
1394 seashell: [255,245,238],
1395 sienna: [160,82,45],
1396 silver: [192,192,192],
1397 skyblue: [135,206,235],
1398 slateblue: [106,90,205],
1399 slategray: [112,128,144],
1400 slategrey: [112,128,144],
1401 snow: [255,250,250],
1402 springgreen: [0,255,127],
1403 steelblue: [70,130,180],
1404 tan: [210,180,140],
1405 teal: [0,128,128],
1406 thistle: [216,191,216],
1407 tomato: [255,99,71],
1408 turquoise: [64,224,208],
1409 violet: [238,130,238],
1410 wheat: [245,222,179],
1411 white: [255,255,255],
1412 whitesmoke: [245,245,245],
1413 yellow: [255,255,0],
1414 yellowgreen: [154,205,50]
1415};
1416
1417var reverseKeywords = {};
1418for (var key in cssKeywords) {
1419 reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
1420}
1421
1422},{}],5:[function(require,module,exports){
1423var conversions = require(4);
1424
1425var convert = function() {
1426 return new Converter();
1427}
1428
1429for (var func in conversions) {
1430 // export Raw versions
1431 convert[func + "Raw"] = (function(func) {
1432 // accept array or plain args
1433 return function(arg) {
1434 if (typeof arg == "number")
1435 arg = Array.prototype.slice.call(arguments);
1436 return conversions[func](arg);
1437 }
1438 })(func);
1439
1440 var pair = /(\w+)2(\w+)/.exec(func),
1441 from = pair[1],
1442 to = pair[2];
1443
1444 // export rgb2hsl and ["rgb"]["hsl"]
1445 convert[from] = convert[from] || {};
1446
1447 convert[from][to] = convert[func] = (function(func) {
1448 return function(arg) {
1449 if (typeof arg == "number")
1450 arg = Array.prototype.slice.call(arguments);
1451
1452 var val = conversions[func](arg);
1453 if (typeof val == "string" || val === undefined)
1454 return val; // keyword
1455
1456 for (var i = 0; i < val.length; i++)
1457 val[i] = Math.round(val[i]);
1458 return val;
1459 }
1460 })(func);
1461}
1462
1463
1464/* Converter does lazy conversion and caching */
1465var Converter = function() {
1466 this.convs = {};
1467};
1468
1469/* Either get the values for a space or
1470 set the values for a space, depending on args */
1471Converter.prototype.routeSpace = function(space, args) {
1472 var values = args[0];
1473 if (values === undefined) {
1474 // color.rgb()
1475 return this.getValues(space);
1476 }
1477 // color.rgb(10, 10, 10)
1478 if (typeof values == "number") {
1479 values = Array.prototype.slice.call(args);
1480 }
1481
1482 return this.setValues(space, values);
1483};
1484
1485/* Set the values for a space, invalidating cache */
1486Converter.prototype.setValues = function(space, values) {
1487 this.space = space;
1488 this.convs = {};
1489 this.convs[space] = values;
1490 return this;
1491};
1492
1493/* Get the values for a space. If there's already
1494 a conversion for the space, fetch it, otherwise
1495 compute it */
1496Converter.prototype.getValues = function(space) {
1497 var vals = this.convs[space];
1498 if (!vals) {
1499 var fspace = this.space,
1500 from = this.convs[fspace];
1501 vals = convert[fspace][space](from);
1502
1503 this.convs[space] = vals;
1504 }
1505 return vals;
1506};
1507
1508["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
1509 Converter.prototype[space] = function(vals) {
1510 return this.routeSpace(space, arguments);
1511 }
1512});
1513
1514module.exports = convert;
1515},{"4":4}],6:[function(require,module,exports){
1516'use strict'
1517
1518module.exports = {
1519 "aliceblue": [240, 248, 255],
1520 "antiquewhite": [250, 235, 215],
1521 "aqua": [0, 255, 255],
1522 "aquamarine": [127, 255, 212],
1523 "azure": [240, 255, 255],
1524 "beige": [245, 245, 220],
1525 "bisque": [255, 228, 196],
1526 "black": [0, 0, 0],
1527 "blanchedalmond": [255, 235, 205],
1528 "blue": [0, 0, 255],
1529 "blueviolet": [138, 43, 226],
1530 "brown": [165, 42, 42],
1531 "burlywood": [222, 184, 135],
1532 "cadetblue": [95, 158, 160],
1533 "chartreuse": [127, 255, 0],
1534 "chocolate": [210, 105, 30],
1535 "coral": [255, 127, 80],
1536 "cornflowerblue": [100, 149, 237],
1537 "cornsilk": [255, 248, 220],
1538 "crimson": [220, 20, 60],
1539 "cyan": [0, 255, 255],
1540 "darkblue": [0, 0, 139],
1541 "darkcyan": [0, 139, 139],
1542 "darkgoldenrod": [184, 134, 11],
1543 "darkgray": [169, 169, 169],
1544 "darkgreen": [0, 100, 0],
1545 "darkgrey": [169, 169, 169],
1546 "darkkhaki": [189, 183, 107],
1547 "darkmagenta": [139, 0, 139],
1548 "darkolivegreen": [85, 107, 47],
1549 "darkorange": [255, 140, 0],
1550 "darkorchid": [153, 50, 204],
1551 "darkred": [139, 0, 0],
1552 "darksalmon": [233, 150, 122],
1553 "darkseagreen": [143, 188, 143],
1554 "darkslateblue": [72, 61, 139],
1555 "darkslategray": [47, 79, 79],
1556 "darkslategrey": [47, 79, 79],
1557 "darkturquoise": [0, 206, 209],
1558 "darkviolet": [148, 0, 211],
1559 "deeppink": [255, 20, 147],
1560 "deepskyblue": [0, 191, 255],
1561 "dimgray": [105, 105, 105],
1562 "dimgrey": [105, 105, 105],
1563 "dodgerblue": [30, 144, 255],
1564 "firebrick": [178, 34, 34],
1565 "floralwhite": [255, 250, 240],
1566 "forestgreen": [34, 139, 34],
1567 "fuchsia": [255, 0, 255],
1568 "gainsboro": [220, 220, 220],
1569 "ghostwhite": [248, 248, 255],
1570 "gold": [255, 215, 0],
1571 "goldenrod": [218, 165, 32],
1572 "gray": [128, 128, 128],
1573 "green": [0, 128, 0],
1574 "greenyellow": [173, 255, 47],
1575 "grey": [128, 128, 128],
1576 "honeydew": [240, 255, 240],
1577 "hotpink": [255, 105, 180],
1578 "indianred": [205, 92, 92],
1579 "indigo": [75, 0, 130],
1580 "ivory": [255, 255, 240],
1581 "khaki": [240, 230, 140],
1582 "lavender": [230, 230, 250],
1583 "lavenderblush": [255, 240, 245],
1584 "lawngreen": [124, 252, 0],
1585 "lemonchiffon": [255, 250, 205],
1586 "lightblue": [173, 216, 230],
1587 "lightcoral": [240, 128, 128],
1588 "lightcyan": [224, 255, 255],
1589 "lightgoldenrodyellow": [250, 250, 210],
1590 "lightgray": [211, 211, 211],
1591 "lightgreen": [144, 238, 144],
1592 "lightgrey": [211, 211, 211],
1593 "lightpink": [255, 182, 193],
1594 "lightsalmon": [255, 160, 122],
1595 "lightseagreen": [32, 178, 170],
1596 "lightskyblue": [135, 206, 250],
1597 "lightslategray": [119, 136, 153],
1598 "lightslategrey": [119, 136, 153],
1599 "lightsteelblue": [176, 196, 222],
1600 "lightyellow": [255, 255, 224],
1601 "lime": [0, 255, 0],
1602 "limegreen": [50, 205, 50],
1603 "linen": [250, 240, 230],
1604 "magenta": [255, 0, 255],
1605 "maroon": [128, 0, 0],
1606 "mediumaquamarine": [102, 205, 170],
1607 "mediumblue": [0, 0, 205],
1608 "mediumorchid": [186, 85, 211],
1609 "mediumpurple": [147, 112, 219],
1610 "mediumseagreen": [60, 179, 113],
1611 "mediumslateblue": [123, 104, 238],
1612 "mediumspringgreen": [0, 250, 154],
1613 "mediumturquoise": [72, 209, 204],
1614 "mediumvioletred": [199, 21, 133],
1615 "midnightblue": [25, 25, 112],
1616 "mintcream": [245, 255, 250],
1617 "mistyrose": [255, 228, 225],
1618 "moccasin": [255, 228, 181],
1619 "navajowhite": [255, 222, 173],
1620 "navy": [0, 0, 128],
1621 "oldlace": [253, 245, 230],
1622 "olive": [128, 128, 0],
1623 "olivedrab": [107, 142, 35],
1624 "orange": [255, 165, 0],
1625 "orangered": [255, 69, 0],
1626 "orchid": [218, 112, 214],
1627 "palegoldenrod": [238, 232, 170],
1628 "palegreen": [152, 251, 152],
1629 "paleturquoise": [175, 238, 238],
1630 "palevioletred": [219, 112, 147],
1631 "papayawhip": [255, 239, 213],
1632 "peachpuff": [255, 218, 185],
1633 "peru": [205, 133, 63],
1634 "pink": [255, 192, 203],
1635 "plum": [221, 160, 221],
1636 "powderblue": [176, 224, 230],
1637 "purple": [128, 0, 128],
1638 "rebeccapurple": [102, 51, 153],
1639 "red": [255, 0, 0],
1640 "rosybrown": [188, 143, 143],
1641 "royalblue": [65, 105, 225],
1642 "saddlebrown": [139, 69, 19],
1643 "salmon": [250, 128, 114],
1644 "sandybrown": [244, 164, 96],
1645 "seagreen": [46, 139, 87],
1646 "seashell": [255, 245, 238],
1647 "sienna": [160, 82, 45],
1648 "silver": [192, 192, 192],
1649 "skyblue": [135, 206, 235],
1650 "slateblue": [106, 90, 205],
1651 "slategray": [112, 128, 144],
1652 "slategrey": [112, 128, 144],
1653 "snow": [255, 250, 250],
1654 "springgreen": [0, 255, 127],
1655 "steelblue": [70, 130, 180],
1656 "tan": [210, 180, 140],
1657 "teal": [0, 128, 128],
1658 "thistle": [216, 191, 216],
1659 "tomato": [255, 99, 71],
1660 "turquoise": [64, 224, 208],
1661 "violet": [238, 130, 238],
1662 "wheat": [245, 222, 179],
1663 "white": [255, 255, 255],
1664 "whitesmoke": [245, 245, 245],
1665 "yellow": [255, 255, 0],
1666 "yellowgreen": [154, 205, 50]
1667};
1668
1669},{}],7:[function(require,module,exports){
1670/**
1671 * @namespace Chart
1672 */
1673var Chart = require(29)();
1674
1675Chart.helpers = require(45);
1676
1677// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
1678require(27)(Chart);
1679
1680Chart.defaults = require(25);
1681Chart.Element = require(26);
1682Chart.elements = require(40);
1683Chart.Interaction = require(28);
1684Chart.platform = require(48);
1685
1686require(31)(Chart);
1687require(22)(Chart);
1688require(23)(Chart);
1689require(24)(Chart);
1690require(30)(Chart);
1691require(33)(Chart);
1692require(32)(Chart);
1693require(35)(Chart);
1694
1695require(54)(Chart);
1696require(52)(Chart);
1697require(53)(Chart);
1698require(55)(Chart);
1699require(56)(Chart);
1700require(57)(Chart);
1701
1702// Controllers must be loaded after elements
1703// See Chart.core.datasetController.dataElementType
1704require(15)(Chart);
1705require(16)(Chart);
1706require(17)(Chart);
1707require(18)(Chart);
1708require(19)(Chart);
1709require(20)(Chart);
1710require(21)(Chart);
1711
1712require(8)(Chart);
1713require(9)(Chart);
1714require(10)(Chart);
1715require(11)(Chart);
1716require(12)(Chart);
1717require(13)(Chart);
1718require(14)(Chart);
1719
1720// Loading built-it plugins
1721var plugins = [];
1722
1723plugins.push(
1724 require(49)(Chart),
1725 require(50)(Chart),
1726 require(51)(Chart)
1727);
1728
1729Chart.plugins.register(plugins);
1730
1731Chart.platform.initialize();
1732
1733module.exports = Chart;
1734if (typeof window !== 'undefined') {
1735 window.Chart = Chart;
1736}
1737
1738// DEPRECATIONS
1739
1740/**
1741 * Provided for backward compatibility, use Chart.helpers.canvas instead.
1742 * @namespace Chart.canvasHelpers
1743 * @deprecated since version 2.6.0
1744 * @todo remove at version 3
1745 * @private
1746 */
1747Chart.canvasHelpers = Chart.helpers.canvas;
1748
1749},{"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){
1750'use strict';
1751
1752module.exports = function(Chart) {
1753
1754 Chart.Bar = function(context, config) {
1755 config.type = 'bar';
1756
1757 return new Chart(context, config);
1758 };
1759
1760};
1761
1762},{}],9:[function(require,module,exports){
1763'use strict';
1764
1765module.exports = function(Chart) {
1766
1767 Chart.Bubble = function(context, config) {
1768 config.type = 'bubble';
1769 return new Chart(context, config);
1770 };
1771
1772};
1773
1774},{}],10:[function(require,module,exports){
1775'use strict';
1776
1777module.exports = function(Chart) {
1778
1779 Chart.Doughnut = function(context, config) {
1780 config.type = 'doughnut';
1781
1782 return new Chart(context, config);
1783 };
1784
1785};
1786
1787},{}],11:[function(require,module,exports){
1788'use strict';
1789
1790module.exports = function(Chart) {
1791
1792 Chart.Line = function(context, config) {
1793 config.type = 'line';
1794
1795 return new Chart(context, config);
1796 };
1797
1798};
1799
1800},{}],12:[function(require,module,exports){
1801'use strict';
1802
1803module.exports = function(Chart) {
1804
1805 Chart.PolarArea = function(context, config) {
1806 config.type = 'polarArea';
1807
1808 return new Chart(context, config);
1809 };
1810
1811};
1812
1813},{}],13:[function(require,module,exports){
1814'use strict';
1815
1816module.exports = function(Chart) {
1817
1818 Chart.Radar = function(context, config) {
1819 config.type = 'radar';
1820
1821 return new Chart(context, config);
1822 };
1823
1824};
1825
1826},{}],14:[function(require,module,exports){
1827'use strict';
1828
1829module.exports = function(Chart) {
1830 Chart.Scatter = function(context, config) {
1831 config.type = 'scatter';
1832 return new Chart(context, config);
1833 };
1834};
1835
1836},{}],15:[function(require,module,exports){
1837'use strict';
1838
1839var defaults = require(25);
1840var elements = require(40);
1841var helpers = require(45);
1842
1843defaults._set('bar', {
1844 hover: {
1845 mode: 'label'
1846 },
1847
1848 scales: {
1849 xAxes: [{
1850 type: 'category',
1851
1852 // Specific to Bar Controller
1853 categoryPercentage: 0.8,
1854 barPercentage: 0.9,
1855
1856 // offset settings
1857 offset: true,
1858
1859 // grid line settings
1860 gridLines: {
1861 offsetGridLines: true
1862 }
1863 }],
1864
1865 yAxes: [{
1866 type: 'linear'
1867 }]
1868 }
1869});
1870
1871defaults._set('horizontalBar', {
1872 hover: {
1873 mode: 'index',
1874 axis: 'y'
1875 },
1876
1877 scales: {
1878 xAxes: [{
1879 type: 'linear',
1880 position: 'bottom'
1881 }],
1882
1883 yAxes: [{
1884 position: 'left',
1885 type: 'category',
1886
1887 // Specific to Horizontal Bar Controller
1888 categoryPercentage: 0.8,
1889 barPercentage: 0.9,
1890
1891 // offset settings
1892 offset: true,
1893
1894 // grid line settings
1895 gridLines: {
1896 offsetGridLines: true
1897 }
1898 }]
1899 },
1900
1901 elements: {
1902 rectangle: {
1903 borderSkipped: 'left'
1904 }
1905 },
1906
1907 tooltips: {
1908 callbacks: {
1909 title: function(item, data) {
1910 // Pick first xLabel for now
1911 var title = '';
1912
1913 if (item.length > 0) {
1914 if (item[0].yLabel) {
1915 title = item[0].yLabel;
1916 } else if (data.labels.length > 0 && item[0].index < data.labels.length) {
1917 title = data.labels[item[0].index];
1918 }
1919 }
1920
1921 return title;
1922 },
1923
1924 label: function(item, data) {
1925 var datasetLabel = data.datasets[item.datasetIndex].label || '';
1926 return datasetLabel + ': ' + item.xLabel;
1927 }
1928 },
1929 mode: 'index',
1930 axis: 'y'
1931 }
1932});
1933
1934module.exports = function(Chart) {
1935
1936 Chart.controllers.bar = Chart.DatasetController.extend({
1937
1938 dataElementType: elements.Rectangle,
1939
1940 initialize: function() {
1941 var me = this;
1942 var meta;
1943
1944 Chart.DatasetController.prototype.initialize.apply(me, arguments);
1945
1946 meta = me.getMeta();
1947 meta.stack = me.getDataset().stack;
1948 meta.bar = true;
1949 },
1950
1951 update: function(reset) {
1952 var me = this;
1953 var rects = me.getMeta().data;
1954 var i, ilen;
1955
1956 me._ruler = me.getRuler();
1957
1958 for (i = 0, ilen = rects.length; i < ilen; ++i) {
1959 me.updateElement(rects[i], i, reset);
1960 }
1961 },
1962
1963 updateElement: function(rectangle, index, reset) {
1964 var me = this;
1965 var chart = me.chart;
1966 var meta = me.getMeta();
1967 var dataset = me.getDataset();
1968 var custom = rectangle.custom || {};
1969 var rectangleOptions = chart.options.elements.rectangle;
1970
1971 rectangle._xScale = me.getScaleForId(meta.xAxisID);
1972 rectangle._yScale = me.getScaleForId(meta.yAxisID);
1973 rectangle._datasetIndex = me.index;
1974 rectangle._index = index;
1975
1976 rectangle._model = {
1977 datasetLabel: dataset.label,
1978 label: chart.data.labels[index],
1979 borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped,
1980 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor),
1981 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor),
1982 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth)
1983 };
1984
1985 me.updateElementGeometry(rectangle, index, reset);
1986
1987 rectangle.pivot();
1988 },
1989
1990 /**
1991 * @private
1992 */
1993 updateElementGeometry: function(rectangle, index, reset) {
1994 var me = this;
1995 var model = rectangle._model;
1996 var vscale = me.getValueScale();
1997 var base = vscale.getBasePixel();
1998 var horizontal = vscale.isHorizontal();
1999 var ruler = me._ruler || me.getRuler();
2000 var vpixels = me.calculateBarValuePixels(me.index, index);
2001 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
2002
2003 model.horizontal = horizontal;
2004 model.base = reset ? base : vpixels.base;
2005 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
2006 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
2007 model.height = horizontal ? ipixels.size : undefined;
2008 model.width = horizontal ? undefined : ipixels.size;
2009 },
2010
2011 /**
2012 * @private
2013 */
2014 getValueScaleId: function() {
2015 return this.getMeta().yAxisID;
2016 },
2017
2018 /**
2019 * @private
2020 */
2021 getIndexScaleId: function() {
2022 return this.getMeta().xAxisID;
2023 },
2024
2025 /**
2026 * @private
2027 */
2028 getValueScale: function() {
2029 return this.getScaleForId(this.getValueScaleId());
2030 },
2031
2032 /**
2033 * @private
2034 */
2035 getIndexScale: function() {
2036 return this.getScaleForId(this.getIndexScaleId());
2037 },
2038
2039 /**
2040 * Returns the effective number of stacks based on groups and bar visibility.
2041 * @private
2042 */
2043 getStackCount: function(last) {
2044 var me = this;
2045 var chart = me.chart;
2046 var scale = me.getIndexScale();
2047 var stacked = scale.options.stacked;
2048 var ilen = last === undefined ? chart.data.datasets.length : last + 1;
2049 var stacks = [];
2050 var i, meta;
2051
2052 for (i = 0; i < ilen; ++i) {
2053 meta = chart.getDatasetMeta(i);
2054 if (meta.bar && chart.isDatasetVisible(i) &&
2055 (stacked === false ||
2056 (stacked === true && stacks.indexOf(meta.stack) === -1) ||
2057 (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
2058 stacks.push(meta.stack);
2059 }
2060 }
2061
2062 return stacks.length;
2063 },
2064
2065 /**
2066 * Returns the stack index for the given dataset based on groups and bar visibility.
2067 * @private
2068 */
2069 getStackIndex: function(datasetIndex) {
2070 return this.getStackCount(datasetIndex) - 1;
2071 },
2072
2073 /**
2074 * @private
2075 */
2076 getRuler: function() {
2077 var me = this;
2078 var scale = me.getIndexScale();
2079 var stackCount = me.getStackCount();
2080 var datasetIndex = me.index;
2081 var pixels = [];
2082 var isHorizontal = scale.isHorizontal();
2083 var start = isHorizontal ? scale.left : scale.top;
2084 var end = start + (isHorizontal ? scale.width : scale.height);
2085 var i, ilen;
2086
2087 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
2088 pixels.push(scale.getPixelForValue(null, i, datasetIndex));
2089 }
2090
2091 return {
2092 pixels: pixels,
2093 start: start,
2094 end: end,
2095 stackCount: stackCount,
2096 scale: scale
2097 };
2098 },
2099
2100 /**
2101 * Note: pixel values are not clamped to the scale area.
2102 * @private
2103 */
2104 calculateBarValuePixels: function(datasetIndex, index) {
2105 var me = this;
2106 var chart = me.chart;
2107 var meta = me.getMeta();
2108 var scale = me.getValueScale();
2109 var datasets = chart.data.datasets;
2110 var value = scale.getRightValue(datasets[datasetIndex].data[index]);
2111 var stacked = scale.options.stacked;
2112 var stack = meta.stack;
2113 var start = 0;
2114 var i, imeta, ivalue, base, head, size;
2115
2116 if (stacked || (stacked === undefined && stack !== undefined)) {
2117 for (i = 0; i < datasetIndex; ++i) {
2118 imeta = chart.getDatasetMeta(i);
2119
2120 if (imeta.bar &&
2121 imeta.stack === stack &&
2122 imeta.controller.getValueScaleId() === scale.id &&
2123 chart.isDatasetVisible(i)) {
2124
2125 ivalue = scale.getRightValue(datasets[i].data[index]);
2126 if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
2127 start += ivalue;
2128 }
2129 }
2130 }
2131 }
2132
2133 base = scale.getPixelForValue(start);
2134 head = scale.getPixelForValue(start + value);
2135 size = (head - base) / 2;
2136
2137 return {
2138 size: size,
2139 base: base,
2140 head: head,
2141 center: head + size / 2
2142 };
2143 },
2144
2145 /**
2146 * @private
2147 */
2148 calculateBarIndexPixels: function(datasetIndex, index, ruler) {
2149 var me = this;
2150 var options = ruler.scale.options;
2151 var stackIndex = me.getStackIndex(datasetIndex);
2152 var pixels = ruler.pixels;
2153 var base = pixels[index];
2154 var length = pixels.length;
2155 var start = ruler.start;
2156 var end = ruler.end;
2157 var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size;
2158
2159 if (length === 1) {
2160 leftSampleSize = base > start ? base - start : end - base;
2161 rightSampleSize = base < end ? end - base : base - start;
2162 } else {
2163 if (index > 0) {
2164 leftSampleSize = (base - pixels[index - 1]) / 2;
2165 if (index === length - 1) {
2166 rightSampleSize = leftSampleSize;
2167 }
2168 }
2169 if (index < length - 1) {
2170 rightSampleSize = (pixels[index + 1] - base) / 2;
2171 if (index === 0) {
2172 leftSampleSize = rightSampleSize;
2173 }
2174 }
2175 }
2176
2177 leftCategorySize = leftSampleSize * options.categoryPercentage;
2178 rightCategorySize = rightSampleSize * options.categoryPercentage;
2179 fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount;
2180 size = fullBarSize * options.barPercentage;
2181
2182 size = Math.min(
2183 helpers.valueOrDefault(options.barThickness, size),
2184 helpers.valueOrDefault(options.maxBarThickness, Infinity));
2185
2186 base -= leftCategorySize;
2187 base += fullBarSize * stackIndex;
2188 base += (fullBarSize - size) / 2;
2189
2190 return {
2191 size: size,
2192 base: base,
2193 head: base + size,
2194 center: base + size / 2
2195 };
2196 },
2197
2198 draw: function() {
2199 var me = this;
2200 var chart = me.chart;
2201 var scale = me.getValueScale();
2202 var rects = me.getMeta().data;
2203 var dataset = me.getDataset();
2204 var ilen = rects.length;
2205 var i = 0;
2206
2207 helpers.canvas.clipArea(chart.ctx, chart.chartArea);
2208
2209 for (; i < ilen; ++i) {
2210 if (!isNaN(scale.getRightValue(dataset.data[i]))) {
2211 rects[i].draw();
2212 }
2213 }
2214
2215 helpers.canvas.unclipArea(chart.ctx);
2216 },
2217
2218 setHoverStyle: function(rectangle) {
2219 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2220 var index = rectangle._index;
2221 var custom = rectangle.custom || {};
2222 var model = rectangle._model;
2223
2224 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
2225 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
2226 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
2227 },
2228
2229 removeHoverStyle: function(rectangle) {
2230 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2231 var index = rectangle._index;
2232 var custom = rectangle.custom || {};
2233 var model = rectangle._model;
2234 var rectangleElementOptions = this.chart.options.elements.rectangle;
2235
2236 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
2237 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
2238 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
2239 }
2240 });
2241
2242 Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
2243 /**
2244 * @private
2245 */
2246 getValueScaleId: function() {
2247 return this.getMeta().xAxisID;
2248 },
2249
2250 /**
2251 * @private
2252 */
2253 getIndexScaleId: function() {
2254 return this.getMeta().yAxisID;
2255 }
2256 });
2257};
2258
2259},{"25":25,"40":40,"45":45}],16:[function(require,module,exports){
2260'use strict';
2261
2262var defaults = require(25);
2263var elements = require(40);
2264var helpers = require(45);
2265
2266defaults._set('bubble', {
2267 hover: {
2268 mode: 'single'
2269 },
2270
2271 scales: {
2272 xAxes: [{
2273 type: 'linear', // bubble should probably use a linear scale by default
2274 position: 'bottom',
2275 id: 'x-axis-0' // need an ID so datasets can reference the scale
2276 }],
2277 yAxes: [{
2278 type: 'linear',
2279 position: 'left',
2280 id: 'y-axis-0'
2281 }]
2282 },
2283
2284 tooltips: {
2285 callbacks: {
2286 title: function() {
2287 // Title doesn't make sense for scatter since we format the data as a point
2288 return '';
2289 },
2290 label: function(item, data) {
2291 var datasetLabel = data.datasets[item.datasetIndex].label || '';
2292 var dataPoint = data.datasets[item.datasetIndex].data[item.index];
2293 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
2294 }
2295 }
2296 }
2297});
2298
2299
2300module.exports = function(Chart) {
2301
2302 Chart.controllers.bubble = Chart.DatasetController.extend({
2303 /**
2304 * @protected
2305 */
2306 dataElementType: elements.Point,
2307
2308 /**
2309 * @protected
2310 */
2311 update: function(reset) {
2312 var me = this;
2313 var meta = me.getMeta();
2314 var points = meta.data;
2315
2316 // Update Points
2317 helpers.each(points, function(point, index) {
2318 me.updateElement(point, index, reset);
2319 });
2320 },
2321
2322 /**
2323 * @protected
2324 */
2325 updateElement: function(point, index, reset) {
2326 var me = this;
2327 var meta = me.getMeta();
2328 var custom = point.custom || {};
2329 var xScale = me.getScaleForId(meta.xAxisID);
2330 var yScale = me.getScaleForId(meta.yAxisID);
2331 var options = me._resolveElementOptions(point, index);
2332 var data = me.getDataset().data[index];
2333 var dsIndex = me.index;
2334
2335 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
2336 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
2337
2338 point._xScale = xScale;
2339 point._yScale = yScale;
2340 point._options = options;
2341 point._datasetIndex = dsIndex;
2342 point._index = index;
2343 point._model = {
2344 backgroundColor: options.backgroundColor,
2345 borderColor: options.borderColor,
2346 borderWidth: options.borderWidth,
2347 hitRadius: options.hitRadius,
2348 pointStyle: options.pointStyle,
2349 radius: reset ? 0 : options.radius,
2350 skip: custom.skip || isNaN(x) || isNaN(y),
2351 x: x,
2352 y: y,
2353 };
2354
2355 point.pivot();
2356 },
2357
2358 /**
2359 * @protected
2360 */
2361 setHoverStyle: function(point) {
2362 var model = point._model;
2363 var options = point._options;
2364
2365 model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
2366 model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
2367 model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
2368 model.radius = options.radius + options.hoverRadius;
2369 },
2370
2371 /**
2372 * @protected
2373 */
2374 removeHoverStyle: function(point) {
2375 var model = point._model;
2376 var options = point._options;
2377
2378 model.backgroundColor = options.backgroundColor;
2379 model.borderColor = options.borderColor;
2380 model.borderWidth = options.borderWidth;
2381 model.radius = options.radius;
2382 },
2383
2384 /**
2385 * @private
2386 */
2387 _resolveElementOptions: function(point, index) {
2388 var me = this;
2389 var chart = me.chart;
2390 var datasets = chart.data.datasets;
2391 var dataset = datasets[me.index];
2392 var custom = point.custom || {};
2393 var options = chart.options.elements.point;
2394 var resolve = helpers.options.resolve;
2395 var data = dataset.data[index];
2396 var values = {};
2397 var i, ilen, key;
2398
2399 // Scriptable options
2400 var context = {
2401 chart: chart,
2402 dataIndex: index,
2403 dataset: dataset,
2404 datasetIndex: me.index
2405 };
2406
2407 var keys = [
2408 'backgroundColor',
2409 'borderColor',
2410 'borderWidth',
2411 'hoverBackgroundColor',
2412 'hoverBorderColor',
2413 'hoverBorderWidth',
2414 'hoverRadius',
2415 'hitRadius',
2416 'pointStyle'
2417 ];
2418
2419 for (i = 0, ilen = keys.length; i < ilen; ++i) {
2420 key = keys[i];
2421 values[key] = resolve([
2422 custom[key],
2423 dataset[key],
2424 options[key]
2425 ], context, index);
2426 }
2427
2428 // Custom radius resolution
2429 values.radius = resolve([
2430 custom.radius,
2431 data ? data.r : undefined,
2432 dataset.radius,
2433 options.radius
2434 ], context, index);
2435
2436 return values;
2437 }
2438 });
2439};
2440
2441},{"25":25,"40":40,"45":45}],17:[function(require,module,exports){
2442'use strict';
2443
2444var defaults = require(25);
2445var elements = require(40);
2446var helpers = require(45);
2447
2448defaults._set('doughnut', {
2449 animation: {
2450 // Boolean - Whether we animate the rotation of the Doughnut
2451 animateRotate: true,
2452 // Boolean - Whether we animate scaling the Doughnut from the centre
2453 animateScale: false
2454 },
2455 hover: {
2456 mode: 'single'
2457 },
2458 legendCallback: function(chart) {
2459 var text = [];
2460 text.push('<ul class="' + chart.id + '-legend">');
2461
2462 var data = chart.data;
2463 var datasets = data.datasets;
2464 var labels = data.labels;
2465
2466 if (datasets.length) {
2467 for (var i = 0; i < datasets[0].data.length; ++i) {
2468 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
2469 if (labels[i]) {
2470 text.push(labels[i]);
2471 }
2472 text.push('</li>');
2473 }
2474 }
2475
2476 text.push('</ul>');
2477 return text.join('');
2478 },
2479 legend: {
2480 labels: {
2481 generateLabels: function(chart) {
2482 var data = chart.data;
2483 if (data.labels.length && data.datasets.length) {
2484 return data.labels.map(function(label, i) {
2485 var meta = chart.getDatasetMeta(0);
2486 var ds = data.datasets[0];
2487 var arc = meta.data[i];
2488 var custom = arc && arc.custom || {};
2489 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2490 var arcOpts = chart.options.elements.arc;
2491 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
2492 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
2493 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
2494
2495 return {
2496 text: label,
2497 fillStyle: fill,
2498 strokeStyle: stroke,
2499 lineWidth: bw,
2500 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
2501
2502 // Extra data used for toggling the correct item
2503 index: i
2504 };
2505 });
2506 }
2507 return [];
2508 }
2509 },
2510
2511 onClick: function(e, legendItem) {
2512 var index = legendItem.index;
2513 var chart = this.chart;
2514 var i, ilen, meta;
2515
2516 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
2517 meta = chart.getDatasetMeta(i);
2518 // toggle visibility of index if exists
2519 if (meta.data[index]) {
2520 meta.data[index].hidden = !meta.data[index].hidden;
2521 }
2522 }
2523
2524 chart.update();
2525 }
2526 },
2527
2528 // The percentage of the chart that we cut out of the middle.
2529 cutoutPercentage: 50,
2530
2531 // The rotation of the chart, where the first data arc begins.
2532 rotation: Math.PI * -0.5,
2533
2534 // The total circumference of the chart.
2535 circumference: Math.PI * 2.0,
2536
2537 // Need to override these to give a nice default
2538 tooltips: {
2539 callbacks: {
2540 title: function() {
2541 return '';
2542 },
2543 label: function(tooltipItem, data) {
2544 var dataLabel = data.labels[tooltipItem.index];
2545 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2546
2547 if (helpers.isArray(dataLabel)) {
2548 // show value on first line of multiline label
2549 // need to clone because we are changing the value
2550 dataLabel = dataLabel.slice();
2551 dataLabel[0] += value;
2552 } else {
2553 dataLabel += value;
2554 }
2555
2556 return dataLabel;
2557 }
2558 }
2559 }
2560});
2561
2562defaults._set('pie', helpers.clone(defaults.doughnut));
2563defaults._set('pie', {
2564 cutoutPercentage: 0
2565});
2566
2567module.exports = function(Chart) {
2568
2569 Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
2570
2571 dataElementType: elements.Arc,
2572
2573 linkScales: helpers.noop,
2574
2575 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
2576 getRingIndex: function(datasetIndex) {
2577 var ringIndex = 0;
2578
2579 for (var j = 0; j < datasetIndex; ++j) {
2580 if (this.chart.isDatasetVisible(j)) {
2581 ++ringIndex;
2582 }
2583 }
2584
2585 return ringIndex;
2586 },
2587
2588 update: function(reset) {
2589 var me = this;
2590 var chart = me.chart;
2591 var chartArea = chart.chartArea;
2592 var opts = chart.options;
2593 var arcOpts = opts.elements.arc;
2594 var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
2595 var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
2596 var minSize = Math.min(availableWidth, availableHeight);
2597 var offset = {x: 0, y: 0};
2598 var meta = me.getMeta();
2599 var cutoutPercentage = opts.cutoutPercentage;
2600 var circumference = opts.circumference;
2601
2602 // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
2603 if (circumference < Math.PI * 2.0) {
2604 var startAngle = opts.rotation % (Math.PI * 2.0);
2605 startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
2606 var endAngle = startAngle + circumference;
2607 var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
2608 var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
2609 var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
2610 var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
2611 var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
2612 var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
2613 var cutout = cutoutPercentage / 100.0;
2614 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))};
2615 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))};
2616 var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
2617 minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
2618 offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
2619 }
2620
2621 chart.borderWidth = me.getMaxBorderWidth(meta.data);
2622 chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
2623 chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
2624 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
2625 chart.offsetX = offset.x * chart.outerRadius;
2626 chart.offsetY = offset.y * chart.outerRadius;
2627
2628 meta.total = me.calculateTotal();
2629
2630 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
2631 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
2632
2633 helpers.each(meta.data, function(arc, index) {
2634 me.updateElement(arc, index, reset);
2635 });
2636 },
2637
2638 updateElement: function(arc, index, reset) {
2639 var me = this;
2640 var chart = me.chart;
2641 var chartArea = chart.chartArea;
2642 var opts = chart.options;
2643 var animationOpts = opts.animation;
2644 var centerX = (chartArea.left + chartArea.right) / 2;
2645 var centerY = (chartArea.top + chartArea.bottom) / 2;
2646 var startAngle = opts.rotation; // non reset case handled later
2647 var endAngle = opts.rotation; // non reset case handled later
2648 var dataset = me.getDataset();
2649 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
2650 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
2651 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
2652 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2653
2654 helpers.extend(arc, {
2655 // Utility
2656 _datasetIndex: me.index,
2657 _index: index,
2658
2659 // Desired view properties
2660 _model: {
2661 x: centerX + chart.offsetX,
2662 y: centerY + chart.offsetY,
2663 startAngle: startAngle,
2664 endAngle: endAngle,
2665 circumference: circumference,
2666 outerRadius: outerRadius,
2667 innerRadius: innerRadius,
2668 label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
2669 }
2670 });
2671
2672 var model = arc._model;
2673 // Resets the visual styles
2674 this.removeHoverStyle(arc);
2675
2676 // Set correct angles if not resetting
2677 if (!reset || !animationOpts.animateRotate) {
2678 if (index === 0) {
2679 model.startAngle = opts.rotation;
2680 } else {
2681 model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
2682 }
2683
2684 model.endAngle = model.startAngle + model.circumference;
2685 }
2686
2687 arc.pivot();
2688 },
2689
2690 removeHoverStyle: function(arc) {
2691 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
2692 },
2693
2694 calculateTotal: function() {
2695 var dataset = this.getDataset();
2696 var meta = this.getMeta();
2697 var total = 0;
2698 var value;
2699
2700 helpers.each(meta.data, function(element, index) {
2701 value = dataset.data[index];
2702 if (!isNaN(value) && !element.hidden) {
2703 total += Math.abs(value);
2704 }
2705 });
2706
2707 /* if (total === 0) {
2708 total = NaN;
2709 }*/
2710
2711 return total;
2712 },
2713
2714 calculateCircumference: function(value) {
2715 var total = this.getMeta().total;
2716 if (total > 0 && !isNaN(value)) {
2717 return (Math.PI * 2.0) * (value / total);
2718 }
2719 return 0;
2720 },
2721
2722 // gets the max border or hover width to properly scale pie charts
2723 getMaxBorderWidth: function(arcs) {
2724 var max = 0;
2725 var index = this.index;
2726 var length = arcs.length;
2727 var borderWidth;
2728 var hoverWidth;
2729
2730 for (var i = 0; i < length; i++) {
2731 borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
2732 hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
2733
2734 max = borderWidth > max ? borderWidth : max;
2735 max = hoverWidth > max ? hoverWidth : max;
2736 }
2737 return max;
2738 }
2739 });
2740};
2741
2742},{"25":25,"40":40,"45":45}],18:[function(require,module,exports){
2743'use strict';
2744
2745var defaults = require(25);
2746var elements = require(40);
2747var helpers = require(45);
2748
2749defaults._set('line', {
2750 showLines: true,
2751 spanGaps: false,
2752
2753 hover: {
2754 mode: 'label'
2755 },
2756
2757 scales: {
2758 xAxes: [{
2759 type: 'category',
2760 id: 'x-axis-0'
2761 }],
2762 yAxes: [{
2763 type: 'linear',
2764 id: 'y-axis-0'
2765 }]
2766 }
2767});
2768
2769module.exports = function(Chart) {
2770
2771 function lineEnabled(dataset, options) {
2772 return helpers.valueOrDefault(dataset.showLine, options.showLines);
2773 }
2774
2775 Chart.controllers.line = Chart.DatasetController.extend({
2776
2777 datasetElementType: elements.Line,
2778
2779 dataElementType: elements.Point,
2780
2781 update: function(reset) {
2782 var me = this;
2783 var meta = me.getMeta();
2784 var line = meta.dataset;
2785 var points = meta.data || [];
2786 var options = me.chart.options;
2787 var lineElementOptions = options.elements.line;
2788 var scale = me.getScaleForId(meta.yAxisID);
2789 var i, ilen, custom;
2790 var dataset = me.getDataset();
2791 var showLine = lineEnabled(dataset, options);
2792
2793 // Update Line
2794 if (showLine) {
2795 custom = line.custom || {};
2796
2797 // Compatibility: If the properties are defined with only the old name, use those values
2798 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
2799 dataset.lineTension = dataset.tension;
2800 }
2801
2802 // Utility
2803 line._scale = scale;
2804 line._datasetIndex = me.index;
2805 // Data
2806 line._children = points;
2807 // Model
2808 line._model = {
2809 // Appearance
2810 // The default behavior of lines is to break at null values, according
2811 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
2812 // This option gives lines the ability to span gaps
2813 spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
2814 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
2815 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
2816 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
2817 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
2818 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
2819 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
2820 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
2821 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
2822 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
2823 steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
2824 cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
2825 };
2826
2827 line.pivot();
2828 }
2829
2830 // Update Points
2831 for (i = 0, ilen = points.length; i < ilen; ++i) {
2832 me.updateElement(points[i], i, reset);
2833 }
2834
2835 if (showLine && line._model.tension !== 0) {
2836 me.updateBezierControlPoints();
2837 }
2838
2839 // Now pivot the point for animation
2840 for (i = 0, ilen = points.length; i < ilen; ++i) {
2841 points[i].pivot();
2842 }
2843 },
2844
2845 getPointBackgroundColor: function(point, index) {
2846 var backgroundColor = this.chart.options.elements.point.backgroundColor;
2847 var dataset = this.getDataset();
2848 var custom = point.custom || {};
2849
2850 if (custom.backgroundColor) {
2851 backgroundColor = custom.backgroundColor;
2852 } else if (dataset.pointBackgroundColor) {
2853 backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
2854 } else if (dataset.backgroundColor) {
2855 backgroundColor = dataset.backgroundColor;
2856 }
2857
2858 return backgroundColor;
2859 },
2860
2861 getPointBorderColor: function(point, index) {
2862 var borderColor = this.chart.options.elements.point.borderColor;
2863 var dataset = this.getDataset();
2864 var custom = point.custom || {};
2865
2866 if (custom.borderColor) {
2867 borderColor = custom.borderColor;
2868 } else if (dataset.pointBorderColor) {
2869 borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
2870 } else if (dataset.borderColor) {
2871 borderColor = dataset.borderColor;
2872 }
2873
2874 return borderColor;
2875 },
2876
2877 getPointBorderWidth: function(point, index) {
2878 var borderWidth = this.chart.options.elements.point.borderWidth;
2879 var dataset = this.getDataset();
2880 var custom = point.custom || {};
2881
2882 if (!isNaN(custom.borderWidth)) {
2883 borderWidth = custom.borderWidth;
2884 } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
2885 borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
2886 } else if (!isNaN(dataset.borderWidth)) {
2887 borderWidth = dataset.borderWidth;
2888 }
2889
2890 return borderWidth;
2891 },
2892
2893 updateElement: function(point, index, reset) {
2894 var me = this;
2895 var meta = me.getMeta();
2896 var custom = point.custom || {};
2897 var dataset = me.getDataset();
2898 var datasetIndex = me.index;
2899 var value = dataset.data[index];
2900 var yScale = me.getScaleForId(meta.yAxisID);
2901 var xScale = me.getScaleForId(meta.xAxisID);
2902 var pointOptions = me.chart.options.elements.point;
2903 var x, y;
2904
2905 // Compatibility: If the properties are defined with only the old name, use those values
2906 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
2907 dataset.pointRadius = dataset.radius;
2908 }
2909 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
2910 dataset.pointHitRadius = dataset.hitRadius;
2911 }
2912
2913 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
2914 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
2915
2916 // Utility
2917 point._xScale = xScale;
2918 point._yScale = yScale;
2919 point._datasetIndex = datasetIndex;
2920 point._index = index;
2921
2922 // Desired view properties
2923 point._model = {
2924 x: x,
2925 y: y,
2926 skip: custom.skip || isNaN(x) || isNaN(y),
2927 // Appearance
2928 radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
2929 pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
2930 backgroundColor: me.getPointBackgroundColor(point, index),
2931 borderColor: me.getPointBorderColor(point, index),
2932 borderWidth: me.getPointBorderWidth(point, index),
2933 tension: meta.dataset._model ? meta.dataset._model.tension : 0,
2934 steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
2935 // Tooltip
2936 hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
2937 };
2938 },
2939
2940 calculatePointY: function(value, index, datasetIndex) {
2941 var me = this;
2942 var chart = me.chart;
2943 var meta = me.getMeta();
2944 var yScale = me.getScaleForId(meta.yAxisID);
2945 var sumPos = 0;
2946 var sumNeg = 0;
2947 var i, ds, dsMeta;
2948
2949 if (yScale.options.stacked) {
2950 for (i = 0; i < datasetIndex; i++) {
2951 ds = chart.data.datasets[i];
2952 dsMeta = chart.getDatasetMeta(i);
2953 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
2954 var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
2955 if (stackedRightValue < 0) {
2956 sumNeg += stackedRightValue || 0;
2957 } else {
2958 sumPos += stackedRightValue || 0;
2959 }
2960 }
2961 }
2962
2963 var rightValue = Number(yScale.getRightValue(value));
2964 if (rightValue < 0) {
2965 return yScale.getPixelForValue(sumNeg + rightValue);
2966 }
2967 return yScale.getPixelForValue(sumPos + rightValue);
2968 }
2969
2970 return yScale.getPixelForValue(value);
2971 },
2972
2973 updateBezierControlPoints: function() {
2974 var me = this;
2975 var meta = me.getMeta();
2976 var area = me.chart.chartArea;
2977 var points = (meta.data || []);
2978 var i, ilen, point, model, controlPoints;
2979
2980 // Only consider points that are drawn in case the spanGaps option is used
2981 if (meta.dataset._model.spanGaps) {
2982 points = points.filter(function(pt) {
2983 return !pt._model.skip;
2984 });
2985 }
2986
2987 function capControlPoint(pt, min, max) {
2988 return Math.max(Math.min(pt, max), min);
2989 }
2990
2991 if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
2992 helpers.splineCurveMonotone(points);
2993 } else {
2994 for (i = 0, ilen = points.length; i < ilen; ++i) {
2995 point = points[i];
2996 model = point._model;
2997 controlPoints = helpers.splineCurve(
2998 helpers.previousItem(points, i)._model,
2999 model,
3000 helpers.nextItem(points, i)._model,
3001 meta.dataset._model.tension
3002 );
3003 model.controlPointPreviousX = controlPoints.previous.x;
3004 model.controlPointPreviousY = controlPoints.previous.y;
3005 model.controlPointNextX = controlPoints.next.x;
3006 model.controlPointNextY = controlPoints.next.y;
3007 }
3008 }
3009
3010 if (me.chart.options.elements.line.capBezierPoints) {
3011 for (i = 0, ilen = points.length; i < ilen; ++i) {
3012 model = points[i]._model;
3013 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
3014 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
3015 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
3016 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
3017 }
3018 }
3019 },
3020
3021 draw: function() {
3022 var me = this;
3023 var chart = me.chart;
3024 var meta = me.getMeta();
3025 var points = meta.data || [];
3026 var area = chart.chartArea;
3027 var ilen = points.length;
3028 var i = 0;
3029
3030 helpers.canvas.clipArea(chart.ctx, area);
3031
3032 if (lineEnabled(me.getDataset(), chart.options)) {
3033 meta.dataset.draw();
3034 }
3035
3036 helpers.canvas.unclipArea(chart.ctx);
3037
3038 // Draw the points
3039 for (; i < ilen; ++i) {
3040 points[i].draw(area);
3041 }
3042 },
3043
3044 setHoverStyle: function(point) {
3045 // Point
3046 var dataset = this.chart.data.datasets[point._datasetIndex];
3047 var index = point._index;
3048 var custom = point.custom || {};
3049 var model = point._model;
3050
3051 model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3052 model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3053 model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3054 model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3055 },
3056
3057 removeHoverStyle: function(point) {
3058 var me = this;
3059 var dataset = me.chart.data.datasets[point._datasetIndex];
3060 var index = point._index;
3061 var custom = point.custom || {};
3062 var model = point._model;
3063
3064 // Compatibility: If the properties are defined with only the old name, use those values
3065 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3066 dataset.pointRadius = dataset.radius;
3067 }
3068
3069 model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
3070 model.backgroundColor = me.getPointBackgroundColor(point, index);
3071 model.borderColor = me.getPointBorderColor(point, index);
3072 model.borderWidth = me.getPointBorderWidth(point, index);
3073 }
3074 });
3075};
3076
3077},{"25":25,"40":40,"45":45}],19:[function(require,module,exports){
3078'use strict';
3079
3080var defaults = require(25);
3081var elements = require(40);
3082var helpers = require(45);
3083
3084defaults._set('polarArea', {
3085 scale: {
3086 type: 'radialLinear',
3087 angleLines: {
3088 display: false
3089 },
3090 gridLines: {
3091 circular: true
3092 },
3093 pointLabels: {
3094 display: false
3095 },
3096 ticks: {
3097 beginAtZero: true
3098 }
3099 },
3100
3101 // Boolean - Whether to animate the rotation of the chart
3102 animation: {
3103 animateRotate: true,
3104 animateScale: true
3105 },
3106
3107 startAngle: -0.5 * Math.PI,
3108 legendCallback: function(chart) {
3109 var text = [];
3110 text.push('<ul class="' + chart.id + '-legend">');
3111
3112 var data = chart.data;
3113 var datasets = data.datasets;
3114 var labels = data.labels;
3115
3116 if (datasets.length) {
3117 for (var i = 0; i < datasets[0].data.length; ++i) {
3118 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
3119 if (labels[i]) {
3120 text.push(labels[i]);
3121 }
3122 text.push('</li>');
3123 }
3124 }
3125
3126 text.push('</ul>');
3127 return text.join('');
3128 },
3129 legend: {
3130 labels: {
3131 generateLabels: function(chart) {
3132 var data = chart.data;
3133 if (data.labels.length && data.datasets.length) {
3134 return data.labels.map(function(label, i) {
3135 var meta = chart.getDatasetMeta(0);
3136 var ds = data.datasets[0];
3137 var arc = meta.data[i];
3138 var custom = arc.custom || {};
3139 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
3140 var arcOpts = chart.options.elements.arc;
3141 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
3142 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
3143 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
3144
3145 return {
3146 text: label,
3147 fillStyle: fill,
3148 strokeStyle: stroke,
3149 lineWidth: bw,
3150 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
3151
3152 // Extra data used for toggling the correct item
3153 index: i
3154 };
3155 });
3156 }
3157 return [];
3158 }
3159 },
3160
3161 onClick: function(e, legendItem) {
3162 var index = legendItem.index;
3163 var chart = this.chart;
3164 var i, ilen, meta;
3165
3166 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
3167 meta = chart.getDatasetMeta(i);
3168 meta.data[index].hidden = !meta.data[index].hidden;
3169 }
3170
3171 chart.update();
3172 }
3173 },
3174
3175 // Need to override these to give a nice default
3176 tooltips: {
3177 callbacks: {
3178 title: function() {
3179 return '';
3180 },
3181 label: function(item, data) {
3182 return data.labels[item.index] + ': ' + item.yLabel;
3183 }
3184 }
3185 }
3186});
3187
3188module.exports = function(Chart) {
3189
3190 Chart.controllers.polarArea = Chart.DatasetController.extend({
3191
3192 dataElementType: elements.Arc,
3193
3194 linkScales: helpers.noop,
3195
3196 update: function(reset) {
3197 var me = this;
3198 var chart = me.chart;
3199 var chartArea = chart.chartArea;
3200 var meta = me.getMeta();
3201 var opts = chart.options;
3202 var arcOpts = opts.elements.arc;
3203 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
3204 chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
3205 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
3206 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
3207
3208 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
3209 me.innerRadius = me.outerRadius - chart.radiusLength;
3210
3211 meta.count = me.countVisibleElements();
3212
3213 helpers.each(meta.data, function(arc, index) {
3214 me.updateElement(arc, index, reset);
3215 });
3216 },
3217
3218 updateElement: function(arc, index, reset) {
3219 var me = this;
3220 var chart = me.chart;
3221 var dataset = me.getDataset();
3222 var opts = chart.options;
3223 var animationOpts = opts.animation;
3224 var scale = chart.scale;
3225 var labels = chart.data.labels;
3226
3227 var circumference = me.calculateCircumference(dataset.data[index]);
3228 var centerX = scale.xCenter;
3229 var centerY = scale.yCenter;
3230
3231 // If there is NaN data before us, we need to calculate the starting angle correctly.
3232 // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
3233 var visibleCount = 0;
3234 var meta = me.getMeta();
3235 for (var i = 0; i < index; ++i) {
3236 if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
3237 ++visibleCount;
3238 }
3239 }
3240
3241 // var negHalfPI = -0.5 * Math.PI;
3242 var datasetStartAngle = opts.startAngle;
3243 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3244 var startAngle = datasetStartAngle + (circumference * visibleCount);
3245 var endAngle = startAngle + (arc.hidden ? 0 : circumference);
3246
3247 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3248
3249 helpers.extend(arc, {
3250 // Utility
3251 _datasetIndex: me.index,
3252 _index: index,
3253 _scale: scale,
3254
3255 // Desired view properties
3256 _model: {
3257 x: centerX,
3258 y: centerY,
3259 innerRadius: 0,
3260 outerRadius: reset ? resetRadius : distance,
3261 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
3262 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
3263 label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
3264 }
3265 });
3266
3267 // Apply border and fill style
3268 me.removeHoverStyle(arc);
3269
3270 arc.pivot();
3271 },
3272
3273 removeHoverStyle: function(arc) {
3274 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
3275 },
3276
3277 countVisibleElements: function() {
3278 var dataset = this.getDataset();
3279 var meta = this.getMeta();
3280 var count = 0;
3281
3282 helpers.each(meta.data, function(element, index) {
3283 if (!isNaN(dataset.data[index]) && !element.hidden) {
3284 count++;
3285 }
3286 });
3287
3288 return count;
3289 },
3290
3291 calculateCircumference: function(value) {
3292 var count = this.getMeta().count;
3293 if (count > 0 && !isNaN(value)) {
3294 return (2 * Math.PI) / count;
3295 }
3296 return 0;
3297 }
3298 });
3299};
3300
3301},{"25":25,"40":40,"45":45}],20:[function(require,module,exports){
3302'use strict';
3303
3304var defaults = require(25);
3305var elements = require(40);
3306var helpers = require(45);
3307
3308defaults._set('radar', {
3309 scale: {
3310 type: 'radialLinear'
3311 },
3312 elements: {
3313 line: {
3314 tension: 0 // no bezier in radar
3315 }
3316 }
3317});
3318
3319module.exports = function(Chart) {
3320
3321 Chart.controllers.radar = Chart.DatasetController.extend({
3322
3323 datasetElementType: elements.Line,
3324
3325 dataElementType: elements.Point,
3326
3327 linkScales: helpers.noop,
3328
3329 update: function(reset) {
3330 var me = this;
3331 var meta = me.getMeta();
3332 var line = meta.dataset;
3333 var points = meta.data;
3334 var custom = line.custom || {};
3335 var dataset = me.getDataset();
3336 var lineElementOptions = me.chart.options.elements.line;
3337 var scale = me.chart.scale;
3338
3339 // Compatibility: If the properties are defined with only the old name, use those values
3340 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
3341 dataset.lineTension = dataset.tension;
3342 }
3343
3344 helpers.extend(meta.dataset, {
3345 // Utility
3346 _datasetIndex: me.index,
3347 _scale: scale,
3348 // Data
3349 _children: points,
3350 _loop: true,
3351 // Model
3352 _model: {
3353 // Appearance
3354 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
3355 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
3356 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
3357 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
3358 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
3359 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
3360 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
3361 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
3362 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
3363 }
3364 });
3365
3366 meta.dataset.pivot();
3367
3368 // Update Points
3369 helpers.each(points, function(point, index) {
3370 me.updateElement(point, index, reset);
3371 }, me);
3372
3373 // Update bezier control points
3374 me.updateBezierControlPoints();
3375 },
3376 updateElement: function(point, index, reset) {
3377 var me = this;
3378 var custom = point.custom || {};
3379 var dataset = me.getDataset();
3380 var scale = me.chart.scale;
3381 var pointElementOptions = me.chart.options.elements.point;
3382 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
3383
3384 // Compatibility: If the properties are defined with only the old name, use those values
3385 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3386 dataset.pointRadius = dataset.radius;
3387 }
3388 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
3389 dataset.pointHitRadius = dataset.hitRadius;
3390 }
3391
3392 helpers.extend(point, {
3393 // Utility
3394 _datasetIndex: me.index,
3395 _index: index,
3396 _scale: scale,
3397
3398 // Desired view properties
3399 _model: {
3400 x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
3401 y: reset ? scale.yCenter : pointPosition.y,
3402
3403 // Appearance
3404 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
3405 radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
3406 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
3407 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
3408 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
3409 pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
3410
3411 // Tooltip
3412 hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
3413 }
3414 });
3415
3416 point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
3417 },
3418 updateBezierControlPoints: function() {
3419 var chartArea = this.chart.chartArea;
3420 var meta = this.getMeta();
3421
3422 helpers.each(meta.data, function(point, index) {
3423 var model = point._model;
3424 var controlPoints = helpers.splineCurve(
3425 helpers.previousItem(meta.data, index, true)._model,
3426 model,
3427 helpers.nextItem(meta.data, index, true)._model,
3428 model.tension
3429 );
3430
3431 // Prevent the bezier going outside of the bounds of the graph
3432 model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
3433 model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
3434
3435 model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
3436 model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
3437
3438 // Now pivot the point for animation
3439 point.pivot();
3440 });
3441 },
3442
3443 setHoverStyle: function(point) {
3444 // Point
3445 var dataset = this.chart.data.datasets[point._datasetIndex];
3446 var custom = point.custom || {};
3447 var index = point._index;
3448 var model = point._model;
3449
3450 model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3451 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3452 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3453 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3454 },
3455
3456 removeHoverStyle: function(point) {
3457 var dataset = this.chart.data.datasets[point._datasetIndex];
3458 var custom = point.custom || {};
3459 var index = point._index;
3460 var model = point._model;
3461 var pointElementOptions = this.chart.options.elements.point;
3462
3463 model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius);
3464 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
3465 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
3466 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
3467 }
3468 });
3469};
3470
3471},{"25":25,"40":40,"45":45}],21:[function(require,module,exports){
3472'use strict';
3473
3474var defaults = require(25);
3475
3476defaults._set('scatter', {
3477 hover: {
3478 mode: 'single'
3479 },
3480
3481 scales: {
3482 xAxes: [{
3483 id: 'x-axis-1', // need an ID so datasets can reference the scale
3484 type: 'linear', // scatter should not use a category axis
3485 position: 'bottom'
3486 }],
3487 yAxes: [{
3488 id: 'y-axis-1',
3489 type: 'linear',
3490 position: 'left'
3491 }]
3492 },
3493
3494 showLines: false,
3495
3496 tooltips: {
3497 callbacks: {
3498 title: function() {
3499 return ''; // doesn't make sense for scatter since data are formatted as a point
3500 },
3501 label: function(item) {
3502 return '(' + item.xLabel + ', ' + item.yLabel + ')';
3503 }
3504 }
3505 }
3506});
3507
3508module.exports = function(Chart) {
3509
3510 // Scatter charts use line controllers
3511 Chart.controllers.scatter = Chart.controllers.line;
3512
3513};
3514
3515},{"25":25}],22:[function(require,module,exports){
3516/* global window: false */
3517'use strict';
3518
3519var defaults = require(25);
3520var Element = require(26);
3521var helpers = require(45);
3522
3523defaults._set('global', {
3524 animation: {
3525 duration: 1000,
3526 easing: 'easeOutQuart',
3527 onProgress: helpers.noop,
3528 onComplete: helpers.noop
3529 }
3530});
3531
3532module.exports = function(Chart) {
3533
3534 Chart.Animation = Element.extend({
3535 chart: null, // the animation associated chart instance
3536 currentStep: 0, // the current animation step
3537 numSteps: 60, // default number of steps
3538 easing: '', // the easing to use for this animation
3539 render: null, // render function used by the animation service
3540
3541 onAnimationProgress: null, // user specified callback to fire on each step of the animation
3542 onAnimationComplete: null, // user specified callback to fire when the animation finishes
3543 });
3544
3545 Chart.animationService = {
3546 frameDuration: 17,
3547 animations: [],
3548 dropFrames: 0,
3549 request: null,
3550
3551 /**
3552 * @param {Chart} chart - The chart to animate.
3553 * @param {Chart.Animation} animation - The animation that we will animate.
3554 * @param {Number} duration - The animation duration in ms.
3555 * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
3556 */
3557 addAnimation: function(chart, animation, duration, lazy) {
3558 var animations = this.animations;
3559 var i, ilen;
3560
3561 animation.chart = chart;
3562
3563 if (!lazy) {
3564 chart.animating = true;
3565 }
3566
3567 for (i = 0, ilen = animations.length; i < ilen; ++i) {
3568 if (animations[i].chart === chart) {
3569 animations[i] = animation;
3570 return;
3571 }
3572 }
3573
3574 animations.push(animation);
3575
3576 // If there are no animations queued, manually kickstart a digest, for lack of a better word
3577 if (animations.length === 1) {
3578 this.requestAnimationFrame();
3579 }
3580 },
3581
3582 cancelAnimation: function(chart) {
3583 var index = helpers.findIndex(this.animations, function(animation) {
3584 return animation.chart === chart;
3585 });
3586
3587 if (index !== -1) {
3588 this.animations.splice(index, 1);
3589 chart.animating = false;
3590 }
3591 },
3592
3593 requestAnimationFrame: function() {
3594 var me = this;
3595 if (me.request === null) {
3596 // Skip animation frame requests until the active one is executed.
3597 // This can happen when processing mouse events, e.g. 'mousemove'
3598 // and 'mouseout' events will trigger multiple renders.
3599 me.request = helpers.requestAnimFrame.call(window, function() {
3600 me.request = null;
3601 me.startDigest();
3602 });
3603 }
3604 },
3605
3606 /**
3607 * @private
3608 */
3609 startDigest: function() {
3610 var me = this;
3611 var startTime = Date.now();
3612 var framesToDrop = 0;
3613
3614 if (me.dropFrames > 1) {
3615 framesToDrop = Math.floor(me.dropFrames);
3616 me.dropFrames = me.dropFrames % 1;
3617 }
3618
3619 me.advance(1 + framesToDrop);
3620
3621 var endTime = Date.now();
3622
3623 me.dropFrames += (endTime - startTime) / me.frameDuration;
3624
3625 // Do we have more stuff to animate?
3626 if (me.animations.length > 0) {
3627 me.requestAnimationFrame();
3628 }
3629 },
3630
3631 /**
3632 * @private
3633 */
3634 advance: function(count) {
3635 var animations = this.animations;
3636 var animation, chart;
3637 var i = 0;
3638
3639 while (i < animations.length) {
3640 animation = animations[i];
3641 chart = animation.chart;
3642
3643 animation.currentStep = (animation.currentStep || 0) + count;
3644 animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
3645
3646 helpers.callback(animation.render, [chart, animation], chart);
3647 helpers.callback(animation.onAnimationProgress, [animation], chart);
3648
3649 if (animation.currentStep >= animation.numSteps) {
3650 helpers.callback(animation.onAnimationComplete, [animation], chart);
3651 chart.animating = false;
3652 animations.splice(i, 1);
3653 } else {
3654 ++i;
3655 }
3656 }
3657 }
3658 };
3659
3660 /**
3661 * Provided for backward compatibility, use Chart.Animation instead
3662 * @prop Chart.Animation#animationObject
3663 * @deprecated since version 2.6.0
3664 * @todo remove at version 3
3665 */
3666 Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
3667 get: function() {
3668 return this;
3669 }
3670 });
3671
3672 /**
3673 * Provided for backward compatibility, use Chart.Animation#chart instead
3674 * @prop Chart.Animation#chartInstance
3675 * @deprecated since version 2.6.0
3676 * @todo remove at version 3
3677 */
3678 Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
3679 get: function() {
3680 return this.chart;
3681 },
3682 set: function(value) {
3683 this.chart = value;
3684 }
3685 });
3686
3687};
3688
3689},{"25":25,"26":26,"45":45}],23:[function(require,module,exports){
3690'use strict';
3691
3692var defaults = require(25);
3693var helpers = require(45);
3694var Interaction = require(28);
3695var platform = require(48);
3696
3697module.exports = function(Chart) {
3698 var plugins = Chart.plugins;
3699
3700 // Create a dictionary of chart types, to allow for extension of existing types
3701 Chart.types = {};
3702
3703 // Store a reference to each instance - allowing us to globally resize chart instances on window resize.
3704 // Destroy method on the chart will remove the instance of the chart from this reference.
3705 Chart.instances = {};
3706
3707 // Controllers available for dataset visualization eg. bar, line, slice, etc.
3708 Chart.controllers = {};
3709
3710 /**
3711 * Initializes the given config with global and chart default values.
3712 */
3713 function initConfig(config) {
3714 config = config || {};
3715
3716 // Do NOT use configMerge() for the data object because this method merges arrays
3717 // and so would change references to labels and datasets, preventing data updates.
3718 var data = config.data = config.data || {};
3719 data.datasets = data.datasets || [];
3720 data.labels = data.labels || [];
3721
3722 config.options = helpers.configMerge(
3723 defaults.global,
3724 defaults[config.type],
3725 config.options || {});
3726
3727 return config;
3728 }
3729
3730 /**
3731 * Updates the config of the chart
3732 * @param chart {Chart} chart to update the options for
3733 */
3734 function updateConfig(chart) {
3735 var newOptions = chart.options;
3736
3737 // Update Scale(s) with options
3738 if (newOptions.scale) {
3739 chart.scale.options = newOptions.scale;
3740 } else if (newOptions.scales) {
3741 newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) {
3742 chart.scales[scaleOptions.id].options = scaleOptions;
3743 });
3744 }
3745
3746 // Tooltip
3747 chart.tooltip._options = newOptions.tooltips;
3748 }
3749
3750 function positionIsHorizontal(position) {
3751 return position === 'top' || position === 'bottom';
3752 }
3753
3754 helpers.extend(Chart.prototype, /** @lends Chart */ {
3755 /**
3756 * @private
3757 */
3758 construct: function(item, config) {
3759 var me = this;
3760
3761 config = initConfig(config);
3762
3763 var context = platform.acquireContext(item, config);
3764 var canvas = context && context.canvas;
3765 var height = canvas && canvas.height;
3766 var width = canvas && canvas.width;
3767
3768 me.id = helpers.uid();
3769 me.ctx = context;
3770 me.canvas = canvas;
3771 me.config = config;
3772 me.width = width;
3773 me.height = height;
3774 me.aspectRatio = height ? width / height : null;
3775 me.options = config.options;
3776 me._bufferedRender = false;
3777
3778 /**
3779 * Provided for backward compatibility, Chart and Chart.Controller have been merged,
3780 * the "instance" still need to be defined since it might be called from plugins.
3781 * @prop Chart#chart
3782 * @deprecated since version 2.6.0
3783 * @todo remove at version 3
3784 * @private
3785 */
3786 me.chart = me;
3787 me.controller = me; // chart.chart.controller #inception
3788
3789 // Add the chart instance to the global namespace
3790 Chart.instances[me.id] = me;
3791
3792 // Define alias to the config data: `chart.data === chart.config.data`
3793 Object.defineProperty(me, 'data', {
3794 get: function() {
3795 return me.config.data;
3796 },
3797 set: function(value) {
3798 me.config.data = value;
3799 }
3800 });
3801
3802 if (!context || !canvas) {
3803 // The given item is not a compatible context2d element, let's return before finalizing
3804 // the chart initialization but after setting basic chart / controller properties that
3805 // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
3806 // https://github.com/chartjs/Chart.js/issues/2807
3807 console.error("Failed to create chart: can't acquire context from the given item");
3808 return;
3809 }
3810
3811 me.initialize();
3812 me.update();
3813 },
3814
3815 /**
3816 * @private
3817 */
3818 initialize: function() {
3819 var me = this;
3820
3821 // Before init plugin notification
3822 plugins.notify(me, 'beforeInit');
3823
3824 helpers.retinaScale(me, me.options.devicePixelRatio);
3825
3826 me.bindEvents();
3827
3828 if (me.options.responsive) {
3829 // Initial resize before chart draws (must be silent to preserve initial animations).
3830 me.resize(true);
3831 }
3832
3833 // Make sure scales have IDs and are built before we build any controllers.
3834 me.ensureScalesHaveIDs();
3835 me.buildScales();
3836 me.initToolTip();
3837
3838 // After init plugin notification
3839 plugins.notify(me, 'afterInit');
3840
3841 return me;
3842 },
3843
3844 clear: function() {
3845 helpers.canvas.clear(this);
3846 return this;
3847 },
3848
3849 stop: function() {
3850 // Stops any current animation loop occurring
3851 Chart.animationService.cancelAnimation(this);
3852 return this;
3853 },
3854
3855 resize: function(silent) {
3856 var me = this;
3857 var options = me.options;
3858 var canvas = me.canvas;
3859 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
3860
3861 // the canvas render width and height will be casted to integers so make sure that
3862 // the canvas display style uses the same integer values to avoid blurring effect.
3863
3864 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased
3865 var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
3866 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
3867
3868 if (me.width === newWidth && me.height === newHeight) {
3869 return;
3870 }
3871
3872 canvas.width = me.width = newWidth;
3873 canvas.height = me.height = newHeight;
3874 canvas.style.width = newWidth + 'px';
3875 canvas.style.height = newHeight + 'px';
3876
3877 helpers.retinaScale(me, options.devicePixelRatio);
3878
3879 if (!silent) {
3880 // Notify any plugins about the resize
3881 var newSize = {width: newWidth, height: newHeight};
3882 plugins.notify(me, 'resize', [newSize]);
3883
3884 // Notify of resize
3885 if (me.options.onResize) {
3886 me.options.onResize(me, newSize);
3887 }
3888
3889 me.stop();
3890 me.update(me.options.responsiveAnimationDuration);
3891 }
3892 },
3893
3894 ensureScalesHaveIDs: function() {
3895 var options = this.options;
3896 var scalesOptions = options.scales || {};
3897 var scaleOptions = options.scale;
3898
3899 helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
3900 xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
3901 });
3902
3903 helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
3904 yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
3905 });
3906
3907 if (scaleOptions) {
3908 scaleOptions.id = scaleOptions.id || 'scale';
3909 }
3910 },
3911
3912 /**
3913 * Builds a map of scale ID to scale object for future lookup.
3914 */
3915 buildScales: function() {
3916 var me = this;
3917 var options = me.options;
3918 var scales = me.scales = {};
3919 var items = [];
3920
3921 if (options.scales) {
3922 items = items.concat(
3923 (options.scales.xAxes || []).map(function(xAxisOptions) {
3924 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
3925 }),
3926 (options.scales.yAxes || []).map(function(yAxisOptions) {
3927 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
3928 })
3929 );
3930 }
3931
3932 if (options.scale) {
3933 items.push({
3934 options: options.scale,
3935 dtype: 'radialLinear',
3936 isDefault: true,
3937 dposition: 'chartArea'
3938 });
3939 }
3940
3941 helpers.each(items, function(item) {
3942 var scaleOptions = item.options;
3943 var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype);
3944 var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
3945 if (!scaleClass) {
3946 return;
3947 }
3948
3949 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
3950 scaleOptions.position = item.dposition;
3951 }
3952
3953 var scale = new scaleClass({
3954 id: scaleOptions.id,
3955 options: scaleOptions,
3956 ctx: me.ctx,
3957 chart: me
3958 });
3959
3960 scales[scale.id] = scale;
3961 scale.mergeTicksOptions();
3962
3963 // TODO(SB): I think we should be able to remove this custom case (options.scale)
3964 // and consider it as a regular scale part of the "scales"" map only! This would
3965 // make the logic easier and remove some useless? custom code.
3966 if (item.isDefault) {
3967 me.scale = scale;
3968 }
3969 });
3970
3971 Chart.scaleService.addScalesToLayout(this);
3972 },
3973
3974 buildOrUpdateControllers: function() {
3975 var me = this;
3976 var types = [];
3977 var newControllers = [];
3978
3979 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
3980 var meta = me.getDatasetMeta(datasetIndex);
3981 var type = dataset.type || me.config.type;
3982
3983 if (meta.type && meta.type !== type) {
3984 me.destroyDatasetMeta(datasetIndex);
3985 meta = me.getDatasetMeta(datasetIndex);
3986 }
3987 meta.type = type;
3988
3989 types.push(meta.type);
3990
3991 if (meta.controller) {
3992 meta.controller.updateIndex(datasetIndex);
3993 } else {
3994 var ControllerClass = Chart.controllers[meta.type];
3995 if (ControllerClass === undefined) {
3996 throw new Error('"' + meta.type + '" is not a chart type.');
3997 }
3998
3999 meta.controller = new ControllerClass(me, datasetIndex);
4000 newControllers.push(meta.controller);
4001 }
4002 }, me);
4003
4004 return newControllers;
4005 },
4006
4007 /**
4008 * Reset the elements of all datasets
4009 * @private
4010 */
4011 resetElements: function() {
4012 var me = this;
4013 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4014 me.getDatasetMeta(datasetIndex).controller.reset();
4015 }, me);
4016 },
4017
4018 /**
4019 * Resets the chart back to it's state before the initial animation
4020 */
4021 reset: function() {
4022 this.resetElements();
4023 this.tooltip.initialize();
4024 },
4025
4026 update: function(config) {
4027 var me = this;
4028
4029 if (!config || typeof config !== 'object') {
4030 // backwards compatibility
4031 config = {
4032 duration: config,
4033 lazy: arguments[1]
4034 };
4035 }
4036
4037 updateConfig(me);
4038
4039 if (plugins.notify(me, 'beforeUpdate') === false) {
4040 return;
4041 }
4042
4043 // In case the entire data object changed
4044 me.tooltip._data = me.data;
4045
4046 // Make sure dataset controllers are updated and new controllers are reset
4047 var newControllers = me.buildOrUpdateControllers();
4048
4049 // Make sure all dataset controllers have correct meta data counts
4050 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4051 me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
4052 }, me);
4053
4054 me.updateLayout();
4055
4056 // Can only reset the new controllers after the scales have been updated
4057 helpers.each(newControllers, function(controller) {
4058 controller.reset();
4059 });
4060
4061 me.updateDatasets();
4062
4063 // Need to reset tooltip in case it is displayed with elements that are removed
4064 // after update.
4065 me.tooltip.initialize();
4066
4067 // Last active contains items that were previously in the tooltip.
4068 // When we reset the tooltip, we need to clear it
4069 me.lastActive = [];
4070
4071 // Do this before render so that any plugins that need final scale updates can use it
4072 plugins.notify(me, 'afterUpdate');
4073
4074 if (me._bufferedRender) {
4075 me._bufferedRequest = {
4076 duration: config.duration,
4077 easing: config.easing,
4078 lazy: config.lazy
4079 };
4080 } else {
4081 me.render(config);
4082 }
4083 },
4084
4085 /**
4086 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
4087 * hook, in which case, plugins will not be called on `afterLayout`.
4088 * @private
4089 */
4090 updateLayout: function() {
4091 var me = this;
4092
4093 if (plugins.notify(me, 'beforeLayout') === false) {
4094 return;
4095 }
4096
4097 Chart.layoutService.update(this, this.width, this.height);
4098
4099 /**
4100 * Provided for backward compatibility, use `afterLayout` instead.
4101 * @method IPlugin#afterScaleUpdate
4102 * @deprecated since version 2.5.0
4103 * @todo remove at version 3
4104 * @private
4105 */
4106 plugins.notify(me, 'afterScaleUpdate');
4107 plugins.notify(me, 'afterLayout');
4108 },
4109
4110 /**
4111 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
4112 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
4113 * @private
4114 */
4115 updateDatasets: function() {
4116 var me = this;
4117
4118 if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
4119 return;
4120 }
4121
4122 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4123 me.updateDataset(i);
4124 }
4125
4126 plugins.notify(me, 'afterDatasetsUpdate');
4127 },
4128
4129 /**
4130 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
4131 * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
4132 * @private
4133 */
4134 updateDataset: function(index) {
4135 var me = this;
4136 var meta = me.getDatasetMeta(index);
4137 var args = {
4138 meta: meta,
4139 index: index
4140 };
4141
4142 if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
4143 return;
4144 }
4145
4146 meta.controller.update();
4147
4148 plugins.notify(me, 'afterDatasetUpdate', [args]);
4149 },
4150
4151 render: function(config) {
4152 var me = this;
4153
4154 if (!config || typeof config !== 'object') {
4155 // backwards compatibility
4156 config = {
4157 duration: config,
4158 lazy: arguments[1]
4159 };
4160 }
4161
4162 var duration = config.duration;
4163 var lazy = config.lazy;
4164
4165 if (plugins.notify(me, 'beforeRender') === false) {
4166 return;
4167 }
4168
4169 var animationOptions = me.options.animation;
4170 var onComplete = function(animation) {
4171 plugins.notify(me, 'afterRender');
4172 helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
4173 };
4174
4175 if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
4176 var animation = new Chart.Animation({
4177 numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
4178 easing: config.easing || animationOptions.easing,
4179
4180 render: function(chart, animationObject) {
4181 var easingFunction = helpers.easing.effects[animationObject.easing];
4182 var currentStep = animationObject.currentStep;
4183 var stepDecimal = currentStep / animationObject.numSteps;
4184
4185 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
4186 },
4187
4188 onAnimationProgress: animationOptions.onProgress,
4189 onAnimationComplete: onComplete
4190 });
4191
4192 Chart.animationService.addAnimation(me, animation, duration, lazy);
4193 } else {
4194 me.draw();
4195
4196 // See https://github.com/chartjs/Chart.js/issues/3781
4197 onComplete(new Chart.Animation({numSteps: 0, chart: me}));
4198 }
4199
4200 return me;
4201 },
4202
4203 draw: function(easingValue) {
4204 var me = this;
4205
4206 me.clear();
4207
4208 if (helpers.isNullOrUndef(easingValue)) {
4209 easingValue = 1;
4210 }
4211
4212 me.transition(easingValue);
4213
4214 if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
4215 return;
4216 }
4217
4218 // Draw all the scales
4219 helpers.each(me.boxes, function(box) {
4220 box.draw(me.chartArea);
4221 }, me);
4222
4223 if (me.scale) {
4224 me.scale.draw();
4225 }
4226
4227 me.drawDatasets(easingValue);
4228 me._drawTooltip(easingValue);
4229
4230 plugins.notify(me, 'afterDraw', [easingValue]);
4231 },
4232
4233 /**
4234 * @private
4235 */
4236 transition: function(easingValue) {
4237 var me = this;
4238
4239 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
4240 if (me.isDatasetVisible(i)) {
4241 me.getDatasetMeta(i).controller.transition(easingValue);
4242 }
4243 }
4244
4245 me.tooltip.transition(easingValue);
4246 },
4247
4248 /**
4249 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
4250 * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
4251 * @private
4252 */
4253 drawDatasets: function(easingValue) {
4254 var me = this;
4255
4256 if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
4257 return;
4258 }
4259
4260 // Draw datasets reversed to support proper line stacking
4261 for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
4262 if (me.isDatasetVisible(i)) {
4263 me.drawDataset(i, easingValue);
4264 }
4265 }
4266
4267 plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
4268 },
4269
4270 /**
4271 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
4272 * hook, in which case, plugins will not be called on `afterDatasetDraw`.
4273 * @private
4274 */
4275 drawDataset: function(index, easingValue) {
4276 var me = this;
4277 var meta = me.getDatasetMeta(index);
4278 var args = {
4279 meta: meta,
4280 index: index,
4281 easingValue: easingValue
4282 };
4283
4284 if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
4285 return;
4286 }
4287
4288 meta.controller.draw(easingValue);
4289
4290 plugins.notify(me, 'afterDatasetDraw', [args]);
4291 },
4292
4293 /**
4294 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
4295 * hook, in which case, plugins will not be called on `afterTooltipDraw`.
4296 * @private
4297 */
4298 _drawTooltip: function(easingValue) {
4299 var me = this;
4300 var tooltip = me.tooltip;
4301 var args = {
4302 tooltip: tooltip,
4303 easingValue: easingValue
4304 };
4305
4306 if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
4307 return;
4308 }
4309
4310 tooltip.draw();
4311
4312 plugins.notify(me, 'afterTooltipDraw', [args]);
4313 },
4314
4315 // Get the single element that was clicked on
4316 // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
4317 getElementAtEvent: function(e) {
4318 return Interaction.modes.single(this, e);
4319 },
4320
4321 getElementsAtEvent: function(e) {
4322 return Interaction.modes.label(this, e, {intersect: true});
4323 },
4324
4325 getElementsAtXAxis: function(e) {
4326 return Interaction.modes['x-axis'](this, e, {intersect: true});
4327 },
4328
4329 getElementsAtEventForMode: function(e, mode, options) {
4330 var method = Interaction.modes[mode];
4331 if (typeof method === 'function') {
4332 return method(this, e, options);
4333 }
4334
4335 return [];
4336 },
4337
4338 getDatasetAtEvent: function(e) {
4339 return Interaction.modes.dataset(this, e, {intersect: true});
4340 },
4341
4342 getDatasetMeta: function(datasetIndex) {
4343 var me = this;
4344 var dataset = me.data.datasets[datasetIndex];
4345 if (!dataset._meta) {
4346 dataset._meta = {};
4347 }
4348
4349 var meta = dataset._meta[me.id];
4350 if (!meta) {
4351 meta = dataset._meta[me.id] = {
4352 type: null,
4353 data: [],
4354 dataset: null,
4355 controller: null,
4356 hidden: null, // See isDatasetVisible() comment
4357 xAxisID: null,
4358 yAxisID: null
4359 };
4360 }
4361
4362 return meta;
4363 },
4364
4365 getVisibleDatasetCount: function() {
4366 var count = 0;
4367 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
4368 if (this.isDatasetVisible(i)) {
4369 count++;
4370 }
4371 }
4372 return count;
4373 },
4374
4375 isDatasetVisible: function(datasetIndex) {
4376 var meta = this.getDatasetMeta(datasetIndex);
4377
4378 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
4379 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
4380 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
4381 },
4382
4383 generateLegend: function() {
4384 return this.options.legendCallback(this);
4385 },
4386
4387 /**
4388 * @private
4389 */
4390 destroyDatasetMeta: function(datasetIndex) {
4391 var id = this.id;
4392 var dataset = this.data.datasets[datasetIndex];
4393 var meta = dataset._meta && dataset._meta[id];
4394
4395 if (meta) {
4396 meta.controller.destroy();
4397 delete dataset._meta[id];
4398 }
4399 },
4400
4401 destroy: function() {
4402 var me = this;
4403 var canvas = me.canvas;
4404 var i, ilen;
4405
4406 me.stop();
4407
4408 // dataset controllers need to cleanup associated data
4409 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4410 me.destroyDatasetMeta(i);
4411 }
4412
4413 if (canvas) {
4414 me.unbindEvents();
4415 helpers.canvas.clear(me);
4416 platform.releaseContext(me.ctx);
4417 me.canvas = null;
4418 me.ctx = null;
4419 }
4420
4421 plugins.notify(me, 'destroy');
4422
4423 delete Chart.instances[me.id];
4424 },
4425
4426 toBase64Image: function() {
4427 return this.canvas.toDataURL.apply(this.canvas, arguments);
4428 },
4429
4430 initToolTip: function() {
4431 var me = this;
4432 me.tooltip = new Chart.Tooltip({
4433 _chart: me,
4434 _chartInstance: me, // deprecated, backward compatibility
4435 _data: me.data,
4436 _options: me.options.tooltips
4437 }, me);
4438 },
4439
4440 /**
4441 * @private
4442 */
4443 bindEvents: function() {
4444 var me = this;
4445 var listeners = me._listeners = {};
4446 var listener = function() {
4447 me.eventHandler.apply(me, arguments);
4448 };
4449
4450 helpers.each(me.options.events, function(type) {
4451 platform.addEventListener(me, type, listener);
4452 listeners[type] = listener;
4453 });
4454
4455 // Elements used to detect size change should not be injected for non responsive charts.
4456 // See https://github.com/chartjs/Chart.js/issues/2210
4457 if (me.options.responsive) {
4458 listener = function() {
4459 me.resize();
4460 };
4461
4462 platform.addEventListener(me, 'resize', listener);
4463 listeners.resize = listener;
4464 }
4465 },
4466
4467 /**
4468 * @private
4469 */
4470 unbindEvents: function() {
4471 var me = this;
4472 var listeners = me._listeners;
4473 if (!listeners) {
4474 return;
4475 }
4476
4477 delete me._listeners;
4478 helpers.each(listeners, function(listener, type) {
4479 platform.removeEventListener(me, type, listener);
4480 });
4481 },
4482
4483 updateHoverStyle: function(elements, mode, enabled) {
4484 var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
4485 var element, i, ilen;
4486
4487 for (i = 0, ilen = elements.length; i < ilen; ++i) {
4488 element = elements[i];
4489 if (element) {
4490 this.getDatasetMeta(element._datasetIndex).controller[method](element);
4491 }
4492 }
4493 },
4494
4495 /**
4496 * @private
4497 */
4498 eventHandler: function(e) {
4499 var me = this;
4500 var tooltip = me.tooltip;
4501
4502 if (plugins.notify(me, 'beforeEvent', [e]) === false) {
4503 return;
4504 }
4505
4506 // Buffer any update calls so that renders do not occur
4507 me._bufferedRender = true;
4508 me._bufferedRequest = null;
4509
4510 var changed = me.handleEvent(e);
4511 changed |= tooltip && tooltip.handleEvent(e);
4512
4513 plugins.notify(me, 'afterEvent', [e]);
4514
4515 var bufferedRequest = me._bufferedRequest;
4516 if (bufferedRequest) {
4517 // If we have an update that was triggered, we need to do a normal render
4518 me.render(bufferedRequest);
4519 } else if (changed && !me.animating) {
4520 // If entering, leaving, or changing elements, animate the change via pivot
4521 me.stop();
4522
4523 // We only need to render at this point. Updating will cause scales to be
4524 // recomputed generating flicker & using more memory than necessary.
4525 me.render(me.options.hover.animationDuration, true);
4526 }
4527
4528 me._bufferedRender = false;
4529 me._bufferedRequest = null;
4530
4531 return me;
4532 },
4533
4534 /**
4535 * Handle an event
4536 * @private
4537 * @param {IEvent} event the event to handle
4538 * @return {Boolean} true if the chart needs to re-render
4539 */
4540 handleEvent: function(e) {
4541 var me = this;
4542 var options = me.options || {};
4543 var hoverOptions = options.hover;
4544 var changed = false;
4545
4546 me.lastActive = me.lastActive || [];
4547
4548 // Find Active Elements for hover and tooltips
4549 if (e.type === 'mouseout') {
4550 me.active = [];
4551 } else {
4552 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
4553 }
4554
4555 // Invoke onHover hook
4556 // Need to call with native event here to not break backwards compatibility
4557 helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
4558
4559 if (e.type === 'mouseup' || e.type === 'click') {
4560 if (options.onClick) {
4561 // Use e.native here for backwards compatibility
4562 options.onClick.call(me, e.native, me.active);
4563 }
4564 }
4565
4566 // Remove styling for last active (even if it may still be active)
4567 if (me.lastActive.length) {
4568 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
4569 }
4570
4571 // Built in hover styling
4572 if (me.active.length && hoverOptions.mode) {
4573 me.updateHoverStyle(me.active, hoverOptions.mode, true);
4574 }
4575
4576 changed = !helpers.arrayEquals(me.active, me.lastActive);
4577
4578 // Remember Last Actives
4579 me.lastActive = me.active;
4580
4581 return changed;
4582 }
4583 });
4584
4585 /**
4586 * Provided for backward compatibility, use Chart instead.
4587 * @class Chart.Controller
4588 * @deprecated since version 2.6.0
4589 * @todo remove at version 3
4590 * @private
4591 */
4592 Chart.Controller = Chart;
4593};
4594
4595},{"25":25,"28":28,"45":45,"48":48}],24:[function(require,module,exports){
4596'use strict';
4597
4598var helpers = require(45);
4599
4600module.exports = function(Chart) {
4601
4602 var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
4603
4604 /**
4605 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
4606 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
4607 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
4608 */
4609 function listenArrayEvents(array, listener) {
4610 if (array._chartjs) {
4611 array._chartjs.listeners.push(listener);
4612 return;
4613 }
4614
4615 Object.defineProperty(array, '_chartjs', {
4616 configurable: true,
4617 enumerable: false,
4618 value: {
4619 listeners: [listener]
4620 }
4621 });
4622
4623 arrayEvents.forEach(function(key) {
4624 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
4625 var base = array[key];
4626
4627 Object.defineProperty(array, key, {
4628 configurable: true,
4629 enumerable: false,
4630 value: function() {
4631 var args = Array.prototype.slice.call(arguments);
4632 var res = base.apply(this, args);
4633
4634 helpers.each(array._chartjs.listeners, function(object) {
4635 if (typeof object[method] === 'function') {
4636 object[method].apply(object, args);
4637 }
4638 });
4639
4640 return res;
4641 }
4642 });
4643 });
4644 }
4645
4646 /**
4647 * Removes the given array event listener and cleanup extra attached properties (such as
4648 * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
4649 */
4650 function unlistenArrayEvents(array, listener) {
4651 var stub = array._chartjs;
4652 if (!stub) {
4653 return;
4654 }
4655
4656 var listeners = stub.listeners;
4657 var index = listeners.indexOf(listener);
4658 if (index !== -1) {
4659 listeners.splice(index, 1);
4660 }
4661
4662 if (listeners.length > 0) {
4663 return;
4664 }
4665
4666 arrayEvents.forEach(function(key) {
4667 delete array[key];
4668 });
4669
4670 delete array._chartjs;
4671 }
4672
4673 // Base class for all dataset controllers (line, bar, etc)
4674 Chart.DatasetController = function(chart, datasetIndex) {
4675 this.initialize(chart, datasetIndex);
4676 };
4677
4678 helpers.extend(Chart.DatasetController.prototype, {
4679
4680 /**
4681 * Element type used to generate a meta dataset (e.g. Chart.element.Line).
4682 * @type {Chart.core.element}
4683 */
4684 datasetElementType: null,
4685
4686 /**
4687 * Element type used to generate a meta data (e.g. Chart.element.Point).
4688 * @type {Chart.core.element}
4689 */
4690 dataElementType: null,
4691
4692 initialize: function(chart, datasetIndex) {
4693 var me = this;
4694 me.chart = chart;
4695 me.index = datasetIndex;
4696 me.linkScales();
4697 me.addElements();
4698 },
4699
4700 updateIndex: function(datasetIndex) {
4701 this.index = datasetIndex;
4702 },
4703
4704 linkScales: function() {
4705 var me = this;
4706 var meta = me.getMeta();
4707 var dataset = me.getDataset();
4708
4709 if (meta.xAxisID === null) {
4710 meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
4711 }
4712 if (meta.yAxisID === null) {
4713 meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
4714 }
4715 },
4716
4717 getDataset: function() {
4718 return this.chart.data.datasets[this.index];
4719 },
4720
4721 getMeta: function() {
4722 return this.chart.getDatasetMeta(this.index);
4723 },
4724
4725 getScaleForId: function(scaleID) {
4726 return this.chart.scales[scaleID];
4727 },
4728
4729 reset: function() {
4730 this.update(true);
4731 },
4732
4733 /**
4734 * @private
4735 */
4736 destroy: function() {
4737 if (this._data) {
4738 unlistenArrayEvents(this._data, this);
4739 }
4740 },
4741
4742 createMetaDataset: function() {
4743 var me = this;
4744 var type = me.datasetElementType;
4745 return type && new type({
4746 _chart: me.chart,
4747 _datasetIndex: me.index
4748 });
4749 },
4750
4751 createMetaData: function(index) {
4752 var me = this;
4753 var type = me.dataElementType;
4754 return type && new type({
4755 _chart: me.chart,
4756 _datasetIndex: me.index,
4757 _index: index
4758 });
4759 },
4760
4761 addElements: function() {
4762 var me = this;
4763 var meta = me.getMeta();
4764 var data = me.getDataset().data || [];
4765 var metaData = meta.data;
4766 var i, ilen;
4767
4768 for (i = 0, ilen = data.length; i < ilen; ++i) {
4769 metaData[i] = metaData[i] || me.createMetaData(i);
4770 }
4771
4772 meta.dataset = meta.dataset || me.createMetaDataset();
4773 },
4774
4775 addElementAndReset: function(index) {
4776 var element = this.createMetaData(index);
4777 this.getMeta().data.splice(index, 0, element);
4778 this.updateElement(element, index, true);
4779 },
4780
4781 buildOrUpdateElements: function() {
4782 var me = this;
4783 var dataset = me.getDataset();
4784 var data = dataset.data || (dataset.data = []);
4785
4786 // In order to correctly handle data addition/deletion animation (an thus simulate
4787 // real-time charts), we need to monitor these data modifications and synchronize
4788 // the internal meta data accordingly.
4789 if (me._data !== data) {
4790 if (me._data) {
4791 // This case happens when the user replaced the data array instance.
4792 unlistenArrayEvents(me._data, me);
4793 }
4794
4795 listenArrayEvents(data, me);
4796 me._data = data;
4797 }
4798
4799 // Re-sync meta data in case the user replaced the data array or if we missed
4800 // any updates and so make sure that we handle number of datapoints changing.
4801 me.resyncElements();
4802 },
4803
4804 update: helpers.noop,
4805
4806 transition: function(easingValue) {
4807 var meta = this.getMeta();
4808 var elements = meta.data || [];
4809 var ilen = elements.length;
4810 var i = 0;
4811
4812 for (; i < ilen; ++i) {
4813 elements[i].transition(easingValue);
4814 }
4815
4816 if (meta.dataset) {
4817 meta.dataset.transition(easingValue);
4818 }
4819 },
4820
4821 draw: function() {
4822 var meta = this.getMeta();
4823 var elements = meta.data || [];
4824 var ilen = elements.length;
4825 var i = 0;
4826
4827 if (meta.dataset) {
4828 meta.dataset.draw();
4829 }
4830
4831 for (; i < ilen; ++i) {
4832 elements[i].draw();
4833 }
4834 },
4835
4836 removeHoverStyle: function(element, elementOpts) {
4837 var dataset = this.chart.data.datasets[element._datasetIndex];
4838 var index = element._index;
4839 var custom = element.custom || {};
4840 var valueOrDefault = helpers.valueAtIndexOrDefault;
4841 var model = element._model;
4842
4843 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
4844 model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
4845 model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
4846 },
4847
4848 setHoverStyle: function(element) {
4849 var dataset = this.chart.data.datasets[element._datasetIndex];
4850 var index = element._index;
4851 var custom = element.custom || {};
4852 var valueOrDefault = helpers.valueAtIndexOrDefault;
4853 var getHoverColor = helpers.getHoverColor;
4854 var model = element._model;
4855
4856 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
4857 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
4858 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
4859 },
4860
4861 /**
4862 * @private
4863 */
4864 resyncElements: function() {
4865 var me = this;
4866 var meta = me.getMeta();
4867 var data = me.getDataset().data;
4868 var numMeta = meta.data.length;
4869 var numData = data.length;
4870
4871 if (numData < numMeta) {
4872 meta.data.splice(numData, numMeta - numData);
4873 } else if (numData > numMeta) {
4874 me.insertElements(numMeta, numData - numMeta);
4875 }
4876 },
4877
4878 /**
4879 * @private
4880 */
4881 insertElements: function(start, count) {
4882 for (var i = 0; i < count; ++i) {
4883 this.addElementAndReset(start + i);
4884 }
4885 },
4886
4887 /**
4888 * @private
4889 */
4890 onDataPush: function() {
4891 this.insertElements(this.getDataset().data.length - 1, arguments.length);
4892 },
4893
4894 /**
4895 * @private
4896 */
4897 onDataPop: function() {
4898 this.getMeta().data.pop();
4899 },
4900
4901 /**
4902 * @private
4903 */
4904 onDataShift: function() {
4905 this.getMeta().data.shift();
4906 },
4907
4908 /**
4909 * @private
4910 */
4911 onDataSplice: function(start, count) {
4912 this.getMeta().data.splice(start, count);
4913 this.insertElements(start, arguments.length - 2);
4914 },
4915
4916 /**
4917 * @private
4918 */
4919 onDataUnshift: function() {
4920 this.insertElements(0, arguments.length);
4921 }
4922 });
4923
4924 Chart.DatasetController.extend = helpers.inherits;
4925};
4926
4927},{"45":45}],25:[function(require,module,exports){
4928'use strict';
4929
4930var helpers = require(45);
4931
4932module.exports = {
4933 /**
4934 * @private
4935 */
4936 _set: function(scope, values) {
4937 return helpers.merge(this[scope] || (this[scope] = {}), values);
4938 }
4939};
4940
4941},{"45":45}],26:[function(require,module,exports){
4942'use strict';
4943
4944var color = require(3);
4945var helpers = require(45);
4946
4947function interpolate(start, view, model, ease) {
4948 var keys = Object.keys(model);
4949 var i, ilen, key, actual, origin, target, type, c0, c1;
4950
4951 for (i = 0, ilen = keys.length; i < ilen; ++i) {
4952 key = keys[i];
4953
4954 target = model[key];
4955
4956 // if a value is added to the model after pivot() has been called, the view
4957 // doesn't contain it, so let's initialize the view to the target value.
4958 if (!view.hasOwnProperty(key)) {
4959 view[key] = target;
4960 }
4961
4962 actual = view[key];
4963
4964 if (actual === target || key[0] === '_') {
4965 continue;
4966 }
4967
4968 if (!start.hasOwnProperty(key)) {
4969 start[key] = actual;
4970 }
4971
4972 origin = start[key];
4973
4974 type = typeof target;
4975
4976 if (type === typeof origin) {
4977 if (type === 'string') {
4978 c0 = color(origin);
4979 if (c0.valid) {
4980 c1 = color(target);
4981 if (c1.valid) {
4982 view[key] = c1.mix(c0, ease).rgbString();
4983 continue;
4984 }
4985 }
4986 } else if (type === 'number' && isFinite(origin) && isFinite(target)) {
4987 view[key] = origin + (target - origin) * ease;
4988 continue;
4989 }
4990 }
4991
4992 view[key] = target;
4993 }
4994}
4995
4996var Element = function(configuration) {
4997 helpers.extend(this, configuration);
4998 this.initialize.apply(this, arguments);
4999};
5000
5001helpers.extend(Element.prototype, {
5002
5003 initialize: function() {
5004 this.hidden = false;
5005 },
5006
5007 pivot: function() {
5008 var me = this;
5009 if (!me._view) {
5010 me._view = helpers.clone(me._model);
5011 }
5012 me._start = {};
5013 return me;
5014 },
5015
5016 transition: function(ease) {
5017 var me = this;
5018 var model = me._model;
5019 var start = me._start;
5020 var view = me._view;
5021
5022 // No animation -> No Transition
5023 if (!model || ease === 1) {
5024 me._view = model;
5025 me._start = null;
5026 return me;
5027 }
5028
5029 if (!view) {
5030 view = me._view = {};
5031 }
5032
5033 if (!start) {
5034 start = me._start = {};
5035 }
5036
5037 interpolate(start, view, model, ease);
5038
5039 return me;
5040 },
5041
5042 tooltipPosition: function() {
5043 return {
5044 x: this._model.x,
5045 y: this._model.y
5046 };
5047 },
5048
5049 hasValue: function() {
5050 return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
5051 }
5052});
5053
5054Element.extend = helpers.inherits;
5055
5056module.exports = Element;
5057
5058},{"3":3,"45":45}],27:[function(require,module,exports){
5059/* global window: false */
5060/* global document: false */
5061'use strict';
5062
5063var color = require(3);
5064var defaults = require(25);
5065var helpers = require(45);
5066
5067module.exports = function(Chart) {
5068
5069 // -- Basic js utility methods
5070
5071 helpers.configMerge = function(/* objects ... */) {
5072 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5073 merger: function(key, target, source, options) {
5074 var tval = target[key] || {};
5075 var sval = source[key];
5076
5077 if (key === 'scales') {
5078 // scale config merging is complex. Add our own function here for that
5079 target[key] = helpers.scaleMerge(tval, sval);
5080 } else if (key === 'scale') {
5081 // used in polar area & radar charts since there is only one scale
5082 target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]);
5083 } else {
5084 helpers._merger(key, target, source, options);
5085 }
5086 }
5087 });
5088 };
5089
5090 helpers.scaleMerge = function(/* objects ... */) {
5091 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5092 merger: function(key, target, source, options) {
5093 if (key === 'xAxes' || key === 'yAxes') {
5094 var slen = source[key].length;
5095 var i, type, scale;
5096
5097 if (!target[key]) {
5098 target[key] = [];
5099 }
5100
5101 for (i = 0; i < slen; ++i) {
5102 scale = source[key][i];
5103 type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear');
5104
5105 if (i >= target[key].length) {
5106 target[key].push({});
5107 }
5108
5109 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
5110 // new/untyped scale or type changed: let's apply the new defaults
5111 // then merge source scale to correctly overwrite the defaults.
5112 helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]);
5113 } else {
5114 // scales type are the same
5115 helpers.merge(target[key][i], scale);
5116 }
5117 }
5118 } else {
5119 helpers._merger(key, target, source, options);
5120 }
5121 }
5122 });
5123 };
5124
5125 helpers.where = function(collection, filterCallback) {
5126 if (helpers.isArray(collection) && Array.prototype.filter) {
5127 return collection.filter(filterCallback);
5128 }
5129 var filtered = [];
5130
5131 helpers.each(collection, function(item) {
5132 if (filterCallback(item)) {
5133 filtered.push(item);
5134 }
5135 });
5136
5137 return filtered;
5138 };
5139 helpers.findIndex = Array.prototype.findIndex ?
5140 function(array, callback, scope) {
5141 return array.findIndex(callback, scope);
5142 } :
5143 function(array, callback, scope) {
5144 scope = scope === undefined ? array : scope;
5145 for (var i = 0, ilen = array.length; i < ilen; ++i) {
5146 if (callback.call(scope, array[i], i, array)) {
5147 return i;
5148 }
5149 }
5150 return -1;
5151 };
5152 helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
5153 // Default to start of the array
5154 if (helpers.isNullOrUndef(startIndex)) {
5155 startIndex = -1;
5156 }
5157 for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
5158 var currentItem = arrayToSearch[i];
5159 if (filterCallback(currentItem)) {
5160 return currentItem;
5161 }
5162 }
5163 };
5164 helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
5165 // Default to end of the array
5166 if (helpers.isNullOrUndef(startIndex)) {
5167 startIndex = arrayToSearch.length;
5168 }
5169 for (var i = startIndex - 1; i >= 0; i--) {
5170 var currentItem = arrayToSearch[i];
5171 if (filterCallback(currentItem)) {
5172 return currentItem;
5173 }
5174 }
5175 };
5176
5177 // -- Math methods
5178 helpers.isNumber = function(n) {
5179 return !isNaN(parseFloat(n)) && isFinite(n);
5180 };
5181 helpers.almostEquals = function(x, y, epsilon) {
5182 return Math.abs(x - y) < epsilon;
5183 };
5184 helpers.almostWhole = function(x, epsilon) {
5185 var rounded = Math.round(x);
5186 return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
5187 };
5188 helpers.max = function(array) {
5189 return array.reduce(function(max, value) {
5190 if (!isNaN(value)) {
5191 return Math.max(max, value);
5192 }
5193 return max;
5194 }, Number.NEGATIVE_INFINITY);
5195 };
5196 helpers.min = function(array) {
5197 return array.reduce(function(min, value) {
5198 if (!isNaN(value)) {
5199 return Math.min(min, value);
5200 }
5201 return min;
5202 }, Number.POSITIVE_INFINITY);
5203 };
5204 helpers.sign = Math.sign ?
5205 function(x) {
5206 return Math.sign(x);
5207 } :
5208 function(x) {
5209 x = +x; // convert to a number
5210 if (x === 0 || isNaN(x)) {
5211 return x;
5212 }
5213 return x > 0 ? 1 : -1;
5214 };
5215 helpers.log10 = Math.log10 ?
5216 function(x) {
5217 return Math.log10(x);
5218 } :
5219 function(x) {
5220 return Math.log(x) / Math.LN10;
5221 };
5222 helpers.toRadians = function(degrees) {
5223 return degrees * (Math.PI / 180);
5224 };
5225 helpers.toDegrees = function(radians) {
5226 return radians * (180 / Math.PI);
5227 };
5228 // Gets the angle from vertical upright to the point about a centre.
5229 helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
5230 var distanceFromXCenter = anglePoint.x - centrePoint.x;
5231 var distanceFromYCenter = anglePoint.y - centrePoint.y;
5232 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
5233
5234 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
5235
5236 if (angle < (-0.5 * Math.PI)) {
5237 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
5238 }
5239
5240 return {
5241 angle: angle,
5242 distance: radialDistanceFromCenter
5243 };
5244 };
5245 helpers.distanceBetweenPoints = function(pt1, pt2) {
5246 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
5247 };
5248 helpers.aliasPixel = function(pixelWidth) {
5249 return (pixelWidth % 2 === 0) ? 0 : 0.5;
5250 };
5251 helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
5252 // Props to Rob Spencer at scaled innovation for his post on splining between points
5253 // http://scaledinnovation.com/analytics/splines/aboutSplines.html
5254
5255 // This function must also respect "skipped" points
5256
5257 var previous = firstPoint.skip ? middlePoint : firstPoint;
5258 var current = middlePoint;
5259 var next = afterPoint.skip ? middlePoint : afterPoint;
5260
5261 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
5262 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
5263
5264 var s01 = d01 / (d01 + d12);
5265 var s12 = d12 / (d01 + d12);
5266
5267 // If all points are the same, s01 & s02 will be inf
5268 s01 = isNaN(s01) ? 0 : s01;
5269 s12 = isNaN(s12) ? 0 : s12;
5270
5271 var fa = t * s01; // scaling factor for triangle Ta
5272 var fb = t * s12;
5273
5274 return {
5275 previous: {
5276 x: current.x - fa * (next.x - previous.x),
5277 y: current.y - fa * (next.y - previous.y)
5278 },
5279 next: {
5280 x: current.x + fb * (next.x - previous.x),
5281 y: current.y + fb * (next.y - previous.y)
5282 }
5283 };
5284 };
5285 helpers.EPSILON = Number.EPSILON || 1e-14;
5286 helpers.splineCurveMonotone = function(points) {
5287 // This function calculates Bézier control points in a similar way than |splineCurve|,
5288 // but preserves monotonicity of the provided data and ensures no local extremums are added
5289 // between the dataset discrete points due to the interpolation.
5290 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
5291
5292 var pointsWithTangents = (points || []).map(function(point) {
5293 return {
5294 model: point._model,
5295 deltaK: 0,
5296 mK: 0
5297 };
5298 });
5299
5300 // Calculate slopes (deltaK) and initialize tangents (mK)
5301 var pointsLen = pointsWithTangents.length;
5302 var i, pointBefore, pointCurrent, pointAfter;
5303 for (i = 0; i < pointsLen; ++i) {
5304 pointCurrent = pointsWithTangents[i];
5305 if (pointCurrent.model.skip) {
5306 continue;
5307 }
5308
5309 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5310 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5311 if (pointAfter && !pointAfter.model.skip) {
5312 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
5313
5314 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
5315 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
5316 }
5317
5318 if (!pointBefore || pointBefore.model.skip) {
5319 pointCurrent.mK = pointCurrent.deltaK;
5320 } else if (!pointAfter || pointAfter.model.skip) {
5321 pointCurrent.mK = pointBefore.deltaK;
5322 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
5323 pointCurrent.mK = 0;
5324 } else {
5325 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
5326 }
5327 }
5328
5329 // Adjust tangents to ensure monotonic properties
5330 var alphaK, betaK, tauK, squaredMagnitude;
5331 for (i = 0; i < pointsLen - 1; ++i) {
5332 pointCurrent = pointsWithTangents[i];
5333 pointAfter = pointsWithTangents[i + 1];
5334 if (pointCurrent.model.skip || pointAfter.model.skip) {
5335 continue;
5336 }
5337
5338 if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
5339 pointCurrent.mK = pointAfter.mK = 0;
5340 continue;
5341 }
5342
5343 alphaK = pointCurrent.mK / pointCurrent.deltaK;
5344 betaK = pointAfter.mK / pointCurrent.deltaK;
5345 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
5346 if (squaredMagnitude <= 9) {
5347 continue;
5348 }
5349
5350 tauK = 3 / Math.sqrt(squaredMagnitude);
5351 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
5352 pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
5353 }
5354
5355 // Compute control points
5356 var deltaX;
5357 for (i = 0; i < pointsLen; ++i) {
5358 pointCurrent = pointsWithTangents[i];
5359 if (pointCurrent.model.skip) {
5360 continue;
5361 }
5362
5363 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5364 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5365 if (pointBefore && !pointBefore.model.skip) {
5366 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
5367 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
5368 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
5369 }
5370 if (pointAfter && !pointAfter.model.skip) {
5371 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
5372 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
5373 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
5374 }
5375 }
5376 };
5377 helpers.nextItem = function(collection, index, loop) {
5378 if (loop) {
5379 return index >= collection.length - 1 ? collection[0] : collection[index + 1];
5380 }
5381 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
5382 };
5383 helpers.previousItem = function(collection, index, loop) {
5384 if (loop) {
5385 return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
5386 }
5387 return index <= 0 ? collection[0] : collection[index - 1];
5388 };
5389 // Implementation of the nice number algorithm used in determining where axis labels will go
5390 helpers.niceNum = function(range, round) {
5391 var exponent = Math.floor(helpers.log10(range));
5392 var fraction = range / Math.pow(10, exponent);
5393 var niceFraction;
5394
5395 if (round) {
5396 if (fraction < 1.5) {
5397 niceFraction = 1;
5398 } else if (fraction < 3) {
5399 niceFraction = 2;
5400 } else if (fraction < 7) {
5401 niceFraction = 5;
5402 } else {
5403 niceFraction = 10;
5404 }
5405 } else if (fraction <= 1.0) {
5406 niceFraction = 1;
5407 } else if (fraction <= 2) {
5408 niceFraction = 2;
5409 } else if (fraction <= 5) {
5410 niceFraction = 5;
5411 } else {
5412 niceFraction = 10;
5413 }
5414
5415 return niceFraction * Math.pow(10, exponent);
5416 };
5417 // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
5418 helpers.requestAnimFrame = (function() {
5419 if (typeof window === 'undefined') {
5420 return function(callback) {
5421 callback();
5422 };
5423 }
5424 return window.requestAnimationFrame ||
5425 window.webkitRequestAnimationFrame ||
5426 window.mozRequestAnimationFrame ||
5427 window.oRequestAnimationFrame ||
5428 window.msRequestAnimationFrame ||
5429 function(callback) {
5430 return window.setTimeout(callback, 1000 / 60);
5431 };
5432 }());
5433 // -- DOM methods
5434 helpers.getRelativePosition = function(evt, chart) {
5435 var mouseX, mouseY;
5436 var e = evt.originalEvent || evt;
5437 var canvas = evt.currentTarget || evt.srcElement;
5438 var boundingRect = canvas.getBoundingClientRect();
5439
5440 var touches = e.touches;
5441 if (touches && touches.length > 0) {
5442 mouseX = touches[0].clientX;
5443 mouseY = touches[0].clientY;
5444
5445 } else {
5446 mouseX = e.clientX;
5447 mouseY = e.clientY;
5448 }
5449
5450 // Scale mouse coordinates into canvas coordinates
5451 // by following the pattern laid out by 'jerryj' in the comments of
5452 // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
5453 var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
5454 var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
5455 var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
5456 var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
5457 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
5458 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
5459
5460 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
5461 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
5462 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
5463 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
5464
5465 return {
5466 x: mouseX,
5467 y: mouseY
5468 };
5469
5470 };
5471
5472 // Private helper function to convert max-width/max-height values that may be percentages into a number
5473 function parseMaxStyle(styleValue, node, parentProperty) {
5474 var valueInPixels;
5475 if (typeof styleValue === 'string') {
5476 valueInPixels = parseInt(styleValue, 10);
5477
5478 if (styleValue.indexOf('%') !== -1) {
5479 // percentage * size in dimension
5480 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
5481 }
5482 } else {
5483 valueInPixels = styleValue;
5484 }
5485
5486 return valueInPixels;
5487 }
5488
5489 /**
5490 * Returns if the given value contains an effective constraint.
5491 * @private
5492 */
5493 function isConstrainedValue(value) {
5494 return value !== undefined && value !== null && value !== 'none';
5495 }
5496
5497 // Private helper to get a constraint dimension
5498 // @param domNode : the node to check the constraint on
5499 // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
5500 // @param percentageProperty : property of parent to use when calculating width as a percentage
5501 // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
5502 function getConstraintDimension(domNode, maxStyle, percentageProperty) {
5503 var view = document.defaultView;
5504 var parentNode = domNode.parentNode;
5505 var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
5506 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
5507 var hasCNode = isConstrainedValue(constrainedNode);
5508 var hasCContainer = isConstrainedValue(constrainedContainer);
5509 var infinity = Number.POSITIVE_INFINITY;
5510
5511 if (hasCNode || hasCContainer) {
5512 return Math.min(
5513 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
5514 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
5515 }
5516
5517 return 'none';
5518 }
5519 // returns Number or undefined if no constraint
5520 helpers.getConstraintWidth = function(domNode) {
5521 return getConstraintDimension(domNode, 'max-width', 'clientWidth');
5522 };
5523 // returns Number or undefined if no constraint
5524 helpers.getConstraintHeight = function(domNode) {
5525 return getConstraintDimension(domNode, 'max-height', 'clientHeight');
5526 };
5527 helpers.getMaximumWidth = function(domNode) {
5528 var container = domNode.parentNode;
5529 if (!container) {
5530 return domNode.clientWidth;
5531 }
5532
5533 var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
5534 var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
5535 var w = container.clientWidth - paddingLeft - paddingRight;
5536 var cw = helpers.getConstraintWidth(domNode);
5537 return isNaN(cw) ? w : Math.min(w, cw);
5538 };
5539 helpers.getMaximumHeight = function(domNode) {
5540 var container = domNode.parentNode;
5541 if (!container) {
5542 return domNode.clientHeight;
5543 }
5544
5545 var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
5546 var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
5547 var h = container.clientHeight - paddingTop - paddingBottom;
5548 var ch = helpers.getConstraintHeight(domNode);
5549 return isNaN(ch) ? h : Math.min(h, ch);
5550 };
5551 helpers.getStyle = function(el, property) {
5552 return el.currentStyle ?
5553 el.currentStyle[property] :
5554 document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
5555 };
5556 helpers.retinaScale = function(chart, forceRatio) {
5557 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1;
5558 if (pixelRatio === 1) {
5559 return;
5560 }
5561
5562 var canvas = chart.canvas;
5563 var height = chart.height;
5564 var width = chart.width;
5565
5566 canvas.height = height * pixelRatio;
5567 canvas.width = width * pixelRatio;
5568 chart.ctx.scale(pixelRatio, pixelRatio);
5569
5570 // If no style has been set on the canvas, the render size is used as display size,
5571 // making the chart visually bigger, so let's enforce it to the "correct" values.
5572 // See https://github.com/chartjs/Chart.js/issues/3575
5573 canvas.style.height = height + 'px';
5574 canvas.style.width = width + 'px';
5575 };
5576 // -- Canvas methods
5577 helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
5578 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
5579 };
5580 helpers.longestText = function(ctx, font, arrayOfThings, cache) {
5581 cache = cache || {};
5582 var data = cache.data = cache.data || {};
5583 var gc = cache.garbageCollect = cache.garbageCollect || [];
5584
5585 if (cache.font !== font) {
5586 data = cache.data = {};
5587 gc = cache.garbageCollect = [];
5588 cache.font = font;
5589 }
5590
5591 ctx.font = font;
5592 var longest = 0;
5593 helpers.each(arrayOfThings, function(thing) {
5594 // Undefined strings and arrays should not be measured
5595 if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
5596 longest = helpers.measureText(ctx, data, gc, longest, thing);
5597 } else if (helpers.isArray(thing)) {
5598 // if it is an array lets measure each element
5599 // to do maybe simplify this function a bit so we can do this more recursively?
5600 helpers.each(thing, function(nestedThing) {
5601 // Undefined strings and arrays should not be measured
5602 if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
5603 longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
5604 }
5605 });
5606 }
5607 });
5608
5609 var gcLen = gc.length / 2;
5610 if (gcLen > arrayOfThings.length) {
5611 for (var i = 0; i < gcLen; i++) {
5612 delete data[gc[i]];
5613 }
5614 gc.splice(0, gcLen);
5615 }
5616 return longest;
5617 };
5618 helpers.measureText = function(ctx, data, gc, longest, string) {
5619 var textWidth = data[string];
5620 if (!textWidth) {
5621 textWidth = data[string] = ctx.measureText(string).width;
5622 gc.push(string);
5623 }
5624 if (textWidth > longest) {
5625 longest = textWidth;
5626 }
5627 return longest;
5628 };
5629 helpers.numberOfLabelLines = function(arrayOfThings) {
5630 var numberOfLines = 1;
5631 helpers.each(arrayOfThings, function(thing) {
5632 if (helpers.isArray(thing)) {
5633 if (thing.length > numberOfLines) {
5634 numberOfLines = thing.length;
5635 }
5636 }
5637 });
5638 return numberOfLines;
5639 };
5640
5641 helpers.color = !color ?
5642 function(value) {
5643 console.error('Color.js not found!');
5644 return value;
5645 } :
5646 function(value) {
5647 /* global CanvasGradient */
5648 if (value instanceof CanvasGradient) {
5649 value = defaults.global.defaultColor;
5650 }
5651
5652 return color(value);
5653 };
5654
5655 helpers.getHoverColor = function(colorValue) {
5656 /* global CanvasPattern */
5657 return (colorValue instanceof CanvasPattern) ?
5658 colorValue :
5659 helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
5660 };
5661};
5662
5663},{"25":25,"3":3,"45":45}],28:[function(require,module,exports){
5664'use strict';
5665
5666var helpers = require(45);
5667
5668/**
5669 * Helper function to get relative position for an event
5670 * @param {Event|IEvent} event - The event to get the position for
5671 * @param {Chart} chart - The chart
5672 * @returns {Point} the event position
5673 */
5674function getRelativePosition(e, chart) {
5675 if (e.native) {
5676 return {
5677 x: e.x,
5678 y: e.y
5679 };
5680 }
5681
5682 return helpers.getRelativePosition(e, chart);
5683}
5684
5685/**
5686 * Helper function to traverse all of the visible elements in the chart
5687 * @param chart {chart} the chart
5688 * @param handler {Function} the callback to execute for each visible item
5689 */
5690function parseVisibleItems(chart, handler) {
5691 var datasets = chart.data.datasets;
5692 var meta, i, j, ilen, jlen;
5693
5694 for (i = 0, ilen = datasets.length; i < ilen; ++i) {
5695 if (!chart.isDatasetVisible(i)) {
5696 continue;
5697 }
5698
5699 meta = chart.getDatasetMeta(i);
5700 for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
5701 var element = meta.data[j];
5702 if (!element._view.skip) {
5703 handler(element);
5704 }
5705 }
5706 }
5707}
5708
5709/**
5710 * Helper function to get the items that intersect the event position
5711 * @param items {ChartElement[]} elements to filter
5712 * @param position {Point} the point to be nearest to
5713 * @return {ChartElement[]} the nearest items
5714 */
5715function getIntersectItems(chart, position) {
5716 var elements = [];
5717
5718 parseVisibleItems(chart, function(element) {
5719 if (element.inRange(position.x, position.y)) {
5720 elements.push(element);
5721 }
5722 });
5723
5724 return elements;
5725}
5726
5727/**
5728 * Helper function to get the items nearest to the event position considering all visible items in teh chart
5729 * @param chart {Chart} the chart to look at elements from
5730 * @param position {Point} the point to be nearest to
5731 * @param intersect {Boolean} if true, only consider items that intersect the position
5732 * @param distanceMetric {Function} function to provide the distance between points
5733 * @return {ChartElement[]} the nearest items
5734 */
5735function getNearestItems(chart, position, intersect, distanceMetric) {
5736 var minDistance = Number.POSITIVE_INFINITY;
5737 var nearestItems = [];
5738
5739 parseVisibleItems(chart, function(element) {
5740 if (intersect && !element.inRange(position.x, position.y)) {
5741 return;
5742 }
5743
5744 var center = element.getCenterPoint();
5745 var distance = distanceMetric(position, center);
5746
5747 if (distance < minDistance) {
5748 nearestItems = [element];
5749 minDistance = distance;
5750 } else if (distance === minDistance) {
5751 // Can have multiple items at the same distance in which case we sort by size
5752 nearestItems.push(element);
5753 }
5754 });
5755
5756 return nearestItems;
5757}
5758
5759/**
5760 * Get a distance metric function for two points based on the
5761 * axis mode setting
5762 * @param {String} axis the axis mode. x|y|xy
5763 */
5764function getDistanceMetricForAxis(axis) {
5765 var useX = axis.indexOf('x') !== -1;
5766 var useY = axis.indexOf('y') !== -1;
5767
5768 return function(pt1, pt2) {
5769 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
5770 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
5771 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
5772 };
5773}
5774
5775function indexMode(chart, e, options) {
5776 var position = getRelativePosition(e, chart);
5777 // Default axis for index mode is 'x' to match old behaviour
5778 options.axis = options.axis || 'x';
5779 var distanceMetric = getDistanceMetricForAxis(options.axis);
5780 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5781 var elements = [];
5782
5783 if (!items.length) {
5784 return [];
5785 }
5786
5787 chart.data.datasets.forEach(function(dataset, datasetIndex) {
5788 if (chart.isDatasetVisible(datasetIndex)) {
5789 var meta = chart.getDatasetMeta(datasetIndex);
5790 var element = meta.data[items[0]._index];
5791
5792 // don't count items that are skipped (null data)
5793 if (element && !element._view.skip) {
5794 elements.push(element);
5795 }
5796 }
5797 });
5798
5799 return elements;
5800}
5801
5802/**
5803 * @interface IInteractionOptions
5804 */
5805/**
5806 * If true, only consider items that intersect the point
5807 * @name IInterfaceOptions#boolean
5808 * @type Boolean
5809 */
5810
5811/**
5812 * Contains interaction related functions
5813 * @namespace Chart.Interaction
5814 */
5815module.exports = {
5816 // Helper function for different modes
5817 modes: {
5818 single: function(chart, e) {
5819 var position = getRelativePosition(e, chart);
5820 var elements = [];
5821
5822 parseVisibleItems(chart, function(element) {
5823 if (element.inRange(position.x, position.y)) {
5824 elements.push(element);
5825 return elements;
5826 }
5827 });
5828
5829 return elements.slice(0, 1);
5830 },
5831
5832 /**
5833 * @function Chart.Interaction.modes.label
5834 * @deprecated since version 2.4.0
5835 * @todo remove at version 3
5836 * @private
5837 */
5838 label: indexMode,
5839
5840 /**
5841 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
5842 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
5843 * @function Chart.Interaction.modes.index
5844 * @since v2.4.0
5845 * @param chart {chart} the chart we are returning items from
5846 * @param e {Event} the event we are find things at
5847 * @param options {IInteractionOptions} options to use during interaction
5848 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5849 */
5850 index: indexMode,
5851
5852 /**
5853 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
5854 * If the options.intersect is false, we find the nearest item and return the items in that dataset
5855 * @function Chart.Interaction.modes.dataset
5856 * @param chart {chart} the chart we are returning items from
5857 * @param e {Event} the event we are find things at
5858 * @param options {IInteractionOptions} options to use during interaction
5859 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5860 */
5861 dataset: function(chart, e, options) {
5862 var position = getRelativePosition(e, chart);
5863 options.axis = options.axis || 'xy';
5864 var distanceMetric = getDistanceMetricForAxis(options.axis);
5865 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5866
5867 if (items.length > 0) {
5868 items = chart.getDatasetMeta(items[0]._datasetIndex).data;
5869 }
5870
5871 return items;
5872 },
5873
5874 /**
5875 * @function Chart.Interaction.modes.x-axis
5876 * @deprecated since version 2.4.0. Use index mode and intersect == true
5877 * @todo remove at version 3
5878 * @private
5879 */
5880 'x-axis': function(chart, e) {
5881 return indexMode(chart, e, {intersect: false});
5882 },
5883
5884 /**
5885 * Point mode returns all elements that hit test based on the event position
5886 * of the event
5887 * @function Chart.Interaction.modes.intersect
5888 * @param chart {chart} the chart we are returning items from
5889 * @param e {Event} the event we are find things at
5890 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5891 */
5892 point: function(chart, e) {
5893 var position = getRelativePosition(e, chart);
5894 return getIntersectItems(chart, position);
5895 },
5896
5897 /**
5898 * nearest mode returns the element closest to the point
5899 * @function Chart.Interaction.modes.intersect
5900 * @param chart {chart} the chart we are returning items from
5901 * @param e {Event} the event we are find things at
5902 * @param options {IInteractionOptions} options to use
5903 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5904 */
5905 nearest: function(chart, e, options) {
5906 var position = getRelativePosition(e, chart);
5907 options.axis = options.axis || 'xy';
5908 var distanceMetric = getDistanceMetricForAxis(options.axis);
5909 var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric);
5910
5911 // We have multiple items at the same distance from the event. Now sort by smallest
5912 if (nearestItems.length > 1) {
5913 nearestItems.sort(function(a, b) {
5914 var sizeA = a.getArea();
5915 var sizeB = b.getArea();
5916 var ret = sizeA - sizeB;
5917
5918 if (ret === 0) {
5919 // if equal sort by dataset index
5920 ret = a._datasetIndex - b._datasetIndex;
5921 }
5922
5923 return ret;
5924 });
5925 }
5926
5927 // Return only 1 item
5928 return nearestItems.slice(0, 1);
5929 },
5930
5931 /**
5932 * x mode returns the elements that hit-test at the current x coordinate
5933 * @function Chart.Interaction.modes.x
5934 * @param chart {chart} the chart we are returning items from
5935 * @param e {Event} the event we are find things at
5936 * @param options {IInteractionOptions} options to use
5937 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5938 */
5939 x: function(chart, e, options) {
5940 var position = getRelativePosition(e, chart);
5941 var items = [];
5942 var intersectsItem = false;
5943
5944 parseVisibleItems(chart, function(element) {
5945 if (element.inXRange(position.x)) {
5946 items.push(element);
5947 }
5948
5949 if (element.inRange(position.x, position.y)) {
5950 intersectsItem = true;
5951 }
5952 });
5953
5954 // If we want to trigger on an intersect and we don't have any items
5955 // that intersect the position, return nothing
5956 if (options.intersect && !intersectsItem) {
5957 items = [];
5958 }
5959 return items;
5960 },
5961
5962 /**
5963 * y mode returns the elements that hit-test at the current y coordinate
5964 * @function Chart.Interaction.modes.y
5965 * @param chart {chart} the chart we are returning items from
5966 * @param e {Event} the event we are find things at
5967 * @param options {IInteractionOptions} options to use
5968 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5969 */
5970 y: function(chart, e, options) {
5971 var position = getRelativePosition(e, chart);
5972 var items = [];
5973 var intersectsItem = false;
5974
5975 parseVisibleItems(chart, function(element) {
5976 if (element.inYRange(position.y)) {
5977 items.push(element);
5978 }
5979
5980 if (element.inRange(position.x, position.y)) {
5981 intersectsItem = true;
5982 }
5983 });
5984
5985 // If we want to trigger on an intersect and we don't have any items
5986 // that intersect the position, return nothing
5987 if (options.intersect && !intersectsItem) {
5988 items = [];
5989 }
5990 return items;
5991 }
5992 }
5993};
5994
5995},{"45":45}],29:[function(require,module,exports){
5996'use strict';
5997
5998var defaults = require(25);
5999
6000defaults._set('global', {
6001 responsive: true,
6002 responsiveAnimationDuration: 0,
6003 maintainAspectRatio: true,
6004 events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
6005 hover: {
6006 onHover: null,
6007 mode: 'nearest',
6008 intersect: true,
6009 animationDuration: 400
6010 },
6011 onClick: null,
6012 defaultColor: 'rgba(0,0,0,0.1)',
6013 defaultFontColor: '#666',
6014 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
6015 defaultFontSize: 12,
6016 defaultFontStyle: 'normal',
6017 showLines: true,
6018
6019 // Element defaults defined in element extensions
6020 elements: {},
6021
6022 // Layout options such as padding
6023 layout: {
6024 padding: {
6025 top: 0,
6026 right: 0,
6027 bottom: 0,
6028 left: 0
6029 }
6030 }
6031});
6032
6033module.exports = function() {
6034
6035 // Occupy the global variable of Chart, and create a simple base class
6036 var Chart = function(item, config) {
6037 this.construct(item, config);
6038 return this;
6039 };
6040
6041 Chart.Chart = Chart;
6042
6043 return Chart;
6044};
6045
6046},{"25":25}],30:[function(require,module,exports){
6047'use strict';
6048
6049var helpers = require(45);
6050
6051module.exports = function(Chart) {
6052
6053 function filterByPosition(array, position) {
6054 return helpers.where(array, function(v) {
6055 return v.position === position;
6056 });
6057 }
6058
6059 function sortByWeight(array, reverse) {
6060 array.forEach(function(v, i) {
6061 v._tmpIndex_ = i;
6062 return v;
6063 });
6064 array.sort(function(a, b) {
6065 var v0 = reverse ? b : a;
6066 var v1 = reverse ? a : b;
6067 return v0.weight === v1.weight ?
6068 v0._tmpIndex_ - v1._tmpIndex_ :
6069 v0.weight - v1.weight;
6070 });
6071 array.forEach(function(v) {
6072 delete v._tmpIndex_;
6073 });
6074 }
6075
6076 /**
6077 * @interface ILayoutItem
6078 * @prop {String} position - The position of the item in the chart layout. Possible values are
6079 * 'left', 'top', 'right', 'bottom', and 'chartArea'
6080 * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
6081 * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
6082 * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
6083 * @prop {Function} update - Takes two parameters: width and height. Returns size of item
6084 * @prop {Function} getPadding - Returns an object with padding on the edges
6085 * @prop {Number} width - Width of item. Must be valid after update()
6086 * @prop {Number} height - Height of item. Must be valid after update()
6087 * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
6088 * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
6089 * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
6090 * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
6091 */
6092
6093 // The layout service is very self explanatory. It's responsible for the layout within a chart.
6094 // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
6095 // It is this service's responsibility of carrying out that layout.
6096 Chart.layoutService = {
6097 defaults: {},
6098
6099 /**
6100 * Register a box to a chart.
6101 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
6102 * @param {Chart} chart - the chart to use
6103 * @param {ILayoutItem} item - the item to add to be layed out
6104 */
6105 addBox: function(chart, item) {
6106 if (!chart.boxes) {
6107 chart.boxes = [];
6108 }
6109
6110 // initialize item with default values
6111 item.fullWidth = item.fullWidth || false;
6112 item.position = item.position || 'top';
6113 item.weight = item.weight || 0;
6114
6115 chart.boxes.push(item);
6116 },
6117
6118 /**
6119 * Remove a layoutItem from a chart
6120 * @param {Chart} chart - the chart to remove the box from
6121 * @param {Object} layoutItem - the item to remove from the layout
6122 */
6123 removeBox: function(chart, layoutItem) {
6124 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
6125 if (index !== -1) {
6126 chart.boxes.splice(index, 1);
6127 }
6128 },
6129
6130 /**
6131 * Sets (or updates) options on the given `item`.
6132 * @param {Chart} chart - the chart in which the item lives (or will be added to)
6133 * @param {Object} item - the item to configure with the given options
6134 * @param {Object} options - the new item options.
6135 */
6136 configure: function(chart, item, options) {
6137 var props = ['fullWidth', 'position', 'weight'];
6138 var ilen = props.length;
6139 var i = 0;
6140 var prop;
6141
6142 for (; i < ilen; ++i) {
6143 prop = props[i];
6144 if (options.hasOwnProperty(prop)) {
6145 item[prop] = options[prop];
6146 }
6147 }
6148 },
6149
6150 /**
6151 * Fits boxes of the given chart into the given size by having each box measure itself
6152 * then running a fitting algorithm
6153 * @param {Chart} chart - the chart
6154 * @param {Number} width - the width to fit into
6155 * @param {Number} height - the height to fit into
6156 */
6157 update: function(chart, width, height) {
6158 if (!chart) {
6159 return;
6160 }
6161
6162 var layoutOptions = chart.options.layout || {};
6163 var padding = helpers.options.toPadding(layoutOptions.padding);
6164 var leftPadding = padding.left;
6165 var rightPadding = padding.right;
6166 var topPadding = padding.top;
6167 var bottomPadding = padding.bottom;
6168
6169 var leftBoxes = filterByPosition(chart.boxes, 'left');
6170 var rightBoxes = filterByPosition(chart.boxes, 'right');
6171 var topBoxes = filterByPosition(chart.boxes, 'top');
6172 var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
6173 var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
6174
6175 // Sort boxes by weight. A higher weight is further away from the chart area
6176 sortByWeight(leftBoxes, true);
6177 sortByWeight(rightBoxes, false);
6178 sortByWeight(topBoxes, true);
6179 sortByWeight(bottomBoxes, false);
6180
6181 // Essentially we now have any number of boxes on each of the 4 sides.
6182 // Our canvas looks like the following.
6183 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
6184 // B1 is the bottom axis
6185 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
6186 // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
6187 // an error will be thrown.
6188 //
6189 // |----------------------------------------------------|
6190 // | T1 (Full Width) |
6191 // |----------------------------------------------------|
6192 // | | | T2 | |
6193 // | |----|-------------------------------------|----|
6194 // | | | C1 | | C2 | |
6195 // | | |----| |----| |
6196 // | | | | |
6197 // | L1 | L2 | ChartArea (C0) | R1 |
6198 // | | | | |
6199 // | | |----| |----| |
6200 // | | | C3 | | C4 | |
6201 // | |----|-------------------------------------|----|
6202 // | | | B1 | |
6203 // |----------------------------------------------------|
6204 // | B2 (Full Width) |
6205 // |----------------------------------------------------|
6206 //
6207 // What we do to find the best sizing, we do the following
6208 // 1. Determine the minimum size of the chart area.
6209 // 2. Split the remaining width equally between each vertical axis
6210 // 3. Split the remaining height equally between each horizontal axis
6211 // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
6212 // 5. Adjust the sizes of each axis based on it's minimum reported size.
6213 // 6. Refit each axis
6214 // 7. Position each axis in the final location
6215 // 8. Tell the chart the final location of the chart area
6216 // 9. Tell any axes that overlay the chart area the positions of the chart area
6217
6218 // Step 1
6219 var chartWidth = width - leftPadding - rightPadding;
6220 var chartHeight = height - topPadding - bottomPadding;
6221 var chartAreaWidth = chartWidth / 2; // min 50%
6222 var chartAreaHeight = chartHeight / 2; // min 50%
6223
6224 // Step 2
6225 var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
6226
6227 // Step 3
6228 var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
6229
6230 // Step 4
6231 var maxChartAreaWidth = chartWidth;
6232 var maxChartAreaHeight = chartHeight;
6233 var minBoxSizes = [];
6234
6235 function getMinimumBoxSize(box) {
6236 var minSize;
6237 var isHorizontal = box.isHorizontal();
6238
6239 if (isHorizontal) {
6240 minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
6241 maxChartAreaHeight -= minSize.height;
6242 } else {
6243 minSize = box.update(verticalBoxWidth, chartAreaHeight);
6244 maxChartAreaWidth -= minSize.width;
6245 }
6246
6247 minBoxSizes.push({
6248 horizontal: isHorizontal,
6249 minSize: minSize,
6250 box: box,
6251 });
6252 }
6253
6254 helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
6255
6256 // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
6257 var maxHorizontalLeftPadding = 0;
6258 var maxHorizontalRightPadding = 0;
6259 var maxVerticalTopPadding = 0;
6260 var maxVerticalBottomPadding = 0;
6261
6262 helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
6263 if (horizontalBox.getPadding) {
6264 var boxPadding = horizontalBox.getPadding();
6265 maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
6266 maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
6267 }
6268 });
6269
6270 helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
6271 if (verticalBox.getPadding) {
6272 var boxPadding = verticalBox.getPadding();
6273 maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
6274 maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
6275 }
6276 });
6277
6278 // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
6279 // be if the axes are drawn at their minimum sizes.
6280 // Steps 5 & 6
6281 var totalLeftBoxesWidth = leftPadding;
6282 var totalRightBoxesWidth = rightPadding;
6283 var totalTopBoxesHeight = topPadding;
6284 var totalBottomBoxesHeight = bottomPadding;
6285
6286 // Function to fit a box
6287 function fitBox(box) {
6288 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
6289 return minBox.box === box;
6290 });
6291
6292 if (minBoxSize) {
6293 if (box.isHorizontal()) {
6294 var scaleMargin = {
6295 left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
6296 right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
6297 top: 0,
6298 bottom: 0
6299 };
6300
6301 // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
6302 // on the margin. Sometimes they need to increase in size slightly
6303 box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
6304 } else {
6305 box.update(minBoxSize.minSize.width, maxChartAreaHeight);
6306 }
6307 }
6308 }
6309
6310 // Update, and calculate the left and right margins for the horizontal boxes
6311 helpers.each(leftBoxes.concat(rightBoxes), fitBox);
6312
6313 helpers.each(leftBoxes, function(box) {
6314 totalLeftBoxesWidth += box.width;
6315 });
6316
6317 helpers.each(rightBoxes, function(box) {
6318 totalRightBoxesWidth += box.width;
6319 });
6320
6321 // Set the Left and Right margins for the horizontal boxes
6322 helpers.each(topBoxes.concat(bottomBoxes), fitBox);
6323
6324 // Figure out how much margin is on the top and bottom of the vertical boxes
6325 helpers.each(topBoxes, function(box) {
6326 totalTopBoxesHeight += box.height;
6327 });
6328
6329 helpers.each(bottomBoxes, function(box) {
6330 totalBottomBoxesHeight += box.height;
6331 });
6332
6333 function finalFitVerticalBox(box) {
6334 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
6335 return minSize.box === box;
6336 });
6337
6338 var scaleMargin = {
6339 left: 0,
6340 right: 0,
6341 top: totalTopBoxesHeight,
6342 bottom: totalBottomBoxesHeight
6343 };
6344
6345 if (minBoxSize) {
6346 box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
6347 }
6348 }
6349
6350 // Let the left layout know the final margin
6351 helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
6352
6353 // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
6354 totalLeftBoxesWidth = leftPadding;
6355 totalRightBoxesWidth = rightPadding;
6356 totalTopBoxesHeight = topPadding;
6357 totalBottomBoxesHeight = bottomPadding;
6358
6359 helpers.each(leftBoxes, function(box) {
6360 totalLeftBoxesWidth += box.width;
6361 });
6362
6363 helpers.each(rightBoxes, function(box) {
6364 totalRightBoxesWidth += box.width;
6365 });
6366
6367 helpers.each(topBoxes, function(box) {
6368 totalTopBoxesHeight += box.height;
6369 });
6370 helpers.each(bottomBoxes, function(box) {
6371 totalBottomBoxesHeight += box.height;
6372 });
6373
6374 // We may be adding some padding to account for rotated x axis labels
6375 var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
6376 totalLeftBoxesWidth += leftPaddingAddition;
6377 totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
6378
6379 var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
6380 totalTopBoxesHeight += topPaddingAddition;
6381 totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
6382
6383 // Figure out if our chart area changed. This would occur if the dataset layout label rotation
6384 // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
6385 // without calling `fit` again
6386 var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
6387 var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
6388
6389 if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
6390 helpers.each(leftBoxes, function(box) {
6391 box.height = newMaxChartAreaHeight;
6392 });
6393
6394 helpers.each(rightBoxes, function(box) {
6395 box.height = newMaxChartAreaHeight;
6396 });
6397
6398 helpers.each(topBoxes, function(box) {
6399 if (!box.fullWidth) {
6400 box.width = newMaxChartAreaWidth;
6401 }
6402 });
6403
6404 helpers.each(bottomBoxes, function(box) {
6405 if (!box.fullWidth) {
6406 box.width = newMaxChartAreaWidth;
6407 }
6408 });
6409
6410 maxChartAreaHeight = newMaxChartAreaHeight;
6411 maxChartAreaWidth = newMaxChartAreaWidth;
6412 }
6413
6414 // Step 7 - Position the boxes
6415 var left = leftPadding + leftPaddingAddition;
6416 var top = topPadding + topPaddingAddition;
6417
6418 function placeBox(box) {
6419 if (box.isHorizontal()) {
6420 box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
6421 box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
6422 box.top = top;
6423 box.bottom = top + box.height;
6424
6425 // Move to next point
6426 top = box.bottom;
6427
6428 } else {
6429
6430 box.left = left;
6431 box.right = left + box.width;
6432 box.top = totalTopBoxesHeight;
6433 box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
6434
6435 // Move to next point
6436 left = box.right;
6437 }
6438 }
6439
6440 helpers.each(leftBoxes.concat(topBoxes), placeBox);
6441
6442 // Account for chart width and height
6443 left += maxChartAreaWidth;
6444 top += maxChartAreaHeight;
6445
6446 helpers.each(rightBoxes, placeBox);
6447 helpers.each(bottomBoxes, placeBox);
6448
6449 // Step 8
6450 chart.chartArea = {
6451 left: totalLeftBoxesWidth,
6452 top: totalTopBoxesHeight,
6453 right: totalLeftBoxesWidth + maxChartAreaWidth,
6454 bottom: totalTopBoxesHeight + maxChartAreaHeight
6455 };
6456
6457 // Step 9
6458 helpers.each(chartAreaBoxes, function(box) {
6459 box.left = chart.chartArea.left;
6460 box.top = chart.chartArea.top;
6461 box.right = chart.chartArea.right;
6462 box.bottom = chart.chartArea.bottom;
6463
6464 box.update(maxChartAreaWidth, maxChartAreaHeight);
6465 });
6466 }
6467 };
6468};
6469
6470},{"45":45}],31:[function(require,module,exports){
6471'use strict';
6472
6473var defaults = require(25);
6474var Element = require(26);
6475var helpers = require(45);
6476
6477defaults._set('global', {
6478 plugins: {}
6479});
6480
6481module.exports = function(Chart) {
6482
6483 /**
6484 * The plugin service singleton
6485 * @namespace Chart.plugins
6486 * @since 2.1.0
6487 */
6488 Chart.plugins = {
6489 /**
6490 * Globally registered plugins.
6491 * @private
6492 */
6493 _plugins: [],
6494
6495 /**
6496 * This identifier is used to invalidate the descriptors cache attached to each chart
6497 * when a global plugin is registered or unregistered. In this case, the cache ID is
6498 * incremented and descriptors are regenerated during following API calls.
6499 * @private
6500 */
6501 _cacheId: 0,
6502
6503 /**
6504 * Registers the given plugin(s) if not already registered.
6505 * @param {Array|Object} plugins plugin instance(s).
6506 */
6507 register: function(plugins) {
6508 var p = this._plugins;
6509 ([]).concat(plugins).forEach(function(plugin) {
6510 if (p.indexOf(plugin) === -1) {
6511 p.push(plugin);
6512 }
6513 });
6514
6515 this._cacheId++;
6516 },
6517
6518 /**
6519 * Unregisters the given plugin(s) only if registered.
6520 * @param {Array|Object} plugins plugin instance(s).
6521 */
6522 unregister: function(plugins) {
6523 var p = this._plugins;
6524 ([]).concat(plugins).forEach(function(plugin) {
6525 var idx = p.indexOf(plugin);
6526 if (idx !== -1) {
6527 p.splice(idx, 1);
6528 }
6529 });
6530
6531 this._cacheId++;
6532 },
6533
6534 /**
6535 * Remove all registered plugins.
6536 * @since 2.1.5
6537 */
6538 clear: function() {
6539 this._plugins = [];
6540 this._cacheId++;
6541 },
6542
6543 /**
6544 * Returns the number of registered plugins?
6545 * @returns {Number}
6546 * @since 2.1.5
6547 */
6548 count: function() {
6549 return this._plugins.length;
6550 },
6551
6552 /**
6553 * Returns all registered plugin instances.
6554 * @returns {Array} array of plugin objects.
6555 * @since 2.1.5
6556 */
6557 getAll: function() {
6558 return this._plugins;
6559 },
6560
6561 /**
6562 * Calls enabled plugins for `chart` on the specified hook and with the given args.
6563 * This method immediately returns as soon as a plugin explicitly returns false. The
6564 * returned value can be used, for instance, to interrupt the current action.
6565 * @param {Object} chart - The chart instance for which plugins should be called.
6566 * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
6567 * @param {Array} [args] - Extra arguments to apply to the hook call.
6568 * @returns {Boolean} false if any of the plugins return false, else returns true.
6569 */
6570 notify: function(chart, hook, args) {
6571 var descriptors = this.descriptors(chart);
6572 var ilen = descriptors.length;
6573 var i, descriptor, plugin, params, method;
6574
6575 for (i = 0; i < ilen; ++i) {
6576 descriptor = descriptors[i];
6577 plugin = descriptor.plugin;
6578 method = plugin[hook];
6579 if (typeof method === 'function') {
6580 params = [chart].concat(args || []);
6581 params.push(descriptor.options);
6582 if (method.apply(plugin, params) === false) {
6583 return false;
6584 }
6585 }
6586 }
6587
6588 return true;
6589 },
6590
6591 /**
6592 * Returns descriptors of enabled plugins for the given chart.
6593 * @returns {Array} [{ plugin, options }]
6594 * @private
6595 */
6596 descriptors: function(chart) {
6597 var cache = chart._plugins || (chart._plugins = {});
6598 if (cache.id === this._cacheId) {
6599 return cache.descriptors;
6600 }
6601
6602 var plugins = [];
6603 var descriptors = [];
6604 var config = (chart && chart.config) || {};
6605 var options = (config.options && config.options.plugins) || {};
6606
6607 this._plugins.concat(config.plugins || []).forEach(function(plugin) {
6608 var idx = plugins.indexOf(plugin);
6609 if (idx !== -1) {
6610 return;
6611 }
6612
6613 var id = plugin.id;
6614 var opts = options[id];
6615 if (opts === false) {
6616 return;
6617 }
6618
6619 if (opts === true) {
6620 opts = helpers.clone(defaults.global.plugins[id]);
6621 }
6622
6623 plugins.push(plugin);
6624 descriptors.push({
6625 plugin: plugin,
6626 options: opts || {}
6627 });
6628 });
6629
6630 cache.descriptors = descriptors;
6631 cache.id = this._cacheId;
6632 return descriptors;
6633 }
6634 };
6635
6636 /**
6637 * Plugin extension hooks.
6638 * @interface IPlugin
6639 * @since 2.1.0
6640 */
6641 /**
6642 * @method IPlugin#beforeInit
6643 * @desc Called before initializing `chart`.
6644 * @param {Chart.Controller} chart - The chart instance.
6645 * @param {Object} options - The plugin options.
6646 */
6647 /**
6648 * @method IPlugin#afterInit
6649 * @desc Called after `chart` has been initialized and before the first update.
6650 * @param {Chart.Controller} chart - The chart instance.
6651 * @param {Object} options - The plugin options.
6652 */
6653 /**
6654 * @method IPlugin#beforeUpdate
6655 * @desc Called before updating `chart`. If any plugin returns `false`, the update
6656 * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
6657 * @param {Chart.Controller} chart - The chart instance.
6658 * @param {Object} options - The plugin options.
6659 * @returns {Boolean} `false` to cancel the chart update.
6660 */
6661 /**
6662 * @method IPlugin#afterUpdate
6663 * @desc Called after `chart` has been updated and before rendering. Note that this
6664 * hook will not be called if the chart update has been previously cancelled.
6665 * @param {Chart.Controller} chart - The chart instance.
6666 * @param {Object} options - The plugin options.
6667 */
6668 /**
6669 * @method IPlugin#beforeDatasetsUpdate
6670 * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
6671 * the datasets update is cancelled until another `update` is triggered.
6672 * @param {Chart.Controller} chart - The chart instance.
6673 * @param {Object} options - The plugin options.
6674 * @returns {Boolean} false to cancel the datasets update.
6675 * @since version 2.1.5
6676 */
6677 /**
6678 * @method IPlugin#afterDatasetsUpdate
6679 * @desc Called after the `chart` datasets have been updated. Note that this hook
6680 * will not be called if the datasets update has been previously cancelled.
6681 * @param {Chart.Controller} chart - The chart instance.
6682 * @param {Object} options - The plugin options.
6683 * @since version 2.1.5
6684 */
6685 /**
6686 * @method IPlugin#beforeDatasetUpdate
6687 * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
6688 * returns `false`, the datasets update is cancelled until another `update` is triggered.
6689 * @param {Chart} chart - The chart instance.
6690 * @param {Object} args - The call arguments.
6691 * @param {Number} args.index - The dataset index.
6692 * @param {Object} args.meta - The dataset metadata.
6693 * @param {Object} options - The plugin options.
6694 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6695 */
6696 /**
6697 * @method IPlugin#afterDatasetUpdate
6698 * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
6699 * that this hook will not be called if the datasets update has been previously cancelled.
6700 * @param {Chart} chart - The chart instance.
6701 * @param {Object} args - The call arguments.
6702 * @param {Number} args.index - The dataset index.
6703 * @param {Object} args.meta - The dataset metadata.
6704 * @param {Object} options - The plugin options.
6705 */
6706 /**
6707 * @method IPlugin#beforeLayout
6708 * @desc Called before laying out `chart`. If any plugin returns `false`,
6709 * the layout update is cancelled until another `update` is triggered.
6710 * @param {Chart.Controller} chart - The chart instance.
6711 * @param {Object} options - The plugin options.
6712 * @returns {Boolean} `false` to cancel the chart layout.
6713 */
6714 /**
6715 * @method IPlugin#afterLayout
6716 * @desc Called after the `chart` has been layed out. Note that this hook will not
6717 * be called if the layout update has been previously cancelled.
6718 * @param {Chart.Controller} chart - The chart instance.
6719 * @param {Object} options - The plugin options.
6720 */
6721 /**
6722 * @method IPlugin#beforeRender
6723 * @desc Called before rendering `chart`. If any plugin returns `false`,
6724 * the rendering is cancelled until another `render` is triggered.
6725 * @param {Chart.Controller} chart - The chart instance.
6726 * @param {Object} options - The plugin options.
6727 * @returns {Boolean} `false` to cancel the chart rendering.
6728 */
6729 /**
6730 * @method IPlugin#afterRender
6731 * @desc Called after the `chart` has been fully rendered (and animation completed). Note
6732 * that this hook will not be called if the rendering has been previously cancelled.
6733 * @param {Chart.Controller} chart - The chart instance.
6734 * @param {Object} options - The plugin options.
6735 */
6736 /**
6737 * @method IPlugin#beforeDraw
6738 * @desc Called before drawing `chart` at every animation frame specified by the given
6739 * easing value. If any plugin returns `false`, the frame drawing is cancelled until
6740 * another `render` is triggered.
6741 * @param {Chart.Controller} chart - The chart instance.
6742 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6743 * @param {Object} options - The plugin options.
6744 * @returns {Boolean} `false` to cancel the chart drawing.
6745 */
6746 /**
6747 * @method IPlugin#afterDraw
6748 * @desc Called after the `chart` has been drawn for the specific easing value. Note
6749 * that this hook will not be called if the drawing has been previously cancelled.
6750 * @param {Chart.Controller} chart - The chart instance.
6751 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6752 * @param {Object} options - The plugin options.
6753 */
6754 /**
6755 * @method IPlugin#beforeDatasetsDraw
6756 * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
6757 * the datasets drawing is cancelled until another `render` is triggered.
6758 * @param {Chart.Controller} chart - The chart instance.
6759 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6760 * @param {Object} options - The plugin options.
6761 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6762 */
6763 /**
6764 * @method IPlugin#afterDatasetsDraw
6765 * @desc Called after the `chart` datasets have been drawn. Note that this hook
6766 * will not be called if the datasets drawing has been previously cancelled.
6767 * @param {Chart.Controller} chart - The chart instance.
6768 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6769 * @param {Object} options - The plugin options.
6770 */
6771 /**
6772 * @method IPlugin#beforeDatasetDraw
6773 * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
6774 * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
6775 * is cancelled until another `render` is triggered.
6776 * @param {Chart} chart - The chart instance.
6777 * @param {Object} args - The call arguments.
6778 * @param {Number} args.index - The dataset index.
6779 * @param {Object} args.meta - The dataset metadata.
6780 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6781 * @param {Object} options - The plugin options.
6782 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6783 */
6784 /**
6785 * @method IPlugin#afterDatasetDraw
6786 * @desc Called after the `chart` datasets at the given `args.index` have been drawn
6787 * (datasets are drawn in the reverse order). Note that this hook will not be called
6788 * if the datasets drawing has been previously cancelled.
6789 * @param {Chart} chart - The chart instance.
6790 * @param {Object} args - The call arguments.
6791 * @param {Number} args.index - The dataset index.
6792 * @param {Object} args.meta - The dataset metadata.
6793 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6794 * @param {Object} options - The plugin options.
6795 */
6796 /**
6797 * @method IPlugin#beforeTooltipDraw
6798 * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
6799 * the tooltip drawing is cancelled until another `render` is triggered.
6800 * @param {Chart} chart - The chart instance.
6801 * @param {Object} args - The call arguments.
6802 * @param {Object} args.tooltip - The tooltip.
6803 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6804 * @param {Object} options - The plugin options.
6805 * @returns {Boolean} `false` to cancel the chart tooltip drawing.
6806 */
6807 /**
6808 * @method IPlugin#afterTooltipDraw
6809 * @desc Called after drawing the `tooltip`. Note that this hook will not
6810 * be called if the tooltip drawing has been previously cancelled.
6811 * @param {Chart} chart - The chart instance.
6812 * @param {Object} args - The call arguments.
6813 * @param {Object} args.tooltip - The tooltip.
6814 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6815 * @param {Object} options - The plugin options.
6816 */
6817 /**
6818 * @method IPlugin#beforeEvent
6819 * @desc Called before processing the specified `event`. If any plugin returns `false`,
6820 * the event will be discarded.
6821 * @param {Chart.Controller} chart - The chart instance.
6822 * @param {IEvent} event - The event object.
6823 * @param {Object} options - The plugin options.
6824 */
6825 /**
6826 * @method IPlugin#afterEvent
6827 * @desc Called after the `event` has been consumed. Note that this hook
6828 * will not be called if the `event` has been previously discarded.
6829 * @param {Chart.Controller} chart - The chart instance.
6830 * @param {IEvent} event - The event object.
6831 * @param {Object} options - The plugin options.
6832 */
6833 /**
6834 * @method IPlugin#resize
6835 * @desc Called after the chart as been resized.
6836 * @param {Chart.Controller} chart - The chart instance.
6837 * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
6838 * @param {Object} options - The plugin options.
6839 */
6840 /**
6841 * @method IPlugin#destroy
6842 * @desc Called after the chart as been destroyed.
6843 * @param {Chart.Controller} chart - The chart instance.
6844 * @param {Object} options - The plugin options.
6845 */
6846
6847 /**
6848 * Provided for backward compatibility, use Chart.plugins instead
6849 * @namespace Chart.pluginService
6850 * @deprecated since version 2.1.5
6851 * @todo remove at version 3
6852 * @private
6853 */
6854 Chart.pluginService = Chart.plugins;
6855
6856 /**
6857 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
6858 * effect, instead simply create/register plugins via plain JavaScript objects.
6859 * @interface Chart.PluginBase
6860 * @deprecated since version 2.5.0
6861 * @todo remove at version 3
6862 * @private
6863 */
6864 Chart.PluginBase = Element.extend({});
6865};
6866
6867},{"25":25,"26":26,"45":45}],32:[function(require,module,exports){
6868'use strict';
6869
6870var defaults = require(25);
6871var Element = require(26);
6872var helpers = require(45);
6873var Ticks = require(34);
6874
6875defaults._set('scale', {
6876 display: true,
6877 position: 'left',
6878 offset: false,
6879
6880 // grid line settings
6881 gridLines: {
6882 display: true,
6883 color: 'rgba(0, 0, 0, 0.1)',
6884 lineWidth: 1,
6885 drawBorder: true,
6886 drawOnChartArea: true,
6887 drawTicks: true,
6888 tickMarkLength: 10,
6889 zeroLineWidth: 1,
6890 zeroLineColor: 'rgba(0,0,0,0.25)',
6891 zeroLineBorderDash: [],
6892 zeroLineBorderDashOffset: 0.0,
6893 offsetGridLines: false,
6894 borderDash: [],
6895 borderDashOffset: 0.0
6896 },
6897
6898 // scale label
6899 scaleLabel: {
6900 // display property
6901 display: false,
6902
6903 // actual label
6904 labelString: '',
6905
6906 // line height
6907 lineHeight: 1.2,
6908
6909 // top/bottom padding
6910 padding: {
6911 top: 4,
6912 bottom: 4
6913 }
6914 },
6915
6916 // label settings
6917 ticks: {
6918 beginAtZero: false,
6919 minRotation: 0,
6920 maxRotation: 50,
6921 mirror: false,
6922 padding: 0,
6923 reverse: false,
6924 display: true,
6925 autoSkip: true,
6926 autoSkipPadding: 0,
6927 labelOffset: 0,
6928 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
6929 callback: Ticks.formatters.values,
6930 minor: {},
6931 major: {}
6932 }
6933});
6934
6935function labelsFromTicks(ticks) {
6936 var labels = [];
6937 var i, ilen;
6938
6939 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
6940 labels.push(ticks[i].label);
6941 }
6942
6943 return labels;
6944}
6945
6946function getLineValue(scale, index, offsetGridLines) {
6947 var lineValue = scale.getPixelForTick(index);
6948
6949 if (offsetGridLines) {
6950 if (index === 0) {
6951 lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
6952 } else {
6953 lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
6954 }
6955 }
6956 return lineValue;
6957}
6958
6959module.exports = function(Chart) {
6960
6961 function computeTextSize(context, tick, font) {
6962 return helpers.isArray(tick) ?
6963 helpers.longestText(context, font, tick) :
6964 context.measureText(tick).width;
6965 }
6966
6967 function parseFontOptions(options) {
6968 var valueOrDefault = helpers.valueOrDefault;
6969 var globalDefaults = defaults.global;
6970 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
6971 var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
6972 var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
6973
6974 return {
6975 size: size,
6976 style: style,
6977 family: family,
6978 font: helpers.fontString(size, style, family)
6979 };
6980 }
6981
6982 function parseLineHeight(options) {
6983 return helpers.options.toLineHeight(
6984 helpers.valueOrDefault(options.lineHeight, 1.2),
6985 helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize));
6986 }
6987
6988 Chart.Scale = Element.extend({
6989 /**
6990 * Get the padding needed for the scale
6991 * @method getPadding
6992 * @private
6993 * @returns {Padding} the necessary padding
6994 */
6995 getPadding: function() {
6996 var me = this;
6997 return {
6998 left: me.paddingLeft || 0,
6999 top: me.paddingTop || 0,
7000 right: me.paddingRight || 0,
7001 bottom: me.paddingBottom || 0
7002 };
7003 },
7004
7005 /**
7006 * Returns the scale tick objects ({label, major})
7007 * @since 2.7
7008 */
7009 getTicks: function() {
7010 return this._ticks;
7011 },
7012
7013 // These methods are ordered by lifecyle. Utilities then follow.
7014 // Any function defined here is inherited by all scale types.
7015 // Any function can be extended by the scale type
7016
7017 mergeTicksOptions: function() {
7018 var ticks = this.options.ticks;
7019 if (ticks.minor === false) {
7020 ticks.minor = {
7021 display: false
7022 };
7023 }
7024 if (ticks.major === false) {
7025 ticks.major = {
7026 display: false
7027 };
7028 }
7029 for (var key in ticks) {
7030 if (key !== 'major' && key !== 'minor') {
7031 if (typeof ticks.minor[key] === 'undefined') {
7032 ticks.minor[key] = ticks[key];
7033 }
7034 if (typeof ticks.major[key] === 'undefined') {
7035 ticks.major[key] = ticks[key];
7036 }
7037 }
7038 }
7039 },
7040 beforeUpdate: function() {
7041 helpers.callback(this.options.beforeUpdate, [this]);
7042 },
7043 update: function(maxWidth, maxHeight, margins) {
7044 var me = this;
7045 var i, ilen, labels, label, ticks, tick;
7046
7047 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
7048 me.beforeUpdate();
7049
7050 // Absorb the master measurements
7051 me.maxWidth = maxWidth;
7052 me.maxHeight = maxHeight;
7053 me.margins = helpers.extend({
7054 left: 0,
7055 right: 0,
7056 top: 0,
7057 bottom: 0
7058 }, margins);
7059 me.longestTextCache = me.longestTextCache || {};
7060
7061 // Dimensions
7062 me.beforeSetDimensions();
7063 me.setDimensions();
7064 me.afterSetDimensions();
7065
7066 // Data min/max
7067 me.beforeDataLimits();
7068 me.determineDataLimits();
7069 me.afterDataLimits();
7070
7071 // Ticks - `this.ticks` is now DEPRECATED!
7072 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
7073 // and must not be accessed directly from outside this class. `this.ticks` being
7074 // around for long time and not marked as private, we can't change its structure
7075 // without unexpected breaking changes. If you need to access the scale ticks,
7076 // use scale.getTicks() instead.
7077
7078 me.beforeBuildTicks();
7079
7080 // New implementations should return an array of objects but for BACKWARD COMPAT,
7081 // we still support no return (`this.ticks` internally set by calling this method).
7082 ticks = me.buildTicks() || [];
7083
7084 me.afterBuildTicks();
7085
7086 me.beforeTickToLabelConversion();
7087
7088 // New implementations should return the formatted tick labels but for BACKWARD
7089 // COMPAT, we still support no return (`this.ticks` internally changed by calling
7090 // this method and supposed to contain only string values).
7091 labels = me.convertTicksToLabels(ticks) || me.ticks;
7092
7093 me.afterTickToLabelConversion();
7094
7095 me.ticks = labels; // BACKWARD COMPATIBILITY
7096
7097 // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
7098
7099 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
7100 for (i = 0, ilen = labels.length; i < ilen; ++i) {
7101 label = labels[i];
7102 tick = ticks[i];
7103 if (!tick) {
7104 ticks.push(tick = {
7105 label: label,
7106 major: false
7107 });
7108 } else {
7109 tick.label = label;
7110 }
7111 }
7112
7113 me._ticks = ticks;
7114
7115 // Tick Rotation
7116 me.beforeCalculateTickRotation();
7117 me.calculateTickRotation();
7118 me.afterCalculateTickRotation();
7119 // Fit
7120 me.beforeFit();
7121 me.fit();
7122 me.afterFit();
7123 //
7124 me.afterUpdate();
7125
7126 return me.minSize;
7127
7128 },
7129 afterUpdate: function() {
7130 helpers.callback(this.options.afterUpdate, [this]);
7131 },
7132
7133 //
7134
7135 beforeSetDimensions: function() {
7136 helpers.callback(this.options.beforeSetDimensions, [this]);
7137 },
7138 setDimensions: function() {
7139 var me = this;
7140 // Set the unconstrained dimension before label rotation
7141 if (me.isHorizontal()) {
7142 // Reset position before calculating rotation
7143 me.width = me.maxWidth;
7144 me.left = 0;
7145 me.right = me.width;
7146 } else {
7147 me.height = me.maxHeight;
7148
7149 // Reset position before calculating rotation
7150 me.top = 0;
7151 me.bottom = me.height;
7152 }
7153
7154 // Reset padding
7155 me.paddingLeft = 0;
7156 me.paddingTop = 0;
7157 me.paddingRight = 0;
7158 me.paddingBottom = 0;
7159 },
7160 afterSetDimensions: function() {
7161 helpers.callback(this.options.afterSetDimensions, [this]);
7162 },
7163
7164 // Data limits
7165 beforeDataLimits: function() {
7166 helpers.callback(this.options.beforeDataLimits, [this]);
7167 },
7168 determineDataLimits: helpers.noop,
7169 afterDataLimits: function() {
7170 helpers.callback(this.options.afterDataLimits, [this]);
7171 },
7172
7173 //
7174 beforeBuildTicks: function() {
7175 helpers.callback(this.options.beforeBuildTicks, [this]);
7176 },
7177 buildTicks: helpers.noop,
7178 afterBuildTicks: function() {
7179 helpers.callback(this.options.afterBuildTicks, [this]);
7180 },
7181
7182 beforeTickToLabelConversion: function() {
7183 helpers.callback(this.options.beforeTickToLabelConversion, [this]);
7184 },
7185 convertTicksToLabels: function() {
7186 var me = this;
7187 // Convert ticks to strings
7188 var tickOpts = me.options.ticks;
7189 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
7190 },
7191 afterTickToLabelConversion: function() {
7192 helpers.callback(this.options.afterTickToLabelConversion, [this]);
7193 },
7194
7195 //
7196
7197 beforeCalculateTickRotation: function() {
7198 helpers.callback(this.options.beforeCalculateTickRotation, [this]);
7199 },
7200 calculateTickRotation: function() {
7201 var me = this;
7202 var context = me.ctx;
7203 var tickOpts = me.options.ticks;
7204 var labels = labelsFromTicks(me._ticks);
7205
7206 // Get the width of each grid by calculating the difference
7207 // between x offsets between 0 and 1.
7208 var tickFont = parseFontOptions(tickOpts);
7209 context.font = tickFont.font;
7210
7211 var labelRotation = tickOpts.minRotation || 0;
7212
7213 if (labels.length && me.options.display && me.isHorizontal()) {
7214 var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache);
7215 var labelWidth = originalLabelWidth;
7216 var cosRotation, sinRotation;
7217
7218 // Allow 3 pixels x2 padding either side for label readability
7219 var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
7220
7221 // Max label rotation can be set or default to 90 - also act as a loop counter
7222 while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
7223 var angleRadians = helpers.toRadians(labelRotation);
7224 cosRotation = Math.cos(angleRadians);
7225 sinRotation = Math.sin(angleRadians);
7226
7227 if (sinRotation * originalLabelWidth > me.maxHeight) {
7228 // go back one step
7229 labelRotation--;
7230 break;
7231 }
7232
7233 labelRotation++;
7234 labelWidth = cosRotation * originalLabelWidth;
7235 }
7236 }
7237
7238 me.labelRotation = labelRotation;
7239 },
7240 afterCalculateTickRotation: function() {
7241 helpers.callback(this.options.afterCalculateTickRotation, [this]);
7242 },
7243
7244 //
7245
7246 beforeFit: function() {
7247 helpers.callback(this.options.beforeFit, [this]);
7248 },
7249 fit: function() {
7250 var me = this;
7251 // Reset
7252 var minSize = me.minSize = {
7253 width: 0,
7254 height: 0
7255 };
7256
7257 var labels = labelsFromTicks(me._ticks);
7258
7259 var opts = me.options;
7260 var tickOpts = opts.ticks;
7261 var scaleLabelOpts = opts.scaleLabel;
7262 var gridLineOpts = opts.gridLines;
7263 var display = opts.display;
7264 var isHorizontal = me.isHorizontal();
7265
7266 var tickFont = parseFontOptions(tickOpts);
7267 var tickMarkLength = opts.gridLines.tickMarkLength;
7268
7269 // Width
7270 if (isHorizontal) {
7271 // subtract the margins to line up with the chartArea if we are a full width scale
7272 minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
7273 } else {
7274 minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7275 }
7276
7277 // height
7278 if (isHorizontal) {
7279 minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7280 } else {
7281 minSize.height = me.maxHeight; // fill all the height
7282 }
7283
7284 // Are we showing a title for the scale?
7285 if (scaleLabelOpts.display && display) {
7286 var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts);
7287 var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding);
7288 var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height;
7289
7290 if (isHorizontal) {
7291 minSize.height += deltaHeight;
7292 } else {
7293 minSize.width += deltaHeight;
7294 }
7295 }
7296
7297 // Don't bother fitting the ticks if we are not showing them
7298 if (tickOpts.display && display) {
7299 var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache);
7300 var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels);
7301 var lineSpace = tickFont.size * 0.5;
7302 var tickPadding = me.options.ticks.padding;
7303
7304 if (isHorizontal) {
7305 // A horizontal axis is more constrained by the height.
7306 me.longestLabelWidth = largestTextWidth;
7307
7308 var angleRadians = helpers.toRadians(me.labelRotation);
7309 var cosRotation = Math.cos(angleRadians);
7310 var sinRotation = Math.sin(angleRadians);
7311
7312 // TODO - improve this calculation
7313 var labelHeight = (sinRotation * largestTextWidth)
7314 + (tickFont.size * tallestLabelHeightInLines)
7315 + (lineSpace * (tallestLabelHeightInLines - 1))
7316 + lineSpace; // padding
7317
7318 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
7319
7320 me.ctx.font = tickFont.font;
7321 var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font);
7322 var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font);
7323
7324 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
7325 // which means that the right padding is dominated by the font height
7326 if (me.labelRotation !== 0) {
7327 me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges
7328 me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3;
7329 } else {
7330 me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
7331 me.paddingRight = lastLabelWidth / 2 + 3;
7332 }
7333 } else {
7334 // A vertical axis is more constrained by the width. Labels are the
7335 // dominant factor here, so get that length first and account for padding
7336 if (tickOpts.mirror) {
7337 largestTextWidth = 0;
7338 } else {
7339 // use lineSpace for consistency with horizontal axis
7340 // tickPadding is not implemented for horizontal
7341 largestTextWidth += tickPadding + lineSpace;
7342 }
7343
7344 minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
7345
7346 me.paddingTop = tickFont.size / 2;
7347 me.paddingBottom = tickFont.size / 2;
7348 }
7349 }
7350
7351 me.handleMargins();
7352
7353 me.width = minSize.width;
7354 me.height = minSize.height;
7355 },
7356
7357 /**
7358 * Handle margins and padding interactions
7359 * @private
7360 */
7361 handleMargins: function() {
7362 var me = this;
7363 if (me.margins) {
7364 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7365 me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
7366 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7367 me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
7368 }
7369 },
7370
7371 afterFit: function() {
7372 helpers.callback(this.options.afterFit, [this]);
7373 },
7374
7375 // Shared Methods
7376 isHorizontal: function() {
7377 return this.options.position === 'top' || this.options.position === 'bottom';
7378 },
7379 isFullWidth: function() {
7380 return (this.options.fullWidth);
7381 },
7382
7383 // 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
7384 getRightValue: function(rawValue) {
7385 // Null and undefined values first
7386 if (helpers.isNullOrUndef(rawValue)) {
7387 return NaN;
7388 }
7389 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
7390 if (typeof rawValue === 'number' && !isFinite(rawValue)) {
7391 return NaN;
7392 }
7393 // If it is in fact an object, dive in one more level
7394 if (rawValue) {
7395 if (this.isHorizontal()) {
7396 if (rawValue.x !== undefined) {
7397 return this.getRightValue(rawValue.x);
7398 }
7399 } else if (rawValue.y !== undefined) {
7400 return this.getRightValue(rawValue.y);
7401 }
7402 }
7403
7404 // Value is good, return it
7405 return rawValue;
7406 },
7407
7408 /**
7409 * Used to get the value to display in the tooltip for the data at the given index
7410 * @param index
7411 * @param datasetIndex
7412 */
7413 getLabelForIndex: helpers.noop,
7414
7415 /**
7416 * Returns the location of the given data point. Value can either be an index or a numerical value
7417 * The coordinate (0, 0) is at the upper-left corner of the canvas
7418 * @param value
7419 * @param index
7420 * @param datasetIndex
7421 */
7422 getPixelForValue: helpers.noop,
7423
7424 /**
7425 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
7426 * The coordinate (0, 0) is at the upper-left corner of the canvas
7427 * @param pixel
7428 */
7429 getValueForPixel: helpers.noop,
7430
7431 /**
7432 * Returns the location of the tick at the given index
7433 * The coordinate (0, 0) is at the upper-left corner of the canvas
7434 */
7435 getPixelForTick: function(index) {
7436 var me = this;
7437 var offset = me.options.offset;
7438 if (me.isHorizontal()) {
7439 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7440 var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
7441 var pixel = (tickWidth * index) + me.paddingLeft;
7442
7443 if (offset) {
7444 pixel += tickWidth / 2;
7445 }
7446
7447 var finalVal = me.left + Math.round(pixel);
7448 finalVal += me.isFullWidth() ? me.margins.left : 0;
7449 return finalVal;
7450 }
7451 var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
7452 return me.top + (index * (innerHeight / (me._ticks.length - 1)));
7453 },
7454
7455 /**
7456 * Utility for getting the pixel location of a percentage of scale
7457 * The coordinate (0, 0) is at the upper-left corner of the canvas
7458 */
7459 getPixelForDecimal: function(decimal) {
7460 var me = this;
7461 if (me.isHorizontal()) {
7462 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7463 var valueOffset = (innerWidth * decimal) + me.paddingLeft;
7464
7465 var finalVal = me.left + Math.round(valueOffset);
7466 finalVal += me.isFullWidth() ? me.margins.left : 0;
7467 return finalVal;
7468 }
7469 return me.top + (decimal * me.height);
7470 },
7471
7472 /**
7473 * Returns the pixel for the minimum chart value
7474 * The coordinate (0, 0) is at the upper-left corner of the canvas
7475 */
7476 getBasePixel: function() {
7477 return this.getPixelForValue(this.getBaseValue());
7478 },
7479
7480 getBaseValue: function() {
7481 var me = this;
7482 var min = me.min;
7483 var max = me.max;
7484
7485 return me.beginAtZero ? 0 :
7486 min < 0 && max < 0 ? max :
7487 min > 0 && max > 0 ? min :
7488 0;
7489 },
7490
7491 /**
7492 * Returns a subset of ticks to be plotted to avoid overlapping labels.
7493 * @private
7494 */
7495 _autoSkip: function(ticks) {
7496 var skipRatio;
7497 var me = this;
7498 var isHorizontal = me.isHorizontal();
7499 var optionTicks = me.options.ticks.minor;
7500 var tickCount = ticks.length;
7501 var labelRotationRadians = helpers.toRadians(me.labelRotation);
7502 var cosRotation = Math.cos(labelRotationRadians);
7503 var longestRotatedLabel = me.longestLabelWidth * cosRotation;
7504 var result = [];
7505 var i, tick, shouldSkip;
7506
7507 // figure out the maximum number of gridlines to show
7508 var maxTicks;
7509 if (optionTicks.maxTicksLimit) {
7510 maxTicks = optionTicks.maxTicksLimit;
7511 }
7512
7513 if (isHorizontal) {
7514 skipRatio = false;
7515
7516 if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) {
7517 skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight)));
7518 }
7519
7520 // if they defined a max number of optionTicks,
7521 // increase skipRatio until that number is met
7522 if (maxTicks && tickCount > maxTicks) {
7523 skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks));
7524 }
7525 }
7526
7527 for (i = 0; i < tickCount; i++) {
7528 tick = ticks[i];
7529
7530 // Since we always show the last tick,we need may need to hide the last shown one before
7531 shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount);
7532 if (shouldSkip && i !== tickCount - 1) {
7533 // leave tick in place but make sure it's not displayed (#4635)
7534 delete tick.label;
7535 }
7536 result.push(tick);
7537 }
7538 return result;
7539 },
7540
7541 // Actually draw the scale on the canvas
7542 // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
7543 draw: function(chartArea) {
7544 var me = this;
7545 var options = me.options;
7546 if (!options.display) {
7547 return;
7548 }
7549
7550 var context = me.ctx;
7551 var globalDefaults = defaults.global;
7552 var optionTicks = options.ticks.minor;
7553 var optionMajorTicks = options.ticks.major || optionTicks;
7554 var gridLines = options.gridLines;
7555 var scaleLabel = options.scaleLabel;
7556
7557 var isRotated = me.labelRotation !== 0;
7558 var isHorizontal = me.isHorizontal();
7559
7560 var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
7561 var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
7562 var tickFont = parseFontOptions(optionTicks);
7563 var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
7564 var majorTickFont = parseFontOptions(optionMajorTicks);
7565
7566 var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
7567
7568 var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
7569 var scaleLabelFont = parseFontOptions(scaleLabel);
7570 var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
7571 var labelRotationRadians = helpers.toRadians(me.labelRotation);
7572
7573 var itemsToDraw = [];
7574
7575 var xTickStart = options.position === 'right' ? me.left : me.right - tl;
7576 var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
7577 var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
7578 var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
7579
7580 helpers.each(ticks, function(tick, index) {
7581 // autoskipper skipped this tick (#4635)
7582 if (helpers.isNullOrUndef(tick.label)) {
7583 return;
7584 }
7585
7586 var label = tick.label;
7587 var lineWidth, lineColor, borderDash, borderDashOffset;
7588 if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
7589 // Draw the first index specially
7590 lineWidth = gridLines.zeroLineWidth;
7591 lineColor = gridLines.zeroLineColor;
7592 borderDash = gridLines.zeroLineBorderDash;
7593 borderDashOffset = gridLines.zeroLineBorderDashOffset;
7594 } else {
7595 lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index);
7596 lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index);
7597 borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
7598 borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
7599 }
7600
7601 // Common properties
7602 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
7603 var textAlign = 'middle';
7604 var textBaseline = 'middle';
7605 var tickPadding = optionTicks.padding;
7606
7607 if (isHorizontal) {
7608 var labelYOffset = tl + tickPadding;
7609
7610 if (options.position === 'bottom') {
7611 // bottom
7612 textBaseline = !isRotated ? 'top' : 'middle';
7613 textAlign = !isRotated ? 'center' : 'right';
7614 labelY = me.top + labelYOffset;
7615 } else {
7616 // top
7617 textBaseline = !isRotated ? 'bottom' : 'middle';
7618 textAlign = !isRotated ? 'center' : 'left';
7619 labelY = me.bottom - labelYOffset;
7620 }
7621
7622 var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7623 if (xLineValue < me.left) {
7624 lineColor = 'rgba(0,0,0,0)';
7625 }
7626 xLineValue += helpers.aliasPixel(lineWidth);
7627
7628 labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
7629
7630 tx1 = tx2 = x1 = x2 = xLineValue;
7631 ty1 = yTickStart;
7632 ty2 = yTickEnd;
7633 y1 = chartArea.top;
7634 y2 = chartArea.bottom;
7635 } else {
7636 var isLeft = options.position === 'left';
7637 var labelXOffset;
7638
7639 if (optionTicks.mirror) {
7640 textAlign = isLeft ? 'left' : 'right';
7641 labelXOffset = tickPadding;
7642 } else {
7643 textAlign = isLeft ? 'right' : 'left';
7644 labelXOffset = tl + tickPadding;
7645 }
7646
7647 labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
7648
7649 var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7650 if (yLineValue < me.top) {
7651 lineColor = 'rgba(0,0,0,0)';
7652 }
7653 yLineValue += helpers.aliasPixel(lineWidth);
7654
7655 labelY = me.getPixelForTick(index) + optionTicks.labelOffset;
7656
7657 tx1 = xTickStart;
7658 tx2 = xTickEnd;
7659 x1 = chartArea.left;
7660 x2 = chartArea.right;
7661 ty1 = ty2 = y1 = y2 = yLineValue;
7662 }
7663
7664 itemsToDraw.push({
7665 tx1: tx1,
7666 ty1: ty1,
7667 tx2: tx2,
7668 ty2: ty2,
7669 x1: x1,
7670 y1: y1,
7671 x2: x2,
7672 y2: y2,
7673 labelX: labelX,
7674 labelY: labelY,
7675 glWidth: lineWidth,
7676 glColor: lineColor,
7677 glBorderDash: borderDash,
7678 glBorderDashOffset: borderDashOffset,
7679 rotation: -1 * labelRotationRadians,
7680 label: label,
7681 major: tick.major,
7682 textBaseline: textBaseline,
7683 textAlign: textAlign
7684 });
7685 });
7686
7687 // Draw all of the tick labels, tick marks, and grid lines at the correct places
7688 helpers.each(itemsToDraw, function(itemToDraw) {
7689 if (gridLines.display) {
7690 context.save();
7691 context.lineWidth = itemToDraw.glWidth;
7692 context.strokeStyle = itemToDraw.glColor;
7693 if (context.setLineDash) {
7694 context.setLineDash(itemToDraw.glBorderDash);
7695 context.lineDashOffset = itemToDraw.glBorderDashOffset;
7696 }
7697
7698 context.beginPath();
7699
7700 if (gridLines.drawTicks) {
7701 context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
7702 context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
7703 }
7704
7705 if (gridLines.drawOnChartArea) {
7706 context.moveTo(itemToDraw.x1, itemToDraw.y1);
7707 context.lineTo(itemToDraw.x2, itemToDraw.y2);
7708 }
7709
7710 context.stroke();
7711 context.restore();
7712 }
7713
7714 if (optionTicks.display) {
7715 // Make sure we draw text in the correct color and font
7716 context.save();
7717 context.translate(itemToDraw.labelX, itemToDraw.labelY);
7718 context.rotate(itemToDraw.rotation);
7719 context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
7720 context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
7721 context.textBaseline = itemToDraw.textBaseline;
7722 context.textAlign = itemToDraw.textAlign;
7723
7724 var label = itemToDraw.label;
7725 if (helpers.isArray(label)) {
7726 for (var i = 0, y = 0; i < label.length; ++i) {
7727 // We just make sure the multiline element is a string here..
7728 context.fillText('' + label[i], 0, y);
7729 // apply same lineSpacing as calculated @ L#320
7730 y += (tickFont.size * 1.5);
7731 }
7732 } else {
7733 context.fillText(label, 0, 0);
7734 }
7735 context.restore();
7736 }
7737 });
7738
7739 if (scaleLabel.display) {
7740 // Draw the scale label
7741 var scaleLabelX;
7742 var scaleLabelY;
7743 var rotation = 0;
7744 var halfLineHeight = parseLineHeight(scaleLabel) / 2;
7745
7746 if (isHorizontal) {
7747 scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
7748 scaleLabelY = options.position === 'bottom'
7749 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
7750 : me.top + halfLineHeight + scaleLabelPadding.top;
7751 } else {
7752 var isLeft = options.position === 'left';
7753 scaleLabelX = isLeft
7754 ? me.left + halfLineHeight + scaleLabelPadding.top
7755 : me.right - halfLineHeight - scaleLabelPadding.top;
7756 scaleLabelY = me.top + ((me.bottom - me.top) / 2);
7757 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
7758 }
7759
7760 context.save();
7761 context.translate(scaleLabelX, scaleLabelY);
7762 context.rotate(rotation);
7763 context.textAlign = 'center';
7764 context.textBaseline = 'middle';
7765 context.fillStyle = scaleLabelFontColor; // render in correct colour
7766 context.font = scaleLabelFont.font;
7767 context.fillText(scaleLabel.labelString, 0, 0);
7768 context.restore();
7769 }
7770
7771 if (gridLines.drawBorder) {
7772 // Draw the line at the edge of the axis
7773 context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0);
7774 context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0);
7775 var x1 = me.left;
7776 var x2 = me.right;
7777 var y1 = me.top;
7778 var y2 = me.bottom;
7779
7780 var aliasPixel = helpers.aliasPixel(context.lineWidth);
7781 if (isHorizontal) {
7782 y1 = y2 = options.position === 'top' ? me.bottom : me.top;
7783 y1 += aliasPixel;
7784 y2 += aliasPixel;
7785 } else {
7786 x1 = x2 = options.position === 'left' ? me.right : me.left;
7787 x1 += aliasPixel;
7788 x2 += aliasPixel;
7789 }
7790
7791 context.beginPath();
7792 context.moveTo(x1, y1);
7793 context.lineTo(x2, y2);
7794 context.stroke();
7795 }
7796 }
7797 });
7798};
7799
7800},{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){
7801'use strict';
7802
7803var defaults = require(25);
7804var helpers = require(45);
7805
7806module.exports = function(Chart) {
7807
7808 Chart.scaleService = {
7809 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
7810 // use the new chart options to grab the correct scale
7811 constructors: {},
7812 // Use a registration function so that we can move to an ES6 map when we no longer need to support
7813 // old browsers
7814
7815 // Scale config defaults
7816 defaults: {},
7817 registerScaleType: function(type, scaleConstructor, scaleDefaults) {
7818 this.constructors[type] = scaleConstructor;
7819 this.defaults[type] = helpers.clone(scaleDefaults);
7820 },
7821 getScaleConstructor: function(type) {
7822 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
7823 },
7824 getScaleDefaults: function(type) {
7825 // Return the scale defaults merged with the global settings so that we always use the latest ones
7826 return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {};
7827 },
7828 updateScaleDefaults: function(type, additions) {
7829 var me = this;
7830 if (me.defaults.hasOwnProperty(type)) {
7831 me.defaults[type] = helpers.extend(me.defaults[type], additions);
7832 }
7833 },
7834 addScalesToLayout: function(chart) {
7835 // Adds each scale to the chart.boxes array to be sized accordingly
7836 helpers.each(chart.scales, function(scale) {
7837 // Set ILayoutItem parameters for backwards compatibility
7838 scale.fullWidth = scale.options.fullWidth;
7839 scale.position = scale.options.position;
7840 scale.weight = scale.options.weight;
7841 Chart.layoutService.addBox(chart, scale);
7842 });
7843 }
7844 };
7845};
7846
7847},{"25":25,"45":45}],34:[function(require,module,exports){
7848'use strict';
7849
7850var helpers = require(45);
7851
7852/**
7853 * Namespace to hold static tick generation functions
7854 * @namespace Chart.Ticks
7855 */
7856module.exports = {
7857 /**
7858 * Namespace to hold generators for different types of ticks
7859 * @namespace Chart.Ticks.generators
7860 */
7861 generators: {
7862 /**
7863 * Interface for the options provided to the numeric tick generator
7864 * @interface INumericTickGenerationOptions
7865 */
7866 /**
7867 * The maximum number of ticks to display
7868 * @name INumericTickGenerationOptions#maxTicks
7869 * @type Number
7870 */
7871 /**
7872 * The distance between each tick.
7873 * @name INumericTickGenerationOptions#stepSize
7874 * @type Number
7875 * @optional
7876 */
7877 /**
7878 * Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum
7879 * @name INumericTickGenerationOptions#min
7880 * @type Number
7881 * @optional
7882 */
7883 /**
7884 * The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum
7885 * @name INumericTickGenerationOptions#max
7886 * @type Number
7887 * @optional
7888 */
7889
7890 /**
7891 * Generate a set of linear ticks
7892 * @method Chart.Ticks.generators.linear
7893 * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7894 * @param dataRange {IRange} the range of the data
7895 * @returns {Array<Number>} array of tick values
7896 */
7897 linear: function(generationOptions, dataRange) {
7898 var ticks = [];
7899 // To get a "nice" value for the tick spacing, we will use the appropriately named
7900 // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
7901 // for details.
7902
7903 var spacing;
7904 if (generationOptions.stepSize && generationOptions.stepSize > 0) {
7905 spacing = generationOptions.stepSize;
7906 } else {
7907 var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
7908 spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
7909 }
7910 var niceMin = Math.floor(dataRange.min / spacing) * spacing;
7911 var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
7912
7913 // If min, max and stepSize is set and they make an evenly spaced scale use it.
7914 if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
7915 // If very close to our whole number, use it.
7916 if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
7917 niceMin = generationOptions.min;
7918 niceMax = generationOptions.max;
7919 }
7920 }
7921
7922 var numSpaces = (niceMax - niceMin) / spacing;
7923 // If very close to our rounded value, use it.
7924 if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
7925 numSpaces = Math.round(numSpaces);
7926 } else {
7927 numSpaces = Math.ceil(numSpaces);
7928 }
7929
7930 // Put the values into the ticks array
7931 ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
7932 for (var j = 1; j < numSpaces; ++j) {
7933 ticks.push(niceMin + (j * spacing));
7934 }
7935 ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
7936
7937 return ticks;
7938 },
7939
7940 /**
7941 * Generate a set of logarithmic ticks
7942 * @method Chart.Ticks.generators.logarithmic
7943 * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7944 * @param dataRange {IRange} the range of the data
7945 * @returns {Array<Number>} array of tick values
7946 */
7947 logarithmic: function(generationOptions, dataRange) {
7948 var ticks = [];
7949 var valueOrDefault = helpers.valueOrDefault;
7950
7951 // Figure out what the max number of ticks we can support it is based on the size of
7952 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
7953 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
7954 // the graph
7955 var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
7956
7957 var endExp = Math.floor(helpers.log10(dataRange.max));
7958 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
7959 var exp, significand;
7960
7961 if (tickVal === 0) {
7962 exp = Math.floor(helpers.log10(dataRange.minNotZero));
7963 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
7964
7965 ticks.push(tickVal);
7966 tickVal = significand * Math.pow(10, exp);
7967 } else {
7968 exp = Math.floor(helpers.log10(tickVal));
7969 significand = Math.floor(tickVal / Math.pow(10, exp));
7970 }
7971
7972 do {
7973 ticks.push(tickVal);
7974
7975 ++significand;
7976 if (significand === 10) {
7977 significand = 1;
7978 ++exp;
7979 }
7980
7981 tickVal = significand * Math.pow(10, exp);
7982 } while (exp < endExp || (exp === endExp && significand < endSignificand));
7983
7984 var lastTick = valueOrDefault(generationOptions.max, tickVal);
7985 ticks.push(lastTick);
7986
7987 return ticks;
7988 }
7989 },
7990
7991 /**
7992 * Namespace to hold formatters for different types of ticks
7993 * @namespace Chart.Ticks.formatters
7994 */
7995 formatters: {
7996 /**
7997 * Formatter for value labels
7998 * @method Chart.Ticks.formatters.values
7999 * @param value the value to display
8000 * @return {String|Array} the label to display
8001 */
8002 values: function(value) {
8003 return helpers.isArray(value) ? value : '' + value;
8004 },
8005
8006 /**
8007 * Formatter for linear numeric ticks
8008 * @method Chart.Ticks.formatters.linear
8009 * @param tickValue {Number} the value to be formatted
8010 * @param index {Number} the position of the tickValue parameter in the ticks array
8011 * @param ticks {Array<Number>} the list of ticks being converted
8012 * @return {String} string representation of the tickValue parameter
8013 */
8014 linear: function(tickValue, index, ticks) {
8015 // If we have lots of ticks, don't use the ones
8016 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
8017
8018 // If we have a number like 2.5 as the delta, figure out how many decimal places we need
8019 if (Math.abs(delta) > 1) {
8020 if (tickValue !== Math.floor(tickValue)) {
8021 // not an integer
8022 delta = tickValue - Math.floor(tickValue);
8023 }
8024 }
8025
8026 var logDelta = helpers.log10(Math.abs(delta));
8027 var tickString = '';
8028
8029 if (tickValue !== 0) {
8030 var numDecimal = -1 * Math.floor(logDelta);
8031 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
8032 tickString = tickValue.toFixed(numDecimal);
8033 } else {
8034 tickString = '0'; // never show decimal places for 0
8035 }
8036
8037 return tickString;
8038 },
8039
8040 logarithmic: function(tickValue, index, ticks) {
8041 var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));
8042
8043 if (tickValue === 0) {
8044 return '0';
8045 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
8046 return tickValue.toExponential();
8047 }
8048 return '';
8049 }
8050 }
8051};
8052
8053},{"45":45}],35:[function(require,module,exports){
8054'use strict';
8055
8056var defaults = require(25);
8057var Element = require(26);
8058var helpers = require(45);
8059
8060defaults._set('global', {
8061 tooltips: {
8062 enabled: true,
8063 custom: null,
8064 mode: 'nearest',
8065 position: 'average',
8066 intersect: true,
8067 backgroundColor: 'rgba(0,0,0,0.8)',
8068 titleFontStyle: 'bold',
8069 titleSpacing: 2,
8070 titleMarginBottom: 6,
8071 titleFontColor: '#fff',
8072 titleAlign: 'left',
8073 bodySpacing: 2,
8074 bodyFontColor: '#fff',
8075 bodyAlign: 'left',
8076 footerFontStyle: 'bold',
8077 footerSpacing: 2,
8078 footerMarginTop: 6,
8079 footerFontColor: '#fff',
8080 footerAlign: 'left',
8081 yPadding: 6,
8082 xPadding: 6,
8083 caretPadding: 2,
8084 caretSize: 5,
8085 cornerRadius: 6,
8086 multiKeyBackground: '#fff',
8087 displayColors: true,
8088 borderColor: 'rgba(0,0,0,0)',
8089 borderWidth: 0,
8090 callbacks: {
8091 // Args are: (tooltipItems, data)
8092 beforeTitle: helpers.noop,
8093 title: function(tooltipItems, data) {
8094 // Pick first xLabel for now
8095 var title = '';
8096 var labels = data.labels;
8097 var labelCount = labels ? labels.length : 0;
8098
8099 if (tooltipItems.length > 0) {
8100 var item = tooltipItems[0];
8101
8102 if (item.xLabel) {
8103 title = item.xLabel;
8104 } else if (labelCount > 0 && item.index < labelCount) {
8105 title = labels[item.index];
8106 }
8107 }
8108
8109 return title;
8110 },
8111 afterTitle: helpers.noop,
8112
8113 // Args are: (tooltipItems, data)
8114 beforeBody: helpers.noop,
8115
8116 // Args are: (tooltipItem, data)
8117 beforeLabel: helpers.noop,
8118 label: function(tooltipItem, data) {
8119 var label = data.datasets[tooltipItem.datasetIndex].label || '';
8120
8121 if (label) {
8122 label += ': ';
8123 }
8124 label += tooltipItem.yLabel;
8125 return label;
8126 },
8127 labelColor: function(tooltipItem, chart) {
8128 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
8129 var activeElement = meta.data[tooltipItem.index];
8130 var view = activeElement._view;
8131 return {
8132 borderColor: view.borderColor,
8133 backgroundColor: view.backgroundColor
8134 };
8135 },
8136 labelTextColor: function() {
8137 return this._options.bodyFontColor;
8138 },
8139 afterLabel: helpers.noop,
8140
8141 // Args are: (tooltipItems, data)
8142 afterBody: helpers.noop,
8143
8144 // Args are: (tooltipItems, data)
8145 beforeFooter: helpers.noop,
8146 footer: helpers.noop,
8147 afterFooter: helpers.noop
8148 }
8149 }
8150});
8151
8152module.exports = function(Chart) {
8153
8154 /**
8155 * Helper method to merge the opacity into a color
8156 */
8157 function mergeOpacity(colorString, opacity) {
8158 var color = helpers.color(colorString);
8159 return color.alpha(opacity * color.alpha()).rgbaString();
8160 }
8161
8162 // Helper to push or concat based on if the 2nd parameter is an array or not
8163 function pushOrConcat(base, toPush) {
8164 if (toPush) {
8165 if (helpers.isArray(toPush)) {
8166 // base = base.concat(toPush);
8167 Array.prototype.push.apply(base, toPush);
8168 } else {
8169 base.push(toPush);
8170 }
8171 }
8172
8173 return base;
8174 }
8175
8176 // Private helper to create a tooltip item model
8177 // @param element : the chart element (point, arc, bar) to create the tooltip item for
8178 // @return : new tooltip item
8179 function createTooltipItem(element) {
8180 var xScale = element._xScale;
8181 var yScale = element._yScale || element._scale; // handle radar || polarArea charts
8182 var index = element._index;
8183 var datasetIndex = element._datasetIndex;
8184
8185 return {
8186 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
8187 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
8188 index: index,
8189 datasetIndex: datasetIndex,
8190 x: element._model.x,
8191 y: element._model.y
8192 };
8193 }
8194
8195 /**
8196 * Helper to get the reset model for the tooltip
8197 * @param tooltipOpts {Object} the tooltip options
8198 */
8199 function getBaseModel(tooltipOpts) {
8200 var globalDefaults = defaults.global;
8201 var valueOrDefault = helpers.valueOrDefault;
8202
8203 return {
8204 // Positioning
8205 xPadding: tooltipOpts.xPadding,
8206 yPadding: tooltipOpts.yPadding,
8207 xAlign: tooltipOpts.xAlign,
8208 yAlign: tooltipOpts.yAlign,
8209
8210 // Body
8211 bodyFontColor: tooltipOpts.bodyFontColor,
8212 _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
8213 _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
8214 _bodyAlign: tooltipOpts.bodyAlign,
8215 bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
8216 bodySpacing: tooltipOpts.bodySpacing,
8217
8218 // Title
8219 titleFontColor: tooltipOpts.titleFontColor,
8220 _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
8221 _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
8222 titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
8223 _titleAlign: tooltipOpts.titleAlign,
8224 titleSpacing: tooltipOpts.titleSpacing,
8225 titleMarginBottom: tooltipOpts.titleMarginBottom,
8226
8227 // Footer
8228 footerFontColor: tooltipOpts.footerFontColor,
8229 _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
8230 _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
8231 footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
8232 _footerAlign: tooltipOpts.footerAlign,
8233 footerSpacing: tooltipOpts.footerSpacing,
8234 footerMarginTop: tooltipOpts.footerMarginTop,
8235
8236 // Appearance
8237 caretSize: tooltipOpts.caretSize,
8238 cornerRadius: tooltipOpts.cornerRadius,
8239 backgroundColor: tooltipOpts.backgroundColor,
8240 opacity: 0,
8241 legendColorBackground: tooltipOpts.multiKeyBackground,
8242 displayColors: tooltipOpts.displayColors,
8243 borderColor: tooltipOpts.borderColor,
8244 borderWidth: tooltipOpts.borderWidth
8245 };
8246 }
8247
8248 /**
8249 * Get the size of the tooltip
8250 */
8251 function getTooltipSize(tooltip, model) {
8252 var ctx = tooltip._chart.ctx;
8253
8254 var height = model.yPadding * 2; // Tooltip Padding
8255 var width = 0;
8256
8257 // Count of all lines in the body
8258 var body = model.body;
8259 var combinedBodyLength = body.reduce(function(count, bodyItem) {
8260 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
8261 }, 0);
8262 combinedBodyLength += model.beforeBody.length + model.afterBody.length;
8263
8264 var titleLineCount = model.title.length;
8265 var footerLineCount = model.footer.length;
8266 var titleFontSize = model.titleFontSize;
8267 var bodyFontSize = model.bodyFontSize;
8268 var footerFontSize = model.footerFontSize;
8269
8270 height += titleLineCount * titleFontSize; // Title Lines
8271 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
8272 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
8273 height += combinedBodyLength * bodyFontSize; // Body Lines
8274 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
8275 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
8276 height += footerLineCount * (footerFontSize); // Footer Lines
8277 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
8278
8279 // Title width
8280 var widthPadding = 0;
8281 var maxLineWidth = function(line) {
8282 width = Math.max(width, ctx.measureText(line).width + widthPadding);
8283 };
8284
8285 ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
8286 helpers.each(model.title, maxLineWidth);
8287
8288 // Body width
8289 ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
8290 helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
8291
8292 // Body lines may include some extra width due to the color box
8293 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
8294 helpers.each(body, function(bodyItem) {
8295 helpers.each(bodyItem.before, maxLineWidth);
8296 helpers.each(bodyItem.lines, maxLineWidth);
8297 helpers.each(bodyItem.after, maxLineWidth);
8298 });
8299
8300 // Reset back to 0
8301 widthPadding = 0;
8302
8303 // Footer width
8304 ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
8305 helpers.each(model.footer, maxLineWidth);
8306
8307 // Add padding
8308 width += 2 * model.xPadding;
8309
8310 return {
8311 width: width,
8312 height: height
8313 };
8314 }
8315
8316 /**
8317 * Helper to get the alignment of a tooltip given the size
8318 */
8319 function determineAlignment(tooltip, size) {
8320 var model = tooltip._model;
8321 var chart = tooltip._chart;
8322 var chartArea = tooltip._chart.chartArea;
8323 var xAlign = 'center';
8324 var yAlign = 'center';
8325
8326 if (model.y < size.height) {
8327 yAlign = 'top';
8328 } else if (model.y > (chart.height - size.height)) {
8329 yAlign = 'bottom';
8330 }
8331
8332 var lf, rf; // functions to determine left, right alignment
8333 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
8334 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
8335 var midX = (chartArea.left + chartArea.right) / 2;
8336 var midY = (chartArea.top + chartArea.bottom) / 2;
8337
8338 if (yAlign === 'center') {
8339 lf = function(x) {
8340 return x <= midX;
8341 };
8342 rf = function(x) {
8343 return x > midX;
8344 };
8345 } else {
8346 lf = function(x) {
8347 return x <= (size.width / 2);
8348 };
8349 rf = function(x) {
8350 return x >= (chart.width - (size.width / 2));
8351 };
8352 }
8353
8354 olf = function(x) {
8355 return x + size.width > chart.width;
8356 };
8357 orf = function(x) {
8358 return x - size.width < 0;
8359 };
8360 yf = function(y) {
8361 return y <= midY ? 'top' : 'bottom';
8362 };
8363
8364 if (lf(model.x)) {
8365 xAlign = 'left';
8366
8367 // Is tooltip too wide and goes over the right side of the chart.?
8368 if (olf(model.x)) {
8369 xAlign = 'center';
8370 yAlign = yf(model.y);
8371 }
8372 } else if (rf(model.x)) {
8373 xAlign = 'right';
8374
8375 // Is tooltip too wide and goes outside left edge of canvas?
8376 if (orf(model.x)) {
8377 xAlign = 'center';
8378 yAlign = yf(model.y);
8379 }
8380 }
8381
8382 var opts = tooltip._options;
8383 return {
8384 xAlign: opts.xAlign ? opts.xAlign : xAlign,
8385 yAlign: opts.yAlign ? opts.yAlign : yAlign
8386 };
8387 }
8388
8389 /**
8390 * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
8391 */
8392 function getBackgroundPoint(vm, size, alignment) {
8393 // Background Position
8394 var x = vm.x;
8395 var y = vm.y;
8396
8397 var caretSize = vm.caretSize;
8398 var caretPadding = vm.caretPadding;
8399 var cornerRadius = vm.cornerRadius;
8400 var xAlign = alignment.xAlign;
8401 var yAlign = alignment.yAlign;
8402 var paddingAndSize = caretSize + caretPadding;
8403 var radiusAndPadding = cornerRadius + caretPadding;
8404
8405 if (xAlign === 'right') {
8406 x -= size.width;
8407 } else if (xAlign === 'center') {
8408 x -= (size.width / 2);
8409 }
8410
8411 if (yAlign === 'top') {
8412 y += paddingAndSize;
8413 } else if (yAlign === 'bottom') {
8414 y -= size.height + paddingAndSize;
8415 } else {
8416 y -= (size.height / 2);
8417 }
8418
8419 if (yAlign === 'center') {
8420 if (xAlign === 'left') {
8421 x += paddingAndSize;
8422 } else if (xAlign === 'right') {
8423 x -= paddingAndSize;
8424 }
8425 } else if (xAlign === 'left') {
8426 x -= radiusAndPadding;
8427 } else if (xAlign === 'right') {
8428 x += radiusAndPadding;
8429 }
8430
8431 return {
8432 x: x,
8433 y: y
8434 };
8435 }
8436
8437 Chart.Tooltip = Element.extend({
8438 initialize: function() {
8439 this._model = getBaseModel(this._options);
8440 this._lastActive = [];
8441 },
8442
8443 // Get the title
8444 // Args are: (tooltipItem, data)
8445 getTitle: function() {
8446 var me = this;
8447 var opts = me._options;
8448 var callbacks = opts.callbacks;
8449
8450 var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
8451 var title = callbacks.title.apply(me, arguments);
8452 var afterTitle = callbacks.afterTitle.apply(me, arguments);
8453
8454 var lines = [];
8455 lines = pushOrConcat(lines, beforeTitle);
8456 lines = pushOrConcat(lines, title);
8457 lines = pushOrConcat(lines, afterTitle);
8458
8459 return lines;
8460 },
8461
8462 // Args are: (tooltipItem, data)
8463 getBeforeBody: function() {
8464 var lines = this._options.callbacks.beforeBody.apply(this, arguments);
8465 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8466 },
8467
8468 // Args are: (tooltipItem, data)
8469 getBody: function(tooltipItems, data) {
8470 var me = this;
8471 var callbacks = me._options.callbacks;
8472 var bodyItems = [];
8473
8474 helpers.each(tooltipItems, function(tooltipItem) {
8475 var bodyItem = {
8476 before: [],
8477 lines: [],
8478 after: []
8479 };
8480 pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
8481 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
8482 pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
8483
8484 bodyItems.push(bodyItem);
8485 });
8486
8487 return bodyItems;
8488 },
8489
8490 // Args are: (tooltipItem, data)
8491 getAfterBody: function() {
8492 var lines = this._options.callbacks.afterBody.apply(this, arguments);
8493 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8494 },
8495
8496 // Get the footer and beforeFooter and afterFooter lines
8497 // Args are: (tooltipItem, data)
8498 getFooter: function() {
8499 var me = this;
8500 var callbacks = me._options.callbacks;
8501
8502 var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
8503 var footer = callbacks.footer.apply(me, arguments);
8504 var afterFooter = callbacks.afterFooter.apply(me, arguments);
8505
8506 var lines = [];
8507 lines = pushOrConcat(lines, beforeFooter);
8508 lines = pushOrConcat(lines, footer);
8509 lines = pushOrConcat(lines, afterFooter);
8510
8511 return lines;
8512 },
8513
8514 update: function(changed) {
8515 var me = this;
8516 var opts = me._options;
8517
8518 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
8519 // 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
8520 // which breaks any animations.
8521 var existingModel = me._model;
8522 var model = me._model = getBaseModel(opts);
8523 var active = me._active;
8524
8525 var data = me._data;
8526
8527 // In the case where active.length === 0 we need to keep these at existing values for good animations
8528 var alignment = {
8529 xAlign: existingModel.xAlign,
8530 yAlign: existingModel.yAlign
8531 };
8532 var backgroundPoint = {
8533 x: existingModel.x,
8534 y: existingModel.y
8535 };
8536 var tooltipSize = {
8537 width: existingModel.width,
8538 height: existingModel.height
8539 };
8540 var tooltipPosition = {
8541 x: existingModel.caretX,
8542 y: existingModel.caretY
8543 };
8544
8545 var i, len;
8546
8547 if (active.length) {
8548 model.opacity = 1;
8549
8550 var labelColors = [];
8551 var labelTextColors = [];
8552 tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition);
8553
8554 var tooltipItems = [];
8555 for (i = 0, len = active.length; i < len; ++i) {
8556 tooltipItems.push(createTooltipItem(active[i]));
8557 }
8558
8559 // If the user provided a filter function, use it to modify the tooltip items
8560 if (opts.filter) {
8561 tooltipItems = tooltipItems.filter(function(a) {
8562 return opts.filter(a, data);
8563 });
8564 }
8565
8566 // If the user provided a sorting function, use it to modify the tooltip items
8567 if (opts.itemSort) {
8568 tooltipItems = tooltipItems.sort(function(a, b) {
8569 return opts.itemSort(a, b, data);
8570 });
8571 }
8572
8573 // Determine colors for boxes
8574 helpers.each(tooltipItems, function(tooltipItem) {
8575 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
8576 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
8577 });
8578
8579
8580 // Build the Text Lines
8581 model.title = me.getTitle(tooltipItems, data);
8582 model.beforeBody = me.getBeforeBody(tooltipItems, data);
8583 model.body = me.getBody(tooltipItems, data);
8584 model.afterBody = me.getAfterBody(tooltipItems, data);
8585 model.footer = me.getFooter(tooltipItems, data);
8586
8587 // Initial positioning and colors
8588 model.x = Math.round(tooltipPosition.x);
8589 model.y = Math.round(tooltipPosition.y);
8590 model.caretPadding = opts.caretPadding;
8591 model.labelColors = labelColors;
8592 model.labelTextColors = labelTextColors;
8593
8594 // data points
8595 model.dataPoints = tooltipItems;
8596
8597 // We need to determine alignment of the tooltip
8598 tooltipSize = getTooltipSize(this, model);
8599 alignment = determineAlignment(this, tooltipSize);
8600 // Final Size and Position
8601 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment);
8602 } else {
8603 model.opacity = 0;
8604 }
8605
8606 model.xAlign = alignment.xAlign;
8607 model.yAlign = alignment.yAlign;
8608 model.x = backgroundPoint.x;
8609 model.y = backgroundPoint.y;
8610 model.width = tooltipSize.width;
8611 model.height = tooltipSize.height;
8612
8613 // Point where the caret on the tooltip points to
8614 model.caretX = tooltipPosition.x;
8615 model.caretY = tooltipPosition.y;
8616
8617 me._model = model;
8618
8619 if (changed && opts.custom) {
8620 opts.custom.call(me, model);
8621 }
8622
8623 return me;
8624 },
8625 drawCaret: function(tooltipPoint, size) {
8626 var ctx = this._chart.ctx;
8627 var vm = this._view;
8628 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
8629
8630 ctx.lineTo(caretPosition.x1, caretPosition.y1);
8631 ctx.lineTo(caretPosition.x2, caretPosition.y2);
8632 ctx.lineTo(caretPosition.x3, caretPosition.y3);
8633 },
8634 getCaretPosition: function(tooltipPoint, size, vm) {
8635 var x1, x2, x3, y1, y2, y3;
8636 var caretSize = vm.caretSize;
8637 var cornerRadius = vm.cornerRadius;
8638 var xAlign = vm.xAlign;
8639 var yAlign = vm.yAlign;
8640 var ptX = tooltipPoint.x;
8641 var ptY = tooltipPoint.y;
8642 var width = size.width;
8643 var height = size.height;
8644
8645 if (yAlign === 'center') {
8646 y2 = ptY + (height / 2);
8647
8648 if (xAlign === 'left') {
8649 x1 = ptX;
8650 x2 = x1 - caretSize;
8651 x3 = x1;
8652
8653 y1 = y2 + caretSize;
8654 y3 = y2 - caretSize;
8655 } else {
8656 x1 = ptX + width;
8657 x2 = x1 + caretSize;
8658 x3 = x1;
8659
8660 y1 = y2 - caretSize;
8661 y3 = y2 + caretSize;
8662 }
8663 } else {
8664 if (xAlign === 'left') {
8665 x2 = ptX + cornerRadius + (caretSize);
8666 x1 = x2 - caretSize;
8667 x3 = x2 + caretSize;
8668 } else if (xAlign === 'right') {
8669 x2 = ptX + width - cornerRadius - caretSize;
8670 x1 = x2 - caretSize;
8671 x3 = x2 + caretSize;
8672 } else {
8673 x2 = ptX + (width / 2);
8674 x1 = x2 - caretSize;
8675 x3 = x2 + caretSize;
8676 }
8677 if (yAlign === 'top') {
8678 y1 = ptY;
8679 y2 = y1 - caretSize;
8680 y3 = y1;
8681 } else {
8682 y1 = ptY + height;
8683 y2 = y1 + caretSize;
8684 y3 = y1;
8685 // invert drawing order
8686 var tmp = x3;
8687 x3 = x1;
8688 x1 = tmp;
8689 }
8690 }
8691 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
8692 },
8693 drawTitle: function(pt, vm, ctx, opacity) {
8694 var title = vm.title;
8695
8696 if (title.length) {
8697 ctx.textAlign = vm._titleAlign;
8698 ctx.textBaseline = 'top';
8699
8700 var titleFontSize = vm.titleFontSize;
8701 var titleSpacing = vm.titleSpacing;
8702
8703 ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
8704 ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8705
8706 var i, len;
8707 for (i = 0, len = title.length; i < len; ++i) {
8708 ctx.fillText(title[i], pt.x, pt.y);
8709 pt.y += titleFontSize + titleSpacing; // Line Height and spacing
8710
8711 if (i + 1 === title.length) {
8712 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
8713 }
8714 }
8715 }
8716 },
8717 drawBody: function(pt, vm, ctx, opacity) {
8718 var bodyFontSize = vm.bodyFontSize;
8719 var bodySpacing = vm.bodySpacing;
8720 var body = vm.body;
8721
8722 ctx.textAlign = vm._bodyAlign;
8723 ctx.textBaseline = 'top';
8724 ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8725
8726 // Before Body
8727 var xLinePadding = 0;
8728 var fillLineOfText = function(line) {
8729 ctx.fillText(line, pt.x + xLinePadding, pt.y);
8730 pt.y += bodyFontSize + bodySpacing;
8731 };
8732
8733 // Before body lines
8734 ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
8735 helpers.each(vm.beforeBody, fillLineOfText);
8736
8737 var drawColorBoxes = vm.displayColors;
8738 xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
8739
8740 // Draw body lines now
8741 helpers.each(body, function(bodyItem, i) {
8742 var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
8743 ctx.fillStyle = textColor;
8744 helpers.each(bodyItem.before, fillLineOfText);
8745
8746 helpers.each(bodyItem.lines, function(line) {
8747 // Draw Legend-like boxes if needed
8748 if (drawColorBoxes) {
8749 // Fill a white rect so that colours merge nicely if the opacity is < 1
8750 ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
8751 ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8752
8753 // Border
8754 ctx.lineWidth = 1;
8755 ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
8756 ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8757
8758 // Inner square
8759 ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
8760 ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
8761 ctx.fillStyle = textColor;
8762 }
8763
8764 fillLineOfText(line);
8765 });
8766
8767 helpers.each(bodyItem.after, fillLineOfText);
8768 });
8769
8770 // Reset back to 0 for after body
8771 xLinePadding = 0;
8772
8773 // After body lines
8774 helpers.each(vm.afterBody, fillLineOfText);
8775 pt.y -= bodySpacing; // Remove last body spacing
8776 },
8777 drawFooter: function(pt, vm, ctx, opacity) {
8778 var footer = vm.footer;
8779
8780 if (footer.length) {
8781 pt.y += vm.footerMarginTop;
8782
8783 ctx.textAlign = vm._footerAlign;
8784 ctx.textBaseline = 'top';
8785
8786 ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
8787 ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8788
8789 helpers.each(footer, function(line) {
8790 ctx.fillText(line, pt.x, pt.y);
8791 pt.y += vm.footerFontSize + vm.footerSpacing;
8792 });
8793 }
8794 },
8795 drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
8796 ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
8797 ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
8798 ctx.lineWidth = vm.borderWidth;
8799 var xAlign = vm.xAlign;
8800 var yAlign = vm.yAlign;
8801 var x = pt.x;
8802 var y = pt.y;
8803 var width = tooltipSize.width;
8804 var height = tooltipSize.height;
8805 var radius = vm.cornerRadius;
8806
8807 ctx.beginPath();
8808 ctx.moveTo(x + radius, y);
8809 if (yAlign === 'top') {
8810 this.drawCaret(pt, tooltipSize);
8811 }
8812 ctx.lineTo(x + width - radius, y);
8813 ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
8814 if (yAlign === 'center' && xAlign === 'right') {
8815 this.drawCaret(pt, tooltipSize);
8816 }
8817 ctx.lineTo(x + width, y + height - radius);
8818 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
8819 if (yAlign === 'bottom') {
8820 this.drawCaret(pt, tooltipSize);
8821 }
8822 ctx.lineTo(x + radius, y + height);
8823 ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
8824 if (yAlign === 'center' && xAlign === 'left') {
8825 this.drawCaret(pt, tooltipSize);
8826 }
8827 ctx.lineTo(x, y + radius);
8828 ctx.quadraticCurveTo(x, y, x + radius, y);
8829 ctx.closePath();
8830
8831 ctx.fill();
8832
8833 if (vm.borderWidth > 0) {
8834 ctx.stroke();
8835 }
8836 },
8837 draw: function() {
8838 var ctx = this._chart.ctx;
8839 var vm = this._view;
8840
8841 if (vm.opacity === 0) {
8842 return;
8843 }
8844
8845 var tooltipSize = {
8846 width: vm.width,
8847 height: vm.height
8848 };
8849 var pt = {
8850 x: vm.x,
8851 y: vm.y
8852 };
8853
8854 // IE11/Edge does not like very small opacities, so snap to 0
8855 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
8856
8857 // Truthy/falsey value for empty tooltip
8858 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
8859
8860 if (this._options.enabled && hasTooltipContent) {
8861 // Draw Background
8862 this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
8863
8864 // Draw Title, Body, and Footer
8865 pt.x += vm.xPadding;
8866 pt.y += vm.yPadding;
8867
8868 // Titles
8869 this.drawTitle(pt, vm, ctx, opacity);
8870
8871 // Body
8872 this.drawBody(pt, vm, ctx, opacity);
8873
8874 // Footer
8875 this.drawFooter(pt, vm, ctx, opacity);
8876 }
8877 },
8878
8879 /**
8880 * Handle an event
8881 * @private
8882 * @param {IEvent} event - The event to handle
8883 * @returns {Boolean} true if the tooltip changed
8884 */
8885 handleEvent: function(e) {
8886 var me = this;
8887 var options = me._options;
8888 var changed = false;
8889
8890 me._lastActive = me._lastActive || [];
8891
8892 // Find Active Elements for tooltips
8893 if (e.type === 'mouseout') {
8894 me._active = [];
8895 } else {
8896 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
8897 }
8898
8899 // Remember Last Actives
8900 changed = !helpers.arrayEquals(me._active, me._lastActive);
8901
8902 // If tooltip didn't change, do not handle the target event
8903 if (!changed) {
8904 return false;
8905 }
8906
8907 me._lastActive = me._active;
8908
8909 if (options.enabled || options.custom) {
8910 me._eventPosition = {
8911 x: e.x,
8912 y: e.y
8913 };
8914
8915 var model = me._model;
8916 me.update(true);
8917 me.pivot();
8918
8919 // See if our tooltip position changed
8920 changed |= (model.x !== me._model.x) || (model.y !== me._model.y);
8921 }
8922
8923 return changed;
8924 }
8925 });
8926
8927 /**
8928 * @namespace Chart.Tooltip.positioners
8929 */
8930 Chart.Tooltip.positioners = {
8931 /**
8932 * Average mode places the tooltip at the average position of the elements shown
8933 * @function Chart.Tooltip.positioners.average
8934 * @param elements {ChartElement[]} the elements being displayed in the tooltip
8935 * @returns {Point} tooltip position
8936 */
8937 average: function(elements) {
8938 if (!elements.length) {
8939 return false;
8940 }
8941
8942 var i, len;
8943 var x = 0;
8944 var y = 0;
8945 var count = 0;
8946
8947 for (i = 0, len = elements.length; i < len; ++i) {
8948 var el = elements[i];
8949 if (el && el.hasValue()) {
8950 var pos = el.tooltipPosition();
8951 x += pos.x;
8952 y += pos.y;
8953 ++count;
8954 }
8955 }
8956
8957 return {
8958 x: Math.round(x / count),
8959 y: Math.round(y / count)
8960 };
8961 },
8962
8963 /**
8964 * Gets the tooltip position nearest of the item nearest to the event position
8965 * @function Chart.Tooltip.positioners.nearest
8966 * @param elements {Chart.Element[]} the tooltip elements
8967 * @param eventPosition {Point} the position of the event in canvas coordinates
8968 * @returns {Point} the tooltip position
8969 */
8970 nearest: function(elements, eventPosition) {
8971 var x = eventPosition.x;
8972 var y = eventPosition.y;
8973 var minDistance = Number.POSITIVE_INFINITY;
8974 var i, len, nearestElement;
8975
8976 for (i = 0, len = elements.length; i < len; ++i) {
8977 var el = elements[i];
8978 if (el && el.hasValue()) {
8979 var center = el.getCenterPoint();
8980 var d = helpers.distanceBetweenPoints(eventPosition, center);
8981
8982 if (d < minDistance) {
8983 minDistance = d;
8984 nearestElement = el;
8985 }
8986 }
8987 }
8988
8989 if (nearestElement) {
8990 var tp = nearestElement.tooltipPosition();
8991 x = tp.x;
8992 y = tp.y;
8993 }
8994
8995 return {
8996 x: x,
8997 y: y
8998 };
8999 }
9000 };
9001};
9002
9003},{"25":25,"26":26,"45":45}],36:[function(require,module,exports){
9004'use strict';
9005
9006var defaults = require(25);
9007var Element = require(26);
9008var helpers = require(45);
9009
9010defaults._set('global', {
9011 elements: {
9012 arc: {
9013 backgroundColor: defaults.global.defaultColor,
9014 borderColor: '#fff',
9015 borderWidth: 2
9016 }
9017 }
9018});
9019
9020module.exports = Element.extend({
9021 inLabelRange: function(mouseX) {
9022 var vm = this._view;
9023
9024 if (vm) {
9025 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
9026 }
9027 return false;
9028 },
9029
9030 inRange: function(chartX, chartY) {
9031 var vm = this._view;
9032
9033 if (vm) {
9034 var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
9035 var angle = pointRelativePosition.angle;
9036 var distance = pointRelativePosition.distance;
9037
9038 // Sanitise angle range
9039 var startAngle = vm.startAngle;
9040 var endAngle = vm.endAngle;
9041 while (endAngle < startAngle) {
9042 endAngle += 2.0 * Math.PI;
9043 }
9044 while (angle > endAngle) {
9045 angle -= 2.0 * Math.PI;
9046 }
9047 while (angle < startAngle) {
9048 angle += 2.0 * Math.PI;
9049 }
9050
9051 // Check if within the range of the open/close angle
9052 var betweenAngles = (angle >= startAngle && angle <= endAngle);
9053 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
9054
9055 return (betweenAngles && withinRadius);
9056 }
9057 return false;
9058 },
9059
9060 getCenterPoint: function() {
9061 var vm = this._view;
9062 var halfAngle = (vm.startAngle + vm.endAngle) / 2;
9063 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
9064 return {
9065 x: vm.x + Math.cos(halfAngle) * halfRadius,
9066 y: vm.y + Math.sin(halfAngle) * halfRadius
9067 };
9068 },
9069
9070 getArea: function() {
9071 var vm = this._view;
9072 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
9073 },
9074
9075 tooltipPosition: function() {
9076 var vm = this._view;
9077 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
9078 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
9079
9080 return {
9081 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
9082 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
9083 };
9084 },
9085
9086 draw: function() {
9087 var ctx = this._chart.ctx;
9088 var vm = this._view;
9089 var sA = vm.startAngle;
9090 var eA = vm.endAngle;
9091
9092 ctx.beginPath();
9093
9094 ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
9095 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
9096
9097 ctx.closePath();
9098 ctx.strokeStyle = vm.borderColor;
9099 ctx.lineWidth = vm.borderWidth;
9100
9101 ctx.fillStyle = vm.backgroundColor;
9102
9103 ctx.fill();
9104 ctx.lineJoin = 'bevel';
9105
9106 if (vm.borderWidth) {
9107 ctx.stroke();
9108 }
9109 }
9110});
9111
9112},{"25":25,"26":26,"45":45}],37:[function(require,module,exports){
9113'use strict';
9114
9115var defaults = require(25);
9116var Element = require(26);
9117var helpers = require(45);
9118
9119var globalDefaults = defaults.global;
9120
9121defaults._set('global', {
9122 elements: {
9123 line: {
9124 tension: 0.4,
9125 backgroundColor: globalDefaults.defaultColor,
9126 borderWidth: 3,
9127 borderColor: globalDefaults.defaultColor,
9128 borderCapStyle: 'butt',
9129 borderDash: [],
9130 borderDashOffset: 0.0,
9131 borderJoinStyle: 'miter',
9132 capBezierPoints: true,
9133 fill: true, // do we fill in the area between the line and its base axis
9134 }
9135 }
9136});
9137
9138module.exports = Element.extend({
9139 draw: function() {
9140 var me = this;
9141 var vm = me._view;
9142 var ctx = me._chart.ctx;
9143 var spanGaps = vm.spanGaps;
9144 var points = me._children.slice(); // clone array
9145 var globalOptionLineElements = globalDefaults.elements.line;
9146 var lastDrawnIndex = -1;
9147 var index, current, previous, currentVM;
9148
9149 // If we are looping, adding the first point again
9150 if (me._loop && points.length) {
9151 points.push(points[0]);
9152 }
9153
9154 ctx.save();
9155
9156 // Stroke Line Options
9157 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
9158
9159 // IE 9 and 10 do not support line dash
9160 if (ctx.setLineDash) {
9161 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
9162 }
9163
9164 ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
9165 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
9166 ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
9167 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
9168
9169 // Stroke Line
9170 ctx.beginPath();
9171 lastDrawnIndex = -1;
9172
9173 for (index = 0; index < points.length; ++index) {
9174 current = points[index];
9175 previous = helpers.previousItem(points, index);
9176 currentVM = current._view;
9177
9178 // First point moves to it's starting position no matter what
9179 if (index === 0) {
9180 if (!currentVM.skip) {
9181 ctx.moveTo(currentVM.x, currentVM.y);
9182 lastDrawnIndex = index;
9183 }
9184 } else {
9185 previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
9186
9187 if (!currentVM.skip) {
9188 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
9189 // There was a gap and this is the first point after the gap
9190 ctx.moveTo(currentVM.x, currentVM.y);
9191 } else {
9192 // Line to next point
9193 helpers.canvas.lineTo(ctx, previous._view, current._view);
9194 }
9195 lastDrawnIndex = index;
9196 }
9197 }
9198 }
9199
9200 ctx.stroke();
9201 ctx.restore();
9202 }
9203});
9204
9205},{"25":25,"26":26,"45":45}],38:[function(require,module,exports){
9206'use strict';
9207
9208var defaults = require(25);
9209var Element = require(26);
9210var helpers = require(45);
9211
9212var defaultColor = defaults.global.defaultColor;
9213
9214defaults._set('global', {
9215 elements: {
9216 point: {
9217 radius: 3,
9218 pointStyle: 'circle',
9219 backgroundColor: defaultColor,
9220 borderColor: defaultColor,
9221 borderWidth: 1,
9222 // Hover
9223 hitRadius: 1,
9224 hoverRadius: 4,
9225 hoverBorderWidth: 1
9226 }
9227 }
9228});
9229
9230function xRange(mouseX) {
9231 var vm = this._view;
9232 return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9233}
9234
9235function yRange(mouseY) {
9236 var vm = this._view;
9237 return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9238}
9239
9240module.exports = Element.extend({
9241 inRange: function(mouseX, mouseY) {
9242 var vm = this._view;
9243 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
9244 },
9245
9246 inLabelRange: xRange,
9247 inXRange: xRange,
9248 inYRange: yRange,
9249
9250 getCenterPoint: function() {
9251 var vm = this._view;
9252 return {
9253 x: vm.x,
9254 y: vm.y
9255 };
9256 },
9257
9258 getArea: function() {
9259 return Math.PI * Math.pow(this._view.radius, 2);
9260 },
9261
9262 tooltipPosition: function() {
9263 var vm = this._view;
9264 return {
9265 x: vm.x,
9266 y: vm.y,
9267 padding: vm.radius + vm.borderWidth
9268 };
9269 },
9270
9271 draw: function(chartArea) {
9272 var vm = this._view;
9273 var model = this._model;
9274 var ctx = this._chart.ctx;
9275 var pointStyle = vm.pointStyle;
9276 var radius = vm.radius;
9277 var x = vm.x;
9278 var y = vm.y;
9279 var color = helpers.color;
9280 var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
9281 var ratio = 0;
9282
9283 if (vm.skip) {
9284 return;
9285 }
9286
9287 ctx.strokeStyle = vm.borderColor || defaultColor;
9288 ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth);
9289 ctx.fillStyle = vm.backgroundColor || defaultColor;
9290
9291 // Cliping for Points.
9292 // going out from inner charArea?
9293 if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) {
9294 // Point fade out
9295 if (model.x < chartArea.left) {
9296 ratio = (x - model.x) / (chartArea.left - model.x);
9297 } else if (chartArea.right * errMargin < model.x) {
9298 ratio = (model.x - x) / (model.x - chartArea.right);
9299 } else if (model.y < chartArea.top) {
9300 ratio = (y - model.y) / (chartArea.top - model.y);
9301 } else if (chartArea.bottom * errMargin < model.y) {
9302 ratio = (model.y - y) / (model.y - chartArea.bottom);
9303 }
9304 ratio = Math.round(ratio * 100) / 100;
9305 ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();
9306 ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
9307 }
9308
9309 helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y);
9310 }
9311});
9312
9313},{"25":25,"26":26,"45":45}],39:[function(require,module,exports){
9314'use strict';
9315
9316var defaults = require(25);
9317var Element = require(26);
9318
9319defaults._set('global', {
9320 elements: {
9321 rectangle: {
9322 backgroundColor: defaults.global.defaultColor,
9323 borderColor: defaults.global.defaultColor,
9324 borderSkipped: 'bottom',
9325 borderWidth: 0
9326 }
9327 }
9328});
9329
9330function isVertical(bar) {
9331 return bar._view.width !== undefined;
9332}
9333
9334/**
9335 * Helper function to get the bounds of the bar regardless of the orientation
9336 * @param bar {Chart.Element.Rectangle} the bar
9337 * @return {Bounds} bounds of the bar
9338 * @private
9339 */
9340function getBarBounds(bar) {
9341 var vm = bar._view;
9342 var x1, x2, y1, y2;
9343
9344 if (isVertical(bar)) {
9345 // vertical
9346 var halfWidth = vm.width / 2;
9347 x1 = vm.x - halfWidth;
9348 x2 = vm.x + halfWidth;
9349 y1 = Math.min(vm.y, vm.base);
9350 y2 = Math.max(vm.y, vm.base);
9351 } else {
9352 // horizontal bar
9353 var halfHeight = vm.height / 2;
9354 x1 = Math.min(vm.x, vm.base);
9355 x2 = Math.max(vm.x, vm.base);
9356 y1 = vm.y - halfHeight;
9357 y2 = vm.y + halfHeight;
9358 }
9359
9360 return {
9361 left: x1,
9362 top: y1,
9363 right: x2,
9364 bottom: y2
9365 };
9366}
9367
9368module.exports = Element.extend({
9369 draw: function() {
9370 var ctx = this._chart.ctx;
9371 var vm = this._view;
9372 var left, right, top, bottom, signX, signY, borderSkipped;
9373 var borderWidth = vm.borderWidth;
9374
9375 if (!vm.horizontal) {
9376 // bar
9377 left = vm.x - vm.width / 2;
9378 right = vm.x + vm.width / 2;
9379 top = vm.y;
9380 bottom = vm.base;
9381 signX = 1;
9382 signY = bottom > top ? 1 : -1;
9383 borderSkipped = vm.borderSkipped || 'bottom';
9384 } else {
9385 // horizontal bar
9386 left = vm.base;
9387 right = vm.x;
9388 top = vm.y - vm.height / 2;
9389 bottom = vm.y + vm.height / 2;
9390 signX = right > left ? 1 : -1;
9391 signY = 1;
9392 borderSkipped = vm.borderSkipped || 'left';
9393 }
9394
9395 // Canvas doesn't allow us to stroke inside the width so we can
9396 // adjust the sizes to fit if we're setting a stroke on the line
9397 if (borderWidth) {
9398 // borderWidth shold be less than bar width and bar height.
9399 var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
9400 borderWidth = borderWidth > barSize ? barSize : borderWidth;
9401 var halfStroke = borderWidth / 2;
9402 // Adjust borderWidth when bar top position is near vm.base(zero).
9403 var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
9404 var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
9405 var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
9406 var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
9407 // not become a vertical line?
9408 if (borderLeft !== borderRight) {
9409 top = borderTop;
9410 bottom = borderBottom;
9411 }
9412 // not become a horizontal line?
9413 if (borderTop !== borderBottom) {
9414 left = borderLeft;
9415 right = borderRight;
9416 }
9417 }
9418
9419 ctx.beginPath();
9420 ctx.fillStyle = vm.backgroundColor;
9421 ctx.strokeStyle = vm.borderColor;
9422 ctx.lineWidth = borderWidth;
9423
9424 // Corner points, from bottom-left to bottom-right clockwise
9425 // | 1 2 |
9426 // | 0 3 |
9427 var corners = [
9428 [left, bottom],
9429 [left, top],
9430 [right, top],
9431 [right, bottom]
9432 ];
9433
9434 // Find first (starting) corner with fallback to 'bottom'
9435 var borders = ['bottom', 'left', 'top', 'right'];
9436 var startCorner = borders.indexOf(borderSkipped, 0);
9437 if (startCorner === -1) {
9438 startCorner = 0;
9439 }
9440
9441 function cornerAt(index) {
9442 return corners[(startCorner + index) % 4];
9443 }
9444
9445 // Draw rectangle from 'startCorner'
9446 var corner = cornerAt(0);
9447 ctx.moveTo(corner[0], corner[1]);
9448
9449 for (var i = 1; i < 4; i++) {
9450 corner = cornerAt(i);
9451 ctx.lineTo(corner[0], corner[1]);
9452 }
9453
9454 ctx.fill();
9455 if (borderWidth) {
9456 ctx.stroke();
9457 }
9458 },
9459
9460 height: function() {
9461 var vm = this._view;
9462 return vm.base - vm.y;
9463 },
9464
9465 inRange: function(mouseX, mouseY) {
9466 var inRange = false;
9467
9468 if (this._view) {
9469 var bounds = getBarBounds(this);
9470 inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
9471 }
9472
9473 return inRange;
9474 },
9475
9476 inLabelRange: function(mouseX, mouseY) {
9477 var me = this;
9478 if (!me._view) {
9479 return false;
9480 }
9481
9482 var inRange = false;
9483 var bounds = getBarBounds(me);
9484
9485 if (isVertical(me)) {
9486 inRange = mouseX >= bounds.left && mouseX <= bounds.right;
9487 } else {
9488 inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
9489 }
9490
9491 return inRange;
9492 },
9493
9494 inXRange: function(mouseX) {
9495 var bounds = getBarBounds(this);
9496 return mouseX >= bounds.left && mouseX <= bounds.right;
9497 },
9498
9499 inYRange: function(mouseY) {
9500 var bounds = getBarBounds(this);
9501 return mouseY >= bounds.top && mouseY <= bounds.bottom;
9502 },
9503
9504 getCenterPoint: function() {
9505 var vm = this._view;
9506 var x, y;
9507 if (isVertical(this)) {
9508 x = vm.x;
9509 y = (vm.y + vm.base) / 2;
9510 } else {
9511 x = (vm.x + vm.base) / 2;
9512 y = vm.y;
9513 }
9514
9515 return {x: x, y: y};
9516 },
9517
9518 getArea: function() {
9519 var vm = this._view;
9520 return vm.width * Math.abs(vm.y - vm.base);
9521 },
9522
9523 tooltipPosition: function() {
9524 var vm = this._view;
9525 return {
9526 x: vm.x,
9527 y: vm.y
9528 };
9529 }
9530});
9531
9532},{"25":25,"26":26}],40:[function(require,module,exports){
9533'use strict';
9534
9535module.exports = {};
9536module.exports.Arc = require(36);
9537module.exports.Line = require(37);
9538module.exports.Point = require(38);
9539module.exports.Rectangle = require(39);
9540
9541},{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){
9542'use strict';
9543
9544var helpers = require(42);
9545
9546/**
9547 * @namespace Chart.helpers.canvas
9548 */
9549var exports = module.exports = {
9550 /**
9551 * Clears the entire canvas associated to the given `chart`.
9552 * @param {Chart} chart - The chart for which to clear the canvas.
9553 */
9554 clear: function(chart) {
9555 chart.ctx.clearRect(0, 0, chart.width, chart.height);
9556 },
9557
9558 /**
9559 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
9560 * given size (width, height) and the same `radius` for all corners.
9561 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
9562 * @param {Number} x - The x axis of the coordinate for the rectangle starting point.
9563 * @param {Number} y - The y axis of the coordinate for the rectangle starting point.
9564 * @param {Number} width - The rectangle's width.
9565 * @param {Number} height - The rectangle's height.
9566 * @param {Number} radius - The rounded amount (in pixels) for the four corners.
9567 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
9568 */
9569 roundedRect: function(ctx, x, y, width, height, radius) {
9570 if (radius) {
9571 var rx = Math.min(radius, width / 2);
9572 var ry = Math.min(radius, height / 2);
9573
9574 ctx.moveTo(x + rx, y);
9575 ctx.lineTo(x + width - rx, y);
9576 ctx.quadraticCurveTo(x + width, y, x + width, y + ry);
9577 ctx.lineTo(x + width, y + height - ry);
9578 ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
9579 ctx.lineTo(x + rx, y + height);
9580 ctx.quadraticCurveTo(x, y + height, x, y + height - ry);
9581 ctx.lineTo(x, y + ry);
9582 ctx.quadraticCurveTo(x, y, x + rx, y);
9583 } else {
9584 ctx.rect(x, y, width, height);
9585 }
9586 },
9587
9588 drawPoint: function(ctx, style, radius, x, y) {
9589 var type, edgeLength, xOffset, yOffset, height, size;
9590
9591 if (style && typeof style === 'object') {
9592 type = style.toString();
9593 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
9594 ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
9595 return;
9596 }
9597 }
9598
9599 if (isNaN(radius) || radius <= 0) {
9600 return;
9601 }
9602
9603 switch (style) {
9604 // Default includes circle
9605 default:
9606 ctx.beginPath();
9607 ctx.arc(x, y, radius, 0, Math.PI * 2);
9608 ctx.closePath();
9609 ctx.fill();
9610 break;
9611 case 'triangle':
9612 ctx.beginPath();
9613 edgeLength = 3 * radius / Math.sqrt(3);
9614 height = edgeLength * Math.sqrt(3) / 2;
9615 ctx.moveTo(x - edgeLength / 2, y + height / 3);
9616 ctx.lineTo(x + edgeLength / 2, y + height / 3);
9617 ctx.lineTo(x, y - 2 * height / 3);
9618 ctx.closePath();
9619 ctx.fill();
9620 break;
9621 case 'rect':
9622 size = 1 / Math.SQRT2 * radius;
9623 ctx.beginPath();
9624 ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
9625 ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
9626 break;
9627 case 'rectRounded':
9628 var offset = radius / Math.SQRT2;
9629 var leftX = x - offset;
9630 var topY = y - offset;
9631 var sideSize = Math.SQRT2 * radius;
9632 ctx.beginPath();
9633 this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2);
9634 ctx.closePath();
9635 ctx.fill();
9636 break;
9637 case 'rectRot':
9638 size = 1 / Math.SQRT2 * radius;
9639 ctx.beginPath();
9640 ctx.moveTo(x - size, y);
9641 ctx.lineTo(x, y + size);
9642 ctx.lineTo(x + size, y);
9643 ctx.lineTo(x, y - size);
9644 ctx.closePath();
9645 ctx.fill();
9646 break;
9647 case 'cross':
9648 ctx.beginPath();
9649 ctx.moveTo(x, y + radius);
9650 ctx.lineTo(x, y - radius);
9651 ctx.moveTo(x - radius, y);
9652 ctx.lineTo(x + radius, y);
9653 ctx.closePath();
9654 break;
9655 case 'crossRot':
9656 ctx.beginPath();
9657 xOffset = Math.cos(Math.PI / 4) * radius;
9658 yOffset = Math.sin(Math.PI / 4) * radius;
9659 ctx.moveTo(x - xOffset, y - yOffset);
9660 ctx.lineTo(x + xOffset, y + yOffset);
9661 ctx.moveTo(x - xOffset, y + yOffset);
9662 ctx.lineTo(x + xOffset, y - yOffset);
9663 ctx.closePath();
9664 break;
9665 case 'star':
9666 ctx.beginPath();
9667 ctx.moveTo(x, y + radius);
9668 ctx.lineTo(x, y - radius);
9669 ctx.moveTo(x - radius, y);
9670 ctx.lineTo(x + radius, y);
9671 xOffset = Math.cos(Math.PI / 4) * radius;
9672 yOffset = Math.sin(Math.PI / 4) * radius;
9673 ctx.moveTo(x - xOffset, y - yOffset);
9674 ctx.lineTo(x + xOffset, y + yOffset);
9675 ctx.moveTo(x - xOffset, y + yOffset);
9676 ctx.lineTo(x + xOffset, y - yOffset);
9677 ctx.closePath();
9678 break;
9679 case 'line':
9680 ctx.beginPath();
9681 ctx.moveTo(x - radius, y);
9682 ctx.lineTo(x + radius, y);
9683 ctx.closePath();
9684 break;
9685 case 'dash':
9686 ctx.beginPath();
9687 ctx.moveTo(x, y);
9688 ctx.lineTo(x + radius, y);
9689 ctx.closePath();
9690 break;
9691 }
9692
9693 ctx.stroke();
9694 },
9695
9696 clipArea: function(ctx, area) {
9697 ctx.save();
9698 ctx.beginPath();
9699 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
9700 ctx.clip();
9701 },
9702
9703 unclipArea: function(ctx) {
9704 ctx.restore();
9705 },
9706
9707 lineTo: function(ctx, previous, target, flip) {
9708 if (target.steppedLine) {
9709 if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) {
9710 ctx.lineTo(previous.x, target.y);
9711 } else {
9712 ctx.lineTo(target.x, previous.y);
9713 }
9714 ctx.lineTo(target.x, target.y);
9715 return;
9716 }
9717
9718 if (!target.tension) {
9719 ctx.lineTo(target.x, target.y);
9720 return;
9721 }
9722
9723 ctx.bezierCurveTo(
9724 flip ? previous.controlPointPreviousX : previous.controlPointNextX,
9725 flip ? previous.controlPointPreviousY : previous.controlPointNextY,
9726 flip ? target.controlPointNextX : target.controlPointPreviousX,
9727 flip ? target.controlPointNextY : target.controlPointPreviousY,
9728 target.x,
9729 target.y);
9730 }
9731};
9732
9733// DEPRECATIONS
9734
9735/**
9736 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
9737 * @namespace Chart.helpers.clear
9738 * @deprecated since version 2.7.0
9739 * @todo remove at version 3
9740 * @private
9741 */
9742helpers.clear = exports.clear;
9743
9744/**
9745 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
9746 * @namespace Chart.helpers.drawRoundedRectangle
9747 * @deprecated since version 2.7.0
9748 * @todo remove at version 3
9749 * @private
9750 */
9751helpers.drawRoundedRectangle = function(ctx) {
9752 ctx.beginPath();
9753 exports.roundedRect.apply(exports, arguments);
9754 ctx.closePath();
9755};
9756
9757},{"42":42}],42:[function(require,module,exports){
9758'use strict';
9759
9760/**
9761 * @namespace Chart.helpers
9762 */
9763var helpers = {
9764 /**
9765 * An empty function that can be used, for example, for optional callback.
9766 */
9767 noop: function() {},
9768
9769 /**
9770 * Returns a unique id, sequentially generated from a global variable.
9771 * @returns {Number}
9772 * @function
9773 */
9774 uid: (function() {
9775 var id = 0;
9776 return function() {
9777 return id++;
9778 };
9779 }()),
9780
9781 /**
9782 * Returns true if `value` is neither null nor undefined, else returns false.
9783 * @param {*} value - The value to test.
9784 * @returns {Boolean}
9785 * @since 2.7.0
9786 */
9787 isNullOrUndef: function(value) {
9788 return value === null || typeof value === 'undefined';
9789 },
9790
9791 /**
9792 * Returns true if `value` is an array, else returns false.
9793 * @param {*} value - The value to test.
9794 * @returns {Boolean}
9795 * @function
9796 */
9797 isArray: Array.isArray ? Array.isArray : function(value) {
9798 return Object.prototype.toString.call(value) === '[object Array]';
9799 },
9800
9801 /**
9802 * Returns true if `value` is an object (excluding null), else returns false.
9803 * @param {*} value - The value to test.
9804 * @returns {Boolean}
9805 * @since 2.7.0
9806 */
9807 isObject: function(value) {
9808 return value !== null && Object.prototype.toString.call(value) === '[object Object]';
9809 },
9810
9811 /**
9812 * Returns `value` if defined, else returns `defaultValue`.
9813 * @param {*} value - The value to return if defined.
9814 * @param {*} defaultValue - The value to return if `value` is undefined.
9815 * @returns {*}
9816 */
9817 valueOrDefault: function(value, defaultValue) {
9818 return typeof value === 'undefined' ? defaultValue : value;
9819 },
9820
9821 /**
9822 * Returns value at the given `index` in array if defined, else returns `defaultValue`.
9823 * @param {Array} value - The array to lookup for value at `index`.
9824 * @param {Number} index - The index in `value` to lookup for value.
9825 * @param {*} defaultValue - The value to return if `value[index]` is undefined.
9826 * @returns {*}
9827 */
9828 valueAtIndexOrDefault: function(value, index, defaultValue) {
9829 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
9830 },
9831
9832 /**
9833 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
9834 * value returned by `fn`. If `fn` is not a function, this method returns undefined.
9835 * @param {Function} fn - The function to call.
9836 * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
9837 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9838 * @returns {*}
9839 */
9840 callback: function(fn, args, thisArg) {
9841 if (fn && typeof fn.call === 'function') {
9842 return fn.apply(thisArg, args);
9843 }
9844 },
9845
9846 /**
9847 * Note(SB) for performance sake, this method should only be used when loopable type
9848 * is unknown or in none intensive code (not called often and small loopable). Else
9849 * it's preferable to use a regular for() loop and save extra function calls.
9850 * @param {Object|Array} loopable - The object or array to be iterated.
9851 * @param {Function} fn - The function to call for each item.
9852 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9853 * @param {Boolean} [reverse] - If true, iterates backward on the loopable.
9854 */
9855 each: function(loopable, fn, thisArg, reverse) {
9856 var i, len, keys;
9857 if (helpers.isArray(loopable)) {
9858 len = loopable.length;
9859 if (reverse) {
9860 for (i = len - 1; i >= 0; i--) {
9861 fn.call(thisArg, loopable[i], i);
9862 }
9863 } else {
9864 for (i = 0; i < len; i++) {
9865 fn.call(thisArg, loopable[i], i);
9866 }
9867 }
9868 } else if (helpers.isObject(loopable)) {
9869 keys = Object.keys(loopable);
9870 len = keys.length;
9871 for (i = 0; i < len; i++) {
9872 fn.call(thisArg, loopable[keys[i]], keys[i]);
9873 }
9874 }
9875 },
9876
9877 /**
9878 * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
9879 * @see http://stackoverflow.com/a/14853974
9880 * @param {Array} a0 - The array to compare
9881 * @param {Array} a1 - The array to compare
9882 * @returns {Boolean}
9883 */
9884 arrayEquals: function(a0, a1) {
9885 var i, ilen, v0, v1;
9886
9887 if (!a0 || !a1 || a0.length !== a1.length) {
9888 return false;
9889 }
9890
9891 for (i = 0, ilen = a0.length; i < ilen; ++i) {
9892 v0 = a0[i];
9893 v1 = a1[i];
9894
9895 if (v0 instanceof Array && v1 instanceof Array) {
9896 if (!helpers.arrayEquals(v0, v1)) {
9897 return false;
9898 }
9899 } else if (v0 !== v1) {
9900 // NOTE: two different object instances will never be equal: {x:20} != {x:20}
9901 return false;
9902 }
9903 }
9904
9905 return true;
9906 },
9907
9908 /**
9909 * Returns a deep copy of `source` without keeping references on objects and arrays.
9910 * @param {*} source - The value to clone.
9911 * @returns {*}
9912 */
9913 clone: function(source) {
9914 if (helpers.isArray(source)) {
9915 return source.map(helpers.clone);
9916 }
9917
9918 if (helpers.isObject(source)) {
9919 var target = {};
9920 var keys = Object.keys(source);
9921 var klen = keys.length;
9922 var k = 0;
9923
9924 for (; k < klen; ++k) {
9925 target[keys[k]] = helpers.clone(source[keys[k]]);
9926 }
9927
9928 return target;
9929 }
9930
9931 return source;
9932 },
9933
9934 /**
9935 * The default merger when Chart.helpers.merge is called without merger option.
9936 * Note(SB): this method is also used by configMerge and scaleMerge as fallback.
9937 * @private
9938 */
9939 _merger: function(key, target, source, options) {
9940 var tval = target[key];
9941 var sval = source[key];
9942
9943 if (helpers.isObject(tval) && helpers.isObject(sval)) {
9944 helpers.merge(tval, sval, options);
9945 } else {
9946 target[key] = helpers.clone(sval);
9947 }
9948 },
9949
9950 /**
9951 * Merges source[key] in target[key] only if target[key] is undefined.
9952 * @private
9953 */
9954 _mergerIf: function(key, target, source) {
9955 var tval = target[key];
9956 var sval = source[key];
9957
9958 if (helpers.isObject(tval) && helpers.isObject(sval)) {
9959 helpers.mergeIf(tval, sval);
9960 } else if (!target.hasOwnProperty(key)) {
9961 target[key] = helpers.clone(sval);
9962 }
9963 },
9964
9965 /**
9966 * Recursively deep copies `source` properties into `target` with the given `options`.
9967 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
9968 * @param {Object} target - The target object in which all sources are merged into.
9969 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
9970 * @param {Object} [options] - Merging options:
9971 * @param {Function} [options.merger] - The merge method (key, target, source, options)
9972 * @returns {Object} The `target` object.
9973 */
9974 merge: function(target, source, options) {
9975 var sources = helpers.isArray(source) ? source : [source];
9976 var ilen = sources.length;
9977 var merge, i, keys, klen, k;
9978
9979 if (!helpers.isObject(target)) {
9980 return target;
9981 }
9982
9983 options = options || {};
9984 merge = options.merger || helpers._merger;
9985
9986 for (i = 0; i < ilen; ++i) {
9987 source = sources[i];
9988 if (!helpers.isObject(source)) {
9989 continue;
9990 }
9991
9992 keys = Object.keys(source);
9993 for (k = 0, klen = keys.length; k < klen; ++k) {
9994 merge(keys[k], target, source, options);
9995 }
9996 }
9997
9998 return target;
9999 },
10000
10001 /**
10002 * Recursively deep copies `source` properties into `target` *only* if not defined in target.
10003 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
10004 * @param {Object} target - The target object in which all sources are merged into.
10005 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
10006 * @returns {Object} The `target` object.
10007 */
10008 mergeIf: function(target, source) {
10009 return helpers.merge(target, source, {merger: helpers._mergerIf});
10010 },
10011
10012 /**
10013 * Applies the contents of two or more objects together into the first object.
10014 * @param {Object} target - The target object in which all objects are merged into.
10015 * @param {Object} arg1 - Object containing additional properties to merge in target.
10016 * @param {Object} argN - Additional objects containing properties to merge in target.
10017 * @returns {Object} The `target` object.
10018 */
10019 extend: function(target) {
10020 var setFn = function(value, key) {
10021 target[key] = value;
10022 };
10023 for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
10024 helpers.each(arguments[i], setFn);
10025 }
10026 return target;
10027 },
10028
10029 /**
10030 * Basic javascript inheritance based on the model created in Backbone.js
10031 */
10032 inherits: function(extensions) {
10033 var me = this;
10034 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
10035 return me.apply(this, arguments);
10036 };
10037
10038 var Surrogate = function() {
10039 this.constructor = ChartElement;
10040 };
10041
10042 Surrogate.prototype = me.prototype;
10043 ChartElement.prototype = new Surrogate();
10044 ChartElement.extend = helpers.inherits;
10045
10046 if (extensions) {
10047 helpers.extend(ChartElement.prototype, extensions);
10048 }
10049
10050 ChartElement.__super__ = me.prototype;
10051 return ChartElement;
10052 }
10053};
10054
10055module.exports = helpers;
10056
10057// DEPRECATIONS
10058
10059/**
10060 * Provided for backward compatibility, use Chart.helpers.callback instead.
10061 * @function Chart.helpers.callCallback
10062 * @deprecated since version 2.6.0
10063 * @todo remove at version 3
10064 * @private
10065 */
10066helpers.callCallback = helpers.callback;
10067
10068/**
10069 * Provided for backward compatibility, use Array.prototype.indexOf instead.
10070 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
10071 * @function Chart.helpers.indexOf
10072 * @deprecated since version 2.7.0
10073 * @todo remove at version 3
10074 * @private
10075 */
10076helpers.indexOf = function(array, item, fromIndex) {
10077 return Array.prototype.indexOf.call(array, item, fromIndex);
10078};
10079
10080/**
10081 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
10082 * @function Chart.helpers.getValueOrDefault
10083 * @deprecated since version 2.7.0
10084 * @todo remove at version 3
10085 * @private
10086 */
10087helpers.getValueOrDefault = helpers.valueOrDefault;
10088
10089/**
10090 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
10091 * @function Chart.helpers.getValueAtIndexOrDefault
10092 * @deprecated since version 2.7.0
10093 * @todo remove at version 3
10094 * @private
10095 */
10096helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
10097
10098},{}],43:[function(require,module,exports){
10099'use strict';
10100
10101var helpers = require(42);
10102
10103/**
10104 * Easing functions adapted from Robert Penner's easing equations.
10105 * @namespace Chart.helpers.easingEffects
10106 * @see http://www.robertpenner.com/easing/
10107 */
10108var effects = {
10109 linear: function(t) {
10110 return t;
10111 },
10112
10113 easeInQuad: function(t) {
10114 return t * t;
10115 },
10116
10117 easeOutQuad: function(t) {
10118 return -t * (t - 2);
10119 },
10120
10121 easeInOutQuad: function(t) {
10122 if ((t /= 0.5) < 1) {
10123 return 0.5 * t * t;
10124 }
10125 return -0.5 * ((--t) * (t - 2) - 1);
10126 },
10127
10128 easeInCubic: function(t) {
10129 return t * t * t;
10130 },
10131
10132 easeOutCubic: function(t) {
10133 return (t = t - 1) * t * t + 1;
10134 },
10135
10136 easeInOutCubic: function(t) {
10137 if ((t /= 0.5) < 1) {
10138 return 0.5 * t * t * t;
10139 }
10140 return 0.5 * ((t -= 2) * t * t + 2);
10141 },
10142
10143 easeInQuart: function(t) {
10144 return t * t * t * t;
10145 },
10146
10147 easeOutQuart: function(t) {
10148 return -((t = t - 1) * t * t * t - 1);
10149 },
10150
10151 easeInOutQuart: function(t) {
10152 if ((t /= 0.5) < 1) {
10153 return 0.5 * t * t * t * t;
10154 }
10155 return -0.5 * ((t -= 2) * t * t * t - 2);
10156 },
10157
10158 easeInQuint: function(t) {
10159 return t * t * t * t * t;
10160 },
10161
10162 easeOutQuint: function(t) {
10163 return (t = t - 1) * t * t * t * t + 1;
10164 },
10165
10166 easeInOutQuint: function(t) {
10167 if ((t /= 0.5) < 1) {
10168 return 0.5 * t * t * t * t * t;
10169 }
10170 return 0.5 * ((t -= 2) * t * t * t * t + 2);
10171 },
10172
10173 easeInSine: function(t) {
10174 return -Math.cos(t * (Math.PI / 2)) + 1;
10175 },
10176
10177 easeOutSine: function(t) {
10178 return Math.sin(t * (Math.PI / 2));
10179 },
10180
10181 easeInOutSine: function(t) {
10182 return -0.5 * (Math.cos(Math.PI * t) - 1);
10183 },
10184
10185 easeInExpo: function(t) {
10186 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
10187 },
10188
10189 easeOutExpo: function(t) {
10190 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
10191 },
10192
10193 easeInOutExpo: function(t) {
10194 if (t === 0) {
10195 return 0;
10196 }
10197 if (t === 1) {
10198 return 1;
10199 }
10200 if ((t /= 0.5) < 1) {
10201 return 0.5 * Math.pow(2, 10 * (t - 1));
10202 }
10203 return 0.5 * (-Math.pow(2, -10 * --t) + 2);
10204 },
10205
10206 easeInCirc: function(t) {
10207 if (t >= 1) {
10208 return t;
10209 }
10210 return -(Math.sqrt(1 - t * t) - 1);
10211 },
10212
10213 easeOutCirc: function(t) {
10214 return Math.sqrt(1 - (t = t - 1) * t);
10215 },
10216
10217 easeInOutCirc: function(t) {
10218 if ((t /= 0.5) < 1) {
10219 return -0.5 * (Math.sqrt(1 - t * t) - 1);
10220 }
10221 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
10222 },
10223
10224 easeInElastic: function(t) {
10225 var s = 1.70158;
10226 var p = 0;
10227 var a = 1;
10228 if (t === 0) {
10229 return 0;
10230 }
10231 if (t === 1) {
10232 return 1;
10233 }
10234 if (!p) {
10235 p = 0.3;
10236 }
10237 if (a < 1) {
10238 a = 1;
10239 s = p / 4;
10240 } else {
10241 s = p / (2 * Math.PI) * Math.asin(1 / a);
10242 }
10243 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10244 },
10245
10246 easeOutElastic: function(t) {
10247 var s = 1.70158;
10248 var p = 0;
10249 var a = 1;
10250 if (t === 0) {
10251 return 0;
10252 }
10253 if (t === 1) {
10254 return 1;
10255 }
10256 if (!p) {
10257 p = 0.3;
10258 }
10259 if (a < 1) {
10260 a = 1;
10261 s = p / 4;
10262 } else {
10263 s = p / (2 * Math.PI) * Math.asin(1 / a);
10264 }
10265 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
10266 },
10267
10268 easeInOutElastic: function(t) {
10269 var s = 1.70158;
10270 var p = 0;
10271 var a = 1;
10272 if (t === 0) {
10273 return 0;
10274 }
10275 if ((t /= 0.5) === 2) {
10276 return 1;
10277 }
10278 if (!p) {
10279 p = 0.45;
10280 }
10281 if (a < 1) {
10282 a = 1;
10283 s = p / 4;
10284 } else {
10285 s = p / (2 * Math.PI) * Math.asin(1 / a);
10286 }
10287 if (t < 1) {
10288 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10289 }
10290 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
10291 },
10292 easeInBack: function(t) {
10293 var s = 1.70158;
10294 return t * t * ((s + 1) * t - s);
10295 },
10296
10297 easeOutBack: function(t) {
10298 var s = 1.70158;
10299 return (t = t - 1) * t * ((s + 1) * t + s) + 1;
10300 },
10301
10302 easeInOutBack: function(t) {
10303 var s = 1.70158;
10304 if ((t /= 0.5) < 1) {
10305 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
10306 }
10307 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
10308 },
10309
10310 easeInBounce: function(t) {
10311 return 1 - effects.easeOutBounce(1 - t);
10312 },
10313
10314 easeOutBounce: function(t) {
10315 if (t < (1 / 2.75)) {
10316 return 7.5625 * t * t;
10317 }
10318 if (t < (2 / 2.75)) {
10319 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
10320 }
10321 if (t < (2.5 / 2.75)) {
10322 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
10323 }
10324 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
10325 },
10326
10327 easeInOutBounce: function(t) {
10328 if (t < 0.5) {
10329 return effects.easeInBounce(t * 2) * 0.5;
10330 }
10331 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
10332 }
10333};
10334
10335module.exports = {
10336 effects: effects
10337};
10338
10339// DEPRECATIONS
10340
10341/**
10342 * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
10343 * @function Chart.helpers.easingEffects
10344 * @deprecated since version 2.7.0
10345 * @todo remove at version 3
10346 * @private
10347 */
10348helpers.easingEffects = effects;
10349
10350},{"42":42}],44:[function(require,module,exports){
10351'use strict';
10352
10353var helpers = require(42);
10354
10355/**
10356 * @alias Chart.helpers.options
10357 * @namespace
10358 */
10359module.exports = {
10360 /**
10361 * Converts the given line height `value` in pixels for a specific font `size`.
10362 * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
10363 * @param {Number} size - The font size (in pixels) used to resolve relative `value`.
10364 * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid).
10365 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
10366 * @since 2.7.0
10367 */
10368 toLineHeight: function(value, size) {
10369 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
10370 if (!matches || matches[1] === 'normal') {
10371 return size * 1.2;
10372 }
10373
10374 value = +matches[2];
10375
10376 switch (matches[3]) {
10377 case 'px':
10378 return value;
10379 case '%':
10380 value /= 100;
10381 break;
10382 default:
10383 break;
10384 }
10385
10386 return size * value;
10387 },
10388
10389 /**
10390 * Converts the given value into a padding object with pre-computed width/height.
10391 * @param {Number|Object} value - If a number, set the value to all TRBL component,
10392 * else, if and object, use defined properties and sets undefined ones to 0.
10393 * @returns {Object} The padding values (top, right, bottom, left, width, height)
10394 * @since 2.7.0
10395 */
10396 toPadding: function(value) {
10397 var t, r, b, l;
10398
10399 if (helpers.isObject(value)) {
10400 t = +value.top || 0;
10401 r = +value.right || 0;
10402 b = +value.bottom || 0;
10403 l = +value.left || 0;
10404 } else {
10405 t = r = b = l = +value || 0;
10406 }
10407
10408 return {
10409 top: t,
10410 right: r,
10411 bottom: b,
10412 left: l,
10413 height: t + b,
10414 width: l + r
10415 };
10416 },
10417
10418 /**
10419 * Evaluates the given `inputs` sequentially and returns the first defined value.
10420 * @param {Array[]} inputs - An array of values, falling back to the last value.
10421 * @param {Object} [context] - If defined and the current value is a function, the value
10422 * is called with `context` as first argument and the result becomes the new input.
10423 * @param {Number} [index] - If defined and the current value is an array, the value
10424 * at `index` become the new input.
10425 * @since 2.7.0
10426 */
10427 resolve: function(inputs, context, index) {
10428 var i, ilen, value;
10429
10430 for (i = 0, ilen = inputs.length; i < ilen; ++i) {
10431 value = inputs[i];
10432 if (value === undefined) {
10433 continue;
10434 }
10435 if (context !== undefined && typeof value === 'function') {
10436 value = value(context);
10437 }
10438 if (index !== undefined && helpers.isArray(value)) {
10439 value = value[index];
10440 }
10441 if (value !== undefined) {
10442 return value;
10443 }
10444 }
10445 }
10446};
10447
10448},{"42":42}],45:[function(require,module,exports){
10449'use strict';
10450
10451module.exports = require(42);
10452module.exports.easing = require(43);
10453module.exports.canvas = require(41);
10454module.exports.options = require(44);
10455
10456},{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){
10457/**
10458 * Platform fallback implementation (minimal).
10459 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
10460 */
10461
10462module.exports = {
10463 acquireContext: function(item) {
10464 if (item && item.canvas) {
10465 // Support for any object associated to a canvas (including a context2d)
10466 item = item.canvas;
10467 }
10468
10469 return item && item.getContext('2d') || null;
10470 }
10471};
10472
10473},{}],47:[function(require,module,exports){
10474/**
10475 * Chart.Platform implementation for targeting a web browser
10476 */
10477
10478'use strict';
10479
10480var helpers = require(45);
10481
10482var EXPANDO_KEY = '$chartjs';
10483var CSS_PREFIX = 'chartjs-';
10484var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
10485var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
10486var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
10487
10488/**
10489 * DOM event types -> Chart.js event types.
10490 * Note: only events with different types are mapped.
10491 * @see https://developer.mozilla.org/en-US/docs/Web/Events
10492 */
10493var EVENT_TYPES = {
10494 touchstart: 'mousedown',
10495 touchmove: 'mousemove',
10496 touchend: 'mouseup',
10497 pointerenter: 'mouseenter',
10498 pointerdown: 'mousedown',
10499 pointermove: 'mousemove',
10500 pointerup: 'mouseup',
10501 pointerleave: 'mouseout',
10502 pointerout: 'mouseout'
10503};
10504
10505/**
10506 * The "used" size is the final value of a dimension property after all calculations have
10507 * been performed. This method uses the computed style of `element` but returns undefined
10508 * if the computed style is not expressed in pixels. That can happen in some cases where
10509 * `element` has a size relative to its parent and this last one is not yet displayed,
10510 * for example because of `display: none` on a parent node.
10511 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
10512 * @returns {Number} Size in pixels or undefined if unknown.
10513 */
10514function readUsedSize(element, property) {
10515 var value = helpers.getStyle(element, property);
10516 var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
10517 return matches ? Number(matches[1]) : undefined;
10518}
10519
10520/**
10521 * Initializes the canvas style and render size without modifying the canvas display size,
10522 * since responsiveness is handled by the controller.resize() method. The config is used
10523 * to determine the aspect ratio to apply in case no explicit height has been specified.
10524 */
10525function initCanvas(canvas, config) {
10526 var style = canvas.style;
10527
10528 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
10529 // returns null or '' if no explicit value has been set to the canvas attribute.
10530 var renderHeight = canvas.getAttribute('height');
10531 var renderWidth = canvas.getAttribute('width');
10532
10533 // Chart.js modifies some canvas values that we want to restore on destroy
10534 canvas[EXPANDO_KEY] = {
10535 initial: {
10536 height: renderHeight,
10537 width: renderWidth,
10538 style: {
10539 display: style.display,
10540 height: style.height,
10541 width: style.width
10542 }
10543 }
10544 };
10545
10546 // Force canvas to display as block to avoid extra space caused by inline
10547 // elements, which would interfere with the responsive resize process.
10548 // https://github.com/chartjs/Chart.js/issues/2538
10549 style.display = style.display || 'block';
10550
10551 if (renderWidth === null || renderWidth === '') {
10552 var displayWidth = readUsedSize(canvas, 'width');
10553 if (displayWidth !== undefined) {
10554 canvas.width = displayWidth;
10555 }
10556 }
10557
10558 if (renderHeight === null || renderHeight === '') {
10559 if (canvas.style.height === '') {
10560 // If no explicit render height and style height, let's apply the aspect ratio,
10561 // which one can be specified by the user but also by charts as default option
10562 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
10563 canvas.height = canvas.width / (config.options.aspectRatio || 2);
10564 } else {
10565 var displayHeight = readUsedSize(canvas, 'height');
10566 if (displayWidth !== undefined) {
10567 canvas.height = displayHeight;
10568 }
10569 }
10570 }
10571
10572 return canvas;
10573}
10574
10575/**
10576 * Detects support for options object argument in addEventListener.
10577 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
10578 * @private
10579 */
10580var supportsEventListenerOptions = (function() {
10581 var supports = false;
10582 try {
10583 var options = Object.defineProperty({}, 'passive', {
10584 get: function() {
10585 supports = true;
10586 }
10587 });
10588 window.addEventListener('e', null, options);
10589 } catch (e) {
10590 // continue regardless of error
10591 }
10592 return supports;
10593}());
10594
10595// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
10596// https://github.com/chartjs/Chart.js/issues/4287
10597var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
10598
10599function addEventListener(node, type, listener) {
10600 node.addEventListener(type, listener, eventListenerOptions);
10601}
10602
10603function removeEventListener(node, type, listener) {
10604 node.removeEventListener(type, listener, eventListenerOptions);
10605}
10606
10607function createEvent(type, chart, x, y, nativeEvent) {
10608 return {
10609 type: type,
10610 chart: chart,
10611 native: nativeEvent || null,
10612 x: x !== undefined ? x : null,
10613 y: y !== undefined ? y : null,
10614 };
10615}
10616
10617function fromNativeEvent(event, chart) {
10618 var type = EVENT_TYPES[event.type] || event.type;
10619 var pos = helpers.getRelativePosition(event, chart);
10620 return createEvent(type, chart, pos.x, pos.y, event);
10621}
10622
10623function throttled(fn, thisArg) {
10624 var ticking = false;
10625 var args = [];
10626
10627 return function() {
10628 args = Array.prototype.slice.call(arguments);
10629 thisArg = thisArg || this;
10630
10631 if (!ticking) {
10632 ticking = true;
10633 helpers.requestAnimFrame.call(window, function() {
10634 ticking = false;
10635 fn.apply(thisArg, args);
10636 });
10637 }
10638 };
10639}
10640
10641// Implementation based on https://github.com/marcj/css-element-queries
10642function createResizer(handler) {
10643 var resizer = document.createElement('div');
10644 var cls = CSS_PREFIX + 'size-monitor';
10645 var maxSize = 1000000;
10646 var style =
10647 'position:absolute;' +
10648 'left:0;' +
10649 'top:0;' +
10650 'right:0;' +
10651 'bottom:0;' +
10652 'overflow:hidden;' +
10653 'pointer-events:none;' +
10654 'visibility:hidden;' +
10655 'z-index:-1;';
10656
10657 resizer.style.cssText = style;
10658 resizer.className = cls;
10659 resizer.innerHTML =
10660 '<div class="' + cls + '-expand" style="' + style + '">' +
10661 '<div style="' +
10662 'position:absolute;' +
10663 'width:' + maxSize + 'px;' +
10664 'height:' + maxSize + 'px;' +
10665 'left:0;' +
10666 'top:0">' +
10667 '</div>' +
10668 '</div>' +
10669 '<div class="' + cls + '-shrink" style="' + style + '">' +
10670 '<div style="' +
10671 'position:absolute;' +
10672 'width:200%;' +
10673 'height:200%;' +
10674 'left:0; ' +
10675 'top:0">' +
10676 '</div>' +
10677 '</div>';
10678
10679 var expand = resizer.childNodes[0];
10680 var shrink = resizer.childNodes[1];
10681
10682 resizer._reset = function() {
10683 expand.scrollLeft = maxSize;
10684 expand.scrollTop = maxSize;
10685 shrink.scrollLeft = maxSize;
10686 shrink.scrollTop = maxSize;
10687 };
10688 var onScroll = function() {
10689 resizer._reset();
10690 handler();
10691 };
10692
10693 addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
10694 addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
10695
10696 return resizer;
10697}
10698
10699// https://davidwalsh.name/detect-node-insertion
10700function watchForRender(node, handler) {
10701 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10702 var proxy = expando.renderProxy = function(e) {
10703 if (e.animationName === CSS_RENDER_ANIMATION) {
10704 handler();
10705 }
10706 };
10707
10708 helpers.each(ANIMATION_START_EVENTS, function(type) {
10709 addEventListener(node, type, proxy);
10710 });
10711
10712 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
10713 // is removed then added back immediately (same animation frame?). Accessing the
10714 // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
10715 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
10716 // https://github.com/chartjs/Chart.js/issues/4737
10717 expando.reflow = !!node.offsetParent;
10718
10719 node.classList.add(CSS_RENDER_MONITOR);
10720}
10721
10722function unwatchForRender(node) {
10723 var expando = node[EXPANDO_KEY] || {};
10724 var proxy = expando.renderProxy;
10725
10726 if (proxy) {
10727 helpers.each(ANIMATION_START_EVENTS, function(type) {
10728 removeEventListener(node, type, proxy);
10729 });
10730
10731 delete expando.renderProxy;
10732 }
10733
10734 node.classList.remove(CSS_RENDER_MONITOR);
10735}
10736
10737function addResizeListener(node, listener, chart) {
10738 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10739
10740 // Let's keep track of this added resizer and thus avoid DOM query when removing it.
10741 var resizer = expando.resizer = createResizer(throttled(function() {
10742 if (expando.resizer) {
10743 return listener(createEvent('resize', chart));
10744 }
10745 }));
10746
10747 // The resizer needs to be attached to the node parent, so we first need to be
10748 // sure that `node` is attached to the DOM before injecting the resizer element.
10749 watchForRender(node, function() {
10750 if (expando.resizer) {
10751 var container = node.parentNode;
10752 if (container && container !== resizer.parentNode) {
10753 container.insertBefore(resizer, container.firstChild);
10754 }
10755
10756 // The container size might have changed, let's reset the resizer state.
10757 resizer._reset();
10758 }
10759 });
10760}
10761
10762function removeResizeListener(node) {
10763 var expando = node[EXPANDO_KEY] || {};
10764 var resizer = expando.resizer;
10765
10766 delete expando.resizer;
10767 unwatchForRender(node);
10768
10769 if (resizer && resizer.parentNode) {
10770 resizer.parentNode.removeChild(resizer);
10771 }
10772}
10773
10774function injectCSS(platform, css) {
10775 // http://stackoverflow.com/q/3922139
10776 var style = platform._style || document.createElement('style');
10777 if (!platform._style) {
10778 platform._style = style;
10779 css = '/* Chart.js */\n' + css;
10780 style.setAttribute('type', 'text/css');
10781 document.getElementsByTagName('head')[0].appendChild(style);
10782 }
10783
10784 style.appendChild(document.createTextNode(css));
10785}
10786
10787module.exports = {
10788 /**
10789 * This property holds whether this platform is enabled for the current environment.
10790 * Currently used by platform.js to select the proper implementation.
10791 * @private
10792 */
10793 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
10794
10795 initialize: function() {
10796 var keyframes = 'from{opacity:0.99}to{opacity:1}';
10797
10798 injectCSS(this,
10799 // DOM rendering detection
10800 // https://davidwalsh.name/detect-node-insertion
10801 '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10802 '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10803 '.' + CSS_RENDER_MONITOR + '{' +
10804 '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10805 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10806 '}'
10807 );
10808 },
10809
10810 acquireContext: function(item, config) {
10811 if (typeof item === 'string') {
10812 item = document.getElementById(item);
10813 } else if (item.length) {
10814 // Support for array based queries (such as jQuery)
10815 item = item[0];
10816 }
10817
10818 if (item && item.canvas) {
10819 // Support for any object associated to a canvas (including a context2d)
10820 item = item.canvas;
10821 }
10822
10823 // To prevent canvas fingerprinting, some add-ons undefine the getContext
10824 // method, for example: https://github.com/kkapsner/CanvasBlocker
10825 // https://github.com/chartjs/Chart.js/issues/2807
10826 var context = item && item.getContext && item.getContext('2d');
10827
10828 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
10829 // inside an iframe or when running in a protected environment. We could guess the
10830 // types from their toString() value but let's keep things flexible and assume it's
10831 // a sufficient condition if the item has a context2D which has item as `canvas`.
10832 // https://github.com/chartjs/Chart.js/issues/3887
10833 // https://github.com/chartjs/Chart.js/issues/4102
10834 // https://github.com/chartjs/Chart.js/issues/4152
10835 if (context && context.canvas === item) {
10836 initCanvas(item, config);
10837 return context;
10838 }
10839
10840 return null;
10841 },
10842
10843 releaseContext: function(context) {
10844 var canvas = context.canvas;
10845 if (!canvas[EXPANDO_KEY]) {
10846 return;
10847 }
10848
10849 var initial = canvas[EXPANDO_KEY].initial;
10850 ['height', 'width'].forEach(function(prop) {
10851 var value = initial[prop];
10852 if (helpers.isNullOrUndef(value)) {
10853 canvas.removeAttribute(prop);
10854 } else {
10855 canvas.setAttribute(prop, value);
10856 }
10857 });
10858
10859 helpers.each(initial.style || {}, function(value, key) {
10860 canvas.style[key] = value;
10861 });
10862
10863 // The canvas render size might have been changed (and thus the state stack discarded),
10864 // we can't use save() and restore() to restore the initial state. So make sure that at
10865 // least the canvas context is reset to the default state by setting the canvas width.
10866 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
10867 canvas.width = canvas.width;
10868
10869 delete canvas[EXPANDO_KEY];
10870 },
10871
10872 addEventListener: function(chart, type, listener) {
10873 var canvas = chart.canvas;
10874 if (type === 'resize') {
10875 // Note: the resize event is not supported on all browsers.
10876 addResizeListener(canvas, listener, chart);
10877 return;
10878 }
10879
10880 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
10881 var proxies = expando.proxies || (expando.proxies = {});
10882 var proxy = proxies[chart.id + '_' + type] = function(event) {
10883 listener(fromNativeEvent(event, chart));
10884 };
10885
10886 addEventListener(canvas, type, proxy);
10887 },
10888
10889 removeEventListener: function(chart, type, listener) {
10890 var canvas = chart.canvas;
10891 if (type === 'resize') {
10892 // Note: the resize event is not supported on all browsers.
10893 removeResizeListener(canvas, listener);
10894 return;
10895 }
10896
10897 var expando = listener[EXPANDO_KEY] || {};
10898 var proxies = expando.proxies || {};
10899 var proxy = proxies[chart.id + '_' + type];
10900 if (!proxy) {
10901 return;
10902 }
10903
10904 removeEventListener(canvas, type, proxy);
10905 }
10906};
10907
10908// DEPRECATIONS
10909
10910/**
10911 * Provided for backward compatibility, use EventTarget.addEventListener instead.
10912 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10913 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
10914 * @function Chart.helpers.addEvent
10915 * @deprecated since version 2.7.0
10916 * @todo remove at version 3
10917 * @private
10918 */
10919helpers.addEvent = addEventListener;
10920
10921/**
10922 * Provided for backward compatibility, use EventTarget.removeEventListener instead.
10923 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10924 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
10925 * @function Chart.helpers.removeEvent
10926 * @deprecated since version 2.7.0
10927 * @todo remove at version 3
10928 * @private
10929 */
10930helpers.removeEvent = removeEventListener;
10931
10932},{"45":45}],48:[function(require,module,exports){
10933'use strict';
10934
10935var helpers = require(45);
10936var basic = require(46);
10937var dom = require(47);
10938
10939// @TODO Make possible to select another platform at build time.
10940var implementation = dom._enabled ? dom : basic;
10941
10942/**
10943 * @namespace Chart.platform
10944 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
10945 * @since 2.4.0
10946 */
10947module.exports = helpers.extend({
10948 /**
10949 * @since 2.7.0
10950 */
10951 initialize: function() {},
10952
10953 /**
10954 * Called at chart construction time, returns a context2d instance implementing
10955 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
10956 * @param {*} item - The native item from which to acquire context (platform specific)
10957 * @param {Object} options - The chart options
10958 * @returns {CanvasRenderingContext2D} context2d instance
10959 */
10960 acquireContext: function() {},
10961
10962 /**
10963 * Called at chart destruction time, releases any resources associated to the context
10964 * previously returned by the acquireContext() method.
10965 * @param {CanvasRenderingContext2D} context - The context2d instance
10966 * @returns {Boolean} true if the method succeeded, else false
10967 */
10968 releaseContext: function() {},
10969
10970 /**
10971 * Registers the specified listener on the given chart.
10972 * @param {Chart} chart - Chart from which to listen for event
10973 * @param {String} type - The ({@link IEvent}) type to listen for
10974 * @param {Function} listener - Receives a notification (an object that implements
10975 * the {@link IEvent} interface) when an event of the specified type occurs.
10976 */
10977 addEventListener: function() {},
10978
10979 /**
10980 * Removes the specified listener previously registered with addEventListener.
10981 * @param {Chart} chart -Chart from which to remove the listener
10982 * @param {String} type - The ({@link IEvent}) type to remove
10983 * @param {Function} listener - The listener function to remove from the event target.
10984 */
10985 removeEventListener: function() {}
10986
10987}, implementation);
10988
10989/**
10990 * @interface IPlatform
10991 * Allows abstracting platform dependencies away from the chart
10992 * @borrows Chart.platform.acquireContext as acquireContext
10993 * @borrows Chart.platform.releaseContext as releaseContext
10994 * @borrows Chart.platform.addEventListener as addEventListener
10995 * @borrows Chart.platform.removeEventListener as removeEventListener
10996 */
10997
10998/**
10999 * @interface IEvent
11000 * @prop {String} type - The event type name, possible values are:
11001 * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',
11002 * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'
11003 * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')
11004 * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)
11005 * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)
11006 */
11007
11008},{"45":45,"46":46,"47":47}],49:[function(require,module,exports){
11009/**
11010 * Plugin based on discussion from the following Chart.js issues:
11011 * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569
11012 * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897
11013 */
11014
11015'use strict';
11016
11017var defaults = require(25);
11018var elements = require(40);
11019var helpers = require(45);
11020
11021defaults._set('global', {
11022 plugins: {
11023 filler: {
11024 propagate: true
11025 }
11026 }
11027});
11028
11029module.exports = function() {
11030
11031 var mappers = {
11032 dataset: function(source) {
11033 var index = source.fill;
11034 var chart = source.chart;
11035 var meta = chart.getDatasetMeta(index);
11036 var visible = meta && chart.isDatasetVisible(index);
11037 var points = (visible && meta.dataset._children) || [];
11038 var length = points.length || 0;
11039
11040 return !length ? null : function(point, i) {
11041 return (i < length && points[i]._view) || null;
11042 };
11043 },
11044
11045 boundary: function(source) {
11046 var boundary = source.boundary;
11047 var x = boundary ? boundary.x : null;
11048 var y = boundary ? boundary.y : null;
11049
11050 return function(point) {
11051 return {
11052 x: x === null ? point.x : x,
11053 y: y === null ? point.y : y,
11054 };
11055 };
11056 }
11057 };
11058
11059 // @todo if (fill[0] === '#')
11060 function decodeFill(el, index, count) {
11061 var model = el._model || {};
11062 var fill = model.fill;
11063 var target;
11064
11065 if (fill === undefined) {
11066 fill = !!model.backgroundColor;
11067 }
11068
11069 if (fill === false || fill === null) {
11070 return false;
11071 }
11072
11073 if (fill === true) {
11074 return 'origin';
11075 }
11076
11077 target = parseFloat(fill, 10);
11078 if (isFinite(target) && Math.floor(target) === target) {
11079 if (fill[0] === '-' || fill[0] === '+') {
11080 target = index + target;
11081 }
11082
11083 if (target === index || target < 0 || target >= count) {
11084 return false;
11085 }
11086
11087 return target;
11088 }
11089
11090 switch (fill) {
11091 // compatibility
11092 case 'bottom':
11093 return 'start';
11094 case 'top':
11095 return 'end';
11096 case 'zero':
11097 return 'origin';
11098 // supported boundaries
11099 case 'origin':
11100 case 'start':
11101 case 'end':
11102 return fill;
11103 // invalid fill values
11104 default:
11105 return false;
11106 }
11107 }
11108
11109 function computeBoundary(source) {
11110 var model = source.el._model || {};
11111 var scale = source.el._scale || {};
11112 var fill = source.fill;
11113 var target = null;
11114 var horizontal;
11115
11116 if (isFinite(fill)) {
11117 return null;
11118 }
11119
11120 // Backward compatibility: until v3, we still need to support boundary values set on
11121 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
11122 // controllers might still use it (e.g. the Smith chart).
11123
11124 if (fill === 'start') {
11125 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
11126 } else if (fill === 'end') {
11127 target = model.scaleTop === undefined ? scale.top : model.scaleTop;
11128 } else if (model.scaleZero !== undefined) {
11129 target = model.scaleZero;
11130 } else if (scale.getBasePosition) {
11131 target = scale.getBasePosition();
11132 } else if (scale.getBasePixel) {
11133 target = scale.getBasePixel();
11134 }
11135
11136 if (target !== undefined && target !== null) {
11137 if (target.x !== undefined && target.y !== undefined) {
11138 return target;
11139 }
11140
11141 if (typeof target === 'number' && isFinite(target)) {
11142 horizontal = scale.isHorizontal();
11143 return {
11144 x: horizontal ? target : null,
11145 y: horizontal ? null : target
11146 };
11147 }
11148 }
11149
11150 return null;
11151 }
11152
11153 function resolveTarget(sources, index, propagate) {
11154 var source = sources[index];
11155 var fill = source.fill;
11156 var visited = [index];
11157 var target;
11158
11159 if (!propagate) {
11160 return fill;
11161 }
11162
11163 while (fill !== false && visited.indexOf(fill) === -1) {
11164 if (!isFinite(fill)) {
11165 return fill;
11166 }
11167
11168 target = sources[fill];
11169 if (!target) {
11170 return false;
11171 }
11172
11173 if (target.visible) {
11174 return fill;
11175 }
11176
11177 visited.push(fill);
11178 fill = target.fill;
11179 }
11180
11181 return false;
11182 }
11183
11184 function createMapper(source) {
11185 var fill = source.fill;
11186 var type = 'dataset';
11187
11188 if (fill === false) {
11189 return null;
11190 }
11191
11192 if (!isFinite(fill)) {
11193 type = 'boundary';
11194 }
11195
11196 return mappers[type](source);
11197 }
11198
11199 function isDrawable(point) {
11200 return point && !point.skip;
11201 }
11202
11203 function drawArea(ctx, curve0, curve1, len0, len1) {
11204 var i;
11205
11206 if (!len0 || !len1) {
11207 return;
11208 }
11209
11210 // building first area curve (normal)
11211 ctx.moveTo(curve0[0].x, curve0[0].y);
11212 for (i = 1; i < len0; ++i) {
11213 helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
11214 }
11215
11216 // joining the two area curves
11217 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
11218
11219 // building opposite area curve (reverse)
11220 for (i = len1 - 1; i > 0; --i) {
11221 helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
11222 }
11223 }
11224
11225 function doFill(ctx, points, mapper, view, color, loop) {
11226 var count = points.length;
11227 var span = view.spanGaps;
11228 var curve0 = [];
11229 var curve1 = [];
11230 var len0 = 0;
11231 var len1 = 0;
11232 var i, ilen, index, p0, p1, d0, d1;
11233
11234 ctx.beginPath();
11235
11236 for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
11237 index = i % count;
11238 p0 = points[index]._view;
11239 p1 = mapper(p0, index, view);
11240 d0 = isDrawable(p0);
11241 d1 = isDrawable(p1);
11242
11243 if (d0 && d1) {
11244 len0 = curve0.push(p0);
11245 len1 = curve1.push(p1);
11246 } else if (len0 && len1) {
11247 if (!span) {
11248 drawArea(ctx, curve0, curve1, len0, len1);
11249 len0 = len1 = 0;
11250 curve0 = [];
11251 curve1 = [];
11252 } else {
11253 if (d0) {
11254 curve0.push(p0);
11255 }
11256 if (d1) {
11257 curve1.push(p1);
11258 }
11259 }
11260 }
11261 }
11262
11263 drawArea(ctx, curve0, curve1, len0, len1);
11264
11265 ctx.closePath();
11266 ctx.fillStyle = color;
11267 ctx.fill();
11268 }
11269
11270 return {
11271 id: 'filler',
11272
11273 afterDatasetsUpdate: function(chart, options) {
11274 var count = (chart.data.datasets || []).length;
11275 var propagate = options.propagate;
11276 var sources = [];
11277 var meta, i, el, source;
11278
11279 for (i = 0; i < count; ++i) {
11280 meta = chart.getDatasetMeta(i);
11281 el = meta.dataset;
11282 source = null;
11283
11284 if (el && el._model && el instanceof elements.Line) {
11285 source = {
11286 visible: chart.isDatasetVisible(i),
11287 fill: decodeFill(el, i, count),
11288 chart: chart,
11289 el: el
11290 };
11291 }
11292
11293 meta.$filler = source;
11294 sources.push(source);
11295 }
11296
11297 for (i = 0; i < count; ++i) {
11298 source = sources[i];
11299 if (!source) {
11300 continue;
11301 }
11302
11303 source.fill = resolveTarget(sources, i, propagate);
11304 source.boundary = computeBoundary(source);
11305 source.mapper = createMapper(source);
11306 }
11307 },
11308
11309 beforeDatasetDraw: function(chart, args) {
11310 var meta = args.meta.$filler;
11311 if (!meta) {
11312 return;
11313 }
11314
11315 var ctx = chart.ctx;
11316 var el = meta.el;
11317 var view = el._view;
11318 var points = el._children || [];
11319 var mapper = meta.mapper;
11320 var color = view.backgroundColor || defaults.global.defaultColor;
11321
11322 if (mapper && color && points.length) {
11323 helpers.canvas.clipArea(ctx, chart.chartArea);
11324 doFill(ctx, points, mapper, view, color, el._loop);
11325 helpers.canvas.unclipArea(ctx);
11326 }
11327 }
11328 };
11329};
11330
11331},{"25":25,"40":40,"45":45}],50:[function(require,module,exports){
11332'use strict';
11333
11334var defaults = require(25);
11335var Element = require(26);
11336var helpers = require(45);
11337
11338defaults._set('global', {
11339 legend: {
11340 display: true,
11341 position: 'top',
11342 fullWidth: true,
11343 reverse: false,
11344 weight: 1000,
11345
11346 // a callback that will handle
11347 onClick: function(e, legendItem) {
11348 var index = legendItem.datasetIndex;
11349 var ci = this.chart;
11350 var meta = ci.getDatasetMeta(index);
11351
11352 // See controller.isDatasetVisible comment
11353 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
11354
11355 // We hid a dataset ... rerender the chart
11356 ci.update();
11357 },
11358
11359 onHover: null,
11360
11361 labels: {
11362 boxWidth: 40,
11363 padding: 10,
11364 // Generates labels shown in the legend
11365 // Valid properties to return:
11366 // text : text to display
11367 // fillStyle : fill of coloured box
11368 // strokeStyle: stroke of coloured box
11369 // hidden : if this legend item refers to a hidden item
11370 // lineCap : cap style for line
11371 // lineDash
11372 // lineDashOffset :
11373 // lineJoin :
11374 // lineWidth :
11375 generateLabels: function(chart) {
11376 var data = chart.data;
11377 return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
11378 return {
11379 text: dataset.label,
11380 fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
11381 hidden: !chart.isDatasetVisible(i),
11382 lineCap: dataset.borderCapStyle,
11383 lineDash: dataset.borderDash,
11384 lineDashOffset: dataset.borderDashOffset,
11385 lineJoin: dataset.borderJoinStyle,
11386 lineWidth: dataset.borderWidth,
11387 strokeStyle: dataset.borderColor,
11388 pointStyle: dataset.pointStyle,
11389
11390 // Below is extra data used for toggling the datasets
11391 datasetIndex: i
11392 };
11393 }, this) : [];
11394 }
11395 }
11396 },
11397
11398 legendCallback: function(chart) {
11399 var text = [];
11400 text.push('<ul class="' + chart.id + '-legend">');
11401 for (var i = 0; i < chart.data.datasets.length; i++) {
11402 text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
11403 if (chart.data.datasets[i].label) {
11404 text.push(chart.data.datasets[i].label);
11405 }
11406 text.push('</li>');
11407 }
11408 text.push('</ul>');
11409 return text.join('');
11410 }
11411});
11412
11413module.exports = function(Chart) {
11414
11415 var layout = Chart.layoutService;
11416 var noop = helpers.noop;
11417
11418 /**
11419 * Helper function to get the box width based on the usePointStyle option
11420 * @param labelopts {Object} the label options on the legend
11421 * @param fontSize {Number} the label font size
11422 * @return {Number} width of the color box area
11423 */
11424 function getBoxWidth(labelOpts, fontSize) {
11425 return labelOpts.usePointStyle ?
11426 fontSize * Math.SQRT2 :
11427 labelOpts.boxWidth;
11428 }
11429
11430 Chart.Legend = Element.extend({
11431
11432 initialize: function(config) {
11433 helpers.extend(this, config);
11434
11435 // Contains hit boxes for each dataset (in dataset order)
11436 this.legendHitBoxes = [];
11437
11438 // Are we in doughnut mode which has a different data type
11439 this.doughnutMode = false;
11440 },
11441
11442 // These methods are ordered by lifecycle. Utilities then follow.
11443 // Any function defined here is inherited by all legend types.
11444 // Any function can be extended by the legend type
11445
11446 beforeUpdate: noop,
11447 update: function(maxWidth, maxHeight, margins) {
11448 var me = this;
11449
11450 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11451 me.beforeUpdate();
11452
11453 // Absorb the master measurements
11454 me.maxWidth = maxWidth;
11455 me.maxHeight = maxHeight;
11456 me.margins = margins;
11457
11458 // Dimensions
11459 me.beforeSetDimensions();
11460 me.setDimensions();
11461 me.afterSetDimensions();
11462 // Labels
11463 me.beforeBuildLabels();
11464 me.buildLabels();
11465 me.afterBuildLabels();
11466
11467 // Fit
11468 me.beforeFit();
11469 me.fit();
11470 me.afterFit();
11471 //
11472 me.afterUpdate();
11473
11474 return me.minSize;
11475 },
11476 afterUpdate: noop,
11477
11478 //
11479
11480 beforeSetDimensions: noop,
11481 setDimensions: function() {
11482 var me = this;
11483 // Set the unconstrained dimension before label rotation
11484 if (me.isHorizontal()) {
11485 // Reset position before calculating rotation
11486 me.width = me.maxWidth;
11487 me.left = 0;
11488 me.right = me.width;
11489 } else {
11490 me.height = me.maxHeight;
11491
11492 // Reset position before calculating rotation
11493 me.top = 0;
11494 me.bottom = me.height;
11495 }
11496
11497 // Reset padding
11498 me.paddingLeft = 0;
11499 me.paddingTop = 0;
11500 me.paddingRight = 0;
11501 me.paddingBottom = 0;
11502
11503 // Reset minSize
11504 me.minSize = {
11505 width: 0,
11506 height: 0
11507 };
11508 },
11509 afterSetDimensions: noop,
11510
11511 //
11512
11513 beforeBuildLabels: noop,
11514 buildLabels: function() {
11515 var me = this;
11516 var labelOpts = me.options.labels || {};
11517 var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
11518
11519 if (labelOpts.filter) {
11520 legendItems = legendItems.filter(function(item) {
11521 return labelOpts.filter(item, me.chart.data);
11522 });
11523 }
11524
11525 if (me.options.reverse) {
11526 legendItems.reverse();
11527 }
11528
11529 me.legendItems = legendItems;
11530 },
11531 afterBuildLabels: noop,
11532
11533 //
11534
11535 beforeFit: noop,
11536 fit: function() {
11537 var me = this;
11538 var opts = me.options;
11539 var labelOpts = opts.labels;
11540 var display = opts.display;
11541
11542 var ctx = me.ctx;
11543
11544 var globalDefault = defaults.global;
11545 var valueOrDefault = helpers.valueOrDefault;
11546 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11547 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11548 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11549 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11550
11551 // Reset hit boxes
11552 var hitboxes = me.legendHitBoxes = [];
11553
11554 var minSize = me.minSize;
11555 var isHorizontal = me.isHorizontal();
11556
11557 if (isHorizontal) {
11558 minSize.width = me.maxWidth; // fill all the width
11559 minSize.height = display ? 10 : 0;
11560 } else {
11561 minSize.width = display ? 10 : 0;
11562 minSize.height = me.maxHeight; // fill all the height
11563 }
11564
11565 // Increase sizes here
11566 if (display) {
11567 ctx.font = labelFont;
11568
11569 if (isHorizontal) {
11570 // Labels
11571
11572 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
11573 var lineWidths = me.lineWidths = [0];
11574 var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
11575
11576 ctx.textAlign = 'left';
11577 ctx.textBaseline = 'top';
11578
11579 helpers.each(me.legendItems, function(legendItem, i) {
11580 var boxWidth = getBoxWidth(labelOpts, fontSize);
11581 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11582
11583 if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
11584 totalHeight += fontSize + (labelOpts.padding);
11585 lineWidths[lineWidths.length] = me.left;
11586 }
11587
11588 // Store the hitbox width and height here. Final position will be updated in `draw`
11589 hitboxes[i] = {
11590 left: 0,
11591 top: 0,
11592 width: width,
11593 height: fontSize
11594 };
11595
11596 lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
11597 });
11598
11599 minSize.height += totalHeight;
11600
11601 } else {
11602 var vPadding = labelOpts.padding;
11603 var columnWidths = me.columnWidths = [];
11604 var totalWidth = labelOpts.padding;
11605 var currentColWidth = 0;
11606 var currentColHeight = 0;
11607 var itemHeight = fontSize + vPadding;
11608
11609 helpers.each(me.legendItems, function(legendItem, i) {
11610 var boxWidth = getBoxWidth(labelOpts, fontSize);
11611 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11612
11613 // If too tall, go to new column
11614 if (currentColHeight + itemHeight > minSize.height) {
11615 totalWidth += currentColWidth + labelOpts.padding;
11616 columnWidths.push(currentColWidth); // previous column width
11617
11618 currentColWidth = 0;
11619 currentColHeight = 0;
11620 }
11621
11622 // Get max width
11623 currentColWidth = Math.max(currentColWidth, itemWidth);
11624 currentColHeight += itemHeight;
11625
11626 // Store the hitbox width and height here. Final position will be updated in `draw`
11627 hitboxes[i] = {
11628 left: 0,
11629 top: 0,
11630 width: itemWidth,
11631 height: fontSize
11632 };
11633 });
11634
11635 totalWidth += currentColWidth;
11636 columnWidths.push(currentColWidth);
11637 minSize.width += totalWidth;
11638 }
11639 }
11640
11641 me.width = minSize.width;
11642 me.height = minSize.height;
11643 },
11644 afterFit: noop,
11645
11646 // Shared Methods
11647 isHorizontal: function() {
11648 return this.options.position === 'top' || this.options.position === 'bottom';
11649 },
11650
11651 // Actually draw the legend on the canvas
11652 draw: function() {
11653 var me = this;
11654 var opts = me.options;
11655 var labelOpts = opts.labels;
11656 var globalDefault = defaults.global;
11657 var lineDefault = globalDefault.elements.line;
11658 var legendWidth = me.width;
11659 var lineWidths = me.lineWidths;
11660
11661 if (opts.display) {
11662 var ctx = me.ctx;
11663 var valueOrDefault = helpers.valueOrDefault;
11664 var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
11665 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11666 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11667 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11668 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11669 var cursor;
11670
11671 // Canvas setup
11672 ctx.textAlign = 'left';
11673 ctx.textBaseline = 'middle';
11674 ctx.lineWidth = 0.5;
11675 ctx.strokeStyle = fontColor; // for strikethrough effect
11676 ctx.fillStyle = fontColor; // render in correct colour
11677 ctx.font = labelFont;
11678
11679 var boxWidth = getBoxWidth(labelOpts, fontSize);
11680 var hitboxes = me.legendHitBoxes;
11681
11682 // current position
11683 var drawLegendBox = function(x, y, legendItem) {
11684 if (isNaN(boxWidth) || boxWidth <= 0) {
11685 return;
11686 }
11687
11688 // Set the ctx for the box
11689 ctx.save();
11690
11691 ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
11692 ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
11693 ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
11694 ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
11695 ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
11696 ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
11697 var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
11698
11699 if (ctx.setLineDash) {
11700 // IE 9 and 10 do not support line dash
11701 ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
11702 }
11703
11704 if (opts.labels && opts.labels.usePointStyle) {
11705 // Recalculate x and y for drawPoint() because its expecting
11706 // x and y to be center of figure (instead of top left)
11707 var radius = fontSize * Math.SQRT2 / 2;
11708 var offSet = radius / Math.SQRT2;
11709 var centerX = x + offSet;
11710 var centerY = y + offSet;
11711
11712 // Draw pointStyle as legend symbol
11713 helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
11714 } else {
11715 // Draw box as legend symbol
11716 if (!isLineWidthZero) {
11717 ctx.strokeRect(x, y, boxWidth, fontSize);
11718 }
11719 ctx.fillRect(x, y, boxWidth, fontSize);
11720 }
11721
11722 ctx.restore();
11723 };
11724 var fillText = function(x, y, legendItem, textWidth) {
11725 var halfFontSize = fontSize / 2;
11726 var xLeft = boxWidth + halfFontSize + x;
11727 var yMiddle = y + halfFontSize;
11728
11729 ctx.fillText(legendItem.text, xLeft, yMiddle);
11730
11731 if (legendItem.hidden) {
11732 // Strikethrough the text if hidden
11733 ctx.beginPath();
11734 ctx.lineWidth = 2;
11735 ctx.moveTo(xLeft, yMiddle);
11736 ctx.lineTo(xLeft + textWidth, yMiddle);
11737 ctx.stroke();
11738 }
11739 };
11740
11741 // Horizontal
11742 var isHorizontal = me.isHorizontal();
11743 if (isHorizontal) {
11744 cursor = {
11745 x: me.left + ((legendWidth - lineWidths[0]) / 2),
11746 y: me.top + labelOpts.padding,
11747 line: 0
11748 };
11749 } else {
11750 cursor = {
11751 x: me.left + labelOpts.padding,
11752 y: me.top + labelOpts.padding,
11753 line: 0
11754 };
11755 }
11756
11757 var itemHeight = fontSize + labelOpts.padding;
11758 helpers.each(me.legendItems, function(legendItem, i) {
11759 var textWidth = ctx.measureText(legendItem.text).width;
11760 var width = boxWidth + (fontSize / 2) + textWidth;
11761 var x = cursor.x;
11762 var y = cursor.y;
11763
11764 if (isHorizontal) {
11765 if (x + width >= legendWidth) {
11766 y = cursor.y += itemHeight;
11767 cursor.line++;
11768 x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
11769 }
11770 } else if (y + itemHeight > me.bottom) {
11771 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
11772 y = cursor.y = me.top + labelOpts.padding;
11773 cursor.line++;
11774 }
11775
11776 drawLegendBox(x, y, legendItem);
11777
11778 hitboxes[i].left = x;
11779 hitboxes[i].top = y;
11780
11781 // Fill the actual label
11782 fillText(x, y, legendItem, textWidth);
11783
11784 if (isHorizontal) {
11785 cursor.x += width + (labelOpts.padding);
11786 } else {
11787 cursor.y += itemHeight;
11788 }
11789
11790 });
11791 }
11792 },
11793
11794 /**
11795 * Handle an event
11796 * @private
11797 * @param {IEvent} event - The event to handle
11798 * @return {Boolean} true if a change occured
11799 */
11800 handleEvent: function(e) {
11801 var me = this;
11802 var opts = me.options;
11803 var type = e.type === 'mouseup' ? 'click' : e.type;
11804 var changed = false;
11805
11806 if (type === 'mousemove') {
11807 if (!opts.onHover) {
11808 return;
11809 }
11810 } else if (type === 'click') {
11811 if (!opts.onClick) {
11812 return;
11813 }
11814 } else {
11815 return;
11816 }
11817
11818 // Chart event already has relative position in it
11819 var x = e.x;
11820 var y = e.y;
11821
11822 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
11823 // See if we are touching one of the dataset boxes
11824 var lh = me.legendHitBoxes;
11825 for (var i = 0; i < lh.length; ++i) {
11826 var hitBox = lh[i];
11827
11828 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
11829 // Touching an element
11830 if (type === 'click') {
11831 // use e.native for backwards compatibility
11832 opts.onClick.call(me, e.native, me.legendItems[i]);
11833 changed = true;
11834 break;
11835 } else if (type === 'mousemove') {
11836 // use e.native for backwards compatibility
11837 opts.onHover.call(me, e.native, me.legendItems[i]);
11838 changed = true;
11839 break;
11840 }
11841 }
11842 }
11843 }
11844
11845 return changed;
11846 }
11847 });
11848
11849 function createNewLegendAndAttach(chart, legendOpts) {
11850 var legend = new Chart.Legend({
11851 ctx: chart.ctx,
11852 options: legendOpts,
11853 chart: chart
11854 });
11855
11856 layout.configure(chart, legend, legendOpts);
11857 layout.addBox(chart, legend);
11858 chart.legend = legend;
11859 }
11860
11861 return {
11862 id: 'legend',
11863
11864 beforeInit: function(chart) {
11865 var legendOpts = chart.options.legend;
11866
11867 if (legendOpts) {
11868 createNewLegendAndAttach(chart, legendOpts);
11869 }
11870 },
11871
11872 beforeUpdate: function(chart) {
11873 var legendOpts = chart.options.legend;
11874 var legend = chart.legend;
11875
11876 if (legendOpts) {
11877 helpers.mergeIf(legendOpts, defaults.global.legend);
11878
11879 if (legend) {
11880 layout.configure(chart, legend, legendOpts);
11881 legend.options = legendOpts;
11882 } else {
11883 createNewLegendAndAttach(chart, legendOpts);
11884 }
11885 } else if (legend) {
11886 layout.removeBox(chart, legend);
11887 delete chart.legend;
11888 }
11889 },
11890
11891 afterEvent: function(chart, e) {
11892 var legend = chart.legend;
11893 if (legend) {
11894 legend.handleEvent(e);
11895 }
11896 }
11897 };
11898};
11899
11900},{"25":25,"26":26,"45":45}],51:[function(require,module,exports){
11901'use strict';
11902
11903var defaults = require(25);
11904var Element = require(26);
11905var helpers = require(45);
11906
11907defaults._set('global', {
11908 title: {
11909 display: false,
11910 fontStyle: 'bold',
11911 fullWidth: true,
11912 lineHeight: 1.2,
11913 padding: 10,
11914 position: 'top',
11915 text: '',
11916 weight: 2000 // by default greater than legend (1000) to be above
11917 }
11918});
11919
11920module.exports = function(Chart) {
11921
11922 var layout = Chart.layoutService;
11923 var noop = helpers.noop;
11924
11925 Chart.Title = Element.extend({
11926 initialize: function(config) {
11927 var me = this;
11928 helpers.extend(me, config);
11929
11930 // Contains hit boxes for each dataset (in dataset order)
11931 me.legendHitBoxes = [];
11932 },
11933
11934 // These methods are ordered by lifecycle. Utilities then follow.
11935
11936 beforeUpdate: noop,
11937 update: function(maxWidth, maxHeight, margins) {
11938 var me = this;
11939
11940 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11941 me.beforeUpdate();
11942
11943 // Absorb the master measurements
11944 me.maxWidth = maxWidth;
11945 me.maxHeight = maxHeight;
11946 me.margins = margins;
11947
11948 // Dimensions
11949 me.beforeSetDimensions();
11950 me.setDimensions();
11951 me.afterSetDimensions();
11952 // Labels
11953 me.beforeBuildLabels();
11954 me.buildLabels();
11955 me.afterBuildLabels();
11956
11957 // Fit
11958 me.beforeFit();
11959 me.fit();
11960 me.afterFit();
11961 //
11962 me.afterUpdate();
11963
11964 return me.minSize;
11965
11966 },
11967 afterUpdate: noop,
11968
11969 //
11970
11971 beforeSetDimensions: noop,
11972 setDimensions: function() {
11973 var me = this;
11974 // Set the unconstrained dimension before label rotation
11975 if (me.isHorizontal()) {
11976 // Reset position before calculating rotation
11977 me.width = me.maxWidth;
11978 me.left = 0;
11979 me.right = me.width;
11980 } else {
11981 me.height = me.maxHeight;
11982
11983 // Reset position before calculating rotation
11984 me.top = 0;
11985 me.bottom = me.height;
11986 }
11987
11988 // Reset padding
11989 me.paddingLeft = 0;
11990 me.paddingTop = 0;
11991 me.paddingRight = 0;
11992 me.paddingBottom = 0;
11993
11994 // Reset minSize
11995 me.minSize = {
11996 width: 0,
11997 height: 0
11998 };
11999 },
12000 afterSetDimensions: noop,
12001
12002 //
12003
12004 beforeBuildLabels: noop,
12005 buildLabels: noop,
12006 afterBuildLabels: noop,
12007
12008 //
12009
12010 beforeFit: noop,
12011 fit: function() {
12012 var me = this;
12013 var valueOrDefault = helpers.valueOrDefault;
12014 var opts = me.options;
12015 var display = opts.display;
12016 var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
12017 var minSize = me.minSize;
12018 var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
12019 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12020 var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
12021
12022 if (me.isHorizontal()) {
12023 minSize.width = me.maxWidth; // fill all the width
12024 minSize.height = textSize;
12025 } else {
12026 minSize.width = textSize;
12027 minSize.height = me.maxHeight; // fill all the height
12028 }
12029
12030 me.width = minSize.width;
12031 me.height = minSize.height;
12032
12033 },
12034 afterFit: noop,
12035
12036 // Shared Methods
12037 isHorizontal: function() {
12038 var pos = this.options.position;
12039 return pos === 'top' || pos === 'bottom';
12040 },
12041
12042 // Actually draw the title block on the canvas
12043 draw: function() {
12044 var me = this;
12045 var ctx = me.ctx;
12046 var valueOrDefault = helpers.valueOrDefault;
12047 var opts = me.options;
12048 var globalDefaults = defaults.global;
12049
12050 if (opts.display) {
12051 var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
12052 var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
12053 var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
12054 var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
12055 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12056 var offset = lineHeight / 2 + opts.padding;
12057 var rotation = 0;
12058 var top = me.top;
12059 var left = me.left;
12060 var bottom = me.bottom;
12061 var right = me.right;
12062 var maxWidth, titleX, titleY;
12063
12064 ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
12065 ctx.font = titleFont;
12066
12067 // Horizontal
12068 if (me.isHorizontal()) {
12069 titleX = left + ((right - left) / 2); // midpoint of the width
12070 titleY = top + offset;
12071 maxWidth = right - left;
12072 } else {
12073 titleX = opts.position === 'left' ? left + offset : right - offset;
12074 titleY = top + ((bottom - top) / 2);
12075 maxWidth = bottom - top;
12076 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
12077 }
12078
12079 ctx.save();
12080 ctx.translate(titleX, titleY);
12081 ctx.rotate(rotation);
12082 ctx.textAlign = 'center';
12083 ctx.textBaseline = 'middle';
12084
12085 var text = opts.text;
12086 if (helpers.isArray(text)) {
12087 var y = 0;
12088 for (var i = 0; i < text.length; ++i) {
12089 ctx.fillText(text[i], 0, y, maxWidth);
12090 y += lineHeight;
12091 }
12092 } else {
12093 ctx.fillText(text, 0, 0, maxWidth);
12094 }
12095
12096 ctx.restore();
12097 }
12098 }
12099 });
12100
12101 function createNewTitleBlockAndAttach(chart, titleOpts) {
12102 var title = new Chart.Title({
12103 ctx: chart.ctx,
12104 options: titleOpts,
12105 chart: chart
12106 });
12107
12108 layout.configure(chart, title, titleOpts);
12109 layout.addBox(chart, title);
12110 chart.titleBlock = title;
12111 }
12112
12113 return {
12114 id: 'title',
12115
12116 beforeInit: function(chart) {
12117 var titleOpts = chart.options.title;
12118
12119 if (titleOpts) {
12120 createNewTitleBlockAndAttach(chart, titleOpts);
12121 }
12122 },
12123
12124 beforeUpdate: function(chart) {
12125 var titleOpts = chart.options.title;
12126 var titleBlock = chart.titleBlock;
12127
12128 if (titleOpts) {
12129 helpers.mergeIf(titleOpts, defaults.global.title);
12130
12131 if (titleBlock) {
12132 layout.configure(chart, titleBlock, titleOpts);
12133 titleBlock.options = titleOpts;
12134 } else {
12135 createNewTitleBlockAndAttach(chart, titleOpts);
12136 }
12137 } else if (titleBlock) {
12138 Chart.layoutService.removeBox(chart, titleBlock);
12139 delete chart.titleBlock;
12140 }
12141 }
12142 };
12143};
12144
12145},{"25":25,"26":26,"45":45}],52:[function(require,module,exports){
12146'use strict';
12147
12148module.exports = function(Chart) {
12149
12150 // Default config for a category scale
12151 var defaultConfig = {
12152 position: 'bottom'
12153 };
12154
12155 var DatasetScale = Chart.Scale.extend({
12156 /**
12157 * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
12158 * else fall back to data.labels
12159 * @private
12160 */
12161 getLabels: function() {
12162 var data = this.chart.data;
12163 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
12164 },
12165
12166 determineDataLimits: function() {
12167 var me = this;
12168 var labels = me.getLabels();
12169 me.minIndex = 0;
12170 me.maxIndex = labels.length - 1;
12171 var findIndex;
12172
12173 if (me.options.ticks.min !== undefined) {
12174 // user specified min value
12175 findIndex = labels.indexOf(me.options.ticks.min);
12176 me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
12177 }
12178
12179 if (me.options.ticks.max !== undefined) {
12180 // user specified max value
12181 findIndex = labels.indexOf(me.options.ticks.max);
12182 me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
12183 }
12184
12185 me.min = labels[me.minIndex];
12186 me.max = labels[me.maxIndex];
12187 },
12188
12189 buildTicks: function() {
12190 var me = this;
12191 var labels = me.getLabels();
12192 // If we are viewing some subset of labels, slice the original array
12193 me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
12194 },
12195
12196 getLabelForIndex: function(index, datasetIndex) {
12197 var me = this;
12198 var data = me.chart.data;
12199 var isHorizontal = me.isHorizontal();
12200
12201 if (data.yLabels && !isHorizontal) {
12202 return me.getRightValue(data.datasets[datasetIndex].data[index]);
12203 }
12204 return me.ticks[index - me.minIndex];
12205 },
12206
12207 // Used to get data value locations. Value can either be an index or a numerical value
12208 getPixelForValue: function(value, index) {
12209 var me = this;
12210 var offset = me.options.offset;
12211 // 1 is added because we need the length but we have the indexes
12212 var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
12213
12214 // If value is a data object, then index is the index in the data array,
12215 // not the index of the scale. We need to change that.
12216 var valueCategory;
12217 if (value !== undefined && value !== null) {
12218 valueCategory = me.isHorizontal() ? value.x : value.y;
12219 }
12220 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
12221 var labels = me.getLabels();
12222 value = valueCategory || value;
12223 var idx = labels.indexOf(value);
12224 index = idx !== -1 ? idx : index;
12225 }
12226
12227 if (me.isHorizontal()) {
12228 var valueWidth = me.width / offsetAmt;
12229 var widthOffset = (valueWidth * (index - me.minIndex));
12230
12231 if (offset) {
12232 widthOffset += (valueWidth / 2);
12233 }
12234
12235 return me.left + Math.round(widthOffset);
12236 }
12237 var valueHeight = me.height / offsetAmt;
12238 var heightOffset = (valueHeight * (index - me.minIndex));
12239
12240 if (offset) {
12241 heightOffset += (valueHeight / 2);
12242 }
12243
12244 return me.top + Math.round(heightOffset);
12245 },
12246 getPixelForTick: function(index) {
12247 return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
12248 },
12249 getValueForPixel: function(pixel) {
12250 var me = this;
12251 var offset = me.options.offset;
12252 var value;
12253 var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
12254 var horz = me.isHorizontal();
12255 var valueDimension = (horz ? me.width : me.height) / offsetAmt;
12256
12257 pixel -= horz ? me.left : me.top;
12258
12259 if (offset) {
12260 pixel -= (valueDimension / 2);
12261 }
12262
12263 if (pixel <= 0) {
12264 value = 0;
12265 } else {
12266 value = Math.round(pixel / valueDimension);
12267 }
12268
12269 return value + me.minIndex;
12270 },
12271 getBasePixel: function() {
12272 return this.bottom;
12273 }
12274 });
12275
12276 Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
12277
12278};
12279
12280},{}],53:[function(require,module,exports){
12281'use strict';
12282
12283var defaults = require(25);
12284var helpers = require(45);
12285var Ticks = require(34);
12286
12287module.exports = function(Chart) {
12288
12289 var defaultConfig = {
12290 position: 'left',
12291 ticks: {
12292 callback: Ticks.formatters.linear
12293 }
12294 };
12295
12296 var LinearScale = Chart.LinearScaleBase.extend({
12297
12298 determineDataLimits: function() {
12299 var me = this;
12300 var opts = me.options;
12301 var chart = me.chart;
12302 var data = chart.data;
12303 var datasets = data.datasets;
12304 var isHorizontal = me.isHorizontal();
12305 var DEFAULT_MIN = 0;
12306 var DEFAULT_MAX = 1;
12307
12308 function IDMatches(meta) {
12309 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12310 }
12311
12312 // First Calculate the range
12313 me.min = null;
12314 me.max = null;
12315
12316 var hasStacks = opts.stacked;
12317 if (hasStacks === undefined) {
12318 helpers.each(datasets, function(dataset, datasetIndex) {
12319 if (hasStacks) {
12320 return;
12321 }
12322
12323 var meta = chart.getDatasetMeta(datasetIndex);
12324 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12325 meta.stack !== undefined) {
12326 hasStacks = true;
12327 }
12328 });
12329 }
12330
12331 if (opts.stacked || hasStacks) {
12332 var valuesPerStack = {};
12333
12334 helpers.each(datasets, function(dataset, datasetIndex) {
12335 var meta = chart.getDatasetMeta(datasetIndex);
12336 var key = [
12337 meta.type,
12338 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12339 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12340 meta.stack
12341 ].join('.');
12342
12343 if (valuesPerStack[key] === undefined) {
12344 valuesPerStack[key] = {
12345 positiveValues: [],
12346 negativeValues: []
12347 };
12348 }
12349
12350 // Store these per type
12351 var positiveValues = valuesPerStack[key].positiveValues;
12352 var negativeValues = valuesPerStack[key].negativeValues;
12353
12354 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12355 helpers.each(dataset.data, function(rawValue, index) {
12356 var value = +me.getRightValue(rawValue);
12357 if (isNaN(value) || meta.data[index].hidden) {
12358 return;
12359 }
12360
12361 positiveValues[index] = positiveValues[index] || 0;
12362 negativeValues[index] = negativeValues[index] || 0;
12363
12364 if (opts.relativePoints) {
12365 positiveValues[index] = 100;
12366 } else if (value < 0) {
12367 negativeValues[index] += value;
12368 } else {
12369 positiveValues[index] += value;
12370 }
12371 });
12372 }
12373 });
12374
12375 helpers.each(valuesPerStack, function(valuesForType) {
12376 var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
12377 var minVal = helpers.min(values);
12378 var maxVal = helpers.max(values);
12379 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12380 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12381 });
12382
12383 } else {
12384 helpers.each(datasets, function(dataset, datasetIndex) {
12385 var meta = chart.getDatasetMeta(datasetIndex);
12386 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12387 helpers.each(dataset.data, function(rawValue, index) {
12388 var value = +me.getRightValue(rawValue);
12389 if (isNaN(value) || meta.data[index].hidden) {
12390 return;
12391 }
12392
12393 if (me.min === null) {
12394 me.min = value;
12395 } else if (value < me.min) {
12396 me.min = value;
12397 }
12398
12399 if (me.max === null) {
12400 me.max = value;
12401 } else if (value > me.max) {
12402 me.max = value;
12403 }
12404 });
12405 }
12406 });
12407 }
12408
12409 me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
12410 me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
12411
12412 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
12413 this.handleTickRangeOptions();
12414 },
12415 getTickLimit: function() {
12416 var maxTicks;
12417 var me = this;
12418 var tickOpts = me.options.ticks;
12419
12420 if (me.isHorizontal()) {
12421 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
12422 } else {
12423 // The factor of 2 used to scale the font size has been experimentally determined.
12424 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize);
12425 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
12426 }
12427
12428 return maxTicks;
12429 },
12430 // Called after the ticks are built. We need
12431 handleDirectionalChanges: function() {
12432 if (!this.isHorizontal()) {
12433 // We are in a vertical orientation. The top value is the highest. So reverse the array
12434 this.ticks.reverse();
12435 }
12436 },
12437 getLabelForIndex: function(index, datasetIndex) {
12438 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12439 },
12440 // Utils
12441 getPixelForValue: function(value) {
12442 // This must be called after fit has been run so that
12443 // this.left, this.top, this.right, and this.bottom have been defined
12444 var me = this;
12445 var start = me.start;
12446
12447 var rightValue = +me.getRightValue(value);
12448 var pixel;
12449 var range = me.end - start;
12450
12451 if (me.isHorizontal()) {
12452 pixel = me.left + (me.width / range * (rightValue - start));
12453 return Math.round(pixel);
12454 }
12455
12456 pixel = me.bottom - (me.height / range * (rightValue - start));
12457 return Math.round(pixel);
12458 },
12459 getValueForPixel: function(pixel) {
12460 var me = this;
12461 var isHorizontal = me.isHorizontal();
12462 var innerDimension = isHorizontal ? me.width : me.height;
12463 var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
12464 return me.start + ((me.end - me.start) * offset);
12465 },
12466 getPixelForTick: function(index) {
12467 return this.getPixelForValue(this.ticksAsNumbers[index]);
12468 }
12469 });
12470 Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
12471
12472};
12473
12474},{"25":25,"34":34,"45":45}],54:[function(require,module,exports){
12475'use strict';
12476
12477var helpers = require(45);
12478var Ticks = require(34);
12479
12480module.exports = function(Chart) {
12481
12482 var noop = helpers.noop;
12483
12484 Chart.LinearScaleBase = Chart.Scale.extend({
12485 getRightValue: function(value) {
12486 if (typeof value === 'string') {
12487 return +value;
12488 }
12489 return Chart.Scale.prototype.getRightValue.call(this, value);
12490 },
12491
12492 handleTickRangeOptions: function() {
12493 var me = this;
12494 var opts = me.options;
12495 var tickOpts = opts.ticks;
12496
12497 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
12498 // do nothing since that would make the chart weird. If the user really wants a weird chart
12499 // axis, they can manually override it
12500 if (tickOpts.beginAtZero) {
12501 var minSign = helpers.sign(me.min);
12502 var maxSign = helpers.sign(me.max);
12503
12504 if (minSign < 0 && maxSign < 0) {
12505 // move the top up to 0
12506 me.max = 0;
12507 } else if (minSign > 0 && maxSign > 0) {
12508 // move the bottom down to 0
12509 me.min = 0;
12510 }
12511 }
12512
12513 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
12514 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
12515
12516 if (tickOpts.min !== undefined) {
12517 me.min = tickOpts.min;
12518 } else if (tickOpts.suggestedMin !== undefined) {
12519 if (me.min === null) {
12520 me.min = tickOpts.suggestedMin;
12521 } else {
12522 me.min = Math.min(me.min, tickOpts.suggestedMin);
12523 }
12524 }
12525
12526 if (tickOpts.max !== undefined) {
12527 me.max = tickOpts.max;
12528 } else if (tickOpts.suggestedMax !== undefined) {
12529 if (me.max === null) {
12530 me.max = tickOpts.suggestedMax;
12531 } else {
12532 me.max = Math.max(me.max, tickOpts.suggestedMax);
12533 }
12534 }
12535
12536 if (setMin !== setMax) {
12537 // We set the min or the max but not both.
12538 // So ensure that our range is good
12539 // Inverted or 0 length range can happen when
12540 // ticks.min is set, and no datasets are visible
12541 if (me.min >= me.max) {
12542 if (setMin) {
12543 me.max = me.min + 1;
12544 } else {
12545 me.min = me.max - 1;
12546 }
12547 }
12548 }
12549
12550 if (me.min === me.max) {
12551 me.max++;
12552
12553 if (!tickOpts.beginAtZero) {
12554 me.min--;
12555 }
12556 }
12557 },
12558 getTickLimit: noop,
12559 handleDirectionalChanges: noop,
12560
12561 buildTicks: function() {
12562 var me = this;
12563 var opts = me.options;
12564 var tickOpts = opts.ticks;
12565
12566 // Figure out what the max number of ticks we can support it is based on the size of
12567 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
12568 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
12569 // the graph. Make sure we always have at least 2 ticks
12570 var maxTicks = me.getTickLimit();
12571 maxTicks = Math.max(2, maxTicks);
12572
12573 var numericGeneratorOptions = {
12574 maxTicks: maxTicks,
12575 min: tickOpts.min,
12576 max: tickOpts.max,
12577 stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
12578 };
12579 var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me);
12580
12581 me.handleDirectionalChanges();
12582
12583 // At this point, we need to update our max and min given the tick values since we have expanded the
12584 // range of the scale
12585 me.max = helpers.max(ticks);
12586 me.min = helpers.min(ticks);
12587
12588 if (tickOpts.reverse) {
12589 ticks.reverse();
12590
12591 me.start = me.max;
12592 me.end = me.min;
12593 } else {
12594 me.start = me.min;
12595 me.end = me.max;
12596 }
12597 },
12598 convertTicksToLabels: function() {
12599 var me = this;
12600 me.ticksAsNumbers = me.ticks.slice();
12601 me.zeroLineIndex = me.ticks.indexOf(0);
12602
12603 Chart.Scale.prototype.convertTicksToLabels.call(me);
12604 }
12605 });
12606};
12607
12608},{"34":34,"45":45}],55:[function(require,module,exports){
12609'use strict';
12610
12611var helpers = require(45);
12612var Ticks = require(34);
12613
12614module.exports = function(Chart) {
12615
12616 var defaultConfig = {
12617 position: 'left',
12618
12619 // label settings
12620 ticks: {
12621 callback: Ticks.formatters.logarithmic
12622 }
12623 };
12624
12625 var LogarithmicScale = Chart.Scale.extend({
12626 determineDataLimits: function() {
12627 var me = this;
12628 var opts = me.options;
12629 var tickOpts = opts.ticks;
12630 var chart = me.chart;
12631 var data = chart.data;
12632 var datasets = data.datasets;
12633 var valueOrDefault = helpers.valueOrDefault;
12634 var isHorizontal = me.isHorizontal();
12635 function IDMatches(meta) {
12636 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12637 }
12638
12639 // Calculate Range
12640 me.min = null;
12641 me.max = null;
12642 me.minNotZero = null;
12643
12644 var hasStacks = opts.stacked;
12645 if (hasStacks === undefined) {
12646 helpers.each(datasets, function(dataset, datasetIndex) {
12647 if (hasStacks) {
12648 return;
12649 }
12650
12651 var meta = chart.getDatasetMeta(datasetIndex);
12652 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12653 meta.stack !== undefined) {
12654 hasStacks = true;
12655 }
12656 });
12657 }
12658
12659 if (opts.stacked || hasStacks) {
12660 var valuesPerStack = {};
12661
12662 helpers.each(datasets, function(dataset, datasetIndex) {
12663 var meta = chart.getDatasetMeta(datasetIndex);
12664 var key = [
12665 meta.type,
12666 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12667 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12668 meta.stack
12669 ].join('.');
12670
12671 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12672 if (valuesPerStack[key] === undefined) {
12673 valuesPerStack[key] = [];
12674 }
12675
12676 helpers.each(dataset.data, function(rawValue, index) {
12677 var values = valuesPerStack[key];
12678 var value = +me.getRightValue(rawValue);
12679 if (isNaN(value) || meta.data[index].hidden) {
12680 return;
12681 }
12682
12683 values[index] = values[index] || 0;
12684
12685 if (opts.relativePoints) {
12686 values[index] = 100;
12687 } else {
12688 // Don't need to split positive and negative since the log scale can't handle a 0 crossing
12689 values[index] += value;
12690 }
12691 });
12692 }
12693 });
12694
12695 helpers.each(valuesPerStack, function(valuesForType) {
12696 var minVal = helpers.min(valuesForType);
12697 var maxVal = helpers.max(valuesForType);
12698 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12699 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12700 });
12701
12702 } else {
12703 helpers.each(datasets, function(dataset, datasetIndex) {
12704 var meta = chart.getDatasetMeta(datasetIndex);
12705 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12706 helpers.each(dataset.data, function(rawValue, index) {
12707 var value = +me.getRightValue(rawValue);
12708 if (isNaN(value) || meta.data[index].hidden) {
12709 return;
12710 }
12711
12712 if (me.min === null) {
12713 me.min = value;
12714 } else if (value < me.min) {
12715 me.min = value;
12716 }
12717
12718 if (me.max === null) {
12719 me.max = value;
12720 } else if (value > me.max) {
12721 me.max = value;
12722 }
12723
12724 if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
12725 me.minNotZero = value;
12726 }
12727 });
12728 }
12729 });
12730 }
12731
12732 me.min = valueOrDefault(tickOpts.min, me.min);
12733 me.max = valueOrDefault(tickOpts.max, me.max);
12734
12735 if (me.min === me.max) {
12736 if (me.min !== 0 && me.min !== null) {
12737 me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
12738 me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
12739 } else {
12740 me.min = 1;
12741 me.max = 10;
12742 }
12743 }
12744 },
12745 buildTicks: function() {
12746 var me = this;
12747 var opts = me.options;
12748 var tickOpts = opts.ticks;
12749
12750 var generationOptions = {
12751 min: tickOpts.min,
12752 max: tickOpts.max
12753 };
12754 var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me);
12755
12756 if (!me.isHorizontal()) {
12757 // We are in a vertical orientation. The top value is the highest. So reverse the array
12758 ticks.reverse();
12759 }
12760
12761 // At this point, we need to update our max and min given the tick values since we have expanded the
12762 // range of the scale
12763 me.max = helpers.max(ticks);
12764 me.min = helpers.min(ticks);
12765
12766 if (tickOpts.reverse) {
12767 ticks.reverse();
12768
12769 me.start = me.max;
12770 me.end = me.min;
12771 } else {
12772 me.start = me.min;
12773 me.end = me.max;
12774 }
12775 },
12776 convertTicksToLabels: function() {
12777 this.tickValues = this.ticks.slice();
12778
12779 Chart.Scale.prototype.convertTicksToLabels.call(this);
12780 },
12781 // Get the correct tooltip label
12782 getLabelForIndex: function(index, datasetIndex) {
12783 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12784 },
12785 getPixelForTick: function(index) {
12786 return this.getPixelForValue(this.tickValues[index]);
12787 },
12788 getPixelForValue: function(value) {
12789 var me = this;
12790 var start = me.start;
12791 var newVal = +me.getRightValue(value);
12792 var opts = me.options;
12793 var tickOpts = opts.ticks;
12794 var innerDimension, pixel, range;
12795
12796 if (me.isHorizontal()) {
12797 range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
12798 if (newVal === 0) {
12799 pixel = me.left;
12800 } else {
12801 innerDimension = me.width;
12802 pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12803 }
12804 } else {
12805 // Bottom - top since pixels increase downward on a screen
12806 innerDimension = me.height;
12807 if (start === 0 && !tickOpts.reverse) {
12808 range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
12809 if (newVal === start) {
12810 pixel = me.bottom;
12811 } else if (newVal === me.minNotZero) {
12812 pixel = me.bottom - innerDimension * 0.02;
12813 } else {
12814 pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12815 }
12816 } else if (me.end === 0 && tickOpts.reverse) {
12817 range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
12818 if (newVal === me.end) {
12819 pixel = me.top;
12820 } else if (newVal === me.minNotZero) {
12821 pixel = me.top + innerDimension * 0.02;
12822 } else {
12823 pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12824 }
12825 } else if (newVal === 0) {
12826 pixel = tickOpts.reverse ? me.top : me.bottom;
12827 } else {
12828 range = helpers.log10(me.end) - helpers.log10(start);
12829 innerDimension = me.height;
12830 pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12831 }
12832 }
12833 return pixel;
12834 },
12835 getValueForPixel: function(pixel) {
12836 var me = this;
12837 var range = helpers.log10(me.end) - helpers.log10(me.start);
12838 var value, innerDimension;
12839
12840 if (me.isHorizontal()) {
12841 innerDimension = me.width;
12842 value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);
12843 } else { // todo: if start === 0
12844 innerDimension = me.height;
12845 value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;
12846 }
12847 return value;
12848 }
12849 });
12850 Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
12851
12852};
12853
12854},{"34":34,"45":45}],56:[function(require,module,exports){
12855'use strict';
12856
12857var defaults = require(25);
12858var helpers = require(45);
12859var Ticks = require(34);
12860
12861module.exports = function(Chart) {
12862
12863 var globalDefaults = defaults.global;
12864
12865 var defaultConfig = {
12866 display: true,
12867
12868 // Boolean - Whether to animate scaling the chart from the centre
12869 animate: true,
12870 position: 'chartArea',
12871
12872 angleLines: {
12873 display: true,
12874 color: 'rgba(0, 0, 0, 0.1)',
12875 lineWidth: 1
12876 },
12877
12878 gridLines: {
12879 circular: false
12880 },
12881
12882 // label settings
12883 ticks: {
12884 // Boolean - Show a backdrop to the scale label
12885 showLabelBackdrop: true,
12886
12887 // String - The colour of the label backdrop
12888 backdropColor: 'rgba(255,255,255,0.75)',
12889
12890 // Number - The backdrop padding above & below the label in pixels
12891 backdropPaddingY: 2,
12892
12893 // Number - The backdrop padding to the side of the label in pixels
12894 backdropPaddingX: 2,
12895
12896 callback: Ticks.formatters.linear
12897 },
12898
12899 pointLabels: {
12900 // Boolean - if true, show point labels
12901 display: true,
12902
12903 // Number - Point label font size in pixels
12904 fontSize: 10,
12905
12906 // Function - Used to convert point labels
12907 callback: function(label) {
12908 return label;
12909 }
12910 }
12911 };
12912
12913 function getValueCount(scale) {
12914 var opts = scale.options;
12915 return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
12916 }
12917
12918 function getPointLabelFontOptions(scale) {
12919 var pointLabelOptions = scale.options.pointLabels;
12920 var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
12921 var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
12922 var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
12923 var font = helpers.fontString(fontSize, fontStyle, fontFamily);
12924
12925 return {
12926 size: fontSize,
12927 style: fontStyle,
12928 family: fontFamily,
12929 font: font
12930 };
12931 }
12932
12933 function measureLabelSize(ctx, fontSize, label) {
12934 if (helpers.isArray(label)) {
12935 return {
12936 w: helpers.longestText(ctx, ctx.font, label),
12937 h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
12938 };
12939 }
12940
12941 return {
12942 w: ctx.measureText(label).width,
12943 h: fontSize
12944 };
12945 }
12946
12947 function determineLimits(angle, pos, size, min, max) {
12948 if (angle === min || angle === max) {
12949 return {
12950 start: pos - (size / 2),
12951 end: pos + (size / 2)
12952 };
12953 } else if (angle < min || angle > max) {
12954 return {
12955 start: pos - size - 5,
12956 end: pos
12957 };
12958 }
12959
12960 return {
12961 start: pos,
12962 end: pos + size + 5
12963 };
12964 }
12965
12966 /**
12967 * Helper function to fit a radial linear scale with point labels
12968 */
12969 function fitWithPointLabels(scale) {
12970 /*
12971 * Right, this is really confusing and there is a lot of maths going on here
12972 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
12973 *
12974 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
12975 *
12976 * Solution:
12977 *
12978 * We assume the radius of the polygon is half the size of the canvas at first
12979 * at each index we check if the text overlaps.
12980 *
12981 * Where it does, we store that angle and that index.
12982 *
12983 * After finding the largest index and angle we calculate how much we need to remove
12984 * from the shape radius to move the point inwards by that x.
12985 *
12986 * We average the left and right distances to get the maximum shape radius that can fit in the box
12987 * along with labels.
12988 *
12989 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
12990 * on each side, removing that from the size, halving it and adding the left x protrusion width.
12991 *
12992 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
12993 * and position it in the most space efficient manner
12994 *
12995 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
12996 */
12997
12998 var plFont = getPointLabelFontOptions(scale);
12999
13000 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
13001 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
13002 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13003 var furthestLimits = {
13004 r: scale.width,
13005 l: 0,
13006 t: scale.height,
13007 b: 0
13008 };
13009 var furthestAngles = {};
13010 var i, textSize, pointPosition;
13011
13012 scale.ctx.font = plFont.font;
13013 scale._pointLabelSizes = [];
13014
13015 var valueCount = getValueCount(scale);
13016 for (i = 0; i < valueCount; i++) {
13017 pointPosition = scale.getPointPosition(i, largestPossibleRadius);
13018 textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
13019 scale._pointLabelSizes[i] = textSize;
13020
13021 // Add quarter circle to make degree 0 mean top of circle
13022 var angleRadians = scale.getIndexAngle(i);
13023 var angle = helpers.toDegrees(angleRadians) % 360;
13024 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
13025 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
13026
13027 if (hLimits.start < furthestLimits.l) {
13028 furthestLimits.l = hLimits.start;
13029 furthestAngles.l = angleRadians;
13030 }
13031
13032 if (hLimits.end > furthestLimits.r) {
13033 furthestLimits.r = hLimits.end;
13034 furthestAngles.r = angleRadians;
13035 }
13036
13037 if (vLimits.start < furthestLimits.t) {
13038 furthestLimits.t = vLimits.start;
13039 furthestAngles.t = angleRadians;
13040 }
13041
13042 if (vLimits.end > furthestLimits.b) {
13043 furthestLimits.b = vLimits.end;
13044 furthestAngles.b = angleRadians;
13045 }
13046 }
13047
13048 scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
13049 }
13050
13051 /**
13052 * Helper function to fit a radial linear scale with no point labels
13053 */
13054 function fit(scale) {
13055 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13056 scale.drawingArea = Math.round(largestPossibleRadius);
13057 scale.setCenterPoint(0, 0, 0, 0);
13058 }
13059
13060 function getTextAlignForAngle(angle) {
13061 if (angle === 0 || angle === 180) {
13062 return 'center';
13063 } else if (angle < 180) {
13064 return 'left';
13065 }
13066
13067 return 'right';
13068 }
13069
13070 function fillText(ctx, text, position, fontSize) {
13071 if (helpers.isArray(text)) {
13072 var y = position.y;
13073 var spacing = 1.5 * fontSize;
13074
13075 for (var i = 0; i < text.length; ++i) {
13076 ctx.fillText(text[i], position.x, y);
13077 y += spacing;
13078 }
13079 } else {
13080 ctx.fillText(text, position.x, position.y);
13081 }
13082 }
13083
13084 function adjustPointPositionForLabelHeight(angle, textSize, position) {
13085 if (angle === 90 || angle === 270) {
13086 position.y -= (textSize.h / 2);
13087 } else if (angle > 270 || angle < 90) {
13088 position.y -= textSize.h;
13089 }
13090 }
13091
13092 function drawPointLabels(scale) {
13093 var ctx = scale.ctx;
13094 var valueOrDefault = helpers.valueOrDefault;
13095 var opts = scale.options;
13096 var angleLineOpts = opts.angleLines;
13097 var pointLabelOpts = opts.pointLabels;
13098
13099 ctx.lineWidth = angleLineOpts.lineWidth;
13100 ctx.strokeStyle = angleLineOpts.color;
13101
13102 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
13103
13104 // Point Label Font
13105 var plFont = getPointLabelFontOptions(scale);
13106
13107 ctx.textBaseline = 'top';
13108
13109 for (var i = getValueCount(scale) - 1; i >= 0; i--) {
13110 if (angleLineOpts.display) {
13111 var outerPosition = scale.getPointPosition(i, outerDistance);
13112 ctx.beginPath();
13113 ctx.moveTo(scale.xCenter, scale.yCenter);
13114 ctx.lineTo(outerPosition.x, outerPosition.y);
13115 ctx.stroke();
13116 ctx.closePath();
13117 }
13118
13119 if (pointLabelOpts.display) {
13120 // Extra 3px out for some label spacing
13121 var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
13122
13123 // Keep this in loop since we may support array properties here
13124 var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
13125 ctx.font = plFont.font;
13126 ctx.fillStyle = pointLabelFontColor;
13127
13128 var angleRadians = scale.getIndexAngle(i);
13129 var angle = helpers.toDegrees(angleRadians);
13130 ctx.textAlign = getTextAlignForAngle(angle);
13131 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
13132 fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
13133 }
13134 }
13135 }
13136
13137 function drawRadiusLine(scale, gridLineOpts, radius, index) {
13138 var ctx = scale.ctx;
13139 ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1);
13140 ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
13141
13142 if (scale.options.gridLines.circular) {
13143 // Draw circular arcs between the points
13144 ctx.beginPath();
13145 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
13146 ctx.closePath();
13147 ctx.stroke();
13148 } else {
13149 // Draw straight lines connecting each index
13150 var valueCount = getValueCount(scale);
13151
13152 if (valueCount === 0) {
13153 return;
13154 }
13155
13156 ctx.beginPath();
13157 var pointPosition = scale.getPointPosition(0, radius);
13158 ctx.moveTo(pointPosition.x, pointPosition.y);
13159
13160 for (var i = 1; i < valueCount; i++) {
13161 pointPosition = scale.getPointPosition(i, radius);
13162 ctx.lineTo(pointPosition.x, pointPosition.y);
13163 }
13164
13165 ctx.closePath();
13166 ctx.stroke();
13167 }
13168 }
13169
13170 function numberOrZero(param) {
13171 return helpers.isNumber(param) ? param : 0;
13172 }
13173
13174 var LinearRadialScale = Chart.LinearScaleBase.extend({
13175 setDimensions: function() {
13176 var me = this;
13177 var opts = me.options;
13178 var tickOpts = opts.ticks;
13179 // Set the unconstrained dimension before label rotation
13180 me.width = me.maxWidth;
13181 me.height = me.maxHeight;
13182 me.xCenter = Math.round(me.width / 2);
13183 me.yCenter = Math.round(me.height / 2);
13184
13185 var minSize = helpers.min([me.height, me.width]);
13186 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13187 me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
13188 },
13189 determineDataLimits: function() {
13190 var me = this;
13191 var chart = me.chart;
13192 var min = Number.POSITIVE_INFINITY;
13193 var max = Number.NEGATIVE_INFINITY;
13194
13195 helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
13196 if (chart.isDatasetVisible(datasetIndex)) {
13197 var meta = chart.getDatasetMeta(datasetIndex);
13198
13199 helpers.each(dataset.data, function(rawValue, index) {
13200 var value = +me.getRightValue(rawValue);
13201 if (isNaN(value) || meta.data[index].hidden) {
13202 return;
13203 }
13204
13205 min = Math.min(value, min);
13206 max = Math.max(value, max);
13207 });
13208 }
13209 });
13210
13211 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
13212 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
13213
13214 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13215 me.handleTickRangeOptions();
13216 },
13217 getTickLimit: function() {
13218 var tickOpts = this.options.ticks;
13219 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13220 return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
13221 },
13222 convertTicksToLabels: function() {
13223 var me = this;
13224
13225 Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
13226
13227 // Point labels
13228 me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
13229 },
13230 getLabelForIndex: function(index, datasetIndex) {
13231 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
13232 },
13233 fit: function() {
13234 if (this.options.pointLabels.display) {
13235 fitWithPointLabels(this);
13236 } else {
13237 fit(this);
13238 }
13239 },
13240 /**
13241 * Set radius reductions and determine new radius and center point
13242 * @private
13243 */
13244 setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
13245 var me = this;
13246 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
13247 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
13248 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
13249 var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
13250
13251 radiusReductionLeft = numberOrZero(radiusReductionLeft);
13252 radiusReductionRight = numberOrZero(radiusReductionRight);
13253 radiusReductionTop = numberOrZero(radiusReductionTop);
13254 radiusReductionBottom = numberOrZero(radiusReductionBottom);
13255
13256 me.drawingArea = Math.min(
13257 Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
13258 Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
13259 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
13260 },
13261 setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
13262 var me = this;
13263 var maxRight = me.width - rightMovement - me.drawingArea;
13264 var maxLeft = leftMovement + me.drawingArea;
13265 var maxTop = topMovement + me.drawingArea;
13266 var maxBottom = me.height - bottomMovement - me.drawingArea;
13267
13268 me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
13269 me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
13270 },
13271
13272 getIndexAngle: function(index) {
13273 var angleMultiplier = (Math.PI * 2) / getValueCount(this);
13274 var startAngle = this.chart.options && this.chart.options.startAngle ?
13275 this.chart.options.startAngle :
13276 0;
13277
13278 var startAngleRadians = startAngle * Math.PI * 2 / 360;
13279
13280 // Start from the top instead of right, so remove a quarter of the circle
13281 return index * angleMultiplier + startAngleRadians;
13282 },
13283 getDistanceFromCenterForValue: function(value) {
13284 var me = this;
13285
13286 if (value === null) {
13287 return 0; // null always in center
13288 }
13289
13290 // Take into account half font size + the yPadding of the top value
13291 var scalingFactor = me.drawingArea / (me.max - me.min);
13292 if (me.options.ticks.reverse) {
13293 return (me.max - value) * scalingFactor;
13294 }
13295 return (value - me.min) * scalingFactor;
13296 },
13297 getPointPosition: function(index, distanceFromCenter) {
13298 var me = this;
13299 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
13300 return {
13301 x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
13302 y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
13303 };
13304 },
13305 getPointPositionForValue: function(index, value) {
13306 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
13307 },
13308
13309 getBasePosition: function() {
13310 var me = this;
13311 var min = me.min;
13312 var max = me.max;
13313
13314 return me.getPointPositionForValue(0,
13315 me.beginAtZero ? 0 :
13316 min < 0 && max < 0 ? max :
13317 min > 0 && max > 0 ? min :
13318 0);
13319 },
13320
13321 draw: function() {
13322 var me = this;
13323 var opts = me.options;
13324 var gridLineOpts = opts.gridLines;
13325 var tickOpts = opts.ticks;
13326 var valueOrDefault = helpers.valueOrDefault;
13327
13328 if (opts.display) {
13329 var ctx = me.ctx;
13330 var startAngle = this.getIndexAngle(0);
13331
13332 // Tick Font
13333 var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13334 var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
13335 var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
13336 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
13337
13338 helpers.each(me.ticks, function(label, index) {
13339 // Don't draw a centre value (if it is minimum)
13340 if (index > 0 || tickOpts.reverse) {
13341 var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13342
13343 // Draw circular lines around the scale
13344 if (gridLineOpts.display && index !== 0) {
13345 drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
13346 }
13347
13348 if (tickOpts.display) {
13349 var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
13350 ctx.font = tickLabelFont;
13351
13352 ctx.save();
13353 ctx.translate(me.xCenter, me.yCenter);
13354 ctx.rotate(startAngle);
13355
13356 if (tickOpts.showLabelBackdrop) {
13357 var labelWidth = ctx.measureText(label).width;
13358 ctx.fillStyle = tickOpts.backdropColor;
13359 ctx.fillRect(
13360 -labelWidth / 2 - tickOpts.backdropPaddingX,
13361 -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY,
13362 labelWidth + tickOpts.backdropPaddingX * 2,
13363 tickFontSize + tickOpts.backdropPaddingY * 2
13364 );
13365 }
13366
13367 ctx.textAlign = 'center';
13368 ctx.textBaseline = 'middle';
13369 ctx.fillStyle = tickFontColor;
13370 ctx.fillText(label, 0, -yCenterOffset);
13371 ctx.restore();
13372 }
13373 }
13374 });
13375
13376 if (opts.angleLines.display || opts.pointLabels.display) {
13377 drawPointLabels(me);
13378 }
13379 }
13380 }
13381 });
13382 Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
13383
13384};
13385
13386},{"25":25,"34":34,"45":45}],57:[function(require,module,exports){
13387/* global window: false */
13388'use strict';
13389
13390var moment = require(1);
13391moment = typeof moment === 'function' ? moment : window.moment;
13392
13393var defaults = require(25);
13394var helpers = require(45);
13395
13396// Integer constants are from the ES6 spec.
13397var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
13398var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
13399
13400var INTERVALS = {
13401 millisecond: {
13402 common: true,
13403 size: 1,
13404 steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
13405 },
13406 second: {
13407 common: true,
13408 size: 1000,
13409 steps: [1, 2, 5, 10, 30]
13410 },
13411 minute: {
13412 common: true,
13413 size: 60000,
13414 steps: [1, 2, 5, 10, 30]
13415 },
13416 hour: {
13417 common: true,
13418 size: 3600000,
13419 steps: [1, 2, 3, 6, 12]
13420 },
13421 day: {
13422 common: true,
13423 size: 86400000,
13424 steps: [1, 2, 5]
13425 },
13426 week: {
13427 common: false,
13428 size: 604800000,
13429 steps: [1, 2, 3, 4]
13430 },
13431 month: {
13432 common: true,
13433 size: 2.628e9,
13434 steps: [1, 2, 3]
13435 },
13436 quarter: {
13437 common: false,
13438 size: 7.884e9,
13439 steps: [1, 2, 3, 4]
13440 },
13441 year: {
13442 common: true,
13443 size: 3.154e10
13444 }
13445};
13446
13447var UNITS = Object.keys(INTERVALS);
13448
13449function sorter(a, b) {
13450 return a - b;
13451}
13452
13453function arrayUnique(items) {
13454 var hash = {};
13455 var out = [];
13456 var i, ilen, item;
13457
13458 for (i = 0, ilen = items.length; i < ilen; ++i) {
13459 item = items[i];
13460 if (!hash[item]) {
13461 hash[item] = true;
13462 out.push(item);
13463 }
13464 }
13465
13466 return out;
13467}
13468
13469/**
13470 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
13471 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
13472 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
13473 * extremity (left + width or top + height). Note that it would be more optimized to directly
13474 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
13475 * to create the lookup table. The table ALWAYS contains at least two items: min and max.
13476 *
13477 * @param {Number[]} timestamps - timestamps sorted from lowest to highest.
13478 * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
13479 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
13480 * If 'series', timestamps will be positioned at the same distance from each other. In this
13481 * case, only timestamps that break the time linearity are registered, meaning that in the
13482 * best case, all timestamps are linear, the table contains only min and max.
13483 */
13484function buildLookupTable(timestamps, min, max, distribution) {
13485 if (distribution === 'linear' || !timestamps.length) {
13486 return [
13487 {time: min, pos: 0},
13488 {time: max, pos: 1}
13489 ];
13490 }
13491
13492 var table = [];
13493 var items = [min];
13494 var i, ilen, prev, curr, next;
13495
13496 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13497 curr = timestamps[i];
13498 if (curr > min && curr < max) {
13499 items.push(curr);
13500 }
13501 }
13502
13503 items.push(max);
13504
13505 for (i = 0, ilen = items.length; i < ilen; ++i) {
13506 next = items[i + 1];
13507 prev = items[i - 1];
13508 curr = items[i];
13509
13510 // only add points that breaks the scale linearity
13511 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
13512 table.push({time: curr, pos: i / (ilen - 1)});
13513 }
13514 }
13515
13516 return table;
13517}
13518
13519// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
13520function lookup(table, key, value) {
13521 var lo = 0;
13522 var hi = table.length - 1;
13523 var mid, i0, i1;
13524
13525 while (lo >= 0 && lo <= hi) {
13526 mid = (lo + hi) >> 1;
13527 i0 = table[mid - 1] || null;
13528 i1 = table[mid];
13529
13530 if (!i0) {
13531 // given value is outside table (before first item)
13532 return {lo: null, hi: i1};
13533 } else if (i1[key] < value) {
13534 lo = mid + 1;
13535 } else if (i0[key] > value) {
13536 hi = mid - 1;
13537 } else {
13538 return {lo: i0, hi: i1};
13539 }
13540 }
13541
13542 // given value is outside table (after last item)
13543 return {lo: i1, hi: null};
13544}
13545
13546/**
13547 * Linearly interpolates the given source `value` using the table items `skey` values and
13548 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
13549 * returns the position for a timestamp equal to 42. If value is out of bounds, values at
13550 * index [0, 1] or [n - 1, n] are used for the interpolation.
13551 */
13552function interpolate(table, skey, sval, tkey) {
13553 var range = lookup(table, skey, sval);
13554
13555 // Note: the lookup table ALWAYS contains at least 2 items (min and max)
13556 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
13557 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
13558
13559 var span = next[skey] - prev[skey];
13560 var ratio = span ? (sval - prev[skey]) / span : 0;
13561 var offset = (next[tkey] - prev[tkey]) * ratio;
13562
13563 return prev[tkey] + offset;
13564}
13565
13566/**
13567 * Convert the given value to a moment object using the given time options.
13568 * @see http://momentjs.com/docs/#/parsing/
13569 */
13570function momentify(value, options) {
13571 var parser = options.parser;
13572 var format = options.parser || options.format;
13573
13574 if (typeof parser === 'function') {
13575 return parser(value);
13576 }
13577
13578 if (typeof value === 'string' && typeof format === 'string') {
13579 return moment(value, format);
13580 }
13581
13582 if (!(value instanceof moment)) {
13583 value = moment(value);
13584 }
13585
13586 if (value.isValid()) {
13587 return value;
13588 }
13589
13590 // Labels are in an incompatible moment format and no `parser` has been provided.
13591 // The user might still use the deprecated `format` option to convert his inputs.
13592 if (typeof format === 'function') {
13593 return format(value);
13594 }
13595
13596 return value;
13597}
13598
13599function parse(input, scale) {
13600 if (helpers.isNullOrUndef(input)) {
13601 return null;
13602 }
13603
13604 var options = scale.options.time;
13605 var value = momentify(scale.getRightValue(input), options);
13606 if (!value.isValid()) {
13607 return null;
13608 }
13609
13610 if (options.round) {
13611 value.startOf(options.round);
13612 }
13613
13614 return value.valueOf();
13615}
13616
13617/**
13618 * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
13619 * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
13620 */
13621function determineStepSize(min, max, unit, capacity) {
13622 var range = max - min;
13623 var interval = INTERVALS[unit];
13624 var milliseconds = interval.size;
13625 var steps = interval.steps;
13626 var i, ilen, factor;
13627
13628 if (!steps) {
13629 return Math.ceil(range / ((capacity || 1) * milliseconds));
13630 }
13631
13632 for (i = 0, ilen = steps.length; i < ilen; ++i) {
13633 factor = steps[i];
13634 if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
13635 break;
13636 }
13637 }
13638
13639 return factor;
13640}
13641
13642/**
13643 * Figures out what unit results in an appropriate number of auto-generated ticks
13644 */
13645function determineUnitForAutoTicks(minUnit, min, max, capacity) {
13646 var ilen = UNITS.length;
13647 var i, interval, factor;
13648
13649 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
13650 interval = INTERVALS[UNITS[i]];
13651 factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
13652
13653 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
13654 return UNITS[i];
13655 }
13656 }
13657
13658 return UNITS[ilen - 1];
13659}
13660
13661/**
13662 * Figures out what unit to format a set of ticks with
13663 */
13664function determineUnitForFormatting(ticks, minUnit, min, max) {
13665 var duration = moment.duration(moment(max).diff(moment(min)));
13666 var ilen = UNITS.length;
13667 var i, unit;
13668
13669 for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
13670 unit = UNITS[i];
13671 if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) {
13672 return unit;
13673 }
13674 }
13675
13676 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
13677}
13678
13679function determineMajorUnit(unit) {
13680 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
13681 if (INTERVALS[UNITS[i]].common) {
13682 return UNITS[i];
13683 }
13684 }
13685}
13686
13687/**
13688 * Generates a maximum of `capacity` timestamps between min and max, rounded to the
13689 * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
13690 * Important: this method can return ticks outside the min and max range, it's the
13691 * responsibility of the calling code to clamp values if needed.
13692 */
13693function generate(min, max, capacity, options) {
13694 var timeOpts = options.time;
13695 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
13696 var major = determineMajorUnit(minor);
13697 var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
13698 var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
13699 var majorTicksEnabled = options.ticks.major.enabled;
13700 var interval = INTERVALS[minor];
13701 var first = moment(min);
13702 var last = moment(max);
13703 var ticks = [];
13704 var time;
13705
13706 if (!stepSize) {
13707 stepSize = determineStepSize(min, max, minor, capacity);
13708 }
13709
13710 // For 'week' unit, handle the first day of week option
13711 if (weekday) {
13712 first = first.isoWeekday(weekday);
13713 last = last.isoWeekday(weekday);
13714 }
13715
13716 // Align first/last ticks on unit
13717 first = first.startOf(weekday ? 'day' : minor);
13718 last = last.startOf(weekday ? 'day' : minor);
13719
13720 // Make sure that the last tick include max
13721 if (last < max) {
13722 last.add(1, minor);
13723 }
13724
13725 time = moment(first);
13726
13727 if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
13728 // Align the first tick on the previous `minor` unit aligned on the `major` unit:
13729 // we first aligned time on the previous `major` unit then add the number of full
13730 // stepSize there is between first and the previous major time.
13731 time.startOf(major);
13732 time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
13733 }
13734
13735 for (; time < last; time.add(stepSize, minor)) {
13736 ticks.push(+time);
13737 }
13738
13739 ticks.push(+time);
13740
13741 return ticks;
13742}
13743
13744/**
13745 * Returns the right and left offsets from edges in the form of {left, right}.
13746 * Offsets are added when the `offset` option is true.
13747 */
13748function computeOffsets(table, ticks, min, max, options) {
13749 var left = 0;
13750 var right = 0;
13751 var upper, lower;
13752
13753 if (options.offset && ticks.length) {
13754 if (!options.time.min) {
13755 upper = ticks.length > 1 ? ticks[1] : max;
13756 lower = ticks[0];
13757 left = (
13758 interpolate(table, 'time', upper, 'pos') -
13759 interpolate(table, 'time', lower, 'pos')
13760 ) / 2;
13761 }
13762 if (!options.time.max) {
13763 upper = ticks[ticks.length - 1];
13764 lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
13765 right = (
13766 interpolate(table, 'time', upper, 'pos') -
13767 interpolate(table, 'time', lower, 'pos')
13768 ) / 2;
13769 }
13770 }
13771
13772 return {left: left, right: right};
13773}
13774
13775function ticksFromTimestamps(values, majorUnit) {
13776 var ticks = [];
13777 var i, ilen, value, major;
13778
13779 for (i = 0, ilen = values.length; i < ilen; ++i) {
13780 value = values[i];
13781 major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
13782
13783 ticks.push({
13784 value: value,
13785 major: major
13786 });
13787 }
13788
13789 return ticks;
13790}
13791
13792module.exports = function(Chart) {
13793
13794 var defaultConfig = {
13795 position: 'bottom',
13796
13797 /**
13798 * Data distribution along the scale:
13799 * - 'linear': data are spread according to their time (distances can vary),
13800 * - 'series': data are spread at the same distance from each other.
13801 * @see https://github.com/chartjs/Chart.js/pull/4507
13802 * @since 2.7.0
13803 */
13804 distribution: 'linear',
13805
13806 /**
13807 * Scale boundary strategy (bypassed by min/max time options)
13808 * - `data`: make sure data are fully visible, ticks outside are removed
13809 * - `ticks`: make sure ticks are fully visible, data outside are truncated
13810 * @see https://github.com/chartjs/Chart.js/pull/4556
13811 * @since 2.7.0
13812 */
13813 bounds: 'data',
13814
13815 time: {
13816 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
13817 format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
13818 unit: false, // false == automatic or override with week, month, year, etc.
13819 round: false, // none, or override with week, month, year, etc.
13820 displayFormat: false, // DEPRECATED
13821 isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
13822 minUnit: 'millisecond',
13823
13824 // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
13825 displayFormats: {
13826 millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
13827 second: 'h:mm:ss a', // 11:20:01 AM
13828 minute: 'h:mm a', // 11:20 AM
13829 hour: 'hA', // 5PM
13830 day: 'MMM D', // Sep 4
13831 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
13832 month: 'MMM YYYY', // Sept 2015
13833 quarter: '[Q]Q - YYYY', // Q3
13834 year: 'YYYY' // 2015
13835 },
13836 },
13837 ticks: {
13838 autoSkip: false,
13839
13840 /**
13841 * Ticks generation input values:
13842 * - 'auto': generates "optimal" ticks based on scale size and time options.
13843 * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
13844 * - 'labels': generates ticks from user given `data.labels` values ONLY.
13845 * @see https://github.com/chartjs/Chart.js/pull/4507
13846 * @since 2.7.0
13847 */
13848 source: 'auto',
13849
13850 major: {
13851 enabled: false
13852 }
13853 }
13854 };
13855
13856 var TimeScale = Chart.Scale.extend({
13857 initialize: function() {
13858 if (!moment) {
13859 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');
13860 }
13861
13862 this.mergeTicksOptions();
13863
13864 Chart.Scale.prototype.initialize.call(this);
13865 },
13866
13867 update: function() {
13868 var me = this;
13869 var options = me.options;
13870
13871 // DEPRECATIONS: output a message only one time per update
13872 if (options.time && options.time.format) {
13873 console.warn('options.time.format is deprecated and replaced by options.time.parser.');
13874 }
13875
13876 return Chart.Scale.prototype.update.apply(me, arguments);
13877 },
13878
13879 /**
13880 * Allows data to be referenced via 't' attribute
13881 */
13882 getRightValue: function(rawValue) {
13883 if (rawValue && rawValue.t !== undefined) {
13884 rawValue = rawValue.t;
13885 }
13886 return Chart.Scale.prototype.getRightValue.call(this, rawValue);
13887 },
13888
13889 determineDataLimits: function() {
13890 var me = this;
13891 var chart = me.chart;
13892 var timeOpts = me.options.time;
13893 var min = MAX_INTEGER;
13894 var max = MIN_INTEGER;
13895 var timestamps = [];
13896 var datasets = [];
13897 var labels = [];
13898 var i, j, ilen, jlen, data, timestamp;
13899
13900 // Convert labels to timestamps
13901 for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) {
13902 labels.push(parse(chart.data.labels[i], me));
13903 }
13904
13905 // Convert data to timestamps
13906 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
13907 if (chart.isDatasetVisible(i)) {
13908 data = chart.data.datasets[i].data;
13909
13910 // Let's consider that all data have the same format.
13911 if (helpers.isObject(data[0])) {
13912 datasets[i] = [];
13913
13914 for (j = 0, jlen = data.length; j < jlen; ++j) {
13915 timestamp = parse(data[j], me);
13916 timestamps.push(timestamp);
13917 datasets[i][j] = timestamp;
13918 }
13919 } else {
13920 timestamps.push.apply(timestamps, labels);
13921 datasets[i] = labels.slice(0);
13922 }
13923 } else {
13924 datasets[i] = [];
13925 }
13926 }
13927
13928 if (labels.length) {
13929 // Sort labels **after** data have been converted
13930 labels = arrayUnique(labels).sort(sorter);
13931 min = Math.min(min, labels[0]);
13932 max = Math.max(max, labels[labels.length - 1]);
13933 }
13934
13935 if (timestamps.length) {
13936 timestamps = arrayUnique(timestamps).sort(sorter);
13937 min = Math.min(min, timestamps[0]);
13938 max = Math.max(max, timestamps[timestamps.length - 1]);
13939 }
13940
13941 min = parse(timeOpts.min, me) || min;
13942 max = parse(timeOpts.max, me) || max;
13943
13944 // In case there is no valid min/max, let's use today limits
13945 min = min === MAX_INTEGER ? +moment().startOf('day') : min;
13946 max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max;
13947
13948 // Make sure that max is strictly higher than min (required by the lookup table)
13949 me.min = Math.min(min, max);
13950 me.max = Math.max(min + 1, max);
13951
13952 // PRIVATE
13953 me._horizontal = me.isHorizontal();
13954 me._table = [];
13955 me._timestamps = {
13956 data: timestamps,
13957 datasets: datasets,
13958 labels: labels
13959 };
13960 },
13961
13962 buildTicks: function() {
13963 var me = this;
13964 var min = me.min;
13965 var max = me.max;
13966 var options = me.options;
13967 var timeOpts = options.time;
13968 var timestamps = [];
13969 var ticks = [];
13970 var i, ilen, timestamp;
13971
13972 switch (options.ticks.source) {
13973 case 'data':
13974 timestamps = me._timestamps.data;
13975 break;
13976 case 'labels':
13977 timestamps = me._timestamps.labels;
13978 break;
13979 case 'auto':
13980 default:
13981 timestamps = generate(min, max, me.getLabelCapacity(min), options);
13982 }
13983
13984 if (options.bounds === 'ticks' && timestamps.length) {
13985 min = timestamps[0];
13986 max = timestamps[timestamps.length - 1];
13987 }
13988
13989 // Enforce limits with user min/max options
13990 min = parse(timeOpts.min, me) || min;
13991 max = parse(timeOpts.max, me) || max;
13992
13993 // Remove ticks outside the min/max range
13994 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13995 timestamp = timestamps[i];
13996 if (timestamp >= min && timestamp <= max) {
13997 ticks.push(timestamp);
13998 }
13999 }
14000
14001 me.min = min;
14002 me.max = max;
14003
14004 // PRIVATE
14005 me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max);
14006 me._majorUnit = determineMajorUnit(me._unit);
14007 me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
14008 me._offsets = computeOffsets(me._table, ticks, min, max, options);
14009
14010 return ticksFromTimestamps(ticks, me._majorUnit);
14011 },
14012
14013 getLabelForIndex: function(index, datasetIndex) {
14014 var me = this;
14015 var data = me.chart.data;
14016 var timeOpts = me.options.time;
14017 var label = data.labels && index < data.labels.length ? data.labels[index] : '';
14018 var value = data.datasets[datasetIndex].data[index];
14019
14020 if (helpers.isObject(value)) {
14021 label = me.getRightValue(value);
14022 }
14023 if (timeOpts.tooltipFormat) {
14024 label = momentify(label, timeOpts).format(timeOpts.tooltipFormat);
14025 }
14026
14027 return label;
14028 },
14029
14030 /**
14031 * Function to format an individual tick mark
14032 * @private
14033 */
14034 tickFormatFunction: function(tick, index, ticks, formatOverride) {
14035 var me = this;
14036 var options = me.options;
14037 var time = tick.valueOf();
14038 var formats = options.time.displayFormats;
14039 var minorFormat = formats[me._unit];
14040 var majorUnit = me._majorUnit;
14041 var majorFormat = formats[majorUnit];
14042 var majorTime = tick.clone().startOf(majorUnit).valueOf();
14043 var majorTickOpts = options.ticks.major;
14044 var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
14045 var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat);
14046 var tickOpts = major ? majorTickOpts : options.ticks.minor;
14047 var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback);
14048
14049 return formatter ? formatter(label, index, ticks) : label;
14050 },
14051
14052 convertTicksToLabels: function(ticks) {
14053 var labels = [];
14054 var i, ilen;
14055
14056 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
14057 labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
14058 }
14059
14060 return labels;
14061 },
14062
14063 /**
14064 * @private
14065 */
14066 getPixelForOffset: function(time) {
14067 var me = this;
14068 var size = me._horizontal ? me.width : me.height;
14069 var start = me._horizontal ? me.left : me.top;
14070 var pos = interpolate(me._table, 'time', time, 'pos');
14071
14072 return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
14073 },
14074
14075 getPixelForValue: function(value, index, datasetIndex) {
14076 var me = this;
14077 var time = null;
14078
14079 if (index !== undefined && datasetIndex !== undefined) {
14080 time = me._timestamps.datasets[datasetIndex][index];
14081 }
14082
14083 if (time === null) {
14084 time = parse(value, me);
14085 }
14086
14087 if (time !== null) {
14088 return me.getPixelForOffset(time);
14089 }
14090 },
14091
14092 getPixelForTick: function(index) {
14093 var ticks = this.getTicks();
14094 return index >= 0 && index < ticks.length ?
14095 this.getPixelForOffset(ticks[index].value) :
14096 null;
14097 },
14098
14099 getValueForPixel: function(pixel) {
14100 var me = this;
14101 var size = me._horizontal ? me.width : me.height;
14102 var start = me._horizontal ? me.left : me.top;
14103 var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
14104 var time = interpolate(me._table, 'pos', pos, 'time');
14105
14106 return moment(time);
14107 },
14108
14109 /**
14110 * Crude approximation of what the label width might be
14111 * @private
14112 */
14113 getLabelWidth: function(label) {
14114 var me = this;
14115 var ticksOpts = me.options.ticks;
14116 var tickLabelWidth = me.ctx.measureText(label).width;
14117 var angle = helpers.toRadians(ticksOpts.maxRotation);
14118 var cosRotation = Math.cos(angle);
14119 var sinRotation = Math.sin(angle);
14120 var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
14121
14122 return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
14123 },
14124
14125 /**
14126 * @private
14127 */
14128 getLabelCapacity: function(exampleTime) {
14129 var me = this;
14130
14131 var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
14132
14133 var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride);
14134 var tickLabelWidth = me.getLabelWidth(exampleLabel);
14135 var innerWidth = me.isHorizontal() ? me.width : me.height;
14136
14137 return Math.floor(innerWidth / tickLabelWidth);
14138 }
14139 });
14140
14141 Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
14142};
14143
14144},{"1":1,"25":25,"45":45}]},{},[7])(7)
14145});