· 8 years ago · Jan 11, 2018, 02:58 PM
1/*!
2 * Chart.js
3 * http://chartjs.org/
4 * Version: 2.7.1
5 *
6 * Copyright 2018 Chart.js Contributors
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.layouts = require(30);
1685Chart.platform = require(48);
1686Chart.plugins = require(31);
1687Chart.Ticks = require(34);
1688
1689require(22)(Chart);
1690require(23)(Chart);
1691require(24)(Chart);
1692require(33)(Chart);
1693require(32)(Chart);
1694require(35)(Chart);
1695
1696require(55)(Chart);
1697require(53)(Chart);
1698require(54)(Chart);
1699require(56)(Chart);
1700require(57)(Chart);
1701require(58)(Chart);
1702
1703// Controllers must be loaded after elements
1704// See Chart.core.datasetController.dataElementType
1705require(15)(Chart);
1706require(16)(Chart);
1707require(17)(Chart);
1708require(18)(Chart);
1709require(19)(Chart);
1710require(20)(Chart);
1711require(21)(Chart);
1712
1713require(8)(Chart);
1714require(9)(Chart);
1715require(10)(Chart);
1716require(11)(Chart);
1717require(12)(Chart);
1718require(13)(Chart);
1719require(14)(Chart);
1720
1721// Loading built-it plugins
1722var plugins = require(49);
1723for (var k in plugins) {
1724 if (plugins.hasOwnProperty(k)) {
1725 Chart.plugins.register(plugins[k]);
1726 }
1727}
1728
1729Chart.platform.initialize();
1730
1731module.exports = Chart;
1732if (typeof window !== 'undefined') {
1733 window.Chart = Chart;
1734}
1735
1736// DEPRECATIONS
1737
1738/**
1739 * Provided for backward compatibility, not available anymore
1740 * @namespace Chart.Legend
1741 * @deprecated since version 2.1.5
1742 * @todo remove at version 3
1743 * @private
1744 */
1745Chart.Legend = plugins.legend._element;
1746
1747/**
1748 * Provided for backward compatibility, not available anymore
1749 * @namespace Chart.Title
1750 * @deprecated since version 2.1.5
1751 * @todo remove at version 3
1752 * @private
1753 */
1754Chart.Title = plugins.title._element;
1755
1756/**
1757 * Provided for backward compatibility, use Chart.plugins instead
1758 * @namespace Chart.pluginService
1759 * @deprecated since version 2.1.5
1760 * @todo remove at version 3
1761 * @private
1762 */
1763Chart.pluginService = Chart.plugins;
1764
1765/**
1766 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
1767 * effect, instead simply create/register plugins via plain JavaScript objects.
1768 * @interface Chart.PluginBase
1769 * @deprecated since version 2.5.0
1770 * @todo remove at version 3
1771 * @private
1772 */
1773Chart.PluginBase = Chart.Element.extend({});
1774
1775/**
1776 * Provided for backward compatibility, use Chart.helpers.canvas instead.
1777 * @namespace Chart.canvasHelpers
1778 * @deprecated since version 2.6.0
1779 * @todo remove at version 3
1780 * @private
1781 */
1782Chart.canvasHelpers = Chart.helpers.canvas;
1783
1784/**
1785 * Provided for backward compatibility, use Chart.layouts instead.
1786 * @namespace Chart.layoutService
1787 * @deprecated since version 2.8.0
1788 * @todo remove at version 3
1789 * @private
1790 */
1791Chart.layoutService = Chart.layouts;
1792
1793},{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"40":40,"45":45,"48":48,"49":49,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"8":8,"9":9}],8:[function(require,module,exports){
1794'use strict';
1795
1796module.exports = function(Chart) {
1797
1798 Chart.Bar = function(context, config) {
1799 config.type = 'bar';
1800
1801 return new Chart(context, config);
1802 };
1803
1804};
1805
1806},{}],9:[function(require,module,exports){
1807'use strict';
1808
1809module.exports = function(Chart) {
1810
1811 Chart.Bubble = function(context, config) {
1812 config.type = 'bubble';
1813 return new Chart(context, config);
1814 };
1815
1816};
1817
1818},{}],10:[function(require,module,exports){
1819'use strict';
1820
1821module.exports = function(Chart) {
1822
1823 Chart.Doughnut = function(context, config) {
1824 config.type = 'doughnut';
1825
1826 return new Chart(context, config);
1827 };
1828
1829};
1830
1831},{}],11:[function(require,module,exports){
1832'use strict';
1833
1834module.exports = function(Chart) {
1835
1836 Chart.Line = function(context, config) {
1837 config.type = 'line';
1838
1839 return new Chart(context, config);
1840 };
1841
1842};
1843
1844},{}],12:[function(require,module,exports){
1845'use strict';
1846
1847module.exports = function(Chart) {
1848
1849 Chart.PolarArea = function(context, config) {
1850 config.type = 'polarArea';
1851
1852 return new Chart(context, config);
1853 };
1854
1855};
1856
1857},{}],13:[function(require,module,exports){
1858'use strict';
1859
1860module.exports = function(Chart) {
1861
1862 Chart.Radar = function(context, config) {
1863 config.type = 'radar';
1864
1865 return new Chart(context, config);
1866 };
1867
1868};
1869
1870},{}],14:[function(require,module,exports){
1871'use strict';
1872
1873module.exports = function(Chart) {
1874 Chart.Scatter = function(context, config) {
1875 config.type = 'scatter';
1876 return new Chart(context, config);
1877 };
1878};
1879
1880},{}],15:[function(require,module,exports){
1881'use strict';
1882
1883var defaults = require(25);
1884var elements = require(40);
1885var helpers = require(45);
1886
1887defaults._set('bar', {
1888 hover: {
1889 mode: 'label'
1890 },
1891
1892 scales: {
1893 xAxes: [{
1894 type: 'category',
1895
1896 // Specific to Bar Controller
1897 categoryPercentage: 0.8,
1898 barPercentage: 0.9,
1899
1900 // offset settings
1901 offset: true,
1902
1903 // grid line settings
1904 gridLines: {
1905 offsetGridLines: true
1906 }
1907 }],
1908
1909 yAxes: [{
1910 type: 'linear'
1911 }]
1912 }
1913});
1914
1915defaults._set('horizontalBar', {
1916 hover: {
1917 mode: 'index',
1918 axis: 'y'
1919 },
1920
1921 scales: {
1922 xAxes: [{
1923 type: 'linear',
1924 position: 'bottom'
1925 }],
1926
1927 yAxes: [{
1928 position: 'left',
1929 type: 'category',
1930
1931 // Specific to Horizontal Bar Controller
1932 categoryPercentage: 0.8,
1933 barPercentage: 0.9,
1934
1935 // offset settings
1936 offset: true,
1937
1938 // grid line settings
1939 gridLines: {
1940 offsetGridLines: true
1941 }
1942 }]
1943 },
1944
1945 elements: {
1946 rectangle: {
1947 borderSkipped: 'left'
1948 }
1949 },
1950
1951 tooltips: {
1952 callbacks: {
1953 title: function(item, data) {
1954 // Pick first xLabel for now
1955 var title = '';
1956
1957 if (item.length > 0) {
1958 if (item[0].yLabel) {
1959 title = item[0].yLabel;
1960 } else if (data.labels.length > 0 && item[0].index < data.labels.length) {
1961 title = data.labels[item[0].index];
1962 }
1963 }
1964
1965 return title;
1966 },
1967
1968 label: function(item, data) {
1969 var datasetLabel = data.datasets[item.datasetIndex].label || '';
1970 return datasetLabel + ': ' + item.xLabel;
1971 }
1972 },
1973 mode: 'index',
1974 axis: 'y'
1975 }
1976});
1977
1978/**
1979 * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
1980 * @private
1981 */
1982function computeMinSampleSize(scale, pixels) {
1983 var min = scale.isHorizontal() ? scale.width : scale.height;
1984 var ticks = scale.getTicks();
1985 var prev, curr, i, ilen;
1986
1987 for (i = 1, ilen = pixels.length; i < ilen; ++i) {
1988 min = Math.min(min, pixels[i] - pixels[i - 1]);
1989 }
1990
1991 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
1992 curr = scale.getPixelForTick(i);
1993 min = i > 0 ? Math.min(min, curr - prev) : min;
1994 prev = curr;
1995 }
1996
1997 return min;
1998}
1999
2000/**
2001 * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null,
2002 * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This
2003 * mode currently always generates bars equally sized (until we introduce scriptable options?).
2004 * @private
2005 */
2006function computeFitCategoryTraits(index, ruler, options) {
2007 var thickness = options.barThickness;
2008 var count = ruler.stackCount;
2009 var curr = ruler.pixels[index];
2010 var size, ratio;
2011
2012 if (helpers.isNullOrUndef(thickness)) {
2013 size = ruler.min * options.categoryPercentage;
2014 ratio = options.barPercentage;
2015 } else {
2016 // When bar thickness is enforced, category and bar percentages are ignored.
2017 // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')
2018 // and deprecate barPercentage since this value is ignored when thickness is absolute.
2019 size = thickness * count;
2020 ratio = 1;
2021 }
2022
2023 return {
2024 chunk: size / count,
2025 ratio: ratio,
2026 start: curr - (size / 2)
2027 };
2028}
2029
2030/**
2031 * Computes an "optimal" category that globally arranges bars side by side (no gap when
2032 * percentage options are 1), based on the previous and following categories. This mode
2033 * generates bars with different widths when data are not evenly spaced.
2034 * @private
2035 */
2036function computeFlexCategoryTraits(index, ruler, options) {
2037 var pixels = ruler.pixels;
2038 var curr = pixels[index];
2039 var prev = index > 0 ? pixels[index - 1] : null;
2040 var next = index < pixels.length - 1 ? pixels[index + 1] : null;
2041 var percent = options.categoryPercentage;
2042 var start, size;
2043
2044 if (prev === null) {
2045 // first data: its size is double based on the next point or,
2046 // if it's also the last data, we use the scale end extremity.
2047 prev = curr - (next === null ? ruler.end - curr : next - curr);
2048 }
2049
2050 if (next === null) {
2051 // last data: its size is also double based on the previous point.
2052 next = curr + curr - prev;
2053 }
2054
2055 start = curr - ((curr - prev) / 2) * percent;
2056 size = ((next - prev) / 2) * percent;
2057
2058 return {
2059 chunk: size / ruler.stackCount,
2060 ratio: options.barPercentage,
2061 start: start
2062 };
2063}
2064
2065module.exports = function(Chart) {
2066
2067 Chart.controllers.bar = Chart.DatasetController.extend({
2068
2069 dataElementType: elements.Rectangle,
2070
2071 initialize: function() {
2072 var me = this;
2073 var meta;
2074
2075 Chart.DatasetController.prototype.initialize.apply(me, arguments);
2076
2077 meta = me.getMeta();
2078 meta.stack = me.getDataset().stack;
2079 meta.bar = true;
2080 },
2081
2082 update: function(reset) {
2083 var me = this;
2084 var rects = me.getMeta().data;
2085 var i, ilen;
2086
2087 me._ruler = me.getRuler();
2088
2089 for (i = 0, ilen = rects.length; i < ilen; ++i) {
2090 me.updateElement(rects[i], i, reset);
2091 }
2092 },
2093
2094 updateElement: function(rectangle, index, reset) {
2095 var me = this;
2096 var chart = me.chart;
2097 var meta = me.getMeta();
2098 var dataset = me.getDataset();
2099 var custom = rectangle.custom || {};
2100 var rectangleOptions = chart.options.elements.rectangle;
2101
2102 rectangle._xScale = me.getScaleForId(meta.xAxisID);
2103 rectangle._yScale = me.getScaleForId(meta.yAxisID);
2104 rectangle._datasetIndex = me.index;
2105 rectangle._index = index;
2106
2107 rectangle._model = {
2108 datasetLabel: dataset.label,
2109 label: chart.data.labels[index],
2110 borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped,
2111 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor),
2112 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor),
2113 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth)
2114 };
2115
2116 me.updateElementGeometry(rectangle, index, reset);
2117
2118 rectangle.pivot();
2119 },
2120
2121 /**
2122 * @private
2123 */
2124 updateElementGeometry: function(rectangle, index, reset) {
2125 var me = this;
2126 var model = rectangle._model;
2127 var vscale = me.getValueScale();
2128 var base = vscale.getBasePixel();
2129 var horizontal = vscale.isHorizontal();
2130 var ruler = me._ruler || me.getRuler();
2131 var vpixels = me.calculateBarValuePixels(me.index, index);
2132 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
2133
2134 model.horizontal = horizontal;
2135 model.base = reset ? base : vpixels.base;
2136 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
2137 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
2138 model.height = horizontal ? ipixels.size : undefined;
2139 model.width = horizontal ? undefined : ipixels.size;
2140 },
2141
2142 /**
2143 * @private
2144 */
2145 getValueScaleId: function() {
2146 return this.getMeta().yAxisID;
2147 },
2148
2149 /**
2150 * @private
2151 */
2152 getIndexScaleId: function() {
2153 return this.getMeta().xAxisID;
2154 },
2155
2156 /**
2157 * @private
2158 */
2159 getValueScale: function() {
2160 return this.getScaleForId(this.getValueScaleId());
2161 },
2162
2163 /**
2164 * @private
2165 */
2166 getIndexScale: function() {
2167 return this.getScaleForId(this.getIndexScaleId());
2168 },
2169
2170 /**
2171 * Returns the stacks based on groups and bar visibility.
2172 * @param {Number} [last] - The dataset index
2173 * @returns {Array} The stack list
2174 * @private
2175 */
2176 _getStacks: function(last) {
2177 var me = this;
2178 var chart = me.chart;
2179 var scale = me.getIndexScale();
2180 var stacked = scale.options.stacked;
2181 var ilen = last === undefined ? chart.data.datasets.length : last + 1;
2182 var stacks = [];
2183 var i, meta;
2184
2185 for (i = 0; i < ilen; ++i) {
2186 meta = chart.getDatasetMeta(i);
2187 if (meta.bar && chart.isDatasetVisible(i) &&
2188 (stacked === false ||
2189 (stacked === true && stacks.indexOf(meta.stack) === -1) ||
2190 (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
2191 stacks.push(meta.stack);
2192 }
2193 }
2194
2195 return stacks;
2196 },
2197
2198 /**
2199 * Returns the effective number of stacks based on groups and bar visibility.
2200 * @private
2201 */
2202 getStackCount: function() {
2203 return this._getStacks().length;
2204 },
2205
2206 /**
2207 * Returns the stack index for the given dataset based on groups and bar visibility.
2208 * @param {Number} [datasetIndex] - The dataset index
2209 * @param {String} [name] - The stack name to find
2210 * @returns {Number} The stack index
2211 * @private
2212 */
2213 getStackIndex: function(datasetIndex, name) {
2214 var stacks = this._getStacks(datasetIndex);
2215 var index = (name !== undefined)
2216 ? stacks.indexOf(name)
2217 : -1; // indexOf returns -1 if element is not present
2218
2219 return (index === -1)
2220 ? stacks.length - 1
2221 : index;
2222 },
2223
2224 /**
2225 * @private
2226 */
2227 getRuler: function() {
2228 var me = this;
2229 var scale = me.getIndexScale();
2230 var stackCount = me.getStackCount();
2231 var datasetIndex = me.index;
2232 var isHorizontal = scale.isHorizontal();
2233 var start = isHorizontal ? scale.left : scale.top;
2234 var end = start + (isHorizontal ? scale.width : scale.height);
2235 var pixels = [];
2236 var i, ilen, min;
2237
2238 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
2239 pixels.push(scale.getPixelForValue(null, i, datasetIndex));
2240 }
2241
2242 min = helpers.isNullOrUndef(scale.options.barThickness)
2243 ? computeMinSampleSize(scale, pixels)
2244 : -1;
2245
2246 return {
2247 min: min,
2248 pixels: pixels,
2249 start: start,
2250 end: end,
2251 stackCount: stackCount,
2252 scale: scale
2253 };
2254 },
2255
2256 /**
2257 * Note: pixel values are not clamped to the scale area.
2258 * @private
2259 */
2260 calculateBarValuePixels: function(datasetIndex, index) {
2261 var me = this;
2262 var chart = me.chart;
2263 var meta = me.getMeta();
2264 var scale = me.getValueScale();
2265 var datasets = chart.data.datasets;
2266 var value = scale.getRightValue(datasets[datasetIndex].data[index]);
2267 var stacked = scale.options.stacked;
2268 var stack = meta.stack;
2269 var start = 0;
2270 var i, imeta, ivalue, base, head, size;
2271
2272 if (stacked || (stacked === undefined && stack !== undefined)) {
2273 for (i = 0; i < datasetIndex; ++i) {
2274 imeta = chart.getDatasetMeta(i);
2275
2276 if (imeta.bar &&
2277 imeta.stack === stack &&
2278 imeta.controller.getValueScaleId() === scale.id &&
2279 chart.isDatasetVisible(i)) {
2280
2281 ivalue = scale.getRightValue(datasets[i].data[index]);
2282 if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
2283 start += ivalue;
2284 }
2285 }
2286 }
2287 }
2288
2289 base = scale.getPixelForValue(start);
2290 head = scale.getPixelForValue(start + value);
2291 size = (head - base) / 2;
2292
2293 return {
2294 size: size,
2295 base: base,
2296 head: head,
2297 center: head + size / 2
2298 };
2299 },
2300
2301 /**
2302 * @private
2303 */
2304 calculateBarIndexPixels: function(datasetIndex, index, ruler) {
2305 var me = this;
2306 var options = ruler.scale.options;
2307 var range = options.barThickness === 'flex'
2308 ? computeFlexCategoryTraits(index, ruler, options)
2309 : computeFitCategoryTraits(index, ruler, options);
2310
2311 var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
2312 var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
2313 var size = Math.min(
2314 helpers.valueOrDefault(options.maxBarThickness, Infinity),
2315 range.chunk * range.ratio);
2316
2317 return {
2318 base: center - size / 2,
2319 head: center + size / 2,
2320 center: center,
2321 size: size
2322 };
2323 },
2324
2325 draw: function() {
2326 var me = this;
2327 var chart = me.chart;
2328 var scale = me.getValueScale();
2329 var rects = me.getMeta().data;
2330 var dataset = me.getDataset();
2331 var ilen = rects.length;
2332 var i = 0;
2333
2334 helpers.canvas.clipArea(chart.ctx, chart.chartArea);
2335
2336 for (; i < ilen; ++i) {
2337 if (!isNaN(scale.getRightValue(dataset.data[i]))) {
2338 rects[i].draw();
2339 }
2340 }
2341
2342 helpers.canvas.unclipArea(chart.ctx);
2343 },
2344
2345 setHoverStyle: function(rectangle) {
2346 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2347 var index = rectangle._index;
2348 var custom = rectangle.custom || {};
2349 var model = rectangle._model;
2350
2351 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
2352 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
2353 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
2354 },
2355
2356 removeHoverStyle: function(rectangle) {
2357 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2358 var index = rectangle._index;
2359 var custom = rectangle.custom || {};
2360 var model = rectangle._model;
2361 var rectangleElementOptions = this.chart.options.elements.rectangle;
2362
2363 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
2364 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
2365 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
2366 }
2367 });
2368
2369 Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
2370 /**
2371 * @private
2372 */
2373 getValueScaleId: function() {
2374 return this.getMeta().xAxisID;
2375 },
2376
2377 /**
2378 * @private
2379 */
2380 getIndexScaleId: function() {
2381 return this.getMeta().yAxisID;
2382 }
2383 });
2384};
2385
2386},{"25":25,"40":40,"45":45}],16:[function(require,module,exports){
2387'use strict';
2388
2389var defaults = require(25);
2390var elements = require(40);
2391var helpers = require(45);
2392
2393defaults._set('bubble', {
2394 hover: {
2395 mode: 'single'
2396 },
2397
2398 scales: {
2399 xAxes: [{
2400 type: 'linear', // bubble should probably use a linear scale by default
2401 position: 'bottom',
2402 id: 'x-axis-0' // need an ID so datasets can reference the scale
2403 }],
2404 yAxes: [{
2405 type: 'linear',
2406 position: 'left',
2407 id: 'y-axis-0'
2408 }]
2409 },
2410
2411 tooltips: {
2412 callbacks: {
2413 title: function() {
2414 // Title doesn't make sense for scatter since we format the data as a point
2415 return '';
2416 },
2417 label: function(item, data) {
2418 var datasetLabel = data.datasets[item.datasetIndex].label || '';
2419 var dataPoint = data.datasets[item.datasetIndex].data[item.index];
2420 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
2421 }
2422 }
2423 }
2424});
2425
2426
2427module.exports = function(Chart) {
2428
2429 Chart.controllers.bubble = Chart.DatasetController.extend({
2430 /**
2431 * @protected
2432 */
2433 dataElementType: elements.Point,
2434
2435 /**
2436 * @protected
2437 */
2438 update: function(reset) {
2439 var me = this;
2440 var meta = me.getMeta();
2441 var points = meta.data;
2442
2443 // Update Points
2444 helpers.each(points, function(point, index) {
2445 me.updateElement(point, index, reset);
2446 });
2447 },
2448
2449 /**
2450 * @protected
2451 */
2452 updateElement: function(point, index, reset) {
2453 var me = this;
2454 var meta = me.getMeta();
2455 var custom = point.custom || {};
2456 var xScale = me.getScaleForId(meta.xAxisID);
2457 var yScale = me.getScaleForId(meta.yAxisID);
2458 var options = me._resolveElementOptions(point, index);
2459 var data = me.getDataset().data[index];
2460 var dsIndex = me.index;
2461
2462 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
2463 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
2464
2465 point._xScale = xScale;
2466 point._yScale = yScale;
2467 point._options = options;
2468 point._datasetIndex = dsIndex;
2469 point._index = index;
2470 point._model = {
2471 backgroundColor: options.backgroundColor,
2472 borderColor: options.borderColor,
2473 borderWidth: options.borderWidth,
2474 hitRadius: options.hitRadius,
2475 pointStyle: options.pointStyle,
2476 radius: reset ? 0 : options.radius,
2477 skip: custom.skip || isNaN(x) || isNaN(y),
2478 x: x,
2479 y: y,
2480 };
2481
2482 point.pivot();
2483 },
2484
2485 /**
2486 * @protected
2487 */
2488 setHoverStyle: function(point) {
2489 var model = point._model;
2490 var options = point._options;
2491
2492 model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
2493 model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
2494 model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
2495 model.radius = options.radius + options.hoverRadius;
2496 },
2497
2498 /**
2499 * @protected
2500 */
2501 removeHoverStyle: function(point) {
2502 var model = point._model;
2503 var options = point._options;
2504
2505 model.backgroundColor = options.backgroundColor;
2506 model.borderColor = options.borderColor;
2507 model.borderWidth = options.borderWidth;
2508 model.radius = options.radius;
2509 },
2510
2511 /**
2512 * @private
2513 */
2514 _resolveElementOptions: function(point, index) {
2515 var me = this;
2516 var chart = me.chart;
2517 var datasets = chart.data.datasets;
2518 var dataset = datasets[me.index];
2519 var custom = point.custom || {};
2520 var options = chart.options.elements.point;
2521 var resolve = helpers.options.resolve;
2522 var data = dataset.data[index];
2523 var values = {};
2524 var i, ilen, key;
2525
2526 // Scriptable options
2527 var context = {
2528 chart: chart,
2529 dataIndex: index,
2530 dataset: dataset,
2531 datasetIndex: me.index
2532 };
2533
2534 var keys = [
2535 'backgroundColor',
2536 'borderColor',
2537 'borderWidth',
2538 'hoverBackgroundColor',
2539 'hoverBorderColor',
2540 'hoverBorderWidth',
2541 'hoverRadius',
2542 'hitRadius',
2543 'pointStyle'
2544 ];
2545
2546 for (i = 0, ilen = keys.length; i < ilen; ++i) {
2547 key = keys[i];
2548 values[key] = resolve([
2549 custom[key],
2550 dataset[key],
2551 options[key]
2552 ], context, index);
2553 }
2554
2555 // Custom radius resolution
2556 values.radius = resolve([
2557 custom.radius,
2558 data ? data.r : undefined,
2559 dataset.radius,
2560 options.radius
2561 ], context, index);
2562
2563 return values;
2564 }
2565 });
2566};
2567
2568},{"25":25,"40":40,"45":45}],17:[function(require,module,exports){
2569'use strict';
2570
2571var defaults = require(25);
2572var elements = require(40);
2573var helpers = require(45);
2574
2575defaults._set('doughnut', {
2576 animation: {
2577 // Boolean - Whether we animate the rotation of the Doughnut
2578 animateRotate: true,
2579 // Boolean - Whether we animate scaling the Doughnut from the centre
2580 animateScale: false
2581 },
2582 hover: {
2583 mode: 'single'
2584 },
2585 legendCallback: function(chart) {
2586 var text = [];
2587 text.push('<ul class="' + chart.id + '-legend">');
2588
2589 var data = chart.data;
2590 var datasets = data.datasets;
2591 var labels = data.labels;
2592
2593 if (datasets.length) {
2594 for (var i = 0; i < datasets[0].data.length; ++i) {
2595 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
2596 if (labels[i]) {
2597 text.push(labels[i]);
2598 }
2599 text.push('</li>');
2600 }
2601 }
2602
2603 text.push('</ul>');
2604 return text.join('');
2605 },
2606 legend: {
2607 labels: {
2608 generateLabels: function(chart) {
2609 var data = chart.data;
2610 if (data.labels.length && data.datasets.length) {
2611 return data.labels.map(function(label, i) {
2612 var meta = chart.getDatasetMeta(0);
2613 var ds = data.datasets[0];
2614 var arc = meta.data[i];
2615 var custom = arc && arc.custom || {};
2616 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2617 var arcOpts = chart.options.elements.arc;
2618 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
2619 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
2620 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
2621
2622 return {
2623 text: label,
2624 fillStyle: fill,
2625 strokeStyle: stroke,
2626 lineWidth: bw,
2627 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
2628
2629 // Extra data used for toggling the correct item
2630 index: i
2631 };
2632 });
2633 }
2634 return [];
2635 }
2636 },
2637
2638 onClick: function(e, legendItem) {
2639 var index = legendItem.index;
2640 var chart = this.chart;
2641 var i, ilen, meta;
2642
2643 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
2644 meta = chart.getDatasetMeta(i);
2645 // toggle visibility of index if exists
2646 if (meta.data[index]) {
2647 meta.data[index].hidden = !meta.data[index].hidden;
2648 }
2649 }
2650
2651 chart.update();
2652 }
2653 },
2654
2655 // The percentage of the chart that we cut out of the middle.
2656 cutoutPercentage: 50,
2657
2658 // The rotation of the chart, where the first data arc begins.
2659 rotation: Math.PI * -0.5,
2660
2661 // The total circumference of the chart.
2662 circumference: Math.PI * 2.0,
2663
2664 // Need to override these to give a nice default
2665 tooltips: {
2666 callbacks: {
2667 title: function() {
2668 return '';
2669 },
2670 label: function(tooltipItem, data) {
2671 var dataLabel = data.labels[tooltipItem.index];
2672 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2673
2674 if (helpers.isArray(dataLabel)) {
2675 // show value on first line of multiline label
2676 // need to clone because we are changing the value
2677 dataLabel = dataLabel.slice();
2678 dataLabel[0] += value;
2679 } else {
2680 dataLabel += value;
2681 }
2682
2683 return dataLabel;
2684 }
2685 }
2686 }
2687});
2688
2689defaults._set('pie', helpers.clone(defaults.doughnut));
2690defaults._set('pie', {
2691 cutoutPercentage: 0
2692});
2693
2694module.exports = function(Chart) {
2695
2696 Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
2697
2698 dataElementType: elements.Arc,
2699
2700 linkScales: helpers.noop,
2701
2702 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
2703 getRingIndex: function(datasetIndex) {
2704 var ringIndex = 0;
2705
2706 for (var j = 0; j < datasetIndex; ++j) {
2707 if (this.chart.isDatasetVisible(j)) {
2708 ++ringIndex;
2709 }
2710 }
2711
2712 return ringIndex;
2713 },
2714
2715 update: function(reset) {
2716 var me = this;
2717 var chart = me.chart;
2718 var chartArea = chart.chartArea;
2719 var opts = chart.options;
2720 var arcOpts = opts.elements.arc;
2721 var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
2722 var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
2723 var minSize = Math.min(availableWidth, availableHeight);
2724 var offset = {x: 0, y: 0};
2725 var meta = me.getMeta();
2726 var cutoutPercentage = opts.cutoutPercentage;
2727 var circumference = opts.circumference;
2728
2729 // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
2730 if (circumference < Math.PI * 2.0) {
2731 var startAngle = opts.rotation % (Math.PI * 2.0);
2732 startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
2733 var endAngle = startAngle + circumference;
2734 var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
2735 var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
2736 var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
2737 var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
2738 var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
2739 var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
2740 var cutout = cutoutPercentage / 100.0;
2741 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))};
2742 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))};
2743 var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
2744 minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
2745 offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
2746 }
2747
2748 chart.borderWidth = me.getMaxBorderWidth(meta.data);
2749 chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
2750 chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
2751 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
2752 chart.offsetX = offset.x * chart.outerRadius;
2753 chart.offsetY = offset.y * chart.outerRadius;
2754
2755 meta.total = me.calculateTotal();
2756
2757 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
2758 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
2759
2760 helpers.each(meta.data, function(arc, index) {
2761 me.updateElement(arc, index, reset);
2762 });
2763 },
2764
2765 updateElement: function(arc, index, reset) {
2766 var me = this;
2767 var chart = me.chart;
2768 var chartArea = chart.chartArea;
2769 var opts = chart.options;
2770 var animationOpts = opts.animation;
2771 var centerX = (chartArea.left + chartArea.right) / 2;
2772 var centerY = (chartArea.top + chartArea.bottom) / 2;
2773 var startAngle = opts.rotation; // non reset case handled later
2774 var endAngle = opts.rotation; // non reset case handled later
2775 var dataset = me.getDataset();
2776 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
2777 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
2778 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
2779 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2780
2781 helpers.extend(arc, {
2782 // Utility
2783 _datasetIndex: me.index,
2784 _index: index,
2785
2786 // Desired view properties
2787 _model: {
2788 x: centerX + chart.offsetX,
2789 y: centerY + chart.offsetY,
2790 startAngle: startAngle,
2791 endAngle: endAngle,
2792 circumference: circumference,
2793 outerRadius: outerRadius,
2794 innerRadius: innerRadius,
2795 label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
2796 }
2797 });
2798
2799 var model = arc._model;
2800 // Resets the visual styles
2801 this.removeHoverStyle(arc);
2802
2803 // Set correct angles if not resetting
2804 if (!reset || !animationOpts.animateRotate) {
2805 if (index === 0) {
2806 model.startAngle = opts.rotation;
2807 } else {
2808 model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
2809 }
2810
2811 model.endAngle = model.startAngle + model.circumference;
2812 }
2813
2814 arc.pivot();
2815 },
2816
2817 removeHoverStyle: function(arc) {
2818 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
2819 },
2820
2821 calculateTotal: function() {
2822 var dataset = this.getDataset();
2823 var meta = this.getMeta();
2824 var total = 0;
2825 var value;
2826
2827 helpers.each(meta.data, function(element, index) {
2828 value = dataset.data[index];
2829 if (!isNaN(value) && !element.hidden) {
2830 total += Math.abs(value);
2831 }
2832 });
2833
2834 /* if (total === 0) {
2835 total = NaN;
2836 }*/
2837
2838 return total;
2839 },
2840
2841 calculateCircumference: function(value) {
2842 var total = this.getMeta().total;
2843 if (total > 0 && !isNaN(value)) {
2844 return (Math.PI * 2.0) * (value / total);
2845 }
2846 return 0;
2847 },
2848
2849 // gets the max border or hover width to properly scale pie charts
2850 getMaxBorderWidth: function(arcs) {
2851 var max = 0;
2852 var index = this.index;
2853 var length = arcs.length;
2854 var borderWidth;
2855 var hoverWidth;
2856
2857 for (var i = 0; i < length; i++) {
2858 borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
2859 hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
2860
2861 max = borderWidth > max ? borderWidth : max;
2862 max = hoverWidth > max ? hoverWidth : max;
2863 }
2864 return max;
2865 }
2866 });
2867};
2868
2869},{"25":25,"40":40,"45":45}],18:[function(require,module,exports){
2870'use strict';
2871
2872var defaults = require(25);
2873var elements = require(40);
2874var helpers = require(45);
2875
2876defaults._set('line', {
2877 showLines: true,
2878 spanGaps: false,
2879
2880 hover: {
2881 mode: 'label'
2882 },
2883
2884 scales: {
2885 xAxes: [{
2886 type: 'category',
2887 id: 'x-axis-0'
2888 }],
2889 yAxes: [{
2890 type: 'linear',
2891 id: 'y-axis-0'
2892 }]
2893 }
2894});
2895
2896module.exports = function(Chart) {
2897
2898 function lineEnabled(dataset, options) {
2899 return helpers.valueOrDefault(dataset.showLine, options.showLines);
2900 }
2901
2902 Chart.controllers.line = Chart.DatasetController.extend({
2903
2904 datasetElementType: elements.Line,
2905
2906 dataElementType: elements.Point,
2907
2908 update: function(reset) {
2909 var me = this;
2910 var meta = me.getMeta();
2911 var line = meta.dataset;
2912 var points = meta.data || [];
2913 var options = me.chart.options;
2914 var lineElementOptions = options.elements.line;
2915 var scale = me.getScaleForId(meta.yAxisID);
2916 var i, ilen, custom;
2917 var dataset = me.getDataset();
2918 var showLine = lineEnabled(dataset, options);
2919
2920 // Update Line
2921 if (showLine) {
2922 custom = line.custom || {};
2923
2924 // Compatibility: If the properties are defined with only the old name, use those values
2925 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
2926 dataset.lineTension = dataset.tension;
2927 }
2928
2929 // Utility
2930 line._scale = scale;
2931 line._datasetIndex = me.index;
2932 // Data
2933 line._children = points;
2934 // Model
2935 line._model = {
2936 // Appearance
2937 // The default behavior of lines is to break at null values, according
2938 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
2939 // This option gives lines the ability to span gaps
2940 spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
2941 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
2942 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
2943 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
2944 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
2945 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
2946 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
2947 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
2948 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
2949 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
2950 steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
2951 cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
2952 };
2953
2954 line.pivot();
2955 }
2956
2957 // Update Points
2958 for (i = 0, ilen = points.length; i < ilen; ++i) {
2959 me.updateElement(points[i], i, reset);
2960 }
2961
2962 if (showLine && line._model.tension !== 0) {
2963 me.updateBezierControlPoints();
2964 }
2965
2966 // Now pivot the point for animation
2967 for (i = 0, ilen = points.length; i < ilen; ++i) {
2968 points[i].pivot();
2969 }
2970 },
2971
2972 getPointBackgroundColor: function(point, index) {
2973 var backgroundColor = this.chart.options.elements.point.backgroundColor;
2974 var dataset = this.getDataset();
2975 var custom = point.custom || {};
2976
2977 if (custom.backgroundColor) {
2978 backgroundColor = custom.backgroundColor;
2979 } else if (dataset.pointBackgroundColor) {
2980 backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
2981 } else if (dataset.backgroundColor) {
2982 backgroundColor = dataset.backgroundColor;
2983 }
2984
2985 return backgroundColor;
2986 },
2987
2988 getPointBorderColor: function(point, index) {
2989 var borderColor = this.chart.options.elements.point.borderColor;
2990 var dataset = this.getDataset();
2991 var custom = point.custom || {};
2992
2993 if (custom.borderColor) {
2994 borderColor = custom.borderColor;
2995 } else if (dataset.pointBorderColor) {
2996 borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
2997 } else if (dataset.borderColor) {
2998 borderColor = dataset.borderColor;
2999 }
3000
3001 return borderColor;
3002 },
3003
3004 getPointBorderWidth: function(point, index) {
3005 var borderWidth = this.chart.options.elements.point.borderWidth;
3006 var dataset = this.getDataset();
3007 var custom = point.custom || {};
3008
3009 if (!isNaN(custom.borderWidth)) {
3010 borderWidth = custom.borderWidth;
3011 } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
3012 borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
3013 } else if (!isNaN(dataset.borderWidth)) {
3014 borderWidth = dataset.borderWidth;
3015 }
3016
3017 return borderWidth;
3018 },
3019
3020 updateElement: function(point, index, reset) {
3021 var me = this;
3022 var meta = me.getMeta();
3023 var custom = point.custom || {};
3024 var dataset = me.getDataset();
3025 var datasetIndex = me.index;
3026 var value = dataset.data[index];
3027 var yScale = me.getScaleForId(meta.yAxisID);
3028 var xScale = me.getScaleForId(meta.xAxisID);
3029 var pointOptions = me.chart.options.elements.point;
3030 var x, y;
3031
3032 // Compatibility: If the properties are defined with only the old name, use those values
3033 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3034 dataset.pointRadius = dataset.radius;
3035 }
3036 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
3037 dataset.pointHitRadius = dataset.hitRadius;
3038 }
3039
3040 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
3041 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
3042
3043 // Utility
3044 point._xScale = xScale;
3045 point._yScale = yScale;
3046 point._datasetIndex = datasetIndex;
3047 point._index = index;
3048
3049 // Desired view properties
3050 point._model = {
3051 x: x,
3052 y: y,
3053 skip: custom.skip || isNaN(x) || isNaN(y),
3054 // Appearance
3055 radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
3056 pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
3057 backgroundColor: me.getPointBackgroundColor(point, index),
3058 borderColor: me.getPointBorderColor(point, index),
3059 borderWidth: me.getPointBorderWidth(point, index),
3060 tension: meta.dataset._model ? meta.dataset._model.tension : 0,
3061 steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
3062 // Tooltip
3063 hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
3064 };
3065 },
3066
3067 calculatePointY: function(value, index, datasetIndex) {
3068 var me = this;
3069 var chart = me.chart;
3070 var meta = me.getMeta();
3071 var yScale = me.getScaleForId(meta.yAxisID);
3072 var sumPos = 0;
3073 var sumNeg = 0;
3074 var i, ds, dsMeta;
3075
3076 if (yScale.options.stacked) {
3077 for (i = 0; i < datasetIndex; i++) {
3078 ds = chart.data.datasets[i];
3079 dsMeta = chart.getDatasetMeta(i);
3080 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
3081 var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
3082 if (stackedRightValue < 0) {
3083 sumNeg += stackedRightValue || 0;
3084 } else {
3085 sumPos += stackedRightValue || 0;
3086 }
3087 }
3088 }
3089
3090 var rightValue = Number(yScale.getRightValue(value));
3091 if (rightValue < 0) {
3092 return yScale.getPixelForValue(sumNeg + rightValue);
3093 }
3094 return yScale.getPixelForValue(sumPos + rightValue);
3095 }
3096
3097 return yScale.getPixelForValue(value);
3098 },
3099
3100 updateBezierControlPoints: function() {
3101 var me = this;
3102 var meta = me.getMeta();
3103 var area = me.chart.chartArea;
3104 var points = (meta.data || []);
3105 var i, ilen, point, model, controlPoints;
3106
3107 // Only consider points that are drawn in case the spanGaps option is used
3108 if (meta.dataset._model.spanGaps) {
3109 points = points.filter(function(pt) {
3110 return !pt._model.skip;
3111 });
3112 }
3113
3114 function capControlPoint(pt, min, max) {
3115 return Math.max(Math.min(pt, max), min);
3116 }
3117
3118 if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
3119 helpers.splineCurveMonotone(points);
3120 } else {
3121 for (i = 0, ilen = points.length; i < ilen; ++i) {
3122 point = points[i];
3123 model = point._model;
3124 controlPoints = helpers.splineCurve(
3125 helpers.previousItem(points, i)._model,
3126 model,
3127 helpers.nextItem(points, i)._model,
3128 meta.dataset._model.tension
3129 );
3130 model.controlPointPreviousX = controlPoints.previous.x;
3131 model.controlPointPreviousY = controlPoints.previous.y;
3132 model.controlPointNextX = controlPoints.next.x;
3133 model.controlPointNextY = controlPoints.next.y;
3134 }
3135 }
3136
3137 if (me.chart.options.elements.line.capBezierPoints) {
3138 for (i = 0, ilen = points.length; i < ilen; ++i) {
3139 model = points[i]._model;
3140 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
3141 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
3142 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
3143 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
3144 }
3145 }
3146 },
3147
3148 draw: function() {
3149 var me = this;
3150 var chart = me.chart;
3151 var meta = me.getMeta();
3152 var points = meta.data || [];
3153 var area = chart.chartArea;
3154 var ilen = points.length;
3155 var i = 0;
3156
3157 helpers.canvas.clipArea(chart.ctx, area);
3158
3159 if (lineEnabled(me.getDataset(), chart.options)) {
3160 meta.dataset.draw();
3161 }
3162
3163 helpers.canvas.unclipArea(chart.ctx);
3164
3165 // Draw the points
3166 for (; i < ilen; ++i) {
3167 points[i].draw(area);
3168 }
3169 },
3170
3171 setHoverStyle: function(point) {
3172 // Point
3173 var dataset = this.chart.data.datasets[point._datasetIndex];
3174 var index = point._index;
3175 var custom = point.custom || {};
3176 var model = point._model;
3177
3178 model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3179 model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3180 model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3181 model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3182 },
3183
3184 removeHoverStyle: function(point) {
3185 var me = this;
3186 var dataset = me.chart.data.datasets[point._datasetIndex];
3187 var index = point._index;
3188 var custom = point.custom || {};
3189 var model = point._model;
3190
3191 // Compatibility: If the properties are defined with only the old name, use those values
3192 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3193 dataset.pointRadius = dataset.radius;
3194 }
3195
3196 model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
3197 model.backgroundColor = me.getPointBackgroundColor(point, index);
3198 model.borderColor = me.getPointBorderColor(point, index);
3199 model.borderWidth = me.getPointBorderWidth(point, index);
3200 }
3201 });
3202};
3203
3204},{"25":25,"40":40,"45":45}],19:[function(require,module,exports){
3205'use strict';
3206
3207var defaults = require(25);
3208var elements = require(40);
3209var helpers = require(45);
3210
3211defaults._set('polarArea', {
3212 scale: {
3213 type: 'radialLinear',
3214 angleLines: {
3215 display: false
3216 },
3217 gridLines: {
3218 circular: true
3219 },
3220 pointLabels: {
3221 display: false
3222 },
3223 ticks: {
3224 beginAtZero: true
3225 }
3226 },
3227
3228 // Boolean - Whether to animate the rotation of the chart
3229 animation: {
3230 animateRotate: true,
3231 animateScale: true
3232 },
3233
3234 startAngle: -0.5 * Math.PI,
3235 legendCallback: function(chart) {
3236 var text = [];
3237 text.push('<ul class="' + chart.id + '-legend">');
3238
3239 var data = chart.data;
3240 var datasets = data.datasets;
3241 var labels = data.labels;
3242
3243 if (datasets.length) {
3244 for (var i = 0; i < datasets[0].data.length; ++i) {
3245 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
3246 if (labels[i]) {
3247 text.push(labels[i]);
3248 }
3249 text.push('</li>');
3250 }
3251 }
3252
3253 text.push('</ul>');
3254 return text.join('');
3255 },
3256 legend: {
3257 labels: {
3258 generateLabels: function(chart) {
3259 var data = chart.data;
3260 if (data.labels.length && data.datasets.length) {
3261 return data.labels.map(function(label, i) {
3262 var meta = chart.getDatasetMeta(0);
3263 var ds = data.datasets[0];
3264 var arc = meta.data[i];
3265 var custom = arc.custom || {};
3266 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
3267 var arcOpts = chart.options.elements.arc;
3268 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
3269 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
3270 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
3271
3272 return {
3273 text: label,
3274 fillStyle: fill,
3275 strokeStyle: stroke,
3276 lineWidth: bw,
3277 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
3278
3279 // Extra data used for toggling the correct item
3280 index: i
3281 };
3282 });
3283 }
3284 return [];
3285 }
3286 },
3287
3288 onClick: function(e, legendItem) {
3289 var index = legendItem.index;
3290 var chart = this.chart;
3291 var i, ilen, meta;
3292
3293 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
3294 meta = chart.getDatasetMeta(i);
3295 meta.data[index].hidden = !meta.data[index].hidden;
3296 }
3297
3298 chart.update();
3299 }
3300 },
3301
3302 // Need to override these to give a nice default
3303 tooltips: {
3304 callbacks: {
3305 title: function() {
3306 return '';
3307 },
3308 label: function(item, data) {
3309 return data.labels[item.index] + ': ' + item.yLabel;
3310 }
3311 }
3312 }
3313});
3314
3315module.exports = function(Chart) {
3316
3317 Chart.controllers.polarArea = Chart.DatasetController.extend({
3318
3319 dataElementType: elements.Arc,
3320
3321 linkScales: helpers.noop,
3322
3323 update: function(reset) {
3324 var me = this;
3325 var chart = me.chart;
3326 var chartArea = chart.chartArea;
3327 var meta = me.getMeta();
3328 var opts = chart.options;
3329 var arcOpts = opts.elements.arc;
3330 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
3331 chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
3332 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
3333 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
3334
3335 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
3336 me.innerRadius = me.outerRadius - chart.radiusLength;
3337
3338 meta.count = me.countVisibleElements();
3339
3340 helpers.each(meta.data, function(arc, index) {
3341 me.updateElement(arc, index, reset);
3342 });
3343 },
3344
3345 updateElement: function(arc, index, reset) {
3346 var me = this;
3347 var chart = me.chart;
3348 var dataset = me.getDataset();
3349 var opts = chart.options;
3350 var animationOpts = opts.animation;
3351 var scale = chart.scale;
3352 var labels = chart.data.labels;
3353
3354 var circumference = me.calculateCircumference(dataset.data[index]);
3355 var centerX = scale.xCenter;
3356 var centerY = scale.yCenter;
3357
3358 // If there is NaN data before us, we need to calculate the starting angle correctly.
3359 // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
3360 var visibleCount = 0;
3361 var meta = me.getMeta();
3362 for (var i = 0; i < index; ++i) {
3363 if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
3364 ++visibleCount;
3365 }
3366 }
3367
3368 // var negHalfPI = -0.5 * Math.PI;
3369 var datasetStartAngle = opts.startAngle;
3370 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3371 var startAngle = datasetStartAngle + (circumference * visibleCount);
3372 var endAngle = startAngle + (arc.hidden ? 0 : circumference);
3373
3374 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3375
3376 helpers.extend(arc, {
3377 // Utility
3378 _datasetIndex: me.index,
3379 _index: index,
3380 _scale: scale,
3381
3382 // Desired view properties
3383 _model: {
3384 x: centerX,
3385 y: centerY,
3386 innerRadius: 0,
3387 outerRadius: reset ? resetRadius : distance,
3388 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
3389 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
3390 label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
3391 }
3392 });
3393
3394 // Apply border and fill style
3395 me.removeHoverStyle(arc);
3396
3397 arc.pivot();
3398 },
3399
3400 removeHoverStyle: function(arc) {
3401 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
3402 },
3403
3404 countVisibleElements: function() {
3405 var dataset = this.getDataset();
3406 var meta = this.getMeta();
3407 var count = 0;
3408
3409 helpers.each(meta.data, function(element, index) {
3410 if (!isNaN(dataset.data[index]) && !element.hidden) {
3411 count++;
3412 }
3413 });
3414
3415 return count;
3416 },
3417
3418 calculateCircumference: function(value) {
3419 var count = this.getMeta().count;
3420 if (count > 0 && !isNaN(value)) {
3421 return (2 * Math.PI) / count;
3422 }
3423 return 0;
3424 }
3425 });
3426};
3427
3428},{"25":25,"40":40,"45":45}],20:[function(require,module,exports){
3429'use strict';
3430
3431var defaults = require(25);
3432var elements = require(40);
3433var helpers = require(45);
3434
3435defaults._set('radar', {
3436 scale: {
3437 type: 'radialLinear'
3438 },
3439 elements: {
3440 line: {
3441 tension: 0 // no bezier in radar
3442 }
3443 }
3444});
3445
3446module.exports = function(Chart) {
3447
3448 Chart.controllers.radar = Chart.DatasetController.extend({
3449
3450 datasetElementType: elements.Line,
3451
3452 dataElementType: elements.Point,
3453
3454 linkScales: helpers.noop,
3455
3456 update: function(reset) {
3457 var me = this;
3458 var meta = me.getMeta();
3459 var line = meta.dataset;
3460 var points = meta.data;
3461 var custom = line.custom || {};
3462 var dataset = me.getDataset();
3463 var lineElementOptions = me.chart.options.elements.line;
3464 var scale = me.chart.scale;
3465
3466 // Compatibility: If the properties are defined with only the old name, use those values
3467 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
3468 dataset.lineTension = dataset.tension;
3469 }
3470
3471 helpers.extend(meta.dataset, {
3472 // Utility
3473 _datasetIndex: me.index,
3474 _scale: scale,
3475 // Data
3476 _children: points,
3477 _loop: true,
3478 // Model
3479 _model: {
3480 // Appearance
3481 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
3482 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
3483 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
3484 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
3485 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
3486 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
3487 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
3488 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
3489 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
3490 }
3491 });
3492
3493 meta.dataset.pivot();
3494
3495 // Update Points
3496 helpers.each(points, function(point, index) {
3497 me.updateElement(point, index, reset);
3498 }, me);
3499
3500 // Update bezier control points
3501 me.updateBezierControlPoints();
3502 },
3503 updateElement: function(point, index, reset) {
3504 var me = this;
3505 var custom = point.custom || {};
3506 var dataset = me.getDataset();
3507 var scale = me.chart.scale;
3508 var pointElementOptions = me.chart.options.elements.point;
3509 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
3510
3511 // Compatibility: If the properties are defined with only the old name, use those values
3512 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3513 dataset.pointRadius = dataset.radius;
3514 }
3515 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
3516 dataset.pointHitRadius = dataset.hitRadius;
3517 }
3518
3519 helpers.extend(point, {
3520 // Utility
3521 _datasetIndex: me.index,
3522 _index: index,
3523 _scale: scale,
3524
3525 // Desired view properties
3526 _model: {
3527 x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
3528 y: reset ? scale.yCenter : pointPosition.y,
3529
3530 // Appearance
3531 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
3532 radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
3533 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
3534 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
3535 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
3536 pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
3537
3538 // Tooltip
3539 hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
3540 }
3541 });
3542
3543 point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
3544 },
3545 updateBezierControlPoints: function() {
3546 var chartArea = this.chart.chartArea;
3547 var meta = this.getMeta();
3548
3549 helpers.each(meta.data, function(point, index) {
3550 var model = point._model;
3551 var controlPoints = helpers.splineCurve(
3552 helpers.previousItem(meta.data, index, true)._model,
3553 model,
3554 helpers.nextItem(meta.data, index, true)._model,
3555 model.tension
3556 );
3557
3558 // Prevent the bezier going outside of the bounds of the graph
3559 model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
3560 model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
3561
3562 model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
3563 model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
3564
3565 // Now pivot the point for animation
3566 point.pivot();
3567 });
3568 },
3569
3570 setHoverStyle: function(point) {
3571 // Point
3572 var dataset = this.chart.data.datasets[point._datasetIndex];
3573 var custom = point.custom || {};
3574 var index = point._index;
3575 var model = point._model;
3576
3577 model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3578 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3579 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3580 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3581 },
3582
3583 removeHoverStyle: function(point) {
3584 var dataset = this.chart.data.datasets[point._datasetIndex];
3585 var custom = point.custom || {};
3586 var index = point._index;
3587 var model = point._model;
3588 var pointElementOptions = this.chart.options.elements.point;
3589
3590 model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius);
3591 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
3592 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
3593 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
3594 }
3595 });
3596};
3597
3598},{"25":25,"40":40,"45":45}],21:[function(require,module,exports){
3599'use strict';
3600
3601var defaults = require(25);
3602
3603defaults._set('scatter', {
3604 hover: {
3605 mode: 'single'
3606 },
3607
3608 scales: {
3609 xAxes: [{
3610 id: 'x-axis-1', // need an ID so datasets can reference the scale
3611 type: 'linear', // scatter should not use a category axis
3612 position: 'bottom'
3613 }],
3614 yAxes: [{
3615 id: 'y-axis-1',
3616 type: 'linear',
3617 position: 'left'
3618 }]
3619 },
3620
3621 showLines: false,
3622
3623 tooltips: {
3624 callbacks: {
3625 title: function() {
3626 return ''; // doesn't make sense for scatter since data are formatted as a point
3627 },
3628 label: function(item) {
3629 return '(' + item.xLabel + ', ' + item.yLabel + ')';
3630 }
3631 }
3632 }
3633});
3634
3635module.exports = function(Chart) {
3636
3637 // Scatter charts use line controllers
3638 Chart.controllers.scatter = Chart.controllers.line;
3639
3640};
3641
3642},{"25":25}],22:[function(require,module,exports){
3643/* global window: false */
3644'use strict';
3645
3646var defaults = require(25);
3647var Element = require(26);
3648var helpers = require(45);
3649
3650defaults._set('global', {
3651 animation: {
3652 duration: 1000,
3653 easing: 'easeOutQuart',
3654 onProgress: helpers.noop,
3655 onComplete: helpers.noop
3656 }
3657});
3658
3659module.exports = function(Chart) {
3660
3661 Chart.Animation = Element.extend({
3662 chart: null, // the animation associated chart instance
3663 currentStep: 0, // the current animation step
3664 numSteps: 60, // default number of steps
3665 easing: '', // the easing to use for this animation
3666 render: null, // render function used by the animation service
3667
3668 onAnimationProgress: null, // user specified callback to fire on each step of the animation
3669 onAnimationComplete: null, // user specified callback to fire when the animation finishes
3670 });
3671
3672 Chart.animationService = {
3673 frameDuration: 17,
3674 animations: [],
3675 dropFrames: 0,
3676 request: null,
3677
3678 /**
3679 * @param {Chart} chart - The chart to animate.
3680 * @param {Chart.Animation} animation - The animation that we will animate.
3681 * @param {Number} duration - The animation duration in ms.
3682 * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
3683 */
3684 addAnimation: function(chart, animation, duration, lazy) {
3685 var animations = this.animations;
3686 var i, ilen;
3687
3688 animation.chart = chart;
3689
3690 if (!lazy) {
3691 chart.animating = true;
3692 }
3693
3694 for (i = 0, ilen = animations.length; i < ilen; ++i) {
3695 if (animations[i].chart === chart) {
3696 animations[i] = animation;
3697 return;
3698 }
3699 }
3700
3701 animations.push(animation);
3702
3703 // If there are no animations queued, manually kickstart a digest, for lack of a better word
3704 if (animations.length === 1) {
3705 this.requestAnimationFrame();
3706 }
3707 },
3708
3709 cancelAnimation: function(chart) {
3710 var index = helpers.findIndex(this.animations, function(animation) {
3711 return animation.chart === chart;
3712 });
3713
3714 if (index !== -1) {
3715 this.animations.splice(index, 1);
3716 chart.animating = false;
3717 }
3718 },
3719
3720 requestAnimationFrame: function() {
3721 var me = this;
3722 if (me.request === null) {
3723 // Skip animation frame requests until the active one is executed.
3724 // This can happen when processing mouse events, e.g. 'mousemove'
3725 // and 'mouseout' events will trigger multiple renders.
3726 me.request = helpers.requestAnimFrame.call(window, function() {
3727 me.request = null;
3728 me.startDigest();
3729 });
3730 }
3731 },
3732
3733 /**
3734 * @private
3735 */
3736 startDigest: function() {
3737 var me = this;
3738 var startTime = Date.now();
3739 var framesToDrop = 0;
3740
3741 if (me.dropFrames > 1) {
3742 framesToDrop = Math.floor(me.dropFrames);
3743 me.dropFrames = me.dropFrames % 1;
3744 }
3745
3746 me.advance(1 + framesToDrop);
3747
3748 var endTime = Date.now();
3749
3750 me.dropFrames += (endTime - startTime) / me.frameDuration;
3751
3752 // Do we have more stuff to animate?
3753 if (me.animations.length > 0) {
3754 me.requestAnimationFrame();
3755 }
3756 },
3757
3758 /**
3759 * @private
3760 */
3761 advance: function(count) {
3762 var animations = this.animations;
3763 var animation, chart;
3764 var i = 0;
3765
3766 while (i < animations.length) {
3767 animation = animations[i];
3768 chart = animation.chart;
3769
3770 animation.currentStep = (animation.currentStep || 0) + count;
3771 animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
3772
3773 helpers.callback(animation.render, [chart, animation], chart);
3774 helpers.callback(animation.onAnimationProgress, [animation], chart);
3775
3776 if (animation.currentStep >= animation.numSteps) {
3777 helpers.callback(animation.onAnimationComplete, [animation], chart);
3778 chart.animating = false;
3779 animations.splice(i, 1);
3780 } else {
3781 ++i;
3782 }
3783 }
3784 }
3785 };
3786
3787 /**
3788 * Provided for backward compatibility, use Chart.Animation instead
3789 * @prop Chart.Animation#animationObject
3790 * @deprecated since version 2.6.0
3791 * @todo remove at version 3
3792 */
3793 Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
3794 get: function() {
3795 return this;
3796 }
3797 });
3798
3799 /**
3800 * Provided for backward compatibility, use Chart.Animation#chart instead
3801 * @prop Chart.Animation#chartInstance
3802 * @deprecated since version 2.6.0
3803 * @todo remove at version 3
3804 */
3805 Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
3806 get: function() {
3807 return this.chart;
3808 },
3809 set: function(value) {
3810 this.chart = value;
3811 }
3812 });
3813
3814};
3815
3816},{"25":25,"26":26,"45":45}],23:[function(require,module,exports){
3817'use strict';
3818
3819var defaults = require(25);
3820var helpers = require(45);
3821var Interaction = require(28);
3822var layouts = require(30);
3823var platform = require(48);
3824var plugins = require(31);
3825
3826module.exports = function(Chart) {
3827
3828 // Create a dictionary of chart types, to allow for extension of existing types
3829 Chart.types = {};
3830
3831 // Store a reference to each instance - allowing us to globally resize chart instances on window resize.
3832 // Destroy method on the chart will remove the instance of the chart from this reference.
3833 Chart.instances = {};
3834
3835 // Controllers available for dataset visualization eg. bar, line, slice, etc.
3836 Chart.controllers = {};
3837
3838 /**
3839 * Initializes the given config with global and chart default values.
3840 */
3841 function initConfig(config) {
3842 config = config || {};
3843
3844 // Do NOT use configMerge() for the data object because this method merges arrays
3845 // and so would change references to labels and datasets, preventing data updates.
3846 var data = config.data = config.data || {};
3847 data.datasets = data.datasets || [];
3848 data.labels = data.labels || [];
3849
3850 config.options = helpers.configMerge(
3851 defaults.global,
3852 defaults[config.type],
3853 config.options || {});
3854
3855 return config;
3856 }
3857
3858 /**
3859 * Updates the config of the chart
3860 * @param chart {Chart} chart to update the options for
3861 */
3862 function updateConfig(chart) {
3863 var newOptions = chart.options;
3864
3865 helpers.each(chart.scales, function(scale) {
3866 layouts.removeBox(chart, scale);
3867 });
3868
3869 newOptions = helpers.configMerge(
3870 Chart.defaults.global,
3871 Chart.defaults[chart.config.type],
3872 newOptions);
3873
3874 chart.options = chart.config.options = newOptions;
3875 chart.ensureScalesHaveIDs();
3876 chart.buildOrUpdateScales();
3877 // Tooltip
3878 chart.tooltip._options = newOptions.tooltips;
3879 chart.tooltip.initialize();
3880 }
3881
3882 function positionIsHorizontal(position) {
3883 return position === 'top' || position === 'bottom';
3884 }
3885
3886 helpers.extend(Chart.prototype, /** @lends Chart */ {
3887 /**
3888 * @private
3889 */
3890 construct: function(item, config) {
3891 var me = this;
3892
3893 config = initConfig(config);
3894
3895 var context = platform.acquireContext(item, config);
3896 var canvas = context && context.canvas;
3897 var height = canvas && canvas.height;
3898 var width = canvas && canvas.width;
3899
3900 me.id = helpers.uid();
3901 me.ctx = context;
3902 me.canvas = canvas;
3903 me.config = config;
3904 me.width = width;
3905 me.height = height;
3906 me.aspectRatio = height ? width / height : null;
3907 me.options = config.options;
3908 me._bufferedRender = false;
3909
3910 /**
3911 * Provided for backward compatibility, Chart and Chart.Controller have been merged,
3912 * the "instance" still need to be defined since it might be called from plugins.
3913 * @prop Chart#chart
3914 * @deprecated since version 2.6.0
3915 * @todo remove at version 3
3916 * @private
3917 */
3918 me.chart = me;
3919 me.controller = me; // chart.chart.controller #inception
3920
3921 // Add the chart instance to the global namespace
3922 Chart.instances[me.id] = me;
3923
3924 // Define alias to the config data: `chart.data === chart.config.data`
3925 Object.defineProperty(me, 'data', {
3926 get: function() {
3927 return me.config.data;
3928 },
3929 set: function(value) {
3930 me.config.data = value;
3931 }
3932 });
3933
3934 if (!context || !canvas) {
3935 // The given item is not a compatible context2d element, let's return before finalizing
3936 // the chart initialization but after setting basic chart / controller properties that
3937 // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
3938 // https://github.com/chartjs/Chart.js/issues/2807
3939 console.error("Failed to create chart: can't acquire context from the given item");
3940 return;
3941 }
3942
3943 me.initialize();
3944 me.update();
3945 },
3946
3947 /**
3948 * @private
3949 */
3950 initialize: function() {
3951 var me = this;
3952
3953 // Before init plugin notification
3954 plugins.notify(me, 'beforeInit');
3955
3956 helpers.retinaScale(me, me.options.devicePixelRatio);
3957
3958 me.bindEvents();
3959
3960 if (me.options.responsive) {
3961 // Initial resize before chart draws (must be silent to preserve initial animations).
3962 me.resize(true);
3963 }
3964
3965 // Make sure scales have IDs and are built before we build any controllers.
3966 me.ensureScalesHaveIDs();
3967 me.buildOrUpdateScales();
3968 me.initToolTip();
3969
3970 // After init plugin notification
3971 plugins.notify(me, 'afterInit');
3972
3973 return me;
3974 },
3975
3976 clear: function() {
3977 helpers.canvas.clear(this);
3978 return this;
3979 },
3980
3981 stop: function() {
3982 // Stops any current animation loop occurring
3983 Chart.animationService.cancelAnimation(this);
3984 return this;
3985 },
3986
3987 resize: function(silent) {
3988 var me = this;
3989 var options = me.options;
3990 var canvas = me.canvas;
3991 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
3992
3993 // the canvas render width and height will be casted to integers so make sure that
3994 // the canvas display style uses the same integer values to avoid blurring effect.
3995
3996 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased
3997 var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
3998 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
3999
4000 if (me.width === newWidth && me.height === newHeight) {
4001 return;
4002 }
4003
4004 canvas.width = me.width = newWidth;
4005 canvas.height = me.height = newHeight;
4006 canvas.style.width = newWidth + 'px';
4007 canvas.style.height = newHeight + 'px';
4008
4009 helpers.retinaScale(me, options.devicePixelRatio);
4010
4011 if (!silent) {
4012 // Notify any plugins about the resize
4013 var newSize = {width: newWidth, height: newHeight};
4014 plugins.notify(me, 'resize', [newSize]);
4015
4016 // Notify of resize
4017 if (me.options.onResize) {
4018 me.options.onResize(me, newSize);
4019 }
4020
4021 me.stop();
4022 me.update(me.options.responsiveAnimationDuration);
4023 }
4024 },
4025
4026 ensureScalesHaveIDs: function() {
4027 var options = this.options;
4028 var scalesOptions = options.scales || {};
4029 var scaleOptions = options.scale;
4030
4031 helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
4032 xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
4033 });
4034
4035 helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
4036 yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
4037 });
4038
4039 if (scaleOptions) {
4040 scaleOptions.id = scaleOptions.id || 'scale';
4041 }
4042 },
4043
4044 /**
4045 * Builds a map of scale ID to scale object for future lookup.
4046 */
4047 buildOrUpdateScales: function() {
4048 var me = this;
4049 var options = me.options;
4050 var scales = me.scales || {};
4051 var items = [];
4052 var updated = Object.keys(scales).reduce(function(obj, id) {
4053 obj[id] = false;
4054 return obj;
4055 }, {});
4056
4057 if (options.scales) {
4058 items = items.concat(
4059 (options.scales.xAxes || []).map(function(xAxisOptions) {
4060 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
4061 }),
4062 (options.scales.yAxes || []).map(function(yAxisOptions) {
4063 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
4064 })
4065 );
4066 }
4067
4068 if (options.scale) {
4069 items.push({
4070 options: options.scale,
4071 dtype: 'radialLinear',
4072 isDefault: true,
4073 dposition: 'chartArea'
4074 });
4075 }
4076
4077 helpers.each(items, function(item) {
4078 var scaleOptions = item.options;
4079 var id = scaleOptions.id;
4080 var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype);
4081
4082 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
4083 scaleOptions.position = item.dposition;
4084 }
4085
4086 updated[id] = true;
4087 var scale = null;
4088 if (id in scales && scales[id].type === scaleType) {
4089 scale = scales[id];
4090 scale.options = scaleOptions;
4091 scale.ctx = me.ctx;
4092 scale.chart = me;
4093 } else {
4094 var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
4095 if (!scaleClass) {
4096 return;
4097 }
4098 scale = new scaleClass({
4099 id: id,
4100 type: scaleType,
4101 options: scaleOptions,
4102 ctx: me.ctx,
4103 chart: me
4104 });
4105 scales[scale.id] = scale;
4106 }
4107
4108 scale.mergeTicksOptions();
4109
4110 // TODO(SB): I think we should be able to remove this custom case (options.scale)
4111 // and consider it as a regular scale part of the "scales"" map only! This would
4112 // make the logic easier and remove some useless? custom code.
4113 if (item.isDefault) {
4114 me.scale = scale;
4115 }
4116 });
4117 // clear up discarded scales
4118 helpers.each(updated, function(hasUpdated, id) {
4119 if (!hasUpdated) {
4120 delete scales[id];
4121 }
4122 });
4123
4124 me.scales = scales;
4125
4126 Chart.scaleService.addScalesToLayout(this);
4127 },
4128
4129 buildOrUpdateControllers: function() {
4130 var me = this;
4131 var types = [];
4132 var newControllers = [];
4133
4134 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4135 var meta = me.getDatasetMeta(datasetIndex);
4136 var type = dataset.type || me.config.type;
4137
4138 if (meta.type && meta.type !== type) {
4139 me.destroyDatasetMeta(datasetIndex);
4140 meta = me.getDatasetMeta(datasetIndex);
4141 }
4142 meta.type = type;
4143
4144 types.push(meta.type);
4145
4146 if (meta.controller) {
4147 meta.controller.updateIndex(datasetIndex);
4148 meta.controller.linkScales();
4149 } else {
4150 var ControllerClass = Chart.controllers[meta.type];
4151 if (ControllerClass === undefined) {
4152 throw new Error('"' + meta.type + '" is not a chart type.');
4153 }
4154
4155 meta.controller = new ControllerClass(me, datasetIndex);
4156 newControllers.push(meta.controller);
4157 }
4158 }, me);
4159
4160 return newControllers;
4161 },
4162
4163 /**
4164 * Reset the elements of all datasets
4165 * @private
4166 */
4167 resetElements: function() {
4168 var me = this;
4169 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4170 me.getDatasetMeta(datasetIndex).controller.reset();
4171 }, me);
4172 },
4173
4174 /**
4175 * Resets the chart back to it's state before the initial animation
4176 */
4177 reset: function() {
4178 this.resetElements();
4179 this.tooltip.initialize();
4180 },
4181
4182 update: function(config) {
4183 var me = this;
4184
4185 if (!config || typeof config !== 'object') {
4186 // backwards compatibility
4187 config = {
4188 duration: config,
4189 lazy: arguments[1]
4190 };
4191 }
4192
4193 updateConfig(me);
4194
4195 if (plugins.notify(me, 'beforeUpdate') === false) {
4196 return;
4197 }
4198
4199 // In case the entire data object changed
4200 me.tooltip._data = me.data;
4201
4202 // Make sure dataset controllers are updated and new controllers are reset
4203 var newControllers = me.buildOrUpdateControllers();
4204
4205 // Make sure all dataset controllers have correct meta data counts
4206 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4207 me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
4208 }, me);
4209
4210 me.updateLayout();
4211
4212 // Can only reset the new controllers after the scales have been updated
4213 if (me.options.animation && me.options.animation.duration) {
4214 helpers.each(newControllers, function(controller) {
4215 controller.reset();
4216 });
4217 }
4218
4219 me.updateDatasets();
4220
4221 // Need to reset tooltip in case it is displayed with elements that are removed
4222 // after update.
4223 me.tooltip.initialize();
4224
4225 // Last active contains items that were previously in the tooltip.
4226 // When we reset the tooltip, we need to clear it
4227 me.lastActive = [];
4228
4229 // Do this before render so that any plugins that need final scale updates can use it
4230 plugins.notify(me, 'afterUpdate');
4231
4232 if (me._bufferedRender) {
4233 me._bufferedRequest = {
4234 duration: config.duration,
4235 easing: config.easing,
4236 lazy: config.lazy
4237 };
4238 } else {
4239 me.render(config);
4240 }
4241 },
4242
4243 /**
4244 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
4245 * hook, in which case, plugins will not be called on `afterLayout`.
4246 * @private
4247 */
4248 updateLayout: function() {
4249 var me = this;
4250
4251 if (plugins.notify(me, 'beforeLayout') === false) {
4252 return;
4253 }
4254
4255 layouts.update(this, this.width, this.height);
4256
4257 /**
4258 * Provided for backward compatibility, use `afterLayout` instead.
4259 * @method IPlugin#afterScaleUpdate
4260 * @deprecated since version 2.5.0
4261 * @todo remove at version 3
4262 * @private
4263 */
4264 plugins.notify(me, 'afterScaleUpdate');
4265 plugins.notify(me, 'afterLayout');
4266 },
4267
4268 /**
4269 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
4270 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
4271 * @private
4272 */
4273 updateDatasets: function() {
4274 var me = this;
4275
4276 if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
4277 return;
4278 }
4279
4280 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4281 me.updateDataset(i);
4282 }
4283
4284 plugins.notify(me, 'afterDatasetsUpdate');
4285 },
4286
4287 /**
4288 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
4289 * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
4290 * @private
4291 */
4292 updateDataset: function(index) {
4293 var me = this;
4294 var meta = me.getDatasetMeta(index);
4295 var args = {
4296 meta: meta,
4297 index: index
4298 };
4299
4300 if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
4301 return;
4302 }
4303
4304 meta.controller.update();
4305
4306 plugins.notify(me, 'afterDatasetUpdate', [args]);
4307 },
4308
4309 render: function(config) {
4310 var me = this;
4311
4312 if (!config || typeof config !== 'object') {
4313 // backwards compatibility
4314 config = {
4315 duration: config,
4316 lazy: arguments[1]
4317 };
4318 }
4319
4320 var duration = config.duration;
4321 var lazy = config.lazy;
4322
4323 if (plugins.notify(me, 'beforeRender') === false) {
4324 return;
4325 }
4326
4327 var animationOptions = me.options.animation;
4328 var onComplete = function(animation) {
4329 plugins.notify(me, 'afterRender');
4330 helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
4331 };
4332
4333 if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
4334 var animation = new Chart.Animation({
4335 numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
4336 easing: config.easing || animationOptions.easing,
4337
4338 render: function(chart, animationObject) {
4339 var easingFunction = helpers.easing.effects[animationObject.easing];
4340 var currentStep = animationObject.currentStep;
4341 var stepDecimal = currentStep / animationObject.numSteps;
4342
4343 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
4344 },
4345
4346 onAnimationProgress: animationOptions.onProgress,
4347 onAnimationComplete: onComplete
4348 });
4349
4350 Chart.animationService.addAnimation(me, animation, duration, lazy);
4351 } else {
4352 me.draw();
4353
4354 // See https://github.com/chartjs/Chart.js/issues/3781
4355 onComplete(new Chart.Animation({numSteps: 0, chart: me}));
4356 }
4357
4358 return me;
4359 },
4360
4361 draw: function(easingValue) {
4362 var me = this;
4363
4364 me.clear();
4365
4366 if (helpers.isNullOrUndef(easingValue)) {
4367 easingValue = 1;
4368 }
4369
4370 me.transition(easingValue);
4371
4372 if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
4373 return;
4374 }
4375
4376 // Draw all the scales
4377 helpers.each(me.boxes, function(box) {
4378 box.draw(me.chartArea);
4379 }, me);
4380
4381 if (me.scale) {
4382 me.scale.draw();
4383 }
4384
4385 me.drawDatasets(easingValue);
4386 me._drawTooltip(easingValue);
4387
4388 plugins.notify(me, 'afterDraw', [easingValue]);
4389 },
4390
4391 /**
4392 * @private
4393 */
4394 transition: function(easingValue) {
4395 var me = this;
4396
4397 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
4398 if (me.isDatasetVisible(i)) {
4399 me.getDatasetMeta(i).controller.transition(easingValue);
4400 }
4401 }
4402
4403 me.tooltip.transition(easingValue);
4404 },
4405
4406 /**
4407 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
4408 * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
4409 * @private
4410 */
4411 drawDatasets: function(easingValue) {
4412 var me = this;
4413
4414 if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
4415 return;
4416 }
4417
4418 // Draw datasets reversed to support proper line stacking
4419 for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
4420 if (me.isDatasetVisible(i)) {
4421 me.drawDataset(i, easingValue);
4422 }
4423 }
4424
4425 plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
4426 },
4427
4428 /**
4429 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
4430 * hook, in which case, plugins will not be called on `afterDatasetDraw`.
4431 * @private
4432 */
4433 drawDataset: function(index, easingValue) {
4434 var me = this;
4435 var meta = me.getDatasetMeta(index);
4436 var args = {
4437 meta: meta,
4438 index: index,
4439 easingValue: easingValue
4440 };
4441
4442 if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
4443 return;
4444 }
4445
4446 meta.controller.draw(easingValue);
4447
4448 plugins.notify(me, 'afterDatasetDraw', [args]);
4449 },
4450
4451 /**
4452 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
4453 * hook, in which case, plugins will not be called on `afterTooltipDraw`.
4454 * @private
4455 */
4456 _drawTooltip: function(easingValue) {
4457 var me = this;
4458 var tooltip = me.tooltip;
4459 var args = {
4460 tooltip: tooltip,
4461 easingValue: easingValue
4462 };
4463
4464 if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
4465 return;
4466 }
4467
4468 tooltip.draw();
4469
4470 plugins.notify(me, 'afterTooltipDraw', [args]);
4471 },
4472
4473 // Get the single element that was clicked on
4474 // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
4475 getElementAtEvent: function(e) {
4476 return Interaction.modes.single(this, e);
4477 },
4478
4479 getElementsAtEvent: function(e) {
4480 return Interaction.modes.label(this, e, {intersect: true});
4481 },
4482
4483 getElementsAtXAxis: function(e) {
4484 return Interaction.modes['x-axis'](this, e, {intersect: true});
4485 },
4486
4487 getElementsAtEventForMode: function(e, mode, options) {
4488 var method = Interaction.modes[mode];
4489 if (typeof method === 'function') {
4490 return method(this, e, options);
4491 }
4492
4493 return [];
4494 },
4495
4496 getDatasetAtEvent: function(e) {
4497 return Interaction.modes.dataset(this, e, {intersect: true});
4498 },
4499
4500 getDatasetMeta: function(datasetIndex) {
4501 var me = this;
4502 var dataset = me.data.datasets[datasetIndex];
4503 if (!dataset._meta) {
4504 dataset._meta = {};
4505 }
4506
4507 var meta = dataset._meta[me.id];
4508 if (!meta) {
4509 meta = dataset._meta[me.id] = {
4510 type: null,
4511 data: [],
4512 dataset: null,
4513 controller: null,
4514 hidden: null, // See isDatasetVisible() comment
4515 xAxisID: null,
4516 yAxisID: null
4517 };
4518 }
4519
4520 return meta;
4521 },
4522
4523 getVisibleDatasetCount: function() {
4524 var count = 0;
4525 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
4526 if (this.isDatasetVisible(i)) {
4527 count++;
4528 }
4529 }
4530 return count;
4531 },
4532
4533 isDatasetVisible: function(datasetIndex) {
4534 var meta = this.getDatasetMeta(datasetIndex);
4535
4536 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
4537 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
4538 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
4539 },
4540
4541 generateLegend: function() {
4542 return this.options.legendCallback(this);
4543 },
4544
4545 /**
4546 * @private
4547 */
4548 destroyDatasetMeta: function(datasetIndex) {
4549 var id = this.id;
4550 var dataset = this.data.datasets[datasetIndex];
4551 var meta = dataset._meta && dataset._meta[id];
4552
4553 if (meta) {
4554 meta.controller.destroy();
4555 delete dataset._meta[id];
4556 }
4557 },
4558
4559 destroy: function() {
4560 var me = this;
4561 var canvas = me.canvas;
4562 var i, ilen;
4563
4564 me.stop();
4565
4566 // dataset controllers need to cleanup associated data
4567 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4568 me.destroyDatasetMeta(i);
4569 }
4570
4571 if (canvas) {
4572 me.unbindEvents();
4573 helpers.canvas.clear(me);
4574 platform.releaseContext(me.ctx);
4575 me.canvas = null;
4576 me.ctx = null;
4577 }
4578
4579 plugins.notify(me, 'destroy');
4580
4581 delete Chart.instances[me.id];
4582 },
4583
4584 toBase64Image: function() {
4585 return this.canvas.toDataURL.apply(this.canvas, arguments);
4586 },
4587
4588 initToolTip: function() {
4589 var me = this;
4590 me.tooltip = new Chart.Tooltip({
4591 _chart: me,
4592 _chartInstance: me, // deprecated, backward compatibility
4593 _data: me.data,
4594 _options: me.options.tooltips
4595 }, me);
4596 },
4597
4598 /**
4599 * @private
4600 */
4601 bindEvents: function() {
4602 var me = this;
4603 var listeners = me._listeners = {};
4604 var listener = function() {
4605 me.eventHandler.apply(me, arguments);
4606 };
4607
4608 helpers.each(me.options.events, function(type) {
4609 platform.addEventListener(me, type, listener);
4610 listeners[type] = listener;
4611 });
4612
4613 // Elements used to detect size change should not be injected for non responsive charts.
4614 // See https://github.com/chartjs/Chart.js/issues/2210
4615 if (me.options.responsive) {
4616 listener = function() {
4617 me.resize();
4618 };
4619
4620 platform.addEventListener(me, 'resize', listener);
4621 listeners.resize = listener;
4622 }
4623 },
4624
4625 /**
4626 * @private
4627 */
4628 unbindEvents: function() {
4629 var me = this;
4630 var listeners = me._listeners;
4631 if (!listeners) {
4632 return;
4633 }
4634
4635 delete me._listeners;
4636 helpers.each(listeners, function(listener, type) {
4637 platform.removeEventListener(me, type, listener);
4638 });
4639 },
4640
4641 updateHoverStyle: function(elements, mode, enabled) {
4642 var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
4643 var element, i, ilen;
4644
4645 for (i = 0, ilen = elements.length; i < ilen; ++i) {
4646 element = elements[i];
4647 if (element) {
4648 this.getDatasetMeta(element._datasetIndex).controller[method](element);
4649 }
4650 }
4651 },
4652
4653 /**
4654 * @private
4655 */
4656 eventHandler: function(e) {
4657 var me = this;
4658 var tooltip = me.tooltip;
4659
4660 if (plugins.notify(me, 'beforeEvent', [e]) === false) {
4661 return;
4662 }
4663
4664 // Buffer any update calls so that renders do not occur
4665 me._bufferedRender = true;
4666 me._bufferedRequest = null;
4667
4668 var changed = me.handleEvent(e);
4669 // for smooth tooltip animations issue #4989
4670 // the tooltip should be the source of change
4671 // Animation check workaround:
4672 // tooltip._start will be null when tooltip isn't animating
4673 if (tooltip) {
4674 changed = tooltip._start
4675 ? tooltip.handleEvent(e)
4676 : changed | tooltip.handleEvent(e);
4677 }
4678
4679 plugins.notify(me, 'afterEvent', [e]);
4680
4681 var bufferedRequest = me._bufferedRequest;
4682 if (bufferedRequest) {
4683 // If we have an update that was triggered, we need to do a normal render
4684 me.render(bufferedRequest);
4685 } else if (changed && !me.animating) {
4686 // If entering, leaving, or changing elements, animate the change via pivot
4687 me.stop();
4688
4689 // We only need to render at this point. Updating will cause scales to be
4690 // recomputed generating flicker & using more memory than necessary.
4691 me.render(me.options.hover.animationDuration, true);
4692 }
4693
4694 me._bufferedRender = false;
4695 me._bufferedRequest = null;
4696
4697 return me;
4698 },
4699
4700 /**
4701 * Handle an event
4702 * @private
4703 * @param {IEvent} event the event to handle
4704 * @return {Boolean} true if the chart needs to re-render
4705 */
4706 handleEvent: function(e) {
4707 var me = this;
4708 var options = me.options || {};
4709 var hoverOptions = options.hover;
4710 var changed = false;
4711
4712 me.lastActive = me.lastActive || [];
4713
4714 // Find Active Elements for hover and tooltips
4715 if (e.type === 'mouseout') {
4716 me.active = [];
4717 } else {
4718 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
4719 }
4720
4721 // Invoke onHover hook
4722 // Need to call with native event here to not break backwards compatibility
4723 helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
4724
4725 if (e.type === 'mouseup' || e.type === 'click') {
4726 if (options.onClick) {
4727 // Use e.native here for backwards compatibility
4728 options.onClick.call(me, e.native, me.active);
4729 }
4730 }
4731
4732 // Remove styling for last active (even if it may still be active)
4733 if (me.lastActive.length) {
4734 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
4735 }
4736
4737 // Built in hover styling
4738 if (me.active.length && hoverOptions.mode) {
4739 me.updateHoverStyle(me.active, hoverOptions.mode, true);
4740 }
4741
4742 changed = !helpers.arrayEquals(me.active, me.lastActive);
4743
4744 // Remember Last Actives
4745 me.lastActive = me.active;
4746
4747 return changed;
4748 }
4749 });
4750
4751 /**
4752 * Provided for backward compatibility, use Chart instead.
4753 * @class Chart.Controller
4754 * @deprecated since version 2.6.0
4755 * @todo remove at version 3
4756 * @private
4757 */
4758 Chart.Controller = Chart;
4759};
4760
4761},{"25":25,"28":28,"30":30,"31":31,"45":45,"48":48}],24:[function(require,module,exports){
4762'use strict';
4763
4764var helpers = require(45);
4765
4766module.exports = function(Chart) {
4767
4768 var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
4769
4770 /**
4771 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
4772 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
4773 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
4774 */
4775 function listenArrayEvents(array, listener) {
4776 if (array._chartjs) {
4777 array._chartjs.listeners.push(listener);
4778 return;
4779 }
4780
4781 Object.defineProperty(array, '_chartjs', {
4782 configurable: true,
4783 enumerable: false,
4784 value: {
4785 listeners: [listener]
4786 }
4787 });
4788
4789 arrayEvents.forEach(function(key) {
4790 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
4791 var base = array[key];
4792
4793 Object.defineProperty(array, key, {
4794 configurable: true,
4795 enumerable: false,
4796 value: function() {
4797 var args = Array.prototype.slice.call(arguments);
4798 var res = base.apply(this, args);
4799
4800 helpers.each(array._chartjs.listeners, function(object) {
4801 if (typeof object[method] === 'function') {
4802 object[method].apply(object, args);
4803 }
4804 });
4805
4806 return res;
4807 }
4808 });
4809 });
4810 }
4811
4812 /**
4813 * Removes the given array event listener and cleanup extra attached properties (such as
4814 * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
4815 */
4816 function unlistenArrayEvents(array, listener) {
4817 var stub = array._chartjs;
4818 if (!stub) {
4819 return;
4820 }
4821
4822 var listeners = stub.listeners;
4823 var index = listeners.indexOf(listener);
4824 if (index !== -1) {
4825 listeners.splice(index, 1);
4826 }
4827
4828 if (listeners.length > 0) {
4829 return;
4830 }
4831
4832 arrayEvents.forEach(function(key) {
4833 delete array[key];
4834 });
4835
4836 delete array._chartjs;
4837 }
4838
4839 // Base class for all dataset controllers (line, bar, etc)
4840 Chart.DatasetController = function(chart, datasetIndex) {
4841 this.initialize(chart, datasetIndex);
4842 };
4843
4844 helpers.extend(Chart.DatasetController.prototype, {
4845
4846 /**
4847 * Element type used to generate a meta dataset (e.g. Chart.element.Line).
4848 * @type {Chart.core.element}
4849 */
4850 datasetElementType: null,
4851
4852 /**
4853 * Element type used to generate a meta data (e.g. Chart.element.Point).
4854 * @type {Chart.core.element}
4855 */
4856 dataElementType: null,
4857
4858 initialize: function(chart, datasetIndex) {
4859 var me = this;
4860 me.chart = chart;
4861 me.index = datasetIndex;
4862 me.linkScales();
4863 me.addElements();
4864 },
4865
4866 updateIndex: function(datasetIndex) {
4867 this.index = datasetIndex;
4868 },
4869
4870 linkScales: function() {
4871 var me = this;
4872 var meta = me.getMeta();
4873 var dataset = me.getDataset();
4874
4875 if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) {
4876 meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
4877 }
4878 if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) {
4879 meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
4880 }
4881 },
4882
4883 getDataset: function() {
4884 return this.chart.data.datasets[this.index];
4885 },
4886
4887 getMeta: function() {
4888 return this.chart.getDatasetMeta(this.index);
4889 },
4890
4891 getScaleForId: function(scaleID) {
4892 return this.chart.scales[scaleID];
4893 },
4894
4895 reset: function() {
4896 this.update(true);
4897 },
4898
4899 /**
4900 * @private
4901 */
4902 destroy: function() {
4903 if (this._data) {
4904 unlistenArrayEvents(this._data, this);
4905 }
4906 },
4907
4908 createMetaDataset: function() {
4909 var me = this;
4910 var type = me.datasetElementType;
4911 return type && new type({
4912 _chart: me.chart,
4913 _datasetIndex: me.index
4914 });
4915 },
4916
4917 createMetaData: function(index) {
4918 var me = this;
4919 var type = me.dataElementType;
4920 return type && new type({
4921 _chart: me.chart,
4922 _datasetIndex: me.index,
4923 _index: index
4924 });
4925 },
4926
4927 addElements: function() {
4928 var me = this;
4929 var meta = me.getMeta();
4930 var data = me.getDataset().data || [];
4931 var metaData = meta.data;
4932 var i, ilen;
4933
4934 for (i = 0, ilen = data.length; i < ilen; ++i) {
4935 metaData[i] = metaData[i] || me.createMetaData(i);
4936 }
4937
4938 meta.dataset = meta.dataset || me.createMetaDataset();
4939 },
4940
4941 addElementAndReset: function(index) {
4942 var element = this.createMetaData(index);
4943 this.getMeta().data.splice(index, 0, element);
4944 this.updateElement(element, index, true);
4945 },
4946
4947 buildOrUpdateElements: function() {
4948 var me = this;
4949 var dataset = me.getDataset();
4950 var data = dataset.data || (dataset.data = []);
4951
4952 // In order to correctly handle data addition/deletion animation (an thus simulate
4953 // real-time charts), we need to monitor these data modifications and synchronize
4954 // the internal meta data accordingly.
4955 if (me._data !== data) {
4956 if (me._data) {
4957 // This case happens when the user replaced the data array instance.
4958 unlistenArrayEvents(me._data, me);
4959 }
4960
4961 listenArrayEvents(data, me);
4962 me._data = data;
4963 }
4964
4965 // Re-sync meta data in case the user replaced the data array or if we missed
4966 // any updates and so make sure that we handle number of datapoints changing.
4967 me.resyncElements();
4968 },
4969
4970 update: helpers.noop,
4971
4972 transition: function(easingValue) {
4973 var meta = this.getMeta();
4974 var elements = meta.data || [];
4975 var ilen = elements.length;
4976 var i = 0;
4977
4978 for (; i < ilen; ++i) {
4979 elements[i].transition(easingValue);
4980 }
4981
4982 if (meta.dataset) {
4983 meta.dataset.transition(easingValue);
4984 }
4985 },
4986
4987 draw: function() {
4988 var meta = this.getMeta();
4989 var elements = meta.data || [];
4990 var ilen = elements.length;
4991 var i = 0;
4992
4993 if (meta.dataset) {
4994 meta.dataset.draw();
4995 }
4996
4997 for (; i < ilen; ++i) {
4998 elements[i].draw();
4999 }
5000 },
5001
5002 removeHoverStyle: function(element, elementOpts) {
5003 var dataset = this.chart.data.datasets[element._datasetIndex];
5004 var index = element._index;
5005 var custom = element.custom || {};
5006 var valueOrDefault = helpers.valueAtIndexOrDefault;
5007 var model = element._model;
5008
5009 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
5010 model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
5011 model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
5012 },
5013
5014 setHoverStyle: function(element) {
5015 var dataset = this.chart.data.datasets[element._datasetIndex];
5016 var index = element._index;
5017 var custom = element.custom || {};
5018 var valueOrDefault = helpers.valueAtIndexOrDefault;
5019 var getHoverColor = helpers.getHoverColor;
5020 var model = element._model;
5021
5022 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
5023 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
5024 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
5025 },
5026
5027 /**
5028 * @private
5029 */
5030 resyncElements: function() {
5031 var me = this;
5032 var meta = me.getMeta();
5033 var data = me.getDataset().data;
5034 var numMeta = meta.data.length;
5035 var numData = data.length;
5036
5037 if (numData < numMeta) {
5038 meta.data.splice(numData, numMeta - numData);
5039 } else if (numData > numMeta) {
5040 me.insertElements(numMeta, numData - numMeta);
5041 }
5042 },
5043
5044 /**
5045 * @private
5046 */
5047 insertElements: function(start, count) {
5048 for (var i = 0; i < count; ++i) {
5049 this.addElementAndReset(start + i);
5050 }
5051 },
5052
5053 /**
5054 * @private
5055 */
5056 onDataPush: function() {
5057 this.insertElements(this.getDataset().data.length - 1, arguments.length);
5058 },
5059
5060 /**
5061 * @private
5062 */
5063 onDataPop: function() {
5064 this.getMeta().data.pop();
5065 },
5066
5067 /**
5068 * @private
5069 */
5070 onDataShift: function() {
5071 this.getMeta().data.shift();
5072 },
5073
5074 /**
5075 * @private
5076 */
5077 onDataSplice: function(start, count) {
5078 this.getMeta().data.splice(start, count);
5079 this.insertElements(start, arguments.length - 2);
5080 },
5081
5082 /**
5083 * @private
5084 */
5085 onDataUnshift: function() {
5086 this.insertElements(0, arguments.length);
5087 }
5088 });
5089
5090 Chart.DatasetController.extend = helpers.inherits;
5091};
5092
5093},{"45":45}],25:[function(require,module,exports){
5094'use strict';
5095
5096var helpers = require(45);
5097
5098module.exports = {
5099 /**
5100 * @private
5101 */
5102 _set: function(scope, values) {
5103 return helpers.merge(this[scope] || (this[scope] = {}), values);
5104 }
5105};
5106
5107},{"45":45}],26:[function(require,module,exports){
5108'use strict';
5109
5110var color = require(3);
5111var helpers = require(45);
5112
5113function interpolate(start, view, model, ease) {
5114 var keys = Object.keys(model);
5115 var i, ilen, key, actual, origin, target, type, c0, c1;
5116
5117 for (i = 0, ilen = keys.length; i < ilen; ++i) {
5118 key = keys[i];
5119
5120 target = model[key];
5121
5122 // if a value is added to the model after pivot() has been called, the view
5123 // doesn't contain it, so let's initialize the view to the target value.
5124 if (!view.hasOwnProperty(key)) {
5125 view[key] = target;
5126 }
5127
5128 actual = view[key];
5129
5130 if (actual === target || key[0] === '_') {
5131 continue;
5132 }
5133
5134 if (!start.hasOwnProperty(key)) {
5135 start[key] = actual;
5136 }
5137
5138 origin = start[key];
5139
5140 type = typeof target;
5141
5142 if (type === typeof origin) {
5143 if (type === 'string') {
5144 c0 = color(origin);
5145 if (c0.valid) {
5146 c1 = color(target);
5147 if (c1.valid) {
5148 view[key] = c1.mix(c0, ease).rgbString();
5149 continue;
5150 }
5151 }
5152 } else if (type === 'number' && isFinite(origin) && isFinite(target)) {
5153 view[key] = origin + (target - origin) * ease;
5154 continue;
5155 }
5156 }
5157
5158 view[key] = target;
5159 }
5160}
5161
5162var Element = function(configuration) {
5163 helpers.extend(this, configuration);
5164 this.initialize.apply(this, arguments);
5165};
5166
5167helpers.extend(Element.prototype, {
5168
5169 initialize: function() {
5170 this.hidden = false;
5171 },
5172
5173 pivot: function() {
5174 var me = this;
5175 if (!me._view) {
5176 me._view = helpers.clone(me._model);
5177 }
5178 me._start = {};
5179 return me;
5180 },
5181
5182 transition: function(ease) {
5183 var me = this;
5184 var model = me._model;
5185 var start = me._start;
5186 var view = me._view;
5187
5188 // No animation -> No Transition
5189 if (!model || ease === 1) {
5190 me._view = model;
5191 me._start = null;
5192 return me;
5193 }
5194
5195 if (!view) {
5196 view = me._view = {};
5197 }
5198
5199 if (!start) {
5200 start = me._start = {};
5201 }
5202
5203 interpolate(start, view, model, ease);
5204
5205 return me;
5206 },
5207
5208 tooltipPosition: function() {
5209 return {
5210 x: this._model.x,
5211 y: this._model.y
5212 };
5213 },
5214
5215 hasValue: function() {
5216 return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
5217 }
5218});
5219
5220Element.extend = helpers.inherits;
5221
5222module.exports = Element;
5223
5224},{"3":3,"45":45}],27:[function(require,module,exports){
5225/* global window: false */
5226/* global document: false */
5227'use strict';
5228
5229var color = require(3);
5230var defaults = require(25);
5231var helpers = require(45);
5232
5233module.exports = function(Chart) {
5234
5235 // -- Basic js utility methods
5236
5237 helpers.configMerge = function(/* objects ... */) {
5238 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5239 merger: function(key, target, source, options) {
5240 var tval = target[key] || {};
5241 var sval = source[key];
5242
5243 if (key === 'scales') {
5244 // scale config merging is complex. Add our own function here for that
5245 target[key] = helpers.scaleMerge(tval, sval);
5246 } else if (key === 'scale') {
5247 // used in polar area & radar charts since there is only one scale
5248 target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]);
5249 } else {
5250 helpers._merger(key, target, source, options);
5251 }
5252 }
5253 });
5254 };
5255
5256 helpers.scaleMerge = function(/* objects ... */) {
5257 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5258 merger: function(key, target, source, options) {
5259 if (key === 'xAxes' || key === 'yAxes') {
5260 var slen = source[key].length;
5261 var i, type, scale;
5262
5263 if (!target[key]) {
5264 target[key] = [];
5265 }
5266
5267 for (i = 0; i < slen; ++i) {
5268 scale = source[key][i];
5269 type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear');
5270
5271 if (i >= target[key].length) {
5272 target[key].push({});
5273 }
5274
5275 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
5276 // new/untyped scale or type changed: let's apply the new defaults
5277 // then merge source scale to correctly overwrite the defaults.
5278 helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]);
5279 } else {
5280 // scales type are the same
5281 helpers.merge(target[key][i], scale);
5282 }
5283 }
5284 } else {
5285 helpers._merger(key, target, source, options);
5286 }
5287 }
5288 });
5289 };
5290
5291 helpers.where = function(collection, filterCallback) {
5292 if (helpers.isArray(collection) && Array.prototype.filter) {
5293 return collection.filter(filterCallback);
5294 }
5295 var filtered = [];
5296
5297 helpers.each(collection, function(item) {
5298 if (filterCallback(item)) {
5299 filtered.push(item);
5300 }
5301 });
5302
5303 return filtered;
5304 };
5305 helpers.findIndex = Array.prototype.findIndex ?
5306 function(array, callback, scope) {
5307 return array.findIndex(callback, scope);
5308 } :
5309 function(array, callback, scope) {
5310 scope = scope === undefined ? array : scope;
5311 for (var i = 0, ilen = array.length; i < ilen; ++i) {
5312 if (callback.call(scope, array[i], i, array)) {
5313 return i;
5314 }
5315 }
5316 return -1;
5317 };
5318 helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
5319 // Default to start of the array
5320 if (helpers.isNullOrUndef(startIndex)) {
5321 startIndex = -1;
5322 }
5323 for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
5324 var currentItem = arrayToSearch[i];
5325 if (filterCallback(currentItem)) {
5326 return currentItem;
5327 }
5328 }
5329 };
5330 helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
5331 // Default to end of the array
5332 if (helpers.isNullOrUndef(startIndex)) {
5333 startIndex = arrayToSearch.length;
5334 }
5335 for (var i = startIndex - 1; i >= 0; i--) {
5336 var currentItem = arrayToSearch[i];
5337 if (filterCallback(currentItem)) {
5338 return currentItem;
5339 }
5340 }
5341 };
5342
5343 // -- Math methods
5344 helpers.isNumber = function(n) {
5345 return !isNaN(parseFloat(n)) && isFinite(n);
5346 };
5347 helpers.almostEquals = function(x, y, epsilon) {
5348 return Math.abs(x - y) < epsilon;
5349 };
5350 helpers.almostWhole = function(x, epsilon) {
5351 var rounded = Math.round(x);
5352 return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
5353 };
5354 helpers.max = function(array) {
5355 return array.reduce(function(max, value) {
5356 if (!isNaN(value)) {
5357 return Math.max(max, value);
5358 }
5359 return max;
5360 }, Number.NEGATIVE_INFINITY);
5361 };
5362 helpers.min = function(array) {
5363 return array.reduce(function(min, value) {
5364 if (!isNaN(value)) {
5365 return Math.min(min, value);
5366 }
5367 return min;
5368 }, Number.POSITIVE_INFINITY);
5369 };
5370 helpers.sign = Math.sign ?
5371 function(x) {
5372 return Math.sign(x);
5373 } :
5374 function(x) {
5375 x = +x; // convert to a number
5376 if (x === 0 || isNaN(x)) {
5377 return x;
5378 }
5379 return x > 0 ? 1 : -1;
5380 };
5381 helpers.log10 = Math.log10 ?
5382 function(x) {
5383 return Math.log10(x);
5384 } :
5385 function(x) {
5386 return Math.log(x) / Math.LN10;
5387 };
5388 helpers.toRadians = function(degrees) {
5389 return degrees * (Math.PI / 180);
5390 };
5391 helpers.toDegrees = function(radians) {
5392 return radians * (180 / Math.PI);
5393 };
5394 // Gets the angle from vertical upright to the point about a centre.
5395 helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
5396 var distanceFromXCenter = anglePoint.x - centrePoint.x;
5397 var distanceFromYCenter = anglePoint.y - centrePoint.y;
5398 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
5399
5400 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
5401
5402 if (angle < (-0.5 * Math.PI)) {
5403 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
5404 }
5405
5406 return {
5407 angle: angle,
5408 distance: radialDistanceFromCenter
5409 };
5410 };
5411 helpers.distanceBetweenPoints = function(pt1, pt2) {
5412 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
5413 };
5414 helpers.aliasPixel = function(pixelWidth) {
5415 return (pixelWidth % 2 === 0) ? 0 : 0.5;
5416 };
5417 helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
5418 // Props to Rob Spencer at scaled innovation for his post on splining between points
5419 // http://scaledinnovation.com/analytics/splines/aboutSplines.html
5420
5421 // This function must also respect "skipped" points
5422
5423 var previous = firstPoint.skip ? middlePoint : firstPoint;
5424 var current = middlePoint;
5425 var next = afterPoint.skip ? middlePoint : afterPoint;
5426
5427 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
5428 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
5429
5430 var s01 = d01 / (d01 + d12);
5431 var s12 = d12 / (d01 + d12);
5432
5433 // If all points are the same, s01 & s02 will be inf
5434 s01 = isNaN(s01) ? 0 : s01;
5435 s12 = isNaN(s12) ? 0 : s12;
5436
5437 var fa = t * s01; // scaling factor for triangle Ta
5438 var fb = t * s12;
5439
5440 return {
5441 previous: {
5442 x: current.x - fa * (next.x - previous.x),
5443 y: current.y - fa * (next.y - previous.y)
5444 },
5445 next: {
5446 x: current.x + fb * (next.x - previous.x),
5447 y: current.y + fb * (next.y - previous.y)
5448 }
5449 };
5450 };
5451 helpers.EPSILON = Number.EPSILON || 1e-14;
5452 helpers.splineCurveMonotone = function(points) {
5453 // This function calculates Bézier control points in a similar way than |splineCurve|,
5454 // but preserves monotonicity of the provided data and ensures no local extremums are added
5455 // between the dataset discrete points due to the interpolation.
5456 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
5457
5458 var pointsWithTangents = (points || []).map(function(point) {
5459 return {
5460 model: point._model,
5461 deltaK: 0,
5462 mK: 0
5463 };
5464 });
5465
5466 // Calculate slopes (deltaK) and initialize tangents (mK)
5467 var pointsLen = pointsWithTangents.length;
5468 var i, pointBefore, pointCurrent, pointAfter;
5469 for (i = 0; i < pointsLen; ++i) {
5470 pointCurrent = pointsWithTangents[i];
5471 if (pointCurrent.model.skip) {
5472 continue;
5473 }
5474
5475 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5476 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5477 if (pointAfter && !pointAfter.model.skip) {
5478 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
5479
5480 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
5481 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
5482 }
5483
5484 if (!pointBefore || pointBefore.model.skip) {
5485 pointCurrent.mK = pointCurrent.deltaK;
5486 } else if (!pointAfter || pointAfter.model.skip) {
5487 pointCurrent.mK = pointBefore.deltaK;
5488 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
5489 pointCurrent.mK = 0;
5490 } else {
5491 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
5492 }
5493 }
5494
5495 // Adjust tangents to ensure monotonic properties
5496 var alphaK, betaK, tauK, squaredMagnitude;
5497 for (i = 0; i < pointsLen - 1; ++i) {
5498 pointCurrent = pointsWithTangents[i];
5499 pointAfter = pointsWithTangents[i + 1];
5500 if (pointCurrent.model.skip || pointAfter.model.skip) {
5501 continue;
5502 }
5503
5504 if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
5505 pointCurrent.mK = pointAfter.mK = 0;
5506 continue;
5507 }
5508
5509 alphaK = pointCurrent.mK / pointCurrent.deltaK;
5510 betaK = pointAfter.mK / pointCurrent.deltaK;
5511 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
5512 if (squaredMagnitude <= 9) {
5513 continue;
5514 }
5515
5516 tauK = 3 / Math.sqrt(squaredMagnitude);
5517 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
5518 pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
5519 }
5520
5521 // Compute control points
5522 var deltaX;
5523 for (i = 0; i < pointsLen; ++i) {
5524 pointCurrent = pointsWithTangents[i];
5525 if (pointCurrent.model.skip) {
5526 continue;
5527 }
5528
5529 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5530 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5531 if (pointBefore && !pointBefore.model.skip) {
5532 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
5533 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
5534 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
5535 }
5536 if (pointAfter && !pointAfter.model.skip) {
5537 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
5538 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
5539 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
5540 }
5541 }
5542 };
5543 helpers.nextItem = function(collection, index, loop) {
5544 if (loop) {
5545 return index >= collection.length - 1 ? collection[0] : collection[index + 1];
5546 }
5547 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
5548 };
5549 helpers.previousItem = function(collection, index, loop) {
5550 if (loop) {
5551 return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
5552 }
5553 return index <= 0 ? collection[0] : collection[index - 1];
5554 };
5555 // Implementation of the nice number algorithm used in determining where axis labels will go
5556 helpers.niceNum = function(range, round) {
5557 var exponent = Math.floor(helpers.log10(range));
5558 var fraction = range / Math.pow(10, exponent);
5559 var niceFraction;
5560
5561 if (round) {
5562 if (fraction < 1.5) {
5563 niceFraction = 1;
5564 } else if (fraction < 3) {
5565 niceFraction = 2;
5566 } else if (fraction < 7) {
5567 niceFraction = 5;
5568 } else {
5569 niceFraction = 10;
5570 }
5571 } else if (fraction <= 1.0) {
5572 niceFraction = 1;
5573 } else if (fraction <= 2) {
5574 niceFraction = 2;
5575 } else if (fraction <= 5) {
5576 niceFraction = 5;
5577 } else {
5578 niceFraction = 10;
5579 }
5580
5581 return niceFraction * Math.pow(10, exponent);
5582 };
5583 // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
5584 helpers.requestAnimFrame = (function() {
5585 if (typeof window === 'undefined') {
5586 return function(callback) {
5587 callback();
5588 };
5589 }
5590 return window.requestAnimationFrame ||
5591 window.webkitRequestAnimationFrame ||
5592 window.mozRequestAnimationFrame ||
5593 window.oRequestAnimationFrame ||
5594 window.msRequestAnimationFrame ||
5595 function(callback) {
5596 return window.setTimeout(callback, 1000 / 60);
5597 };
5598 }());
5599 // -- DOM methods
5600 helpers.getRelativePosition = function(evt, chart) {
5601 var mouseX, mouseY;
5602 var e = evt.originalEvent || evt;
5603 var canvas = evt.currentTarget || evt.srcElement;
5604 var boundingRect = canvas.getBoundingClientRect();
5605
5606 var touches = e.touches;
5607 if (touches && touches.length > 0) {
5608 mouseX = touches[0].clientX;
5609 mouseY = touches[0].clientY;
5610
5611 } else {
5612 mouseX = e.clientX;
5613 mouseY = e.clientY;
5614 }
5615
5616 // Scale mouse coordinates into canvas coordinates
5617 // by following the pattern laid out by 'jerryj' in the comments of
5618 // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
5619 var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
5620 var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
5621 var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
5622 var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
5623 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
5624 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
5625
5626 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
5627 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
5628 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
5629 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
5630
5631 return {
5632 x: mouseX,
5633 y: mouseY
5634 };
5635
5636 };
5637
5638 // Private helper function to convert max-width/max-height values that may be percentages into a number
5639 function parseMaxStyle(styleValue, node, parentProperty) {
5640 var valueInPixels;
5641 if (typeof styleValue === 'string') {
5642 valueInPixels = parseInt(styleValue, 10);
5643
5644 if (styleValue.indexOf('%') !== -1) {
5645 // percentage * size in dimension
5646 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
5647 }
5648 } else {
5649 valueInPixels = styleValue;
5650 }
5651
5652 return valueInPixels;
5653 }
5654
5655 /**
5656 * Returns if the given value contains an effective constraint.
5657 * @private
5658 */
5659 function isConstrainedValue(value) {
5660 return value !== undefined && value !== null && value !== 'none';
5661 }
5662
5663 // Private helper to get a constraint dimension
5664 // @param domNode : the node to check the constraint on
5665 // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
5666 // @param percentageProperty : property of parent to use when calculating width as a percentage
5667 // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
5668 function getConstraintDimension(domNode, maxStyle, percentageProperty) {
5669 var view = document.defaultView;
5670 var parentNode = domNode.parentNode;
5671 var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
5672 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
5673 var hasCNode = isConstrainedValue(constrainedNode);
5674 var hasCContainer = isConstrainedValue(constrainedContainer);
5675 var infinity = Number.POSITIVE_INFINITY;
5676
5677 if (hasCNode || hasCContainer) {
5678 return Math.min(
5679 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
5680 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
5681 }
5682
5683 return 'none';
5684 }
5685 // returns Number or undefined if no constraint
5686 helpers.getConstraintWidth = function(domNode) {
5687 return getConstraintDimension(domNode, 'max-width', 'clientWidth');
5688 };
5689 // returns Number or undefined if no constraint
5690 helpers.getConstraintHeight = function(domNode) {
5691 return getConstraintDimension(domNode, 'max-height', 'clientHeight');
5692 };
5693 helpers.getMaximumWidth = function(domNode) {
5694 var container = domNode.parentNode;
5695 if (!container) {
5696 return domNode.clientWidth;
5697 }
5698
5699 var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
5700 var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
5701 var w = container.clientWidth - paddingLeft - paddingRight;
5702 var cw = helpers.getConstraintWidth(domNode);
5703 return isNaN(cw) ? w : Math.min(w, cw);
5704 };
5705 helpers.getMaximumHeight = function(domNode) {
5706 var container = domNode.parentNode;
5707 if (!container) {
5708 return domNode.clientHeight;
5709 }
5710
5711 var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
5712 var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
5713 var h = container.clientHeight - paddingTop - paddingBottom;
5714 var ch = helpers.getConstraintHeight(domNode);
5715 return isNaN(ch) ? h : Math.min(h, ch);
5716 };
5717 helpers.getStyle = function(el, property) {
5718 return el.currentStyle ?
5719 el.currentStyle[property] :
5720 document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
5721 };
5722 helpers.retinaScale = function(chart, forceRatio) {
5723 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1;
5724 if (pixelRatio === 1) {
5725 return;
5726 }
5727
5728 var canvas = chart.canvas;
5729 var height = chart.height;
5730 var width = chart.width;
5731
5732 canvas.height = height * pixelRatio;
5733 canvas.width = width * pixelRatio;
5734 chart.ctx.scale(pixelRatio, pixelRatio);
5735
5736 // If no style has been set on the canvas, the render size is used as display size,
5737 // making the chart visually bigger, so let's enforce it to the "correct" values.
5738 // See https://github.com/chartjs/Chart.js/issues/3575
5739 if (!canvas.style.height && !canvas.style.width) {
5740 canvas.style.height = height + 'px';
5741 canvas.style.width = width + 'px';
5742 }
5743 };
5744 // -- Canvas methods
5745 helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
5746 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
5747 };
5748 helpers.longestText = function(ctx, font, arrayOfThings, cache) {
5749 cache = cache || {};
5750 var data = cache.data = cache.data || {};
5751 var gc = cache.garbageCollect = cache.garbageCollect || [];
5752
5753 if (cache.font !== font) {
5754 data = cache.data = {};
5755 gc = cache.garbageCollect = [];
5756 cache.font = font;
5757 }
5758
5759 ctx.font = font;
5760 var longest = 0;
5761 helpers.each(arrayOfThings, function(thing) {
5762 // Undefined strings and arrays should not be measured
5763 if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
5764 longest = helpers.measureText(ctx, data, gc, longest, thing);
5765 } else if (helpers.isArray(thing)) {
5766 // if it is an array lets measure each element
5767 // to do maybe simplify this function a bit so we can do this more recursively?
5768 helpers.each(thing, function(nestedThing) {
5769 // Undefined strings and arrays should not be measured
5770 if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
5771 longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
5772 }
5773 });
5774 }
5775 });
5776
5777 var gcLen = gc.length / 2;
5778 if (gcLen > arrayOfThings.length) {
5779 for (var i = 0; i < gcLen; i++) {
5780 delete data[gc[i]];
5781 }
5782 gc.splice(0, gcLen);
5783 }
5784 return longest;
5785 };
5786 helpers.measureText = function(ctx, data, gc, longest, string) {
5787 var textWidth = data[string];
5788 if (!textWidth) {
5789 textWidth = data[string] = ctx.measureText(string).width;
5790 gc.push(string);
5791 }
5792 if (textWidth > longest) {
5793 longest = textWidth;
5794 }
5795 return longest;
5796 };
5797 helpers.numberOfLabelLines = function(arrayOfThings) {
5798 var numberOfLines = 1;
5799 helpers.each(arrayOfThings, function(thing) {
5800 if (helpers.isArray(thing)) {
5801 if (thing.length > numberOfLines) {
5802 numberOfLines = thing.length;
5803 }
5804 }
5805 });
5806 return numberOfLines;
5807 };
5808
5809 helpers.color = !color ?
5810 function(value) {
5811 console.error('Color.js not found!');
5812 return value;
5813 } :
5814 function(value) {
5815 /* global CanvasGradient */
5816 if (value instanceof CanvasGradient) {
5817 value = defaults.global.defaultColor;
5818 }
5819
5820 return color(value);
5821 };
5822
5823 helpers.getHoverColor = function(colorValue) {
5824 /* global CanvasPattern */
5825 return (colorValue instanceof CanvasPattern) ?
5826 colorValue :
5827 helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
5828 };
5829};
5830
5831},{"25":25,"3":3,"45":45}],28:[function(require,module,exports){
5832'use strict';
5833
5834var helpers = require(45);
5835
5836/**
5837 * Helper function to get relative position for an event
5838 * @param {Event|IEvent} event - The event to get the position for
5839 * @param {Chart} chart - The chart
5840 * @returns {Point} the event position
5841 */
5842function getRelativePosition(e, chart) {
5843 if (e.native) {
5844 return {
5845 x: e.x,
5846 y: e.y
5847 };
5848 }
5849
5850 return helpers.getRelativePosition(e, chart);
5851}
5852
5853/**
5854 * Helper function to traverse all of the visible elements in the chart
5855 * @param chart {chart} the chart
5856 * @param handler {Function} the callback to execute for each visible item
5857 */
5858function parseVisibleItems(chart, handler) {
5859 var datasets = chart.data.datasets;
5860 var meta, i, j, ilen, jlen;
5861
5862 for (i = 0, ilen = datasets.length; i < ilen; ++i) {
5863 if (!chart.isDatasetVisible(i)) {
5864 continue;
5865 }
5866
5867 meta = chart.getDatasetMeta(i);
5868 for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
5869 var element = meta.data[j];
5870 if (!element._view.skip) {
5871 handler(element);
5872 }
5873 }
5874 }
5875}
5876
5877/**
5878 * Helper function to get the items that intersect the event position
5879 * @param items {ChartElement[]} elements to filter
5880 * @param position {Point} the point to be nearest to
5881 * @return {ChartElement[]} the nearest items
5882 */
5883function getIntersectItems(chart, position) {
5884 var elements = [];
5885
5886 parseVisibleItems(chart, function(element) {
5887 if (element.inRange(position.x, position.y)) {
5888 elements.push(element);
5889 }
5890 });
5891
5892 return elements;
5893}
5894
5895/**
5896 * Helper function to get the items nearest to the event position considering all visible items in teh chart
5897 * @param chart {Chart} the chart to look at elements from
5898 * @param position {Point} the point to be nearest to
5899 * @param intersect {Boolean} if true, only consider items that intersect the position
5900 * @param distanceMetric {Function} function to provide the distance between points
5901 * @return {ChartElement[]} the nearest items
5902 */
5903function getNearestItems(chart, position, intersect, distanceMetric) {
5904 var minDistance = Number.POSITIVE_INFINITY;
5905 var nearestItems = [];
5906
5907 parseVisibleItems(chart, function(element) {
5908 if (intersect && !element.inRange(position.x, position.y)) {
5909 return;
5910 }
5911
5912 var center = element.getCenterPoint();
5913 var distance = distanceMetric(position, center);
5914
5915 if (distance < minDistance) {
5916 nearestItems = [element];
5917 minDistance = distance;
5918 } else if (distance === minDistance) {
5919 // Can have multiple items at the same distance in which case we sort by size
5920 nearestItems.push(element);
5921 }
5922 });
5923
5924 return nearestItems;
5925}
5926
5927/**
5928 * Get a distance metric function for two points based on the
5929 * axis mode setting
5930 * @param {String} axis the axis mode. x|y|xy
5931 */
5932function getDistanceMetricForAxis(axis) {
5933 var useX = axis.indexOf('x') !== -1;
5934 var useY = axis.indexOf('y') !== -1;
5935
5936 return function(pt1, pt2) {
5937 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
5938 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
5939 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
5940 };
5941}
5942
5943function indexMode(chart, e, options) {
5944 var position = getRelativePosition(e, chart);
5945 // Default axis for index mode is 'x' to match old behaviour
5946 options.axis = options.axis || 'x';
5947 var distanceMetric = getDistanceMetricForAxis(options.axis);
5948 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5949 var elements = [];
5950
5951 if (!items.length) {
5952 return [];
5953 }
5954
5955 chart.data.datasets.forEach(function(dataset, datasetIndex) {
5956 if (chart.isDatasetVisible(datasetIndex)) {
5957 var meta = chart.getDatasetMeta(datasetIndex);
5958 var element = meta.data[items[0]._index];
5959
5960 // don't count items that are skipped (null data)
5961 if (element && !element._view.skip) {
5962 elements.push(element);
5963 }
5964 }
5965 });
5966
5967 return elements;
5968}
5969
5970/**
5971 * @interface IInteractionOptions
5972 */
5973/**
5974 * If true, only consider items that intersect the point
5975 * @name IInterfaceOptions#boolean
5976 * @type Boolean
5977 */
5978
5979/**
5980 * Contains interaction related functions
5981 * @namespace Chart.Interaction
5982 */
5983module.exports = {
5984 // Helper function for different modes
5985 modes: {
5986 single: function(chart, e) {
5987 var position = getRelativePosition(e, chart);
5988 var elements = [];
5989
5990 parseVisibleItems(chart, function(element) {
5991 if (element.inRange(position.x, position.y)) {
5992 elements.push(element);
5993 return elements;
5994 }
5995 });
5996
5997 return elements.slice(0, 1);
5998 },
5999
6000 /**
6001 * @function Chart.Interaction.modes.label
6002 * @deprecated since version 2.4.0
6003 * @todo remove at version 3
6004 * @private
6005 */
6006 label: indexMode,
6007
6008 /**
6009 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
6010 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
6011 * @function Chart.Interaction.modes.index
6012 * @since v2.4.0
6013 * @param chart {chart} the chart we are returning items from
6014 * @param e {Event} the event we are find things at
6015 * @param options {IInteractionOptions} options to use during interaction
6016 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6017 */
6018 index: indexMode,
6019
6020 /**
6021 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
6022 * If the options.intersect is false, we find the nearest item and return the items in that dataset
6023 * @function Chart.Interaction.modes.dataset
6024 * @param chart {chart} the chart we are returning items from
6025 * @param e {Event} the event we are find things at
6026 * @param options {IInteractionOptions} options to use during interaction
6027 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6028 */
6029 dataset: function(chart, e, options) {
6030 var position = getRelativePosition(e, chart);
6031 options.axis = options.axis || 'xy';
6032 var distanceMetric = getDistanceMetricForAxis(options.axis);
6033 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
6034
6035 if (items.length > 0) {
6036 items = chart.getDatasetMeta(items[0]._datasetIndex).data;
6037 }
6038
6039 return items;
6040 },
6041
6042 /**
6043 * @function Chart.Interaction.modes.x-axis
6044 * @deprecated since version 2.4.0. Use index mode and intersect == true
6045 * @todo remove at version 3
6046 * @private
6047 */
6048 'x-axis': function(chart, e) {
6049 return indexMode(chart, e, {intersect: false});
6050 },
6051
6052 /**
6053 * Point mode returns all elements that hit test based on the event position
6054 * of the event
6055 * @function Chart.Interaction.modes.intersect
6056 * @param chart {chart} the chart we are returning items from
6057 * @param e {Event} the event we are find things at
6058 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6059 */
6060 point: function(chart, e) {
6061 var position = getRelativePosition(e, chart);
6062 return getIntersectItems(chart, position);
6063 },
6064
6065 /**
6066 * nearest mode returns the element closest to the point
6067 * @function Chart.Interaction.modes.intersect
6068 * @param chart {chart} the chart we are returning items from
6069 * @param e {Event} the event we are find things at
6070 * @param options {IInteractionOptions} options to use
6071 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6072 */
6073 nearest: function(chart, e, options) {
6074 var position = getRelativePosition(e, chart);
6075 options.axis = options.axis || 'xy';
6076 var distanceMetric = getDistanceMetricForAxis(options.axis);
6077 var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric);
6078
6079 // We have multiple items at the same distance from the event. Now sort by smallest
6080 if (nearestItems.length > 1) {
6081 nearestItems.sort(function(a, b) {
6082 var sizeA = a.getArea();
6083 var sizeB = b.getArea();
6084 var ret = sizeA - sizeB;
6085
6086 if (ret === 0) {
6087 // if equal sort by dataset index
6088 ret = a._datasetIndex - b._datasetIndex;
6089 }
6090
6091 return ret;
6092 });
6093 }
6094
6095 // Return only 1 item
6096 return nearestItems.slice(0, 1);
6097 },
6098
6099 /**
6100 * x mode returns the elements that hit-test at the current x coordinate
6101 * @function Chart.Interaction.modes.x
6102 * @param chart {chart} the chart we are returning items from
6103 * @param e {Event} the event we are find things at
6104 * @param options {IInteractionOptions} options to use
6105 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6106 */
6107 x: function(chart, e, options) {
6108 var position = getRelativePosition(e, chart);
6109 var items = [];
6110 var intersectsItem = false;
6111
6112 parseVisibleItems(chart, function(element) {
6113 if (element.inXRange(position.x)) {
6114 items.push(element);
6115 }
6116
6117 if (element.inRange(position.x, position.y)) {
6118 intersectsItem = true;
6119 }
6120 });
6121
6122 // If we want to trigger on an intersect and we don't have any items
6123 // that intersect the position, return nothing
6124 if (options.intersect && !intersectsItem) {
6125 items = [];
6126 }
6127 return items;
6128 },
6129
6130 /**
6131 * y mode returns the elements that hit-test at the current y coordinate
6132 * @function Chart.Interaction.modes.y
6133 * @param chart {chart} the chart we are returning items from
6134 * @param e {Event} the event we are find things at
6135 * @param options {IInteractionOptions} options to use
6136 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6137 */
6138 y: function(chart, e, options) {
6139 var position = getRelativePosition(e, chart);
6140 var items = [];
6141 var intersectsItem = false;
6142
6143 parseVisibleItems(chart, function(element) {
6144 if (element.inYRange(position.y)) {
6145 items.push(element);
6146 }
6147
6148 if (element.inRange(position.x, position.y)) {
6149 intersectsItem = true;
6150 }
6151 });
6152
6153 // If we want to trigger on an intersect and we don't have any items
6154 // that intersect the position, return nothing
6155 if (options.intersect && !intersectsItem) {
6156 items = [];
6157 }
6158 return items;
6159 }
6160 }
6161};
6162
6163},{"45":45}],29:[function(require,module,exports){
6164'use strict';
6165
6166var defaults = require(25);
6167
6168defaults._set('global', {
6169 responsive: true,
6170 responsiveAnimationDuration: 0,
6171 maintainAspectRatio: true,
6172 events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
6173 hover: {
6174 onHover: null,
6175 mode: 'nearest',
6176 intersect: true,
6177 animationDuration: 400
6178 },
6179 onClick: null,
6180 defaultColor: 'rgba(0,0,0,0.1)',
6181 defaultFontColor: '#666',
6182 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
6183 defaultFontSize: 12,
6184 defaultFontStyle: 'normal',
6185 showLines: true,
6186
6187 // Element defaults defined in element extensions
6188 elements: {},
6189
6190 // Layout options such as padding
6191 layout: {
6192 padding: {
6193 top: 0,
6194 right: 0,
6195 bottom: 0,
6196 left: 0
6197 }
6198 }
6199});
6200
6201module.exports = function() {
6202
6203 // Occupy the global variable of Chart, and create a simple base class
6204 var Chart = function(item, config) {
6205 this.construct(item, config);
6206 return this;
6207 };
6208
6209 Chart.Chart = Chart;
6210
6211 return Chart;
6212};
6213
6214},{"25":25}],30:[function(require,module,exports){
6215'use strict';
6216
6217var helpers = require(45);
6218
6219function filterByPosition(array, position) {
6220 return helpers.where(array, function(v) {
6221 return v.position === position;
6222 });
6223}
6224
6225function sortByWeight(array, reverse) {
6226 array.forEach(function(v, i) {
6227 v._tmpIndex_ = i;
6228 return v;
6229 });
6230 array.sort(function(a, b) {
6231 var v0 = reverse ? b : a;
6232 var v1 = reverse ? a : b;
6233 return v0.weight === v1.weight ?
6234 v0._tmpIndex_ - v1._tmpIndex_ :
6235 v0.weight - v1.weight;
6236 });
6237 array.forEach(function(v) {
6238 delete v._tmpIndex_;
6239 });
6240}
6241
6242/**
6243 * @interface ILayoutItem
6244 * @prop {String} position - The position of the item in the chart layout. Possible values are
6245 * 'left', 'top', 'right', 'bottom', and 'chartArea'
6246 * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
6247 * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
6248 * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
6249 * @prop {Function} update - Takes two parameters: width and height. Returns size of item
6250 * @prop {Function} getPadding - Returns an object with padding on the edges
6251 * @prop {Number} width - Width of item. Must be valid after update()
6252 * @prop {Number} height - Height of item. Must be valid after update()
6253 * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
6254 * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
6255 * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
6256 * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
6257 */
6258
6259// The layout service is very self explanatory. It's responsible for the layout within a chart.
6260// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
6261// It is this service's responsibility of carrying out that layout.
6262module.exports = {
6263 defaults: {},
6264
6265 /**
6266 * Register a box to a chart.
6267 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
6268 * @param {Chart} chart - the chart to use
6269 * @param {ILayoutItem} item - the item to add to be layed out
6270 */
6271 addBox: function(chart, item) {
6272 if (!chart.boxes) {
6273 chart.boxes = [];
6274 }
6275
6276 // initialize item with default values
6277 item.fullWidth = item.fullWidth || false;
6278 item.position = item.position || 'top';
6279 item.weight = item.weight || 0;
6280
6281 chart.boxes.push(item);
6282 },
6283
6284 /**
6285 * Remove a layoutItem from a chart
6286 * @param {Chart} chart - the chart to remove the box from
6287 * @param {Object} layoutItem - the item to remove from the layout
6288 */
6289 removeBox: function(chart, layoutItem) {
6290 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
6291 if (index !== -1) {
6292 chart.boxes.splice(index, 1);
6293 }
6294 },
6295
6296 /**
6297 * Sets (or updates) options on the given `item`.
6298 * @param {Chart} chart - the chart in which the item lives (or will be added to)
6299 * @param {Object} item - the item to configure with the given options
6300 * @param {Object} options - the new item options.
6301 */
6302 configure: function(chart, item, options) {
6303 var props = ['fullWidth', 'position', 'weight'];
6304 var ilen = props.length;
6305 var i = 0;
6306 var prop;
6307
6308 for (; i < ilen; ++i) {
6309 prop = props[i];
6310 if (options.hasOwnProperty(prop)) {
6311 item[prop] = options[prop];
6312 }
6313 }
6314 },
6315
6316 /**
6317 * Fits boxes of the given chart into the given size by having each box measure itself
6318 * then running a fitting algorithm
6319 * @param {Chart} chart - the chart
6320 * @param {Number} width - the width to fit into
6321 * @param {Number} height - the height to fit into
6322 */
6323 update: function(chart, width, height) {
6324 if (!chart) {
6325 return;
6326 }
6327
6328 var layoutOptions = chart.options.layout || {};
6329 var padding = helpers.options.toPadding(layoutOptions.padding);
6330 var leftPadding = padding.left;
6331 var rightPadding = padding.right;
6332 var topPadding = padding.top;
6333 var bottomPadding = padding.bottom;
6334
6335 var leftBoxes = filterByPosition(chart.boxes, 'left');
6336 var rightBoxes = filterByPosition(chart.boxes, 'right');
6337 var topBoxes = filterByPosition(chart.boxes, 'top');
6338 var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
6339 var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
6340
6341 // Sort boxes by weight. A higher weight is further away from the chart area
6342 sortByWeight(leftBoxes, true);
6343 sortByWeight(rightBoxes, false);
6344 sortByWeight(topBoxes, true);
6345 sortByWeight(bottomBoxes, false);
6346
6347 // Essentially we now have any number of boxes on each of the 4 sides.
6348 // Our canvas looks like the following.
6349 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
6350 // B1 is the bottom axis
6351 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
6352 // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
6353 // an error will be thrown.
6354 //
6355 // |----------------------------------------------------|
6356 // | T1 (Full Width) |
6357 // |----------------------------------------------------|
6358 // | | | T2 | |
6359 // | |----|-------------------------------------|----|
6360 // | | | C1 | | C2 | |
6361 // | | |----| |----| |
6362 // | | | | |
6363 // | L1 | L2 | ChartArea (C0) | R1 |
6364 // | | | | |
6365 // | | |----| |----| |
6366 // | | | C3 | | C4 | |
6367 // | |----|-------------------------------------|----|
6368 // | | | B1 | |
6369 // |----------------------------------------------------|
6370 // | B2 (Full Width) |
6371 // |----------------------------------------------------|
6372 //
6373 // What we do to find the best sizing, we do the following
6374 // 1. Determine the minimum size of the chart area.
6375 // 2. Split the remaining width equally between each vertical axis
6376 // 3. Split the remaining height equally between each horizontal axis
6377 // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
6378 // 5. Adjust the sizes of each axis based on it's minimum reported size.
6379 // 6. Refit each axis
6380 // 7. Position each axis in the final location
6381 // 8. Tell the chart the final location of the chart area
6382 // 9. Tell any axes that overlay the chart area the positions of the chart area
6383
6384 // Step 1
6385 var chartWidth = width - leftPadding - rightPadding;
6386 var chartHeight = height - topPadding - bottomPadding;
6387 var chartAreaWidth = chartWidth / 2; // min 50%
6388 var chartAreaHeight = chartHeight / 2; // min 50%
6389
6390 // Step 2
6391 var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
6392
6393 // Step 3
6394 var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
6395
6396 // Step 4
6397 var maxChartAreaWidth = chartWidth;
6398 var maxChartAreaHeight = chartHeight;
6399 var minBoxSizes = [];
6400
6401 function getMinimumBoxSize(box) {
6402 var minSize;
6403 var isHorizontal = box.isHorizontal();
6404
6405 if (isHorizontal) {
6406 minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
6407 maxChartAreaHeight -= minSize.height;
6408 } else {
6409 minSize = box.update(verticalBoxWidth, maxChartAreaHeight);
6410 maxChartAreaWidth -= minSize.width;
6411 }
6412
6413 minBoxSizes.push({
6414 horizontal: isHorizontal,
6415 minSize: minSize,
6416 box: box,
6417 });
6418 }
6419
6420 helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
6421
6422 // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
6423 var maxHorizontalLeftPadding = 0;
6424 var maxHorizontalRightPadding = 0;
6425 var maxVerticalTopPadding = 0;
6426 var maxVerticalBottomPadding = 0;
6427
6428 helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
6429 if (horizontalBox.getPadding) {
6430 var boxPadding = horizontalBox.getPadding();
6431 maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
6432 maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
6433 }
6434 });
6435
6436 helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
6437 if (verticalBox.getPadding) {
6438 var boxPadding = verticalBox.getPadding();
6439 maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
6440 maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
6441 }
6442 });
6443
6444 // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
6445 // be if the axes are drawn at their minimum sizes.
6446 // Steps 5 & 6
6447 var totalLeftBoxesWidth = leftPadding;
6448 var totalRightBoxesWidth = rightPadding;
6449 var totalTopBoxesHeight = topPadding;
6450 var totalBottomBoxesHeight = bottomPadding;
6451
6452 // Function to fit a box
6453 function fitBox(box) {
6454 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
6455 return minBox.box === box;
6456 });
6457
6458 if (minBoxSize) {
6459 if (box.isHorizontal()) {
6460 var scaleMargin = {
6461 left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
6462 right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
6463 top: 0,
6464 bottom: 0
6465 };
6466
6467 // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
6468 // on the margin. Sometimes they need to increase in size slightly
6469 box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
6470 } else {
6471 box.update(minBoxSize.minSize.width, maxChartAreaHeight);
6472 }
6473 }
6474 }
6475
6476 // Update, and calculate the left and right margins for the horizontal boxes
6477 helpers.each(leftBoxes.concat(rightBoxes), fitBox);
6478
6479 helpers.each(leftBoxes, function(box) {
6480 totalLeftBoxesWidth += box.width;
6481 });
6482
6483 helpers.each(rightBoxes, function(box) {
6484 totalRightBoxesWidth += box.width;
6485 });
6486
6487 // Set the Left and Right margins for the horizontal boxes
6488 helpers.each(topBoxes.concat(bottomBoxes), fitBox);
6489
6490 // Figure out how much margin is on the top and bottom of the vertical boxes
6491 helpers.each(topBoxes, function(box) {
6492 totalTopBoxesHeight += box.height;
6493 });
6494
6495 helpers.each(bottomBoxes, function(box) {
6496 totalBottomBoxesHeight += box.height;
6497 });
6498
6499 function finalFitVerticalBox(box) {
6500 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
6501 return minSize.box === box;
6502 });
6503
6504 var scaleMargin = {
6505 left: 0,
6506 right: 0,
6507 top: totalTopBoxesHeight,
6508 bottom: totalBottomBoxesHeight
6509 };
6510
6511 if (minBoxSize) {
6512 box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
6513 }
6514 }
6515
6516 // Let the left layout know the final margin
6517 helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
6518
6519 // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
6520 totalLeftBoxesWidth = leftPadding;
6521 totalRightBoxesWidth = rightPadding;
6522 totalTopBoxesHeight = topPadding;
6523 totalBottomBoxesHeight = bottomPadding;
6524
6525 helpers.each(leftBoxes, function(box) {
6526 totalLeftBoxesWidth += box.width;
6527 });
6528
6529 helpers.each(rightBoxes, function(box) {
6530 totalRightBoxesWidth += box.width;
6531 });
6532
6533 helpers.each(topBoxes, function(box) {
6534 totalTopBoxesHeight += box.height;
6535 });
6536 helpers.each(bottomBoxes, function(box) {
6537 totalBottomBoxesHeight += box.height;
6538 });
6539
6540 // We may be adding some padding to account for rotated x axis labels
6541 var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
6542 totalLeftBoxesWidth += leftPaddingAddition;
6543 totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
6544
6545 var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
6546 totalTopBoxesHeight += topPaddingAddition;
6547 totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
6548
6549 // Figure out if our chart area changed. This would occur if the dataset layout label rotation
6550 // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
6551 // without calling `fit` again
6552 var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
6553 var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
6554
6555 if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
6556 helpers.each(leftBoxes, function(box) {
6557 box.height = newMaxChartAreaHeight;
6558 });
6559
6560 helpers.each(rightBoxes, function(box) {
6561 box.height = newMaxChartAreaHeight;
6562 });
6563
6564 helpers.each(topBoxes, function(box) {
6565 if (!box.fullWidth) {
6566 box.width = newMaxChartAreaWidth;
6567 }
6568 });
6569
6570 helpers.each(bottomBoxes, function(box) {
6571 if (!box.fullWidth) {
6572 box.width = newMaxChartAreaWidth;
6573 }
6574 });
6575
6576 maxChartAreaHeight = newMaxChartAreaHeight;
6577 maxChartAreaWidth = newMaxChartAreaWidth;
6578 }
6579
6580 // Step 7 - Position the boxes
6581 var left = leftPadding + leftPaddingAddition;
6582 var top = topPadding + topPaddingAddition;
6583
6584 function placeBox(box) {
6585 if (box.isHorizontal()) {
6586 box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
6587 box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
6588 box.top = top;
6589 box.bottom = top + box.height;
6590
6591 // Move to next point
6592 top = box.bottom;
6593
6594 } else {
6595
6596 box.left = left;
6597 box.right = left + box.width;
6598 box.top = totalTopBoxesHeight;
6599 box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
6600
6601 // Move to next point
6602 left = box.right;
6603 }
6604 }
6605
6606 helpers.each(leftBoxes.concat(topBoxes), placeBox);
6607
6608 // Account for chart width and height
6609 left += maxChartAreaWidth;
6610 top += maxChartAreaHeight;
6611
6612 helpers.each(rightBoxes, placeBox);
6613 helpers.each(bottomBoxes, placeBox);
6614
6615 // Step 8
6616 chart.chartArea = {
6617 left: totalLeftBoxesWidth,
6618 top: totalTopBoxesHeight,
6619 right: totalLeftBoxesWidth + maxChartAreaWidth,
6620 bottom: totalTopBoxesHeight + maxChartAreaHeight
6621 };
6622
6623 // Step 9
6624 helpers.each(chartAreaBoxes, function(box) {
6625 box.left = chart.chartArea.left;
6626 box.top = chart.chartArea.top;
6627 box.right = chart.chartArea.right;
6628 box.bottom = chart.chartArea.bottom;
6629
6630 box.update(maxChartAreaWidth, maxChartAreaHeight);
6631 });
6632 }
6633};
6634
6635},{"45":45}],31:[function(require,module,exports){
6636'use strict';
6637
6638var defaults = require(25);
6639var helpers = require(45);
6640
6641defaults._set('global', {
6642 plugins: {}
6643});
6644
6645/**
6646 * The plugin service singleton
6647 * @namespace Chart.plugins
6648 * @since 2.1.0
6649 */
6650module.exports = {
6651 /**
6652 * Globally registered plugins.
6653 * @private
6654 */
6655 _plugins: [],
6656
6657 /**
6658 * This identifier is used to invalidate the descriptors cache attached to each chart
6659 * when a global plugin is registered or unregistered. In this case, the cache ID is
6660 * incremented and descriptors are regenerated during following API calls.
6661 * @private
6662 */
6663 _cacheId: 0,
6664
6665 /**
6666 * Registers the given plugin(s) if not already registered.
6667 * @param {Array|Object} plugins plugin instance(s).
6668 */
6669 register: function(plugins) {
6670 var p = this._plugins;
6671 ([]).concat(plugins).forEach(function(plugin) {
6672 if (p.indexOf(plugin) === -1) {
6673 p.push(plugin);
6674 }
6675 });
6676
6677 this._cacheId++;
6678 },
6679
6680 /**
6681 * Unregisters the given plugin(s) only if registered.
6682 * @param {Array|Object} plugins plugin instance(s).
6683 */
6684 unregister: function(plugins) {
6685 var p = this._plugins;
6686 ([]).concat(plugins).forEach(function(plugin) {
6687 var idx = p.indexOf(plugin);
6688 if (idx !== -1) {
6689 p.splice(idx, 1);
6690 }
6691 });
6692
6693 this._cacheId++;
6694 },
6695
6696 /**
6697 * Remove all registered plugins.
6698 * @since 2.1.5
6699 */
6700 clear: function() {
6701 this._plugins = [];
6702 this._cacheId++;
6703 },
6704
6705 /**
6706 * Returns the number of registered plugins?
6707 * @returns {Number}
6708 * @since 2.1.5
6709 */
6710 count: function() {
6711 return this._plugins.length;
6712 },
6713
6714 /**
6715 * Returns all registered plugin instances.
6716 * @returns {Array} array of plugin objects.
6717 * @since 2.1.5
6718 */
6719 getAll: function() {
6720 return this._plugins;
6721 },
6722
6723 /**
6724 * Calls enabled plugins for `chart` on the specified hook and with the given args.
6725 * This method immediately returns as soon as a plugin explicitly returns false. The
6726 * returned value can be used, for instance, to interrupt the current action.
6727 * @param {Object} chart - The chart instance for which plugins should be called.
6728 * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
6729 * @param {Array} [args] - Extra arguments to apply to the hook call.
6730 * @returns {Boolean} false if any of the plugins return false, else returns true.
6731 */
6732 notify: function(chart, hook, args) {
6733 var descriptors = this.descriptors(chart);
6734 var ilen = descriptors.length;
6735 var i, descriptor, plugin, params, method;
6736
6737 for (i = 0; i < ilen; ++i) {
6738 descriptor = descriptors[i];
6739 plugin = descriptor.plugin;
6740 method = plugin[hook];
6741 if (typeof method === 'function') {
6742 params = [chart].concat(args || []);
6743 params.push(descriptor.options);
6744 if (method.apply(plugin, params) === false) {
6745 return false;
6746 }
6747 }
6748 }
6749
6750 return true;
6751 },
6752
6753 /**
6754 * Returns descriptors of enabled plugins for the given chart.
6755 * @returns {Array} [{ plugin, options }]
6756 * @private
6757 */
6758 descriptors: function(chart) {
6759 var cache = chart._plugins || (chart._plugins = {});
6760 if (cache.id === this._cacheId) {
6761 return cache.descriptors;
6762 }
6763
6764 var plugins = [];
6765 var descriptors = [];
6766 var config = (chart && chart.config) || {};
6767 var options = (config.options && config.options.plugins) || {};
6768
6769 this._plugins.concat(config.plugins || []).forEach(function(plugin) {
6770 var idx = plugins.indexOf(plugin);
6771 if (idx !== -1) {
6772 return;
6773 }
6774
6775 var id = plugin.id;
6776 var opts = options[id];
6777 if (opts === false) {
6778 return;
6779 }
6780
6781 if (opts === true) {
6782 opts = helpers.clone(defaults.global.plugins[id]);
6783 }
6784
6785 plugins.push(plugin);
6786 descriptors.push({
6787 plugin: plugin,
6788 options: opts || {}
6789 });
6790 });
6791
6792 cache.descriptors = descriptors;
6793 cache.id = this._cacheId;
6794 return descriptors;
6795 }
6796};
6797
6798/**
6799 * Plugin extension hooks.
6800 * @interface IPlugin
6801 * @since 2.1.0
6802 */
6803/**
6804 * @method IPlugin#beforeInit
6805 * @desc Called before initializing `chart`.
6806 * @param {Chart.Controller} chart - The chart instance.
6807 * @param {Object} options - The plugin options.
6808 */
6809/**
6810 * @method IPlugin#afterInit
6811 * @desc Called after `chart` has been initialized and before the first update.
6812 * @param {Chart.Controller} chart - The chart instance.
6813 * @param {Object} options - The plugin options.
6814 */
6815/**
6816 * @method IPlugin#beforeUpdate
6817 * @desc Called before updating `chart`. If any plugin returns `false`, the update
6818 * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
6819 * @param {Chart.Controller} chart - The chart instance.
6820 * @param {Object} options - The plugin options.
6821 * @returns {Boolean} `false` to cancel the chart update.
6822 */
6823/**
6824 * @method IPlugin#afterUpdate
6825 * @desc Called after `chart` has been updated and before rendering. Note that this
6826 * hook will not be called if the chart update has been previously cancelled.
6827 * @param {Chart.Controller} chart - The chart instance.
6828 * @param {Object} options - The plugin options.
6829 */
6830/**
6831 * @method IPlugin#beforeDatasetsUpdate
6832 * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
6833 * the datasets update is cancelled until another `update` is triggered.
6834 * @param {Chart.Controller} chart - The chart instance.
6835 * @param {Object} options - The plugin options.
6836 * @returns {Boolean} false to cancel the datasets update.
6837 * @since version 2.1.5
6838*/
6839/**
6840 * @method IPlugin#afterDatasetsUpdate
6841 * @desc Called after the `chart` datasets have been updated. Note that this hook
6842 * will not be called if the datasets update has been previously cancelled.
6843 * @param {Chart.Controller} chart - The chart instance.
6844 * @param {Object} options - The plugin options.
6845 * @since version 2.1.5
6846 */
6847/**
6848 * @method IPlugin#beforeDatasetUpdate
6849 * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
6850 * returns `false`, the datasets update is cancelled until another `update` is triggered.
6851 * @param {Chart} chart - The chart instance.
6852 * @param {Object} args - The call arguments.
6853 * @param {Number} args.index - The dataset index.
6854 * @param {Object} args.meta - The dataset metadata.
6855 * @param {Object} options - The plugin options.
6856 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6857 */
6858/**
6859 * @method IPlugin#afterDatasetUpdate
6860 * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
6861 * that this hook will not be called if the datasets update has been previously cancelled.
6862 * @param {Chart} chart - The chart instance.
6863 * @param {Object} args - The call arguments.
6864 * @param {Number} args.index - The dataset index.
6865 * @param {Object} args.meta - The dataset metadata.
6866 * @param {Object} options - The plugin options.
6867 */
6868/**
6869 * @method IPlugin#beforeLayout
6870 * @desc Called before laying out `chart`. If any plugin returns `false`,
6871 * the layout update is cancelled until another `update` is triggered.
6872 * @param {Chart.Controller} chart - The chart instance.
6873 * @param {Object} options - The plugin options.
6874 * @returns {Boolean} `false` to cancel the chart layout.
6875 */
6876/**
6877 * @method IPlugin#afterLayout
6878 * @desc Called after the `chart` has been layed out. Note that this hook will not
6879 * be called if the layout update has been previously cancelled.
6880 * @param {Chart.Controller} chart - The chart instance.
6881 * @param {Object} options - The plugin options.
6882 */
6883/**
6884 * @method IPlugin#beforeRender
6885 * @desc Called before rendering `chart`. If any plugin returns `false`,
6886 * the rendering is cancelled until another `render` is triggered.
6887 * @param {Chart.Controller} chart - The chart instance.
6888 * @param {Object} options - The plugin options.
6889 * @returns {Boolean} `false` to cancel the chart rendering.
6890 */
6891/**
6892 * @method IPlugin#afterRender
6893 * @desc Called after the `chart` has been fully rendered (and animation completed). Note
6894 * that this hook will not be called if the rendering has been previously cancelled.
6895 * @param {Chart.Controller} chart - The chart instance.
6896 * @param {Object} options - The plugin options.
6897 */
6898/**
6899 * @method IPlugin#beforeDraw
6900 * @desc Called before drawing `chart` at every animation frame specified by the given
6901 * easing value. If any plugin returns `false`, the frame drawing is cancelled until
6902 * another `render` is triggered.
6903 * @param {Chart.Controller} chart - The chart instance.
6904 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6905 * @param {Object} options - The plugin options.
6906 * @returns {Boolean} `false` to cancel the chart drawing.
6907 */
6908/**
6909 * @method IPlugin#afterDraw
6910 * @desc Called after the `chart` has been drawn for the specific easing value. Note
6911 * that this hook will not be called if the drawing has been previously cancelled.
6912 * @param {Chart.Controller} chart - The chart instance.
6913 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6914 * @param {Object} options - The plugin options.
6915 */
6916/**
6917 * @method IPlugin#beforeDatasetsDraw
6918 * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
6919 * the datasets drawing is cancelled until another `render` is triggered.
6920 * @param {Chart.Controller} chart - The chart instance.
6921 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6922 * @param {Object} options - The plugin options.
6923 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6924 */
6925/**
6926 * @method IPlugin#afterDatasetsDraw
6927 * @desc Called after the `chart` datasets have been drawn. Note that this hook
6928 * will not be called if the datasets drawing has been previously cancelled.
6929 * @param {Chart.Controller} chart - The chart instance.
6930 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6931 * @param {Object} options - The plugin options.
6932 */
6933/**
6934 * @method IPlugin#beforeDatasetDraw
6935 * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
6936 * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
6937 * is cancelled until another `render` is triggered.
6938 * @param {Chart} chart - The chart instance.
6939 * @param {Object} args - The call arguments.
6940 * @param {Number} args.index - The dataset index.
6941 * @param {Object} args.meta - The dataset metadata.
6942 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6943 * @param {Object} options - The plugin options.
6944 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6945 */
6946/**
6947 * @method IPlugin#afterDatasetDraw
6948 * @desc Called after the `chart` datasets at the given `args.index` have been drawn
6949 * (datasets are drawn in the reverse order). Note that this hook will not be called
6950 * if the datasets drawing has been previously cancelled.
6951 * @param {Chart} chart - The chart instance.
6952 * @param {Object} args - The call arguments.
6953 * @param {Number} args.index - The dataset index.
6954 * @param {Object} args.meta - The dataset metadata.
6955 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6956 * @param {Object} options - The plugin options.
6957 */
6958/**
6959 * @method IPlugin#beforeTooltipDraw
6960 * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
6961 * the tooltip drawing is cancelled until another `render` is triggered.
6962 * @param {Chart} chart - The chart instance.
6963 * @param {Object} args - The call arguments.
6964 * @param {Object} args.tooltip - The tooltip.
6965 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6966 * @param {Object} options - The plugin options.
6967 * @returns {Boolean} `false` to cancel the chart tooltip drawing.
6968 */
6969/**
6970 * @method IPlugin#afterTooltipDraw
6971 * @desc Called after drawing the `tooltip`. Note that this hook will not
6972 * be called if the tooltip drawing has been previously cancelled.
6973 * @param {Chart} chart - The chart instance.
6974 * @param {Object} args - The call arguments.
6975 * @param {Object} args.tooltip - The tooltip.
6976 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6977 * @param {Object} options - The plugin options.
6978 */
6979/**
6980 * @method IPlugin#beforeEvent
6981 * @desc Called before processing the specified `event`. If any plugin returns `false`,
6982 * the event will be discarded.
6983 * @param {Chart.Controller} chart - The chart instance.
6984 * @param {IEvent} event - The event object.
6985 * @param {Object} options - The plugin options.
6986 */
6987/**
6988 * @method IPlugin#afterEvent
6989 * @desc Called after the `event` has been consumed. Note that this hook
6990 * will not be called if the `event` has been previously discarded.
6991 * @param {Chart.Controller} chart - The chart instance.
6992 * @param {IEvent} event - The event object.
6993 * @param {Object} options - The plugin options.
6994 */
6995/**
6996 * @method IPlugin#resize
6997 * @desc Called after the chart as been resized.
6998 * @param {Chart.Controller} chart - The chart instance.
6999 * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
7000 * @param {Object} options - The plugin options.
7001 */
7002/**
7003 * @method IPlugin#destroy
7004 * @desc Called after the chart as been destroyed.
7005 * @param {Chart.Controller} chart - The chart instance.
7006 * @param {Object} options - The plugin options.
7007 */
7008
7009},{"25":25,"45":45}],32:[function(require,module,exports){
7010'use strict';
7011
7012var defaults = require(25);
7013var Element = require(26);
7014var helpers = require(45);
7015var Ticks = require(34);
7016
7017defaults._set('scale', {
7018 display: true,
7019 position: 'left',
7020 offset: false,
7021
7022 // grid line settings
7023 gridLines: {
7024 display: true,
7025 color: 'rgba(0, 0, 0, 0.1)',
7026 lineWidth: 1,
7027 drawBorder: true,
7028 drawOnChartArea: true,
7029 drawTicks: true,
7030 tickMarkLength: 10,
7031 zeroLineWidth: 1,
7032 zeroLineColor: 'rgba(0,0,0,0.25)',
7033 zeroLineBorderDash: [],
7034 zeroLineBorderDashOffset: 0.0,
7035 offsetGridLines: false,
7036 borderDash: [],
7037 borderDashOffset: 0.0
7038 },
7039
7040 // scale label
7041 scaleLabel: {
7042 // display property
7043 display: false,
7044
7045 // actual label
7046 labelString: '',
7047
7048 // line height
7049 lineHeight: 1.2,
7050
7051 // top/bottom padding
7052 padding: {
7053 top: 4,
7054 bottom: 4
7055 }
7056 },
7057
7058 // label settings
7059 ticks: {
7060 beginAtZero: false,
7061 minRotation: 0,
7062 maxRotation: 50,
7063 mirror: false,
7064 padding: 0,
7065 reverse: false,
7066 display: true,
7067 autoSkip: true,
7068 autoSkipPadding: 0,
7069 labelOffset: 0,
7070 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
7071 callback: Ticks.formatters.values,
7072 minor: {},
7073 major: {}
7074 }
7075});
7076
7077function labelsFromTicks(ticks) {
7078 var labels = [];
7079 var i, ilen;
7080
7081 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
7082 labels.push(ticks[i].label);
7083 }
7084
7085 return labels;
7086}
7087
7088function getLineValue(scale, index, offsetGridLines) {
7089 var lineValue = scale.getPixelForTick(index);
7090
7091 if (offsetGridLines) {
7092 if (index === 0) {
7093 lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
7094 } else {
7095 lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
7096 }
7097 }
7098 return lineValue;
7099}
7100
7101module.exports = function(Chart) {
7102
7103 function computeTextSize(context, tick, font) {
7104 return helpers.isArray(tick) ?
7105 helpers.longestText(context, font, tick) :
7106 context.measureText(tick).width;
7107 }
7108
7109 function parseFontOptions(options) {
7110 var valueOrDefault = helpers.valueOrDefault;
7111 var globalDefaults = defaults.global;
7112 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
7113 var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
7114 var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
7115
7116 return {
7117 size: size,
7118 style: style,
7119 family: family,
7120 font: helpers.fontString(size, style, family)
7121 };
7122 }
7123
7124 function parseLineHeight(options) {
7125 return helpers.options.toLineHeight(
7126 helpers.valueOrDefault(options.lineHeight, 1.2),
7127 helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize));
7128 }
7129
7130 Chart.Scale = Element.extend({
7131 /**
7132 * Get the padding needed for the scale
7133 * @method getPadding
7134 * @private
7135 * @returns {Padding} the necessary padding
7136 */
7137 getPadding: function() {
7138 var me = this;
7139 return {
7140 left: me.paddingLeft || 0,
7141 top: me.paddingTop || 0,
7142 right: me.paddingRight || 0,
7143 bottom: me.paddingBottom || 0
7144 };
7145 },
7146
7147 /**
7148 * Returns the scale tick objects ({label, major})
7149 * @since 2.7
7150 */
7151 getTicks: function() {
7152 return this._ticks;
7153 },
7154
7155 // These methods are ordered by lifecyle. Utilities then follow.
7156 // Any function defined here is inherited by all scale types.
7157 // Any function can be extended by the scale type
7158
7159 mergeTicksOptions: function() {
7160 var ticks = this.options.ticks;
7161 if (ticks.minor === false) {
7162 ticks.minor = {
7163 display: false
7164 };
7165 }
7166 if (ticks.major === false) {
7167 ticks.major = {
7168 display: false
7169 };
7170 }
7171 for (var key in ticks) {
7172 if (key !== 'major' && key !== 'minor') {
7173 if (typeof ticks.minor[key] === 'undefined') {
7174 ticks.minor[key] = ticks[key];
7175 }
7176 if (typeof ticks.major[key] === 'undefined') {
7177 ticks.major[key] = ticks[key];
7178 }
7179 }
7180 }
7181 },
7182 beforeUpdate: function() {
7183 helpers.callback(this.options.beforeUpdate, [this]);
7184 },
7185 update: function(maxWidth, maxHeight, margins) {
7186 var me = this;
7187 var i, ilen, labels, label, ticks, tick;
7188
7189 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
7190 me.beforeUpdate();
7191
7192 // Absorb the master measurements
7193 me.maxWidth = maxWidth;
7194 me.maxHeight = maxHeight;
7195 me.margins = helpers.extend({
7196 left: 0,
7197 right: 0,
7198 top: 0,
7199 bottom: 0
7200 }, margins);
7201 me.longestTextCache = me.longestTextCache || {};
7202
7203 // Dimensions
7204 me.beforeSetDimensions();
7205 me.setDimensions();
7206 me.afterSetDimensions();
7207
7208 // Data min/max
7209 me.beforeDataLimits();
7210 me.determineDataLimits();
7211 me.afterDataLimits();
7212
7213 // Ticks - `this.ticks` is now DEPRECATED!
7214 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
7215 // and must not be accessed directly from outside this class. `this.ticks` being
7216 // around for long time and not marked as private, we can't change its structure
7217 // without unexpected breaking changes. If you need to access the scale ticks,
7218 // use scale.getTicks() instead.
7219
7220 me.beforeBuildTicks();
7221
7222 // New implementations should return an array of objects but for BACKWARD COMPAT,
7223 // we still support no return (`this.ticks` internally set by calling this method).
7224 ticks = me.buildTicks() || [];
7225
7226 me.afterBuildTicks();
7227
7228 me.beforeTickToLabelConversion();
7229
7230 // New implementations should return the formatted tick labels but for BACKWARD
7231 // COMPAT, we still support no return (`this.ticks` internally changed by calling
7232 // this method and supposed to contain only string values).
7233 labels = me.convertTicksToLabels(ticks) || me.ticks;
7234
7235 me.afterTickToLabelConversion();
7236
7237 me.ticks = labels; // BACKWARD COMPATIBILITY
7238
7239 // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
7240
7241 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
7242 for (i = 0, ilen = labels.length; i < ilen; ++i) {
7243 label = labels[i];
7244 tick = ticks[i];
7245 if (!tick) {
7246 ticks.push(tick = {
7247 label: label,
7248 major: false
7249 });
7250 } else {
7251 tick.label = label;
7252 }
7253 }
7254
7255 me._ticks = ticks;
7256
7257 // Tick Rotation
7258 me.beforeCalculateTickRotation();
7259 me.calculateTickRotation();
7260 me.afterCalculateTickRotation();
7261 // Fit
7262 me.beforeFit();
7263 me.fit();
7264 me.afterFit();
7265 //
7266 me.afterUpdate();
7267
7268 return me.minSize;
7269
7270 },
7271 afterUpdate: function() {
7272 helpers.callback(this.options.afterUpdate, [this]);
7273 },
7274
7275 //
7276
7277 beforeSetDimensions: function() {
7278 helpers.callback(this.options.beforeSetDimensions, [this]);
7279 },
7280 setDimensions: function() {
7281 var me = this;
7282 // Set the unconstrained dimension before label rotation
7283 if (me.isHorizontal()) {
7284 // Reset position before calculating rotation
7285 me.width = me.maxWidth;
7286 me.left = 0;
7287 me.right = me.width;
7288 } else {
7289 me.height = me.maxHeight;
7290
7291 // Reset position before calculating rotation
7292 me.top = 0;
7293 me.bottom = me.height;
7294 }
7295
7296 // Reset padding
7297 me.paddingLeft = 0;
7298 me.paddingTop = 0;
7299 me.paddingRight = 0;
7300 me.paddingBottom = 0;
7301 },
7302 afterSetDimensions: function() {
7303 helpers.callback(this.options.afterSetDimensions, [this]);
7304 },
7305
7306 // Data limits
7307 beforeDataLimits: function() {
7308 helpers.callback(this.options.beforeDataLimits, [this]);
7309 },
7310 determineDataLimits: helpers.noop,
7311 afterDataLimits: function() {
7312 helpers.callback(this.options.afterDataLimits, [this]);
7313 },
7314
7315 //
7316 beforeBuildTicks: function() {
7317 helpers.callback(this.options.beforeBuildTicks, [this]);
7318 },
7319 buildTicks: helpers.noop,
7320 afterBuildTicks: function() {
7321 helpers.callback(this.options.afterBuildTicks, [this]);
7322 },
7323
7324 beforeTickToLabelConversion: function() {
7325 helpers.callback(this.options.beforeTickToLabelConversion, [this]);
7326 },
7327 convertTicksToLabels: function() {
7328 var me = this;
7329 // Convert ticks to strings
7330 var tickOpts = me.options.ticks;
7331 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
7332 },
7333 afterTickToLabelConversion: function() {
7334 helpers.callback(this.options.afterTickToLabelConversion, [this]);
7335 },
7336
7337 //
7338
7339 beforeCalculateTickRotation: function() {
7340 helpers.callback(this.options.beforeCalculateTickRotation, [this]);
7341 },
7342 calculateTickRotation: function() {
7343 var me = this;
7344 var context = me.ctx;
7345 var tickOpts = me.options.ticks;
7346 var labels = labelsFromTicks(me._ticks);
7347
7348 // Get the width of each grid by calculating the difference
7349 // between x offsets between 0 and 1.
7350 var tickFont = parseFontOptions(tickOpts);
7351 context.font = tickFont.font;
7352
7353 var labelRotation = tickOpts.minRotation || 0;
7354
7355 if (labels.length && me.options.display && me.isHorizontal()) {
7356 var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache);
7357 var labelWidth = originalLabelWidth;
7358 var cosRotation, sinRotation;
7359
7360 // Allow 3 pixels x2 padding either side for label readability
7361 var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
7362
7363 // Max label rotation can be set or default to 90 - also act as a loop counter
7364 while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
7365 var angleRadians = helpers.toRadians(labelRotation);
7366 cosRotation = Math.cos(angleRadians);
7367 sinRotation = Math.sin(angleRadians);
7368
7369 if (sinRotation * originalLabelWidth > me.maxHeight) {
7370 // go back one step
7371 labelRotation--;
7372 break;
7373 }
7374
7375 labelRotation++;
7376 labelWidth = cosRotation * originalLabelWidth;
7377 }
7378 }
7379
7380 me.labelRotation = labelRotation;
7381 },
7382 afterCalculateTickRotation: function() {
7383 helpers.callback(this.options.afterCalculateTickRotation, [this]);
7384 },
7385
7386 //
7387
7388 beforeFit: function() {
7389 helpers.callback(this.options.beforeFit, [this]);
7390 },
7391 fit: function() {
7392 var me = this;
7393 // Reset
7394 var minSize = me.minSize = {
7395 width: 0,
7396 height: 0
7397 };
7398
7399 var labels = labelsFromTicks(me._ticks);
7400
7401 var opts = me.options;
7402 var tickOpts = opts.ticks;
7403 var scaleLabelOpts = opts.scaleLabel;
7404 var gridLineOpts = opts.gridLines;
7405 var display = opts.display;
7406 var isHorizontal = me.isHorizontal();
7407
7408 var tickFont = parseFontOptions(tickOpts);
7409 var tickMarkLength = opts.gridLines.tickMarkLength;
7410
7411 // Width
7412 if (isHorizontal) {
7413 // subtract the margins to line up with the chartArea if we are a full width scale
7414 minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
7415 } else {
7416 minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7417 }
7418
7419 // height
7420 if (isHorizontal) {
7421 minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7422 } else {
7423 minSize.height = me.maxHeight; // fill all the height
7424 }
7425
7426 // Are we showing a title for the scale?
7427 if (scaleLabelOpts.display && display) {
7428 var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts);
7429 var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding);
7430 var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height;
7431
7432 if (isHorizontal) {
7433 minSize.height += deltaHeight;
7434 } else {
7435 minSize.width += deltaHeight;
7436 }
7437 }
7438
7439 // Don't bother fitting the ticks if we are not showing them
7440 if (tickOpts.display && display) {
7441 var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache);
7442 var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels);
7443 var lineSpace = tickFont.size * 0.5;
7444 var tickPadding = me.options.ticks.padding;
7445
7446 if (isHorizontal) {
7447 // A horizontal axis is more constrained by the height.
7448 me.longestLabelWidth = largestTextWidth;
7449
7450 var angleRadians = helpers.toRadians(me.labelRotation);
7451 var cosRotation = Math.cos(angleRadians);
7452 var sinRotation = Math.sin(angleRadians);
7453
7454 // TODO - improve this calculation
7455 var labelHeight = (sinRotation * largestTextWidth)
7456 + (tickFont.size * tallestLabelHeightInLines)
7457 + (lineSpace * (tallestLabelHeightInLines - 1))
7458 + lineSpace; // padding
7459
7460 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
7461
7462 me.ctx.font = tickFont.font;
7463 var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font);
7464 var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font);
7465
7466 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
7467 // which means that the right padding is dominated by the font height
7468 if (me.labelRotation !== 0) {
7469 me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges
7470 me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3;
7471 } else {
7472 me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
7473 me.paddingRight = lastLabelWidth / 2 + 3;
7474 }
7475 } else {
7476 // A vertical axis is more constrained by the width. Labels are the
7477 // dominant factor here, so get that length first and account for padding
7478 if (tickOpts.mirror) {
7479 largestTextWidth = 0;
7480 } else {
7481 // use lineSpace for consistency with horizontal axis
7482 // tickPadding is not implemented for horizontal
7483 largestTextWidth += tickPadding + lineSpace;
7484 }
7485
7486 minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
7487
7488 me.paddingTop = tickFont.size / 2;
7489 me.paddingBottom = tickFont.size / 2;
7490 }
7491 }
7492
7493 me.handleMargins();
7494
7495 me.width = minSize.width;
7496 me.height = minSize.height;
7497 },
7498
7499 /**
7500 * Handle margins and padding interactions
7501 * @private
7502 */
7503 handleMargins: function() {
7504 var me = this;
7505 if (me.margins) {
7506 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7507 me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
7508 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7509 me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
7510 }
7511 },
7512
7513 afterFit: function() {
7514 helpers.callback(this.options.afterFit, [this]);
7515 },
7516
7517 // Shared Methods
7518 isHorizontal: function() {
7519 return this.options.position === 'top' || this.options.position === 'bottom';
7520 },
7521 isFullWidth: function() {
7522 return (this.options.fullWidth);
7523 },
7524
7525 // 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
7526 getRightValue: function(rawValue) {
7527 // Null and undefined values first
7528 if (helpers.isNullOrUndef(rawValue)) {
7529 return NaN;
7530 }
7531 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
7532 if (typeof rawValue === 'number' && !isFinite(rawValue)) {
7533 return NaN;
7534 }
7535 // If it is in fact an object, dive in one more level
7536 if (rawValue) {
7537 if (this.isHorizontal()) {
7538 if (rawValue.x !== undefined) {
7539 return this.getRightValue(rawValue.x);
7540 }
7541 } else if (rawValue.y !== undefined) {
7542 return this.getRightValue(rawValue.y);
7543 }
7544 }
7545
7546 // Value is good, return it
7547 return rawValue;
7548 },
7549
7550 /**
7551 * Used to get the value to display in the tooltip for the data at the given index
7552 * @param index
7553 * @param datasetIndex
7554 */
7555 getLabelForIndex: helpers.noop,
7556
7557 /**
7558 * Returns the location of the given data point. Value can either be an index or a numerical value
7559 * The coordinate (0, 0) is at the upper-left corner of the canvas
7560 * @param value
7561 * @param index
7562 * @param datasetIndex
7563 */
7564 getPixelForValue: helpers.noop,
7565
7566 /**
7567 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
7568 * The coordinate (0, 0) is at the upper-left corner of the canvas
7569 * @param pixel
7570 */
7571 getValueForPixel: helpers.noop,
7572
7573 /**
7574 * Returns the location of the tick at the given index
7575 * The coordinate (0, 0) is at the upper-left corner of the canvas
7576 */
7577 getPixelForTick: function(index) {
7578 var me = this;
7579 var offset = me.options.offset;
7580 if (me.isHorizontal()) {
7581 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7582 var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
7583 var pixel = (tickWidth * index) + me.paddingLeft;
7584
7585 if (offset) {
7586 pixel += tickWidth / 2;
7587 }
7588
7589 var finalVal = me.left + Math.round(pixel);
7590 finalVal += me.isFullWidth() ? me.margins.left : 0;
7591 return finalVal;
7592 }
7593 var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
7594 return me.top + (index * (innerHeight / (me._ticks.length - 1)));
7595 },
7596
7597 /**
7598 * Utility for getting the pixel location of a percentage of scale
7599 * The coordinate (0, 0) is at the upper-left corner of the canvas
7600 */
7601 getPixelForDecimal: function(decimal) {
7602 var me = this;
7603 if (me.isHorizontal()) {
7604 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7605 var valueOffset = (innerWidth * decimal) + me.paddingLeft;
7606
7607 var finalVal = me.left + Math.round(valueOffset);
7608 finalVal += me.isFullWidth() ? me.margins.left : 0;
7609 return finalVal;
7610 }
7611 return me.top + (decimal * me.height);
7612 },
7613
7614 /**
7615 * Returns the pixel for the minimum chart value
7616 * The coordinate (0, 0) is at the upper-left corner of the canvas
7617 */
7618 getBasePixel: function() {
7619 return this.getPixelForValue(this.getBaseValue());
7620 },
7621
7622 getBaseValue: function() {
7623 var me = this;
7624 var min = me.min;
7625 var max = me.max;
7626
7627 return me.beginAtZero ? 0 :
7628 min < 0 && max < 0 ? max :
7629 min > 0 && max > 0 ? min :
7630 0;
7631 },
7632
7633 /**
7634 * Returns a subset of ticks to be plotted to avoid overlapping labels.
7635 * @private
7636 */
7637 _autoSkip: function(ticks) {
7638 var skipRatio;
7639 var me = this;
7640 var isHorizontal = me.isHorizontal();
7641 var optionTicks = me.options.ticks.minor;
7642 var tickCount = ticks.length;
7643 var labelRotationRadians = helpers.toRadians(me.labelRotation);
7644 var cosRotation = Math.cos(labelRotationRadians);
7645 var longestRotatedLabel = me.longestLabelWidth * cosRotation;
7646 var result = [];
7647 var i, tick, shouldSkip;
7648
7649 // figure out the maximum number of gridlines to show
7650 var maxTicks;
7651 if (optionTicks.maxTicksLimit) {
7652 maxTicks = optionTicks.maxTicksLimit;
7653 }
7654
7655 if (isHorizontal) {
7656 skipRatio = false;
7657
7658 if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) {
7659 skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight)));
7660 }
7661
7662 // if they defined a max number of optionTicks,
7663 // increase skipRatio until that number is met
7664 if (maxTicks && tickCount > maxTicks) {
7665 skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks));
7666 }
7667 }
7668
7669 for (i = 0; i < tickCount; i++) {
7670 tick = ticks[i];
7671
7672 // Since we always show the last tick,we need may need to hide the last shown one before
7673 shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount);
7674 if (shouldSkip && i !== tickCount - 1) {
7675 // leave tick in place but make sure it's not displayed (#4635)
7676 delete tick.label;
7677 }
7678 result.push(tick);
7679 }
7680 return result;
7681 },
7682
7683 // Actually draw the scale on the canvas
7684 // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
7685 draw: function(chartArea) {
7686 var me = this;
7687 var options = me.options;
7688 if (!options.display) {
7689 return;
7690 }
7691
7692 var context = me.ctx;
7693 var globalDefaults = defaults.global;
7694 var optionTicks = options.ticks.minor;
7695 var optionMajorTicks = options.ticks.major || optionTicks;
7696 var gridLines = options.gridLines;
7697 var scaleLabel = options.scaleLabel;
7698
7699 var isRotated = me.labelRotation !== 0;
7700 var isHorizontal = me.isHorizontal();
7701
7702 var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
7703 var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
7704 var tickFont = parseFontOptions(optionTicks);
7705 var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
7706 var majorTickFont = parseFontOptions(optionMajorTicks);
7707
7708 var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
7709
7710 var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
7711 var scaleLabelFont = parseFontOptions(scaleLabel);
7712 var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
7713 var labelRotationRadians = helpers.toRadians(me.labelRotation);
7714
7715 var itemsToDraw = [];
7716
7717 var xTickStart = options.position === 'right' ? me.left : me.right - tl;
7718 var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
7719 var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
7720 var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
7721
7722 helpers.each(ticks, function(tick, index) {
7723 // autoskipper skipped this tick (#4635)
7724 if (helpers.isNullOrUndef(tick.label)) {
7725 return;
7726 }
7727
7728 var label = tick.label;
7729 var lineWidth, lineColor, borderDash, borderDashOffset;
7730 if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
7731 // Draw the first index specially
7732 lineWidth = gridLines.zeroLineWidth;
7733 lineColor = gridLines.zeroLineColor;
7734 borderDash = gridLines.zeroLineBorderDash;
7735 borderDashOffset = gridLines.zeroLineBorderDashOffset;
7736 } else {
7737 lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index);
7738 lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index);
7739 borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
7740 borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
7741 }
7742
7743 // Common properties
7744 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
7745 var textAlign = 'middle';
7746 var textBaseline = 'middle';
7747 var tickPadding = optionTicks.padding;
7748
7749 if (isHorizontal) {
7750 var labelYOffset = tl + tickPadding;
7751
7752 if (options.position === 'bottom') {
7753 // bottom
7754 textBaseline = !isRotated ? 'top' : 'middle';
7755 textAlign = !isRotated ? 'center' : 'right';
7756 labelY = me.top + labelYOffset;
7757 } else {
7758 // top
7759 textBaseline = !isRotated ? 'bottom' : 'middle';
7760 textAlign = !isRotated ? 'center' : 'left';
7761 labelY = me.bottom - labelYOffset;
7762 }
7763
7764 var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7765 if (xLineValue < me.left) {
7766 lineColor = 'rgba(0,0,0,0)';
7767 }
7768 xLineValue += helpers.aliasPixel(lineWidth);
7769
7770 labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
7771
7772 tx1 = tx2 = x1 = x2 = xLineValue;
7773 ty1 = yTickStart;
7774 ty2 = yTickEnd;
7775 y1 = chartArea.top;
7776 y2 = chartArea.bottom;
7777 } else {
7778 var isLeft = options.position === 'left';
7779 var labelXOffset;
7780
7781 if (optionTicks.mirror) {
7782 textAlign = isLeft ? 'left' : 'right';
7783 labelXOffset = tickPadding;
7784 } else {
7785 textAlign = isLeft ? 'right' : 'left';
7786 labelXOffset = tl + tickPadding;
7787 }
7788
7789 labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
7790
7791 var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7792 if (yLineValue < me.top) {
7793 lineColor = 'rgba(0,0,0,0)';
7794 }
7795 yLineValue += helpers.aliasPixel(lineWidth);
7796
7797 labelY = me.getPixelForTick(index) + optionTicks.labelOffset;
7798
7799 tx1 = xTickStart;
7800 tx2 = xTickEnd;
7801 x1 = chartArea.left;
7802 x2 = chartArea.right;
7803 ty1 = ty2 = y1 = y2 = yLineValue;
7804 }
7805
7806 itemsToDraw.push({
7807 tx1: tx1,
7808 ty1: ty1,
7809 tx2: tx2,
7810 ty2: ty2,
7811 x1: x1,
7812 y1: y1,
7813 x2: x2,
7814 y2: y2,
7815 labelX: labelX,
7816 labelY: labelY,
7817 glWidth: lineWidth,
7818 glColor: lineColor,
7819 glBorderDash: borderDash,
7820 glBorderDashOffset: borderDashOffset,
7821 rotation: -1 * labelRotationRadians,
7822 label: label,
7823 major: tick.major,
7824 textBaseline: textBaseline,
7825 textAlign: textAlign
7826 });
7827 });
7828
7829 // Draw all of the tick labels, tick marks, and grid lines at the correct places
7830 helpers.each(itemsToDraw, function(itemToDraw) {
7831 if (gridLines.display) {
7832 context.save();
7833 context.lineWidth = itemToDraw.glWidth;
7834 context.strokeStyle = itemToDraw.glColor;
7835 if (context.setLineDash) {
7836 context.setLineDash(itemToDraw.glBorderDash);
7837 context.lineDashOffset = itemToDraw.glBorderDashOffset;
7838 }
7839
7840 context.beginPath();
7841
7842 if (gridLines.drawTicks) {
7843 context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
7844 context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
7845 }
7846
7847 if (gridLines.drawOnChartArea) {
7848 context.moveTo(itemToDraw.x1, itemToDraw.y1);
7849 context.lineTo(itemToDraw.x2, itemToDraw.y2);
7850 }
7851
7852 context.stroke();
7853 context.restore();
7854 }
7855
7856 if (optionTicks.display) {
7857 // Make sure we draw text in the correct color and font
7858 context.save();
7859 context.translate(itemToDraw.labelX, itemToDraw.labelY);
7860 context.rotate(itemToDraw.rotation);
7861 context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
7862 context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
7863 context.textBaseline = itemToDraw.textBaseline;
7864 context.textAlign = itemToDraw.textAlign;
7865
7866 var label = itemToDraw.label;
7867 if (helpers.isArray(label)) {
7868 for (var i = 0, y = 0; i < label.length; ++i) {
7869 // We just make sure the multiline element is a string here..
7870 context.fillText('' + label[i], 0, y);
7871 // apply same lineSpacing as calculated @ L#320
7872 y += (tickFont.size * 1.5);
7873 }
7874 } else {
7875 context.fillText(label, 0, 0);
7876 }
7877 context.restore();
7878 }
7879 });
7880
7881 if (scaleLabel.display) {
7882 // Draw the scale label
7883 var scaleLabelX;
7884 var scaleLabelY;
7885 var rotation = 0;
7886 var halfLineHeight = parseLineHeight(scaleLabel) / 2;
7887
7888 if (isHorizontal) {
7889 scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
7890 scaleLabelY = options.position === 'bottom'
7891 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
7892 : me.top + halfLineHeight + scaleLabelPadding.top;
7893 } else {
7894 var isLeft = options.position === 'left';
7895 scaleLabelX = isLeft
7896 ? me.left + halfLineHeight + scaleLabelPadding.top
7897 : me.right - halfLineHeight - scaleLabelPadding.top;
7898 scaleLabelY = me.top + ((me.bottom - me.top) / 2);
7899 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
7900 }
7901
7902 context.save();
7903 context.translate(scaleLabelX, scaleLabelY);
7904 context.rotate(rotation);
7905 context.textAlign = 'center';
7906 context.textBaseline = 'middle';
7907 context.fillStyle = scaleLabelFontColor; // render in correct colour
7908 context.font = scaleLabelFont.font;
7909 context.fillText(scaleLabel.labelString, 0, 0);
7910 context.restore();
7911 }
7912
7913 if (gridLines.drawBorder) {
7914 // Draw the line at the edge of the axis
7915 context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0);
7916 context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0);
7917 var x1 = me.left;
7918 var x2 = me.right;
7919 var y1 = me.top;
7920 var y2 = me.bottom;
7921
7922 var aliasPixel = helpers.aliasPixel(context.lineWidth);
7923 if (isHorizontal) {
7924 y1 = y2 = options.position === 'top' ? me.bottom : me.top;
7925 y1 += aliasPixel;
7926 y2 += aliasPixel;
7927 } else {
7928 x1 = x2 = options.position === 'left' ? me.right : me.left;
7929 x1 += aliasPixel;
7930 x2 += aliasPixel;
7931 }
7932
7933 context.beginPath();
7934 context.moveTo(x1, y1);
7935 context.lineTo(x2, y2);
7936 context.stroke();
7937 }
7938 }
7939 });
7940};
7941
7942},{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){
7943'use strict';
7944
7945var defaults = require(25);
7946var helpers = require(45);
7947var layouts = require(30);
7948
7949module.exports = function(Chart) {
7950
7951 Chart.scaleService = {
7952 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
7953 // use the new chart options to grab the correct scale
7954 constructors: {},
7955 // Use a registration function so that we can move to an ES6 map when we no longer need to support
7956 // old browsers
7957
7958 // Scale config defaults
7959 defaults: {},
7960 registerScaleType: function(type, scaleConstructor, scaleDefaults) {
7961 this.constructors[type] = scaleConstructor;
7962 this.defaults[type] = helpers.clone(scaleDefaults);
7963 },
7964 getScaleConstructor: function(type) {
7965 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
7966 },
7967 getScaleDefaults: function(type) {
7968 // Return the scale defaults merged with the global settings so that we always use the latest ones
7969 return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {};
7970 },
7971 updateScaleDefaults: function(type, additions) {
7972 var me = this;
7973 if (me.defaults.hasOwnProperty(type)) {
7974 me.defaults[type] = helpers.extend(me.defaults[type], additions);
7975 }
7976 },
7977 addScalesToLayout: function(chart) {
7978 // Adds each scale to the chart.boxes array to be sized accordingly
7979 helpers.each(chart.scales, function(scale) {
7980 // Set ILayoutItem parameters for backwards compatibility
7981 scale.fullWidth = scale.options.fullWidth;
7982 scale.position = scale.options.position;
7983 scale.weight = scale.options.weight;
7984 layouts.addBox(chart, scale);
7985 });
7986 }
7987 };
7988};
7989
7990},{"25":25,"30":30,"45":45}],34:[function(require,module,exports){
7991'use strict';
7992
7993var helpers = require(45);
7994
7995/**
7996 * Namespace to hold static tick generation functions
7997 * @namespace Chart.Ticks
7998 */
7999module.exports = {
8000 /**
8001 * Namespace to hold formatters for different types of ticks
8002 * @namespace Chart.Ticks.formatters
8003 */
8004 formatters: {
8005 /**
8006 * Formatter for value labels
8007 * @method Chart.Ticks.formatters.values
8008 * @param value the value to display
8009 * @return {String|Array} the label to display
8010 */
8011 values: function(value) {
8012 return helpers.isArray(value) ? value : '' + value;
8013 },
8014
8015 /**
8016 * Formatter for linear numeric ticks
8017 * @method Chart.Ticks.formatters.linear
8018 * @param tickValue {Number} the value to be formatted
8019 * @param index {Number} the position of the tickValue parameter in the ticks array
8020 * @param ticks {Array<Number>} the list of ticks being converted
8021 * @return {String} string representation of the tickValue parameter
8022 */
8023 linear: function(tickValue, index, ticks) {
8024 // If we have lots of ticks, don't use the ones
8025 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
8026
8027 // If we have a number like 2.5 as the delta, figure out how many decimal places we need
8028 if (Math.abs(delta) > 1) {
8029 if (tickValue !== Math.floor(tickValue)) {
8030 // not an integer
8031 delta = tickValue - Math.floor(tickValue);
8032 }
8033 }
8034
8035 var logDelta = helpers.log10(Math.abs(delta));
8036 var tickString = '';
8037
8038 if (tickValue !== 0) {
8039 var numDecimal = -1 * Math.floor(logDelta);
8040 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
8041 tickString = tickValue.toFixed(numDecimal);
8042 } else {
8043 tickString = '0'; // never show decimal places for 0
8044 }
8045
8046 return tickString;
8047 },
8048
8049 logarithmic: function(tickValue, index, ticks) {
8050 var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));
8051
8052 if (tickValue === 0) {
8053 return '0';
8054 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
8055 return tickValue.toExponential();
8056 }
8057 return '';
8058 }
8059 }
8060};
8061
8062},{"45":45}],35:[function(require,module,exports){
8063'use strict';
8064
8065var defaults = require(25);
8066var Element = require(26);
8067var helpers = require(45);
8068
8069defaults._set('global', {
8070 tooltips: {
8071 enabled: true,
8072 custom: null,
8073 mode: 'nearest',
8074 position: 'average',
8075 intersect: true,
8076 backgroundColor: 'rgba(0,0,0,0.8)',
8077 titleFontStyle: 'bold',
8078 titleSpacing: 2,
8079 titleMarginBottom: 6,
8080 titleFontColor: '#fff',
8081 titleAlign: 'left',
8082 bodySpacing: 2,
8083 bodyFontColor: '#fff',
8084 bodyAlign: 'left',
8085 footerFontStyle: 'bold',
8086 footerSpacing: 2,
8087 footerMarginTop: 6,
8088 footerFontColor: '#fff',
8089 footerAlign: 'left',
8090 yPadding: 6,
8091 xPadding: 6,
8092 caretPadding: 2,
8093 caretSize: 5,
8094 cornerRadius: 6,
8095 multiKeyBackground: '#fff',
8096 displayColors: true,
8097 borderColor: 'rgba(0,0,0,0)',
8098 borderWidth: 0,
8099 callbacks: {
8100 // Args are: (tooltipItems, data)
8101 beforeTitle: helpers.noop,
8102 title: function(tooltipItems, data) {
8103 // Pick first xLabel for now
8104 var title = '';
8105 var labels = data.labels;
8106 var labelCount = labels ? labels.length : 0;
8107
8108 if (tooltipItems.length > 0) {
8109 var item = tooltipItems[0];
8110
8111 if (item.xLabel) {
8112 title = item.xLabel;
8113 } else if (labelCount > 0 && item.index < labelCount) {
8114 title = labels[item.index];
8115 }
8116 }
8117
8118 return title;
8119 },
8120 afterTitle: helpers.noop,
8121
8122 // Args are: (tooltipItems, data)
8123 beforeBody: helpers.noop,
8124
8125 // Args are: (tooltipItem, data)
8126 beforeLabel: helpers.noop,
8127 label: function(tooltipItem, data) {
8128 var label = data.datasets[tooltipItem.datasetIndex].label || '';
8129
8130 if (label) {
8131 label += ': ';
8132 }
8133 label += tooltipItem.yLabel;
8134 return label;
8135 },
8136 labelColor: function(tooltipItem, chart) {
8137 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
8138 var activeElement = meta.data[tooltipItem.index];
8139 var view = activeElement._view;
8140 return {
8141 borderColor: view.borderColor,
8142 backgroundColor: view.backgroundColor
8143 };
8144 },
8145 labelTextColor: function() {
8146 return this._options.bodyFontColor;
8147 },
8148 afterLabel: helpers.noop,
8149
8150 // Args are: (tooltipItems, data)
8151 afterBody: helpers.noop,
8152
8153 // Args are: (tooltipItems, data)
8154 beforeFooter: helpers.noop,
8155 footer: helpers.noop,
8156 afterFooter: helpers.noop
8157 }
8158 }
8159});
8160
8161module.exports = function(Chart) {
8162
8163 /**
8164 * Helper method to merge the opacity into a color
8165 */
8166 function mergeOpacity(colorString, opacity) {
8167 var color = helpers.color(colorString);
8168 return color.alpha(opacity * color.alpha()).rgbaString();
8169 }
8170
8171 // Helper to push or concat based on if the 2nd parameter is an array or not
8172 function pushOrConcat(base, toPush) {
8173 if (toPush) {
8174 if (helpers.isArray(toPush)) {
8175 // base = base.concat(toPush);
8176 Array.prototype.push.apply(base, toPush);
8177 } else {
8178 base.push(toPush);
8179 }
8180 }
8181
8182 return base;
8183 }
8184
8185 // Private helper to create a tooltip item model
8186 // @param element : the chart element (point, arc, bar) to create the tooltip item for
8187 // @return : new tooltip item
8188 function createTooltipItem(element) {
8189 var xScale = element._xScale;
8190 var yScale = element._yScale || element._scale; // handle radar || polarArea charts
8191 var index = element._index;
8192 var datasetIndex = element._datasetIndex;
8193
8194 return {
8195 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
8196 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
8197 index: index,
8198 datasetIndex: datasetIndex,
8199 x: element._model.x,
8200 y: element._model.y
8201 };
8202 }
8203
8204 /**
8205 * Helper to get the reset model for the tooltip
8206 * @param tooltipOpts {Object} the tooltip options
8207 */
8208 function getBaseModel(tooltipOpts) {
8209 var globalDefaults = defaults.global;
8210 var valueOrDefault = helpers.valueOrDefault;
8211
8212 return {
8213 // Positioning
8214 xPadding: tooltipOpts.xPadding,
8215 yPadding: tooltipOpts.yPadding,
8216 xAlign: tooltipOpts.xAlign,
8217 yAlign: tooltipOpts.yAlign,
8218
8219 // Body
8220 bodyFontColor: tooltipOpts.bodyFontColor,
8221 _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
8222 _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
8223 _bodyAlign: tooltipOpts.bodyAlign,
8224 bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
8225 bodySpacing: tooltipOpts.bodySpacing,
8226
8227 // Title
8228 titleFontColor: tooltipOpts.titleFontColor,
8229 _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
8230 _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
8231 titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
8232 _titleAlign: tooltipOpts.titleAlign,
8233 titleSpacing: tooltipOpts.titleSpacing,
8234 titleMarginBottom: tooltipOpts.titleMarginBottom,
8235
8236 // Footer
8237 footerFontColor: tooltipOpts.footerFontColor,
8238 _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
8239 _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
8240 footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
8241 _footerAlign: tooltipOpts.footerAlign,
8242 footerSpacing: tooltipOpts.footerSpacing,
8243 footerMarginTop: tooltipOpts.footerMarginTop,
8244
8245 // Appearance
8246 caretSize: tooltipOpts.caretSize,
8247 cornerRadius: tooltipOpts.cornerRadius,
8248 backgroundColor: tooltipOpts.backgroundColor,
8249 opacity: 0,
8250 legendColorBackground: tooltipOpts.multiKeyBackground,
8251 displayColors: tooltipOpts.displayColors,
8252 borderColor: tooltipOpts.borderColor,
8253 borderWidth: tooltipOpts.borderWidth
8254 };
8255 }
8256
8257 /**
8258 * Get the size of the tooltip
8259 */
8260 function getTooltipSize(tooltip, model) {
8261 var ctx = tooltip._chart.ctx;
8262
8263 var height = model.yPadding * 2; // Tooltip Padding
8264 var width = 0;
8265
8266 // Count of all lines in the body
8267 var body = model.body;
8268 var combinedBodyLength = body.reduce(function(count, bodyItem) {
8269 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
8270 }, 0);
8271 combinedBodyLength += model.beforeBody.length + model.afterBody.length;
8272
8273 var titleLineCount = model.title.length;
8274 var footerLineCount = model.footer.length;
8275 var titleFontSize = model.titleFontSize;
8276 var bodyFontSize = model.bodyFontSize;
8277 var footerFontSize = model.footerFontSize;
8278
8279 height += titleLineCount * titleFontSize; // Title Lines
8280 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
8281 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
8282 height += combinedBodyLength * bodyFontSize; // Body Lines
8283 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
8284 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
8285 height += footerLineCount * (footerFontSize); // Footer Lines
8286 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
8287
8288 // Title width
8289 var widthPadding = 0;
8290 var maxLineWidth = function(line) {
8291 width = Math.max(width, ctx.measureText(line).width + widthPadding);
8292 };
8293
8294 ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
8295 helpers.each(model.title, maxLineWidth);
8296
8297 // Body width
8298 ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
8299 helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
8300
8301 // Body lines may include some extra width due to the color box
8302 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
8303 helpers.each(body, function(bodyItem) {
8304 helpers.each(bodyItem.before, maxLineWidth);
8305 helpers.each(bodyItem.lines, maxLineWidth);
8306 helpers.each(bodyItem.after, maxLineWidth);
8307 });
8308
8309 // Reset back to 0
8310 widthPadding = 0;
8311
8312 // Footer width
8313 ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
8314 helpers.each(model.footer, maxLineWidth);
8315
8316 // Add padding
8317 width += 2 * model.xPadding;
8318
8319 return {
8320 width: width,
8321 height: height
8322 };
8323 }
8324
8325 /**
8326 * Helper to get the alignment of a tooltip given the size
8327 */
8328 function determineAlignment(tooltip, size) {
8329 var model = tooltip._model;
8330 var chart = tooltip._chart;
8331 var chartArea = tooltip._chart.chartArea;
8332 var xAlign = 'center';
8333 var yAlign = 'center';
8334
8335 if (model.y < size.height) {
8336 yAlign = 'top';
8337 } else if (model.y > (chart.height - size.height)) {
8338 yAlign = 'bottom';
8339 }
8340
8341 var lf, rf; // functions to determine left, right alignment
8342 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
8343 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
8344 var midX = (chartArea.left + chartArea.right) / 2;
8345 var midY = (chartArea.top + chartArea.bottom) / 2;
8346
8347 if (yAlign === 'center') {
8348 lf = function(x) {
8349 return x <= midX;
8350 };
8351 rf = function(x) {
8352 return x > midX;
8353 };
8354 } else {
8355 lf = function(x) {
8356 return x <= (size.width / 2);
8357 };
8358 rf = function(x) {
8359 return x >= (chart.width - (size.width / 2));
8360 };
8361 }
8362
8363 olf = function(x) {
8364 return x + size.width + model.caretSize + model.caretPadding > chart.width;
8365 };
8366 orf = function(x) {
8367 return x - size.width - model.caretSize - model.caretPadding < 0;
8368 };
8369 yf = function(y) {
8370 return y <= midY ? 'top' : 'bottom';
8371 };
8372
8373 if (lf(model.x)) {
8374 xAlign = 'left';
8375
8376 // Is tooltip too wide and goes over the right side of the chart.?
8377 if (olf(model.x)) {
8378 xAlign = 'center';
8379 yAlign = yf(model.y);
8380 }
8381 } else if (rf(model.x)) {
8382 xAlign = 'right';
8383
8384 // Is tooltip too wide and goes outside left edge of canvas?
8385 if (orf(model.x)) {
8386 xAlign = 'center';
8387 yAlign = yf(model.y);
8388 }
8389 }
8390
8391 var opts = tooltip._options;
8392 return {
8393 xAlign: opts.xAlign ? opts.xAlign : xAlign,
8394 yAlign: opts.yAlign ? opts.yAlign : yAlign
8395 };
8396 }
8397
8398 /**
8399 * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
8400 */
8401 function getBackgroundPoint(vm, size, alignment, chart) {
8402 // Background Position
8403 var x = vm.x;
8404 var y = vm.y;
8405
8406 var caretSize = vm.caretSize;
8407 var caretPadding = vm.caretPadding;
8408 var cornerRadius = vm.cornerRadius;
8409 var xAlign = alignment.xAlign;
8410 var yAlign = alignment.yAlign;
8411 var paddingAndSize = caretSize + caretPadding;
8412 var radiusAndPadding = cornerRadius + caretPadding;
8413
8414 if (xAlign === 'right') {
8415 x -= size.width;
8416 } else if (xAlign === 'center') {
8417 x -= (size.width / 2);
8418 if (x + size.width > chart.width) {
8419 x = chart.width - size.width;
8420 }
8421 if (x < 0) {
8422 x = 0;
8423 }
8424 }
8425
8426 if (yAlign === 'top') {
8427 y += paddingAndSize;
8428 } else if (yAlign === 'bottom') {
8429 y -= size.height + paddingAndSize;
8430 } else {
8431 y -= (size.height / 2);
8432 }
8433
8434 if (yAlign === 'center') {
8435 if (xAlign === 'left') {
8436 x += paddingAndSize;
8437 } else if (xAlign === 'right') {
8438 x -= paddingAndSize;
8439 }
8440 } else if (xAlign === 'left') {
8441 x -= radiusAndPadding;
8442 } else if (xAlign === 'right') {
8443 x += radiusAndPadding;
8444 }
8445
8446 return {
8447 x: x,
8448 y: y
8449 };
8450 }
8451
8452 Chart.Tooltip = Element.extend({
8453 initialize: function() {
8454 this._model = getBaseModel(this._options);
8455 this._lastActive = [];
8456 },
8457
8458 // Get the title
8459 // Args are: (tooltipItem, data)
8460 getTitle: function() {
8461 var me = this;
8462 var opts = me._options;
8463 var callbacks = opts.callbacks;
8464
8465 var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
8466 var title = callbacks.title.apply(me, arguments);
8467 var afterTitle = callbacks.afterTitle.apply(me, arguments);
8468
8469 var lines = [];
8470 lines = pushOrConcat(lines, beforeTitle);
8471 lines = pushOrConcat(lines, title);
8472 lines = pushOrConcat(lines, afterTitle);
8473
8474 return lines;
8475 },
8476
8477 // Args are: (tooltipItem, data)
8478 getBeforeBody: function() {
8479 var lines = this._options.callbacks.beforeBody.apply(this, arguments);
8480 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8481 },
8482
8483 // Args are: (tooltipItem, data)
8484 getBody: function(tooltipItems, data) {
8485 var me = this;
8486 var callbacks = me._options.callbacks;
8487 var bodyItems = [];
8488
8489 helpers.each(tooltipItems, function(tooltipItem) {
8490 var bodyItem = {
8491 before: [],
8492 lines: [],
8493 after: []
8494 };
8495 pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
8496 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
8497 pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
8498
8499 bodyItems.push(bodyItem);
8500 });
8501
8502 return bodyItems;
8503 },
8504
8505 // Args are: (tooltipItem, data)
8506 getAfterBody: function() {
8507 var lines = this._options.callbacks.afterBody.apply(this, arguments);
8508 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8509 },
8510
8511 // Get the footer and beforeFooter and afterFooter lines
8512 // Args are: (tooltipItem, data)
8513 getFooter: function() {
8514 var me = this;
8515 var callbacks = me._options.callbacks;
8516
8517 var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
8518 var footer = callbacks.footer.apply(me, arguments);
8519 var afterFooter = callbacks.afterFooter.apply(me, arguments);
8520
8521 var lines = [];
8522 lines = pushOrConcat(lines, beforeFooter);
8523 lines = pushOrConcat(lines, footer);
8524 lines = pushOrConcat(lines, afterFooter);
8525
8526 return lines;
8527 },
8528
8529 update: function(changed) {
8530 var me = this;
8531 var opts = me._options;
8532
8533 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
8534 // 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
8535 // which breaks any animations.
8536 var existingModel = me._model;
8537 var model = me._model = getBaseModel(opts);
8538 var active = me._active;
8539
8540 var data = me._data;
8541
8542 // In the case where active.length === 0 we need to keep these at existing values for good animations
8543 var alignment = {
8544 xAlign: existingModel.xAlign,
8545 yAlign: existingModel.yAlign
8546 };
8547 var backgroundPoint = {
8548 x: existingModel.x,
8549 y: existingModel.y
8550 };
8551 var tooltipSize = {
8552 width: existingModel.width,
8553 height: existingModel.height
8554 };
8555 var tooltipPosition = {
8556 x: existingModel.caretX,
8557 y: existingModel.caretY
8558 };
8559
8560 var i, len;
8561
8562 if (active.length) {
8563 model.opacity = 1;
8564
8565 var labelColors = [];
8566 var labelTextColors = [];
8567 tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition);
8568
8569 var tooltipItems = [];
8570 for (i = 0, len = active.length; i < len; ++i) {
8571 tooltipItems.push(createTooltipItem(active[i]));
8572 }
8573
8574 // If the user provided a filter function, use it to modify the tooltip items
8575 if (opts.filter) {
8576 tooltipItems = tooltipItems.filter(function(a) {
8577 return opts.filter(a, data);
8578 });
8579 }
8580
8581 // If the user provided a sorting function, use it to modify the tooltip items
8582 if (opts.itemSort) {
8583 tooltipItems = tooltipItems.sort(function(a, b) {
8584 return opts.itemSort(a, b, data);
8585 });
8586 }
8587
8588 // Determine colors for boxes
8589 helpers.each(tooltipItems, function(tooltipItem) {
8590 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
8591 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
8592 });
8593
8594
8595 // Build the Text Lines
8596 model.title = me.getTitle(tooltipItems, data);
8597 model.beforeBody = me.getBeforeBody(tooltipItems, data);
8598 model.body = me.getBody(tooltipItems, data);
8599 model.afterBody = me.getAfterBody(tooltipItems, data);
8600 model.footer = me.getFooter(tooltipItems, data);
8601
8602 // Initial positioning and colors
8603 model.x = Math.round(tooltipPosition.x);
8604 model.y = Math.round(tooltipPosition.y);
8605 model.caretPadding = opts.caretPadding;
8606 model.labelColors = labelColors;
8607 model.labelTextColors = labelTextColors;
8608
8609 // data points
8610 model.dataPoints = tooltipItems;
8611
8612 // We need to determine alignment of the tooltip
8613 tooltipSize = getTooltipSize(this, model);
8614 alignment = determineAlignment(this, tooltipSize);
8615 // Final Size and Position
8616 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
8617 } else {
8618 model.opacity = 0;
8619 }
8620
8621 model.xAlign = alignment.xAlign;
8622 model.yAlign = alignment.yAlign;
8623 model.x = backgroundPoint.x;
8624 model.y = backgroundPoint.y;
8625 model.width = tooltipSize.width;
8626 model.height = tooltipSize.height;
8627
8628 // Point where the caret on the tooltip points to
8629 model.caretX = tooltipPosition.x;
8630 model.caretY = tooltipPosition.y;
8631
8632 me._model = model;
8633
8634 if (changed && opts.custom) {
8635 opts.custom.call(me, model);
8636 }
8637
8638 return me;
8639 },
8640 drawCaret: function(tooltipPoint, size) {
8641 var ctx = this._chart.ctx;
8642 var vm = this._view;
8643 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
8644
8645 ctx.lineTo(caretPosition.x1, caretPosition.y1);
8646 ctx.lineTo(caretPosition.x2, caretPosition.y2);
8647 ctx.lineTo(caretPosition.x3, caretPosition.y3);
8648 },
8649 getCaretPosition: function(tooltipPoint, size, vm) {
8650 var x1, x2, x3, y1, y2, y3;
8651 var caretSize = vm.caretSize;
8652 var cornerRadius = vm.cornerRadius;
8653 var xAlign = vm.xAlign;
8654 var yAlign = vm.yAlign;
8655 var ptX = tooltipPoint.x;
8656 var ptY = tooltipPoint.y;
8657 var width = size.width;
8658 var height = size.height;
8659
8660 if (yAlign === 'center') {
8661 y2 = ptY + (height / 2);
8662
8663 if (xAlign === 'left') {
8664 x1 = ptX;
8665 x2 = x1 - caretSize;
8666 x3 = x1;
8667
8668 y1 = y2 + caretSize;
8669 y3 = y2 - caretSize;
8670 } else {
8671 x1 = ptX + width;
8672 x2 = x1 + caretSize;
8673 x3 = x1;
8674
8675 y1 = y2 - caretSize;
8676 y3 = y2 + caretSize;
8677 }
8678 } else {
8679 if (xAlign === 'left') {
8680 x2 = ptX + cornerRadius + (caretSize);
8681 x1 = x2 - caretSize;
8682 x3 = x2 + caretSize;
8683 } else if (xAlign === 'right') {
8684 x2 = ptX + width - cornerRadius - caretSize;
8685 x1 = x2 - caretSize;
8686 x3 = x2 + caretSize;
8687 } else {
8688 x2 = vm.caretX;
8689 x1 = x2 - caretSize;
8690 x3 = x2 + caretSize;
8691 }
8692 if (yAlign === 'top') {
8693 y1 = ptY;
8694 y2 = y1 - caretSize;
8695 y3 = y1;
8696 } else {
8697 y1 = ptY + height;
8698 y2 = y1 + caretSize;
8699 y3 = y1;
8700 // invert drawing order
8701 var tmp = x3;
8702 x3 = x1;
8703 x1 = tmp;
8704 }
8705 }
8706 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
8707 },
8708 drawTitle: function(pt, vm, ctx, opacity) {
8709 var title = vm.title;
8710
8711 if (title.length) {
8712 ctx.textAlign = vm._titleAlign;
8713 ctx.textBaseline = 'top';
8714
8715 var titleFontSize = vm.titleFontSize;
8716 var titleSpacing = vm.titleSpacing;
8717
8718 ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
8719 ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8720
8721 var i, len;
8722 for (i = 0, len = title.length; i < len; ++i) {
8723 ctx.fillText(title[i], pt.x, pt.y);
8724 pt.y += titleFontSize + titleSpacing; // Line Height and spacing
8725
8726 if (i + 1 === title.length) {
8727 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
8728 }
8729 }
8730 }
8731 },
8732 drawBody: function(pt, vm, ctx, opacity) {
8733 var bodyFontSize = vm.bodyFontSize;
8734 var bodySpacing = vm.bodySpacing;
8735 var body = vm.body;
8736
8737 ctx.textAlign = vm._bodyAlign;
8738 ctx.textBaseline = 'top';
8739 ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8740
8741 // Before Body
8742 var xLinePadding = 0;
8743 var fillLineOfText = function(line) {
8744 ctx.fillText(line, pt.x + xLinePadding, pt.y);
8745 pt.y += bodyFontSize + bodySpacing;
8746 };
8747
8748 // Before body lines
8749 ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
8750 helpers.each(vm.beforeBody, fillLineOfText);
8751
8752 var drawColorBoxes = vm.displayColors;
8753 xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
8754
8755 // Draw body lines now
8756 helpers.each(body, function(bodyItem, i) {
8757 var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
8758 ctx.fillStyle = textColor;
8759 helpers.each(bodyItem.before, fillLineOfText);
8760
8761 helpers.each(bodyItem.lines, function(line) {
8762 // Draw Legend-like boxes if needed
8763 if (drawColorBoxes) {
8764 // Fill a white rect so that colours merge nicely if the opacity is < 1
8765 ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
8766 ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8767
8768 // Border
8769 ctx.lineWidth = 1;
8770 ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
8771 ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8772
8773 // Inner square
8774 ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
8775 ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
8776 ctx.fillStyle = textColor;
8777 }
8778
8779 fillLineOfText(line);
8780 });
8781
8782 helpers.each(bodyItem.after, fillLineOfText);
8783 });
8784
8785 // Reset back to 0 for after body
8786 xLinePadding = 0;
8787
8788 // After body lines
8789 helpers.each(vm.afterBody, fillLineOfText);
8790 pt.y -= bodySpacing; // Remove last body spacing
8791 },
8792 drawFooter: function(pt, vm, ctx, opacity) {
8793 var footer = vm.footer;
8794
8795 if (footer.length) {
8796 pt.y += vm.footerMarginTop;
8797
8798 ctx.textAlign = vm._footerAlign;
8799 ctx.textBaseline = 'top';
8800
8801 ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
8802 ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8803
8804 helpers.each(footer, function(line) {
8805 ctx.fillText(line, pt.x, pt.y);
8806 pt.y += vm.footerFontSize + vm.footerSpacing;
8807 });
8808 }
8809 },
8810 drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
8811 ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
8812 ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
8813 ctx.lineWidth = vm.borderWidth;
8814 var xAlign = vm.xAlign;
8815 var yAlign = vm.yAlign;
8816 var x = pt.x;
8817 var y = pt.y;
8818 var width = tooltipSize.width;
8819 var height = tooltipSize.height;
8820 var radius = vm.cornerRadius;
8821
8822 ctx.beginPath();
8823 ctx.moveTo(x + radius, y);
8824 if (yAlign === 'top') {
8825 this.drawCaret(pt, tooltipSize);
8826 }
8827 ctx.lineTo(x + width - radius, y);
8828 ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
8829 if (yAlign === 'center' && xAlign === 'right') {
8830 this.drawCaret(pt, tooltipSize);
8831 }
8832 ctx.lineTo(x + width, y + height - radius);
8833 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
8834 if (yAlign === 'bottom') {
8835 this.drawCaret(pt, tooltipSize);
8836 }
8837 ctx.lineTo(x + radius, y + height);
8838 ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
8839 if (yAlign === 'center' && xAlign === 'left') {
8840 this.drawCaret(pt, tooltipSize);
8841 }
8842 ctx.lineTo(x, y + radius);
8843 ctx.quadraticCurveTo(x, y, x + radius, y);
8844 ctx.closePath();
8845
8846 ctx.fill();
8847
8848 if (vm.borderWidth > 0) {
8849 ctx.stroke();
8850 }
8851 },
8852 draw: function() {
8853 var ctx = this._chart.ctx;
8854 var vm = this._view;
8855
8856 if (vm.opacity === 0) {
8857 return;
8858 }
8859
8860 var tooltipSize = {
8861 width: vm.width,
8862 height: vm.height
8863 };
8864 var pt = {
8865 x: vm.x,
8866 y: vm.y
8867 };
8868
8869 // IE11/Edge does not like very small opacities, so snap to 0
8870 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
8871
8872 // Truthy/falsey value for empty tooltip
8873 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
8874
8875 if (this._options.enabled && hasTooltipContent) {
8876 // Draw Background
8877 this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
8878
8879 // Draw Title, Body, and Footer
8880 pt.x += vm.xPadding;
8881 pt.y += vm.yPadding;
8882
8883 // Titles
8884 this.drawTitle(pt, vm, ctx, opacity);
8885
8886 // Body
8887 this.drawBody(pt, vm, ctx, opacity);
8888
8889 // Footer
8890 this.drawFooter(pt, vm, ctx, opacity);
8891 }
8892 },
8893
8894 /**
8895 * Handle an event
8896 * @private
8897 * @param {IEvent} event - The event to handle
8898 * @returns {Boolean} true if the tooltip changed
8899 */
8900 handleEvent: function(e) {
8901 var me = this;
8902 var options = me._options;
8903 var changed = false;
8904
8905 me._lastActive = me._lastActive || [];
8906
8907 // Find Active Elements for tooltips
8908 if (e.type === 'mouseout') {
8909 me._active = [];
8910 } else {
8911 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
8912 }
8913
8914 // Remember Last Actives
8915 changed = !helpers.arrayEquals(me._active, me._lastActive);
8916
8917 // Only handle target event on tooltip change
8918 if (changed) {
8919 me._lastActive = me._active;
8920
8921 if (options.enabled || options.custom) {
8922 me._eventPosition = {
8923 x: e.x,
8924 y: e.y
8925 };
8926
8927 me.update(true);
8928 me.pivot();
8929 }
8930 }
8931
8932 return changed;
8933 }
8934 });
8935
8936 /**
8937 * @namespace Chart.Tooltip.positioners
8938 */
8939 Chart.Tooltip.positioners = {
8940 /**
8941 * Average mode places the tooltip at the average position of the elements shown
8942 * @function Chart.Tooltip.positioners.average
8943 * @param elements {ChartElement[]} the elements being displayed in the tooltip
8944 * @returns {Point} tooltip position
8945 */
8946 average: function(elements) {
8947 if (!elements.length) {
8948 return false;
8949 }
8950
8951 var i, len;
8952 var x = 0;
8953 var y = 0;
8954 var count = 0;
8955
8956 for (i = 0, len = elements.length; i < len; ++i) {
8957 var el = elements[i];
8958 if (el && el.hasValue()) {
8959 var pos = el.tooltipPosition();
8960 x += pos.x;
8961 y += pos.y;
8962 ++count;
8963 }
8964 }
8965
8966 return {
8967 x: Math.round(x / count),
8968 y: Math.round(y / count)
8969 };
8970 },
8971
8972 /**
8973 * Gets the tooltip position nearest of the item nearest to the event position
8974 * @function Chart.Tooltip.positioners.nearest
8975 * @param elements {Chart.Element[]} the tooltip elements
8976 * @param eventPosition {Point} the position of the event in canvas coordinates
8977 * @returns {Point} the tooltip position
8978 */
8979 nearest: function(elements, eventPosition) {
8980 var x = eventPosition.x;
8981 var y = eventPosition.y;
8982 var minDistance = Number.POSITIVE_INFINITY;
8983 var i, len, nearestElement;
8984
8985 for (i = 0, len = elements.length; i < len; ++i) {
8986 var el = elements[i];
8987 if (el && el.hasValue()) {
8988 var center = el.getCenterPoint();
8989 var d = helpers.distanceBetweenPoints(eventPosition, center);
8990
8991 if (d < minDistance) {
8992 minDistance = d;
8993 nearestElement = el;
8994 }
8995 }
8996 }
8997
8998 if (nearestElement) {
8999 var tp = nearestElement.tooltipPosition();
9000 x = tp.x;
9001 y = tp.y;
9002 }
9003
9004 return {
9005 x: x,
9006 y: y
9007 };
9008 }
9009 };
9010};
9011
9012},{"25":25,"26":26,"45":45}],36:[function(require,module,exports){
9013'use strict';
9014
9015var defaults = require(25);
9016var Element = require(26);
9017var helpers = require(45);
9018
9019defaults._set('global', {
9020 elements: {
9021 arc: {
9022 backgroundColor: defaults.global.defaultColor,
9023 borderColor: '#fff',
9024 borderWidth: 2
9025 }
9026 }
9027});
9028
9029module.exports = Element.extend({
9030 inLabelRange: function(mouseX) {
9031 var vm = this._view;
9032
9033 if (vm) {
9034 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
9035 }
9036 return false;
9037 },
9038
9039 inRange: function(chartX, chartY) {
9040 var vm = this._view;
9041
9042 if (vm) {
9043 var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
9044 var angle = pointRelativePosition.angle;
9045 var distance = pointRelativePosition.distance;
9046
9047 // Sanitise angle range
9048 var startAngle = vm.startAngle;
9049 var endAngle = vm.endAngle;
9050 while (endAngle < startAngle) {
9051 endAngle += 2.0 * Math.PI;
9052 }
9053 while (angle > endAngle) {
9054 angle -= 2.0 * Math.PI;
9055 }
9056 while (angle < startAngle) {
9057 angle += 2.0 * Math.PI;
9058 }
9059
9060 // Check if within the range of the open/close angle
9061 var betweenAngles = (angle >= startAngle && angle <= endAngle);
9062 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
9063
9064 return (betweenAngles && withinRadius);
9065 }
9066 return false;
9067 },
9068
9069 getCenterPoint: function() {
9070 var vm = this._view;
9071 var halfAngle = (vm.startAngle + vm.endAngle) / 2;
9072 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
9073 return {
9074 x: vm.x + Math.cos(halfAngle) * halfRadius,
9075 y: vm.y + Math.sin(halfAngle) * halfRadius
9076 };
9077 },
9078
9079 getArea: function() {
9080 var vm = this._view;
9081 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
9082 },
9083
9084 tooltipPosition: function() {
9085 var vm = this._view;
9086 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
9087 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
9088
9089 return {
9090 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
9091 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
9092 };
9093 },
9094
9095 draw: function() {
9096 var ctx = this._chart.ctx;
9097 var vm = this._view;
9098 var sA = vm.startAngle;
9099 var eA = vm.endAngle;
9100
9101 ctx.beginPath();
9102
9103 ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
9104 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
9105
9106 ctx.closePath();
9107 ctx.strokeStyle = vm.borderColor;
9108 ctx.lineWidth = vm.borderWidth;
9109
9110 ctx.fillStyle = vm.backgroundColor;
9111
9112 ctx.fill();
9113 ctx.lineJoin = 'bevel';
9114
9115 if (vm.borderWidth) {
9116 ctx.stroke();
9117 }
9118 }
9119});
9120
9121},{"25":25,"26":26,"45":45}],37:[function(require,module,exports){
9122'use strict';
9123
9124var defaults = require(25);
9125var Element = require(26);
9126var helpers = require(45);
9127
9128var globalDefaults = defaults.global;
9129
9130defaults._set('global', {
9131 elements: {
9132 line: {
9133 tension: 0.4,
9134 backgroundColor: globalDefaults.defaultColor,
9135 borderWidth: 3,
9136 borderColor: globalDefaults.defaultColor,
9137 borderCapStyle: 'butt',
9138 borderDash: [],
9139 borderDashOffset: 0.0,
9140 borderJoinStyle: 'miter',
9141 capBezierPoints: true,
9142 fill: true, // do we fill in the area between the line and its base axis
9143 }
9144 }
9145});
9146
9147module.exports = Element.extend({
9148 draw: function() {
9149 var me = this;
9150 var vm = me._view;
9151 var ctx = me._chart.ctx;
9152 var spanGaps = vm.spanGaps;
9153 var points = me._children.slice(); // clone array
9154 var globalOptionLineElements = globalDefaults.elements.line;
9155 var lastDrawnIndex = -1;
9156 var index, current, previous, currentVM;
9157
9158 // If we are looping, adding the first point again
9159 if (me._loop && points.length) {
9160 points.push(points[0]);
9161 }
9162
9163 ctx.save();
9164
9165 // Stroke Line Options
9166 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
9167
9168 // IE 9 and 10 do not support line dash
9169 if (ctx.setLineDash) {
9170 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
9171 }
9172
9173 ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
9174 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
9175 ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
9176 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
9177
9178 // Stroke Line
9179 ctx.beginPath();
9180 lastDrawnIndex = -1;
9181
9182 for (index = 0; index < points.length; ++index) {
9183 current = points[index];
9184 previous = helpers.previousItem(points, index);
9185 currentVM = current._view;
9186
9187 // First point moves to it's starting position no matter what
9188 if (index === 0) {
9189 if (!currentVM.skip) {
9190 ctx.moveTo(currentVM.x, currentVM.y);
9191 lastDrawnIndex = index;
9192 }
9193 } else {
9194 previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
9195
9196 if (!currentVM.skip) {
9197 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
9198 // There was a gap and this is the first point after the gap
9199 ctx.moveTo(currentVM.x, currentVM.y);
9200 } else {
9201 // Line to next point
9202 helpers.canvas.lineTo(ctx, previous._view, current._view);
9203 }
9204 lastDrawnIndex = index;
9205 }
9206 }
9207 }
9208
9209 ctx.stroke();
9210 ctx.restore();
9211 }
9212});
9213
9214},{"25":25,"26":26,"45":45}],38:[function(require,module,exports){
9215'use strict';
9216
9217var defaults = require(25);
9218var Element = require(26);
9219var helpers = require(45);
9220
9221var defaultColor = defaults.global.defaultColor;
9222
9223defaults._set('global', {
9224 elements: {
9225 point: {
9226 radius: 3,
9227 pointStyle: 'circle',
9228 backgroundColor: defaultColor,
9229 borderColor: defaultColor,
9230 borderWidth: 1,
9231 // Hover
9232 hitRadius: 1,
9233 hoverRadius: 4,
9234 hoverBorderWidth: 1
9235 }
9236 }
9237});
9238
9239function xRange(mouseX) {
9240 var vm = this._view;
9241 return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false;
9242}
9243
9244function yRange(mouseY) {
9245 var vm = this._view;
9246 return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false;
9247}
9248
9249module.exports = Element.extend({
9250 inRange: function(mouseX, mouseY) {
9251 var vm = this._view;
9252 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
9253 },
9254
9255 inLabelRange: xRange,
9256 inXRange: xRange,
9257 inYRange: yRange,
9258
9259 getCenterPoint: function() {
9260 var vm = this._view;
9261 return {
9262 x: vm.x,
9263 y: vm.y
9264 };
9265 },
9266
9267 getArea: function() {
9268 return Math.PI * Math.pow(this._view.radius, 2);
9269 },
9270
9271 tooltipPosition: function() {
9272 var vm = this._view;
9273 return {
9274 x: vm.x,
9275 y: vm.y,
9276 padding: vm.radius + vm.borderWidth
9277 };
9278 },
9279
9280 draw: function(chartArea) {
9281 var vm = this._view;
9282 var model = this._model;
9283 var ctx = this._chart.ctx;
9284 var pointStyle = vm.pointStyle;
9285 var radius = vm.radius;
9286 var x = vm.x;
9287 var y = vm.y;
9288 var color = helpers.color;
9289 var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
9290 var ratio = 0;
9291
9292 if (vm.skip) {
9293 return;
9294 }
9295
9296 ctx.strokeStyle = vm.borderColor || defaultColor;
9297 ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth);
9298 ctx.fillStyle = vm.backgroundColor || defaultColor;
9299
9300 // Cliping for Points.
9301 // going out from inner charArea?
9302 if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) {
9303 // Point fade out
9304 if (model.x < chartArea.left) {
9305 ratio = (x - model.x) / (chartArea.left - model.x);
9306 } else if (chartArea.right * errMargin < model.x) {
9307 ratio = (model.x - x) / (model.x - chartArea.right);
9308 } else if (model.y < chartArea.top) {
9309 ratio = (y - model.y) / (chartArea.top - model.y);
9310 } else if (chartArea.bottom * errMargin < model.y) {
9311 ratio = (model.y - y) / (model.y - chartArea.bottom);
9312 }
9313 ratio = Math.round(ratio * 100) / 100;
9314 ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();
9315 ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
9316 }
9317
9318 helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y);
9319 }
9320});
9321
9322},{"25":25,"26":26,"45":45}],39:[function(require,module,exports){
9323'use strict';
9324
9325var defaults = require(25);
9326var Element = require(26);
9327
9328defaults._set('global', {
9329 elements: {
9330 rectangle: {
9331 backgroundColor: defaults.global.defaultColor,
9332 borderColor: defaults.global.defaultColor,
9333 borderSkipped: 'bottom',
9334 borderWidth: 0
9335 }
9336 }
9337});
9338
9339function isVertical(bar) {
9340 return bar._view.width !== undefined;
9341}
9342
9343/**
9344 * Helper function to get the bounds of the bar regardless of the orientation
9345 * @param bar {Chart.Element.Rectangle} the bar
9346 * @return {Bounds} bounds of the bar
9347 * @private
9348 */
9349function getBarBounds(bar) {
9350 var vm = bar._view;
9351 var x1, x2, y1, y2;
9352
9353 if (isVertical(bar)) {
9354 // vertical
9355 var halfWidth = vm.width / 2;
9356 x1 = vm.x - halfWidth;
9357 x2 = vm.x + halfWidth;
9358 y1 = Math.min(vm.y, vm.base);
9359 y2 = Math.max(vm.y, vm.base);
9360 } else {
9361 // horizontal bar
9362 var halfHeight = vm.height / 2;
9363 x1 = Math.min(vm.x, vm.base);
9364 x2 = Math.max(vm.x, vm.base);
9365 y1 = vm.y - halfHeight;
9366 y2 = vm.y + halfHeight;
9367 }
9368
9369 return {
9370 left: x1,
9371 top: y1,
9372 right: x2,
9373 bottom: y2
9374 };
9375}
9376
9377module.exports = Element.extend({
9378 draw: function() {
9379 var ctx = this._chart.ctx;
9380 var vm = this._view;
9381 var left, right, top, bottom, signX, signY, borderSkipped;
9382 var borderWidth = vm.borderWidth;
9383
9384 if (!vm.horizontal) {
9385 // bar
9386 left = vm.x - vm.width / 2;
9387 right = vm.x + vm.width / 2;
9388 top = vm.y;
9389 bottom = vm.base;
9390 signX = 1;
9391 signY = bottom > top ? 1 : -1;
9392 borderSkipped = vm.borderSkipped || 'bottom';
9393 } else {
9394 // horizontal bar
9395 left = vm.base;
9396 right = vm.x;
9397 top = vm.y - vm.height / 2;
9398 bottom = vm.y + vm.height / 2;
9399 signX = right > left ? 1 : -1;
9400 signY = 1;
9401 borderSkipped = vm.borderSkipped || 'left';
9402 }
9403
9404 // Canvas doesn't allow us to stroke inside the width so we can
9405 // adjust the sizes to fit if we're setting a stroke on the line
9406 if (borderWidth) {
9407 // borderWidth shold be less than bar width and bar height.
9408 var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
9409 borderWidth = borderWidth > barSize ? barSize : borderWidth;
9410 var halfStroke = borderWidth / 2;
9411 // Adjust borderWidth when bar top position is near vm.base(zero).
9412 var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
9413 var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
9414 var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
9415 var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
9416 // not become a vertical line?
9417 if (borderLeft !== borderRight) {
9418 top = borderTop;
9419 bottom = borderBottom;
9420 }
9421 // not become a horizontal line?
9422 if (borderTop !== borderBottom) {
9423 left = borderLeft;
9424 right = borderRight;
9425 }
9426 }
9427
9428 ctx.beginPath();
9429 ctx.fillStyle = vm.backgroundColor;
9430 ctx.strokeStyle = vm.borderColor;
9431 ctx.lineWidth = borderWidth;
9432
9433 // Corner points, from bottom-left to bottom-right clockwise
9434 // | 1 2 |
9435 // | 0 3 |
9436 var corners = [
9437 [left, bottom],
9438 [left, top],
9439 [right, top],
9440 [right, bottom]
9441 ];
9442
9443 // Find first (starting) corner with fallback to 'bottom'
9444 var borders = ['bottom', 'left', 'top', 'right'];
9445 var startCorner = borders.indexOf(borderSkipped, 0);
9446 if (startCorner === -1) {
9447 startCorner = 0;
9448 }
9449
9450 function cornerAt(index) {
9451 return corners[(startCorner + index) % 4];
9452 }
9453
9454 // Draw rectangle from 'startCorner'
9455 var corner = cornerAt(0);
9456 ctx.moveTo(corner[0], corner[1]);
9457
9458 for (var i = 1; i < 4; i++) {
9459 corner = cornerAt(i);
9460 ctx.lineTo(corner[0], corner[1]);
9461 }
9462
9463 ctx.fill();
9464 if (borderWidth) {
9465 ctx.stroke();
9466 }
9467 },
9468
9469 height: function() {
9470 var vm = this._view;
9471 return vm.base - vm.y;
9472 },
9473
9474 inRange: function(mouseX, mouseY) {
9475 var inRange = false;
9476
9477 if (this._view) {
9478 var bounds = getBarBounds(this);
9479 inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
9480 }
9481
9482 return inRange;
9483 },
9484
9485 inLabelRange: function(mouseX, mouseY) {
9486 var me = this;
9487 if (!me._view) {
9488 return false;
9489 }
9490
9491 var inRange = false;
9492 var bounds = getBarBounds(me);
9493
9494 if (isVertical(me)) {
9495 inRange = mouseX >= bounds.left && mouseX <= bounds.right;
9496 } else {
9497 inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
9498 }
9499
9500 return inRange;
9501 },
9502
9503 inXRange: function(mouseX) {
9504 var bounds = getBarBounds(this);
9505 return mouseX >= bounds.left && mouseX <= bounds.right;
9506 },
9507
9508 inYRange: function(mouseY) {
9509 var bounds = getBarBounds(this);
9510 return mouseY >= bounds.top && mouseY <= bounds.bottom;
9511 },
9512
9513 getCenterPoint: function() {
9514 var vm = this._view;
9515 var x, y;
9516 if (isVertical(this)) {
9517 x = vm.x;
9518 y = (vm.y + vm.base) / 2;
9519 } else {
9520 x = (vm.x + vm.base) / 2;
9521 y = vm.y;
9522 }
9523
9524 return {x: x, y: y};
9525 },
9526
9527 getArea: function() {
9528 var vm = this._view;
9529 return vm.width * Math.abs(vm.y - vm.base);
9530 },
9531
9532 tooltipPosition: function() {
9533 var vm = this._view;
9534 return {
9535 x: vm.x,
9536 y: vm.y
9537 };
9538 }
9539});
9540
9541},{"25":25,"26":26}],40:[function(require,module,exports){
9542'use strict';
9543
9544module.exports = {};
9545module.exports.Arc = require(36);
9546module.exports.Line = require(37);
9547module.exports.Point = require(38);
9548module.exports.Rectangle = require(39);
9549
9550},{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){
9551'use strict';
9552
9553var helpers = require(42);
9554
9555/**
9556 * @namespace Chart.helpers.canvas
9557 */
9558var exports = module.exports = {
9559 /**
9560 * Clears the entire canvas associated to the given `chart`.
9561 * @param {Chart} chart - The chart for which to clear the canvas.
9562 */
9563 clear: function(chart) {
9564 chart.ctx.clearRect(0, 0, chart.width, chart.height);
9565 },
9566
9567 /**
9568 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
9569 * given size (width, height) and the same `radius` for all corners.
9570 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
9571 * @param {Number} x - The x axis of the coordinate for the rectangle starting point.
9572 * @param {Number} y - The y axis of the coordinate for the rectangle starting point.
9573 * @param {Number} width - The rectangle's width.
9574 * @param {Number} height - The rectangle's height.
9575 * @param {Number} radius - The rounded amount (in pixels) for the four corners.
9576 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
9577 */
9578 roundedRect: function(ctx, x, y, width, height, radius) {
9579 if (radius) {
9580 var rx = Math.min(radius, width / 2);
9581 var ry = Math.min(radius, height / 2);
9582
9583 ctx.moveTo(x + rx, y);
9584 ctx.lineTo(x + width - rx, y);
9585 ctx.quadraticCurveTo(x + width, y, x + width, y + ry);
9586 ctx.lineTo(x + width, y + height - ry);
9587 ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
9588 ctx.lineTo(x + rx, y + height);
9589 ctx.quadraticCurveTo(x, y + height, x, y + height - ry);
9590 ctx.lineTo(x, y + ry);
9591 ctx.quadraticCurveTo(x, y, x + rx, y);
9592 } else {
9593 ctx.rect(x, y, width, height);
9594 }
9595 },
9596
9597 drawPoint: function(ctx, style, radius, x, y) {
9598 var type, edgeLength, xOffset, yOffset, height, size;
9599
9600 if (style && typeof style === 'object') {
9601 type = style.toString();
9602 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
9603 ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
9604 return;
9605 }
9606 }
9607
9608 if (isNaN(radius) || radius <= 0) {
9609 return;
9610 }
9611
9612 switch (style) {
9613 // Default includes circle
9614 default:
9615 ctx.beginPath();
9616 ctx.arc(x, y, radius, 0, Math.PI * 2);
9617 ctx.closePath();
9618 ctx.fill();
9619 break;
9620 case 'triangle':
9621 ctx.beginPath();
9622 edgeLength = 3 * radius / Math.sqrt(3);
9623 height = edgeLength * Math.sqrt(3) / 2;
9624 ctx.moveTo(x - edgeLength / 2, y + height / 3);
9625 ctx.lineTo(x + edgeLength / 2, y + height / 3);
9626 ctx.lineTo(x, y - 2 * height / 3);
9627 ctx.closePath();
9628 ctx.fill();
9629 break;
9630 case 'rect':
9631 size = 1 / Math.SQRT2 * radius;
9632 ctx.beginPath();
9633 ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
9634 ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
9635 break;
9636 case 'rectRounded':
9637 var offset = radius / Math.SQRT2;
9638 var leftX = x - offset;
9639 var topY = y - offset;
9640 var sideSize = Math.SQRT2 * radius;
9641 ctx.beginPath();
9642 this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2);
9643 ctx.closePath();
9644 ctx.fill();
9645 break;
9646 case 'rectRot':
9647 size = 1 / Math.SQRT2 * radius;
9648 ctx.beginPath();
9649 ctx.moveTo(x - size, y);
9650 ctx.lineTo(x, y + size);
9651 ctx.lineTo(x + size, y);
9652 ctx.lineTo(x, y - size);
9653 ctx.closePath();
9654 ctx.fill();
9655 break;
9656 case 'cross':
9657 ctx.beginPath();
9658 ctx.moveTo(x, y + radius);
9659 ctx.lineTo(x, y - radius);
9660 ctx.moveTo(x - radius, y);
9661 ctx.lineTo(x + radius, y);
9662 ctx.closePath();
9663 break;
9664 case 'crossRot':
9665 ctx.beginPath();
9666 xOffset = Math.cos(Math.PI / 4) * radius;
9667 yOffset = Math.sin(Math.PI / 4) * radius;
9668 ctx.moveTo(x - xOffset, y - yOffset);
9669 ctx.lineTo(x + xOffset, y + yOffset);
9670 ctx.moveTo(x - xOffset, y + yOffset);
9671 ctx.lineTo(x + xOffset, y - yOffset);
9672 ctx.closePath();
9673 break;
9674 case 'star':
9675 ctx.beginPath();
9676 ctx.moveTo(x, y + radius);
9677 ctx.lineTo(x, y - radius);
9678 ctx.moveTo(x - radius, y);
9679 ctx.lineTo(x + radius, y);
9680 xOffset = Math.cos(Math.PI / 4) * radius;
9681 yOffset = Math.sin(Math.PI / 4) * radius;
9682 ctx.moveTo(x - xOffset, y - yOffset);
9683 ctx.lineTo(x + xOffset, y + yOffset);
9684 ctx.moveTo(x - xOffset, y + yOffset);
9685 ctx.lineTo(x + xOffset, y - yOffset);
9686 ctx.closePath();
9687 break;
9688 case 'line':
9689 ctx.beginPath();
9690 ctx.moveTo(x - radius, y);
9691 ctx.lineTo(x + radius, y);
9692 ctx.closePath();
9693 break;
9694 case 'dash':
9695 ctx.beginPath();
9696 ctx.moveTo(x, y);
9697 ctx.lineTo(x + radius, y);
9698 ctx.closePath();
9699 break;
9700 }
9701
9702 ctx.stroke();
9703 },
9704
9705 clipArea: function(ctx, area) {
9706 ctx.save();
9707 ctx.beginPath();
9708 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
9709 ctx.clip();
9710 },
9711
9712 unclipArea: function(ctx) {
9713 ctx.restore();
9714 },
9715
9716 lineTo: function(ctx, previous, target, flip) {
9717 if (target.steppedLine) {
9718 if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) {
9719 ctx.lineTo(previous.x, target.y);
9720 } else {
9721 ctx.lineTo(target.x, previous.y);
9722 }
9723 ctx.lineTo(target.x, target.y);
9724 return;
9725 }
9726
9727 if (!target.tension) {
9728 ctx.lineTo(target.x, target.y);
9729 return;
9730 }
9731
9732 ctx.bezierCurveTo(
9733 flip ? previous.controlPointPreviousX : previous.controlPointNextX,
9734 flip ? previous.controlPointPreviousY : previous.controlPointNextY,
9735 flip ? target.controlPointNextX : target.controlPointPreviousX,
9736 flip ? target.controlPointNextY : target.controlPointPreviousY,
9737 target.x,
9738 target.y);
9739 }
9740};
9741
9742// DEPRECATIONS
9743
9744/**
9745 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
9746 * @namespace Chart.helpers.clear
9747 * @deprecated since version 2.7.0
9748 * @todo remove at version 3
9749 * @private
9750 */
9751helpers.clear = exports.clear;
9752
9753/**
9754 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
9755 * @namespace Chart.helpers.drawRoundedRectangle
9756 * @deprecated since version 2.7.0
9757 * @todo remove at version 3
9758 * @private
9759 */
9760helpers.drawRoundedRectangle = function(ctx) {
9761 ctx.beginPath();
9762 exports.roundedRect.apply(exports, arguments);
9763 ctx.closePath();
9764};
9765
9766},{"42":42}],42:[function(require,module,exports){
9767'use strict';
9768
9769/**
9770 * @namespace Chart.helpers
9771 */
9772var helpers = {
9773 /**
9774 * An empty function that can be used, for example, for optional callback.
9775 */
9776 noop: function() {},
9777
9778 /**
9779 * Returns a unique id, sequentially generated from a global variable.
9780 * @returns {Number}
9781 * @function
9782 */
9783 uid: (function() {
9784 var id = 0;
9785 return function() {
9786 return id++;
9787 };
9788 }()),
9789
9790 /**
9791 * Returns true if `value` is neither null nor undefined, else returns false.
9792 * @param {*} value - The value to test.
9793 * @returns {Boolean}
9794 * @since 2.7.0
9795 */
9796 isNullOrUndef: function(value) {
9797 return value === null || typeof value === 'undefined';
9798 },
9799
9800 /**
9801 * Returns true if `value` is an array, else returns false.
9802 * @param {*} value - The value to test.
9803 * @returns {Boolean}
9804 * @function
9805 */
9806 isArray: Array.isArray ? Array.isArray : function(value) {
9807 return Object.prototype.toString.call(value) === '[object Array]';
9808 },
9809
9810 /**
9811 * Returns true if `value` is an object (excluding null), else returns false.
9812 * @param {*} value - The value to test.
9813 * @returns {Boolean}
9814 * @since 2.7.0
9815 */
9816 isObject: function(value) {
9817 return value !== null && Object.prototype.toString.call(value) === '[object Object]';
9818 },
9819
9820 /**
9821 * Returns `value` if defined, else returns `defaultValue`.
9822 * @param {*} value - The value to return if defined.
9823 * @param {*} defaultValue - The value to return if `value` is undefined.
9824 * @returns {*}
9825 */
9826 valueOrDefault: function(value, defaultValue) {
9827 return typeof value === 'undefined' ? defaultValue : value;
9828 },
9829
9830 /**
9831 * Returns value at the given `index` in array if defined, else returns `defaultValue`.
9832 * @param {Array} value - The array to lookup for value at `index`.
9833 * @param {Number} index - The index in `value` to lookup for value.
9834 * @param {*} defaultValue - The value to return if `value[index]` is undefined.
9835 * @returns {*}
9836 */
9837 valueAtIndexOrDefault: function(value, index, defaultValue) {
9838 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
9839 },
9840
9841 /**
9842 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
9843 * value returned by `fn`. If `fn` is not a function, this method returns undefined.
9844 * @param {Function} fn - The function to call.
9845 * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
9846 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9847 * @returns {*}
9848 */
9849 callback: function(fn, args, thisArg) {
9850 if (fn && typeof fn.call === 'function') {
9851 return fn.apply(thisArg, args);
9852 }
9853 },
9854
9855 /**
9856 * Note(SB) for performance sake, this method should only be used when loopable type
9857 * is unknown or in none intensive code (not called often and small loopable). Else
9858 * it's preferable to use a regular for() loop and save extra function calls.
9859 * @param {Object|Array} loopable - The object or array to be iterated.
9860 * @param {Function} fn - The function to call for each item.
9861 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9862 * @param {Boolean} [reverse] - If true, iterates backward on the loopable.
9863 */
9864 each: function(loopable, fn, thisArg, reverse) {
9865 var i, len, keys;
9866 if (helpers.isArray(loopable)) {
9867 len = loopable.length;
9868 if (reverse) {
9869 for (i = len - 1; i >= 0; i--) {
9870 fn.call(thisArg, loopable[i], i);
9871 }
9872 } else {
9873 for (i = 0; i < len; i++) {
9874 fn.call(thisArg, loopable[i], i);
9875 }
9876 }
9877 } else if (helpers.isObject(loopable)) {
9878 keys = Object.keys(loopable);
9879 len = keys.length;
9880 for (i = 0; i < len; i++) {
9881 fn.call(thisArg, loopable[keys[i]], keys[i]);
9882 }
9883 }
9884 },
9885
9886 /**
9887 * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
9888 * @see http://stackoverflow.com/a/14853974
9889 * @param {Array} a0 - The array to compare
9890 * @param {Array} a1 - The array to compare
9891 * @returns {Boolean}
9892 */
9893 arrayEquals: function(a0, a1) {
9894 var i, ilen, v0, v1;
9895
9896 if (!a0 || !a1 || a0.length !== a1.length) {
9897 return false;
9898 }
9899
9900 for (i = 0, ilen = a0.length; i < ilen; ++i) {
9901 v0 = a0[i];
9902 v1 = a1[i];
9903
9904 if (v0 instanceof Array && v1 instanceof Array) {
9905 if (!helpers.arrayEquals(v0, v1)) {
9906 return false;
9907 }
9908 } else if (v0 !== v1) {
9909 // NOTE: two different object instances will never be equal: {x:20} != {x:20}
9910 return false;
9911 }
9912 }
9913
9914 return true;
9915 },
9916
9917 /**
9918 * Returns a deep copy of `source` without keeping references on objects and arrays.
9919 * @param {*} source - The value to clone.
9920 * @returns {*}
9921 */
9922 clone: function(source) {
9923 if (helpers.isArray(source)) {
9924 return source.map(helpers.clone);
9925 }
9926
9927 if (helpers.isObject(source)) {
9928 var target = {};
9929 var keys = Object.keys(source);
9930 var klen = keys.length;
9931 var k = 0;
9932
9933 for (; k < klen; ++k) {
9934 target[keys[k]] = helpers.clone(source[keys[k]]);
9935 }
9936
9937 return target;
9938 }
9939
9940 return source;
9941 },
9942
9943 /**
9944 * The default merger when Chart.helpers.merge is called without merger option.
9945 * Note(SB): this method is also used by configMerge and scaleMerge as fallback.
9946 * @private
9947 */
9948 _merger: function(key, target, source, options) {
9949 var tval = target[key];
9950 var sval = source[key];
9951
9952 if (helpers.isObject(tval) && helpers.isObject(sval)) {
9953 helpers.merge(tval, sval, options);
9954 } else {
9955 target[key] = helpers.clone(sval);
9956 }
9957 },
9958
9959 /**
9960 * Merges source[key] in target[key] only if target[key] is undefined.
9961 * @private
9962 */
9963 _mergerIf: function(key, target, source) {
9964 var tval = target[key];
9965 var sval = source[key];
9966
9967 if (helpers.isObject(tval) && helpers.isObject(sval)) {
9968 helpers.mergeIf(tval, sval);
9969 } else if (!target.hasOwnProperty(key)) {
9970 target[key] = helpers.clone(sval);
9971 }
9972 },
9973
9974 /**
9975 * Recursively deep copies `source` properties into `target` with the given `options`.
9976 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
9977 * @param {Object} target - The target object in which all sources are merged into.
9978 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
9979 * @param {Object} [options] - Merging options:
9980 * @param {Function} [options.merger] - The merge method (key, target, source, options)
9981 * @returns {Object} The `target` object.
9982 */
9983 merge: function(target, source, options) {
9984 var sources = helpers.isArray(source) ? source : [source];
9985 var ilen = sources.length;
9986 var merge, i, keys, klen, k;
9987
9988 if (!helpers.isObject(target)) {
9989 return target;
9990 }
9991
9992 options = options || {};
9993 merge = options.merger || helpers._merger;
9994
9995 for (i = 0; i < ilen; ++i) {
9996 source = sources[i];
9997 if (!helpers.isObject(source)) {
9998 continue;
9999 }
10000
10001 keys = Object.keys(source);
10002 for (k = 0, klen = keys.length; k < klen; ++k) {
10003 merge(keys[k], target, source, options);
10004 }
10005 }
10006
10007 return target;
10008 },
10009
10010 /**
10011 * Recursively deep copies `source` properties into `target` *only* if not defined in target.
10012 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
10013 * @param {Object} target - The target object in which all sources are merged into.
10014 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
10015 * @returns {Object} The `target` object.
10016 */
10017 mergeIf: function(target, source) {
10018 return helpers.merge(target, source, {merger: helpers._mergerIf});
10019 },
10020
10021 /**
10022 * Applies the contents of two or more objects together into the first object.
10023 * @param {Object} target - The target object in which all objects are merged into.
10024 * @param {Object} arg1 - Object containing additional properties to merge in target.
10025 * @param {Object} argN - Additional objects containing properties to merge in target.
10026 * @returns {Object} The `target` object.
10027 */
10028 extend: function(target) {
10029 var setFn = function(value, key) {
10030 target[key] = value;
10031 };
10032 for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
10033 helpers.each(arguments[i], setFn);
10034 }
10035 return target;
10036 },
10037
10038 /**
10039 * Basic javascript inheritance based on the model created in Backbone.js
10040 */
10041 inherits: function(extensions) {
10042 var me = this;
10043 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
10044 return me.apply(this, arguments);
10045 };
10046
10047 var Surrogate = function() {
10048 this.constructor = ChartElement;
10049 };
10050
10051 Surrogate.prototype = me.prototype;
10052 ChartElement.prototype = new Surrogate();
10053 ChartElement.extend = helpers.inherits;
10054
10055 if (extensions) {
10056 helpers.extend(ChartElement.prototype, extensions);
10057 }
10058
10059 ChartElement.__super__ = me.prototype;
10060 return ChartElement;
10061 }
10062};
10063
10064module.exports = helpers;
10065
10066// DEPRECATIONS
10067
10068/**
10069 * Provided for backward compatibility, use Chart.helpers.callback instead.
10070 * @function Chart.helpers.callCallback
10071 * @deprecated since version 2.6.0
10072 * @todo remove at version 3
10073 * @private
10074 */
10075helpers.callCallback = helpers.callback;
10076
10077/**
10078 * Provided for backward compatibility, use Array.prototype.indexOf instead.
10079 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
10080 * @function Chart.helpers.indexOf
10081 * @deprecated since version 2.7.0
10082 * @todo remove at version 3
10083 * @private
10084 */
10085helpers.indexOf = function(array, item, fromIndex) {
10086 return Array.prototype.indexOf.call(array, item, fromIndex);
10087};
10088
10089/**
10090 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
10091 * @function Chart.helpers.getValueOrDefault
10092 * @deprecated since version 2.7.0
10093 * @todo remove at version 3
10094 * @private
10095 */
10096helpers.getValueOrDefault = helpers.valueOrDefault;
10097
10098/**
10099 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
10100 * @function Chart.helpers.getValueAtIndexOrDefault
10101 * @deprecated since version 2.7.0
10102 * @todo remove at version 3
10103 * @private
10104 */
10105helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
10106
10107},{}],43:[function(require,module,exports){
10108'use strict';
10109
10110var helpers = require(42);
10111
10112/**
10113 * Easing functions adapted from Robert Penner's easing equations.
10114 * @namespace Chart.helpers.easingEffects
10115 * @see http://www.robertpenner.com/easing/
10116 */
10117var effects = {
10118 linear: function(t) {
10119 return t;
10120 },
10121
10122 easeInQuad: function(t) {
10123 return t * t;
10124 },
10125
10126 easeOutQuad: function(t) {
10127 return -t * (t - 2);
10128 },
10129
10130 easeInOutQuad: function(t) {
10131 if ((t /= 0.5) < 1) {
10132 return 0.5 * t * t;
10133 }
10134 return -0.5 * ((--t) * (t - 2) - 1);
10135 },
10136
10137 easeInCubic: function(t) {
10138 return t * t * t;
10139 },
10140
10141 easeOutCubic: function(t) {
10142 return (t = t - 1) * t * t + 1;
10143 },
10144
10145 easeInOutCubic: function(t) {
10146 if ((t /= 0.5) < 1) {
10147 return 0.5 * t * t * t;
10148 }
10149 return 0.5 * ((t -= 2) * t * t + 2);
10150 },
10151
10152 easeInQuart: function(t) {
10153 return t * t * t * t;
10154 },
10155
10156 easeOutQuart: function(t) {
10157 return -((t = t - 1) * t * t * t - 1);
10158 },
10159
10160 easeInOutQuart: function(t) {
10161 if ((t /= 0.5) < 1) {
10162 return 0.5 * t * t * t * t;
10163 }
10164 return -0.5 * ((t -= 2) * t * t * t - 2);
10165 },
10166
10167 easeInQuint: function(t) {
10168 return t * t * t * t * t;
10169 },
10170
10171 easeOutQuint: function(t) {
10172 return (t = t - 1) * t * t * t * t + 1;
10173 },
10174
10175 easeInOutQuint: function(t) {
10176 if ((t /= 0.5) < 1) {
10177 return 0.5 * t * t * t * t * t;
10178 }
10179 return 0.5 * ((t -= 2) * t * t * t * t + 2);
10180 },
10181
10182 easeInSine: function(t) {
10183 return -Math.cos(t * (Math.PI / 2)) + 1;
10184 },
10185
10186 easeOutSine: function(t) {
10187 return Math.sin(t * (Math.PI / 2));
10188 },
10189
10190 easeInOutSine: function(t) {
10191 return -0.5 * (Math.cos(Math.PI * t) - 1);
10192 },
10193
10194 easeInExpo: function(t) {
10195 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
10196 },
10197
10198 easeOutExpo: function(t) {
10199 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
10200 },
10201
10202 easeInOutExpo: function(t) {
10203 if (t === 0) {
10204 return 0;
10205 }
10206 if (t === 1) {
10207 return 1;
10208 }
10209 if ((t /= 0.5) < 1) {
10210 return 0.5 * Math.pow(2, 10 * (t - 1));
10211 }
10212 return 0.5 * (-Math.pow(2, -10 * --t) + 2);
10213 },
10214
10215 easeInCirc: function(t) {
10216 if (t >= 1) {
10217 return t;
10218 }
10219 return -(Math.sqrt(1 - t * t) - 1);
10220 },
10221
10222 easeOutCirc: function(t) {
10223 return Math.sqrt(1 - (t = t - 1) * t);
10224 },
10225
10226 easeInOutCirc: function(t) {
10227 if ((t /= 0.5) < 1) {
10228 return -0.5 * (Math.sqrt(1 - t * t) - 1);
10229 }
10230 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
10231 },
10232
10233 easeInElastic: function(t) {
10234 var s = 1.70158;
10235 var p = 0;
10236 var a = 1;
10237 if (t === 0) {
10238 return 0;
10239 }
10240 if (t === 1) {
10241 return 1;
10242 }
10243 if (!p) {
10244 p = 0.3;
10245 }
10246 if (a < 1) {
10247 a = 1;
10248 s = p / 4;
10249 } else {
10250 s = p / (2 * Math.PI) * Math.asin(1 / a);
10251 }
10252 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10253 },
10254
10255 easeOutElastic: function(t) {
10256 var s = 1.70158;
10257 var p = 0;
10258 var a = 1;
10259 if (t === 0) {
10260 return 0;
10261 }
10262 if (t === 1) {
10263 return 1;
10264 }
10265 if (!p) {
10266 p = 0.3;
10267 }
10268 if (a < 1) {
10269 a = 1;
10270 s = p / 4;
10271 } else {
10272 s = p / (2 * Math.PI) * Math.asin(1 / a);
10273 }
10274 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
10275 },
10276
10277 easeInOutElastic: function(t) {
10278 var s = 1.70158;
10279 var p = 0;
10280 var a = 1;
10281 if (t === 0) {
10282 return 0;
10283 }
10284 if ((t /= 0.5) === 2) {
10285 return 1;
10286 }
10287 if (!p) {
10288 p = 0.45;
10289 }
10290 if (a < 1) {
10291 a = 1;
10292 s = p / 4;
10293 } else {
10294 s = p / (2 * Math.PI) * Math.asin(1 / a);
10295 }
10296 if (t < 1) {
10297 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10298 }
10299 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
10300 },
10301 easeInBack: function(t) {
10302 var s = 1.70158;
10303 return t * t * ((s + 1) * t - s);
10304 },
10305
10306 easeOutBack: function(t) {
10307 var s = 1.70158;
10308 return (t = t - 1) * t * ((s + 1) * t + s) + 1;
10309 },
10310
10311 easeInOutBack: function(t) {
10312 var s = 1.70158;
10313 if ((t /= 0.5) < 1) {
10314 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
10315 }
10316 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
10317 },
10318
10319 easeInBounce: function(t) {
10320 return 1 - effects.easeOutBounce(1 - t);
10321 },
10322
10323 easeOutBounce: function(t) {
10324 if (t < (1 / 2.75)) {
10325 return 7.5625 * t * t;
10326 }
10327 if (t < (2 / 2.75)) {
10328 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
10329 }
10330 if (t < (2.5 / 2.75)) {
10331 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
10332 }
10333 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
10334 },
10335
10336 easeInOutBounce: function(t) {
10337 if (t < 0.5) {
10338 return effects.easeInBounce(t * 2) * 0.5;
10339 }
10340 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
10341 }
10342};
10343
10344module.exports = {
10345 effects: effects
10346};
10347
10348// DEPRECATIONS
10349
10350/**
10351 * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
10352 * @function Chart.helpers.easingEffects
10353 * @deprecated since version 2.7.0
10354 * @todo remove at version 3
10355 * @private
10356 */
10357helpers.easingEffects = effects;
10358
10359},{"42":42}],44:[function(require,module,exports){
10360'use strict';
10361
10362var helpers = require(42);
10363
10364/**
10365 * @alias Chart.helpers.options
10366 * @namespace
10367 */
10368module.exports = {
10369 /**
10370 * Converts the given line height `value` in pixels for a specific font `size`.
10371 * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
10372 * @param {Number} size - The font size (in pixels) used to resolve relative `value`.
10373 * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid).
10374 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
10375 * @since 2.7.0
10376 */
10377 toLineHeight: function(value, size) {
10378 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
10379 if (!matches || matches[1] === 'normal') {
10380 return size * 1.2;
10381 }
10382
10383 value = +matches[2];
10384
10385 switch (matches[3]) {
10386 case 'px':
10387 return value;
10388 case '%':
10389 value /= 100;
10390 break;
10391 default:
10392 break;
10393 }
10394
10395 return size * value;
10396 },
10397
10398 /**
10399 * Converts the given value into a padding object with pre-computed width/height.
10400 * @param {Number|Object} value - If a number, set the value to all TRBL component,
10401 * else, if and object, use defined properties and sets undefined ones to 0.
10402 * @returns {Object} The padding values (top, right, bottom, left, width, height)
10403 * @since 2.7.0
10404 */
10405 toPadding: function(value) {
10406 var t, r, b, l;
10407
10408 if (helpers.isObject(value)) {
10409 t = +value.top || 0;
10410 r = +value.right || 0;
10411 b = +value.bottom || 0;
10412 l = +value.left || 0;
10413 } else {
10414 t = r = b = l = +value || 0;
10415 }
10416
10417 return {
10418 top: t,
10419 right: r,
10420 bottom: b,
10421 left: l,
10422 height: t + b,
10423 width: l + r
10424 };
10425 },
10426
10427 /**
10428 * Evaluates the given `inputs` sequentially and returns the first defined value.
10429 * @param {Array[]} inputs - An array of values, falling back to the last value.
10430 * @param {Object} [context] - If defined and the current value is a function, the value
10431 * is called with `context` as first argument and the result becomes the new input.
10432 * @param {Number} [index] - If defined and the current value is an array, the value
10433 * at `index` become the new input.
10434 * @since 2.7.0
10435 */
10436 resolve: function(inputs, context, index) {
10437 var i, ilen, value;
10438
10439 for (i = 0, ilen = inputs.length; i < ilen; ++i) {
10440 value = inputs[i];
10441 if (value === undefined) {
10442 continue;
10443 }
10444 if (context !== undefined && typeof value === 'function') {
10445 value = value(context);
10446 }
10447 if (index !== undefined && helpers.isArray(value)) {
10448 value = value[index];
10449 }
10450 if (value !== undefined) {
10451 return value;
10452 }
10453 }
10454 }
10455};
10456
10457},{"42":42}],45:[function(require,module,exports){
10458'use strict';
10459
10460module.exports = require(42);
10461module.exports.easing = require(43);
10462module.exports.canvas = require(41);
10463module.exports.options = require(44);
10464
10465},{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){
10466/**
10467 * Platform fallback implementation (minimal).
10468 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
10469 */
10470
10471module.exports = {
10472 acquireContext: function(item) {
10473 if (item && item.canvas) {
10474 // Support for any object associated to a canvas (including a context2d)
10475 item = item.canvas;
10476 }
10477
10478 return item && item.getContext('2d') || null;
10479 }
10480};
10481
10482},{}],47:[function(require,module,exports){
10483/**
10484 * Chart.Platform implementation for targeting a web browser
10485 */
10486
10487'use strict';
10488
10489var helpers = require(45);
10490
10491var EXPANDO_KEY = '$chartjs';
10492var CSS_PREFIX = 'chartjs-';
10493var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
10494var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
10495var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
10496
10497/**
10498 * DOM event types -> Chart.js event types.
10499 * Note: only events with different types are mapped.
10500 * @see https://developer.mozilla.org/en-US/docs/Web/Events
10501 */
10502var EVENT_TYPES = {
10503 touchstart: 'mousedown',
10504 touchmove: 'mousemove',
10505 touchend: 'mouseup',
10506 pointerenter: 'mouseenter',
10507 pointerdown: 'mousedown',
10508 pointermove: 'mousemove',
10509 pointerup: 'mouseup',
10510 pointerleave: 'mouseout',
10511 pointerout: 'mouseout'
10512};
10513
10514/**
10515 * The "used" size is the final value of a dimension property after all calculations have
10516 * been performed. This method uses the computed style of `element` but returns undefined
10517 * if the computed style is not expressed in pixels. That can happen in some cases where
10518 * `element` has a size relative to its parent and this last one is not yet displayed,
10519 * for example because of `display: none` on a parent node.
10520 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
10521 * @returns {Number} Size in pixels or undefined if unknown.
10522 */
10523function readUsedSize(element, property) {
10524 var value = helpers.getStyle(element, property);
10525 var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
10526 return matches ? Number(matches[1]) : undefined;
10527}
10528
10529/**
10530 * Initializes the canvas style and render size without modifying the canvas display size,
10531 * since responsiveness is handled by the controller.resize() method. The config is used
10532 * to determine the aspect ratio to apply in case no explicit height has been specified.
10533 */
10534function initCanvas(canvas, config) {
10535 var style = canvas.style;
10536
10537 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
10538 // returns null or '' if no explicit value has been set to the canvas attribute.
10539 var renderHeight = canvas.getAttribute('height');
10540 var renderWidth = canvas.getAttribute('width');
10541
10542 // Chart.js modifies some canvas values that we want to restore on destroy
10543 canvas[EXPANDO_KEY] = {
10544 initial: {
10545 height: renderHeight,
10546 width: renderWidth,
10547 style: {
10548 display: style.display,
10549 height: style.height,
10550 width: style.width
10551 }
10552 }
10553 };
10554
10555 // Force canvas to display as block to avoid extra space caused by inline
10556 // elements, which would interfere with the responsive resize process.
10557 // https://github.com/chartjs/Chart.js/issues/2538
10558 style.display = style.display || 'block';
10559
10560 if (renderWidth === null || renderWidth === '') {
10561 var displayWidth = readUsedSize(canvas, 'width');
10562 if (displayWidth !== undefined) {
10563 canvas.width = displayWidth;
10564 }
10565 }
10566
10567 if (renderHeight === null || renderHeight === '') {
10568 if (canvas.style.height === '') {
10569 // If no explicit render height and style height, let's apply the aspect ratio,
10570 // which one can be specified by the user but also by charts as default option
10571 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
10572 canvas.height = canvas.width / (config.options.aspectRatio || 2);
10573 } else {
10574 var displayHeight = readUsedSize(canvas, 'height');
10575 if (displayWidth !== undefined) {
10576 canvas.height = displayHeight;
10577 }
10578 }
10579 }
10580
10581 return canvas;
10582}
10583
10584/**
10585 * Detects support for options object argument in addEventListener.
10586 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
10587 * @private
10588 */
10589var supportsEventListenerOptions = (function() {
10590 var supports = false;
10591 try {
10592 var options = Object.defineProperty({}, 'passive', {
10593 get: function() {
10594 supports = true;
10595 }
10596 });
10597 window.addEventListener('e', null, options);
10598 } catch (e) {
10599 // continue regardless of error
10600 }
10601 return supports;
10602}());
10603
10604// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
10605// https://github.com/chartjs/Chart.js/issues/4287
10606var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
10607
10608function addEventListener(node, type, listener) {
10609 node.addEventListener(type, listener, eventListenerOptions);
10610}
10611
10612function removeEventListener(node, type, listener) {
10613 node.removeEventListener(type, listener, eventListenerOptions);
10614}
10615
10616function createEvent(type, chart, x, y, nativeEvent) {
10617 return {
10618 type: type,
10619 chart: chart,
10620 native: nativeEvent || null,
10621 x: x !== undefined ? x : null,
10622 y: y !== undefined ? y : null,
10623 };
10624}
10625
10626function fromNativeEvent(event, chart) {
10627 var type = EVENT_TYPES[event.type] || event.type;
10628 var pos = helpers.getRelativePosition(event, chart);
10629 return createEvent(type, chart, pos.x, pos.y, event);
10630}
10631
10632function throttled(fn, thisArg) {
10633 var ticking = false;
10634 var args = [];
10635
10636 return function() {
10637 args = Array.prototype.slice.call(arguments);
10638 thisArg = thisArg || this;
10639
10640 if (!ticking) {
10641 ticking = true;
10642 helpers.requestAnimFrame.call(window, function() {
10643 ticking = false;
10644 fn.apply(thisArg, args);
10645 });
10646 }
10647 };
10648}
10649
10650// Implementation based on https://github.com/marcj/css-element-queries
10651function createResizer(handler) {
10652 var resizer = document.createElement('div');
10653 var cls = CSS_PREFIX + 'size-monitor';
10654 var maxSize = 1000000;
10655 var style =
10656 'position:absolute;' +
10657 'left:0;' +
10658 'top:0;' +
10659 'right:0;' +
10660 'bottom:0;' +
10661 'overflow:hidden;' +
10662 'pointer-events:none;' +
10663 'visibility:hidden;' +
10664 'z-index:-1;';
10665
10666 resizer.style.cssText = style;
10667 resizer.className = cls;
10668 resizer.innerHTML =
10669 '<div class="' + cls + '-expand" style="' + style + '">' +
10670 '<div style="' +
10671 'position:absolute;' +
10672 'width:' + maxSize + 'px;' +
10673 'height:' + maxSize + 'px;' +
10674 'left:0;' +
10675 'top:0">' +
10676 '</div>' +
10677 '</div>' +
10678 '<div class="' + cls + '-shrink" style="' + style + '">' +
10679 '<div style="' +
10680 'position:absolute;' +
10681 'width:200%;' +
10682 'height:200%;' +
10683 'left:0; ' +
10684 'top:0">' +
10685 '</div>' +
10686 '</div>';
10687
10688 var expand = resizer.childNodes[0];
10689 var shrink = resizer.childNodes[1];
10690
10691 resizer._reset = function() {
10692 expand.scrollLeft = maxSize;
10693 expand.scrollTop = maxSize;
10694 shrink.scrollLeft = maxSize;
10695 shrink.scrollTop = maxSize;
10696 };
10697 var onScroll = function() {
10698 resizer._reset();
10699 handler();
10700 };
10701
10702 addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
10703 addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
10704
10705 return resizer;
10706}
10707
10708// https://davidwalsh.name/detect-node-insertion
10709function watchForRender(node, handler) {
10710 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10711 var proxy = expando.renderProxy = function(e) {
10712 if (e.animationName === CSS_RENDER_ANIMATION) {
10713 handler();
10714 }
10715 };
10716
10717 helpers.each(ANIMATION_START_EVENTS, function(type) {
10718 addEventListener(node, type, proxy);
10719 });
10720
10721 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
10722 // is removed then added back immediately (same animation frame?). Accessing the
10723 // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
10724 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
10725 // https://github.com/chartjs/Chart.js/issues/4737
10726 expando.reflow = !!node.offsetParent;
10727
10728 node.classList.add(CSS_RENDER_MONITOR);
10729}
10730
10731function unwatchForRender(node) {
10732 var expando = node[EXPANDO_KEY] || {};
10733 var proxy = expando.renderProxy;
10734
10735 if (proxy) {
10736 helpers.each(ANIMATION_START_EVENTS, function(type) {
10737 removeEventListener(node, type, proxy);
10738 });
10739
10740 delete expando.renderProxy;
10741 }
10742
10743 node.classList.remove(CSS_RENDER_MONITOR);
10744}
10745
10746function addResizeListener(node, listener, chart) {
10747 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10748
10749 // Let's keep track of this added resizer and thus avoid DOM query when removing it.
10750 var resizer = expando.resizer = createResizer(throttled(function() {
10751 if (expando.resizer) {
10752 return listener(createEvent('resize', chart));
10753 }
10754 }));
10755
10756 // The resizer needs to be attached to the node parent, so we first need to be
10757 // sure that `node` is attached to the DOM before injecting the resizer element.
10758 watchForRender(node, function() {
10759 if (expando.resizer) {
10760 var container = node.parentNode;
10761 if (container && container !== resizer.parentNode) {
10762 container.insertBefore(resizer, container.firstChild);
10763 }
10764
10765 // The container size might have changed, let's reset the resizer state.
10766 resizer._reset();
10767 }
10768 });
10769}
10770
10771function removeResizeListener(node) {
10772 var expando = node[EXPANDO_KEY] || {};
10773 var resizer = expando.resizer;
10774
10775 delete expando.resizer;
10776 unwatchForRender(node);
10777
10778 if (resizer && resizer.parentNode) {
10779 resizer.parentNode.removeChild(resizer);
10780 }
10781}
10782
10783function injectCSS(platform, css) {
10784 // http://stackoverflow.com/q/3922139
10785 var style = platform._style || document.createElement('style');
10786 if (!platform._style) {
10787 platform._style = style;
10788 css = '/* Chart.js */\n' + css;
10789 style.setAttribute('type', 'text/css');
10790 document.getElementsByTagName('head')[0].appendChild(style);
10791 }
10792
10793 style.appendChild(document.createTextNode(css));
10794}
10795
10796module.exports = {
10797 /**
10798 * This property holds whether this platform is enabled for the current environment.
10799 * Currently used by platform.js to select the proper implementation.
10800 * @private
10801 */
10802 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
10803
10804 initialize: function() {
10805 var keyframes = 'from{opacity:0.99}to{opacity:1}';
10806
10807 injectCSS(this,
10808 // DOM rendering detection
10809 // https://davidwalsh.name/detect-node-insertion
10810 '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10811 '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10812 '.' + CSS_RENDER_MONITOR + '{' +
10813 '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10814 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10815 '}'
10816 );
10817 },
10818
10819 acquireContext: function(item, config) {
10820 if (typeof item === 'string') {
10821 item = document.getElementById(item);
10822 } else if (item.length) {
10823 // Support for array based queries (such as jQuery)
10824 item = item[0];
10825 }
10826
10827 if (item && item.canvas) {
10828 // Support for any object associated to a canvas (including a context2d)
10829 item = item.canvas;
10830 }
10831
10832 // To prevent canvas fingerprinting, some add-ons undefine the getContext
10833 // method, for example: https://github.com/kkapsner/CanvasBlocker
10834 // https://github.com/chartjs/Chart.js/issues/2807
10835 var context = item && item.getContext && item.getContext('2d');
10836
10837 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
10838 // inside an iframe or when running in a protected environment. We could guess the
10839 // types from their toString() value but let's keep things flexible and assume it's
10840 // a sufficient condition if the item has a context2D which has item as `canvas`.
10841 // https://github.com/chartjs/Chart.js/issues/3887
10842 // https://github.com/chartjs/Chart.js/issues/4102
10843 // https://github.com/chartjs/Chart.js/issues/4152
10844 if (context && context.canvas === item) {
10845 initCanvas(item, config);
10846 return context;
10847 }
10848
10849 return null;
10850 },
10851
10852 releaseContext: function(context) {
10853 var canvas = context.canvas;
10854 if (!canvas[EXPANDO_KEY]) {
10855 return;
10856 }
10857
10858 var initial = canvas[EXPANDO_KEY].initial;
10859 ['height', 'width'].forEach(function(prop) {
10860 var value = initial[prop];
10861 if (helpers.isNullOrUndef(value)) {
10862 canvas.removeAttribute(prop);
10863 } else {
10864 canvas.setAttribute(prop, value);
10865 }
10866 });
10867
10868 helpers.each(initial.style || {}, function(value, key) {
10869 canvas.style[key] = value;
10870 });
10871
10872 // The canvas render size might have been changed (and thus the state stack discarded),
10873 // we can't use save() and restore() to restore the initial state. So make sure that at
10874 // least the canvas context is reset to the default state by setting the canvas width.
10875 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
10876 canvas.width = canvas.width;
10877
10878 delete canvas[EXPANDO_KEY];
10879 },
10880
10881 addEventListener: function(chart, type, listener) {
10882 var canvas = chart.canvas;
10883 if (type === 'resize') {
10884 // Note: the resize event is not supported on all browsers.
10885 addResizeListener(canvas, listener, chart);
10886 return;
10887 }
10888
10889 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
10890 var proxies = expando.proxies || (expando.proxies = {});
10891 var proxy = proxies[chart.id + '_' + type] = function(event) {
10892 listener(fromNativeEvent(event, chart));
10893 };
10894
10895 addEventListener(canvas, type, proxy);
10896 },
10897
10898 removeEventListener: function(chart, type, listener) {
10899 var canvas = chart.canvas;
10900 if (type === 'resize') {
10901 // Note: the resize event is not supported on all browsers.
10902 removeResizeListener(canvas, listener);
10903 return;
10904 }
10905
10906 var expando = listener[EXPANDO_KEY] || {};
10907 var proxies = expando.proxies || {};
10908 var proxy = proxies[chart.id + '_' + type];
10909 if (!proxy) {
10910 return;
10911 }
10912
10913 removeEventListener(canvas, type, proxy);
10914 }
10915};
10916
10917// DEPRECATIONS
10918
10919/**
10920 * Provided for backward compatibility, use EventTarget.addEventListener instead.
10921 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10922 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
10923 * @function Chart.helpers.addEvent
10924 * @deprecated since version 2.7.0
10925 * @todo remove at version 3
10926 * @private
10927 */
10928helpers.addEvent = addEventListener;
10929
10930/**
10931 * Provided for backward compatibility, use EventTarget.removeEventListener instead.
10932 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10933 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
10934 * @function Chart.helpers.removeEvent
10935 * @deprecated since version 2.7.0
10936 * @todo remove at version 3
10937 * @private
10938 */
10939helpers.removeEvent = removeEventListener;
10940
10941},{"45":45}],48:[function(require,module,exports){
10942'use strict';
10943
10944var helpers = require(45);
10945var basic = require(46);
10946var dom = require(47);
10947
10948// @TODO Make possible to select another platform at build time.
10949var implementation = dom._enabled ? dom : basic;
10950
10951/**
10952 * @namespace Chart.platform
10953 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
10954 * @since 2.4.0
10955 */
10956module.exports = helpers.extend({
10957 /**
10958 * @since 2.7.0
10959 */
10960 initialize: function() {},
10961
10962 /**
10963 * Called at chart construction time, returns a context2d instance implementing
10964 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
10965 * @param {*} item - The native item from which to acquire context (platform specific)
10966 * @param {Object} options - The chart options
10967 * @returns {CanvasRenderingContext2D} context2d instance
10968 */
10969 acquireContext: function() {},
10970
10971 /**
10972 * Called at chart destruction time, releases any resources associated to the context
10973 * previously returned by the acquireContext() method.
10974 * @param {CanvasRenderingContext2D} context - The context2d instance
10975 * @returns {Boolean} true if the method succeeded, else false
10976 */
10977 releaseContext: function() {},
10978
10979 /**
10980 * Registers the specified listener on the given chart.
10981 * @param {Chart} chart - Chart from which to listen for event
10982 * @param {String} type - The ({@link IEvent}) type to listen for
10983 * @param {Function} listener - Receives a notification (an object that implements
10984 * the {@link IEvent} interface) when an event of the specified type occurs.
10985 */
10986 addEventListener: function() {},
10987
10988 /**
10989 * Removes the specified listener previously registered with addEventListener.
10990 * @param {Chart} chart -Chart from which to remove the listener
10991 * @param {String} type - The ({@link IEvent}) type to remove
10992 * @param {Function} listener - The listener function to remove from the event target.
10993 */
10994 removeEventListener: function() {}
10995
10996}, implementation);
10997
10998/**
10999 * @interface IPlatform
11000 * Allows abstracting platform dependencies away from the chart
11001 * @borrows Chart.platform.acquireContext as acquireContext
11002 * @borrows Chart.platform.releaseContext as releaseContext
11003 * @borrows Chart.platform.addEventListener as addEventListener
11004 * @borrows Chart.platform.removeEventListener as removeEventListener
11005 */
11006
11007/**
11008 * @interface IEvent
11009 * @prop {String} type - The event type name, possible values are:
11010 * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',
11011 * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'
11012 * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')
11013 * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)
11014 * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)
11015 */
11016
11017},{"45":45,"46":46,"47":47}],49:[function(require,module,exports){
11018'use strict';
11019
11020module.exports = {};
11021module.exports.filler = require(50);
11022module.exports.legend = require(51);
11023module.exports.title = require(52);
11024
11025},{"50":50,"51":51,"52":52}],50:[function(require,module,exports){
11026/**
11027 * Plugin based on discussion from the following Chart.js issues:
11028 * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569
11029 * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897
11030 */
11031
11032'use strict';
11033
11034var defaults = require(25);
11035var elements = require(40);
11036var helpers = require(45);
11037
11038defaults._set('global', {
11039 plugins: {
11040 filler: {
11041 propagate: true
11042 }
11043 }
11044});
11045
11046var mappers = {
11047 dataset: function(source) {
11048 var index = source.fill;
11049 var chart = source.chart;
11050 var meta = chart.getDatasetMeta(index);
11051 var visible = meta && chart.isDatasetVisible(index);
11052 var points = (visible && meta.dataset._children) || [];
11053 var length = points.length || 0;
11054
11055 return !length ? null : function(point, i) {
11056 return (i < length && points[i]._view) || null;
11057 };
11058 },
11059
11060 boundary: function(source) {
11061 var boundary = source.boundary;
11062 var x = boundary ? boundary.x : null;
11063 var y = boundary ? boundary.y : null;
11064
11065 return function(point) {
11066 return {
11067 x: x === null ? point.x : x,
11068 y: y === null ? point.y : y,
11069 };
11070 };
11071 }
11072};
11073
11074// @todo if (fill[0] === '#')
11075function decodeFill(el, index, count) {
11076 var model = el._model || {};
11077 var fill = model.fill;
11078 var target;
11079
11080 if (fill === undefined) {
11081 fill = !!model.backgroundColor;
11082 }
11083
11084 if (fill === false || fill === null) {
11085 return false;
11086 }
11087
11088 if (fill === true) {
11089 return 'origin';
11090 }
11091
11092 target = parseFloat(fill, 10);
11093 if (isFinite(target) && Math.floor(target) === target) {
11094 if (fill[0] === '-' || fill[0] === '+') {
11095 target = index + target;
11096 }
11097
11098 if (target === index || target < 0 || target >= count) {
11099 return false;
11100 }
11101
11102 return target;
11103 }
11104
11105 switch (fill) {
11106 // compatibility
11107 case 'bottom':
11108 return 'start';
11109 case 'top':
11110 return 'end';
11111 case 'zero':
11112 return 'origin';
11113 // supported boundaries
11114 case 'origin':
11115 case 'start':
11116 case 'end':
11117 return fill;
11118 // invalid fill values
11119 default:
11120 return false;
11121 }
11122}
11123
11124function computeBoundary(source) {
11125 var model = source.el._model || {};
11126 var scale = source.el._scale || {};
11127 var fill = source.fill;
11128 var target = null;
11129 var horizontal;
11130
11131 if (isFinite(fill)) {
11132 return null;
11133 }
11134
11135 // Backward compatibility: until v3, we still need to support boundary values set on
11136 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
11137 // controllers might still use it (e.g. the Smith chart).
11138
11139 if (fill === 'start') {
11140 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
11141 } else if (fill === 'end') {
11142 target = model.scaleTop === undefined ? scale.top : model.scaleTop;
11143 } else if (model.scaleZero !== undefined) {
11144 target = model.scaleZero;
11145 } else if (scale.getBasePosition) {
11146 target = scale.getBasePosition();
11147 } else if (scale.getBasePixel) {
11148 target = scale.getBasePixel();
11149 }
11150
11151 if (target !== undefined && target !== null) {
11152 if (target.x !== undefined && target.y !== undefined) {
11153 return target;
11154 }
11155
11156 if (typeof target === 'number' && isFinite(target)) {
11157 horizontal = scale.isHorizontal();
11158 return {
11159 x: horizontal ? target : null,
11160 y: horizontal ? null : target
11161 };
11162 }
11163 }
11164
11165 return null;
11166}
11167
11168function resolveTarget(sources, index, propagate) {
11169 var source = sources[index];
11170 var fill = source.fill;
11171 var visited = [index];
11172 var target;
11173
11174 if (!propagate) {
11175 return fill;
11176 }
11177
11178 while (fill !== false && visited.indexOf(fill) === -1) {
11179 if (!isFinite(fill)) {
11180 return fill;
11181 }
11182
11183 target = sources[fill];
11184 if (!target) {
11185 return false;
11186 }
11187
11188 if (target.visible) {
11189 return fill;
11190 }
11191
11192 visited.push(fill);
11193 fill = target.fill;
11194 }
11195
11196 return false;
11197}
11198
11199function createMapper(source) {
11200 var fill = source.fill;
11201 var type = 'dataset';
11202
11203 if (fill === false) {
11204 return null;
11205 }
11206
11207 if (!isFinite(fill)) {
11208 type = 'boundary';
11209 }
11210
11211 return mappers[type](source);
11212}
11213
11214function isDrawable(point) {
11215 return point && !point.skip;
11216}
11217
11218function drawArea(ctx, curve0, curve1, len0, len1) {
11219 var i;
11220
11221 if (!len0 || !len1) {
11222 return;
11223 }
11224
11225 // building first area curve (normal)
11226 ctx.moveTo(curve0[0].x, curve0[0].y);
11227 for (i = 1; i < len0; ++i) {
11228 helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
11229 }
11230
11231 // joining the two area curves
11232 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
11233
11234 // building opposite area curve (reverse)
11235 for (i = len1 - 1; i > 0; --i) {
11236 helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
11237 }
11238}
11239
11240function doFill(ctx, points, mapper, view, color, loop) {
11241 var count = points.length;
11242 var span = view.spanGaps;
11243 var curve0 = [];
11244 var curve1 = [];
11245 var len0 = 0;
11246 var len1 = 0;
11247 var i, ilen, index, p0, p1, d0, d1;
11248
11249 ctx.beginPath();
11250
11251 for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
11252 index = i % count;
11253 p0 = points[index]._view;
11254 p1 = mapper(p0, index, view);
11255 d0 = isDrawable(p0);
11256 d1 = isDrawable(p1);
11257
11258 if (d0 && d1) {
11259 len0 = curve0.push(p0);
11260 len1 = curve1.push(p1);
11261 } else if (len0 && len1) {
11262 if (!span) {
11263 drawArea(ctx, curve0, curve1, len0, len1);
11264 len0 = len1 = 0;
11265 curve0 = [];
11266 curve1 = [];
11267 } else {
11268 if (d0) {
11269 curve0.push(p0);
11270 }
11271 if (d1) {
11272 curve1.push(p1);
11273 }
11274 }
11275 }
11276 }
11277
11278 drawArea(ctx, curve0, curve1, len0, len1);
11279
11280 ctx.closePath();
11281 ctx.fillStyle = color;
11282 ctx.fill();
11283}
11284
11285module.exports = {
11286 id: 'filler',
11287
11288 afterDatasetsUpdate: function(chart, options) {
11289 var count = (chart.data.datasets || []).length;
11290 var propagate = options.propagate;
11291 var sources = [];
11292 var meta, i, el, source;
11293
11294 for (i = 0; i < count; ++i) {
11295 meta = chart.getDatasetMeta(i);
11296 el = meta.dataset;
11297 source = null;
11298
11299 if (el && el._model && el instanceof elements.Line) {
11300 source = {
11301 visible: chart.isDatasetVisible(i),
11302 fill: decodeFill(el, i, count),
11303 chart: chart,
11304 el: el
11305 };
11306 }
11307
11308 meta.$filler = source;
11309 sources.push(source);
11310 }
11311
11312 for (i = 0; i < count; ++i) {
11313 source = sources[i];
11314 if (!source) {
11315 continue;
11316 }
11317
11318 source.fill = resolveTarget(sources, i, propagate);
11319 source.boundary = computeBoundary(source);
11320 source.mapper = createMapper(source);
11321 }
11322 },
11323
11324 beforeDatasetDraw: function(chart, args) {
11325 var meta = args.meta.$filler;
11326 if (!meta) {
11327 return;
11328 }
11329
11330 var ctx = chart.ctx;
11331 var el = meta.el;
11332 var view = el._view;
11333 var points = el._children || [];
11334 var mapper = meta.mapper;
11335 var color = view.backgroundColor || defaults.global.defaultColor;
11336
11337 if (mapper && color && points.length) {
11338 helpers.canvas.clipArea(ctx, chart.chartArea);
11339 doFill(ctx, points, mapper, view, color, el._loop);
11340 helpers.canvas.unclipArea(ctx);
11341 }
11342 }
11343};
11344
11345},{"25":25,"40":40,"45":45}],51:[function(require,module,exports){
11346'use strict';
11347
11348var defaults = require(25);
11349var Element = require(26);
11350var helpers = require(45);
11351var layouts = require(30);
11352
11353var noop = helpers.noop;
11354
11355defaults._set('global', {
11356 legend: {
11357 display: true,
11358 position: 'top',
11359 fullWidth: true,
11360 reverse: false,
11361 weight: 1000,
11362
11363 // a callback that will handle
11364 onClick: function(e, legendItem) {
11365 var index = legendItem.datasetIndex;
11366 var ci = this.chart;
11367 var meta = ci.getDatasetMeta(index);
11368
11369 // See controller.isDatasetVisible comment
11370 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
11371
11372 // We hid a dataset ... rerender the chart
11373 ci.update();
11374 },
11375
11376 onHover: null,
11377
11378 labels: {
11379 boxWidth: 40,
11380 padding: 10,
11381 // Generates labels shown in the legend
11382 // Valid properties to return:
11383 // text : text to display
11384 // fillStyle : fill of coloured box
11385 // strokeStyle: stroke of coloured box
11386 // hidden : if this legend item refers to a hidden item
11387 // lineCap : cap style for line
11388 // lineDash
11389 // lineDashOffset :
11390 // lineJoin :
11391 // lineWidth :
11392 generateLabels: function(chart) {
11393 var data = chart.data;
11394 return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
11395 return {
11396 text: dataset.label,
11397 fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
11398 hidden: !chart.isDatasetVisible(i),
11399 lineCap: dataset.borderCapStyle,
11400 lineDash: dataset.borderDash,
11401 lineDashOffset: dataset.borderDashOffset,
11402 lineJoin: dataset.borderJoinStyle,
11403 lineWidth: dataset.borderWidth,
11404 strokeStyle: dataset.borderColor,
11405 pointStyle: dataset.pointStyle,
11406
11407 // Below is extra data used for toggling the datasets
11408 datasetIndex: i
11409 };
11410 }, this) : [];
11411 }
11412 }
11413 },
11414
11415 legendCallback: function(chart) {
11416 var text = [];
11417 text.push('<ul class="' + chart.id + '-legend">');
11418 for (var i = 0; i < chart.data.datasets.length; i++) {
11419 text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
11420 if (chart.data.datasets[i].label) {
11421 text.push(chart.data.datasets[i].label);
11422 }
11423 text.push('</li>');
11424 }
11425 text.push('</ul>');
11426 return text.join('');
11427 }
11428});
11429
11430/**
11431 * Helper function to get the box width based on the usePointStyle option
11432 * @param labelopts {Object} the label options on the legend
11433 * @param fontSize {Number} the label font size
11434 * @return {Number} width of the color box area
11435 */
11436function getBoxWidth(labelOpts, fontSize) {
11437 return labelOpts.usePointStyle ?
11438 fontSize * Math.SQRT2 :
11439 labelOpts.boxWidth;
11440}
11441
11442/**
11443 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
11444 */
11445var Legend = Element.extend({
11446
11447 initialize: function(config) {
11448 helpers.extend(this, config);
11449
11450 // Contains hit boxes for each dataset (in dataset order)
11451 this.legendHitBoxes = [];
11452
11453 // Are we in doughnut mode which has a different data type
11454 this.doughnutMode = false;
11455 },
11456
11457 // These methods are ordered by lifecycle. Utilities then follow.
11458 // Any function defined here is inherited by all legend types.
11459 // Any function can be extended by the legend type
11460
11461 beforeUpdate: noop,
11462 update: function(maxWidth, maxHeight, margins) {
11463 var me = this;
11464
11465 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11466 me.beforeUpdate();
11467
11468 // Absorb the master measurements
11469 me.maxWidth = maxWidth;
11470 me.maxHeight = maxHeight;
11471 me.margins = margins;
11472
11473 // Dimensions
11474 me.beforeSetDimensions();
11475 me.setDimensions();
11476 me.afterSetDimensions();
11477 // Labels
11478 me.beforeBuildLabels();
11479 me.buildLabels();
11480 me.afterBuildLabels();
11481
11482 // Fit
11483 me.beforeFit();
11484 me.fit();
11485 me.afterFit();
11486 //
11487 me.afterUpdate();
11488
11489 return me.minSize;
11490 },
11491 afterUpdate: noop,
11492
11493 //
11494
11495 beforeSetDimensions: noop,
11496 setDimensions: function() {
11497 var me = this;
11498 // Set the unconstrained dimension before label rotation
11499 if (me.isHorizontal()) {
11500 // Reset position before calculating rotation
11501 me.width = me.maxWidth;
11502 me.left = 0;
11503 me.right = me.width;
11504 } else {
11505 me.height = me.maxHeight;
11506
11507 // Reset position before calculating rotation
11508 me.top = 0;
11509 me.bottom = me.height;
11510 }
11511
11512 // Reset padding
11513 me.paddingLeft = 0;
11514 me.paddingTop = 0;
11515 me.paddingRight = 0;
11516 me.paddingBottom = 0;
11517
11518 // Reset minSize
11519 me.minSize = {
11520 width: 0,
11521 height: 0
11522 };
11523 },
11524 afterSetDimensions: noop,
11525
11526 //
11527
11528 beforeBuildLabels: noop,
11529 buildLabels: function() {
11530 var me = this;
11531 var labelOpts = me.options.labels || {};
11532 var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
11533
11534 if (labelOpts.filter) {
11535 legendItems = legendItems.filter(function(item) {
11536 return labelOpts.filter(item, me.chart.data);
11537 });
11538 }
11539
11540 if (me.options.reverse) {
11541 legendItems.reverse();
11542 }
11543
11544 me.legendItems = legendItems;
11545 },
11546 afterBuildLabels: noop,
11547
11548 //
11549
11550 beforeFit: noop,
11551 fit: function() {
11552 var me = this;
11553 var opts = me.options;
11554 var labelOpts = opts.labels;
11555 var display = opts.display;
11556
11557 var ctx = me.ctx;
11558
11559 var globalDefault = defaults.global;
11560 var valueOrDefault = helpers.valueOrDefault;
11561 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11562 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11563 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11564 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11565
11566 // Reset hit boxes
11567 var hitboxes = me.legendHitBoxes = [];
11568
11569 var minSize = me.minSize;
11570 var isHorizontal = me.isHorizontal();
11571
11572 if (isHorizontal) {
11573 minSize.width = me.maxWidth; // fill all the width
11574 minSize.height = display ? 10 : 0;
11575 } else {
11576 minSize.width = display ? 10 : 0;
11577 minSize.height = me.maxHeight; // fill all the height
11578 }
11579
11580 // Increase sizes here
11581 if (display) {
11582 ctx.font = labelFont;
11583
11584 if (isHorizontal) {
11585 // Labels
11586
11587 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
11588 var lineWidths = me.lineWidths = [0];
11589 var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
11590
11591 ctx.textAlign = 'left';
11592 ctx.textBaseline = 'top';
11593
11594 helpers.each(me.legendItems, function(legendItem, i) {
11595 var boxWidth = getBoxWidth(labelOpts, fontSize);
11596 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11597
11598 if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
11599 totalHeight += fontSize + (labelOpts.padding);
11600 lineWidths[lineWidths.length] = me.left;
11601 }
11602
11603 // Store the hitbox width and height here. Final position will be updated in `draw`
11604 hitboxes[i] = {
11605 left: 0,
11606 top: 0,
11607 width: width,
11608 height: fontSize
11609 };
11610
11611 lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
11612 });
11613
11614 minSize.height += totalHeight;
11615
11616 } else {
11617 var vPadding = labelOpts.padding;
11618 var columnWidths = me.columnWidths = [];
11619 var totalWidth = labelOpts.padding;
11620 var currentColWidth = 0;
11621 var currentColHeight = 0;
11622 var itemHeight = fontSize + vPadding;
11623
11624 helpers.each(me.legendItems, function(legendItem, i) {
11625 var boxWidth = getBoxWidth(labelOpts, fontSize);
11626 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11627
11628 // If too tall, go to new column
11629 if (currentColHeight + itemHeight > minSize.height) {
11630 totalWidth += currentColWidth + labelOpts.padding;
11631 columnWidths.push(currentColWidth); // previous column width
11632
11633 currentColWidth = 0;
11634 currentColHeight = 0;
11635 }
11636
11637 // Get max width
11638 currentColWidth = Math.max(currentColWidth, itemWidth);
11639 currentColHeight += itemHeight;
11640
11641 // Store the hitbox width and height here. Final position will be updated in `draw`
11642 hitboxes[i] = {
11643 left: 0,
11644 top: 0,
11645 width: itemWidth,
11646 height: fontSize
11647 };
11648 });
11649
11650 totalWidth += currentColWidth;
11651 columnWidths.push(currentColWidth);
11652 minSize.width += totalWidth;
11653 }
11654 }
11655
11656 me.width = minSize.width;
11657 me.height = minSize.height;
11658 },
11659 afterFit: noop,
11660
11661 // Shared Methods
11662 isHorizontal: function() {
11663 return this.options.position === 'top' || this.options.position === 'bottom';
11664 },
11665
11666 // Actually draw the legend on the canvas
11667 draw: function() {
11668 var me = this;
11669 var opts = me.options;
11670 var labelOpts = opts.labels;
11671 var globalDefault = defaults.global;
11672 var lineDefault = globalDefault.elements.line;
11673 var legendWidth = me.width;
11674 var lineWidths = me.lineWidths;
11675
11676 if (opts.display) {
11677 var ctx = me.ctx;
11678 var valueOrDefault = helpers.valueOrDefault;
11679 var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
11680 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11681 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11682 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11683 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11684 var cursor;
11685
11686 // Canvas setup
11687 ctx.textAlign = 'left';
11688 ctx.textBaseline = 'middle';
11689 ctx.lineWidth = 0.5;
11690 ctx.strokeStyle = fontColor; // for strikethrough effect
11691 ctx.fillStyle = fontColor; // render in correct colour
11692 ctx.font = labelFont;
11693
11694 var boxWidth = getBoxWidth(labelOpts, fontSize);
11695 var hitboxes = me.legendHitBoxes;
11696
11697 // current position
11698 var drawLegendBox = function(x, y, legendItem) {
11699 if (isNaN(boxWidth) || boxWidth <= 0) {
11700 return;
11701 }
11702
11703 // Set the ctx for the box
11704 ctx.save();
11705
11706 ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
11707 ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
11708 ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
11709 ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
11710 ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
11711 ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
11712 var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
11713
11714 if (ctx.setLineDash) {
11715 // IE 9 and 10 do not support line dash
11716 ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
11717 }
11718
11719 if (opts.labels && opts.labels.usePointStyle) {
11720 // Recalculate x and y for drawPoint() because its expecting
11721 // x and y to be center of figure (instead of top left)
11722 var radius = fontSize * Math.SQRT2 / 2;
11723 var offSet = radius / Math.SQRT2;
11724 var centerX = x + offSet;
11725 var centerY = y + offSet;
11726
11727 // Draw pointStyle as legend symbol
11728 helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
11729 } else {
11730 // Draw box as legend symbol
11731 if (!isLineWidthZero) {
11732 ctx.strokeRect(x, y, boxWidth, fontSize);
11733 }
11734 ctx.fillRect(x, y, boxWidth, fontSize);
11735 }
11736
11737 ctx.restore();
11738 };
11739 var fillText = function(x, y, legendItem, textWidth) {
11740 var halfFontSize = fontSize / 2;
11741 var xLeft = boxWidth + halfFontSize + x;
11742 var yMiddle = y + halfFontSize;
11743
11744 ctx.fillText(legendItem.text, xLeft, yMiddle);
11745
11746 if (legendItem.hidden) {
11747 // Strikethrough the text if hidden
11748 ctx.beginPath();
11749 ctx.lineWidth = 2;
11750 ctx.moveTo(xLeft, yMiddle);
11751 ctx.lineTo(xLeft + textWidth, yMiddle);
11752 ctx.stroke();
11753 }
11754 };
11755
11756 // Horizontal
11757 var isHorizontal = me.isHorizontal();
11758 if (isHorizontal) {
11759 cursor = {
11760 x: me.left + ((legendWidth - lineWidths[0]) / 2),
11761 y: me.top + labelOpts.padding,
11762 line: 0
11763 };
11764 } else {
11765 cursor = {
11766 x: me.left + labelOpts.padding,
11767 y: me.top + labelOpts.padding,
11768 line: 0
11769 };
11770 }
11771
11772 var itemHeight = fontSize + labelOpts.padding;
11773 helpers.each(me.legendItems, function(legendItem, i) {
11774 var textWidth = ctx.measureText(legendItem.text).width;
11775 var width = boxWidth + (fontSize / 2) + textWidth;
11776 var x = cursor.x;
11777 var y = cursor.y;
11778
11779 if (isHorizontal) {
11780 if (x + width >= legendWidth) {
11781 y = cursor.y += itemHeight;
11782 cursor.line++;
11783 x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
11784 }
11785 } else if (y + itemHeight > me.bottom) {
11786 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
11787 y = cursor.y = me.top + labelOpts.padding;
11788 cursor.line++;
11789 }
11790
11791 drawLegendBox(x, y, legendItem);
11792
11793 hitboxes[i].left = x;
11794 hitboxes[i].top = y;
11795
11796 // Fill the actual label
11797 fillText(x, y, legendItem, textWidth);
11798
11799 if (isHorizontal) {
11800 cursor.x += width + (labelOpts.padding);
11801 } else {
11802 cursor.y += itemHeight;
11803 }
11804
11805 });
11806 }
11807 },
11808
11809 /**
11810 * Handle an event
11811 * @private
11812 * @param {IEvent} event - The event to handle
11813 * @return {Boolean} true if a change occured
11814 */
11815 handleEvent: function(e) {
11816 var me = this;
11817 var opts = me.options;
11818 var type = e.type === 'mouseup' ? 'click' : e.type;
11819 var changed = false;
11820
11821 if (type === 'mousemove') {
11822 if (!opts.onHover) {
11823 return;
11824 }
11825 } else if (type === 'click') {
11826 if (!opts.onClick) {
11827 return;
11828 }
11829 } else {
11830 return;
11831 }
11832
11833 // Chart event already has relative position in it
11834 var x = e.x;
11835 var y = e.y;
11836
11837 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
11838 // See if we are touching one of the dataset boxes
11839 var lh = me.legendHitBoxes;
11840 for (var i = 0; i < lh.length; ++i) {
11841 var hitBox = lh[i];
11842
11843 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
11844 // Touching an element
11845 if (type === 'click') {
11846 // use e.native for backwards compatibility
11847 opts.onClick.call(me, e.native, me.legendItems[i]);
11848 changed = true;
11849 break;
11850 } else if (type === 'mousemove') {
11851 // use e.native for backwards compatibility
11852 opts.onHover.call(me, e.native, me.legendItems[i]);
11853 changed = true;
11854 break;
11855 }
11856 }
11857 }
11858 }
11859
11860 return changed;
11861 }
11862});
11863
11864function createNewLegendAndAttach(chart, legendOpts) {
11865 var legend = new Legend({
11866 ctx: chart.ctx,
11867 options: legendOpts,
11868 chart: chart
11869 });
11870
11871 layouts.configure(chart, legend, legendOpts);
11872 layouts.addBox(chart, legend);
11873 chart.legend = legend;
11874}
11875
11876module.exports = {
11877 id: 'legend',
11878
11879 /**
11880 * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making
11881 * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of
11882 * the plugin, which one will be re-exposed in the chart.js file.
11883 * https://github.com/chartjs/Chart.js/pull/2640
11884 * @private
11885 */
11886 _element: Legend,
11887
11888 beforeInit: function(chart) {
11889 var legendOpts = chart.options.legend;
11890
11891 if (legendOpts) {
11892 createNewLegendAndAttach(chart, legendOpts);
11893 }
11894 },
11895
11896 beforeUpdate: function(chart) {
11897 var legendOpts = chart.options.legend;
11898 var legend = chart.legend;
11899
11900 if (legendOpts) {
11901 helpers.mergeIf(legendOpts, defaults.global.legend);
11902
11903 if (legend) {
11904 layouts.configure(chart, legend, legendOpts);
11905 legend.options = legendOpts;
11906 } else {
11907 createNewLegendAndAttach(chart, legendOpts);
11908 }
11909 } else if (legend) {
11910 layouts.removeBox(chart, legend);
11911 delete chart.legend;
11912 }
11913 },
11914
11915 afterEvent: function(chart, e) {
11916 var legend = chart.legend;
11917 if (legend) {
11918 legend.handleEvent(e);
11919 }
11920 }
11921};
11922
11923},{"25":25,"26":26,"30":30,"45":45}],52:[function(require,module,exports){
11924'use strict';
11925
11926var defaults = require(25);
11927var Element = require(26);
11928var helpers = require(45);
11929var layouts = require(30);
11930
11931var noop = helpers.noop;
11932
11933defaults._set('global', {
11934 title: {
11935 display: false,
11936 fontStyle: 'bold',
11937 fullWidth: true,
11938 lineHeight: 1.2,
11939 padding: 10,
11940 position: 'top',
11941 text: '',
11942 weight: 2000 // by default greater than legend (1000) to be above
11943 }
11944});
11945
11946/**
11947 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
11948 */
11949var Title = Element.extend({
11950 initialize: function(config) {
11951 var me = this;
11952 helpers.extend(me, config);
11953
11954 // Contains hit boxes for each dataset (in dataset order)
11955 me.legendHitBoxes = [];
11956 },
11957
11958 // These methods are ordered by lifecycle. Utilities then follow.
11959
11960 beforeUpdate: noop,
11961 update: function(maxWidth, maxHeight, margins) {
11962 var me = this;
11963
11964 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11965 me.beforeUpdate();
11966
11967 // Absorb the master measurements
11968 me.maxWidth = maxWidth;
11969 me.maxHeight = maxHeight;
11970 me.margins = margins;
11971
11972 // Dimensions
11973 me.beforeSetDimensions();
11974 me.setDimensions();
11975 me.afterSetDimensions();
11976 // Labels
11977 me.beforeBuildLabels();
11978 me.buildLabels();
11979 me.afterBuildLabels();
11980
11981 // Fit
11982 me.beforeFit();
11983 me.fit();
11984 me.afterFit();
11985 //
11986 me.afterUpdate();
11987
11988 return me.minSize;
11989
11990 },
11991 afterUpdate: noop,
11992
11993 //
11994
11995 beforeSetDimensions: noop,
11996 setDimensions: function() {
11997 var me = this;
11998 // Set the unconstrained dimension before label rotation
11999 if (me.isHorizontal()) {
12000 // Reset position before calculating rotation
12001 me.width = me.maxWidth;
12002 me.left = 0;
12003 me.right = me.width;
12004 } else {
12005 me.height = me.maxHeight;
12006
12007 // Reset position before calculating rotation
12008 me.top = 0;
12009 me.bottom = me.height;
12010 }
12011
12012 // Reset padding
12013 me.paddingLeft = 0;
12014 me.paddingTop = 0;
12015 me.paddingRight = 0;
12016 me.paddingBottom = 0;
12017
12018 // Reset minSize
12019 me.minSize = {
12020 width: 0,
12021 height: 0
12022 };
12023 },
12024 afterSetDimensions: noop,
12025
12026 //
12027
12028 beforeBuildLabels: noop,
12029 buildLabels: noop,
12030 afterBuildLabels: noop,
12031
12032 //
12033
12034 beforeFit: noop,
12035 fit: function() {
12036 var me = this;
12037 var valueOrDefault = helpers.valueOrDefault;
12038 var opts = me.options;
12039 var display = opts.display;
12040 var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
12041 var minSize = me.minSize;
12042 var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
12043 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12044 var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
12045
12046 if (me.isHorizontal()) {
12047 minSize.width = me.maxWidth; // fill all the width
12048 minSize.height = textSize;
12049 } else {
12050 minSize.width = textSize;
12051 minSize.height = me.maxHeight; // fill all the height
12052 }
12053
12054 me.width = minSize.width;
12055 me.height = minSize.height;
12056
12057 },
12058 afterFit: noop,
12059
12060 // Shared Methods
12061 isHorizontal: function() {
12062 var pos = this.options.position;
12063 return pos === 'top' || pos === 'bottom';
12064 },
12065
12066 // Actually draw the title block on the canvas
12067 draw: function() {
12068 var me = this;
12069 var ctx = me.ctx;
12070 var valueOrDefault = helpers.valueOrDefault;
12071 var opts = me.options;
12072 var globalDefaults = defaults.global;
12073
12074 if (opts.display) {
12075 var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
12076 var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
12077 var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
12078 var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
12079 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12080 var offset = lineHeight / 2 + opts.padding;
12081 var rotation = 0;
12082 var top = me.top;
12083 var left = me.left;
12084 var bottom = me.bottom;
12085 var right = me.right;
12086 var maxWidth, titleX, titleY;
12087
12088 ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
12089 ctx.font = titleFont;
12090
12091 // Horizontal
12092 if (me.isHorizontal()) {
12093 titleX = left + ((right - left) / 2); // midpoint of the width
12094 titleY = top + offset;
12095 maxWidth = right - left;
12096 } else {
12097 titleX = opts.position === 'left' ? left + offset : right - offset;
12098 titleY = top + ((bottom - top) / 2);
12099 maxWidth = bottom - top;
12100 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
12101 }
12102
12103 ctx.save();
12104 ctx.translate(titleX, titleY);
12105 ctx.rotate(rotation);
12106 ctx.textAlign = 'center';
12107 ctx.textBaseline = 'middle';
12108
12109 var text = opts.text;
12110 if (helpers.isArray(text)) {
12111 var y = 0;
12112 for (var i = 0; i < text.length; ++i) {
12113 ctx.fillText(text[i], 0, y, maxWidth);
12114 y += lineHeight;
12115 }
12116 } else {
12117 ctx.fillText(text, 0, 0, maxWidth);
12118 }
12119
12120 ctx.restore();
12121 }
12122 }
12123});
12124
12125function createNewTitleBlockAndAttach(chart, titleOpts) {
12126 var title = new Title({
12127 ctx: chart.ctx,
12128 options: titleOpts,
12129 chart: chart
12130 });
12131
12132 layouts.configure(chart, title, titleOpts);
12133 layouts.addBox(chart, title);
12134 chart.titleBlock = title;
12135}
12136
12137module.exports = {
12138 id: 'title',
12139
12140 /**
12141 * Backward compatibility: since 2.1.5, the title is registered as a plugin, making
12142 * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of
12143 * the plugin, which one will be re-exposed in the chart.js file.
12144 * https://github.com/chartjs/Chart.js/pull/2640
12145 * @private
12146 */
12147 _element: Title,
12148
12149 beforeInit: function(chart) {
12150 var titleOpts = chart.options.title;
12151
12152 if (titleOpts) {
12153 createNewTitleBlockAndAttach(chart, titleOpts);
12154 }
12155 },
12156
12157 beforeUpdate: function(chart) {
12158 var titleOpts = chart.options.title;
12159 var titleBlock = chart.titleBlock;
12160
12161 if (titleOpts) {
12162 helpers.mergeIf(titleOpts, defaults.global.title);
12163
12164 if (titleBlock) {
12165 layouts.configure(chart, titleBlock, titleOpts);
12166 titleBlock.options = titleOpts;
12167 } else {
12168 createNewTitleBlockAndAttach(chart, titleOpts);
12169 }
12170 } else if (titleBlock) {
12171 layouts.removeBox(chart, titleBlock);
12172 delete chart.titleBlock;
12173 }
12174 }
12175};
12176
12177},{"25":25,"26":26,"30":30,"45":45}],53:[function(require,module,exports){
12178'use strict';
12179
12180module.exports = function(Chart) {
12181
12182 // Default config for a category scale
12183 var defaultConfig = {
12184 position: 'bottom'
12185 };
12186
12187 var DatasetScale = Chart.Scale.extend({
12188 /**
12189 * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
12190 * else fall back to data.labels
12191 * @private
12192 */
12193 getLabels: function() {
12194 var data = this.chart.data;
12195 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
12196 },
12197
12198 determineDataLimits: function() {
12199 var me = this;
12200 var labels = me.getLabels();
12201 me.minIndex = 0;
12202 me.maxIndex = labels.length - 1;
12203 var findIndex;
12204
12205 if (me.options.ticks.min !== undefined) {
12206 // user specified min value
12207 findIndex = labels.indexOf(me.options.ticks.min);
12208 me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
12209 }
12210
12211 if (me.options.ticks.max !== undefined) {
12212 // user specified max value
12213 findIndex = labels.indexOf(me.options.ticks.max);
12214 me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
12215 }
12216
12217 me.min = labels[me.minIndex];
12218 me.max = labels[me.maxIndex];
12219 },
12220
12221 buildTicks: function() {
12222 var me = this;
12223 var labels = me.getLabels();
12224 // If we are viewing some subset of labels, slice the original array
12225 me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
12226 },
12227
12228 getLabelForIndex: function(index, datasetIndex) {
12229 var me = this;
12230 var data = me.chart.data;
12231 var isHorizontal = me.isHorizontal();
12232
12233 if (data.yLabels && !isHorizontal) {
12234 return me.getRightValue(data.datasets[datasetIndex].data[index]);
12235 }
12236 return me.ticks[index - me.minIndex];
12237 },
12238
12239 // Used to get data value locations. Value can either be an index or a numerical value
12240 getPixelForValue: function(value, index) {
12241 var me = this;
12242 var offset = me.options.offset;
12243 // 1 is added because we need the length but we have the indexes
12244 var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
12245
12246 // If value is a data object, then index is the index in the data array,
12247 // not the index of the scale. We need to change that.
12248 var valueCategory;
12249 if (value !== undefined && value !== null) {
12250 valueCategory = me.isHorizontal() ? value.x : value.y;
12251 }
12252 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
12253 var labels = me.getLabels();
12254 value = valueCategory || value;
12255 var idx = labels.indexOf(value);
12256 index = idx !== -1 ? idx : index;
12257 }
12258
12259 if (me.isHorizontal()) {
12260 var valueWidth = me.width / offsetAmt;
12261 var widthOffset = (valueWidth * (index - me.minIndex));
12262
12263 if (offset) {
12264 widthOffset += (valueWidth / 2);
12265 }
12266
12267 return me.left + Math.round(widthOffset);
12268 }
12269 var valueHeight = me.height / offsetAmt;
12270 var heightOffset = (valueHeight * (index - me.minIndex));
12271
12272 if (offset) {
12273 heightOffset += (valueHeight / 2);
12274 }
12275
12276 return me.top + Math.round(heightOffset);
12277 },
12278 getPixelForTick: function(index) {
12279 return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
12280 },
12281 getValueForPixel: function(pixel) {
12282 var me = this;
12283 var offset = me.options.offset;
12284 var value;
12285 var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
12286 var horz = me.isHorizontal();
12287 var valueDimension = (horz ? me.width : me.height) / offsetAmt;
12288
12289 pixel -= horz ? me.left : me.top;
12290
12291 if (offset) {
12292 pixel -= (valueDimension / 2);
12293 }
12294
12295 if (pixel <= 0) {
12296 value = 0;
12297 } else {
12298 value = Math.round(pixel / valueDimension);
12299 }
12300
12301 return value + me.minIndex;
12302 },
12303 getBasePixel: function() {
12304 return this.bottom;
12305 }
12306 });
12307
12308 Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
12309
12310};
12311
12312},{}],54:[function(require,module,exports){
12313'use strict';
12314
12315var defaults = require(25);
12316var helpers = require(45);
12317var Ticks = require(34);
12318
12319module.exports = function(Chart) {
12320
12321 var defaultConfig = {
12322 position: 'left',
12323 ticks: {
12324 callback: Ticks.formatters.linear
12325 }
12326 };
12327
12328 var LinearScale = Chart.LinearScaleBase.extend({
12329
12330 determineDataLimits: function() {
12331 var me = this;
12332 var opts = me.options;
12333 var chart = me.chart;
12334 var data = chart.data;
12335 var datasets = data.datasets;
12336 var isHorizontal = me.isHorizontal();
12337 var DEFAULT_MIN = 0;
12338 var DEFAULT_MAX = 1;
12339
12340 function IDMatches(meta) {
12341 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12342 }
12343
12344 // First Calculate the range
12345 me.min = null;
12346 me.max = null;
12347
12348 var hasStacks = opts.stacked;
12349 if (hasStacks === undefined) {
12350 helpers.each(datasets, function(dataset, datasetIndex) {
12351 if (hasStacks) {
12352 return;
12353 }
12354
12355 var meta = chart.getDatasetMeta(datasetIndex);
12356 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12357 meta.stack !== undefined) {
12358 hasStacks = true;
12359 }
12360 });
12361 }
12362
12363 if (opts.stacked || hasStacks) {
12364 var valuesPerStack = {};
12365
12366 helpers.each(datasets, function(dataset, datasetIndex) {
12367 var meta = chart.getDatasetMeta(datasetIndex);
12368 var key = [
12369 meta.type,
12370 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12371 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12372 meta.stack
12373 ].join('.');
12374
12375 if (valuesPerStack[key] === undefined) {
12376 valuesPerStack[key] = {
12377 positiveValues: [],
12378 negativeValues: []
12379 };
12380 }
12381
12382 // Store these per type
12383 var positiveValues = valuesPerStack[key].positiveValues;
12384 var negativeValues = valuesPerStack[key].negativeValues;
12385
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 positiveValues[index] = positiveValues[index] || 0;
12394 negativeValues[index] = negativeValues[index] || 0;
12395
12396 if (opts.relativePoints) {
12397 positiveValues[index] = 100;
12398 } else if (value < 0) {
12399 negativeValues[index] += value;
12400 } else {
12401 positiveValues[index] += value;
12402 }
12403 });
12404 }
12405 });
12406
12407 helpers.each(valuesPerStack, function(valuesForType) {
12408 var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
12409 var minVal = helpers.min(values);
12410 var maxVal = helpers.max(values);
12411 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12412 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12413 });
12414
12415 } else {
12416 helpers.each(datasets, function(dataset, datasetIndex) {
12417 var meta = chart.getDatasetMeta(datasetIndex);
12418 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12419 helpers.each(dataset.data, function(rawValue, index) {
12420 var value = +me.getRightValue(rawValue);
12421 if (isNaN(value) || meta.data[index].hidden) {
12422 return;
12423 }
12424
12425 if (me.min === null) {
12426 me.min = value;
12427 } else if (value < me.min) {
12428 me.min = value;
12429 }
12430
12431 if (me.max === null) {
12432 me.max = value;
12433 } else if (value > me.max) {
12434 me.max = value;
12435 }
12436 });
12437 }
12438 });
12439 }
12440
12441 me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
12442 me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
12443
12444 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
12445 this.handleTickRangeOptions();
12446 },
12447 getTickLimit: function() {
12448 var maxTicks;
12449 var me = this;
12450 var tickOpts = me.options.ticks;
12451
12452 if (me.isHorizontal()) {
12453 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
12454 } else {
12455 // The factor of 2 used to scale the font size has been experimentally determined.
12456 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize);
12457 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
12458 }
12459
12460 return maxTicks;
12461 },
12462 // Called after the ticks are built. We need
12463 handleDirectionalChanges: function() {
12464 if (!this.isHorizontal()) {
12465 // We are in a vertical orientation. The top value is the highest. So reverse the array
12466 this.ticks.reverse();
12467 }
12468 },
12469 getLabelForIndex: function(index, datasetIndex) {
12470 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12471 },
12472 // Utils
12473 getPixelForValue: function(value) {
12474 // This must be called after fit has been run so that
12475 // this.left, this.top, this.right, and this.bottom have been defined
12476 var me = this;
12477 var start = me.start;
12478
12479 var rightValue = +me.getRightValue(value);
12480 var pixel;
12481 var range = me.end - start;
12482
12483 if (me.isHorizontal()) {
12484 pixel = me.left + (me.width / range * (rightValue - start));
12485 } else {
12486 pixel = me.bottom - (me.height / range * (rightValue - start));
12487 }
12488 return pixel;
12489 },
12490 getValueForPixel: function(pixel) {
12491 var me = this;
12492 var isHorizontal = me.isHorizontal();
12493 var innerDimension = isHorizontal ? me.width : me.height;
12494 var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
12495 return me.start + ((me.end - me.start) * offset);
12496 },
12497 getPixelForTick: function(index) {
12498 return this.getPixelForValue(this.ticksAsNumbers[index]);
12499 }
12500 });
12501 Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
12502
12503};
12504
12505},{"25":25,"34":34,"45":45}],55:[function(require,module,exports){
12506'use strict';
12507
12508var helpers = require(45);
12509
12510/**
12511 * Generate a set of linear ticks
12512 * @param generationOptions the options used to generate the ticks
12513 * @param dataRange the range of the data
12514 * @returns {Array<Number>} array of tick values
12515 */
12516function generateTicks(generationOptions, dataRange) {
12517 var ticks = [];
12518 // To get a "nice" value for the tick spacing, we will use the appropriately named
12519 // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
12520 // for details.
12521
12522 var spacing;
12523 if (generationOptions.stepSize && generationOptions.stepSize > 0) {
12524 spacing = generationOptions.stepSize;
12525 } else {
12526 var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
12527 spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
12528 }
12529 var niceMin = Math.floor(dataRange.min / spacing) * spacing;
12530 var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
12531
12532 // If min, max and stepSize is set and they make an evenly spaced scale use it.
12533 if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
12534 // If very close to our whole number, use it.
12535 if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
12536 niceMin = generationOptions.min;
12537 niceMax = generationOptions.max;
12538 }
12539 }
12540
12541 var numSpaces = (niceMax - niceMin) / spacing;
12542 // If very close to our rounded value, use it.
12543 if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
12544 numSpaces = Math.round(numSpaces);
12545 } else {
12546 numSpaces = Math.ceil(numSpaces);
12547 }
12548
12549 var precision = 1;
12550 if (spacing < 1) {
12551 precision = Math.pow(10, spacing.toString().length - 2);
12552 niceMin = Math.round(niceMin * precision) / precision;
12553 niceMax = Math.round(niceMax * precision) / precision;
12554 }
12555 ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
12556 for (var j = 1; j < numSpaces; ++j) {
12557 ticks.push(Math.round((niceMin + j * spacing) * precision) / precision);
12558 }
12559 ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
12560
12561 return ticks;
12562}
12563
12564
12565module.exports = function(Chart) {
12566
12567 var noop = helpers.noop;
12568
12569 Chart.LinearScaleBase = Chart.Scale.extend({
12570 getRightValue: function(value) {
12571 if (typeof value === 'string') {
12572 return +value;
12573 }
12574 return Chart.Scale.prototype.getRightValue.call(this, value);
12575 },
12576
12577 handleTickRangeOptions: function() {
12578 var me = this;
12579 var opts = me.options;
12580 var tickOpts = opts.ticks;
12581
12582 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
12583 // do nothing since that would make the chart weird. If the user really wants a weird chart
12584 // axis, they can manually override it
12585 if (tickOpts.beginAtZero) {
12586 var minSign = helpers.sign(me.min);
12587 var maxSign = helpers.sign(me.max);
12588
12589 if (minSign < 0 && maxSign < 0) {
12590 // move the top up to 0
12591 me.max = 0;
12592 } else if (minSign > 0 && maxSign > 0) {
12593 // move the bottom down to 0
12594 me.min = 0;
12595 }
12596 }
12597
12598 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
12599 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
12600
12601 if (tickOpts.min !== undefined) {
12602 me.min = tickOpts.min;
12603 } else if (tickOpts.suggestedMin !== undefined) {
12604 if (me.min === null) {
12605 me.min = tickOpts.suggestedMin;
12606 } else {
12607 me.min = Math.min(me.min, tickOpts.suggestedMin);
12608 }
12609 }
12610
12611 if (tickOpts.max !== undefined) {
12612 me.max = tickOpts.max;
12613 } else if (tickOpts.suggestedMax !== undefined) {
12614 if (me.max === null) {
12615 me.max = tickOpts.suggestedMax;
12616 } else {
12617 me.max = Math.max(me.max, tickOpts.suggestedMax);
12618 }
12619 }
12620
12621 if (setMin !== setMax) {
12622 // We set the min or the max but not both.
12623 // So ensure that our range is good
12624 // Inverted or 0 length range can happen when
12625 // ticks.min is set, and no datasets are visible
12626 if (me.min >= me.max) {
12627 if (setMin) {
12628 me.max = me.min + 1;
12629 } else {
12630 me.min = me.max - 1;
12631 }
12632 }
12633 }
12634
12635 if (me.min === me.max) {
12636 me.max++;
12637
12638 if (!tickOpts.beginAtZero) {
12639 me.min--;
12640 }
12641 }
12642 },
12643 getTickLimit: noop,
12644 handleDirectionalChanges: noop,
12645
12646 buildTicks: function() {
12647 var me = this;
12648 var opts = me.options;
12649 var tickOpts = opts.ticks;
12650
12651 // Figure out what the max number of ticks we can support it is based on the size of
12652 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
12653 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
12654 // the graph. Make sure we always have at least 2 ticks
12655 var maxTicks = me.getTickLimit();
12656 maxTicks = Math.max(2, maxTicks);
12657
12658 var numericGeneratorOptions = {
12659 maxTicks: maxTicks,
12660 min: tickOpts.min,
12661 max: tickOpts.max,
12662 stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
12663 };
12664 var ticks = me.ticks = generateTicks(numericGeneratorOptions, me);
12665
12666 me.handleDirectionalChanges();
12667
12668 // At this point, we need to update our max and min given the tick values since we have expanded the
12669 // range of the scale
12670 me.max = helpers.max(ticks);
12671 me.min = helpers.min(ticks);
12672
12673 if (tickOpts.reverse) {
12674 ticks.reverse();
12675
12676 me.start = me.max;
12677 me.end = me.min;
12678 } else {
12679 me.start = me.min;
12680 me.end = me.max;
12681 }
12682 },
12683 convertTicksToLabels: function() {
12684 var me = this;
12685 me.ticksAsNumbers = me.ticks.slice();
12686 me.zeroLineIndex = me.ticks.indexOf(0);
12687
12688 Chart.Scale.prototype.convertTicksToLabels.call(me);
12689 }
12690 });
12691};
12692
12693},{"45":45}],56:[function(require,module,exports){
12694'use strict';
12695
12696var helpers = require(45);
12697var Ticks = require(34);
12698
12699/**
12700 * Generate a set of logarithmic ticks
12701 * @param generationOptions the options used to generate the ticks
12702 * @param dataRange the range of the data
12703 * @returns {Array<Number>} array of tick values
12704 */
12705function generateTicks(generationOptions, dataRange) {
12706 var ticks = [];
12707 var valueOrDefault = helpers.valueOrDefault;
12708
12709 // Figure out what the max number of ticks we can support it is based on the size of
12710 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
12711 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
12712 // the graph
12713 var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
12714
12715 var endExp = Math.floor(helpers.log10(dataRange.max));
12716 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
12717 var exp, significand;
12718
12719 if (tickVal === 0) {
12720 exp = Math.floor(helpers.log10(dataRange.minNotZero));
12721 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
12722
12723 ticks.push(tickVal);
12724 tickVal = significand * Math.pow(10, exp);
12725 } else {
12726 exp = Math.floor(helpers.log10(tickVal));
12727 significand = Math.floor(tickVal / Math.pow(10, exp));
12728 }
12729 var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
12730
12731 do {
12732 ticks.push(tickVal);
12733
12734 ++significand;
12735 if (significand === 10) {
12736 significand = 1;
12737 ++exp;
12738 precision = exp >= 0 ? 1 : precision;
12739 }
12740
12741 tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
12742 } while (exp < endExp || (exp === endExp && significand < endSignificand));
12743
12744 var lastTick = valueOrDefault(generationOptions.max, tickVal);
12745 ticks.push(lastTick);
12746
12747 return ticks;
12748}
12749
12750
12751module.exports = function(Chart) {
12752
12753 var defaultConfig = {
12754 position: 'left',
12755
12756 // label settings
12757 ticks: {
12758 callback: Ticks.formatters.logarithmic
12759 }
12760 };
12761
12762 var LogarithmicScale = Chart.Scale.extend({
12763 determineDataLimits: function() {
12764 var me = this;
12765 var opts = me.options;
12766 var chart = me.chart;
12767 var data = chart.data;
12768 var datasets = data.datasets;
12769 var isHorizontal = me.isHorizontal();
12770 function IDMatches(meta) {
12771 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12772 }
12773
12774 // Calculate Range
12775 me.min = null;
12776 me.max = null;
12777 me.minNotZero = null;
12778
12779 var hasStacks = opts.stacked;
12780 if (hasStacks === undefined) {
12781 helpers.each(datasets, function(dataset, datasetIndex) {
12782 if (hasStacks) {
12783 return;
12784 }
12785
12786 var meta = chart.getDatasetMeta(datasetIndex);
12787 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12788 meta.stack !== undefined) {
12789 hasStacks = true;
12790 }
12791 });
12792 }
12793
12794 if (opts.stacked || hasStacks) {
12795 var valuesPerStack = {};
12796
12797 helpers.each(datasets, function(dataset, datasetIndex) {
12798 var meta = chart.getDatasetMeta(datasetIndex);
12799 var key = [
12800 meta.type,
12801 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12802 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12803 meta.stack
12804 ].join('.');
12805
12806 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12807 if (valuesPerStack[key] === undefined) {
12808 valuesPerStack[key] = [];
12809 }
12810
12811 helpers.each(dataset.data, function(rawValue, index) {
12812 var values = valuesPerStack[key];
12813 var value = +me.getRightValue(rawValue);
12814 // invalid, hidden and negative values are ignored
12815 if (isNaN(value) || meta.data[index].hidden || value < 0) {
12816 return;
12817 }
12818 values[index] = values[index] || 0;
12819 values[index] += value;
12820 });
12821 }
12822 });
12823
12824 helpers.each(valuesPerStack, function(valuesForType) {
12825 if (valuesForType.length > 0) {
12826 var minVal = helpers.min(valuesForType);
12827 var maxVal = helpers.max(valuesForType);
12828 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12829 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12830 }
12831 });
12832
12833 } else {
12834 helpers.each(datasets, function(dataset, datasetIndex) {
12835 var meta = chart.getDatasetMeta(datasetIndex);
12836 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12837 helpers.each(dataset.data, function(rawValue, index) {
12838 var value = +me.getRightValue(rawValue);
12839 // invalid, hidden and negative values are ignored
12840 if (isNaN(value) || meta.data[index].hidden || value < 0) {
12841 return;
12842 }
12843
12844 if (me.min === null) {
12845 me.min = value;
12846 } else if (value < me.min) {
12847 me.min = value;
12848 }
12849
12850 if (me.max === null) {
12851 me.max = value;
12852 } else if (value > me.max) {
12853 me.max = value;
12854 }
12855
12856 if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
12857 me.minNotZero = value;
12858 }
12859 });
12860 }
12861 });
12862 }
12863
12864 // Common base implementation to handle ticks.min, ticks.max
12865 this.handleTickRangeOptions();
12866 },
12867 handleTickRangeOptions: function() {
12868 var me = this;
12869 var opts = me.options;
12870 var tickOpts = opts.ticks;
12871 var valueOrDefault = helpers.valueOrDefault;
12872 var DEFAULT_MIN = 1;
12873 var DEFAULT_MAX = 10;
12874
12875 me.min = valueOrDefault(tickOpts.min, me.min);
12876 me.max = valueOrDefault(tickOpts.max, me.max);
12877
12878 if (me.min === me.max) {
12879 if (me.min !== 0 && me.min !== null) {
12880 me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
12881 me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
12882 } else {
12883 me.min = DEFAULT_MIN;
12884 me.max = DEFAULT_MAX;
12885 }
12886 }
12887 if (me.min === null) {
12888 me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1);
12889 }
12890 if (me.max === null) {
12891 me.max = me.min !== 0
12892 ? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1)
12893 : DEFAULT_MAX;
12894 }
12895 if (me.minNotZero === null) {
12896 if (me.min > 0) {
12897 me.minNotZero = me.min;
12898 } else if (me.max < 1) {
12899 me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max)));
12900 } else {
12901 me.minNotZero = DEFAULT_MIN;
12902 }
12903 }
12904 },
12905 buildTicks: function() {
12906 var me = this;
12907 var opts = me.options;
12908 var tickOpts = opts.ticks;
12909 var reverse = !me.isHorizontal();
12910
12911 var generationOptions = {
12912 min: tickOpts.min,
12913 max: tickOpts.max
12914 };
12915 var ticks = me.ticks = generateTicks(generationOptions, me);
12916
12917 // At this point, we need to update our max and min given the tick values since we have expanded the
12918 // range of the scale
12919 me.max = helpers.max(ticks);
12920 me.min = helpers.min(ticks);
12921
12922 if (tickOpts.reverse) {
12923 reverse = !reverse;
12924 me.start = me.max;
12925 me.end = me.min;
12926 } else {
12927 me.start = me.min;
12928 me.end = me.max;
12929 }
12930 if (reverse) {
12931 ticks.reverse();
12932 }
12933 },
12934 convertTicksToLabels: function() {
12935 this.tickValues = this.ticks.slice();
12936
12937 Chart.Scale.prototype.convertTicksToLabels.call(this);
12938 },
12939 // Get the correct tooltip label
12940 getLabelForIndex: function(index, datasetIndex) {
12941 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12942 },
12943 getPixelForTick: function(index) {
12944 return this.getPixelForValue(this.tickValues[index]);
12945 },
12946 /**
12947 * Returns the value of the first tick.
12948 * @param {Number} value - The minimum not zero value.
12949 * @return {Number} The first tick value.
12950 * @private
12951 */
12952 _getFirstTickValue: function(value) {
12953 var exp = Math.floor(helpers.log10(value));
12954 var significand = Math.floor(value / Math.pow(10, exp));
12955
12956 return significand * Math.pow(10, exp);
12957 },
12958 getPixelForValue: function(value) {
12959 var me = this;
12960 var reverse = me.options.ticks.reverse;
12961 var log10 = helpers.log10;
12962 var firstTickValue = me._getFirstTickValue(me.minNotZero);
12963 var offset = 0;
12964 var innerDimension, pixel, start, end, sign;
12965
12966 value = +me.getRightValue(value);
12967 if (reverse) {
12968 start = me.end;
12969 end = me.start;
12970 sign = -1;
12971 } else {
12972 start = me.start;
12973 end = me.end;
12974 sign = 1;
12975 }
12976 if (me.isHorizontal()) {
12977 innerDimension = me.width;
12978 pixel = reverse ? me.right : me.left;
12979 } else {
12980 innerDimension = me.height;
12981 sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0)
12982 pixel = reverse ? me.top : me.bottom;
12983 }
12984 if (value !== start) {
12985 if (start === 0) { // include zero tick
12986 offset = helpers.getValueOrDefault(
12987 me.options.ticks.fontSize,
12988 Chart.defaults.global.defaultFontSize
12989 );
12990 innerDimension -= offset;
12991 start = firstTickValue;
12992 }
12993 if (value !== 0) {
12994 offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start));
12995 }
12996 pixel += sign * offset;
12997 }
12998 return pixel;
12999 },
13000 getValueForPixel: function(pixel) {
13001 var me = this;
13002 var reverse = me.options.ticks.reverse;
13003 var log10 = helpers.log10;
13004 var firstTickValue = me._getFirstTickValue(me.minNotZero);
13005 var innerDimension, start, end, value;
13006
13007 if (reverse) {
13008 start = me.end;
13009 end = me.start;
13010 } else {
13011 start = me.start;
13012 end = me.end;
13013 }
13014 if (me.isHorizontal()) {
13015 innerDimension = me.width;
13016 value = reverse ? me.right - pixel : pixel - me.left;
13017 } else {
13018 innerDimension = me.height;
13019 value = reverse ? pixel - me.top : me.bottom - pixel;
13020 }
13021 if (value !== start) {
13022 if (start === 0) { // include zero tick
13023 var offset = helpers.getValueOrDefault(
13024 me.options.ticks.fontSize,
13025 Chart.defaults.global.defaultFontSize
13026 );
13027 value -= offset;
13028 innerDimension -= offset;
13029 start = firstTickValue;
13030 }
13031 value *= log10(end) - log10(start);
13032 value /= innerDimension;
13033 value = Math.pow(10, log10(start) + value);
13034 }
13035 return value;
13036 }
13037 });
13038 Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
13039
13040};
13041
13042},{"34":34,"45":45}],57:[function(require,module,exports){
13043'use strict';
13044
13045var defaults = require(25);
13046var helpers = require(45);
13047var Ticks = require(34);
13048
13049module.exports = function(Chart) {
13050
13051 var globalDefaults = defaults.global;
13052
13053 var defaultConfig = {
13054 display: true,
13055
13056 // Boolean - Whether to animate scaling the chart from the centre
13057 animate: true,
13058 position: 'chartArea',
13059
13060 angleLines: {
13061 display: true,
13062 color: 'rgba(0, 0, 0, 0.1)',
13063 lineWidth: 1
13064 },
13065
13066 gridLines: {
13067 circular: false
13068 },
13069
13070 // label settings
13071 ticks: {
13072 // Boolean - Show a backdrop to the scale label
13073 showLabelBackdrop: true,
13074
13075 // String - The colour of the label backdrop
13076 backdropColor: 'rgba(255,255,255,0.75)',
13077
13078 // Number - The backdrop padding above & below the label in pixels
13079 backdropPaddingY: 2,
13080
13081 // Number - The backdrop padding to the side of the label in pixels
13082 backdropPaddingX: 2,
13083
13084 callback: Ticks.formatters.linear
13085 },
13086
13087 pointLabels: {
13088 // Boolean - if true, show point labels
13089 display: true,
13090
13091 // Number - Point label font size in pixels
13092 fontSize: 10,
13093
13094 // Function - Used to convert point labels
13095 callback: function(label) {
13096 return label;
13097 }
13098 }
13099 };
13100
13101 function getValueCount(scale) {
13102 var opts = scale.options;
13103 return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
13104 }
13105
13106 function getPointLabelFontOptions(scale) {
13107 var pointLabelOptions = scale.options.pointLabels;
13108 var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
13109 var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
13110 var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
13111 var font = helpers.fontString(fontSize, fontStyle, fontFamily);
13112
13113 return {
13114 size: fontSize,
13115 style: fontStyle,
13116 family: fontFamily,
13117 font: font
13118 };
13119 }
13120
13121 function measureLabelSize(ctx, fontSize, label) {
13122 if (helpers.isArray(label)) {
13123 return {
13124 w: helpers.longestText(ctx, ctx.font, label),
13125 h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
13126 };
13127 }
13128
13129 return {
13130 w: ctx.measureText(label).width,
13131 h: fontSize
13132 };
13133 }
13134
13135 function determineLimits(angle, pos, size, min, max) {
13136 if (angle === min || angle === max) {
13137 return {
13138 start: pos - (size / 2),
13139 end: pos + (size / 2)
13140 };
13141 } else if (angle < min || angle > max) {
13142 return {
13143 start: pos - size - 5,
13144 end: pos
13145 };
13146 }
13147
13148 return {
13149 start: pos,
13150 end: pos + size + 5
13151 };
13152 }
13153
13154 /**
13155 * Helper function to fit a radial linear scale with point labels
13156 */
13157 function fitWithPointLabels(scale) {
13158 /*
13159 * Right, this is really confusing and there is a lot of maths going on here
13160 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
13161 *
13162 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
13163 *
13164 * Solution:
13165 *
13166 * We assume the radius of the polygon is half the size of the canvas at first
13167 * at each index we check if the text overlaps.
13168 *
13169 * Where it does, we store that angle and that index.
13170 *
13171 * After finding the largest index and angle we calculate how much we need to remove
13172 * from the shape radius to move the point inwards by that x.
13173 *
13174 * We average the left and right distances to get the maximum shape radius that can fit in the box
13175 * along with labels.
13176 *
13177 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
13178 * on each side, removing that from the size, halving it and adding the left x protrusion width.
13179 *
13180 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
13181 * and position it in the most space efficient manner
13182 *
13183 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
13184 */
13185
13186 var plFont = getPointLabelFontOptions(scale);
13187
13188 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
13189 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
13190 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13191 var furthestLimits = {
13192 r: scale.width,
13193 l: 0,
13194 t: scale.height,
13195 b: 0
13196 };
13197 var furthestAngles = {};
13198 var i, textSize, pointPosition;
13199
13200 scale.ctx.font = plFont.font;
13201 scale._pointLabelSizes = [];
13202
13203 var valueCount = getValueCount(scale);
13204 for (i = 0; i < valueCount; i++) {
13205 pointPosition = scale.getPointPosition(i, largestPossibleRadius);
13206 textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
13207 scale._pointLabelSizes[i] = textSize;
13208
13209 // Add quarter circle to make degree 0 mean top of circle
13210 var angleRadians = scale.getIndexAngle(i);
13211 var angle = helpers.toDegrees(angleRadians) % 360;
13212 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
13213 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
13214
13215 if (hLimits.start < furthestLimits.l) {
13216 furthestLimits.l = hLimits.start;
13217 furthestAngles.l = angleRadians;
13218 }
13219
13220 if (hLimits.end > furthestLimits.r) {
13221 furthestLimits.r = hLimits.end;
13222 furthestAngles.r = angleRadians;
13223 }
13224
13225 if (vLimits.start < furthestLimits.t) {
13226 furthestLimits.t = vLimits.start;
13227 furthestAngles.t = angleRadians;
13228 }
13229
13230 if (vLimits.end > furthestLimits.b) {
13231 furthestLimits.b = vLimits.end;
13232 furthestAngles.b = angleRadians;
13233 }
13234 }
13235
13236 scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
13237 }
13238
13239 /**
13240 * Helper function to fit a radial linear scale with no point labels
13241 */
13242 function fit(scale) {
13243 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13244 scale.drawingArea = Math.round(largestPossibleRadius);
13245 scale.setCenterPoint(0, 0, 0, 0);
13246 }
13247
13248 function getTextAlignForAngle(angle) {
13249 if (angle === 0 || angle === 180) {
13250 return 'center';
13251 } else if (angle < 180) {
13252 return 'left';
13253 }
13254
13255 return 'right';
13256 }
13257
13258 function fillText(ctx, text, position, fontSize) {
13259 if (helpers.isArray(text)) {
13260 var y = position.y;
13261 var spacing = 1.5 * fontSize;
13262
13263 for (var i = 0; i < text.length; ++i) {
13264 ctx.fillText(text[i], position.x, y);
13265 y += spacing;
13266 }
13267 } else {
13268 ctx.fillText(text, position.x, position.y);
13269 }
13270 }
13271
13272 function adjustPointPositionForLabelHeight(angle, textSize, position) {
13273 if (angle === 90 || angle === 270) {
13274 position.y -= (textSize.h / 2);
13275 } else if (angle > 270 || angle < 90) {
13276 position.y -= textSize.h;
13277 }
13278 }
13279
13280 function drawPointLabels(scale) {
13281 var ctx = scale.ctx;
13282 var valueOrDefault = helpers.valueOrDefault;
13283 var opts = scale.options;
13284 var angleLineOpts = opts.angleLines;
13285 var pointLabelOpts = opts.pointLabels;
13286
13287 ctx.lineWidth = angleLineOpts.lineWidth;
13288 ctx.strokeStyle = angleLineOpts.color;
13289
13290 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
13291
13292 // Point Label Font
13293 var plFont = getPointLabelFontOptions(scale);
13294
13295 ctx.textBaseline = 'top';
13296
13297 for (var i = getValueCount(scale) - 1; i >= 0; i--) {
13298 if (angleLineOpts.display) {
13299 var outerPosition = scale.getPointPosition(i, outerDistance);
13300 ctx.beginPath();
13301 ctx.moveTo(scale.xCenter, scale.yCenter);
13302 ctx.lineTo(outerPosition.x, outerPosition.y);
13303 ctx.stroke();
13304 ctx.closePath();
13305 }
13306
13307 if (pointLabelOpts.display) {
13308 // Extra 3px out for some label spacing
13309 var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
13310
13311 // Keep this in loop since we may support array properties here
13312 var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
13313 ctx.font = plFont.font;
13314 ctx.fillStyle = pointLabelFontColor;
13315
13316 var angleRadians = scale.getIndexAngle(i);
13317 var angle = helpers.toDegrees(angleRadians);
13318 ctx.textAlign = getTextAlignForAngle(angle);
13319 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
13320 fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
13321 }
13322 }
13323 }
13324
13325 function drawRadiusLine(scale, gridLineOpts, radius, index) {
13326 var ctx = scale.ctx;
13327 ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1);
13328 ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
13329
13330 if (scale.options.gridLines.circular) {
13331 // Draw circular arcs between the points
13332 ctx.beginPath();
13333 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
13334 ctx.closePath();
13335 ctx.stroke();
13336 } else {
13337 // Draw straight lines connecting each index
13338 var valueCount = getValueCount(scale);
13339
13340 if (valueCount === 0) {
13341 return;
13342 }
13343
13344 ctx.beginPath();
13345 var pointPosition = scale.getPointPosition(0, radius);
13346 ctx.moveTo(pointPosition.x, pointPosition.y);
13347
13348 for (var i = 1; i < valueCount; i++) {
13349 pointPosition = scale.getPointPosition(i, radius);
13350 ctx.lineTo(pointPosition.x, pointPosition.y);
13351 }
13352
13353 ctx.closePath();
13354 ctx.stroke();
13355 }
13356 }
13357
13358 function numberOrZero(param) {
13359 return helpers.isNumber(param) ? param : 0;
13360 }
13361
13362 var LinearRadialScale = Chart.LinearScaleBase.extend({
13363 setDimensions: function() {
13364 var me = this;
13365 var opts = me.options;
13366 var tickOpts = opts.ticks;
13367 // Set the unconstrained dimension before label rotation
13368 me.width = me.maxWidth;
13369 me.height = me.maxHeight;
13370 me.xCenter = Math.round(me.width / 2);
13371 me.yCenter = Math.round(me.height / 2);
13372
13373 var minSize = helpers.min([me.height, me.width]);
13374 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13375 me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
13376 },
13377 determineDataLimits: function() {
13378 var me = this;
13379 var chart = me.chart;
13380 var min = Number.POSITIVE_INFINITY;
13381 var max = Number.NEGATIVE_INFINITY;
13382
13383 helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
13384 if (chart.isDatasetVisible(datasetIndex)) {
13385 var meta = chart.getDatasetMeta(datasetIndex);
13386
13387 helpers.each(dataset.data, function(rawValue, index) {
13388 var value = +me.getRightValue(rawValue);
13389 if (isNaN(value) || meta.data[index].hidden) {
13390 return;
13391 }
13392
13393 min = Math.min(value, min);
13394 max = Math.max(value, max);
13395 });
13396 }
13397 });
13398
13399 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
13400 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
13401
13402 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13403 me.handleTickRangeOptions();
13404 },
13405 getTickLimit: function() {
13406 var tickOpts = this.options.ticks;
13407 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13408 return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
13409 },
13410 convertTicksToLabels: function() {
13411 var me = this;
13412
13413 Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
13414
13415 // Point labels
13416 me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
13417 },
13418 getLabelForIndex: function(index, datasetIndex) {
13419 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
13420 },
13421 fit: function() {
13422 if (this.options.pointLabels.display) {
13423 fitWithPointLabels(this);
13424 } else {
13425 fit(this);
13426 }
13427 },
13428 /**
13429 * Set radius reductions and determine new radius and center point
13430 * @private
13431 */
13432 setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
13433 var me = this;
13434 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
13435 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
13436 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
13437 var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
13438
13439 radiusReductionLeft = numberOrZero(radiusReductionLeft);
13440 radiusReductionRight = numberOrZero(radiusReductionRight);
13441 radiusReductionTop = numberOrZero(radiusReductionTop);
13442 radiusReductionBottom = numberOrZero(radiusReductionBottom);
13443
13444 me.drawingArea = Math.min(
13445 Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
13446 Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
13447 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
13448 },
13449 setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
13450 var me = this;
13451 var maxRight = me.width - rightMovement - me.drawingArea;
13452 var maxLeft = leftMovement + me.drawingArea;
13453 var maxTop = topMovement + me.drawingArea;
13454 var maxBottom = me.height - bottomMovement - me.drawingArea;
13455
13456 me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
13457 me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
13458 },
13459
13460 getIndexAngle: function(index) {
13461 var angleMultiplier = (Math.PI * 2) / getValueCount(this);
13462 var startAngle = this.chart.options && this.chart.options.startAngle ?
13463 this.chart.options.startAngle :
13464 0;
13465
13466 var startAngleRadians = startAngle * Math.PI * 2 / 360;
13467
13468 // Start from the top instead of right, so remove a quarter of the circle
13469 return index * angleMultiplier + startAngleRadians;
13470 },
13471 getDistanceFromCenterForValue: function(value) {
13472 var me = this;
13473
13474 if (value === null) {
13475 return 0; // null always in center
13476 }
13477
13478 // Take into account half font size + the yPadding of the top value
13479 var scalingFactor = me.drawingArea / (me.max - me.min);
13480 if (me.options.ticks.reverse) {
13481 return (me.max - value) * scalingFactor;
13482 }
13483 return (value - me.min) * scalingFactor;
13484 },
13485 getPointPosition: function(index, distanceFromCenter) {
13486 var me = this;
13487 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
13488 return {
13489 x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
13490 y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
13491 };
13492 },
13493 getPointPositionForValue: function(index, value) {
13494 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
13495 },
13496
13497 getBasePosition: function() {
13498 var me = this;
13499 var min = me.min;
13500 var max = me.max;
13501
13502 return me.getPointPositionForValue(0,
13503 me.beginAtZero ? 0 :
13504 min < 0 && max < 0 ? max :
13505 min > 0 && max > 0 ? min :
13506 0);
13507 },
13508
13509 draw: function() {
13510 var me = this;
13511 var opts = me.options;
13512 var gridLineOpts = opts.gridLines;
13513 var tickOpts = opts.ticks;
13514 var valueOrDefault = helpers.valueOrDefault;
13515
13516 if (opts.display) {
13517 var ctx = me.ctx;
13518 var startAngle = this.getIndexAngle(0);
13519
13520 // Tick Font
13521 var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13522 var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
13523 var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
13524 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
13525
13526 helpers.each(me.ticks, function(label, index) {
13527 // Don't draw a centre value (if it is minimum)
13528 if (index > 0 || tickOpts.reverse) {
13529 var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13530
13531 // Draw circular lines around the scale
13532 if (gridLineOpts.display && index !== 0) {
13533 drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
13534 }
13535
13536 if (tickOpts.display) {
13537 var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
13538 ctx.font = tickLabelFont;
13539
13540 ctx.save();
13541 ctx.translate(me.xCenter, me.yCenter);
13542 ctx.rotate(startAngle);
13543
13544 if (tickOpts.showLabelBackdrop) {
13545 var labelWidth = ctx.measureText(label).width;
13546 ctx.fillStyle = tickOpts.backdropColor;
13547 ctx.fillRect(
13548 -labelWidth / 2 - tickOpts.backdropPaddingX,
13549 -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY,
13550 labelWidth + tickOpts.backdropPaddingX * 2,
13551 tickFontSize + tickOpts.backdropPaddingY * 2
13552 );
13553 }
13554
13555 ctx.textAlign = 'center';
13556 ctx.textBaseline = 'middle';
13557 ctx.fillStyle = tickFontColor;
13558 ctx.fillText(label, 0, -yCenterOffset);
13559 ctx.restore();
13560 }
13561 }
13562 });
13563
13564 if (opts.angleLines.display || opts.pointLabels.display) {
13565 drawPointLabels(me);
13566 }
13567 }
13568 }
13569 });
13570 Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
13571
13572};
13573
13574},{"25":25,"34":34,"45":45}],58:[function(require,module,exports){
13575/* global window: false */
13576'use strict';
13577
13578var moment = require(1);
13579moment = typeof moment === 'function' ? moment : window.moment;
13580
13581var defaults = require(25);
13582var helpers = require(45);
13583
13584// Integer constants are from the ES6 spec.
13585var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
13586var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
13587
13588var INTERVALS = {
13589 millisecond: {
13590 common: true,
13591 size: 1,
13592 steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
13593 },
13594 second: {
13595 common: true,
13596 size: 1000,
13597 steps: [1, 2, 5, 10, 30]
13598 },
13599 minute: {
13600 common: true,
13601 size: 60000,
13602 steps: [1, 2, 5, 10, 30]
13603 },
13604 hour: {
13605 common: true,
13606 size: 3600000,
13607 steps: [1, 2, 3, 6, 12]
13608 },
13609 day: {
13610 common: true,
13611 size: 86400000,
13612 steps: [1, 2, 5]
13613 },
13614 week: {
13615 common: false,
13616 size: 604800000,
13617 steps: [1, 2, 3, 4]
13618 },
13619 month: {
13620 common: true,
13621 size: 2.628e9,
13622 steps: [1, 2, 3]
13623 },
13624 quarter: {
13625 common: false,
13626 size: 7.884e9,
13627 steps: [1, 2, 3, 4]
13628 },
13629 year: {
13630 common: true,
13631 size: 3.154e10
13632 }
13633};
13634
13635var UNITS = Object.keys(INTERVALS);
13636
13637function sorter(a, b) {
13638 return a - b;
13639}
13640
13641function arrayUnique(items) {
13642 var hash = {};
13643 var out = [];
13644 var i, ilen, item;
13645
13646 for (i = 0, ilen = items.length; i < ilen; ++i) {
13647 item = items[i];
13648 if (!hash[item]) {
13649 hash[item] = true;
13650 out.push(item);
13651 }
13652 }
13653
13654 return out;
13655}
13656
13657/**
13658 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
13659 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
13660 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
13661 * extremity (left + width or top + height). Note that it would be more optimized to directly
13662 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
13663 * to create the lookup table. The table ALWAYS contains at least two items: min and max.
13664 *
13665 * @param {Number[]} timestamps - timestamps sorted from lowest to highest.
13666 * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
13667 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
13668 * If 'series', timestamps will be positioned at the same distance from each other. In this
13669 * case, only timestamps that break the time linearity are registered, meaning that in the
13670 * best case, all timestamps are linear, the table contains only min and max.
13671 */
13672function buildLookupTable(timestamps, min, max, distribution) {
13673 if (distribution === 'linear' || !timestamps.length) {
13674 return [
13675 {time: min, pos: 0},
13676 {time: max, pos: 1}
13677 ];
13678 }
13679
13680 var table = [];
13681 var items = [min];
13682 var i, ilen, prev, curr, next;
13683
13684 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13685 curr = timestamps[i];
13686 if (curr > min && curr < max) {
13687 items.push(curr);
13688 }
13689 }
13690
13691 items.push(max);
13692
13693 for (i = 0, ilen = items.length; i < ilen; ++i) {
13694 next = items[i + 1];
13695 prev = items[i - 1];
13696 curr = items[i];
13697
13698 // only add points that breaks the scale linearity
13699 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
13700 table.push({time: curr, pos: i / (ilen - 1)});
13701 }
13702 }
13703
13704 return table;
13705}
13706
13707// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
13708function lookup(table, key, value) {
13709 var lo = 0;
13710 var hi = table.length - 1;
13711 var mid, i0, i1;
13712
13713 while (lo >= 0 && lo <= hi) {
13714 mid = (lo + hi) >> 1;
13715 i0 = table[mid - 1] || null;
13716 i1 = table[mid];
13717
13718 if (!i0) {
13719 // given value is outside table (before first item)
13720 return {lo: null, hi: i1};
13721 } else if (i1[key] < value) {
13722 lo = mid + 1;
13723 } else if (i0[key] > value) {
13724 hi = mid - 1;
13725 } else {
13726 return {lo: i0, hi: i1};
13727 }
13728 }
13729
13730 // given value is outside table (after last item)
13731 return {lo: i1, hi: null};
13732}
13733
13734/**
13735 * Linearly interpolates the given source `value` using the table items `skey` values and
13736 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
13737 * returns the position for a timestamp equal to 42. If value is out of bounds, values at
13738 * index [0, 1] or [n - 1, n] are used for the interpolation.
13739 */
13740function interpolate(table, skey, sval, tkey) {
13741 var range = lookup(table, skey, sval);
13742
13743 // Note: the lookup table ALWAYS contains at least 2 items (min and max)
13744 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
13745 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
13746
13747 var span = next[skey] - prev[skey];
13748 var ratio = span ? (sval - prev[skey]) / span : 0;
13749 var offset = (next[tkey] - prev[tkey]) * ratio;
13750
13751 return prev[tkey] + offset;
13752}
13753
13754/**
13755 * Convert the given value to a moment object using the given time options.
13756 * @see http://momentjs.com/docs/#/parsing/
13757 */
13758function momentify(value, options) {
13759 var parser = options.parser;
13760 var format = options.parser || options.format;
13761
13762 if (typeof parser === 'function') {
13763 return parser(value);
13764 }
13765
13766 if (typeof value === 'string' && typeof format === 'string') {
13767 return moment(value, format);
13768 }
13769
13770 if (!(value instanceof moment)) {
13771 value = moment(value);
13772 }
13773
13774 if (value.isValid()) {
13775 return value;
13776 }
13777
13778 // Labels are in an incompatible moment format and no `parser` has been provided.
13779 // The user might still use the deprecated `format` option to convert his inputs.
13780 if (typeof format === 'function') {
13781 return format(value);
13782 }
13783
13784 return value;
13785}
13786
13787function parse(input, scale) {
13788 if (helpers.isNullOrUndef(input)) {
13789 return null;
13790 }
13791
13792 var options = scale.options.time;
13793 var value = momentify(scale.getRightValue(input), options);
13794 if (!value.isValid()) {
13795 return null;
13796 }
13797
13798 if (options.round) {
13799 value.startOf(options.round);
13800 }
13801
13802 return value.valueOf();
13803}
13804
13805/**
13806 * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
13807 * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
13808 */
13809function determineStepSize(min, max, unit, capacity) {
13810 var range = max - min;
13811 var interval = INTERVALS[unit];
13812 var milliseconds = interval.size;
13813 var steps = interval.steps;
13814 var i, ilen, factor;
13815
13816 if (!steps) {
13817 return Math.ceil(range / (capacity * milliseconds));
13818 }
13819
13820 for (i = 0, ilen = steps.length; i < ilen; ++i) {
13821 factor = steps[i];
13822 if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
13823 break;
13824 }
13825 }
13826
13827 return factor;
13828}
13829
13830/**
13831 * Figures out what unit results in an appropriate number of auto-generated ticks
13832 */
13833function determineUnitForAutoTicks(minUnit, min, max, capacity) {
13834 var ilen = UNITS.length;
13835 var i, interval, factor;
13836
13837 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
13838 interval = INTERVALS[UNITS[i]];
13839 factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
13840
13841 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
13842 return UNITS[i];
13843 }
13844 }
13845
13846 return UNITS[ilen - 1];
13847}
13848
13849/**
13850 * Figures out what unit to format a set of ticks with
13851 */
13852function determineUnitForFormatting(ticks, minUnit, min, max) {
13853 var duration = moment.duration(moment(max).diff(moment(min)));
13854 var ilen = UNITS.length;
13855 var i, unit;
13856
13857 for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
13858 unit = UNITS[i];
13859 if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) {
13860 return unit;
13861 }
13862 }
13863
13864 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
13865}
13866
13867function determineMajorUnit(unit) {
13868 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
13869 if (INTERVALS[UNITS[i]].common) {
13870 return UNITS[i];
13871 }
13872 }
13873}
13874
13875/**
13876 * Generates a maximum of `capacity` timestamps between min and max, rounded to the
13877 * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
13878 * Important: this method can return ticks outside the min and max range, it's the
13879 * responsibility of the calling code to clamp values if needed.
13880 */
13881function generate(min, max, capacity, options) {
13882 var timeOpts = options.time;
13883 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
13884 var major = determineMajorUnit(minor);
13885 var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
13886 var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
13887 var majorTicksEnabled = options.ticks.major.enabled;
13888 var interval = INTERVALS[minor];
13889 var first = moment(min);
13890 var last = moment(max);
13891 var ticks = [];
13892 var time;
13893
13894 if (!stepSize) {
13895 stepSize = determineStepSize(min, max, minor, capacity);
13896 }
13897
13898 // For 'week' unit, handle the first day of week option
13899 if (weekday) {
13900 first = first.isoWeekday(weekday);
13901 last = last.isoWeekday(weekday);
13902 }
13903
13904 // Align first/last ticks on unit
13905 first = first.startOf(weekday ? 'day' : minor);
13906 last = last.startOf(weekday ? 'day' : minor);
13907
13908 // Make sure that the last tick include max
13909 if (last < max) {
13910 last.add(1, minor);
13911 }
13912
13913 time = moment(first);
13914
13915 if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
13916 // Align the first tick on the previous `minor` unit aligned on the `major` unit:
13917 // we first aligned time on the previous `major` unit then add the number of full
13918 // stepSize there is between first and the previous major time.
13919 time.startOf(major);
13920 time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
13921 }
13922
13923 for (; time < last; time.add(stepSize, minor)) {
13924 ticks.push(+time);
13925 }
13926
13927 ticks.push(+time);
13928
13929 return ticks;
13930}
13931
13932/**
13933 * Returns the right and left offsets from edges in the form of {left, right}.
13934 * Offsets are added when the `offset` option is true.
13935 */
13936function computeOffsets(table, ticks, min, max, options) {
13937 var left = 0;
13938 var right = 0;
13939 var upper, lower;
13940
13941 if (options.offset && ticks.length) {
13942 if (!options.time.min) {
13943 upper = ticks.length > 1 ? ticks[1] : max;
13944 lower = ticks[0];
13945 left = (
13946 interpolate(table, 'time', upper, 'pos') -
13947 interpolate(table, 'time', lower, 'pos')
13948 ) / 2;
13949 }
13950 if (!options.time.max) {
13951 upper = ticks[ticks.length - 1];
13952 lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
13953 right = (
13954 interpolate(table, 'time', upper, 'pos') -
13955 interpolate(table, 'time', lower, 'pos')
13956 ) / 2;
13957 }
13958 }
13959
13960 return {left: left, right: right};
13961}
13962
13963function ticksFromTimestamps(values, majorUnit) {
13964 var ticks = [];
13965 var i, ilen, value, major;
13966
13967 for (i = 0, ilen = values.length; i < ilen; ++i) {
13968 value = values[i];
13969 major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
13970
13971 ticks.push({
13972 value: value,
13973 major: major
13974 });
13975 }
13976
13977 return ticks;
13978}
13979
13980module.exports = function(Chart) {
13981
13982 var defaultConfig = {
13983 position: 'bottom',
13984
13985 /**
13986 * Data distribution along the scale:
13987 * - 'linear': data are spread according to their time (distances can vary),
13988 * - 'series': data are spread at the same distance from each other.
13989 * @see https://github.com/chartjs/Chart.js/pull/4507
13990 * @since 2.7.0
13991 */
13992 distribution: 'linear',
13993
13994 /**
13995 * Scale boundary strategy (bypassed by min/max time options)
13996 * - `data`: make sure data are fully visible, ticks outside are removed
13997 * - `ticks`: make sure ticks are fully visible, data outside are truncated
13998 * @see https://github.com/chartjs/Chart.js/pull/4556
13999 * @since 2.7.0
14000 */
14001 bounds: 'data',
14002
14003 time: {
14004 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
14005 format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
14006 unit: false, // false == automatic or override with week, month, year, etc.
14007 round: false, // none, or override with week, month, year, etc.
14008 displayFormat: false, // DEPRECATED
14009 isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
14010 minUnit: 'millisecond',
14011
14012 // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
14013 displayFormats: {
14014 millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
14015 second: 'h:mm:ss a', // 11:20:01 AM
14016 minute: 'h:mm a', // 11:20 AM
14017 hour: 'hA', // 5PM
14018 day: 'MMM D', // Sep 4
14019 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
14020 month: 'MMM YYYY', // Sept 2015
14021 quarter: '[Q]Q - YYYY', // Q3
14022 year: 'YYYY' // 2015
14023 },
14024 },
14025 ticks: {
14026 autoSkip: false,
14027
14028 /**
14029 * Ticks generation input values:
14030 * - 'auto': generates "optimal" ticks based on scale size and time options.
14031 * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
14032 * - 'labels': generates ticks from user given `data.labels` values ONLY.
14033 * @see https://github.com/chartjs/Chart.js/pull/4507
14034 * @since 2.7.0
14035 */
14036 source: 'auto',
14037
14038 major: {
14039 enabled: false
14040 }
14041 }
14042 };
14043
14044 var TimeScale = Chart.Scale.extend({
14045 initialize: function() {
14046 if (!moment) {
14047 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');
14048 }
14049
14050 this.mergeTicksOptions();
14051
14052 Chart.Scale.prototype.initialize.call(this);
14053 },
14054
14055 update: function() {
14056 var me = this;
14057 var options = me.options;
14058
14059 // DEPRECATIONS: output a message only one time per update
14060 if (options.time && options.time.format) {
14061 console.warn('options.time.format is deprecated and replaced by options.time.parser.');
14062 }
14063
14064 return Chart.Scale.prototype.update.apply(me, arguments);
14065 },
14066
14067 /**
14068 * Allows data to be referenced via 't' attribute
14069 */
14070 getRightValue: function(rawValue) {
14071 if (rawValue && rawValue.t !== undefined) {
14072 rawValue = rawValue.t;
14073 }
14074 return Chart.Scale.prototype.getRightValue.call(this, rawValue);
14075 },
14076
14077 determineDataLimits: function() {
14078 var me = this;
14079 var chart = me.chart;
14080 var timeOpts = me.options.time;
14081 var unit = timeOpts.unit || 'day';
14082 var min = MAX_INTEGER;
14083 var max = MIN_INTEGER;
14084 var timestamps = [];
14085 var datasets = [];
14086 var labels = [];
14087 var i, j, ilen, jlen, data, timestamp;
14088
14089 // Convert labels to timestamps
14090 for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) {
14091 labels.push(parse(chart.data.labels[i], me));
14092 }
14093
14094 // Convert data to timestamps
14095 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
14096 if (chart.isDatasetVisible(i)) {
14097 data = chart.data.datasets[i].data;
14098
14099 // Let's consider that all data have the same format.
14100 if (helpers.isObject(data[0])) {
14101 datasets[i] = [];
14102
14103 for (j = 0, jlen = data.length; j < jlen; ++j) {
14104 timestamp = parse(data[j], me);
14105 timestamps.push(timestamp);
14106 datasets[i][j] = timestamp;
14107 }
14108 } else {
14109 timestamps.push.apply(timestamps, labels);
14110 datasets[i] = labels.slice(0);
14111 }
14112 } else {
14113 datasets[i] = [];
14114 }
14115 }
14116
14117 if (labels.length) {
14118 // Sort labels **after** data have been converted
14119 labels = arrayUnique(labels).sort(sorter);
14120 min = Math.min(min, labels[0]);
14121 max = Math.max(max, labels[labels.length - 1]);
14122 }
14123
14124 if (timestamps.length) {
14125 timestamps = arrayUnique(timestamps).sort(sorter);
14126 min = Math.min(min, timestamps[0]);
14127 max = Math.max(max, timestamps[timestamps.length - 1]);
14128 }
14129
14130 min = parse(timeOpts.min, me) || min;
14131 max = parse(timeOpts.max, me) || max;
14132
14133 // In case there is no valid min/max, set limits based on unit time option
14134 min = min === MAX_INTEGER ? +moment().startOf(unit) : min;
14135 max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max;
14136
14137 // Make sure that max is strictly higher than min (required by the lookup table)
14138 me.min = Math.min(min, max);
14139 me.max = Math.max(min + 1, max);
14140
14141 // PRIVATE
14142 me._horizontal = me.isHorizontal();
14143 me._table = [];
14144 me._timestamps = {
14145 data: timestamps,
14146 datasets: datasets,
14147 labels: labels
14148 };
14149 },
14150
14151 buildTicks: function() {
14152 var me = this;
14153 var min = me.min;
14154 var max = me.max;
14155 var options = me.options;
14156 var timeOpts = options.time;
14157 var timestamps = [];
14158 var ticks = [];
14159 var i, ilen, timestamp;
14160
14161 switch (options.ticks.source) {
14162 case 'data':
14163 timestamps = me._timestamps.data;
14164 break;
14165 case 'labels':
14166 timestamps = me._timestamps.labels;
14167 break;
14168 case 'auto':
14169 default:
14170 timestamps = generate(min, max, me.getLabelCapacity(min), options);
14171 }
14172
14173 if (options.bounds === 'ticks' && timestamps.length) {
14174 min = timestamps[0];
14175 max = timestamps[timestamps.length - 1];
14176 }
14177
14178 // Enforce limits with user min/max options
14179 min = parse(timeOpts.min, me) || min;
14180 max = parse(timeOpts.max, me) || max;
14181
14182 // Remove ticks outside the min/max range
14183 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
14184 timestamp = timestamps[i];
14185 if (timestamp >= min && timestamp <= max) {
14186 ticks.push(timestamp);
14187 }
14188 }
14189
14190 me.min = min;
14191 me.max = max;
14192
14193 // PRIVATE
14194 me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max);
14195 me._majorUnit = determineMajorUnit(me._unit);
14196 me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
14197 me._offsets = computeOffsets(me._table, ticks, min, max, options);
14198
14199 return ticksFromTimestamps(ticks, me._majorUnit);
14200 },
14201
14202 getLabelForIndex: function(index, datasetIndex) {
14203 var me = this;
14204 var data = me.chart.data;
14205 var timeOpts = me.options.time;
14206 var label = data.labels && index < data.labels.length ? data.labels[index] : '';
14207 var value = data.datasets[datasetIndex].data[index];
14208
14209 if (helpers.isObject(value)) {
14210 label = me.getRightValue(value);
14211 }
14212 if (timeOpts.tooltipFormat) {
14213 label = momentify(label, timeOpts).format(timeOpts.tooltipFormat);
14214 }
14215
14216 return label;
14217 },
14218
14219 /**
14220 * Function to format an individual tick mark
14221 * @private
14222 */
14223 tickFormatFunction: function(tick, index, ticks, formatOverride) {
14224 var me = this;
14225 var options = me.options;
14226 var time = tick.valueOf();
14227 var formats = options.time.displayFormats;
14228 var minorFormat = formats[me._unit];
14229 var majorUnit = me._majorUnit;
14230 var majorFormat = formats[majorUnit];
14231 var majorTime = tick.clone().startOf(majorUnit).valueOf();
14232 var majorTickOpts = options.ticks.major;
14233 var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
14234 var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat);
14235 var tickOpts = major ? majorTickOpts : options.ticks.minor;
14236 var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback);
14237
14238 return formatter ? formatter(label, index, ticks) : label;
14239 },
14240
14241 convertTicksToLabels: function(ticks) {
14242 var labels = [];
14243 var i, ilen;
14244
14245 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
14246 labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
14247 }
14248
14249 return labels;
14250 },
14251
14252 /**
14253 * @private
14254 */
14255 getPixelForOffset: function(time) {
14256 var me = this;
14257 var size = me._horizontal ? me.width : me.height;
14258 var start = me._horizontal ? me.left : me.top;
14259 var pos = interpolate(me._table, 'time', time, 'pos');
14260
14261 return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
14262 },
14263
14264 getPixelForValue: function(value, index, datasetIndex) {
14265 var me = this;
14266 var time = null;
14267
14268 if (index !== undefined && datasetIndex !== undefined) {
14269 time = me._timestamps.datasets[datasetIndex][index];
14270 }
14271
14272 if (time === null) {
14273 time = parse(value, me);
14274 }
14275
14276 if (time !== null) {
14277 return me.getPixelForOffset(time);
14278 }
14279 },
14280
14281 getPixelForTick: function(index) {
14282 var ticks = this.getTicks();
14283 return index >= 0 && index < ticks.length ?
14284 this.getPixelForOffset(ticks[index].value) :
14285 null;
14286 },
14287
14288 getValueForPixel: function(pixel) {
14289 var me = this;
14290 var size = me._horizontal ? me.width : me.height;
14291 var start = me._horizontal ? me.left : me.top;
14292 var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
14293 var time = interpolate(me._table, 'pos', pos, 'time');
14294
14295 return moment(time);
14296 },
14297
14298 /**
14299 * Crude approximation of what the label width might be
14300 * @private
14301 */
14302 getLabelWidth: function(label) {
14303 var me = this;
14304 var ticksOpts = me.options.ticks;
14305 var tickLabelWidth = me.ctx.measureText(label).width;
14306 var angle = helpers.toRadians(ticksOpts.maxRotation);
14307 var cosRotation = Math.cos(angle);
14308 var sinRotation = Math.sin(angle);
14309 var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
14310
14311 return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
14312 },
14313
14314 /**
14315 * @private
14316 */
14317 getLabelCapacity: function(exampleTime) {
14318 var me = this;
14319
14320 var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
14321
14322 var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride);
14323 var tickLabelWidth = me.getLabelWidth(exampleLabel);
14324 var innerWidth = me.isHorizontal() ? me.width : me.height;
14325
14326 var capacity = Math.floor(innerWidth / tickLabelWidth);
14327 return capacity > 0 ? capacity : 1;
14328 }
14329 });
14330
14331 Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
14332};
14333
14334},{"1":1,"25":25,"45":45}]},{},[7])(7)
14335});