· 8 years ago · Jan 22, 2018, 11:08 AM
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/* MIT license */
12var colorNames = require(5);
13
14module.exports = {
15 getRgba: getRgba,
16 getHsla: getHsla,
17 getRgb: getRgb,
18 getHsl: getHsl,
19 getHwb: getHwb,
20 getAlpha: getAlpha,
21
22 hexString: hexString,
23 rgbString: rgbString,
24 rgbaString: rgbaString,
25 percentString: percentString,
26 percentaString: percentaString,
27 hslString: hslString,
28 hslaString: hslaString,
29 hwbString: hwbString,
30 keyword: keyword
31}
32
33function getRgba(string) {
34 if (!string) {
35 return;
36 }
37 var abbr = /^#([a-fA-F0-9]{3})$/i,
38 hex = /^#([a-fA-F0-9]{6})$/i,
39 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
40 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
41 keyword = /(\w+)/;
42
43 var rgb = [0, 0, 0],
44 a = 1,
45 match = string.match(abbr);
46 if (match) {
47 match = match[1];
48 for (var i = 0; i < rgb.length; i++) {
49 rgb[i] = parseInt(match[i] + match[i], 16);
50 }
51 }
52 else if (match = string.match(hex)) {
53 match = match[1];
54 for (var i = 0; i < rgb.length; i++) {
55 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
56 }
57 }
58 else if (match = string.match(rgba)) {
59 for (var i = 0; i < rgb.length; i++) {
60 rgb[i] = parseInt(match[i + 1]);
61 }
62 a = parseFloat(match[4]);
63 }
64 else if (match = string.match(per)) {
65 for (var i = 0; i < rgb.length; i++) {
66 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
67 }
68 a = parseFloat(match[4]);
69 }
70 else if (match = string.match(keyword)) {
71 if (match[1] == "transparent") {
72 return [0, 0, 0, 0];
73 }
74 rgb = colorNames[match[1]];
75 if (!rgb) {
76 return;
77 }
78 }
79
80 for (var i = 0; i < rgb.length; i++) {
81 rgb[i] = scale(rgb[i], 0, 255);
82 }
83 if (!a && a != 0) {
84 a = 1;
85 }
86 else {
87 a = scale(a, 0, 1);
88 }
89 rgb[3] = a;
90 return rgb;
91}
92
93function getHsla(string) {
94 if (!string) {
95 return;
96 }
97 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
98 var match = string.match(hsl);
99 if (match) {
100 var alpha = parseFloat(match[4]);
101 var h = scale(parseInt(match[1]), 0, 360),
102 s = scale(parseFloat(match[2]), 0, 100),
103 l = scale(parseFloat(match[3]), 0, 100),
104 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
105 return [h, s, l, a];
106 }
107}
108
109function getHwb(string) {
110 if (!string) {
111 return;
112 }
113 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
114 var match = string.match(hwb);
115 if (match) {
116 var alpha = parseFloat(match[4]);
117 var h = scale(parseInt(match[1]), 0, 360),
118 w = scale(parseFloat(match[2]), 0, 100),
119 b = scale(parseFloat(match[3]), 0, 100),
120 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
121 return [h, w, b, a];
122 }
123}
124
125function getRgb(string) {
126 var rgba = getRgba(string);
127 return rgba && rgba.slice(0, 3);
128}
129
130function getHsl(string) {
131 var hsla = getHsla(string);
132 return hsla && hsla.slice(0, 3);
133}
134
135function getAlpha(string) {
136 var vals = getRgba(string);
137 if (vals) {
138 return vals[3];
139 }
140 else if (vals = getHsla(string)) {
141 return vals[3];
142 }
143 else if (vals = getHwb(string)) {
144 return vals[3];
145 }
146}
147
148// generators
149function hexString(rgb) {
150 return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
151 + hexDouble(rgb[2]);
152}
153
154function rgbString(rgba, alpha) {
155 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
156 return rgbaString(rgba, alpha);
157 }
158 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
159}
160
161function rgbaString(rgba, alpha) {
162 if (alpha === undefined) {
163 alpha = (rgba[3] !== undefined ? rgba[3] : 1);
164 }
165 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
166 + ", " + alpha + ")";
167}
168
169function percentString(rgba, alpha) {
170 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
171 return percentaString(rgba, alpha);
172 }
173 var r = Math.round(rgba[0]/255 * 100),
174 g = Math.round(rgba[1]/255 * 100),
175 b = Math.round(rgba[2]/255 * 100);
176
177 return "rgb(" + r + "%, " + g + "%, " + b + "%)";
178}
179
180function percentaString(rgba, alpha) {
181 var r = Math.round(rgba[0]/255 * 100),
182 g = Math.round(rgba[1]/255 * 100),
183 b = Math.round(rgba[2]/255 * 100);
184 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
185}
186
187function hslString(hsla, alpha) {
188 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
189 return hslaString(hsla, alpha);
190 }
191 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
192}
193
194function hslaString(hsla, alpha) {
195 if (alpha === undefined) {
196 alpha = (hsla[3] !== undefined ? hsla[3] : 1);
197 }
198 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
199 + alpha + ")";
200}
201
202// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
203// (hwb have alpha optional & 1 is default value)
204function hwbString(hwb, alpha) {
205 if (alpha === undefined) {
206 alpha = (hwb[3] !== undefined ? hwb[3] : 1);
207 }
208 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
209 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
210}
211
212function keyword(rgb) {
213 return reverseNames[rgb.slice(0, 3)];
214}
215
216// helpers
217function scale(num, min, max) {
218 return Math.min(Math.max(min, num), max);
219}
220
221function hexDouble(num) {
222 var str = num.toString(16).toUpperCase();
223 return (str.length < 2) ? "0" + str : str;
224}
225
226
227//create a list of reverse color names
228var reverseNames = {};
229for (var name in colorNames) {
230 reverseNames[colorNames[name]] = name;
231}
232
233},{"5":5}],2:[function(require,module,exports){
234/* MIT license */
235var convert = require(4);
236var string = require(1);
237
238var Color = function (obj) {
239 if (obj instanceof Color) {
240 return obj;
241 }
242 if (!(this instanceof Color)) {
243 return new Color(obj);
244 }
245
246 this.valid = false;
247 this.values = {
248 rgb: [0, 0, 0],
249 hsl: [0, 0, 0],
250 hsv: [0, 0, 0],
251 hwb: [0, 0, 0],
252 cmyk: [0, 0, 0, 0],
253 alpha: 1
254 };
255
256 // parse Color() argument
257 var vals;
258 if (typeof obj === 'string') {
259 vals = string.getRgba(obj);
260 if (vals) {
261 this.setValues('rgb', vals);
262 } else if (vals = string.getHsla(obj)) {
263 this.setValues('hsl', vals);
264 } else if (vals = string.getHwb(obj)) {
265 this.setValues('hwb', vals);
266 }
267 } else if (typeof obj === 'object') {
268 vals = obj;
269 if (vals.r !== undefined || vals.red !== undefined) {
270 this.setValues('rgb', vals);
271 } else if (vals.l !== undefined || vals.lightness !== undefined) {
272 this.setValues('hsl', vals);
273 } else if (vals.v !== undefined || vals.value !== undefined) {
274 this.setValues('hsv', vals);
275 } else if (vals.w !== undefined || vals.whiteness !== undefined) {
276 this.setValues('hwb', vals);
277 } else if (vals.c !== undefined || vals.cyan !== undefined) {
278 this.setValues('cmyk', vals);
279 }
280 }
281};
282
283Color.prototype = {
284 isValid: function () {
285 return this.valid;
286 },
287 rgb: function () {
288 return this.setSpace('rgb', arguments);
289 },
290 hsl: function () {
291 return this.setSpace('hsl', arguments);
292 },
293 hsv: function () {
294 return this.setSpace('hsv', arguments);
295 },
296 hwb: function () {
297 return this.setSpace('hwb', arguments);
298 },
299 cmyk: function () {
300 return this.setSpace('cmyk', arguments);
301 },
302
303 rgbArray: function () {
304 return this.values.rgb;
305 },
306 hslArray: function () {
307 return this.values.hsl;
308 },
309 hsvArray: function () {
310 return this.values.hsv;
311 },
312 hwbArray: function () {
313 var values = this.values;
314 if (values.alpha !== 1) {
315 return values.hwb.concat([values.alpha]);
316 }
317 return values.hwb;
318 },
319 cmykArray: function () {
320 return this.values.cmyk;
321 },
322 rgbaArray: function () {
323 var values = this.values;
324 return values.rgb.concat([values.alpha]);
325 },
326 hslaArray: function () {
327 var values = this.values;
328 return values.hsl.concat([values.alpha]);
329 },
330 alpha: function (val) {
331 if (val === undefined) {
332 return this.values.alpha;
333 }
334 this.setValues('alpha', val);
335 return this;
336 },
337
338 red: function (val) {
339 return this.setChannel('rgb', 0, val);
340 },
341 green: function (val) {
342 return this.setChannel('rgb', 1, val);
343 },
344 blue: function (val) {
345 return this.setChannel('rgb', 2, val);
346 },
347 hue: function (val) {
348 if (val) {
349 val %= 360;
350 val = val < 0 ? 360 + val : val;
351 }
352 return this.setChannel('hsl', 0, val);
353 },
354 saturation: function (val) {
355 return this.setChannel('hsl', 1, val);
356 },
357 lightness: function (val) {
358 return this.setChannel('hsl', 2, val);
359 },
360 saturationv: function (val) {
361 return this.setChannel('hsv', 1, val);
362 },
363 whiteness: function (val) {
364 return this.setChannel('hwb', 1, val);
365 },
366 blackness: function (val) {
367 return this.setChannel('hwb', 2, val);
368 },
369 value: function (val) {
370 return this.setChannel('hsv', 2, val);
371 },
372 cyan: function (val) {
373 return this.setChannel('cmyk', 0, val);
374 },
375 magenta: function (val) {
376 return this.setChannel('cmyk', 1, val);
377 },
378 yellow: function (val) {
379 return this.setChannel('cmyk', 2, val);
380 },
381 black: function (val) {
382 return this.setChannel('cmyk', 3, val);
383 },
384
385 hexString: function () {
386 return string.hexString(this.values.rgb);
387 },
388 rgbString: function () {
389 return string.rgbString(this.values.rgb, this.values.alpha);
390 },
391 rgbaString: function () {
392 return string.rgbaString(this.values.rgb, this.values.alpha);
393 },
394 percentString: function () {
395 return string.percentString(this.values.rgb, this.values.alpha);
396 },
397 hslString: function () {
398 return string.hslString(this.values.hsl, this.values.alpha);
399 },
400 hslaString: function () {
401 return string.hslaString(this.values.hsl, this.values.alpha);
402 },
403 hwbString: function () {
404 return string.hwbString(this.values.hwb, this.values.alpha);
405 },
406 keyword: function () {
407 return string.keyword(this.values.rgb, this.values.alpha);
408 },
409
410 rgbNumber: function () {
411 var rgb = this.values.rgb;
412 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
413 },
414
415 luminosity: function () {
416 // http://www.w3.org/TR/WCAG20/#relativeluminancedef
417 var rgb = this.values.rgb;
418 var lum = [];
419 for (var i = 0; i < rgb.length; i++) {
420 var chan = rgb[i] / 255;
421 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
422 }
423 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
424 },
425
426 contrast: function (color2) {
427 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
428 var lum1 = this.luminosity();
429 var lum2 = color2.luminosity();
430 if (lum1 > lum2) {
431 return (lum1 + 0.05) / (lum2 + 0.05);
432 }
433 return (lum2 + 0.05) / (lum1 + 0.05);
434 },
435
436 level: function (color2) {
437 var contrastRatio = this.contrast(color2);
438 if (contrastRatio >= 7.1) {
439 return 'AAA';
440 }
441
442 return (contrastRatio >= 4.5) ? 'AA' : '';
443 },
444
445 dark: function () {
446 // YIQ equation from http://24ways.org/2010/calculating-color-contrast
447 var rgb = this.values.rgb;
448 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
449 return yiq < 128;
450 },
451
452 light: function () {
453 return !this.dark();
454 },
455
456 negate: function () {
457 var rgb = [];
458 for (var i = 0; i < 3; i++) {
459 rgb[i] = 255 - this.values.rgb[i];
460 }
461 this.setValues('rgb', rgb);
462 return this;
463 },
464
465 lighten: function (ratio) {
466 var hsl = this.values.hsl;
467 hsl[2] += hsl[2] * ratio;
468 this.setValues('hsl', hsl);
469 return this;
470 },
471
472 darken: function (ratio) {
473 var hsl = this.values.hsl;
474 hsl[2] -= hsl[2] * ratio;
475 this.setValues('hsl', hsl);
476 return this;
477 },
478
479 saturate: function (ratio) {
480 var hsl = this.values.hsl;
481 hsl[1] += hsl[1] * ratio;
482 this.setValues('hsl', hsl);
483 return this;
484 },
485
486 desaturate: function (ratio) {
487 var hsl = this.values.hsl;
488 hsl[1] -= hsl[1] * ratio;
489 this.setValues('hsl', hsl);
490 return this;
491 },
492
493 whiten: function (ratio) {
494 var hwb = this.values.hwb;
495 hwb[1] += hwb[1] * ratio;
496 this.setValues('hwb', hwb);
497 return this;
498 },
499
500 blacken: function (ratio) {
501 var hwb = this.values.hwb;
502 hwb[2] += hwb[2] * ratio;
503 this.setValues('hwb', hwb);
504 return this;
505 },
506
507 greyscale: function () {
508 var rgb = this.values.rgb;
509 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
510 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
511 this.setValues('rgb', [val, val, val]);
512 return this;
513 },
514
515 clearer: function (ratio) {
516 var alpha = this.values.alpha;
517 this.setValues('alpha', alpha - (alpha * ratio));
518 return this;
519 },
520
521 opaquer: function (ratio) {
522 var alpha = this.values.alpha;
523 this.setValues('alpha', alpha + (alpha * ratio));
524 return this;
525 },
526
527 rotate: function (degrees) {
528 var hsl = this.values.hsl;
529 var hue = (hsl[0] + degrees) % 360;
530 hsl[0] = hue < 0 ? 360 + hue : hue;
531 this.setValues('hsl', hsl);
532 return this;
533 },
534
535 /**
536 * Ported from sass implementation in C
537 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
538 */
539 mix: function (mixinColor, weight) {
540 var color1 = this;
541 var color2 = mixinColor;
542 var p = weight === undefined ? 0.5 : weight;
543
544 var w = 2 * p - 1;
545 var a = color1.alpha() - color2.alpha();
546
547 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
548 var w2 = 1 - w1;
549
550 return this
551 .rgb(
552 w1 * color1.red() + w2 * color2.red(),
553 w1 * color1.green() + w2 * color2.green(),
554 w1 * color1.blue() + w2 * color2.blue()
555 )
556 .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
557 },
558
559 toJSON: function () {
560 return this.rgb();
561 },
562
563 clone: function () {
564 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
565 // making the final build way to big to embed in Chart.js. So let's do it manually,
566 // assuming that values to clone are 1 dimension arrays containing only numbers,
567 // except 'alpha' which is a number.
568 var result = new Color();
569 var source = this.values;
570 var target = result.values;
571 var value, type;
572
573 for (var prop in source) {
574 if (source.hasOwnProperty(prop)) {
575 value = source[prop];
576 type = ({}).toString.call(value);
577 if (type === '[object Array]') {
578 target[prop] = value.slice(0);
579 } else if (type === '[object Number]') {
580 target[prop] = value;
581 } else {
582 console.error('unexpected color value:', value);
583 }
584 }
585 }
586
587 return result;
588 }
589};
590
591Color.prototype.spaces = {
592 rgb: ['red', 'green', 'blue'],
593 hsl: ['hue', 'saturation', 'lightness'],
594 hsv: ['hue', 'saturation', 'value'],
595 hwb: ['hue', 'whiteness', 'blackness'],
596 cmyk: ['cyan', 'magenta', 'yellow', 'black']
597};
598
599Color.prototype.maxes = {
600 rgb: [255, 255, 255],
601 hsl: [360, 100, 100],
602 hsv: [360, 100, 100],
603 hwb: [360, 100, 100],
604 cmyk: [100, 100, 100, 100]
605};
606
607Color.prototype.getValues = function (space) {
608 var values = this.values;
609 var vals = {};
610
611 for (var i = 0; i < space.length; i++) {
612 vals[space.charAt(i)] = values[space][i];
613 }
614
615 if (values.alpha !== 1) {
616 vals.a = values.alpha;
617 }
618
619 // {r: 255, g: 255, b: 255, a: 0.4}
620 return vals;
621};
622
623Color.prototype.setValues = function (space, vals) {
624 var values = this.values;
625 var spaces = this.spaces;
626 var maxes = this.maxes;
627 var alpha = 1;
628 var i;
629
630 this.valid = true;
631
632 if (space === 'alpha') {
633 alpha = vals;
634 } else if (vals.length) {
635 // [10, 10, 10]
636 values[space] = vals.slice(0, space.length);
637 alpha = vals[space.length];
638 } else if (vals[space.charAt(0)] !== undefined) {
639 // {r: 10, g: 10, b: 10}
640 for (i = 0; i < space.length; i++) {
641 values[space][i] = vals[space.charAt(i)];
642 }
643
644 alpha = vals.a;
645 } else if (vals[spaces[space][0]] !== undefined) {
646 // {red: 10, green: 10, blue: 10}
647 var chans = spaces[space];
648
649 for (i = 0; i < space.length; i++) {
650 values[space][i] = vals[chans[i]];
651 }
652
653 alpha = vals.alpha;
654 }
655
656 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
657
658 if (space === 'alpha') {
659 return false;
660 }
661
662 var capped;
663
664 // cap values of the space prior converting all values
665 for (i = 0; i < space.length; i++) {
666 capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
667 values[space][i] = Math.round(capped);
668 }
669
670 // convert to all the other color spaces
671 for (var sname in spaces) {
672 if (sname !== space) {
673 values[sname] = convert[space][sname](values[space]);
674 }
675 }
676
677 return true;
678};
679
680Color.prototype.setSpace = function (space, args) {
681 var vals = args[0];
682
683 if (vals === undefined) {
684 // color.rgb()
685 return this.getValues(space);
686 }
687
688 // color.rgb(10, 10, 10)
689 if (typeof vals === 'number') {
690 vals = Array.prototype.slice.call(args);
691 }
692
693 this.setValues(space, vals);
694 return this;
695};
696
697Color.prototype.setChannel = function (space, index, val) {
698 var svalues = this.values[space];
699 if (val === undefined) {
700 // color.red()
701 return svalues[index];
702 } else if (val === svalues[index]) {
703 // color.red(color.red())
704 return this;
705 }
706
707 // color.red(100)
708 svalues[index] = val;
709 this.setValues(space, svalues);
710
711 return this;
712};
713
714if (typeof window !== 'undefined') {
715 window.Color = Color;
716}
717
718module.exports = Color;
719
720},{"1":1,"4":4}],3:[function(require,module,exports){
721/* MIT license */
722
723module.exports = {
724 rgb2hsl: rgb2hsl,
725 rgb2hsv: rgb2hsv,
726 rgb2hwb: rgb2hwb,
727 rgb2cmyk: rgb2cmyk,
728 rgb2keyword: rgb2keyword,
729 rgb2xyz: rgb2xyz,
730 rgb2lab: rgb2lab,
731 rgb2lch: rgb2lch,
732
733 hsl2rgb: hsl2rgb,
734 hsl2hsv: hsl2hsv,
735 hsl2hwb: hsl2hwb,
736 hsl2cmyk: hsl2cmyk,
737 hsl2keyword: hsl2keyword,
738
739 hsv2rgb: hsv2rgb,
740 hsv2hsl: hsv2hsl,
741 hsv2hwb: hsv2hwb,
742 hsv2cmyk: hsv2cmyk,
743 hsv2keyword: hsv2keyword,
744
745 hwb2rgb: hwb2rgb,
746 hwb2hsl: hwb2hsl,
747 hwb2hsv: hwb2hsv,
748 hwb2cmyk: hwb2cmyk,
749 hwb2keyword: hwb2keyword,
750
751 cmyk2rgb: cmyk2rgb,
752 cmyk2hsl: cmyk2hsl,
753 cmyk2hsv: cmyk2hsv,
754 cmyk2hwb: cmyk2hwb,
755 cmyk2keyword: cmyk2keyword,
756
757 keyword2rgb: keyword2rgb,
758 keyword2hsl: keyword2hsl,
759 keyword2hsv: keyword2hsv,
760 keyword2hwb: keyword2hwb,
761 keyword2cmyk: keyword2cmyk,
762 keyword2lab: keyword2lab,
763 keyword2xyz: keyword2xyz,
764
765 xyz2rgb: xyz2rgb,
766 xyz2lab: xyz2lab,
767 xyz2lch: xyz2lch,
768
769 lab2xyz: lab2xyz,
770 lab2rgb: lab2rgb,
771 lab2lch: lab2lch,
772
773 lch2lab: lch2lab,
774 lch2xyz: lch2xyz,
775 lch2rgb: lch2rgb
776}
777
778
779function rgb2hsl(rgb) {
780 var r = rgb[0]/255,
781 g = rgb[1]/255,
782 b = rgb[2]/255,
783 min = Math.min(r, g, b),
784 max = Math.max(r, g, b),
785 delta = max - min,
786 h, s, l;
787
788 if (max == min)
789 h = 0;
790 else if (r == max)
791 h = (g - b) / delta;
792 else if (g == max)
793 h = 2 + (b - r) / delta;
794 else if (b == max)
795 h = 4 + (r - g)/ delta;
796
797 h = Math.min(h * 60, 360);
798
799 if (h < 0)
800 h += 360;
801
802 l = (min + max) / 2;
803
804 if (max == min)
805 s = 0;
806 else if (l <= 0.5)
807 s = delta / (max + min);
808 else
809 s = delta / (2 - max - min);
810
811 return [h, s * 100, l * 100];
812}
813
814function rgb2hsv(rgb) {
815 var r = rgb[0],
816 g = rgb[1],
817 b = rgb[2],
818 min = Math.min(r, g, b),
819 max = Math.max(r, g, b),
820 delta = max - min,
821 h, s, v;
822
823 if (max == 0)
824 s = 0;
825 else
826 s = (delta/max * 1000)/10;
827
828 if (max == min)
829 h = 0;
830 else if (r == max)
831 h = (g - b) / delta;
832 else if (g == max)
833 h = 2 + (b - r) / delta;
834 else if (b == max)
835 h = 4 + (r - g) / delta;
836
837 h = Math.min(h * 60, 360);
838
839 if (h < 0)
840 h += 360;
841
842 v = ((max / 255) * 1000) / 10;
843
844 return [h, s, v];
845}
846
847function rgb2hwb(rgb) {
848 var r = rgb[0],
849 g = rgb[1],
850 b = rgb[2],
851 h = rgb2hsl(rgb)[0],
852 w = 1/255 * Math.min(r, Math.min(g, b)),
853 b = 1 - 1/255 * Math.max(r, Math.max(g, b));
854
855 return [h, w * 100, b * 100];
856}
857
858function rgb2cmyk(rgb) {
859 var r = rgb[0] / 255,
860 g = rgb[1] / 255,
861 b = rgb[2] / 255,
862 c, m, y, k;
863
864 k = Math.min(1 - r, 1 - g, 1 - b);
865 c = (1 - r - k) / (1 - k) || 0;
866 m = (1 - g - k) / (1 - k) || 0;
867 y = (1 - b - k) / (1 - k) || 0;
868 return [c * 100, m * 100, y * 100, k * 100];
869}
870
871function rgb2keyword(rgb) {
872 return reverseKeywords[JSON.stringify(rgb)];
873}
874
875function rgb2xyz(rgb) {
876 var r = rgb[0] / 255,
877 g = rgb[1] / 255,
878 b = rgb[2] / 255;
879
880 // assume sRGB
881 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
882 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
883 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
884
885 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
886 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
887 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
888
889 return [x * 100, y *100, z * 100];
890}
891
892function rgb2lab(rgb) {
893 var xyz = rgb2xyz(rgb),
894 x = xyz[0],
895 y = xyz[1],
896 z = xyz[2],
897 l, a, b;
898
899 x /= 95.047;
900 y /= 100;
901 z /= 108.883;
902
903 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
904 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
905 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
906
907 l = (116 * y) - 16;
908 a = 500 * (x - y);
909 b = 200 * (y - z);
910
911 return [l, a, b];
912}
913
914function rgb2lch(args) {
915 return lab2lch(rgb2lab(args));
916}
917
918function hsl2rgb(hsl) {
919 var h = hsl[0] / 360,
920 s = hsl[1] / 100,
921 l = hsl[2] / 100,
922 t1, t2, t3, rgb, val;
923
924 if (s == 0) {
925 val = l * 255;
926 return [val, val, val];
927 }
928
929 if (l < 0.5)
930 t2 = l * (1 + s);
931 else
932 t2 = l + s - l * s;
933 t1 = 2 * l - t2;
934
935 rgb = [0, 0, 0];
936 for (var i = 0; i < 3; i++) {
937 t3 = h + 1 / 3 * - (i - 1);
938 t3 < 0 && t3++;
939 t3 > 1 && t3--;
940
941 if (6 * t3 < 1)
942 val = t1 + (t2 - t1) * 6 * t3;
943 else if (2 * t3 < 1)
944 val = t2;
945 else if (3 * t3 < 2)
946 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
947 else
948 val = t1;
949
950 rgb[i] = val * 255;
951 }
952
953 return rgb;
954}
955
956function hsl2hsv(hsl) {
957 var h = hsl[0],
958 s = hsl[1] / 100,
959 l = hsl[2] / 100,
960 sv, v;
961
962 if(l === 0) {
963 // no need to do calc on black
964 // also avoids divide by 0 error
965 return [0, 0, 0];
966 }
967
968 l *= 2;
969 s *= (l <= 1) ? l : 2 - l;
970 v = (l + s) / 2;
971 sv = (2 * s) / (l + s);
972 return [h, sv * 100, v * 100];
973}
974
975function hsl2hwb(args) {
976 return rgb2hwb(hsl2rgb(args));
977}
978
979function hsl2cmyk(args) {
980 return rgb2cmyk(hsl2rgb(args));
981}
982
983function hsl2keyword(args) {
984 return rgb2keyword(hsl2rgb(args));
985}
986
987
988function hsv2rgb(hsv) {
989 var h = hsv[0] / 60,
990 s = hsv[1] / 100,
991 v = hsv[2] / 100,
992 hi = Math.floor(h) % 6;
993
994 var f = h - Math.floor(h),
995 p = 255 * v * (1 - s),
996 q = 255 * v * (1 - (s * f)),
997 t = 255 * v * (1 - (s * (1 - f))),
998 v = 255 * v;
999
1000 switch(hi) {
1001 case 0:
1002 return [v, t, p];
1003 case 1:
1004 return [q, v, p];
1005 case 2:
1006 return [p, v, t];
1007 case 3:
1008 return [p, q, v];
1009 case 4:
1010 return [t, p, v];
1011 case 5:
1012 return [v, p, q];
1013 }
1014}
1015
1016function hsv2hsl(hsv) {
1017 var h = hsv[0],
1018 s = hsv[1] / 100,
1019 v = hsv[2] / 100,
1020 sl, l;
1021
1022 l = (2 - s) * v;
1023 sl = s * v;
1024 sl /= (l <= 1) ? l : 2 - l;
1025 sl = sl || 0;
1026 l /= 2;
1027 return [h, sl * 100, l * 100];
1028}
1029
1030function hsv2hwb(args) {
1031 return rgb2hwb(hsv2rgb(args))
1032}
1033
1034function hsv2cmyk(args) {
1035 return rgb2cmyk(hsv2rgb(args));
1036}
1037
1038function hsv2keyword(args) {
1039 return rgb2keyword(hsv2rgb(args));
1040}
1041
1042// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
1043function hwb2rgb(hwb) {
1044 var h = hwb[0] / 360,
1045 wh = hwb[1] / 100,
1046 bl = hwb[2] / 100,
1047 ratio = wh + bl,
1048 i, v, f, n;
1049
1050 // wh + bl cant be > 1
1051 if (ratio > 1) {
1052 wh /= ratio;
1053 bl /= ratio;
1054 }
1055
1056 i = Math.floor(6 * h);
1057 v = 1 - bl;
1058 f = 6 * h - i;
1059 if ((i & 0x01) != 0) {
1060 f = 1 - f;
1061 }
1062 n = wh + f * (v - wh); // linear interpolation
1063
1064 switch (i) {
1065 default:
1066 case 6:
1067 case 0: r = v; g = n; b = wh; break;
1068 case 1: r = n; g = v; b = wh; break;
1069 case 2: r = wh; g = v; b = n; break;
1070 case 3: r = wh; g = n; b = v; break;
1071 case 4: r = n; g = wh; b = v; break;
1072 case 5: r = v; g = wh; b = n; break;
1073 }
1074
1075 return [r * 255, g * 255, b * 255];
1076}
1077
1078function hwb2hsl(args) {
1079 return rgb2hsl(hwb2rgb(args));
1080}
1081
1082function hwb2hsv(args) {
1083 return rgb2hsv(hwb2rgb(args));
1084}
1085
1086function hwb2cmyk(args) {
1087 return rgb2cmyk(hwb2rgb(args));
1088}
1089
1090function hwb2keyword(args) {
1091 return rgb2keyword(hwb2rgb(args));
1092}
1093
1094function cmyk2rgb(cmyk) {
1095 var c = cmyk[0] / 100,
1096 m = cmyk[1] / 100,
1097 y = cmyk[2] / 100,
1098 k = cmyk[3] / 100,
1099 r, g, b;
1100
1101 r = 1 - Math.min(1, c * (1 - k) + k);
1102 g = 1 - Math.min(1, m * (1 - k) + k);
1103 b = 1 - Math.min(1, y * (1 - k) + k);
1104 return [r * 255, g * 255, b * 255];
1105}
1106
1107function cmyk2hsl(args) {
1108 return rgb2hsl(cmyk2rgb(args));
1109}
1110
1111function cmyk2hsv(args) {
1112 return rgb2hsv(cmyk2rgb(args));
1113}
1114
1115function cmyk2hwb(args) {
1116 return rgb2hwb(cmyk2rgb(args));
1117}
1118
1119function cmyk2keyword(args) {
1120 return rgb2keyword(cmyk2rgb(args));
1121}
1122
1123
1124function xyz2rgb(xyz) {
1125 var x = xyz[0] / 100,
1126 y = xyz[1] / 100,
1127 z = xyz[2] / 100,
1128 r, g, b;
1129
1130 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
1131 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
1132 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
1133
1134 // assume sRGB
1135 r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
1136 : r = (r * 12.92);
1137
1138 g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
1139 : g = (g * 12.92);
1140
1141 b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
1142 : b = (b * 12.92);
1143
1144 r = Math.min(Math.max(0, r), 1);
1145 g = Math.min(Math.max(0, g), 1);
1146 b = Math.min(Math.max(0, b), 1);
1147
1148 return [r * 255, g * 255, b * 255];
1149}
1150
1151function xyz2lab(xyz) {
1152 var x = xyz[0],
1153 y = xyz[1],
1154 z = xyz[2],
1155 l, a, b;
1156
1157 x /= 95.047;
1158 y /= 100;
1159 z /= 108.883;
1160
1161 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
1162 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
1163 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
1164
1165 l = (116 * y) - 16;
1166 a = 500 * (x - y);
1167 b = 200 * (y - z);
1168
1169 return [l, a, b];
1170}
1171
1172function xyz2lch(args) {
1173 return lab2lch(xyz2lab(args));
1174}
1175
1176function lab2xyz(lab) {
1177 var l = lab[0],
1178 a = lab[1],
1179 b = lab[2],
1180 x, y, z, y2;
1181
1182 if (l <= 8) {
1183 y = (l * 100) / 903.3;
1184 y2 = (7.787 * (y / 100)) + (16 / 116);
1185 } else {
1186 y = 100 * Math.pow((l + 16) / 116, 3);
1187 y2 = Math.pow(y / 100, 1/3);
1188 }
1189
1190 x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
1191
1192 z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
1193
1194 return [x, y, z];
1195}
1196
1197function lab2lch(lab) {
1198 var l = lab[0],
1199 a = lab[1],
1200 b = lab[2],
1201 hr, h, c;
1202
1203 hr = Math.atan2(b, a);
1204 h = hr * 360 / 2 / Math.PI;
1205 if (h < 0) {
1206 h += 360;
1207 }
1208 c = Math.sqrt(a * a + b * b);
1209 return [l, c, h];
1210}
1211
1212function lab2rgb(args) {
1213 return xyz2rgb(lab2xyz(args));
1214}
1215
1216function lch2lab(lch) {
1217 var l = lch[0],
1218 c = lch[1],
1219 h = lch[2],
1220 a, b, hr;
1221
1222 hr = h / 360 * 2 * Math.PI;
1223 a = c * Math.cos(hr);
1224 b = c * Math.sin(hr);
1225 return [l, a, b];
1226}
1227
1228function lch2xyz(args) {
1229 return lab2xyz(lch2lab(args));
1230}
1231
1232function lch2rgb(args) {
1233 return lab2rgb(lch2lab(args));
1234}
1235
1236function keyword2rgb(keyword) {
1237 return cssKeywords[keyword];
1238}
1239
1240function keyword2hsl(args) {
1241 return rgb2hsl(keyword2rgb(args));
1242}
1243
1244function keyword2hsv(args) {
1245 return rgb2hsv(keyword2rgb(args));
1246}
1247
1248function keyword2hwb(args) {
1249 return rgb2hwb(keyword2rgb(args));
1250}
1251
1252function keyword2cmyk(args) {
1253 return rgb2cmyk(keyword2rgb(args));
1254}
1255
1256function keyword2lab(args) {
1257 return rgb2lab(keyword2rgb(args));
1258}
1259
1260function keyword2xyz(args) {
1261 return rgb2xyz(keyword2rgb(args));
1262}
1263
1264var cssKeywords = {
1265 aliceblue: [240,248,255],
1266 antiquewhite: [250,235,215],
1267 aqua: [0,255,255],
1268 aquamarine: [127,255,212],
1269 azure: [240,255,255],
1270 beige: [245,245,220],
1271 bisque: [255,228,196],
1272 black: [0,0,0],
1273 blanchedalmond: [255,235,205],
1274 blue: [0,0,255],
1275 blueviolet: [138,43,226],
1276 brown: [165,42,42],
1277 burlywood: [222,184,135],
1278 cadetblue: [95,158,160],
1279 chartreuse: [127,255,0],
1280 chocolate: [210,105,30],
1281 coral: [255,127,80],
1282 cornflowerblue: [100,149,237],
1283 cornsilk: [255,248,220],
1284 crimson: [220,20,60],
1285 cyan: [0,255,255],
1286 darkblue: [0,0,139],
1287 darkcyan: [0,139,139],
1288 darkgoldenrod: [184,134,11],
1289 darkgray: [169,169,169],
1290 darkgreen: [0,100,0],
1291 darkgrey: [169,169,169],
1292 darkkhaki: [189,183,107],
1293 darkmagenta: [139,0,139],
1294 darkolivegreen: [85,107,47],
1295 darkorange: [255,140,0],
1296 darkorchid: [153,50,204],
1297 darkred: [139,0,0],
1298 darksalmon: [233,150,122],
1299 darkseagreen: [143,188,143],
1300 darkslateblue: [72,61,139],
1301 darkslategray: [47,79,79],
1302 darkslategrey: [47,79,79],
1303 darkturquoise: [0,206,209],
1304 darkviolet: [148,0,211],
1305 deeppink: [255,20,147],
1306 deepskyblue: [0,191,255],
1307 dimgray: [105,105,105],
1308 dimgrey: [105,105,105],
1309 dodgerblue: [30,144,255],
1310 firebrick: [178,34,34],
1311 floralwhite: [255,250,240],
1312 forestgreen: [34,139,34],
1313 fuchsia: [255,0,255],
1314 gainsboro: [220,220,220],
1315 ghostwhite: [248,248,255],
1316 gold: [255,215,0],
1317 goldenrod: [218,165,32],
1318 gray: [128,128,128],
1319 green: [0,128,0],
1320 greenyellow: [173,255,47],
1321 grey: [128,128,128],
1322 honeydew: [240,255,240],
1323 hotpink: [255,105,180],
1324 indianred: [205,92,92],
1325 indigo: [75,0,130],
1326 ivory: [255,255,240],
1327 khaki: [240,230,140],
1328 lavender: [230,230,250],
1329 lavenderblush: [255,240,245],
1330 lawngreen: [124,252,0],
1331 lemonchiffon: [255,250,205],
1332 lightblue: [173,216,230],
1333 lightcoral: [240,128,128],
1334 lightcyan: [224,255,255],
1335 lightgoldenrodyellow: [250,250,210],
1336 lightgray: [211,211,211],
1337 lightgreen: [144,238,144],
1338 lightgrey: [211,211,211],
1339 lightpink: [255,182,193],
1340 lightsalmon: [255,160,122],
1341 lightseagreen: [32,178,170],
1342 lightskyblue: [135,206,250],
1343 lightslategray: [119,136,153],
1344 lightslategrey: [119,136,153],
1345 lightsteelblue: [176,196,222],
1346 lightyellow: [255,255,224],
1347 lime: [0,255,0],
1348 limegreen: [50,205,50],
1349 linen: [250,240,230],
1350 magenta: [255,0,255],
1351 maroon: [128,0,0],
1352 mediumaquamarine: [102,205,170],
1353 mediumblue: [0,0,205],
1354 mediumorchid: [186,85,211],
1355 mediumpurple: [147,112,219],
1356 mediumseagreen: [60,179,113],
1357 mediumslateblue: [123,104,238],
1358 mediumspringgreen: [0,250,154],
1359 mediumturquoise: [72,209,204],
1360 mediumvioletred: [199,21,133],
1361 midnightblue: [25,25,112],
1362 mintcream: [245,255,250],
1363 mistyrose: [255,228,225],
1364 moccasin: [255,228,181],
1365 navajowhite: [255,222,173],
1366 navy: [0,0,128],
1367 oldlace: [253,245,230],
1368 olive: [128,128,0],
1369 olivedrab: [107,142,35],
1370 orange: [255,165,0],
1371 orangered: [255,69,0],
1372 orchid: [218,112,214],
1373 palegoldenrod: [238,232,170],
1374 palegreen: [152,251,152],
1375 paleturquoise: [175,238,238],
1376 palevioletred: [219,112,147],
1377 papayawhip: [255,239,213],
1378 peachpuff: [255,218,185],
1379 peru: [205,133,63],
1380 pink: [255,192,203],
1381 plum: [221,160,221],
1382 powderblue: [176,224,230],
1383 purple: [128,0,128],
1384 rebeccapurple: [102, 51, 153],
1385 red: [255,0,0],
1386 rosybrown: [188,143,143],
1387 royalblue: [65,105,225],
1388 saddlebrown: [139,69,19],
1389 salmon: [250,128,114],
1390 sandybrown: [244,164,96],
1391 seagreen: [46,139,87],
1392 seashell: [255,245,238],
1393 sienna: [160,82,45],
1394 silver: [192,192,192],
1395 skyblue: [135,206,235],
1396 slateblue: [106,90,205],
1397 slategray: [112,128,144],
1398 slategrey: [112,128,144],
1399 snow: [255,250,250],
1400 springgreen: [0,255,127],
1401 steelblue: [70,130,180],
1402 tan: [210,180,140],
1403 teal: [0,128,128],
1404 thistle: [216,191,216],
1405 tomato: [255,99,71],
1406 turquoise: [64,224,208],
1407 violet: [238,130,238],
1408 wheat: [245,222,179],
1409 white: [255,255,255],
1410 whitesmoke: [245,245,245],
1411 yellow: [255,255,0],
1412 yellowgreen: [154,205,50]
1413};
1414
1415var reverseKeywords = {};
1416for (var key in cssKeywords) {
1417 reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
1418}
1419
1420},{}],4:[function(require,module,exports){
1421var conversions = require(3);
1422
1423var convert = function() {
1424 return new Converter();
1425}
1426
1427for (var func in conversions) {
1428 // export Raw versions
1429 convert[func + "Raw"] = (function(func) {
1430 // accept array or plain args
1431 return function(arg) {
1432 if (typeof arg == "number")
1433 arg = Array.prototype.slice.call(arguments);
1434 return conversions[func](arg);
1435 }
1436 })(func);
1437
1438 var pair = /(\w+)2(\w+)/.exec(func),
1439 from = pair[1],
1440 to = pair[2];
1441
1442 // export rgb2hsl and ["rgb"]["hsl"]
1443 convert[from] = convert[from] || {};
1444
1445 convert[from][to] = convert[func] = (function(func) {
1446 return function(arg) {
1447 if (typeof arg == "number")
1448 arg = Array.prototype.slice.call(arguments);
1449
1450 var val = conversions[func](arg);
1451 if (typeof val == "string" || val === undefined)
1452 return val; // keyword
1453
1454 for (var i = 0; i < val.length; i++)
1455 val[i] = Math.round(val[i]);
1456 return val;
1457 }
1458 })(func);
1459}
1460
1461
1462/* Converter does lazy conversion and caching */
1463var Converter = function() {
1464 this.convs = {};
1465};
1466
1467/* Either get the values for a space or
1468 set the values for a space, depending on args */
1469Converter.prototype.routeSpace = function(space, args) {
1470 var values = args[0];
1471 if (values === undefined) {
1472 // color.rgb()
1473 return this.getValues(space);
1474 }
1475 // color.rgb(10, 10, 10)
1476 if (typeof values == "number") {
1477 values = Array.prototype.slice.call(args);
1478 }
1479
1480 return this.setValues(space, values);
1481};
1482
1483/* Set the values for a space, invalidating cache */
1484Converter.prototype.setValues = function(space, values) {
1485 this.space = space;
1486 this.convs = {};
1487 this.convs[space] = values;
1488 return this;
1489};
1490
1491/* Get the values for a space. If there's already
1492 a conversion for the space, fetch it, otherwise
1493 compute it */
1494Converter.prototype.getValues = function(space) {
1495 var vals = this.convs[space];
1496 if (!vals) {
1497 var fspace = this.space,
1498 from = this.convs[fspace];
1499 vals = convert[fspace][space](from);
1500
1501 this.convs[space] = vals;
1502 }
1503 return vals;
1504};
1505
1506["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
1507 Converter.prototype[space] = function(vals) {
1508 return this.routeSpace(space, arguments);
1509 }
1510});
1511
1512module.exports = convert;
1513},{"3":3}],5:[function(require,module,exports){
1514'use strict'
1515
1516module.exports = {
1517 "aliceblue": [240, 248, 255],
1518 "antiquewhite": [250, 235, 215],
1519 "aqua": [0, 255, 255],
1520 "aquamarine": [127, 255, 212],
1521 "azure": [240, 255, 255],
1522 "beige": [245, 245, 220],
1523 "bisque": [255, 228, 196],
1524 "black": [0, 0, 0],
1525 "blanchedalmond": [255, 235, 205],
1526 "blue": [0, 0, 255],
1527 "blueviolet": [138, 43, 226],
1528 "brown": [165, 42, 42],
1529 "burlywood": [222, 184, 135],
1530 "cadetblue": [95, 158, 160],
1531 "chartreuse": [127, 255, 0],
1532 "chocolate": [210, 105, 30],
1533 "coral": [255, 127, 80],
1534 "cornflowerblue": [100, 149, 237],
1535 "cornsilk": [255, 248, 220],
1536 "crimson": [220, 20, 60],
1537 "cyan": [0, 255, 255],
1538 "darkblue": [0, 0, 139],
1539 "darkcyan": [0, 139, 139],
1540 "darkgoldenrod": [184, 134, 11],
1541 "darkgray": [169, 169, 169],
1542 "darkgreen": [0, 100, 0],
1543 "darkgrey": [169, 169, 169],
1544 "darkkhaki": [189, 183, 107],
1545 "darkmagenta": [139, 0, 139],
1546 "darkolivegreen": [85, 107, 47],
1547 "darkorange": [255, 140, 0],
1548 "darkorchid": [153, 50, 204],
1549 "darkred": [139, 0, 0],
1550 "darksalmon": [233, 150, 122],
1551 "darkseagreen": [143, 188, 143],
1552 "darkslateblue": [72, 61, 139],
1553 "darkslategray": [47, 79, 79],
1554 "darkslategrey": [47, 79, 79],
1555 "darkturquoise": [0, 206, 209],
1556 "darkviolet": [148, 0, 211],
1557 "deeppink": [255, 20, 147],
1558 "deepskyblue": [0, 191, 255],
1559 "dimgray": [105, 105, 105],
1560 "dimgrey": [105, 105, 105],
1561 "dodgerblue": [30, 144, 255],
1562 "firebrick": [178, 34, 34],
1563 "floralwhite": [255, 250, 240],
1564 "forestgreen": [34, 139, 34],
1565 "fuchsia": [255, 0, 255],
1566 "gainsboro": [220, 220, 220],
1567 "ghostwhite": [248, 248, 255],
1568 "gold": [255, 215, 0],
1569 "goldenrod": [218, 165, 32],
1570 "gray": [128, 128, 128],
1571 "green": [0, 128, 0],
1572 "greenyellow": [173, 255, 47],
1573 "grey": [128, 128, 128],
1574 "honeydew": [240, 255, 240],
1575 "hotpink": [255, 105, 180],
1576 "indianred": [205, 92, 92],
1577 "indigo": [75, 0, 130],
1578 "ivory": [255, 255, 240],
1579 "khaki": [240, 230, 140],
1580 "lavender": [230, 230, 250],
1581 "lavenderblush": [255, 240, 245],
1582 "lawngreen": [124, 252, 0],
1583 "lemonchiffon": [255, 250, 205],
1584 "lightblue": [173, 216, 230],
1585 "lightcoral": [240, 128, 128],
1586 "lightcyan": [224, 255, 255],
1587 "lightgoldenrodyellow": [250, 250, 210],
1588 "lightgray": [211, 211, 211],
1589 "lightgreen": [144, 238, 144],
1590 "lightgrey": [211, 211, 211],
1591 "lightpink": [255, 182, 193],
1592 "lightsalmon": [255, 160, 122],
1593 "lightseagreen": [32, 178, 170],
1594 "lightskyblue": [135, 206, 250],
1595 "lightslategray": [119, 136, 153],
1596 "lightslategrey": [119, 136, 153],
1597 "lightsteelblue": [176, 196, 222],
1598 "lightyellow": [255, 255, 224],
1599 "lime": [0, 255, 0],
1600 "limegreen": [50, 205, 50],
1601 "linen": [250, 240, 230],
1602 "magenta": [255, 0, 255],
1603 "maroon": [128, 0, 0],
1604 "mediumaquamarine": [102, 205, 170],
1605 "mediumblue": [0, 0, 205],
1606 "mediumorchid": [186, 85, 211],
1607 "mediumpurple": [147, 112, 219],
1608 "mediumseagreen": [60, 179, 113],
1609 "mediumslateblue": [123, 104, 238],
1610 "mediumspringgreen": [0, 250, 154],
1611 "mediumturquoise": [72, 209, 204],
1612 "mediumvioletred": [199, 21, 133],
1613 "midnightblue": [25, 25, 112],
1614 "mintcream": [245, 255, 250],
1615 "mistyrose": [255, 228, 225],
1616 "moccasin": [255, 228, 181],
1617 "navajowhite": [255, 222, 173],
1618 "navy": [0, 0, 128],
1619 "oldlace": [253, 245, 230],
1620 "olive": [128, 128, 0],
1621 "olivedrab": [107, 142, 35],
1622 "orange": [255, 165, 0],
1623 "orangered": [255, 69, 0],
1624 "orchid": [218, 112, 214],
1625 "palegoldenrod": [238, 232, 170],
1626 "palegreen": [152, 251, 152],
1627 "paleturquoise": [175, 238, 238],
1628 "palevioletred": [219, 112, 147],
1629 "papayawhip": [255, 239, 213],
1630 "peachpuff": [255, 218, 185],
1631 "peru": [205, 133, 63],
1632 "pink": [255, 192, 203],
1633 "plum": [221, 160, 221],
1634 "powderblue": [176, 224, 230],
1635 "purple": [128, 0, 128],
1636 "rebeccapurple": [102, 51, 153],
1637 "red": [255, 0, 0],
1638 "rosybrown": [188, 143, 143],
1639 "royalblue": [65, 105, 225],
1640 "saddlebrown": [139, 69, 19],
1641 "salmon": [250, 128, 114],
1642 "sandybrown": [244, 164, 96],
1643 "seagreen": [46, 139, 87],
1644 "seashell": [255, 245, 238],
1645 "sienna": [160, 82, 45],
1646 "silver": [192, 192, 192],
1647 "skyblue": [135, 206, 235],
1648 "slateblue": [106, 90, 205],
1649 "slategray": [112, 128, 144],
1650 "slategrey": [112, 128, 144],
1651 "snow": [255, 250, 250],
1652 "springgreen": [0, 255, 127],
1653 "steelblue": [70, 130, 180],
1654 "tan": [210, 180, 140],
1655 "teal": [0, 128, 128],
1656 "thistle": [216, 191, 216],
1657 "tomato": [255, 99, 71],
1658 "turquoise": [64, 224, 208],
1659 "violet": [238, 130, 238],
1660 "wheat": [245, 222, 179],
1661 "white": [255, 255, 255],
1662 "whitesmoke": [245, 245, 245],
1663 "yellow": [255, 255, 0],
1664 "yellowgreen": [154, 205, 50]
1665};
1666
1667},{}],6:[function(require,module,exports){
1668//! moment.js
1669//! version : 2.20.1
1670//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
1671//! license : MIT
1672//! momentjs.com
1673
1674;(function (global, factory) {
1675 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
1676 typeof define === 'function' && define.amd ? define(factory) :
1677 global.moment = factory()
1678}(this, (function () { 'use strict';
1679
1680var hookCallback;
1681
1682function hooks () {
1683 return hookCallback.apply(null, arguments);
1684}
1685
1686// This is done to register the method called with moment()
1687// without creating circular dependencies.
1688function setHookCallback (callback) {
1689 hookCallback = callback;
1690}
1691
1692function isArray(input) {
1693 return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
1694}
1695
1696function isObject(input) {
1697 // IE8 will treat undefined and null as object if it wasn't for
1698 // input != null
1699 return input != null && Object.prototype.toString.call(input) === '[object Object]';
1700}
1701
1702function isObjectEmpty(obj) {
1703 if (Object.getOwnPropertyNames) {
1704 return (Object.getOwnPropertyNames(obj).length === 0);
1705 } else {
1706 var k;
1707 for (k in obj) {
1708 if (obj.hasOwnProperty(k)) {
1709 return false;
1710 }
1711 }
1712 return true;
1713 }
1714}
1715
1716function isUndefined(input) {
1717 return input === void 0;
1718}
1719
1720function isNumber(input) {
1721 return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
1722}
1723
1724function isDate(input) {
1725 return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
1726}
1727
1728function map(arr, fn) {
1729 var res = [], i;
1730 for (i = 0; i < arr.length; ++i) {
1731 res.push(fn(arr[i], i));
1732 }
1733 return res;
1734}
1735
1736function hasOwnProp(a, b) {
1737 return Object.prototype.hasOwnProperty.call(a, b);
1738}
1739
1740function extend(a, b) {
1741 for (var i in b) {
1742 if (hasOwnProp(b, i)) {
1743 a[i] = b[i];
1744 }
1745 }
1746
1747 if (hasOwnProp(b, 'toString')) {
1748 a.toString = b.toString;
1749 }
1750
1751 if (hasOwnProp(b, 'valueOf')) {
1752 a.valueOf = b.valueOf;
1753 }
1754
1755 return a;
1756}
1757
1758function createUTC (input, format, locale, strict) {
1759 return createLocalOrUTC(input, format, locale, strict, true).utc();
1760}
1761
1762function defaultParsingFlags() {
1763 // We need to deep clone this object.
1764 return {
1765 empty : false,
1766 unusedTokens : [],
1767 unusedInput : [],
1768 overflow : -2,
1769 charsLeftOver : 0,
1770 nullInput : false,
1771 invalidMonth : null,
1772 invalidFormat : false,
1773 userInvalidated : false,
1774 iso : false,
1775 parsedDateParts : [],
1776 meridiem : null,
1777 rfc2822 : false,
1778 weekdayMismatch : false
1779 };
1780}
1781
1782function getParsingFlags(m) {
1783 if (m._pf == null) {
1784 m._pf = defaultParsingFlags();
1785 }
1786 return m._pf;
1787}
1788
1789var some;
1790if (Array.prototype.some) {
1791 some = Array.prototype.some;
1792} else {
1793 some = function (fun) {
1794 var t = Object(this);
1795 var len = t.length >>> 0;
1796
1797 for (var i = 0; i < len; i++) {
1798 if (i in t && fun.call(this, t[i], i, t)) {
1799 return true;
1800 }
1801 }
1802
1803 return false;
1804 };
1805}
1806
1807function isValid(m) {
1808 if (m._isValid == null) {
1809 var flags = getParsingFlags(m);
1810 var parsedParts = some.call(flags.parsedDateParts, function (i) {
1811 return i != null;
1812 });
1813 var isNowValid = !isNaN(m._d.getTime()) &&
1814 flags.overflow < 0 &&
1815 !flags.empty &&
1816 !flags.invalidMonth &&
1817 !flags.invalidWeekday &&
1818 !flags.weekdayMismatch &&
1819 !flags.nullInput &&
1820 !flags.invalidFormat &&
1821 !flags.userInvalidated &&
1822 (!flags.meridiem || (flags.meridiem && parsedParts));
1823
1824 if (m._strict) {
1825 isNowValid = isNowValid &&
1826 flags.charsLeftOver === 0 &&
1827 flags.unusedTokens.length === 0 &&
1828 flags.bigHour === undefined;
1829 }
1830
1831 if (Object.isFrozen == null || !Object.isFrozen(m)) {
1832 m._isValid = isNowValid;
1833 }
1834 else {
1835 return isNowValid;
1836 }
1837 }
1838 return m._isValid;
1839}
1840
1841function createInvalid (flags) {
1842 var m = createUTC(NaN);
1843 if (flags != null) {
1844 extend(getParsingFlags(m), flags);
1845 }
1846 else {
1847 getParsingFlags(m).userInvalidated = true;
1848 }
1849
1850 return m;
1851}
1852
1853// Plugins that add properties should also add the key here (null value),
1854// so we can properly clone ourselves.
1855var momentProperties = hooks.momentProperties = [];
1856
1857function copyConfig(to, from) {
1858 var i, prop, val;
1859
1860 if (!isUndefined(from._isAMomentObject)) {
1861 to._isAMomentObject = from._isAMomentObject;
1862 }
1863 if (!isUndefined(from._i)) {
1864 to._i = from._i;
1865 }
1866 if (!isUndefined(from._f)) {
1867 to._f = from._f;
1868 }
1869 if (!isUndefined(from._l)) {
1870 to._l = from._l;
1871 }
1872 if (!isUndefined(from._strict)) {
1873 to._strict = from._strict;
1874 }
1875 if (!isUndefined(from._tzm)) {
1876 to._tzm = from._tzm;
1877 }
1878 if (!isUndefined(from._isUTC)) {
1879 to._isUTC = from._isUTC;
1880 }
1881 if (!isUndefined(from._offset)) {
1882 to._offset = from._offset;
1883 }
1884 if (!isUndefined(from._pf)) {
1885 to._pf = getParsingFlags(from);
1886 }
1887 if (!isUndefined(from._locale)) {
1888 to._locale = from._locale;
1889 }
1890
1891 if (momentProperties.length > 0) {
1892 for (i = 0; i < momentProperties.length; i++) {
1893 prop = momentProperties[i];
1894 val = from[prop];
1895 if (!isUndefined(val)) {
1896 to[prop] = val;
1897 }
1898 }
1899 }
1900
1901 return to;
1902}
1903
1904var updateInProgress = false;
1905
1906// Moment prototype object
1907function Moment(config) {
1908 copyConfig(this, config);
1909 this._d = new Date(config._d != null ? config._d.getTime() : NaN);
1910 if (!this.isValid()) {
1911 this._d = new Date(NaN);
1912 }
1913 // Prevent infinite loop in case updateOffset creates new moment
1914 // objects.
1915 if (updateInProgress === false) {
1916 updateInProgress = true;
1917 hooks.updateOffset(this);
1918 updateInProgress = false;
1919 }
1920}
1921
1922function isMoment (obj) {
1923 return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
1924}
1925
1926function absFloor (number) {
1927 if (number < 0) {
1928 // -0 -> 0
1929 return Math.ceil(number) || 0;
1930 } else {
1931 return Math.floor(number);
1932 }
1933}
1934
1935function toInt(argumentForCoercion) {
1936 var coercedNumber = +argumentForCoercion,
1937 value = 0;
1938
1939 if (coercedNumber !== 0 && isFinite(coercedNumber)) {
1940 value = absFloor(coercedNumber);
1941 }
1942
1943 return value;
1944}
1945
1946// compare two arrays, return the number of differences
1947function compareArrays(array1, array2, dontConvert) {
1948 var len = Math.min(array1.length, array2.length),
1949 lengthDiff = Math.abs(array1.length - array2.length),
1950 diffs = 0,
1951 i;
1952 for (i = 0; i < len; i++) {
1953 if ((dontConvert && array1[i] !== array2[i]) ||
1954 (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
1955 diffs++;
1956 }
1957 }
1958 return diffs + lengthDiff;
1959}
1960
1961function warn(msg) {
1962 if (hooks.suppressDeprecationWarnings === false &&
1963 (typeof console !== 'undefined') && console.warn) {
1964 console.warn('Deprecation warning: ' + msg);
1965 }
1966}
1967
1968function deprecate(msg, fn) {
1969 var firstTime = true;
1970
1971 return extend(function () {
1972 if (hooks.deprecationHandler != null) {
1973 hooks.deprecationHandler(null, msg);
1974 }
1975 if (firstTime) {
1976 var args = [];
1977 var arg;
1978 for (var i = 0; i < arguments.length; i++) {
1979 arg = '';
1980 if (typeof arguments[i] === 'object') {
1981 arg += '\n[' + i + '] ';
1982 for (var key in arguments[0]) {
1983 arg += key + ': ' + arguments[0][key] + ', ';
1984 }
1985 arg = arg.slice(0, -2); // Remove trailing comma and space
1986 } else {
1987 arg = arguments[i];
1988 }
1989 args.push(arg);
1990 }
1991 warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
1992 firstTime = false;
1993 }
1994 return fn.apply(this, arguments);
1995 }, fn);
1996}
1997
1998var deprecations = {};
1999
2000function deprecateSimple(name, msg) {
2001 if (hooks.deprecationHandler != null) {
2002 hooks.deprecationHandler(name, msg);
2003 }
2004 if (!deprecations[name]) {
2005 warn(msg);
2006 deprecations[name] = true;
2007 }
2008}
2009
2010hooks.suppressDeprecationWarnings = false;
2011hooks.deprecationHandler = null;
2012
2013function isFunction(input) {
2014 return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
2015}
2016
2017function set (config) {
2018 var prop, i;
2019 for (i in config) {
2020 prop = config[i];
2021 if (isFunction(prop)) {
2022 this[i] = prop;
2023 } else {
2024 this['_' + i] = prop;
2025 }
2026 }
2027 this._config = config;
2028 // Lenient ordinal parsing accepts just a number in addition to
2029 // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
2030 // TODO: Remove "ordinalParse" fallback in next major release.
2031 this._dayOfMonthOrdinalParseLenient = new RegExp(
2032 (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
2033 '|' + (/\d{1,2}/).source);
2034}
2035
2036function mergeConfigs(parentConfig, childConfig) {
2037 var res = extend({}, parentConfig), prop;
2038 for (prop in childConfig) {
2039 if (hasOwnProp(childConfig, prop)) {
2040 if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
2041 res[prop] = {};
2042 extend(res[prop], parentConfig[prop]);
2043 extend(res[prop], childConfig[prop]);
2044 } else if (childConfig[prop] != null) {
2045 res[prop] = childConfig[prop];
2046 } else {
2047 delete res[prop];
2048 }
2049 }
2050 }
2051 for (prop in parentConfig) {
2052 if (hasOwnProp(parentConfig, prop) &&
2053 !hasOwnProp(childConfig, prop) &&
2054 isObject(parentConfig[prop])) {
2055 // make sure changes to properties don't modify parent config
2056 res[prop] = extend({}, res[prop]);
2057 }
2058 }
2059 return res;
2060}
2061
2062function Locale(config) {
2063 if (config != null) {
2064 this.set(config);
2065 }
2066}
2067
2068var keys;
2069
2070if (Object.keys) {
2071 keys = Object.keys;
2072} else {
2073 keys = function (obj) {
2074 var i, res = [];
2075 for (i in obj) {
2076 if (hasOwnProp(obj, i)) {
2077 res.push(i);
2078 }
2079 }
2080 return res;
2081 };
2082}
2083
2084var defaultCalendar = {
2085 sameDay : '[Today at] LT',
2086 nextDay : '[Tomorrow at] LT',
2087 nextWeek : 'dddd [at] LT',
2088 lastDay : '[Yesterday at] LT',
2089 lastWeek : '[Last] dddd [at] LT',
2090 sameElse : 'L'
2091};
2092
2093function calendar (key, mom, now) {
2094 var output = this._calendar[key] || this._calendar['sameElse'];
2095 return isFunction(output) ? output.call(mom, now) : output;
2096}
2097
2098var defaultLongDateFormat = {
2099 LTS : 'h:mm:ss A',
2100 LT : 'h:mm A',
2101 L : 'MM/DD/YYYY',
2102 LL : 'MMMM D, YYYY',
2103 LLL : 'MMMM D, YYYY h:mm A',
2104 LLLL : 'dddd, MMMM D, YYYY h:mm A'
2105};
2106
2107function longDateFormat (key) {
2108 var format = this._longDateFormat[key],
2109 formatUpper = this._longDateFormat[key.toUpperCase()];
2110
2111 if (format || !formatUpper) {
2112 return format;
2113 }
2114
2115 this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
2116 return val.slice(1);
2117 });
2118
2119 return this._longDateFormat[key];
2120}
2121
2122var defaultInvalidDate = 'Invalid date';
2123
2124function invalidDate () {
2125 return this._invalidDate;
2126}
2127
2128var defaultOrdinal = '%d';
2129var defaultDayOfMonthOrdinalParse = /\d{1,2}/;
2130
2131function ordinal (number) {
2132 return this._ordinal.replace('%d', number);
2133}
2134
2135var defaultRelativeTime = {
2136 future : 'in %s',
2137 past : '%s ago',
2138 s : 'a few seconds',
2139 ss : '%d seconds',
2140 m : 'a minute',
2141 mm : '%d minutes',
2142 h : 'an hour',
2143 hh : '%d hours',
2144 d : 'a day',
2145 dd : '%d days',
2146 M : 'a month',
2147 MM : '%d months',
2148 y : 'a year',
2149 yy : '%d years'
2150};
2151
2152function relativeTime (number, withoutSuffix, string, isFuture) {
2153 var output = this._relativeTime[string];
2154 return (isFunction(output)) ?
2155 output(number, withoutSuffix, string, isFuture) :
2156 output.replace(/%d/i, number);
2157}
2158
2159function pastFuture (diff, output) {
2160 var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
2161 return isFunction(format) ? format(output) : format.replace(/%s/i, output);
2162}
2163
2164var aliases = {};
2165
2166function addUnitAlias (unit, shorthand) {
2167 var lowerCase = unit.toLowerCase();
2168 aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
2169}
2170
2171function normalizeUnits(units) {
2172 return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
2173}
2174
2175function normalizeObjectUnits(inputObject) {
2176 var normalizedInput = {},
2177 normalizedProp,
2178 prop;
2179
2180 for (prop in inputObject) {
2181 if (hasOwnProp(inputObject, prop)) {
2182 normalizedProp = normalizeUnits(prop);
2183 if (normalizedProp) {
2184 normalizedInput[normalizedProp] = inputObject[prop];
2185 }
2186 }
2187 }
2188
2189 return normalizedInput;
2190}
2191
2192var priorities = {};
2193
2194function addUnitPriority(unit, priority) {
2195 priorities[unit] = priority;
2196}
2197
2198function getPrioritizedUnits(unitsObj) {
2199 var units = [];
2200 for (var u in unitsObj) {
2201 units.push({unit: u, priority: priorities[u]});
2202 }
2203 units.sort(function (a, b) {
2204 return a.priority - b.priority;
2205 });
2206 return units;
2207}
2208
2209function zeroFill(number, targetLength, forceSign) {
2210 var absNumber = '' + Math.abs(number),
2211 zerosToFill = targetLength - absNumber.length,
2212 sign = number >= 0;
2213 return (sign ? (forceSign ? '+' : '') : '-') +
2214 Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
2215}
2216
2217var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
2218
2219var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
2220
2221var formatFunctions = {};
2222
2223var formatTokenFunctions = {};
2224
2225// token: 'M'
2226// padded: ['MM', 2]
2227// ordinal: 'Mo'
2228// callback: function () { this.month() + 1 }
2229function addFormatToken (token, padded, ordinal, callback) {
2230 var func = callback;
2231 if (typeof callback === 'string') {
2232 func = function () {
2233 return this[callback]();
2234 };
2235 }
2236 if (token) {
2237 formatTokenFunctions[token] = func;
2238 }
2239 if (padded) {
2240 formatTokenFunctions[padded[0]] = function () {
2241 return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
2242 };
2243 }
2244 if (ordinal) {
2245 formatTokenFunctions[ordinal] = function () {
2246 return this.localeData().ordinal(func.apply(this, arguments), token);
2247 };
2248 }
2249}
2250
2251function removeFormattingTokens(input) {
2252 if (input.match(/\[[\s\S]/)) {
2253 return input.replace(/^\[|\]$/g, '');
2254 }
2255 return input.replace(/\\/g, '');
2256}
2257
2258function makeFormatFunction(format) {
2259 var array = format.match(formattingTokens), i, length;
2260
2261 for (i = 0, length = array.length; i < length; i++) {
2262 if (formatTokenFunctions[array[i]]) {
2263 array[i] = formatTokenFunctions[array[i]];
2264 } else {
2265 array[i] = removeFormattingTokens(array[i]);
2266 }
2267 }
2268
2269 return function (mom) {
2270 var output = '', i;
2271 for (i = 0; i < length; i++) {
2272 output += isFunction(array[i]) ? array[i].call(mom, format) : array[i];
2273 }
2274 return output;
2275 };
2276}
2277
2278// format date using native date object
2279function formatMoment(m, format) {
2280 if (!m.isValid()) {
2281 return m.localeData().invalidDate();
2282 }
2283
2284 format = expandFormat(format, m.localeData());
2285 formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
2286
2287 return formatFunctions[format](m);
2288}
2289
2290function expandFormat(format, locale) {
2291 var i = 5;
2292
2293 function replaceLongDateFormatTokens(input) {
2294 return locale.longDateFormat(input) || input;
2295 }
2296
2297 localFormattingTokens.lastIndex = 0;
2298 while (i >= 0 && localFormattingTokens.test(format)) {
2299 format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
2300 localFormattingTokens.lastIndex = 0;
2301 i -= 1;
2302 }
2303
2304 return format;
2305}
2306
2307var match1 = /\d/; // 0 - 9
2308var match2 = /\d\d/; // 00 - 99
2309var match3 = /\d{3}/; // 000 - 999
2310var match4 = /\d{4}/; // 0000 - 9999
2311var match6 = /[+-]?\d{6}/; // -999999 - 999999
2312var match1to2 = /\d\d?/; // 0 - 99
2313var match3to4 = /\d\d\d\d?/; // 999 - 9999
2314var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999
2315var match1to3 = /\d{1,3}/; // 0 - 999
2316var match1to4 = /\d{1,4}/; // 0 - 9999
2317var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999
2318
2319var matchUnsigned = /\d+/; // 0 - inf
2320var matchSigned = /[+-]?\d+/; // -inf - inf
2321
2322var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
2323var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
2324
2325var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
2326
2327// any word (or two) characters or numbers including two/three word month in arabic.
2328// includes scottish gaelic two word and hyphenated months
2329var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;
2330
2331
2332var regexes = {};
2333
2334function addRegexToken (token, regex, strictRegex) {
2335 regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
2336 return (isStrict && strictRegex) ? strictRegex : regex;
2337 };
2338}
2339
2340function getParseRegexForToken (token, config) {
2341 if (!hasOwnProp(regexes, token)) {
2342 return new RegExp(unescapeFormat(token));
2343 }
2344
2345 return regexes[token](config._strict, config._locale);
2346}
2347
2348// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
2349function unescapeFormat(s) {
2350 return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
2351 return p1 || p2 || p3 || p4;
2352 }));
2353}
2354
2355function regexEscape(s) {
2356 return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
2357}
2358
2359var tokens = {};
2360
2361function addParseToken (token, callback) {
2362 var i, func = callback;
2363 if (typeof token === 'string') {
2364 token = [token];
2365 }
2366 if (isNumber(callback)) {
2367 func = function (input, array) {
2368 array[callback] = toInt(input);
2369 };
2370 }
2371 for (i = 0; i < token.length; i++) {
2372 tokens[token[i]] = func;
2373 }
2374}
2375
2376function addWeekParseToken (token, callback) {
2377 addParseToken(token, function (input, array, config, token) {
2378 config._w = config._w || {};
2379 callback(input, config._w, config, token);
2380 });
2381}
2382
2383function addTimeToArrayFromToken(token, input, config) {
2384 if (input != null && hasOwnProp(tokens, token)) {
2385 tokens[token](input, config._a, config, token);
2386 }
2387}
2388
2389var YEAR = 0;
2390var MONTH = 1;
2391var DATE = 2;
2392var HOUR = 3;
2393var MINUTE = 4;
2394var SECOND = 5;
2395var MILLISECOND = 6;
2396var WEEK = 7;
2397var WEEKDAY = 8;
2398
2399// FORMATTING
2400
2401addFormatToken('Y', 0, 0, function () {
2402 var y = this.year();
2403 return y <= 9999 ? '' + y : '+' + y;
2404});
2405
2406addFormatToken(0, ['YY', 2], 0, function () {
2407 return this.year() % 100;
2408});
2409
2410addFormatToken(0, ['YYYY', 4], 0, 'year');
2411addFormatToken(0, ['YYYYY', 5], 0, 'year');
2412addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
2413
2414// ALIASES
2415
2416addUnitAlias('year', 'y');
2417
2418// PRIORITIES
2419
2420addUnitPriority('year', 1);
2421
2422// PARSING
2423
2424addRegexToken('Y', matchSigned);
2425addRegexToken('YY', match1to2, match2);
2426addRegexToken('YYYY', match1to4, match4);
2427addRegexToken('YYYYY', match1to6, match6);
2428addRegexToken('YYYYYY', match1to6, match6);
2429
2430addParseToken(['YYYYY', 'YYYYYY'], YEAR);
2431addParseToken('YYYY', function (input, array) {
2432 array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
2433});
2434addParseToken('YY', function (input, array) {
2435 array[YEAR] = hooks.parseTwoDigitYear(input);
2436});
2437addParseToken('Y', function (input, array) {
2438 array[YEAR] = parseInt(input, 10);
2439});
2440
2441// HELPERS
2442
2443function daysInYear(year) {
2444 return isLeapYear(year) ? 366 : 365;
2445}
2446
2447function isLeapYear(year) {
2448 return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
2449}
2450
2451// HOOKS
2452
2453hooks.parseTwoDigitYear = function (input) {
2454 return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
2455};
2456
2457// MOMENTS
2458
2459var getSetYear = makeGetSet('FullYear', true);
2460
2461function getIsLeapYear () {
2462 return isLeapYear(this.year());
2463}
2464
2465function makeGetSet (unit, keepTime) {
2466 return function (value) {
2467 if (value != null) {
2468 set$1(this, unit, value);
2469 hooks.updateOffset(this, keepTime);
2470 return this;
2471 } else {
2472 return get(this, unit);
2473 }
2474 };
2475}
2476
2477function get (mom, unit) {
2478 return mom.isValid() ?
2479 mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
2480}
2481
2482function set$1 (mom, unit, value) {
2483 if (mom.isValid() && !isNaN(value)) {
2484 if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) {
2485 mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month()));
2486 }
2487 else {
2488 mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
2489 }
2490 }
2491}
2492
2493// MOMENTS
2494
2495function stringGet (units) {
2496 units = normalizeUnits(units);
2497 if (isFunction(this[units])) {
2498 return this[units]();
2499 }
2500 return this;
2501}
2502
2503
2504function stringSet (units, value) {
2505 if (typeof units === 'object') {
2506 units = normalizeObjectUnits(units);
2507 var prioritized = getPrioritizedUnits(units);
2508 for (var i = 0; i < prioritized.length; i++) {
2509 this[prioritized[i].unit](units[prioritized[i].unit]);
2510 }
2511 } else {
2512 units = normalizeUnits(units);
2513 if (isFunction(this[units])) {
2514 return this[units](value);
2515 }
2516 }
2517 return this;
2518}
2519
2520function mod(n, x) {
2521 return ((n % x) + x) % x;
2522}
2523
2524var indexOf;
2525
2526if (Array.prototype.indexOf) {
2527 indexOf = Array.prototype.indexOf;
2528} else {
2529 indexOf = function (o) {
2530 // I know
2531 var i;
2532 for (i = 0; i < this.length; ++i) {
2533 if (this[i] === o) {
2534 return i;
2535 }
2536 }
2537 return -1;
2538 };
2539}
2540
2541function daysInMonth(year, month) {
2542 if (isNaN(year) || isNaN(month)) {
2543 return NaN;
2544 }
2545 var modMonth = mod(month, 12);
2546 year += (month - modMonth) / 12;
2547 return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
2548}
2549
2550// FORMATTING
2551
2552addFormatToken('M', ['MM', 2], 'Mo', function () {
2553 return this.month() + 1;
2554});
2555
2556addFormatToken('MMM', 0, 0, function (format) {
2557 return this.localeData().monthsShort(this, format);
2558});
2559
2560addFormatToken('MMMM', 0, 0, function (format) {
2561 return this.localeData().months(this, format);
2562});
2563
2564// ALIASES
2565
2566addUnitAlias('month', 'M');
2567
2568// PRIORITY
2569
2570addUnitPriority('month', 8);
2571
2572// PARSING
2573
2574addRegexToken('M', match1to2);
2575addRegexToken('MM', match1to2, match2);
2576addRegexToken('MMM', function (isStrict, locale) {
2577 return locale.monthsShortRegex(isStrict);
2578});
2579addRegexToken('MMMM', function (isStrict, locale) {
2580 return locale.monthsRegex(isStrict);
2581});
2582
2583addParseToken(['M', 'MM'], function (input, array) {
2584 array[MONTH] = toInt(input) - 1;
2585});
2586
2587addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
2588 var month = config._locale.monthsParse(input, token, config._strict);
2589 // if we didn't find a month name, mark the date as invalid.
2590 if (month != null) {
2591 array[MONTH] = month;
2592 } else {
2593 getParsingFlags(config).invalidMonth = input;
2594 }
2595});
2596
2597// LOCALES
2598
2599var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
2600var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
2601function localeMonths (m, format) {
2602 if (!m) {
2603 return isArray(this._months) ? this._months :
2604 this._months['standalone'];
2605 }
2606 return isArray(this._months) ? this._months[m.month()] :
2607 this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
2608}
2609
2610var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
2611function localeMonthsShort (m, format) {
2612 if (!m) {
2613 return isArray(this._monthsShort) ? this._monthsShort :
2614 this._monthsShort['standalone'];
2615 }
2616 return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
2617 this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
2618}
2619
2620function handleStrictParse(monthName, format, strict) {
2621 var i, ii, mom, llc = monthName.toLocaleLowerCase();
2622 if (!this._monthsParse) {
2623 // this is not used
2624 this._monthsParse = [];
2625 this._longMonthsParse = [];
2626 this._shortMonthsParse = [];
2627 for (i = 0; i < 12; ++i) {
2628 mom = createUTC([2000, i]);
2629 this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
2630 this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
2631 }
2632 }
2633
2634 if (strict) {
2635 if (format === 'MMM') {
2636 ii = indexOf.call(this._shortMonthsParse, llc);
2637 return ii !== -1 ? ii : null;
2638 } else {
2639 ii = indexOf.call(this._longMonthsParse, llc);
2640 return ii !== -1 ? ii : null;
2641 }
2642 } else {
2643 if (format === 'MMM') {
2644 ii = indexOf.call(this._shortMonthsParse, llc);
2645 if (ii !== -1) {
2646 return ii;
2647 }
2648 ii = indexOf.call(this._longMonthsParse, llc);
2649 return ii !== -1 ? ii : null;
2650 } else {
2651 ii = indexOf.call(this._longMonthsParse, llc);
2652 if (ii !== -1) {
2653 return ii;
2654 }
2655 ii = indexOf.call(this._shortMonthsParse, llc);
2656 return ii !== -1 ? ii : null;
2657 }
2658 }
2659}
2660
2661function localeMonthsParse (monthName, format, strict) {
2662 var i, mom, regex;
2663
2664 if (this._monthsParseExact) {
2665 return handleStrictParse.call(this, monthName, format, strict);
2666 }
2667
2668 if (!this._monthsParse) {
2669 this._monthsParse = [];
2670 this._longMonthsParse = [];
2671 this._shortMonthsParse = [];
2672 }
2673
2674 // TODO: add sorting
2675 // Sorting makes sure if one month (or abbr) is a prefix of another
2676 // see sorting in computeMonthsParse
2677 for (i = 0; i < 12; i++) {
2678 // make the regex if we don't have it already
2679 mom = createUTC([2000, i]);
2680 if (strict && !this._longMonthsParse[i]) {
2681 this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
2682 this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
2683 }
2684 if (!strict && !this._monthsParse[i]) {
2685 regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
2686 this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
2687 }
2688 // test the regex
2689 if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
2690 return i;
2691 } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
2692 return i;
2693 } else if (!strict && this._monthsParse[i].test(monthName)) {
2694 return i;
2695 }
2696 }
2697}
2698
2699// MOMENTS
2700
2701function setMonth (mom, value) {
2702 var dayOfMonth;
2703
2704 if (!mom.isValid()) {
2705 // No op
2706 return mom;
2707 }
2708
2709 if (typeof value === 'string') {
2710 if (/^\d+$/.test(value)) {
2711 value = toInt(value);
2712 } else {
2713 value = mom.localeData().monthsParse(value);
2714 // TODO: Another silent failure?
2715 if (!isNumber(value)) {
2716 return mom;
2717 }
2718 }
2719 }
2720
2721 dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
2722 mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
2723 return mom;
2724}
2725
2726function getSetMonth (value) {
2727 if (value != null) {
2728 setMonth(this, value);
2729 hooks.updateOffset(this, true);
2730 return this;
2731 } else {
2732 return get(this, 'Month');
2733 }
2734}
2735
2736function getDaysInMonth () {
2737 return daysInMonth(this.year(), this.month());
2738}
2739
2740var defaultMonthsShortRegex = matchWord;
2741function monthsShortRegex (isStrict) {
2742 if (this._monthsParseExact) {
2743 if (!hasOwnProp(this, '_monthsRegex')) {
2744 computeMonthsParse.call(this);
2745 }
2746 if (isStrict) {
2747 return this._monthsShortStrictRegex;
2748 } else {
2749 return this._monthsShortRegex;
2750 }
2751 } else {
2752 if (!hasOwnProp(this, '_monthsShortRegex')) {
2753 this._monthsShortRegex = defaultMonthsShortRegex;
2754 }
2755 return this._monthsShortStrictRegex && isStrict ?
2756 this._monthsShortStrictRegex : this._monthsShortRegex;
2757 }
2758}
2759
2760var defaultMonthsRegex = matchWord;
2761function monthsRegex (isStrict) {
2762 if (this._monthsParseExact) {
2763 if (!hasOwnProp(this, '_monthsRegex')) {
2764 computeMonthsParse.call(this);
2765 }
2766 if (isStrict) {
2767 return this._monthsStrictRegex;
2768 } else {
2769 return this._monthsRegex;
2770 }
2771 } else {
2772 if (!hasOwnProp(this, '_monthsRegex')) {
2773 this._monthsRegex = defaultMonthsRegex;
2774 }
2775 return this._monthsStrictRegex && isStrict ?
2776 this._monthsStrictRegex : this._monthsRegex;
2777 }
2778}
2779
2780function computeMonthsParse () {
2781 function cmpLenRev(a, b) {
2782 return b.length - a.length;
2783 }
2784
2785 var shortPieces = [], longPieces = [], mixedPieces = [],
2786 i, mom;
2787 for (i = 0; i < 12; i++) {
2788 // make the regex if we don't have it already
2789 mom = createUTC([2000, i]);
2790 shortPieces.push(this.monthsShort(mom, ''));
2791 longPieces.push(this.months(mom, ''));
2792 mixedPieces.push(this.months(mom, ''));
2793 mixedPieces.push(this.monthsShort(mom, ''));
2794 }
2795 // Sorting makes sure if one month (or abbr) is a prefix of another it
2796 // will match the longer piece.
2797 shortPieces.sort(cmpLenRev);
2798 longPieces.sort(cmpLenRev);
2799 mixedPieces.sort(cmpLenRev);
2800 for (i = 0; i < 12; i++) {
2801 shortPieces[i] = regexEscape(shortPieces[i]);
2802 longPieces[i] = regexEscape(longPieces[i]);
2803 }
2804 for (i = 0; i < 24; i++) {
2805 mixedPieces[i] = regexEscape(mixedPieces[i]);
2806 }
2807
2808 this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
2809 this._monthsShortRegex = this._monthsRegex;
2810 this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
2811 this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
2812}
2813
2814function createDate (y, m, d, h, M, s, ms) {
2815 // can't just apply() to create a date:
2816 // https://stackoverflow.com/q/181348
2817 var date = new Date(y, m, d, h, M, s, ms);
2818
2819 // the date constructor remaps years 0-99 to 1900-1999
2820 if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
2821 date.setFullYear(y);
2822 }
2823 return date;
2824}
2825
2826function createUTCDate (y) {
2827 var date = new Date(Date.UTC.apply(null, arguments));
2828
2829 // the Date.UTC function remaps years 0-99 to 1900-1999
2830 if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
2831 date.setUTCFullYear(y);
2832 }
2833 return date;
2834}
2835
2836// start-of-first-week - start-of-year
2837function firstWeekOffset(year, dow, doy) {
2838 var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
2839 fwd = 7 + dow - doy,
2840 // first-week day local weekday -- which local weekday is fwd
2841 fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
2842
2843 return -fwdlw + fwd - 1;
2844}
2845
2846// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
2847function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
2848 var localWeekday = (7 + weekday - dow) % 7,
2849 weekOffset = firstWeekOffset(year, dow, doy),
2850 dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
2851 resYear, resDayOfYear;
2852
2853 if (dayOfYear <= 0) {
2854 resYear = year - 1;
2855 resDayOfYear = daysInYear(resYear) + dayOfYear;
2856 } else if (dayOfYear > daysInYear(year)) {
2857 resYear = year + 1;
2858 resDayOfYear = dayOfYear - daysInYear(year);
2859 } else {
2860 resYear = year;
2861 resDayOfYear = dayOfYear;
2862 }
2863
2864 return {
2865 year: resYear,
2866 dayOfYear: resDayOfYear
2867 };
2868}
2869
2870function weekOfYear(mom, dow, doy) {
2871 var weekOffset = firstWeekOffset(mom.year(), dow, doy),
2872 week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
2873 resWeek, resYear;
2874
2875 if (week < 1) {
2876 resYear = mom.year() - 1;
2877 resWeek = week + weeksInYear(resYear, dow, doy);
2878 } else if (week > weeksInYear(mom.year(), dow, doy)) {
2879 resWeek = week - weeksInYear(mom.year(), dow, doy);
2880 resYear = mom.year() + 1;
2881 } else {
2882 resYear = mom.year();
2883 resWeek = week;
2884 }
2885
2886 return {
2887 week: resWeek,
2888 year: resYear
2889 };
2890}
2891
2892function weeksInYear(year, dow, doy) {
2893 var weekOffset = firstWeekOffset(year, dow, doy),
2894 weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
2895 return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
2896}
2897
2898// FORMATTING
2899
2900addFormatToken('w', ['ww', 2], 'wo', 'week');
2901addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
2902
2903// ALIASES
2904
2905addUnitAlias('week', 'w');
2906addUnitAlias('isoWeek', 'W');
2907
2908// PRIORITIES
2909
2910addUnitPriority('week', 5);
2911addUnitPriority('isoWeek', 5);
2912
2913// PARSING
2914
2915addRegexToken('w', match1to2);
2916addRegexToken('ww', match1to2, match2);
2917addRegexToken('W', match1to2);
2918addRegexToken('WW', match1to2, match2);
2919
2920addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
2921 week[token.substr(0, 1)] = toInt(input);
2922});
2923
2924// HELPERS
2925
2926// LOCALES
2927
2928function localeWeek (mom) {
2929 return weekOfYear(mom, this._week.dow, this._week.doy).week;
2930}
2931
2932var defaultLocaleWeek = {
2933 dow : 0, // Sunday is the first day of the week.
2934 doy : 6 // The week that contains Jan 1st is the first week of the year.
2935};
2936
2937function localeFirstDayOfWeek () {
2938 return this._week.dow;
2939}
2940
2941function localeFirstDayOfYear () {
2942 return this._week.doy;
2943}
2944
2945// MOMENTS
2946
2947function getSetWeek (input) {
2948 var week = this.localeData().week(this);
2949 return input == null ? week : this.add((input - week) * 7, 'd');
2950}
2951
2952function getSetISOWeek (input) {
2953 var week = weekOfYear(this, 1, 4).week;
2954 return input == null ? week : this.add((input - week) * 7, 'd');
2955}
2956
2957// FORMATTING
2958
2959addFormatToken('d', 0, 'do', 'day');
2960
2961addFormatToken('dd', 0, 0, function (format) {
2962 return this.localeData().weekdaysMin(this, format);
2963});
2964
2965addFormatToken('ddd', 0, 0, function (format) {
2966 return this.localeData().weekdaysShort(this, format);
2967});
2968
2969addFormatToken('dddd', 0, 0, function (format) {
2970 return this.localeData().weekdays(this, format);
2971});
2972
2973addFormatToken('e', 0, 0, 'weekday');
2974addFormatToken('E', 0, 0, 'isoWeekday');
2975
2976// ALIASES
2977
2978addUnitAlias('day', 'd');
2979addUnitAlias('weekday', 'e');
2980addUnitAlias('isoWeekday', 'E');
2981
2982// PRIORITY
2983addUnitPriority('day', 11);
2984addUnitPriority('weekday', 11);
2985addUnitPriority('isoWeekday', 11);
2986
2987// PARSING
2988
2989addRegexToken('d', match1to2);
2990addRegexToken('e', match1to2);
2991addRegexToken('E', match1to2);
2992addRegexToken('dd', function (isStrict, locale) {
2993 return locale.weekdaysMinRegex(isStrict);
2994});
2995addRegexToken('ddd', function (isStrict, locale) {
2996 return locale.weekdaysShortRegex(isStrict);
2997});
2998addRegexToken('dddd', function (isStrict, locale) {
2999 return locale.weekdaysRegex(isStrict);
3000});
3001
3002addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
3003 var weekday = config._locale.weekdaysParse(input, token, config._strict);
3004 // if we didn't get a weekday name, mark the date as invalid
3005 if (weekday != null) {
3006 week.d = weekday;
3007 } else {
3008 getParsingFlags(config).invalidWeekday = input;
3009 }
3010});
3011
3012addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
3013 week[token] = toInt(input);
3014});
3015
3016// HELPERS
3017
3018function parseWeekday(input, locale) {
3019 if (typeof input !== 'string') {
3020 return input;
3021 }
3022
3023 if (!isNaN(input)) {
3024 return parseInt(input, 10);
3025 }
3026
3027 input = locale.weekdaysParse(input);
3028 if (typeof input === 'number') {
3029 return input;
3030 }
3031
3032 return null;
3033}
3034
3035function parseIsoWeekday(input, locale) {
3036 if (typeof input === 'string') {
3037 return locale.weekdaysParse(input) % 7 || 7;
3038 }
3039 return isNaN(input) ? null : input;
3040}
3041
3042// LOCALES
3043
3044var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
3045function localeWeekdays (m, format) {
3046 if (!m) {
3047 return isArray(this._weekdays) ? this._weekdays :
3048 this._weekdays['standalone'];
3049 }
3050 return isArray(this._weekdays) ? this._weekdays[m.day()] :
3051 this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
3052}
3053
3054var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
3055function localeWeekdaysShort (m) {
3056 return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
3057}
3058
3059var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
3060function localeWeekdaysMin (m) {
3061 return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
3062}
3063
3064function handleStrictParse$1(weekdayName, format, strict) {
3065 var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
3066 if (!this._weekdaysParse) {
3067 this._weekdaysParse = [];
3068 this._shortWeekdaysParse = [];
3069 this._minWeekdaysParse = [];
3070
3071 for (i = 0; i < 7; ++i) {
3072 mom = createUTC([2000, 1]).day(i);
3073 this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
3074 this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
3075 this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
3076 }
3077 }
3078
3079 if (strict) {
3080 if (format === 'dddd') {
3081 ii = indexOf.call(this._weekdaysParse, llc);
3082 return ii !== -1 ? ii : null;
3083 } else if (format === 'ddd') {
3084 ii = indexOf.call(this._shortWeekdaysParse, llc);
3085 return ii !== -1 ? ii : null;
3086 } else {
3087 ii = indexOf.call(this._minWeekdaysParse, llc);
3088 return ii !== -1 ? ii : null;
3089 }
3090 } else {
3091 if (format === 'dddd') {
3092 ii = indexOf.call(this._weekdaysParse, llc);
3093 if (ii !== -1) {
3094 return ii;
3095 }
3096 ii = indexOf.call(this._shortWeekdaysParse, llc);
3097 if (ii !== -1) {
3098 return ii;
3099 }
3100 ii = indexOf.call(this._minWeekdaysParse, llc);
3101 return ii !== -1 ? ii : null;
3102 } else if (format === 'ddd') {
3103 ii = indexOf.call(this._shortWeekdaysParse, llc);
3104 if (ii !== -1) {
3105 return ii;
3106 }
3107 ii = indexOf.call(this._weekdaysParse, llc);
3108 if (ii !== -1) {
3109 return ii;
3110 }
3111 ii = indexOf.call(this._minWeekdaysParse, llc);
3112 return ii !== -1 ? ii : null;
3113 } else {
3114 ii = indexOf.call(this._minWeekdaysParse, llc);
3115 if (ii !== -1) {
3116 return ii;
3117 }
3118 ii = indexOf.call(this._weekdaysParse, llc);
3119 if (ii !== -1) {
3120 return ii;
3121 }
3122 ii = indexOf.call(this._shortWeekdaysParse, llc);
3123 return ii !== -1 ? ii : null;
3124 }
3125 }
3126}
3127
3128function localeWeekdaysParse (weekdayName, format, strict) {
3129 var i, mom, regex;
3130
3131 if (this._weekdaysParseExact) {
3132 return handleStrictParse$1.call(this, weekdayName, format, strict);
3133 }
3134
3135 if (!this._weekdaysParse) {
3136 this._weekdaysParse = [];
3137 this._minWeekdaysParse = [];
3138 this._shortWeekdaysParse = [];
3139 this._fullWeekdaysParse = [];
3140 }
3141
3142 for (i = 0; i < 7; i++) {
3143 // make the regex if we don't have it already
3144
3145 mom = createUTC([2000, 1]).day(i);
3146 if (strict && !this._fullWeekdaysParse[i]) {
3147 this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
3148 this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
3149 this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
3150 }
3151 if (!this._weekdaysParse[i]) {
3152 regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
3153 this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
3154 }
3155 // test the regex
3156 if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
3157 return i;
3158 } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
3159 return i;
3160 } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
3161 return i;
3162 } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
3163 return i;
3164 }
3165 }
3166}
3167
3168// MOMENTS
3169
3170function getSetDayOfWeek (input) {
3171 if (!this.isValid()) {
3172 return input != null ? this : NaN;
3173 }
3174 var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
3175 if (input != null) {
3176 input = parseWeekday(input, this.localeData());
3177 return this.add(input - day, 'd');
3178 } else {
3179 return day;
3180 }
3181}
3182
3183function getSetLocaleDayOfWeek (input) {
3184 if (!this.isValid()) {
3185 return input != null ? this : NaN;
3186 }
3187 var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
3188 return input == null ? weekday : this.add(input - weekday, 'd');
3189}
3190
3191function getSetISODayOfWeek (input) {
3192 if (!this.isValid()) {
3193 return input != null ? this : NaN;
3194 }
3195
3196 // behaves the same as moment#day except
3197 // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
3198 // as a setter, sunday should belong to the previous week.
3199
3200 if (input != null) {
3201 var weekday = parseIsoWeekday(input, this.localeData());
3202 return this.day(this.day() % 7 ? weekday : weekday - 7);
3203 } else {
3204 return this.day() || 7;
3205 }
3206}
3207
3208var defaultWeekdaysRegex = matchWord;
3209function weekdaysRegex (isStrict) {
3210 if (this._weekdaysParseExact) {
3211 if (!hasOwnProp(this, '_weekdaysRegex')) {
3212 computeWeekdaysParse.call(this);
3213 }
3214 if (isStrict) {
3215 return this._weekdaysStrictRegex;
3216 } else {
3217 return this._weekdaysRegex;
3218 }
3219 } else {
3220 if (!hasOwnProp(this, '_weekdaysRegex')) {
3221 this._weekdaysRegex = defaultWeekdaysRegex;
3222 }
3223 return this._weekdaysStrictRegex && isStrict ?
3224 this._weekdaysStrictRegex : this._weekdaysRegex;
3225 }
3226}
3227
3228var defaultWeekdaysShortRegex = matchWord;
3229function weekdaysShortRegex (isStrict) {
3230 if (this._weekdaysParseExact) {
3231 if (!hasOwnProp(this, '_weekdaysRegex')) {
3232 computeWeekdaysParse.call(this);
3233 }
3234 if (isStrict) {
3235 return this._weekdaysShortStrictRegex;
3236 } else {
3237 return this._weekdaysShortRegex;
3238 }
3239 } else {
3240 if (!hasOwnProp(this, '_weekdaysShortRegex')) {
3241 this._weekdaysShortRegex = defaultWeekdaysShortRegex;
3242 }
3243 return this._weekdaysShortStrictRegex && isStrict ?
3244 this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
3245 }
3246}
3247
3248var defaultWeekdaysMinRegex = matchWord;
3249function weekdaysMinRegex (isStrict) {
3250 if (this._weekdaysParseExact) {
3251 if (!hasOwnProp(this, '_weekdaysRegex')) {
3252 computeWeekdaysParse.call(this);
3253 }
3254 if (isStrict) {
3255 return this._weekdaysMinStrictRegex;
3256 } else {
3257 return this._weekdaysMinRegex;
3258 }
3259 } else {
3260 if (!hasOwnProp(this, '_weekdaysMinRegex')) {
3261 this._weekdaysMinRegex = defaultWeekdaysMinRegex;
3262 }
3263 return this._weekdaysMinStrictRegex && isStrict ?
3264 this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
3265 }
3266}
3267
3268
3269function computeWeekdaysParse () {
3270 function cmpLenRev(a, b) {
3271 return b.length - a.length;
3272 }
3273
3274 var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
3275 i, mom, minp, shortp, longp;
3276 for (i = 0; i < 7; i++) {
3277 // make the regex if we don't have it already
3278 mom = createUTC([2000, 1]).day(i);
3279 minp = this.weekdaysMin(mom, '');
3280 shortp = this.weekdaysShort(mom, '');
3281 longp = this.weekdays(mom, '');
3282 minPieces.push(minp);
3283 shortPieces.push(shortp);
3284 longPieces.push(longp);
3285 mixedPieces.push(minp);
3286 mixedPieces.push(shortp);
3287 mixedPieces.push(longp);
3288 }
3289 // Sorting makes sure if one weekday (or abbr) is a prefix of another it
3290 // will match the longer piece.
3291 minPieces.sort(cmpLenRev);
3292 shortPieces.sort(cmpLenRev);
3293 longPieces.sort(cmpLenRev);
3294 mixedPieces.sort(cmpLenRev);
3295 for (i = 0; i < 7; i++) {
3296 shortPieces[i] = regexEscape(shortPieces[i]);
3297 longPieces[i] = regexEscape(longPieces[i]);
3298 mixedPieces[i] = regexEscape(mixedPieces[i]);
3299 }
3300
3301 this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
3302 this._weekdaysShortRegex = this._weekdaysRegex;
3303 this._weekdaysMinRegex = this._weekdaysRegex;
3304
3305 this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
3306 this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
3307 this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
3308}
3309
3310// FORMATTING
3311
3312function hFormat() {
3313 return this.hours() % 12 || 12;
3314}
3315
3316function kFormat() {
3317 return this.hours() || 24;
3318}
3319
3320addFormatToken('H', ['HH', 2], 0, 'hour');
3321addFormatToken('h', ['hh', 2], 0, hFormat);
3322addFormatToken('k', ['kk', 2], 0, kFormat);
3323
3324addFormatToken('hmm', 0, 0, function () {
3325 return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
3326});
3327
3328addFormatToken('hmmss', 0, 0, function () {
3329 return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
3330 zeroFill(this.seconds(), 2);
3331});
3332
3333addFormatToken('Hmm', 0, 0, function () {
3334 return '' + this.hours() + zeroFill(this.minutes(), 2);
3335});
3336
3337addFormatToken('Hmmss', 0, 0, function () {
3338 return '' + this.hours() + zeroFill(this.minutes(), 2) +
3339 zeroFill(this.seconds(), 2);
3340});
3341
3342function meridiem (token, lowercase) {
3343 addFormatToken(token, 0, 0, function () {
3344 return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
3345 });
3346}
3347
3348meridiem('a', true);
3349meridiem('A', false);
3350
3351// ALIASES
3352
3353addUnitAlias('hour', 'h');
3354
3355// PRIORITY
3356addUnitPriority('hour', 13);
3357
3358// PARSING
3359
3360function matchMeridiem (isStrict, locale) {
3361 return locale._meridiemParse;
3362}
3363
3364addRegexToken('a', matchMeridiem);
3365addRegexToken('A', matchMeridiem);
3366addRegexToken('H', match1to2);
3367addRegexToken('h', match1to2);
3368addRegexToken('k', match1to2);
3369addRegexToken('HH', match1to2, match2);
3370addRegexToken('hh', match1to2, match2);
3371addRegexToken('kk', match1to2, match2);
3372
3373addRegexToken('hmm', match3to4);
3374addRegexToken('hmmss', match5to6);
3375addRegexToken('Hmm', match3to4);
3376addRegexToken('Hmmss', match5to6);
3377
3378addParseToken(['H', 'HH'], HOUR);
3379addParseToken(['k', 'kk'], function (input, array, config) {
3380 var kInput = toInt(input);
3381 array[HOUR] = kInput === 24 ? 0 : kInput;
3382});
3383addParseToken(['a', 'A'], function (input, array, config) {
3384 config._isPm = config._locale.isPM(input);
3385 config._meridiem = input;
3386});
3387addParseToken(['h', 'hh'], function (input, array, config) {
3388 array[HOUR] = toInt(input);
3389 getParsingFlags(config).bigHour = true;
3390});
3391addParseToken('hmm', function (input, array, config) {
3392 var pos = input.length - 2;
3393 array[HOUR] = toInt(input.substr(0, pos));
3394 array[MINUTE] = toInt(input.substr(pos));
3395 getParsingFlags(config).bigHour = true;
3396});
3397addParseToken('hmmss', function (input, array, config) {
3398 var pos1 = input.length - 4;
3399 var pos2 = input.length - 2;
3400 array[HOUR] = toInt(input.substr(0, pos1));
3401 array[MINUTE] = toInt(input.substr(pos1, 2));
3402 array[SECOND] = toInt(input.substr(pos2));
3403 getParsingFlags(config).bigHour = true;
3404});
3405addParseToken('Hmm', function (input, array, config) {
3406 var pos = input.length - 2;
3407 array[HOUR] = toInt(input.substr(0, pos));
3408 array[MINUTE] = toInt(input.substr(pos));
3409});
3410addParseToken('Hmmss', function (input, array, config) {
3411 var pos1 = input.length - 4;
3412 var pos2 = input.length - 2;
3413 array[HOUR] = toInt(input.substr(0, pos1));
3414 array[MINUTE] = toInt(input.substr(pos1, 2));
3415 array[SECOND] = toInt(input.substr(pos2));
3416});
3417
3418// LOCALES
3419
3420function localeIsPM (input) {
3421 // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
3422 // Using charAt should be more compatible.
3423 return ((input + '').toLowerCase().charAt(0) === 'p');
3424}
3425
3426var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
3427function localeMeridiem (hours, minutes, isLower) {
3428 if (hours > 11) {
3429 return isLower ? 'pm' : 'PM';
3430 } else {
3431 return isLower ? 'am' : 'AM';
3432 }
3433}
3434
3435
3436// MOMENTS
3437
3438// Setting the hour should keep the time, because the user explicitly
3439// specified which hour he wants. So trying to maintain the same hour (in
3440// a new timezone) makes sense. Adding/subtracting hours does not follow
3441// this rule.
3442var getSetHour = makeGetSet('Hours', true);
3443
3444// months
3445// week
3446// weekdays
3447// meridiem
3448var baseConfig = {
3449 calendar: defaultCalendar,
3450 longDateFormat: defaultLongDateFormat,
3451 invalidDate: defaultInvalidDate,
3452 ordinal: defaultOrdinal,
3453 dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
3454 relativeTime: defaultRelativeTime,
3455
3456 months: defaultLocaleMonths,
3457 monthsShort: defaultLocaleMonthsShort,
3458
3459 week: defaultLocaleWeek,
3460
3461 weekdays: defaultLocaleWeekdays,
3462 weekdaysMin: defaultLocaleWeekdaysMin,
3463 weekdaysShort: defaultLocaleWeekdaysShort,
3464
3465 meridiemParse: defaultLocaleMeridiemParse
3466};
3467
3468// internal storage for locale config files
3469var locales = {};
3470var localeFamilies = {};
3471var globalLocale;
3472
3473function normalizeLocale(key) {
3474 return key ? key.toLowerCase().replace('_', '-') : key;
3475}
3476
3477// pick the locale from the array
3478// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
3479// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
3480function chooseLocale(names) {
3481 var i = 0, j, next, locale, split;
3482
3483 while (i < names.length) {
3484 split = normalizeLocale(names[i]).split('-');
3485 j = split.length;
3486 next = normalizeLocale(names[i + 1]);
3487 next = next ? next.split('-') : null;
3488 while (j > 0) {
3489 locale = loadLocale(split.slice(0, j).join('-'));
3490 if (locale) {
3491 return locale;
3492 }
3493 if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
3494 //the next array item is better than a shallower substring of this one
3495 break;
3496 }
3497 j--;
3498 }
3499 i++;
3500 }
3501 return null;
3502}
3503
3504function loadLocale(name) {
3505 var oldLocale = null;
3506 // TODO: Find a better way to register and load all the locales in Node
3507 if (!locales[name] && (typeof module !== 'undefined') &&
3508 module && module.exports) {
3509 try {
3510 oldLocale = globalLocale._abbr;
3511 var aliasedRequire = require;
3512 aliasedRequire('./locale/' + name);
3513 getSetGlobalLocale(oldLocale);
3514 } catch (e) {}
3515 }
3516 return locales[name];
3517}
3518
3519// This function will load locale and then set the global locale. If
3520// no arguments are passed in, it will simply return the current global
3521// locale key.
3522function getSetGlobalLocale (key, values) {
3523 var data;
3524 if (key) {
3525 if (isUndefined(values)) {
3526 data = getLocale(key);
3527 }
3528 else {
3529 data = defineLocale(key, values);
3530 }
3531
3532 if (data) {
3533 // moment.duration._locale = moment._locale = data;
3534 globalLocale = data;
3535 }
3536 }
3537
3538 return globalLocale._abbr;
3539}
3540
3541function defineLocale (name, config) {
3542 if (config !== null) {
3543 var parentConfig = baseConfig;
3544 config.abbr = name;
3545 if (locales[name] != null) {
3546 deprecateSimple('defineLocaleOverride',
3547 'use moment.updateLocale(localeName, config) to change ' +
3548 'an existing locale. moment.defineLocale(localeName, ' +
3549 'config) should only be used for creating a new locale ' +
3550 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
3551 parentConfig = locales[name]._config;
3552 } else if (config.parentLocale != null) {
3553 if (locales[config.parentLocale] != null) {
3554 parentConfig = locales[config.parentLocale]._config;
3555 } else {
3556 if (!localeFamilies[config.parentLocale]) {
3557 localeFamilies[config.parentLocale] = [];
3558 }
3559 localeFamilies[config.parentLocale].push({
3560 name: name,
3561 config: config
3562 });
3563 return null;
3564 }
3565 }
3566 locales[name] = new Locale(mergeConfigs(parentConfig, config));
3567
3568 if (localeFamilies[name]) {
3569 localeFamilies[name].forEach(function (x) {
3570 defineLocale(x.name, x.config);
3571 });
3572 }
3573
3574 // backwards compat for now: also set the locale
3575 // make sure we set the locale AFTER all child locales have been
3576 // created, so we won't end up with the child locale set.
3577 getSetGlobalLocale(name);
3578
3579
3580 return locales[name];
3581 } else {
3582 // useful for testing
3583 delete locales[name];
3584 return null;
3585 }
3586}
3587
3588function updateLocale(name, config) {
3589 if (config != null) {
3590 var locale, tmpLocale, parentConfig = baseConfig;
3591 // MERGE
3592 tmpLocale = loadLocale(name);
3593 if (tmpLocale != null) {
3594 parentConfig = tmpLocale._config;
3595 }
3596 config = mergeConfigs(parentConfig, config);
3597 locale = new Locale(config);
3598 locale.parentLocale = locales[name];
3599 locales[name] = locale;
3600
3601 // backwards compat for now: also set the locale
3602 getSetGlobalLocale(name);
3603 } else {
3604 // pass null for config to unupdate, useful for tests
3605 if (locales[name] != null) {
3606 if (locales[name].parentLocale != null) {
3607 locales[name] = locales[name].parentLocale;
3608 } else if (locales[name] != null) {
3609 delete locales[name];
3610 }
3611 }
3612 }
3613 return locales[name];
3614}
3615
3616// returns locale data
3617function getLocale (key) {
3618 var locale;
3619
3620 if (key && key._locale && key._locale._abbr) {
3621 key = key._locale._abbr;
3622 }
3623
3624 if (!key) {
3625 return globalLocale;
3626 }
3627
3628 if (!isArray(key)) {
3629 //short-circuit everything else
3630 locale = loadLocale(key);
3631 if (locale) {
3632 return locale;
3633 }
3634 key = [key];
3635 }
3636
3637 return chooseLocale(key);
3638}
3639
3640function listLocales() {
3641 return keys(locales);
3642}
3643
3644function checkOverflow (m) {
3645 var overflow;
3646 var a = m._a;
3647
3648 if (a && getParsingFlags(m).overflow === -2) {
3649 overflow =
3650 a[MONTH] < 0 || a[MONTH] > 11 ? MONTH :
3651 a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
3652 a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
3653 a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE :
3654 a[SECOND] < 0 || a[SECOND] > 59 ? SECOND :
3655 a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
3656 -1;
3657
3658 if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
3659 overflow = DATE;
3660 }
3661 if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
3662 overflow = WEEK;
3663 }
3664 if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
3665 overflow = WEEKDAY;
3666 }
3667
3668 getParsingFlags(m).overflow = overflow;
3669 }
3670
3671 return m;
3672}
3673
3674// Pick the first defined of two or three arguments.
3675function defaults(a, b, c) {
3676 if (a != null) {
3677 return a;
3678 }
3679 if (b != null) {
3680 return b;
3681 }
3682 return c;
3683}
3684
3685function currentDateArray(config) {
3686 // hooks is actually the exported moment object
3687 var nowValue = new Date(hooks.now());
3688 if (config._useUTC) {
3689 return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
3690 }
3691 return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
3692}
3693
3694// convert an array to a date.
3695// the array should mirror the parameters below
3696// note: all values past the year are optional and will default to the lowest possible value.
3697// [year, month, day , hour, minute, second, millisecond]
3698function configFromArray (config) {
3699 var i, date, input = [], currentDate, expectedWeekday, yearToUse;
3700
3701 if (config._d) {
3702 return;
3703 }
3704
3705 currentDate = currentDateArray(config);
3706
3707 //compute day of the year from weeks and weekdays
3708 if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
3709 dayOfYearFromWeekInfo(config);
3710 }
3711
3712 //if the day of the year is set, figure out what it is
3713 if (config._dayOfYear != null) {
3714 yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
3715
3716 if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) {
3717 getParsingFlags(config)._overflowDayOfYear = true;
3718 }
3719
3720 date = createUTCDate(yearToUse, 0, config._dayOfYear);
3721 config._a[MONTH] = date.getUTCMonth();
3722 config._a[DATE] = date.getUTCDate();
3723 }
3724
3725 // Default to current date.
3726 // * if no year, month, day of month are given, default to today
3727 // * if day of month is given, default month and year
3728 // * if month is given, default only year
3729 // * if year is given, don't default anything
3730 for (i = 0; i < 3 && config._a[i] == null; ++i) {
3731 config._a[i] = input[i] = currentDate[i];
3732 }
3733
3734 // Zero out whatever was not defaulted, including time
3735 for (; i < 7; i++) {
3736 config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
3737 }
3738
3739 // Check for 24:00:00.000
3740 if (config._a[HOUR] === 24 &&
3741 config._a[MINUTE] === 0 &&
3742 config._a[SECOND] === 0 &&
3743 config._a[MILLISECOND] === 0) {
3744 config._nextDay = true;
3745 config._a[HOUR] = 0;
3746 }
3747
3748 config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
3749 expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay();
3750
3751 // Apply timezone offset from input. The actual utcOffset can be changed
3752 // with parseZone.
3753 if (config._tzm != null) {
3754 config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
3755 }
3756
3757 if (config._nextDay) {
3758 config._a[HOUR] = 24;
3759 }
3760
3761 // check for mismatching day of week
3762 if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) {
3763 getParsingFlags(config).weekdayMismatch = true;
3764 }
3765}
3766
3767function dayOfYearFromWeekInfo(config) {
3768 var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
3769
3770 w = config._w;
3771 if (w.GG != null || w.W != null || w.E != null) {
3772 dow = 1;
3773 doy = 4;
3774
3775 // TODO: We need to take the current isoWeekYear, but that depends on
3776 // how we interpret now (local, utc, fixed offset). So create
3777 // a now version of current config (take local/utc/offset flags, and
3778 // create now).
3779 weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
3780 week = defaults(w.W, 1);
3781 weekday = defaults(w.E, 1);
3782 if (weekday < 1 || weekday > 7) {
3783 weekdayOverflow = true;
3784 }
3785 } else {
3786 dow = config._locale._week.dow;
3787 doy = config._locale._week.doy;
3788
3789 var curWeek = weekOfYear(createLocal(), dow, doy);
3790
3791 weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
3792
3793 // Default to current week.
3794 week = defaults(w.w, curWeek.week);
3795
3796 if (w.d != null) {
3797 // weekday -- low day numbers are considered next week
3798 weekday = w.d;
3799 if (weekday < 0 || weekday > 6) {
3800 weekdayOverflow = true;
3801 }
3802 } else if (w.e != null) {
3803 // local weekday -- counting starts from begining of week
3804 weekday = w.e + dow;
3805 if (w.e < 0 || w.e > 6) {
3806 weekdayOverflow = true;
3807 }
3808 } else {
3809 // default to begining of week
3810 weekday = dow;
3811 }
3812 }
3813 if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
3814 getParsingFlags(config)._overflowWeeks = true;
3815 } else if (weekdayOverflow != null) {
3816 getParsingFlags(config)._overflowWeekday = true;
3817 } else {
3818 temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
3819 config._a[YEAR] = temp.year;
3820 config._dayOfYear = temp.dayOfYear;
3821 }
3822}
3823
3824// iso 8601 regex
3825// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
3826var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
3827var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
3828
3829var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
3830
3831var isoDates = [
3832 ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
3833 ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
3834 ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
3835 ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
3836 ['YYYY-DDD', /\d{4}-\d{3}/],
3837 ['YYYY-MM', /\d{4}-\d\d/, false],
3838 ['YYYYYYMMDD', /[+-]\d{10}/],
3839 ['YYYYMMDD', /\d{8}/],
3840 // YYYYMM is NOT allowed by the standard
3841 ['GGGG[W]WWE', /\d{4}W\d{3}/],
3842 ['GGGG[W]WW', /\d{4}W\d{2}/, false],
3843 ['YYYYDDD', /\d{7}/]
3844];
3845
3846// iso time formats and regexes
3847var isoTimes = [
3848 ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
3849 ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
3850 ['HH:mm:ss', /\d\d:\d\d:\d\d/],
3851 ['HH:mm', /\d\d:\d\d/],
3852 ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
3853 ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
3854 ['HHmmss', /\d\d\d\d\d\d/],
3855 ['HHmm', /\d\d\d\d/],
3856 ['HH', /\d\d/]
3857];
3858
3859var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
3860
3861// date from iso format
3862function configFromISO(config) {
3863 var i, l,
3864 string = config._i,
3865 match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
3866 allowTime, dateFormat, timeFormat, tzFormat;
3867
3868 if (match) {
3869 getParsingFlags(config).iso = true;
3870
3871 for (i = 0, l = isoDates.length; i < l; i++) {
3872 if (isoDates[i][1].exec(match[1])) {
3873 dateFormat = isoDates[i][0];
3874 allowTime = isoDates[i][2] !== false;
3875 break;
3876 }
3877 }
3878 if (dateFormat == null) {
3879 config._isValid = false;
3880 return;
3881 }
3882 if (match[3]) {
3883 for (i = 0, l = isoTimes.length; i < l; i++) {
3884 if (isoTimes[i][1].exec(match[3])) {
3885 // match[2] should be 'T' or space
3886 timeFormat = (match[2] || ' ') + isoTimes[i][0];
3887 break;
3888 }
3889 }
3890 if (timeFormat == null) {
3891 config._isValid = false;
3892 return;
3893 }
3894 }
3895 if (!allowTime && timeFormat != null) {
3896 config._isValid = false;
3897 return;
3898 }
3899 if (match[4]) {
3900 if (tzRegex.exec(match[4])) {
3901 tzFormat = 'Z';
3902 } else {
3903 config._isValid = false;
3904 return;
3905 }
3906 }
3907 config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
3908 configFromStringAndFormat(config);
3909 } else {
3910 config._isValid = false;
3911 }
3912}
3913
3914// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
3915var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
3916
3917function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
3918 var result = [
3919 untruncateYear(yearStr),
3920 defaultLocaleMonthsShort.indexOf(monthStr),
3921 parseInt(dayStr, 10),
3922 parseInt(hourStr, 10),
3923 parseInt(minuteStr, 10)
3924 ];
3925
3926 if (secondStr) {
3927 result.push(parseInt(secondStr, 10));
3928 }
3929
3930 return result;
3931}
3932
3933function untruncateYear(yearStr) {
3934 var year = parseInt(yearStr, 10);
3935 if (year <= 49) {
3936 return 2000 + year;
3937 } else if (year <= 999) {
3938 return 1900 + year;
3939 }
3940 return year;
3941}
3942
3943function preprocessRFC2822(s) {
3944 // Remove comments and folding whitespace and replace multiple-spaces with a single space
3945 return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').trim();
3946}
3947
3948function checkWeekday(weekdayStr, parsedInput, config) {
3949 if (weekdayStr) {
3950 // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
3951 var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
3952 weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
3953 if (weekdayProvided !== weekdayActual) {
3954 getParsingFlags(config).weekdayMismatch = true;
3955 config._isValid = false;
3956 return false;
3957 }
3958 }
3959 return true;
3960}
3961
3962var obsOffsets = {
3963 UT: 0,
3964 GMT: 0,
3965 EDT: -4 * 60,
3966 EST: -5 * 60,
3967 CDT: -5 * 60,
3968 CST: -6 * 60,
3969 MDT: -6 * 60,
3970 MST: -7 * 60,
3971 PDT: -7 * 60,
3972 PST: -8 * 60
3973};
3974
3975function calculateOffset(obsOffset, militaryOffset, numOffset) {
3976 if (obsOffset) {
3977 return obsOffsets[obsOffset];
3978 } else if (militaryOffset) {
3979 // the only allowed military tz is Z
3980 return 0;
3981 } else {
3982 var hm = parseInt(numOffset, 10);
3983 var m = hm % 100, h = (hm - m) / 100;
3984 return h * 60 + m;
3985 }
3986}
3987
3988// date and time from ref 2822 format
3989function configFromRFC2822(config) {
3990 var match = rfc2822.exec(preprocessRFC2822(config._i));
3991 if (match) {
3992 var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
3993 if (!checkWeekday(match[1], parsedArray, config)) {
3994 return;
3995 }
3996
3997 config._a = parsedArray;
3998 config._tzm = calculateOffset(match[8], match[9], match[10]);
3999
4000 config._d = createUTCDate.apply(null, config._a);
4001 config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
4002
4003 getParsingFlags(config).rfc2822 = true;
4004 } else {
4005 config._isValid = false;
4006 }
4007}
4008
4009// date from iso format or fallback
4010function configFromString(config) {
4011 var matched = aspNetJsonRegex.exec(config._i);
4012
4013 if (matched !== null) {
4014 config._d = new Date(+matched[1]);
4015 return;
4016 }
4017
4018 configFromISO(config);
4019 if (config._isValid === false) {
4020 delete config._isValid;
4021 } else {
4022 return;
4023 }
4024
4025 configFromRFC2822(config);
4026 if (config._isValid === false) {
4027 delete config._isValid;
4028 } else {
4029 return;
4030 }
4031
4032 // Final attempt, use Input Fallback
4033 hooks.createFromInputFallback(config);
4034}
4035
4036hooks.createFromInputFallback = deprecate(
4037 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
4038 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
4039 'discouraged and will be removed in an upcoming major release. Please refer to ' +
4040 'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
4041 function (config) {
4042 config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
4043 }
4044);
4045
4046// constant that refers to the ISO standard
4047hooks.ISO_8601 = function () {};
4048
4049// constant that refers to the RFC 2822 form
4050hooks.RFC_2822 = function () {};
4051
4052// date from string and format string
4053function configFromStringAndFormat(config) {
4054 // TODO: Move this to another part of the creation flow to prevent circular deps
4055 if (config._f === hooks.ISO_8601) {
4056 configFromISO(config);
4057 return;
4058 }
4059 if (config._f === hooks.RFC_2822) {
4060 configFromRFC2822(config);
4061 return;
4062 }
4063 config._a = [];
4064 getParsingFlags(config).empty = true;
4065
4066 // This array is used to make a Date, either with `new Date` or `Date.UTC`
4067 var string = '' + config._i,
4068 i, parsedInput, tokens, token, skipped,
4069 stringLength = string.length,
4070 totalParsedInputLength = 0;
4071
4072 tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
4073
4074 for (i = 0; i < tokens.length; i++) {
4075 token = tokens[i];
4076 parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
4077 // console.log('token', token, 'parsedInput', parsedInput,
4078 // 'regex', getParseRegexForToken(token, config));
4079 if (parsedInput) {
4080 skipped = string.substr(0, string.indexOf(parsedInput));
4081 if (skipped.length > 0) {
4082 getParsingFlags(config).unusedInput.push(skipped);
4083 }
4084 string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
4085 totalParsedInputLength += parsedInput.length;
4086 }
4087 // don't parse if it's not a known token
4088 if (formatTokenFunctions[token]) {
4089 if (parsedInput) {
4090 getParsingFlags(config).empty = false;
4091 }
4092 else {
4093 getParsingFlags(config).unusedTokens.push(token);
4094 }
4095 addTimeToArrayFromToken(token, parsedInput, config);
4096 }
4097 else if (config._strict && !parsedInput) {
4098 getParsingFlags(config).unusedTokens.push(token);
4099 }
4100 }
4101
4102 // add remaining unparsed input length to the string
4103 getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
4104 if (string.length > 0) {
4105 getParsingFlags(config).unusedInput.push(string);
4106 }
4107
4108 // clear _12h flag if hour is <= 12
4109 if (config._a[HOUR] <= 12 &&
4110 getParsingFlags(config).bigHour === true &&
4111 config._a[HOUR] > 0) {
4112 getParsingFlags(config).bigHour = undefined;
4113 }
4114
4115 getParsingFlags(config).parsedDateParts = config._a.slice(0);
4116 getParsingFlags(config).meridiem = config._meridiem;
4117 // handle meridiem
4118 config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
4119
4120 configFromArray(config);
4121 checkOverflow(config);
4122}
4123
4124
4125function meridiemFixWrap (locale, hour, meridiem) {
4126 var isPm;
4127
4128 if (meridiem == null) {
4129 // nothing to do
4130 return hour;
4131 }
4132 if (locale.meridiemHour != null) {
4133 return locale.meridiemHour(hour, meridiem);
4134 } else if (locale.isPM != null) {
4135 // Fallback
4136 isPm = locale.isPM(meridiem);
4137 if (isPm && hour < 12) {
4138 hour += 12;
4139 }
4140 if (!isPm && hour === 12) {
4141 hour = 0;
4142 }
4143 return hour;
4144 } else {
4145 // this is not supposed to happen
4146 return hour;
4147 }
4148}
4149
4150// date from string and array of format strings
4151function configFromStringAndArray(config) {
4152 var tempConfig,
4153 bestMoment,
4154
4155 scoreToBeat,
4156 i,
4157 currentScore;
4158
4159 if (config._f.length === 0) {
4160 getParsingFlags(config).invalidFormat = true;
4161 config._d = new Date(NaN);
4162 return;
4163 }
4164
4165 for (i = 0; i < config._f.length; i++) {
4166 currentScore = 0;
4167 tempConfig = copyConfig({}, config);
4168 if (config._useUTC != null) {
4169 tempConfig._useUTC = config._useUTC;
4170 }
4171 tempConfig._f = config._f[i];
4172 configFromStringAndFormat(tempConfig);
4173
4174 if (!isValid(tempConfig)) {
4175 continue;
4176 }
4177
4178 // if there is any input that was not parsed add a penalty for that format
4179 currentScore += getParsingFlags(tempConfig).charsLeftOver;
4180
4181 //or tokens
4182 currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
4183
4184 getParsingFlags(tempConfig).score = currentScore;
4185
4186 if (scoreToBeat == null || currentScore < scoreToBeat) {
4187 scoreToBeat = currentScore;
4188 bestMoment = tempConfig;
4189 }
4190 }
4191
4192 extend(config, bestMoment || tempConfig);
4193}
4194
4195function configFromObject(config) {
4196 if (config._d) {
4197 return;
4198 }
4199
4200 var i = normalizeObjectUnits(config._i);
4201 config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
4202 return obj && parseInt(obj, 10);
4203 });
4204
4205 configFromArray(config);
4206}
4207
4208function createFromConfig (config) {
4209 var res = new Moment(checkOverflow(prepareConfig(config)));
4210 if (res._nextDay) {
4211 // Adding is smart enough around DST
4212 res.add(1, 'd');
4213 res._nextDay = undefined;
4214 }
4215
4216 return res;
4217}
4218
4219function prepareConfig (config) {
4220 var input = config._i,
4221 format = config._f;
4222
4223 config._locale = config._locale || getLocale(config._l);
4224
4225 if (input === null || (format === undefined && input === '')) {
4226 return createInvalid({nullInput: true});
4227 }
4228
4229 if (typeof input === 'string') {
4230 config._i = input = config._locale.preparse(input);
4231 }
4232
4233 if (isMoment(input)) {
4234 return new Moment(checkOverflow(input));
4235 } else if (isDate(input)) {
4236 config._d = input;
4237 } else if (isArray(format)) {
4238 configFromStringAndArray(config);
4239 } else if (format) {
4240 configFromStringAndFormat(config);
4241 } else {
4242 configFromInput(config);
4243 }
4244
4245 if (!isValid(config)) {
4246 config._d = null;
4247 }
4248
4249 return config;
4250}
4251
4252function configFromInput(config) {
4253 var input = config._i;
4254 if (isUndefined(input)) {
4255 config._d = new Date(hooks.now());
4256 } else if (isDate(input)) {
4257 config._d = new Date(input.valueOf());
4258 } else if (typeof input === 'string') {
4259 configFromString(config);
4260 } else if (isArray(input)) {
4261 config._a = map(input.slice(0), function (obj) {
4262 return parseInt(obj, 10);
4263 });
4264 configFromArray(config);
4265 } else if (isObject(input)) {
4266 configFromObject(config);
4267 } else if (isNumber(input)) {
4268 // from milliseconds
4269 config._d = new Date(input);
4270 } else {
4271 hooks.createFromInputFallback(config);
4272 }
4273}
4274
4275function createLocalOrUTC (input, format, locale, strict, isUTC) {
4276 var c = {};
4277
4278 if (locale === true || locale === false) {
4279 strict = locale;
4280 locale = undefined;
4281 }
4282
4283 if ((isObject(input) && isObjectEmpty(input)) ||
4284 (isArray(input) && input.length === 0)) {
4285 input = undefined;
4286 }
4287 // object construction must be done this way.
4288 // https://github.com/moment/moment/issues/1423
4289 c._isAMomentObject = true;
4290 c._useUTC = c._isUTC = isUTC;
4291 c._l = locale;
4292 c._i = input;
4293 c._f = format;
4294 c._strict = strict;
4295
4296 return createFromConfig(c);
4297}
4298
4299function createLocal (input, format, locale, strict) {
4300 return createLocalOrUTC(input, format, locale, strict, false);
4301}
4302
4303var prototypeMin = deprecate(
4304 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
4305 function () {
4306 var other = createLocal.apply(null, arguments);
4307 if (this.isValid() && other.isValid()) {
4308 return other < this ? this : other;
4309 } else {
4310 return createInvalid();
4311 }
4312 }
4313);
4314
4315var prototypeMax = deprecate(
4316 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
4317 function () {
4318 var other = createLocal.apply(null, arguments);
4319 if (this.isValid() && other.isValid()) {
4320 return other > this ? this : other;
4321 } else {
4322 return createInvalid();
4323 }
4324 }
4325);
4326
4327// Pick a moment m from moments so that m[fn](other) is true for all
4328// other. This relies on the function fn to be transitive.
4329//
4330// moments should either be an array of moment objects or an array, whose
4331// first element is an array of moment objects.
4332function pickBy(fn, moments) {
4333 var res, i;
4334 if (moments.length === 1 && isArray(moments[0])) {
4335 moments = moments[0];
4336 }
4337 if (!moments.length) {
4338 return createLocal();
4339 }
4340 res = moments[0];
4341 for (i = 1; i < moments.length; ++i) {
4342 if (!moments[i].isValid() || moments[i][fn](res)) {
4343 res = moments[i];
4344 }
4345 }
4346 return res;
4347}
4348
4349// TODO: Use [].sort instead?
4350function min () {
4351 var args = [].slice.call(arguments, 0);
4352
4353 return pickBy('isBefore', args);
4354}
4355
4356function max () {
4357 var args = [].slice.call(arguments, 0);
4358
4359 return pickBy('isAfter', args);
4360}
4361
4362var now = function () {
4363 return Date.now ? Date.now() : +(new Date());
4364};
4365
4366var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
4367
4368function isDurationValid(m) {
4369 for (var key in m) {
4370 if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) {
4371 return false;
4372 }
4373 }
4374
4375 var unitHasDecimal = false;
4376 for (var i = 0; i < ordering.length; ++i) {
4377 if (m[ordering[i]]) {
4378 if (unitHasDecimal) {
4379 return false; // only allow non-integers for smallest unit
4380 }
4381 if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
4382 unitHasDecimal = true;
4383 }
4384 }
4385 }
4386
4387 return true;
4388}
4389
4390function isValid$1() {
4391 return this._isValid;
4392}
4393
4394function createInvalid$1() {
4395 return createDuration(NaN);
4396}
4397
4398function Duration (duration) {
4399 var normalizedInput = normalizeObjectUnits(duration),
4400 years = normalizedInput.year || 0,
4401 quarters = normalizedInput.quarter || 0,
4402 months = normalizedInput.month || 0,
4403 weeks = normalizedInput.week || 0,
4404 days = normalizedInput.day || 0,
4405 hours = normalizedInput.hour || 0,
4406 minutes = normalizedInput.minute || 0,
4407 seconds = normalizedInput.second || 0,
4408 milliseconds = normalizedInput.millisecond || 0;
4409
4410 this._isValid = isDurationValid(normalizedInput);
4411
4412 // representation for dateAddRemove
4413 this._milliseconds = +milliseconds +
4414 seconds * 1e3 + // 1000
4415 minutes * 6e4 + // 1000 * 60
4416 hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
4417 // Because of dateAddRemove treats 24 hours as different from a
4418 // day when working around DST, we need to store them separately
4419 this._days = +days +
4420 weeks * 7;
4421 // It is impossible to translate months into days without knowing
4422 // which months you are are talking about, so we have to store
4423 // it separately.
4424 this._months = +months +
4425 quarters * 3 +
4426 years * 12;
4427
4428 this._data = {};
4429
4430 this._locale = getLocale();
4431
4432 this._bubble();
4433}
4434
4435function isDuration (obj) {
4436 return obj instanceof Duration;
4437}
4438
4439function absRound (number) {
4440 if (number < 0) {
4441 return Math.round(-1 * number) * -1;
4442 } else {
4443 return Math.round(number);
4444 }
4445}
4446
4447// FORMATTING
4448
4449function offset (token, separator) {
4450 addFormatToken(token, 0, 0, function () {
4451 var offset = this.utcOffset();
4452 var sign = '+';
4453 if (offset < 0) {
4454 offset = -offset;
4455 sign = '-';
4456 }
4457 return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
4458 });
4459}
4460
4461offset('Z', ':');
4462offset('ZZ', '');
4463
4464// PARSING
4465
4466addRegexToken('Z', matchShortOffset);
4467addRegexToken('ZZ', matchShortOffset);
4468addParseToken(['Z', 'ZZ'], function (input, array, config) {
4469 config._useUTC = true;
4470 config._tzm = offsetFromString(matchShortOffset, input);
4471});
4472
4473// HELPERS
4474
4475// timezone chunker
4476// '+10:00' > ['10', '00']
4477// '-1530' > ['-15', '30']
4478var chunkOffset = /([\+\-]|\d\d)/gi;
4479
4480function offsetFromString(matcher, string) {
4481 var matches = (string || '').match(matcher);
4482
4483 if (matches === null) {
4484 return null;
4485 }
4486
4487 var chunk = matches[matches.length - 1] || [];
4488 var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
4489 var minutes = +(parts[1] * 60) + toInt(parts[2]);
4490
4491 return minutes === 0 ?
4492 0 :
4493 parts[0] === '+' ? minutes : -minutes;
4494}
4495
4496// Return a moment from input, that is local/utc/zone equivalent to model.
4497function cloneWithOffset(input, model) {
4498 var res, diff;
4499 if (model._isUTC) {
4500 res = model.clone();
4501 diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
4502 // Use low-level api, because this fn is low-level api.
4503 res._d.setTime(res._d.valueOf() + diff);
4504 hooks.updateOffset(res, false);
4505 return res;
4506 } else {
4507 return createLocal(input).local();
4508 }
4509}
4510
4511function getDateOffset (m) {
4512 // On Firefox.24 Date#getTimezoneOffset returns a floating point.
4513 // https://github.com/moment/moment/pull/1871
4514 return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
4515}
4516
4517// HOOKS
4518
4519// This function will be called whenever a moment is mutated.
4520// It is intended to keep the offset in sync with the timezone.
4521hooks.updateOffset = function () {};
4522
4523// MOMENTS
4524
4525// keepLocalTime = true means only change the timezone, without
4526// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
4527// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
4528// +0200, so we adjust the time as needed, to be valid.
4529//
4530// Keeping the time actually adds/subtracts (one hour)
4531// from the actual represented time. That is why we call updateOffset
4532// a second time. In case it wants us to change the offset again
4533// _changeInProgress == true case, then we have to adjust, because
4534// there is no such time in the given timezone.
4535function getSetOffset (input, keepLocalTime, keepMinutes) {
4536 var offset = this._offset || 0,
4537 localAdjust;
4538 if (!this.isValid()) {
4539 return input != null ? this : NaN;
4540 }
4541 if (input != null) {
4542 if (typeof input === 'string') {
4543 input = offsetFromString(matchShortOffset, input);
4544 if (input === null) {
4545 return this;
4546 }
4547 } else if (Math.abs(input) < 16 && !keepMinutes) {
4548 input = input * 60;
4549 }
4550 if (!this._isUTC && keepLocalTime) {
4551 localAdjust = getDateOffset(this);
4552 }
4553 this._offset = input;
4554 this._isUTC = true;
4555 if (localAdjust != null) {
4556 this.add(localAdjust, 'm');
4557 }
4558 if (offset !== input) {
4559 if (!keepLocalTime || this._changeInProgress) {
4560 addSubtract(this, createDuration(input - offset, 'm'), 1, false);
4561 } else if (!this._changeInProgress) {
4562 this._changeInProgress = true;
4563 hooks.updateOffset(this, true);
4564 this._changeInProgress = null;
4565 }
4566 }
4567 return this;
4568 } else {
4569 return this._isUTC ? offset : getDateOffset(this);
4570 }
4571}
4572
4573function getSetZone (input, keepLocalTime) {
4574 if (input != null) {
4575 if (typeof input !== 'string') {
4576 input = -input;
4577 }
4578
4579 this.utcOffset(input, keepLocalTime);
4580
4581 return this;
4582 } else {
4583 return -this.utcOffset();
4584 }
4585}
4586
4587function setOffsetToUTC (keepLocalTime) {
4588 return this.utcOffset(0, keepLocalTime);
4589}
4590
4591function setOffsetToLocal (keepLocalTime) {
4592 if (this._isUTC) {
4593 this.utcOffset(0, keepLocalTime);
4594 this._isUTC = false;
4595
4596 if (keepLocalTime) {
4597 this.subtract(getDateOffset(this), 'm');
4598 }
4599 }
4600 return this;
4601}
4602
4603function setOffsetToParsedOffset () {
4604 if (this._tzm != null) {
4605 this.utcOffset(this._tzm, false, true);
4606 } else if (typeof this._i === 'string') {
4607 var tZone = offsetFromString(matchOffset, this._i);
4608 if (tZone != null) {
4609 this.utcOffset(tZone);
4610 }
4611 else {
4612 this.utcOffset(0, true);
4613 }
4614 }
4615 return this;
4616}
4617
4618function hasAlignedHourOffset (input) {
4619 if (!this.isValid()) {
4620 return false;
4621 }
4622 input = input ? createLocal(input).utcOffset() : 0;
4623
4624 return (this.utcOffset() - input) % 60 === 0;
4625}
4626
4627function isDaylightSavingTime () {
4628 return (
4629 this.utcOffset() > this.clone().month(0).utcOffset() ||
4630 this.utcOffset() > this.clone().month(5).utcOffset()
4631 );
4632}
4633
4634function isDaylightSavingTimeShifted () {
4635 if (!isUndefined(this._isDSTShifted)) {
4636 return this._isDSTShifted;
4637 }
4638
4639 var c = {};
4640
4641 copyConfig(c, this);
4642 c = prepareConfig(c);
4643
4644 if (c._a) {
4645 var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
4646 this._isDSTShifted = this.isValid() &&
4647 compareArrays(c._a, other.toArray()) > 0;
4648 } else {
4649 this._isDSTShifted = false;
4650 }
4651
4652 return this._isDSTShifted;
4653}
4654
4655function isLocal () {
4656 return this.isValid() ? !this._isUTC : false;
4657}
4658
4659function isUtcOffset () {
4660 return this.isValid() ? this._isUTC : false;
4661}
4662
4663function isUtc () {
4664 return this.isValid() ? this._isUTC && this._offset === 0 : false;
4665}
4666
4667// ASP.NET json date format regex
4668var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
4669
4670// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
4671// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
4672// and further modified to allow for strings containing both week and day
4673var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
4674
4675function createDuration (input, key) {
4676 var duration = input,
4677 // matching against regexp is expensive, do it on demand
4678 match = null,
4679 sign,
4680 ret,
4681 diffRes;
4682
4683 if (isDuration(input)) {
4684 duration = {
4685 ms : input._milliseconds,
4686 d : input._days,
4687 M : input._months
4688 };
4689 } else if (isNumber(input)) {
4690 duration = {};
4691 if (key) {
4692 duration[key] = input;
4693 } else {
4694 duration.milliseconds = input;
4695 }
4696 } else if (!!(match = aspNetRegex.exec(input))) {
4697 sign = (match[1] === '-') ? -1 : 1;
4698 duration = {
4699 y : 0,
4700 d : toInt(match[DATE]) * sign,
4701 h : toInt(match[HOUR]) * sign,
4702 m : toInt(match[MINUTE]) * sign,
4703 s : toInt(match[SECOND]) * sign,
4704 ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
4705 };
4706 } else if (!!(match = isoRegex.exec(input))) {
4707 sign = (match[1] === '-') ? -1 : (match[1] === '+') ? 1 : 1;
4708 duration = {
4709 y : parseIso(match[2], sign),
4710 M : parseIso(match[3], sign),
4711 w : parseIso(match[4], sign),
4712 d : parseIso(match[5], sign),
4713 h : parseIso(match[6], sign),
4714 m : parseIso(match[7], sign),
4715 s : parseIso(match[8], sign)
4716 };
4717 } else if (duration == null) {// checks for null or undefined
4718 duration = {};
4719 } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
4720 diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
4721
4722 duration = {};
4723 duration.ms = diffRes.milliseconds;
4724 duration.M = diffRes.months;
4725 }
4726
4727 ret = new Duration(duration);
4728
4729 if (isDuration(input) && hasOwnProp(input, '_locale')) {
4730 ret._locale = input._locale;
4731 }
4732
4733 return ret;
4734}
4735
4736createDuration.fn = Duration.prototype;
4737createDuration.invalid = createInvalid$1;
4738
4739function parseIso (inp, sign) {
4740 // We'd normally use ~~inp for this, but unfortunately it also
4741 // converts floats to ints.
4742 // inp may be undefined, so careful calling replace on it.
4743 var res = inp && parseFloat(inp.replace(',', '.'));
4744 // apply sign while we're at it
4745 return (isNaN(res) ? 0 : res) * sign;
4746}
4747
4748function positiveMomentsDifference(base, other) {
4749 var res = {milliseconds: 0, months: 0};
4750
4751 res.months = other.month() - base.month() +
4752 (other.year() - base.year()) * 12;
4753 if (base.clone().add(res.months, 'M').isAfter(other)) {
4754 --res.months;
4755 }
4756
4757 res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
4758
4759 return res;
4760}
4761
4762function momentsDifference(base, other) {
4763 var res;
4764 if (!(base.isValid() && other.isValid())) {
4765 return {milliseconds: 0, months: 0};
4766 }
4767
4768 other = cloneWithOffset(other, base);
4769 if (base.isBefore(other)) {
4770 res = positiveMomentsDifference(base, other);
4771 } else {
4772 res = positiveMomentsDifference(other, base);
4773 res.milliseconds = -res.milliseconds;
4774 res.months = -res.months;
4775 }
4776
4777 return res;
4778}
4779
4780// TODO: remove 'name' arg after deprecation is removed
4781function createAdder(direction, name) {
4782 return function (val, period) {
4783 var dur, tmp;
4784 //invert the arguments, but complain about it
4785 if (period !== null && !isNaN(+period)) {
4786 deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' +
4787 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
4788 tmp = val; val = period; period = tmp;
4789 }
4790
4791 val = typeof val === 'string' ? +val : val;
4792 dur = createDuration(val, period);
4793 addSubtract(this, dur, direction);
4794 return this;
4795 };
4796}
4797
4798function addSubtract (mom, duration, isAdding, updateOffset) {
4799 var milliseconds = duration._milliseconds,
4800 days = absRound(duration._days),
4801 months = absRound(duration._months);
4802
4803 if (!mom.isValid()) {
4804 // No op
4805 return;
4806 }
4807
4808 updateOffset = updateOffset == null ? true : updateOffset;
4809
4810 if (months) {
4811 setMonth(mom, get(mom, 'Month') + months * isAdding);
4812 }
4813 if (days) {
4814 set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
4815 }
4816 if (milliseconds) {
4817 mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
4818 }
4819 if (updateOffset) {
4820 hooks.updateOffset(mom, days || months);
4821 }
4822}
4823
4824var add = createAdder(1, 'add');
4825var subtract = createAdder(-1, 'subtract');
4826
4827function getCalendarFormat(myMoment, now) {
4828 var diff = myMoment.diff(now, 'days', true);
4829 return diff < -6 ? 'sameElse' :
4830 diff < -1 ? 'lastWeek' :
4831 diff < 0 ? 'lastDay' :
4832 diff < 1 ? 'sameDay' :
4833 diff < 2 ? 'nextDay' :
4834 diff < 7 ? 'nextWeek' : 'sameElse';
4835}
4836
4837function calendar$1 (time, formats) {
4838 // We want to compare the start of today, vs this.
4839 // Getting start-of-today depends on whether we're local/utc/offset or not.
4840 var now = time || createLocal(),
4841 sod = cloneWithOffset(now, this).startOf('day'),
4842 format = hooks.calendarFormat(this, sod) || 'sameElse';
4843
4844 var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
4845
4846 return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
4847}
4848
4849function clone () {
4850 return new Moment(this);
4851}
4852
4853function isAfter (input, units) {
4854 var localInput = isMoment(input) ? input : createLocal(input);
4855 if (!(this.isValid() && localInput.isValid())) {
4856 return false;
4857 }
4858 units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
4859 if (units === 'millisecond') {
4860 return this.valueOf() > localInput.valueOf();
4861 } else {
4862 return localInput.valueOf() < this.clone().startOf(units).valueOf();
4863 }
4864}
4865
4866function isBefore (input, units) {
4867 var localInput = isMoment(input) ? input : createLocal(input);
4868 if (!(this.isValid() && localInput.isValid())) {
4869 return false;
4870 }
4871 units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
4872 if (units === 'millisecond') {
4873 return this.valueOf() < localInput.valueOf();
4874 } else {
4875 return this.clone().endOf(units).valueOf() < localInput.valueOf();
4876 }
4877}
4878
4879function isBetween (from, to, units, inclusivity) {
4880 inclusivity = inclusivity || '()';
4881 return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) &&
4882 (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units));
4883}
4884
4885function isSame (input, units) {
4886 var localInput = isMoment(input) ? input : createLocal(input),
4887 inputMs;
4888 if (!(this.isValid() && localInput.isValid())) {
4889 return false;
4890 }
4891 units = normalizeUnits(units || 'millisecond');
4892 if (units === 'millisecond') {
4893 return this.valueOf() === localInput.valueOf();
4894 } else {
4895 inputMs = localInput.valueOf();
4896 return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
4897 }
4898}
4899
4900function isSameOrAfter (input, units) {
4901 return this.isSame(input, units) || this.isAfter(input,units);
4902}
4903
4904function isSameOrBefore (input, units) {
4905 return this.isSame(input, units) || this.isBefore(input,units);
4906}
4907
4908function diff (input, units, asFloat) {
4909 var that,
4910 zoneDelta,
4911 delta, output;
4912
4913 if (!this.isValid()) {
4914 return NaN;
4915 }
4916
4917 that = cloneWithOffset(input, this);
4918
4919 if (!that.isValid()) {
4920 return NaN;
4921 }
4922
4923 zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
4924
4925 units = normalizeUnits(units);
4926
4927 switch (units) {
4928 case 'year': output = monthDiff(this, that) / 12; break;
4929 case 'month': output = monthDiff(this, that); break;
4930 case 'quarter': output = monthDiff(this, that) / 3; break;
4931 case 'second': output = (this - that) / 1e3; break; // 1000
4932 case 'minute': output = (this - that) / 6e4; break; // 1000 * 60
4933 case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60
4934 case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst
4935 case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst
4936 default: output = this - that;
4937 }
4938
4939 return asFloat ? output : absFloor(output);
4940}
4941
4942function monthDiff (a, b) {
4943 // difference in months
4944 var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
4945 // b is in (anchor - 1 month, anchor + 1 month)
4946 anchor = a.clone().add(wholeMonthDiff, 'months'),
4947 anchor2, adjust;
4948
4949 if (b - anchor < 0) {
4950 anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
4951 // linear across the month
4952 adjust = (b - anchor) / (anchor - anchor2);
4953 } else {
4954 anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
4955 // linear across the month
4956 adjust = (b - anchor) / (anchor2 - anchor);
4957 }
4958
4959 //check for negative zero, return zero if negative zero
4960 return -(wholeMonthDiff + adjust) || 0;
4961}
4962
4963hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
4964hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
4965
4966function toString () {
4967 return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
4968}
4969
4970function toISOString(keepOffset) {
4971 if (!this.isValid()) {
4972 return null;
4973 }
4974 var utc = keepOffset !== true;
4975 var m = utc ? this.clone().utc() : this;
4976 if (m.year() < 0 || m.year() > 9999) {
4977 return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ');
4978 }
4979 if (isFunction(Date.prototype.toISOString)) {
4980 // native implementation is ~50x faster, use it when we can
4981 if (utc) {
4982 return this.toDate().toISOString();
4983 } else {
4984 return new Date(this._d.valueOf()).toISOString().replace('Z', formatMoment(m, 'Z'));
4985 }
4986 }
4987 return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ');
4988}
4989
4990/**
4991 * Return a human readable representation of a moment that can
4992 * also be evaluated to get a new moment which is the same
4993 *
4994 * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
4995 */
4996function inspect () {
4997 if (!this.isValid()) {
4998 return 'moment.invalid(/* ' + this._i + ' */)';
4999 }
5000 var func = 'moment';
5001 var zone = '';
5002 if (!this.isLocal()) {
5003 func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
5004 zone = 'Z';
5005 }
5006 var prefix = '[' + func + '("]';
5007 var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
5008 var datetime = '-MM-DD[T]HH:mm:ss.SSS';
5009 var suffix = zone + '[")]';
5010
5011 return this.format(prefix + year + datetime + suffix);
5012}
5013
5014function format (inputString) {
5015 if (!inputString) {
5016 inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
5017 }
5018 var output = formatMoment(this, inputString);
5019 return this.localeData().postformat(output);
5020}
5021
5022function from (time, withoutSuffix) {
5023 if (this.isValid() &&
5024 ((isMoment(time) && time.isValid()) ||
5025 createLocal(time).isValid())) {
5026 return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
5027 } else {
5028 return this.localeData().invalidDate();
5029 }
5030}
5031
5032function fromNow (withoutSuffix) {
5033 return this.from(createLocal(), withoutSuffix);
5034}
5035
5036function to (time, withoutSuffix) {
5037 if (this.isValid() &&
5038 ((isMoment(time) && time.isValid()) ||
5039 createLocal(time).isValid())) {
5040 return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
5041 } else {
5042 return this.localeData().invalidDate();
5043 }
5044}
5045
5046function toNow (withoutSuffix) {
5047 return this.to(createLocal(), withoutSuffix);
5048}
5049
5050// If passed a locale key, it will set the locale for this
5051// instance. Otherwise, it will return the locale configuration
5052// variables for this instance.
5053function locale (key) {
5054 var newLocaleData;
5055
5056 if (key === undefined) {
5057 return this._locale._abbr;
5058 } else {
5059 newLocaleData = getLocale(key);
5060 if (newLocaleData != null) {
5061 this._locale = newLocaleData;
5062 }
5063 return this;
5064 }
5065}
5066
5067var lang = deprecate(
5068 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
5069 function (key) {
5070 if (key === undefined) {
5071 return this.localeData();
5072 } else {
5073 return this.locale(key);
5074 }
5075 }
5076);
5077
5078function localeData () {
5079 return this._locale;
5080}
5081
5082function startOf (units) {
5083 units = normalizeUnits(units);
5084 // the following switch intentionally omits break keywords
5085 // to utilize falling through the cases.
5086 switch (units) {
5087 case 'year':
5088 this.month(0);
5089 /* falls through */
5090 case 'quarter':
5091 case 'month':
5092 this.date(1);
5093 /* falls through */
5094 case 'week':
5095 case 'isoWeek':
5096 case 'day':
5097 case 'date':
5098 this.hours(0);
5099 /* falls through */
5100 case 'hour':
5101 this.minutes(0);
5102 /* falls through */
5103 case 'minute':
5104 this.seconds(0);
5105 /* falls through */
5106 case 'second':
5107 this.milliseconds(0);
5108 }
5109
5110 // weeks are a special case
5111 if (units === 'week') {
5112 this.weekday(0);
5113 }
5114 if (units === 'isoWeek') {
5115 this.isoWeekday(1);
5116 }
5117
5118 // quarters are also special
5119 if (units === 'quarter') {
5120 this.month(Math.floor(this.month() / 3) * 3);
5121 }
5122
5123 return this;
5124}
5125
5126function endOf (units) {
5127 units = normalizeUnits(units);
5128 if (units === undefined || units === 'millisecond') {
5129 return this;
5130 }
5131
5132 // 'date' is an alias for 'day', so it should be considered as such.
5133 if (units === 'date') {
5134 units = 'day';
5135 }
5136
5137 return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
5138}
5139
5140function valueOf () {
5141 return this._d.valueOf() - ((this._offset || 0) * 60000);
5142}
5143
5144function unix () {
5145 return Math.floor(this.valueOf() / 1000);
5146}
5147
5148function toDate () {
5149 return new Date(this.valueOf());
5150}
5151
5152function toArray () {
5153 var m = this;
5154 return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
5155}
5156
5157function toObject () {
5158 var m = this;
5159 return {
5160 years: m.year(),
5161 months: m.month(),
5162 date: m.date(),
5163 hours: m.hours(),
5164 minutes: m.minutes(),
5165 seconds: m.seconds(),
5166 milliseconds: m.milliseconds()
5167 };
5168}
5169
5170function toJSON () {
5171 // new Date(NaN).toJSON() === null
5172 return this.isValid() ? this.toISOString() : null;
5173}
5174
5175function isValid$2 () {
5176 return isValid(this);
5177}
5178
5179function parsingFlags () {
5180 return extend({}, getParsingFlags(this));
5181}
5182
5183function invalidAt () {
5184 return getParsingFlags(this).overflow;
5185}
5186
5187function creationData() {
5188 return {
5189 input: this._i,
5190 format: this._f,
5191 locale: this._locale,
5192 isUTC: this._isUTC,
5193 strict: this._strict
5194 };
5195}
5196
5197// FORMATTING
5198
5199addFormatToken(0, ['gg', 2], 0, function () {
5200 return this.weekYear() % 100;
5201});
5202
5203addFormatToken(0, ['GG', 2], 0, function () {
5204 return this.isoWeekYear() % 100;
5205});
5206
5207function addWeekYearFormatToken (token, getter) {
5208 addFormatToken(0, [token, token.length], 0, getter);
5209}
5210
5211addWeekYearFormatToken('gggg', 'weekYear');
5212addWeekYearFormatToken('ggggg', 'weekYear');
5213addWeekYearFormatToken('GGGG', 'isoWeekYear');
5214addWeekYearFormatToken('GGGGG', 'isoWeekYear');
5215
5216// ALIASES
5217
5218addUnitAlias('weekYear', 'gg');
5219addUnitAlias('isoWeekYear', 'GG');
5220
5221// PRIORITY
5222
5223addUnitPriority('weekYear', 1);
5224addUnitPriority('isoWeekYear', 1);
5225
5226
5227// PARSING
5228
5229addRegexToken('G', matchSigned);
5230addRegexToken('g', matchSigned);
5231addRegexToken('GG', match1to2, match2);
5232addRegexToken('gg', match1to2, match2);
5233addRegexToken('GGGG', match1to4, match4);
5234addRegexToken('gggg', match1to4, match4);
5235addRegexToken('GGGGG', match1to6, match6);
5236addRegexToken('ggggg', match1to6, match6);
5237
5238addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
5239 week[token.substr(0, 2)] = toInt(input);
5240});
5241
5242addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
5243 week[token] = hooks.parseTwoDigitYear(input);
5244});
5245
5246// MOMENTS
5247
5248function getSetWeekYear (input) {
5249 return getSetWeekYearHelper.call(this,
5250 input,
5251 this.week(),
5252 this.weekday(),
5253 this.localeData()._week.dow,
5254 this.localeData()._week.doy);
5255}
5256
5257function getSetISOWeekYear (input) {
5258 return getSetWeekYearHelper.call(this,
5259 input, this.isoWeek(), this.isoWeekday(), 1, 4);
5260}
5261
5262function getISOWeeksInYear () {
5263 return weeksInYear(this.year(), 1, 4);
5264}
5265
5266function getWeeksInYear () {
5267 var weekInfo = this.localeData()._week;
5268 return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
5269}
5270
5271function getSetWeekYearHelper(input, week, weekday, dow, doy) {
5272 var weeksTarget;
5273 if (input == null) {
5274 return weekOfYear(this, dow, doy).year;
5275 } else {
5276 weeksTarget = weeksInYear(input, dow, doy);
5277 if (week > weeksTarget) {
5278 week = weeksTarget;
5279 }
5280 return setWeekAll.call(this, input, week, weekday, dow, doy);
5281 }
5282}
5283
5284function setWeekAll(weekYear, week, weekday, dow, doy) {
5285 var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
5286 date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
5287
5288 this.year(date.getUTCFullYear());
5289 this.month(date.getUTCMonth());
5290 this.date(date.getUTCDate());
5291 return this;
5292}
5293
5294// FORMATTING
5295
5296addFormatToken('Q', 0, 'Qo', 'quarter');
5297
5298// ALIASES
5299
5300addUnitAlias('quarter', 'Q');
5301
5302// PRIORITY
5303
5304addUnitPriority('quarter', 7);
5305
5306// PARSING
5307
5308addRegexToken('Q', match1);
5309addParseToken('Q', function (input, array) {
5310 array[MONTH] = (toInt(input) - 1) * 3;
5311});
5312
5313// MOMENTS
5314
5315function getSetQuarter (input) {
5316 return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
5317}
5318
5319// FORMATTING
5320
5321addFormatToken('D', ['DD', 2], 'Do', 'date');
5322
5323// ALIASES
5324
5325addUnitAlias('date', 'D');
5326
5327// PRIOROITY
5328addUnitPriority('date', 9);
5329
5330// PARSING
5331
5332addRegexToken('D', match1to2);
5333addRegexToken('DD', match1to2, match2);
5334addRegexToken('Do', function (isStrict, locale) {
5335 // TODO: Remove "ordinalParse" fallback in next major release.
5336 return isStrict ?
5337 (locale._dayOfMonthOrdinalParse || locale._ordinalParse) :
5338 locale._dayOfMonthOrdinalParseLenient;
5339});
5340
5341addParseToken(['D', 'DD'], DATE);
5342addParseToken('Do', function (input, array) {
5343 array[DATE] = toInt(input.match(match1to2)[0]);
5344});
5345
5346// MOMENTS
5347
5348var getSetDayOfMonth = makeGetSet('Date', true);
5349
5350// FORMATTING
5351
5352addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
5353
5354// ALIASES
5355
5356addUnitAlias('dayOfYear', 'DDD');
5357
5358// PRIORITY
5359addUnitPriority('dayOfYear', 4);
5360
5361// PARSING
5362
5363addRegexToken('DDD', match1to3);
5364addRegexToken('DDDD', match3);
5365addParseToken(['DDD', 'DDDD'], function (input, array, config) {
5366 config._dayOfYear = toInt(input);
5367});
5368
5369// HELPERS
5370
5371// MOMENTS
5372
5373function getSetDayOfYear (input) {
5374 var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
5375 return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
5376}
5377
5378// FORMATTING
5379
5380addFormatToken('m', ['mm', 2], 0, 'minute');
5381
5382// ALIASES
5383
5384addUnitAlias('minute', 'm');
5385
5386// PRIORITY
5387
5388addUnitPriority('minute', 14);
5389
5390// PARSING
5391
5392addRegexToken('m', match1to2);
5393addRegexToken('mm', match1to2, match2);
5394addParseToken(['m', 'mm'], MINUTE);
5395
5396// MOMENTS
5397
5398var getSetMinute = makeGetSet('Minutes', false);
5399
5400// FORMATTING
5401
5402addFormatToken('s', ['ss', 2], 0, 'second');
5403
5404// ALIASES
5405
5406addUnitAlias('second', 's');
5407
5408// PRIORITY
5409
5410addUnitPriority('second', 15);
5411
5412// PARSING
5413
5414addRegexToken('s', match1to2);
5415addRegexToken('ss', match1to2, match2);
5416addParseToken(['s', 'ss'], SECOND);
5417
5418// MOMENTS
5419
5420var getSetSecond = makeGetSet('Seconds', false);
5421
5422// FORMATTING
5423
5424addFormatToken('S', 0, 0, function () {
5425 return ~~(this.millisecond() / 100);
5426});
5427
5428addFormatToken(0, ['SS', 2], 0, function () {
5429 return ~~(this.millisecond() / 10);
5430});
5431
5432addFormatToken(0, ['SSS', 3], 0, 'millisecond');
5433addFormatToken(0, ['SSSS', 4], 0, function () {
5434 return this.millisecond() * 10;
5435});
5436addFormatToken(0, ['SSSSS', 5], 0, function () {
5437 return this.millisecond() * 100;
5438});
5439addFormatToken(0, ['SSSSSS', 6], 0, function () {
5440 return this.millisecond() * 1000;
5441});
5442addFormatToken(0, ['SSSSSSS', 7], 0, function () {
5443 return this.millisecond() * 10000;
5444});
5445addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
5446 return this.millisecond() * 100000;
5447});
5448addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
5449 return this.millisecond() * 1000000;
5450});
5451
5452
5453// ALIASES
5454
5455addUnitAlias('millisecond', 'ms');
5456
5457// PRIORITY
5458
5459addUnitPriority('millisecond', 16);
5460
5461// PARSING
5462
5463addRegexToken('S', match1to3, match1);
5464addRegexToken('SS', match1to3, match2);
5465addRegexToken('SSS', match1to3, match3);
5466
5467var token;
5468for (token = 'SSSS'; token.length <= 9; token += 'S') {
5469 addRegexToken(token, matchUnsigned);
5470}
5471
5472function parseMs(input, array) {
5473 array[MILLISECOND] = toInt(('0.' + input) * 1000);
5474}
5475
5476for (token = 'S'; token.length <= 9; token += 'S') {
5477 addParseToken(token, parseMs);
5478}
5479// MOMENTS
5480
5481var getSetMillisecond = makeGetSet('Milliseconds', false);
5482
5483// FORMATTING
5484
5485addFormatToken('z', 0, 0, 'zoneAbbr');
5486addFormatToken('zz', 0, 0, 'zoneName');
5487
5488// MOMENTS
5489
5490function getZoneAbbr () {
5491 return this._isUTC ? 'UTC' : '';
5492}
5493
5494function getZoneName () {
5495 return this._isUTC ? 'Coordinated Universal Time' : '';
5496}
5497
5498var proto = Moment.prototype;
5499
5500proto.add = add;
5501proto.calendar = calendar$1;
5502proto.clone = clone;
5503proto.diff = diff;
5504proto.endOf = endOf;
5505proto.format = format;
5506proto.from = from;
5507proto.fromNow = fromNow;
5508proto.to = to;
5509proto.toNow = toNow;
5510proto.get = stringGet;
5511proto.invalidAt = invalidAt;
5512proto.isAfter = isAfter;
5513proto.isBefore = isBefore;
5514proto.isBetween = isBetween;
5515proto.isSame = isSame;
5516proto.isSameOrAfter = isSameOrAfter;
5517proto.isSameOrBefore = isSameOrBefore;
5518proto.isValid = isValid$2;
5519proto.lang = lang;
5520proto.locale = locale;
5521proto.localeData = localeData;
5522proto.max = prototypeMax;
5523proto.min = prototypeMin;
5524proto.parsingFlags = parsingFlags;
5525proto.set = stringSet;
5526proto.startOf = startOf;
5527proto.subtract = subtract;
5528proto.toArray = toArray;
5529proto.toObject = toObject;
5530proto.toDate = toDate;
5531proto.toISOString = toISOString;
5532proto.inspect = inspect;
5533proto.toJSON = toJSON;
5534proto.toString = toString;
5535proto.unix = unix;
5536proto.valueOf = valueOf;
5537proto.creationData = creationData;
5538
5539// Year
5540proto.year = getSetYear;
5541proto.isLeapYear = getIsLeapYear;
5542
5543// Week Year
5544proto.weekYear = getSetWeekYear;
5545proto.isoWeekYear = getSetISOWeekYear;
5546
5547// Quarter
5548proto.quarter = proto.quarters = getSetQuarter;
5549
5550// Month
5551proto.month = getSetMonth;
5552proto.daysInMonth = getDaysInMonth;
5553
5554// Week
5555proto.week = proto.weeks = getSetWeek;
5556proto.isoWeek = proto.isoWeeks = getSetISOWeek;
5557proto.weeksInYear = getWeeksInYear;
5558proto.isoWeeksInYear = getISOWeeksInYear;
5559
5560// Day
5561proto.date = getSetDayOfMonth;
5562proto.day = proto.days = getSetDayOfWeek;
5563proto.weekday = getSetLocaleDayOfWeek;
5564proto.isoWeekday = getSetISODayOfWeek;
5565proto.dayOfYear = getSetDayOfYear;
5566
5567// Hour
5568proto.hour = proto.hours = getSetHour;
5569
5570// Minute
5571proto.minute = proto.minutes = getSetMinute;
5572
5573// Second
5574proto.second = proto.seconds = getSetSecond;
5575
5576// Millisecond
5577proto.millisecond = proto.milliseconds = getSetMillisecond;
5578
5579// Offset
5580proto.utcOffset = getSetOffset;
5581proto.utc = setOffsetToUTC;
5582proto.local = setOffsetToLocal;
5583proto.parseZone = setOffsetToParsedOffset;
5584proto.hasAlignedHourOffset = hasAlignedHourOffset;
5585proto.isDST = isDaylightSavingTime;
5586proto.isLocal = isLocal;
5587proto.isUtcOffset = isUtcOffset;
5588proto.isUtc = isUtc;
5589proto.isUTC = isUtc;
5590
5591// Timezone
5592proto.zoneAbbr = getZoneAbbr;
5593proto.zoneName = getZoneName;
5594
5595// Deprecations
5596proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
5597proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
5598proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear);
5599proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
5600proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
5601
5602function createUnix (input) {
5603 return createLocal(input * 1000);
5604}
5605
5606function createInZone () {
5607 return createLocal.apply(null, arguments).parseZone();
5608}
5609
5610function preParsePostFormat (string) {
5611 return string;
5612}
5613
5614var proto$1 = Locale.prototype;
5615
5616proto$1.calendar = calendar;
5617proto$1.longDateFormat = longDateFormat;
5618proto$1.invalidDate = invalidDate;
5619proto$1.ordinal = ordinal;
5620proto$1.preparse = preParsePostFormat;
5621proto$1.postformat = preParsePostFormat;
5622proto$1.relativeTime = relativeTime;
5623proto$1.pastFuture = pastFuture;
5624proto$1.set = set;
5625
5626// Month
5627proto$1.months = localeMonths;
5628proto$1.monthsShort = localeMonthsShort;
5629proto$1.monthsParse = localeMonthsParse;
5630proto$1.monthsRegex = monthsRegex;
5631proto$1.monthsShortRegex = monthsShortRegex;
5632
5633// Week
5634proto$1.week = localeWeek;
5635proto$1.firstDayOfYear = localeFirstDayOfYear;
5636proto$1.firstDayOfWeek = localeFirstDayOfWeek;
5637
5638// Day of Week
5639proto$1.weekdays = localeWeekdays;
5640proto$1.weekdaysMin = localeWeekdaysMin;
5641proto$1.weekdaysShort = localeWeekdaysShort;
5642proto$1.weekdaysParse = localeWeekdaysParse;
5643
5644proto$1.weekdaysRegex = weekdaysRegex;
5645proto$1.weekdaysShortRegex = weekdaysShortRegex;
5646proto$1.weekdaysMinRegex = weekdaysMinRegex;
5647
5648// Hours
5649proto$1.isPM = localeIsPM;
5650proto$1.meridiem = localeMeridiem;
5651
5652function get$1 (format, index, field, setter) {
5653 var locale = getLocale();
5654 var utc = createUTC().set(setter, index);
5655 return locale[field](utc, format);
5656}
5657
5658function listMonthsImpl (format, index, field) {
5659 if (isNumber(format)) {
5660 index = format;
5661 format = undefined;
5662 }
5663
5664 format = format || '';
5665
5666 if (index != null) {
5667 return get$1(format, index, field, 'month');
5668 }
5669
5670 var i;
5671 var out = [];
5672 for (i = 0; i < 12; i++) {
5673 out[i] = get$1(format, i, field, 'month');
5674 }
5675 return out;
5676}
5677
5678// ()
5679// (5)
5680// (fmt, 5)
5681// (fmt)
5682// (true)
5683// (true, 5)
5684// (true, fmt, 5)
5685// (true, fmt)
5686function listWeekdaysImpl (localeSorted, format, index, field) {
5687 if (typeof localeSorted === 'boolean') {
5688 if (isNumber(format)) {
5689 index = format;
5690 format = undefined;
5691 }
5692
5693 format = format || '';
5694 } else {
5695 format = localeSorted;
5696 index = format;
5697 localeSorted = false;
5698
5699 if (isNumber(format)) {
5700 index = format;
5701 format = undefined;
5702 }
5703
5704 format = format || '';
5705 }
5706
5707 var locale = getLocale(),
5708 shift = localeSorted ? locale._week.dow : 0;
5709
5710 if (index != null) {
5711 return get$1(format, (index + shift) % 7, field, 'day');
5712 }
5713
5714 var i;
5715 var out = [];
5716 for (i = 0; i < 7; i++) {
5717 out[i] = get$1(format, (i + shift) % 7, field, 'day');
5718 }
5719 return out;
5720}
5721
5722function listMonths (format, index) {
5723 return listMonthsImpl(format, index, 'months');
5724}
5725
5726function listMonthsShort (format, index) {
5727 return listMonthsImpl(format, index, 'monthsShort');
5728}
5729
5730function listWeekdays (localeSorted, format, index) {
5731 return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
5732}
5733
5734function listWeekdaysShort (localeSorted, format, index) {
5735 return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
5736}
5737
5738function listWeekdaysMin (localeSorted, format, index) {
5739 return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
5740}
5741
5742getSetGlobalLocale('en', {
5743 dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
5744 ordinal : function (number) {
5745 var b = number % 10,
5746 output = (toInt(number % 100 / 10) === 1) ? 'th' :
5747 (b === 1) ? 'st' :
5748 (b === 2) ? 'nd' :
5749 (b === 3) ? 'rd' : 'th';
5750 return number + output;
5751 }
5752});
5753
5754// Side effect imports
5755hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
5756hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
5757
5758var mathAbs = Math.abs;
5759
5760function abs () {
5761 var data = this._data;
5762
5763 this._milliseconds = mathAbs(this._milliseconds);
5764 this._days = mathAbs(this._days);
5765 this._months = mathAbs(this._months);
5766
5767 data.milliseconds = mathAbs(data.milliseconds);
5768 data.seconds = mathAbs(data.seconds);
5769 data.minutes = mathAbs(data.minutes);
5770 data.hours = mathAbs(data.hours);
5771 data.months = mathAbs(data.months);
5772 data.years = mathAbs(data.years);
5773
5774 return this;
5775}
5776
5777function addSubtract$1 (duration, input, value, direction) {
5778 var other = createDuration(input, value);
5779
5780 duration._milliseconds += direction * other._milliseconds;
5781 duration._days += direction * other._days;
5782 duration._months += direction * other._months;
5783
5784 return duration._bubble();
5785}
5786
5787// supports only 2.0-style add(1, 's') or add(duration)
5788function add$1 (input, value) {
5789 return addSubtract$1(this, input, value, 1);
5790}
5791
5792// supports only 2.0-style subtract(1, 's') or subtract(duration)
5793function subtract$1 (input, value) {
5794 return addSubtract$1(this, input, value, -1);
5795}
5796
5797function absCeil (number) {
5798 if (number < 0) {
5799 return Math.floor(number);
5800 } else {
5801 return Math.ceil(number);
5802 }
5803}
5804
5805function bubble () {
5806 var milliseconds = this._milliseconds;
5807 var days = this._days;
5808 var months = this._months;
5809 var data = this._data;
5810 var seconds, minutes, hours, years, monthsFromDays;
5811
5812 // if we have a mix of positive and negative values, bubble down first
5813 // check: https://github.com/moment/moment/issues/2166
5814 if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
5815 (milliseconds <= 0 && days <= 0 && months <= 0))) {
5816 milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
5817 days = 0;
5818 months = 0;
5819 }
5820
5821 // The following code bubbles up values, see the tests for
5822 // examples of what that means.
5823 data.milliseconds = milliseconds % 1000;
5824
5825 seconds = absFloor(milliseconds / 1000);
5826 data.seconds = seconds % 60;
5827
5828 minutes = absFloor(seconds / 60);
5829 data.minutes = minutes % 60;
5830
5831 hours = absFloor(minutes / 60);
5832 data.hours = hours % 24;
5833
5834 days += absFloor(hours / 24);
5835
5836 // convert days to months
5837 monthsFromDays = absFloor(daysToMonths(days));
5838 months += monthsFromDays;
5839 days -= absCeil(monthsToDays(monthsFromDays));
5840
5841 // 12 months -> 1 year
5842 years = absFloor(months / 12);
5843 months %= 12;
5844
5845 data.days = days;
5846 data.months = months;
5847 data.years = years;
5848
5849 return this;
5850}
5851
5852function daysToMonths (days) {
5853 // 400 years have 146097 days (taking into account leap year rules)
5854 // 400 years have 12 months === 4800
5855 return days * 4800 / 146097;
5856}
5857
5858function monthsToDays (months) {
5859 // the reverse of daysToMonths
5860 return months * 146097 / 4800;
5861}
5862
5863function as (units) {
5864 if (!this.isValid()) {
5865 return NaN;
5866 }
5867 var days;
5868 var months;
5869 var milliseconds = this._milliseconds;
5870
5871 units = normalizeUnits(units);
5872
5873 if (units === 'month' || units === 'year') {
5874 days = this._days + milliseconds / 864e5;
5875 months = this._months + daysToMonths(days);
5876 return units === 'month' ? months : months / 12;
5877 } else {
5878 // handle milliseconds separately because of floating point math errors (issue #1867)
5879 days = this._days + Math.round(monthsToDays(this._months));
5880 switch (units) {
5881 case 'week' : return days / 7 + milliseconds / 6048e5;
5882 case 'day' : return days + milliseconds / 864e5;
5883 case 'hour' : return days * 24 + milliseconds / 36e5;
5884 case 'minute' : return days * 1440 + milliseconds / 6e4;
5885 case 'second' : return days * 86400 + milliseconds / 1000;
5886 // Math.floor prevents floating point math errors here
5887 case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
5888 default: throw new Error('Unknown unit ' + units);
5889 }
5890 }
5891}
5892
5893// TODO: Use this.as('ms')?
5894function valueOf$1 () {
5895 if (!this.isValid()) {
5896 return NaN;
5897 }
5898 return (
5899 this._milliseconds +
5900 this._days * 864e5 +
5901 (this._months % 12) * 2592e6 +
5902 toInt(this._months / 12) * 31536e6
5903 );
5904}
5905
5906function makeAs (alias) {
5907 return function () {
5908 return this.as(alias);
5909 };
5910}
5911
5912var asMilliseconds = makeAs('ms');
5913var asSeconds = makeAs('s');
5914var asMinutes = makeAs('m');
5915var asHours = makeAs('h');
5916var asDays = makeAs('d');
5917var asWeeks = makeAs('w');
5918var asMonths = makeAs('M');
5919var asYears = makeAs('y');
5920
5921function clone$1 () {
5922 return createDuration(this);
5923}
5924
5925function get$2 (units) {
5926 units = normalizeUnits(units);
5927 return this.isValid() ? this[units + 's']() : NaN;
5928}
5929
5930function makeGetter(name) {
5931 return function () {
5932 return this.isValid() ? this._data[name] : NaN;
5933 };
5934}
5935
5936var milliseconds = makeGetter('milliseconds');
5937var seconds = makeGetter('seconds');
5938var minutes = makeGetter('minutes');
5939var hours = makeGetter('hours');
5940var days = makeGetter('days');
5941var months = makeGetter('months');
5942var years = makeGetter('years');
5943
5944function weeks () {
5945 return absFloor(this.days() / 7);
5946}
5947
5948var round = Math.round;
5949var thresholds = {
5950 ss: 44, // a few seconds to seconds
5951 s : 45, // seconds to minute
5952 m : 45, // minutes to hour
5953 h : 22, // hours to day
5954 d : 26, // days to month
5955 M : 11 // months to year
5956};
5957
5958// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
5959function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
5960 return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
5961}
5962
5963function relativeTime$1 (posNegDuration, withoutSuffix, locale) {
5964 var duration = createDuration(posNegDuration).abs();
5965 var seconds = round(duration.as('s'));
5966 var minutes = round(duration.as('m'));
5967 var hours = round(duration.as('h'));
5968 var days = round(duration.as('d'));
5969 var months = round(duration.as('M'));
5970 var years = round(duration.as('y'));
5971
5972 var a = seconds <= thresholds.ss && ['s', seconds] ||
5973 seconds < thresholds.s && ['ss', seconds] ||
5974 minutes <= 1 && ['m'] ||
5975 minutes < thresholds.m && ['mm', minutes] ||
5976 hours <= 1 && ['h'] ||
5977 hours < thresholds.h && ['hh', hours] ||
5978 days <= 1 && ['d'] ||
5979 days < thresholds.d && ['dd', days] ||
5980 months <= 1 && ['M'] ||
5981 months < thresholds.M && ['MM', months] ||
5982 years <= 1 && ['y'] || ['yy', years];
5983
5984 a[2] = withoutSuffix;
5985 a[3] = +posNegDuration > 0;
5986 a[4] = locale;
5987 return substituteTimeAgo.apply(null, a);
5988}
5989
5990// This function allows you to set the rounding function for relative time strings
5991function getSetRelativeTimeRounding (roundingFunction) {
5992 if (roundingFunction === undefined) {
5993 return round;
5994 }
5995 if (typeof(roundingFunction) === 'function') {
5996 round = roundingFunction;
5997 return true;
5998 }
5999 return false;
6000}
6001
6002// This function allows you to set a threshold for relative time strings
6003function getSetRelativeTimeThreshold (threshold, limit) {
6004 if (thresholds[threshold] === undefined) {
6005 return false;
6006 }
6007 if (limit === undefined) {
6008 return thresholds[threshold];
6009 }
6010 thresholds[threshold] = limit;
6011 if (threshold === 's') {
6012 thresholds.ss = limit - 1;
6013 }
6014 return true;
6015}
6016
6017function humanize (withSuffix) {
6018 if (!this.isValid()) {
6019 return this.localeData().invalidDate();
6020 }
6021
6022 var locale = this.localeData();
6023 var output = relativeTime$1(this, !withSuffix, locale);
6024
6025 if (withSuffix) {
6026 output = locale.pastFuture(+this, output);
6027 }
6028
6029 return locale.postformat(output);
6030}
6031
6032var abs$1 = Math.abs;
6033
6034function sign(x) {
6035 return ((x > 0) - (x < 0)) || +x;
6036}
6037
6038function toISOString$1() {
6039 // for ISO strings we do not use the normal bubbling rules:
6040 // * milliseconds bubble up until they become hours
6041 // * days do not bubble at all
6042 // * months bubble up until they become years
6043 // This is because there is no context-free conversion between hours and days
6044 // (think of clock changes)
6045 // and also not between days and months (28-31 days per month)
6046 if (!this.isValid()) {
6047 return this.localeData().invalidDate();
6048 }
6049
6050 var seconds = abs$1(this._milliseconds) / 1000;
6051 var days = abs$1(this._days);
6052 var months = abs$1(this._months);
6053 var minutes, hours, years;
6054
6055 // 3600 seconds -> 60 minutes -> 1 hour
6056 minutes = absFloor(seconds / 60);
6057 hours = absFloor(minutes / 60);
6058 seconds %= 60;
6059 minutes %= 60;
6060
6061 // 12 months -> 1 year
6062 years = absFloor(months / 12);
6063 months %= 12;
6064
6065
6066 // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
6067 var Y = years;
6068 var M = months;
6069 var D = days;
6070 var h = hours;
6071 var m = minutes;
6072 var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
6073 var total = this.asSeconds();
6074
6075 if (!total) {
6076 // this is the same as C#'s (Noda) and python (isodate)...
6077 // but not other JS (goog.date)
6078 return 'P0D';
6079 }
6080
6081 var totalSign = total < 0 ? '-' : '';
6082 var ymSign = sign(this._months) !== sign(total) ? '-' : '';
6083 var daysSign = sign(this._days) !== sign(total) ? '-' : '';
6084 var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
6085
6086 return totalSign + 'P' +
6087 (Y ? ymSign + Y + 'Y' : '') +
6088 (M ? ymSign + M + 'M' : '') +
6089 (D ? daysSign + D + 'D' : '') +
6090 ((h || m || s) ? 'T' : '') +
6091 (h ? hmsSign + h + 'H' : '') +
6092 (m ? hmsSign + m + 'M' : '') +
6093 (s ? hmsSign + s + 'S' : '');
6094}
6095
6096var proto$2 = Duration.prototype;
6097
6098proto$2.isValid = isValid$1;
6099proto$2.abs = abs;
6100proto$2.add = add$1;
6101proto$2.subtract = subtract$1;
6102proto$2.as = as;
6103proto$2.asMilliseconds = asMilliseconds;
6104proto$2.asSeconds = asSeconds;
6105proto$2.asMinutes = asMinutes;
6106proto$2.asHours = asHours;
6107proto$2.asDays = asDays;
6108proto$2.asWeeks = asWeeks;
6109proto$2.asMonths = asMonths;
6110proto$2.asYears = asYears;
6111proto$2.valueOf = valueOf$1;
6112proto$2._bubble = bubble;
6113proto$2.clone = clone$1;
6114proto$2.get = get$2;
6115proto$2.milliseconds = milliseconds;
6116proto$2.seconds = seconds;
6117proto$2.minutes = minutes;
6118proto$2.hours = hours;
6119proto$2.days = days;
6120proto$2.weeks = weeks;
6121proto$2.months = months;
6122proto$2.years = years;
6123proto$2.humanize = humanize;
6124proto$2.toISOString = toISOString$1;
6125proto$2.toString = toISOString$1;
6126proto$2.toJSON = toISOString$1;
6127proto$2.locale = locale;
6128proto$2.localeData = localeData;
6129
6130// Deprecations
6131proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
6132proto$2.lang = lang;
6133
6134// Side effect imports
6135
6136// FORMATTING
6137
6138addFormatToken('X', 0, 0, 'unix');
6139addFormatToken('x', 0, 0, 'valueOf');
6140
6141// PARSING
6142
6143addRegexToken('x', matchSigned);
6144addRegexToken('X', matchTimestamp);
6145addParseToken('X', function (input, array, config) {
6146 config._d = new Date(parseFloat(input, 10) * 1000);
6147});
6148addParseToken('x', function (input, array, config) {
6149 config._d = new Date(toInt(input));
6150});
6151
6152// Side effect imports
6153
6154
6155hooks.version = '2.20.1';
6156
6157setHookCallback(createLocal);
6158
6159hooks.fn = proto;
6160hooks.min = min;
6161hooks.max = max;
6162hooks.now = now;
6163hooks.utc = createUTC;
6164hooks.unix = createUnix;
6165hooks.months = listMonths;
6166hooks.isDate = isDate;
6167hooks.locale = getSetGlobalLocale;
6168hooks.invalid = createInvalid;
6169hooks.duration = createDuration;
6170hooks.isMoment = isMoment;
6171hooks.weekdays = listWeekdays;
6172hooks.parseZone = createInZone;
6173hooks.localeData = getLocale;
6174hooks.isDuration = isDuration;
6175hooks.monthsShort = listMonthsShort;
6176hooks.weekdaysMin = listWeekdaysMin;
6177hooks.defineLocale = defineLocale;
6178hooks.updateLocale = updateLocale;
6179hooks.locales = listLocales;
6180hooks.weekdaysShort = listWeekdaysShort;
6181hooks.normalizeUnits = normalizeUnits;
6182hooks.relativeTimeRounding = getSetRelativeTimeRounding;
6183hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
6184hooks.calendarFormat = getCalendarFormat;
6185hooks.prototype = proto;
6186
6187// currently HTML5 input type only supports 24-hour formats
6188hooks.HTML5_FMT = {
6189 DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
6190 DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
6191 DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
6192 DATE: 'YYYY-MM-DD', // <input type="date" />
6193 TIME: 'HH:mm', // <input type="time" />
6194 TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
6195 TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
6196 WEEK: 'YYYY-[W]WW', // <input type="week" />
6197 MONTH: 'YYYY-MM' // <input type="month" />
6198};
6199
6200return hooks;
6201
6202})));
6203
6204},{}],7:[function(require,module,exports){
6205/**
6206 * @namespace Chart
6207 */
6208var Chart = require(29)();
6209
6210Chart.helpers = require(45);
6211
6212// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
6213require(27)(Chart);
6214
6215Chart.defaults = require(25);
6216Chart.Element = require(26);
6217Chart.elements = require(40);
6218Chart.Interaction = require(28);
6219Chart.layouts = require(30);
6220Chart.platform = require(48);
6221Chart.plugins = require(31);
6222Chart.Ticks = require(34);
6223
6224require(22)(Chart);
6225require(23)(Chart);
6226require(24)(Chart);
6227require(33)(Chart);
6228require(32)(Chart);
6229require(35)(Chart);
6230
6231require(55)(Chart);
6232require(53)(Chart);
6233require(54)(Chart);
6234require(56)(Chart);
6235require(57)(Chart);
6236require(58)(Chart);
6237
6238// Controllers must be loaded after elements
6239// See Chart.core.datasetController.dataElementType
6240require(15)(Chart);
6241require(16)(Chart);
6242require(17)(Chart);
6243require(18)(Chart);
6244require(19)(Chart);
6245require(20)(Chart);
6246require(21)(Chart);
6247
6248require(8)(Chart);
6249require(9)(Chart);
6250require(10)(Chart);
6251require(11)(Chart);
6252require(12)(Chart);
6253require(13)(Chart);
6254require(14)(Chart);
6255
6256// Loading built-it plugins
6257var plugins = require(49);
6258for (var k in plugins) {
6259 if (plugins.hasOwnProperty(k)) {
6260 Chart.plugins.register(plugins[k]);
6261 }
6262}
6263
6264Chart.platform.initialize();
6265
6266module.exports = Chart;
6267if (typeof window !== 'undefined') {
6268 window.Chart = Chart;
6269}
6270
6271// DEPRECATIONS
6272
6273/**
6274 * Provided for backward compatibility, not available anymore
6275 * @namespace Chart.Legend
6276 * @deprecated since version 2.1.5
6277 * @todo remove at version 3
6278 * @private
6279 */
6280Chart.Legend = plugins.legend._element;
6281
6282/**
6283 * Provided for backward compatibility, not available anymore
6284 * @namespace Chart.Title
6285 * @deprecated since version 2.1.5
6286 * @todo remove at version 3
6287 * @private
6288 */
6289Chart.Title = plugins.title._element;
6290
6291/**
6292 * Provided for backward compatibility, use Chart.plugins instead
6293 * @namespace Chart.pluginService
6294 * @deprecated since version 2.1.5
6295 * @todo remove at version 3
6296 * @private
6297 */
6298Chart.pluginService = Chart.plugins;
6299
6300/**
6301 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
6302 * effect, instead simply create/register plugins via plain JavaScript objects.
6303 * @interface Chart.PluginBase
6304 * @deprecated since version 2.5.0
6305 * @todo remove at version 3
6306 * @private
6307 */
6308Chart.PluginBase = Chart.Element.extend({});
6309
6310/**
6311 * Provided for backward compatibility, use Chart.helpers.canvas instead.
6312 * @namespace Chart.canvasHelpers
6313 * @deprecated since version 2.6.0
6314 * @todo remove at version 3
6315 * @private
6316 */
6317Chart.canvasHelpers = Chart.helpers.canvas;
6318
6319/**
6320 * Provided for backward compatibility, use Chart.layouts instead.
6321 * @namespace Chart.layoutService
6322 * @deprecated since version 2.8.0
6323 * @todo remove at version 3
6324 * @private
6325 */
6326Chart.layoutService = Chart.layouts;
6327
6328},{"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){
6329'use strict';
6330
6331module.exports = function(Chart) {
6332
6333 Chart.Bar = function(context, config) {
6334 config.type = 'bar';
6335
6336 return new Chart(context, config);
6337 };
6338
6339};
6340
6341},{}],9:[function(require,module,exports){
6342'use strict';
6343
6344module.exports = function(Chart) {
6345
6346 Chart.Bubble = function(context, config) {
6347 config.type = 'bubble';
6348 return new Chart(context, config);
6349 };
6350
6351};
6352
6353},{}],10:[function(require,module,exports){
6354'use strict';
6355
6356module.exports = function(Chart) {
6357
6358 Chart.Doughnut = function(context, config) {
6359 config.type = 'doughnut';
6360
6361 return new Chart(context, config);
6362 };
6363
6364};
6365
6366},{}],11:[function(require,module,exports){
6367'use strict';
6368
6369module.exports = function(Chart) {
6370
6371 Chart.Line = function(context, config) {
6372 config.type = 'line';
6373
6374 return new Chart(context, config);
6375 };
6376
6377};
6378
6379},{}],12:[function(require,module,exports){
6380'use strict';
6381
6382module.exports = function(Chart) {
6383
6384 Chart.PolarArea = function(context, config) {
6385 config.type = 'polarArea';
6386
6387 return new Chart(context, config);
6388 };
6389
6390};
6391
6392},{}],13:[function(require,module,exports){
6393'use strict';
6394
6395module.exports = function(Chart) {
6396
6397 Chart.Radar = function(context, config) {
6398 config.type = 'radar';
6399
6400 return new Chart(context, config);
6401 };
6402
6403};
6404
6405},{}],14:[function(require,module,exports){
6406'use strict';
6407
6408module.exports = function(Chart) {
6409 Chart.Scatter = function(context, config) {
6410 config.type = 'scatter';
6411 return new Chart(context, config);
6412 };
6413};
6414
6415},{}],15:[function(require,module,exports){
6416'use strict';
6417
6418var defaults = require(25);
6419var elements = require(40);
6420var helpers = require(45);
6421
6422defaults._set('bar', {
6423 hover: {
6424 mode: 'label'
6425 },
6426
6427 scales: {
6428 xAxes: [{
6429 type: 'category',
6430
6431 // Specific to Bar Controller
6432 categoryPercentage: 0.8,
6433 barPercentage: 0.9,
6434
6435 // offset settings
6436 offset: true,
6437
6438 // grid line settings
6439 gridLines: {
6440 offsetGridLines: true
6441 }
6442 }],
6443
6444 yAxes: [{
6445 type: 'linear'
6446 }]
6447 }
6448});
6449
6450defaults._set('horizontalBar', {
6451 hover: {
6452 mode: 'index',
6453 axis: 'y'
6454 },
6455
6456 scales: {
6457 xAxes: [{
6458 type: 'linear',
6459 position: 'bottom'
6460 }],
6461
6462 yAxes: [{
6463 position: 'left',
6464 type: 'category',
6465
6466 // Specific to Horizontal Bar Controller
6467 categoryPercentage: 0.8,
6468 barPercentage: 0.9,
6469
6470 // offset settings
6471 offset: true,
6472
6473 // grid line settings
6474 gridLines: {
6475 offsetGridLines: true
6476 }
6477 }]
6478 },
6479
6480 elements: {
6481 rectangle: {
6482 borderSkipped: 'left'
6483 }
6484 },
6485
6486 tooltips: {
6487 callbacks: {
6488 title: function(item, data) {
6489 // Pick first xLabel for now
6490 var title = '';
6491
6492 if (item.length > 0) {
6493 if (item[0].yLabel) {
6494 title = item[0].yLabel;
6495 } else if (data.labels.length > 0 && item[0].index < data.labels.length) {
6496 title = data.labels[item[0].index];
6497 }
6498 }
6499
6500 return title;
6501 },
6502
6503 label: function(item, data) {
6504 var datasetLabel = data.datasets[item.datasetIndex].label || '';
6505 return datasetLabel + ': ' + item.xLabel;
6506 }
6507 },
6508 mode: 'index',
6509 axis: 'y'
6510 }
6511});
6512
6513/**
6514 * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
6515 * @private
6516 */
6517function computeMinSampleSize(scale, pixels) {
6518 var min = scale.isHorizontal() ? scale.width : scale.height;
6519 var ticks = scale.getTicks();
6520 var prev, curr, i, ilen;
6521
6522 for (i = 1, ilen = pixels.length; i < ilen; ++i) {
6523 min = Math.min(min, pixels[i] - pixels[i - 1]);
6524 }
6525
6526 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
6527 curr = scale.getPixelForTick(i);
6528 min = i > 0 ? Math.min(min, curr - prev) : min;
6529 prev = curr;
6530 }
6531
6532 return min;
6533}
6534
6535/**
6536 * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null,
6537 * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This
6538 * mode currently always generates bars equally sized (until we introduce scriptable options?).
6539 * @private
6540 */
6541function computeFitCategoryTraits(index, ruler, options) {
6542 var thickness = options.barThickness;
6543 var count = ruler.stackCount;
6544 var curr = ruler.pixels[index];
6545 var size, ratio;
6546
6547 if (helpers.isNullOrUndef(thickness)) {
6548 size = ruler.min * options.categoryPercentage;
6549 ratio = options.barPercentage;
6550 } else {
6551 // When bar thickness is enforced, category and bar percentages are ignored.
6552 // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')
6553 // and deprecate barPercentage since this value is ignored when thickness is absolute.
6554 size = thickness * count;
6555 ratio = 1;
6556 }
6557
6558 return {
6559 chunk: size / count,
6560 ratio: ratio,
6561 start: curr - (size / 2)
6562 };
6563}
6564
6565/**
6566 * Computes an "optimal" category that globally arranges bars side by side (no gap when
6567 * percentage options are 1), based on the previous and following categories. This mode
6568 * generates bars with different widths when data are not evenly spaced.
6569 * @private
6570 */
6571function computeFlexCategoryTraits(index, ruler, options) {
6572 var pixels = ruler.pixels;
6573 var curr = pixels[index];
6574 var prev = index > 0 ? pixels[index - 1] : null;
6575 var next = index < pixels.length - 1 ? pixels[index + 1] : null;
6576 var percent = options.categoryPercentage;
6577 var start, size;
6578
6579 if (prev === null) {
6580 // first data: its size is double based on the next point or,
6581 // if it's also the last data, we use the scale end extremity.
6582 prev = curr - (next === null ? ruler.end - curr : next - curr);
6583 }
6584
6585 if (next === null) {
6586 // last data: its size is also double based on the previous point.
6587 next = curr + curr - prev;
6588 }
6589
6590 start = curr - ((curr - prev) / 2) * percent;
6591 size = ((next - prev) / 2) * percent;
6592
6593 return {
6594 chunk: size / ruler.stackCount,
6595 ratio: options.barPercentage,
6596 start: start
6597 };
6598}
6599
6600module.exports = function(Chart) {
6601
6602 Chart.controllers.bar = Chart.DatasetController.extend({
6603
6604 dataElementType: elements.Rectangle,
6605
6606 initialize: function() {
6607 var me = this;
6608 var meta;
6609
6610 Chart.DatasetController.prototype.initialize.apply(me, arguments);
6611
6612 meta = me.getMeta();
6613 meta.stack = me.getDataset().stack;
6614 meta.bar = true;
6615 },
6616
6617 update: function(reset) {
6618 var me = this;
6619 var rects = me.getMeta().data;
6620 var i, ilen;
6621
6622 me._ruler = me.getRuler();
6623
6624 for (i = 0, ilen = rects.length; i < ilen; ++i) {
6625 me.updateElement(rects[i], i, reset);
6626 }
6627 },
6628
6629 updateElement: function(rectangle, index, reset) {
6630 var me = this;
6631 var chart = me.chart;
6632 var meta = me.getMeta();
6633 var dataset = me.getDataset();
6634 var custom = rectangle.custom || {};
6635 var rectangleOptions = chart.options.elements.rectangle;
6636
6637 rectangle._xScale = me.getScaleForId(meta.xAxisID);
6638 rectangle._yScale = me.getScaleForId(meta.yAxisID);
6639 rectangle._datasetIndex = me.index;
6640 rectangle._index = index;
6641
6642 rectangle._model = {
6643 datasetLabel: dataset.label,
6644 label: chart.data.labels[index],
6645 borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped,
6646 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor),
6647 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor),
6648 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth)
6649 };
6650
6651 me.updateElementGeometry(rectangle, index, reset);
6652
6653 rectangle.pivot();
6654 },
6655
6656 /**
6657 * @private
6658 */
6659 updateElementGeometry: function(rectangle, index, reset) {
6660 var me = this;
6661 var model = rectangle._model;
6662 var vscale = me.getValueScale();
6663 var base = vscale.getBasePixel();
6664 var horizontal = vscale.isHorizontal();
6665 var ruler = me._ruler || me.getRuler();
6666 var vpixels = me.calculateBarValuePixels(me.index, index);
6667 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
6668
6669 model.horizontal = horizontal;
6670 model.base = reset ? base : vpixels.base;
6671 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
6672 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
6673 model.height = horizontal ? ipixels.size : undefined;
6674 model.width = horizontal ? undefined : ipixels.size;
6675 },
6676
6677 /**
6678 * @private
6679 */
6680 getValueScaleId: function() {
6681 return this.getMeta().yAxisID;
6682 },
6683
6684 /**
6685 * @private
6686 */
6687 getIndexScaleId: function() {
6688 return this.getMeta().xAxisID;
6689 },
6690
6691 /**
6692 * @private
6693 */
6694 getValueScale: function() {
6695 return this.getScaleForId(this.getValueScaleId());
6696 },
6697
6698 /**
6699 * @private
6700 */
6701 getIndexScale: function() {
6702 return this.getScaleForId(this.getIndexScaleId());
6703 },
6704
6705 /**
6706 * Returns the stacks based on groups and bar visibility.
6707 * @param {Number} [last] - The dataset index
6708 * @returns {Array} The stack list
6709 * @private
6710 */
6711 _getStacks: function(last) {
6712 var me = this;
6713 var chart = me.chart;
6714 var scale = me.getIndexScale();
6715 var stacked = scale.options.stacked;
6716 var ilen = last === undefined ? chart.data.datasets.length : last + 1;
6717 var stacks = [];
6718 var i, meta;
6719
6720 for (i = 0; i < ilen; ++i) {
6721 meta = chart.getDatasetMeta(i);
6722 if (meta.bar && chart.isDatasetVisible(i) &&
6723 (stacked === false ||
6724 (stacked === true && stacks.indexOf(meta.stack) === -1) ||
6725 (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
6726 stacks.push(meta.stack);
6727 }
6728 }
6729
6730 return stacks;
6731 },
6732
6733 /**
6734 * Returns the effective number of stacks based on groups and bar visibility.
6735 * @private
6736 */
6737 getStackCount: function() {
6738 return this._getStacks().length;
6739 },
6740
6741 /**
6742 * Returns the stack index for the given dataset based on groups and bar visibility.
6743 * @param {Number} [datasetIndex] - The dataset index
6744 * @param {String} [name] - The stack name to find
6745 * @returns {Number} The stack index
6746 * @private
6747 */
6748 getStackIndex: function(datasetIndex, name) {
6749 var stacks = this._getStacks(datasetIndex);
6750 var index = (name !== undefined)
6751 ? stacks.indexOf(name)
6752 : -1; // indexOf returns -1 if element is not present
6753
6754 return (index === -1)
6755 ? stacks.length - 1
6756 : index;
6757 },
6758
6759 /**
6760 * @private
6761 */
6762 getRuler: function() {
6763 var me = this;
6764 var scale = me.getIndexScale();
6765 var stackCount = me.getStackCount();
6766 var datasetIndex = me.index;
6767 var isHorizontal = scale.isHorizontal();
6768 var start = isHorizontal ? scale.left : scale.top;
6769 var end = start + (isHorizontal ? scale.width : scale.height);
6770 var pixels = [];
6771 var i, ilen, min;
6772
6773 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
6774 pixels.push(scale.getPixelForValue(null, i, datasetIndex));
6775 }
6776
6777 min = helpers.isNullOrUndef(scale.options.barThickness)
6778 ? computeMinSampleSize(scale, pixels)
6779 : -1;
6780
6781 return {
6782 min: min,
6783 pixels: pixels,
6784 start: start,
6785 end: end,
6786 stackCount: stackCount,
6787 scale: scale
6788 };
6789 },
6790
6791 /**
6792 * Note: pixel values are not clamped to the scale area.
6793 * @private
6794 */
6795 calculateBarValuePixels: function(datasetIndex, index) {
6796 var me = this;
6797 var chart = me.chart;
6798 var meta = me.getMeta();
6799 var scale = me.getValueScale();
6800 var datasets = chart.data.datasets;
6801 var value = scale.getRightValue(datasets[datasetIndex].data[index]);
6802 var stacked = scale.options.stacked;
6803 var stack = meta.stack;
6804 var start = 0;
6805 var i, imeta, ivalue, base, head, size;
6806
6807 if (stacked || (stacked === undefined && stack !== undefined)) {
6808 for (i = 0; i < datasetIndex; ++i) {
6809 imeta = chart.getDatasetMeta(i);
6810
6811 if (imeta.bar &&
6812 imeta.stack === stack &&
6813 imeta.controller.getValueScaleId() === scale.id &&
6814 chart.isDatasetVisible(i)) {
6815
6816 ivalue = scale.getRightValue(datasets[i].data[index]);
6817 if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
6818 start += ivalue;
6819 }
6820 }
6821 }
6822 }
6823
6824 base = scale.getPixelForValue(start);
6825 head = scale.getPixelForValue(start + value);
6826 size = (head - base) / 2;
6827
6828 return {
6829 size: size,
6830 base: base,
6831 head: head,
6832 center: head + size / 2
6833 };
6834 },
6835
6836 /**
6837 * @private
6838 */
6839 calculateBarIndexPixels: function(datasetIndex, index, ruler) {
6840 var me = this;
6841 var options = ruler.scale.options;
6842 var range = options.barThickness === 'flex'
6843 ? computeFlexCategoryTraits(index, ruler, options)
6844 : computeFitCategoryTraits(index, ruler, options);
6845
6846 var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
6847 var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
6848 var size = Math.min(
6849 helpers.valueOrDefault(options.maxBarThickness, Infinity),
6850 range.chunk * range.ratio);
6851
6852 return {
6853 base: center - size / 2,
6854 head: center + size / 2,
6855 center: center,
6856 size: size
6857 };
6858 },
6859
6860 draw: function() {
6861 var me = this;
6862 var chart = me.chart;
6863 var scale = me.getValueScale();
6864 var rects = me.getMeta().data;
6865 var dataset = me.getDataset();
6866 var ilen = rects.length;
6867 var i = 0;
6868
6869 helpers.canvas.clipArea(chart.ctx, chart.chartArea);
6870
6871 for (; i < ilen; ++i) {
6872 if (!isNaN(scale.getRightValue(dataset.data[i]))) {
6873 rects[i].draw();
6874 }
6875 }
6876
6877 helpers.canvas.unclipArea(chart.ctx);
6878 },
6879
6880 setHoverStyle: function(rectangle) {
6881 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
6882 var index = rectangle._index;
6883 var custom = rectangle.custom || {};
6884 var model = rectangle._model;
6885
6886 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
6887 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
6888 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
6889 },
6890
6891 removeHoverStyle: function(rectangle) {
6892 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
6893 var index = rectangle._index;
6894 var custom = rectangle.custom || {};
6895 var model = rectangle._model;
6896 var rectangleElementOptions = this.chart.options.elements.rectangle;
6897
6898 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
6899 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
6900 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
6901 }
6902 });
6903
6904 Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
6905 /**
6906 * @private
6907 */
6908 getValueScaleId: function() {
6909 return this.getMeta().xAxisID;
6910 },
6911
6912 /**
6913 * @private
6914 */
6915 getIndexScaleId: function() {
6916 return this.getMeta().yAxisID;
6917 }
6918 });
6919};
6920
6921},{"25":25,"40":40,"45":45}],16:[function(require,module,exports){
6922'use strict';
6923
6924var defaults = require(25);
6925var elements = require(40);
6926var helpers = require(45);
6927
6928defaults._set('bubble', {
6929 hover: {
6930 mode: 'single'
6931 },
6932
6933 scales: {
6934 xAxes: [{
6935 type: 'linear', // bubble should probably use a linear scale by default
6936 position: 'bottom',
6937 id: 'x-axis-0' // need an ID so datasets can reference the scale
6938 }],
6939 yAxes: [{
6940 type: 'linear',
6941 position: 'left',
6942 id: 'y-axis-0'
6943 }]
6944 },
6945
6946 tooltips: {
6947 callbacks: {
6948 title: function() {
6949 // Title doesn't make sense for scatter since we format the data as a point
6950 return '';
6951 },
6952 label: function(item, data) {
6953 var datasetLabel = data.datasets[item.datasetIndex].label || '';
6954 var dataPoint = data.datasets[item.datasetIndex].data[item.index];
6955 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
6956 }
6957 }
6958 }
6959});
6960
6961
6962module.exports = function(Chart) {
6963
6964 Chart.controllers.bubble = Chart.DatasetController.extend({
6965 /**
6966 * @protected
6967 */
6968 dataElementType: elements.Point,
6969
6970 /**
6971 * @protected
6972 */
6973 update: function(reset) {
6974 var me = this;
6975 var meta = me.getMeta();
6976 var points = meta.data;
6977
6978 // Update Points
6979 helpers.each(points, function(point, index) {
6980 me.updateElement(point, index, reset);
6981 });
6982 },
6983
6984 /**
6985 * @protected
6986 */
6987 updateElement: function(point, index, reset) {
6988 var me = this;
6989 var meta = me.getMeta();
6990 var custom = point.custom || {};
6991 var xScale = me.getScaleForId(meta.xAxisID);
6992 var yScale = me.getScaleForId(meta.yAxisID);
6993 var options = me._resolveElementOptions(point, index);
6994 var data = me.getDataset().data[index];
6995 var dsIndex = me.index;
6996
6997 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
6998 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
6999
7000 point._xScale = xScale;
7001 point._yScale = yScale;
7002 point._options = options;
7003 point._datasetIndex = dsIndex;
7004 point._index = index;
7005 point._model = {
7006 backgroundColor: options.backgroundColor,
7007 borderColor: options.borderColor,
7008 borderWidth: options.borderWidth,
7009 hitRadius: options.hitRadius,
7010 pointStyle: options.pointStyle,
7011 radius: reset ? 0 : options.radius,
7012 skip: custom.skip || isNaN(x) || isNaN(y),
7013 x: x,
7014 y: y,
7015 };
7016
7017 point.pivot();
7018 },
7019
7020 /**
7021 * @protected
7022 */
7023 setHoverStyle: function(point) {
7024 var model = point._model;
7025 var options = point._options;
7026
7027 model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
7028 model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
7029 model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
7030 model.radius = options.radius + options.hoverRadius;
7031 },
7032
7033 /**
7034 * @protected
7035 */
7036 removeHoverStyle: function(point) {
7037 var model = point._model;
7038 var options = point._options;
7039
7040 model.backgroundColor = options.backgroundColor;
7041 model.borderColor = options.borderColor;
7042 model.borderWidth = options.borderWidth;
7043 model.radius = options.radius;
7044 },
7045
7046 /**
7047 * @private
7048 */
7049 _resolveElementOptions: function(point, index) {
7050 var me = this;
7051 var chart = me.chart;
7052 var datasets = chart.data.datasets;
7053 var dataset = datasets[me.index];
7054 var custom = point.custom || {};
7055 var options = chart.options.elements.point;
7056 var resolve = helpers.options.resolve;
7057 var data = dataset.data[index];
7058 var values = {};
7059 var i, ilen, key;
7060
7061 // Scriptable options
7062 var context = {
7063 chart: chart,
7064 dataIndex: index,
7065 dataset: dataset,
7066 datasetIndex: me.index
7067 };
7068
7069 var keys = [
7070 'backgroundColor',
7071 'borderColor',
7072 'borderWidth',
7073 'hoverBackgroundColor',
7074 'hoverBorderColor',
7075 'hoverBorderWidth',
7076 'hoverRadius',
7077 'hitRadius',
7078 'pointStyle'
7079 ];
7080
7081 for (i = 0, ilen = keys.length; i < ilen; ++i) {
7082 key = keys[i];
7083 values[key] = resolve([
7084 custom[key],
7085 dataset[key],
7086 options[key]
7087 ], context, index);
7088 }
7089
7090 // Custom radius resolution
7091 values.radius = resolve([
7092 custom.radius,
7093 data ? data.r : undefined,
7094 dataset.radius,
7095 options.radius
7096 ], context, index);
7097
7098 return values;
7099 }
7100 });
7101};
7102
7103},{"25":25,"40":40,"45":45}],17:[function(require,module,exports){
7104'use strict';
7105
7106var defaults = require(25);
7107var elements = require(40);
7108var helpers = require(45);
7109
7110defaults._set('doughnut', {
7111 animation: {
7112 // Boolean - Whether we animate the rotation of the Doughnut
7113 animateRotate: true,
7114 // Boolean - Whether we animate scaling the Doughnut from the centre
7115 animateScale: false
7116 },
7117 hover: {
7118 mode: 'single'
7119 },
7120 legendCallback: function(chart) {
7121 var text = [];
7122 text.push('<ul class="' + chart.id + '-legend">');
7123
7124 var data = chart.data;
7125 var datasets = data.datasets;
7126 var labels = data.labels;
7127
7128 if (datasets.length) {
7129 for (var i = 0; i < datasets[0].data.length; ++i) {
7130 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
7131 if (labels[i]) {
7132 text.push(labels[i]);
7133 }
7134 text.push('</li>');
7135 }
7136 }
7137
7138 text.push('</ul>');
7139 return text.join('');
7140 },
7141 legend: {
7142 labels: {
7143 generateLabels: function(chart) {
7144 var data = chart.data;
7145 if (data.labels.length && data.datasets.length) {
7146 return data.labels.map(function(label, i) {
7147 var meta = chart.getDatasetMeta(0);
7148 var ds = data.datasets[0];
7149 var arc = meta.data[i];
7150 var custom = arc && arc.custom || {};
7151 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
7152 var arcOpts = chart.options.elements.arc;
7153 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
7154 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
7155 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
7156
7157 return {
7158 text: label,
7159 fillStyle: fill,
7160 strokeStyle: stroke,
7161 lineWidth: bw,
7162 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
7163
7164 // Extra data used for toggling the correct item
7165 index: i
7166 };
7167 });
7168 }
7169 return [];
7170 }
7171 },
7172
7173 onClick: function(e, legendItem) {
7174 var index = legendItem.index;
7175 var chart = this.chart;
7176 var i, ilen, meta;
7177
7178 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
7179 meta = chart.getDatasetMeta(i);
7180 // toggle visibility of index if exists
7181 if (meta.data[index]) {
7182 meta.data[index].hidden = !meta.data[index].hidden;
7183 }
7184 }
7185
7186 chart.update();
7187 }
7188 },
7189
7190 // The percentage of the chart that we cut out of the middle.
7191 cutoutPercentage: 50,
7192
7193 // The rotation of the chart, where the first data arc begins.
7194 rotation: Math.PI * -0.5,
7195
7196 // The total circumference of the chart.
7197 circumference: Math.PI * 2.0,
7198
7199 // Need to override these to give a nice default
7200 tooltips: {
7201 callbacks: {
7202 title: function() {
7203 return '';
7204 },
7205 label: function(tooltipItem, data) {
7206 var dataLabel = data.labels[tooltipItem.index];
7207 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
7208
7209 if (helpers.isArray(dataLabel)) {
7210 // show value on first line of multiline label
7211 // need to clone because we are changing the value
7212 dataLabel = dataLabel.slice();
7213 dataLabel[0] += value;
7214 } else {
7215 dataLabel += value;
7216 }
7217
7218 return dataLabel;
7219 }
7220 }
7221 }
7222});
7223
7224defaults._set('pie', helpers.clone(defaults.doughnut));
7225defaults._set('pie', {
7226 cutoutPercentage: 0
7227});
7228
7229module.exports = function(Chart) {
7230
7231 Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
7232
7233 dataElementType: elements.Arc,
7234
7235 linkScales: helpers.noop,
7236
7237 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
7238 getRingIndex: function(datasetIndex) {
7239 var ringIndex = 0;
7240
7241 for (var j = 0; j < datasetIndex; ++j) {
7242 if (this.chart.isDatasetVisible(j)) {
7243 ++ringIndex;
7244 }
7245 }
7246
7247 return ringIndex;
7248 },
7249
7250 update: function(reset) {
7251 var me = this;
7252 var chart = me.chart;
7253 var chartArea = chart.chartArea;
7254 var opts = chart.options;
7255 var arcOpts = opts.elements.arc;
7256 var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
7257 var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
7258 var minSize = Math.min(availableWidth, availableHeight);
7259 var offset = {x: 0, y: 0};
7260 var meta = me.getMeta();
7261 var cutoutPercentage = opts.cutoutPercentage;
7262 var circumference = opts.circumference;
7263
7264 // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
7265 if (circumference < Math.PI * 2.0) {
7266 var startAngle = opts.rotation % (Math.PI * 2.0);
7267 startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
7268 var endAngle = startAngle + circumference;
7269 var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
7270 var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
7271 var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
7272 var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
7273 var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
7274 var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
7275 var cutout = cutoutPercentage / 100.0;
7276 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))};
7277 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))};
7278 var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
7279 minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
7280 offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
7281 }
7282
7283 chart.borderWidth = me.getMaxBorderWidth(meta.data);
7284 chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
7285 chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
7286 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
7287 chart.offsetX = offset.x * chart.outerRadius;
7288 chart.offsetY = offset.y * chart.outerRadius;
7289
7290 meta.total = me.calculateTotal();
7291
7292 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
7293 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
7294
7295 helpers.each(meta.data, function(arc, index) {
7296 me.updateElement(arc, index, reset);
7297 });
7298 },
7299
7300 updateElement: function(arc, index, reset) {
7301 var me = this;
7302 var chart = me.chart;
7303 var chartArea = chart.chartArea;
7304 var opts = chart.options;
7305 var animationOpts = opts.animation;
7306 var centerX = (chartArea.left + chartArea.right) / 2;
7307 var centerY = (chartArea.top + chartArea.bottom) / 2;
7308 var startAngle = opts.rotation; // non reset case handled later
7309 var endAngle = opts.rotation; // non reset case handled later
7310 var dataset = me.getDataset();
7311 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
7312 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
7313 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
7314 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
7315
7316 helpers.extend(arc, {
7317 // Utility
7318 _datasetIndex: me.index,
7319 _index: index,
7320
7321 // Desired view properties
7322 _model: {
7323 x: centerX + chart.offsetX,
7324 y: centerY + chart.offsetY,
7325 startAngle: startAngle,
7326 endAngle: endAngle,
7327 circumference: circumference,
7328 outerRadius: outerRadius,
7329 innerRadius: innerRadius,
7330 label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
7331 }
7332 });
7333
7334 var model = arc._model;
7335 // Resets the visual styles
7336 this.removeHoverStyle(arc);
7337
7338 // Set correct angles if not resetting
7339 if (!reset || !animationOpts.animateRotate) {
7340 if (index === 0) {
7341 model.startAngle = opts.rotation;
7342 } else {
7343 model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
7344 }
7345
7346 model.endAngle = model.startAngle + model.circumference;
7347 }
7348
7349 arc.pivot();
7350 },
7351
7352 removeHoverStyle: function(arc) {
7353 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
7354 },
7355
7356 calculateTotal: function() {
7357 var dataset = this.getDataset();
7358 var meta = this.getMeta();
7359 var total = 0;
7360 var value;
7361
7362 helpers.each(meta.data, function(element, index) {
7363 value = dataset.data[index];
7364 if (!isNaN(value) && !element.hidden) {
7365 total += Math.abs(value);
7366 }
7367 });
7368
7369 /* if (total === 0) {
7370 total = NaN;
7371 }*/
7372
7373 return total;
7374 },
7375
7376 calculateCircumference: function(value) {
7377 var total = this.getMeta().total;
7378 if (total > 0 && !isNaN(value)) {
7379 return (Math.PI * 2.0) * (Math.abs(value) / total);
7380 }
7381 return 0;
7382 },
7383
7384 // gets the max border or hover width to properly scale pie charts
7385 getMaxBorderWidth: function(arcs) {
7386 var max = 0;
7387 var index = this.index;
7388 var length = arcs.length;
7389 var borderWidth;
7390 var hoverWidth;
7391
7392 for (var i = 0; i < length; i++) {
7393 borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
7394 hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
7395
7396 max = borderWidth > max ? borderWidth : max;
7397 max = hoverWidth > max ? hoverWidth : max;
7398 }
7399 return max;
7400 }
7401 });
7402};
7403
7404},{"25":25,"40":40,"45":45}],18:[function(require,module,exports){
7405'use strict';
7406
7407var defaults = require(25);
7408var elements = require(40);
7409var helpers = require(45);
7410
7411defaults._set('line', {
7412 showLines: true,
7413 spanGaps: false,
7414
7415 hover: {
7416 mode: 'label'
7417 },
7418
7419 scales: {
7420 xAxes: [{
7421 type: 'category',
7422 id: 'x-axis-0'
7423 }],
7424 yAxes: [{
7425 type: 'linear',
7426 id: 'y-axis-0'
7427 }]
7428 }
7429});
7430
7431module.exports = function(Chart) {
7432
7433 function lineEnabled(dataset, options) {
7434 return helpers.valueOrDefault(dataset.showLine, options.showLines);
7435 }
7436
7437 Chart.controllers.line = Chart.DatasetController.extend({
7438
7439 datasetElementType: elements.Line,
7440
7441 dataElementType: elements.Point,
7442
7443 update: function(reset) {
7444 var me = this;
7445 var meta = me.getMeta();
7446 var line = meta.dataset;
7447 var points = meta.data || [];
7448 var options = me.chart.options;
7449 var lineElementOptions = options.elements.line;
7450 var scale = me.getScaleForId(meta.yAxisID);
7451 var i, ilen, custom;
7452 var dataset = me.getDataset();
7453 var showLine = lineEnabled(dataset, options);
7454
7455 // Update Line
7456 if (showLine) {
7457 custom = line.custom || {};
7458
7459 // Compatibility: If the properties are defined with only the old name, use those values
7460 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
7461 dataset.lineTension = dataset.tension;
7462 }
7463
7464 // Utility
7465 line._scale = scale;
7466 line._datasetIndex = me.index;
7467 // Data
7468 line._children = points;
7469 // Model
7470 line._model = {
7471 // Appearance
7472 // The default behavior of lines is to break at null values, according
7473 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
7474 // This option gives lines the ability to span gaps
7475 spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
7476 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
7477 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
7478 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
7479 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
7480 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
7481 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
7482 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
7483 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
7484 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
7485 steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
7486 cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
7487 };
7488
7489 line.pivot();
7490 }
7491
7492 // Update Points
7493 for (i = 0, ilen = points.length; i < ilen; ++i) {
7494 me.updateElement(points[i], i, reset);
7495 }
7496
7497 if (showLine && line._model.tension !== 0) {
7498 me.updateBezierControlPoints();
7499 }
7500
7501 // Now pivot the point for animation
7502 for (i = 0, ilen = points.length; i < ilen; ++i) {
7503 points[i].pivot();
7504 }
7505 },
7506
7507 getPointBackgroundColor: function(point, index) {
7508 var backgroundColor = this.chart.options.elements.point.backgroundColor;
7509 var dataset = this.getDataset();
7510 var custom = point.custom || {};
7511
7512 if (custom.backgroundColor) {
7513 backgroundColor = custom.backgroundColor;
7514 } else if (dataset.pointBackgroundColor) {
7515 backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
7516 } else if (dataset.backgroundColor) {
7517 backgroundColor = dataset.backgroundColor;
7518 }
7519
7520 return backgroundColor;
7521 },
7522
7523 getPointBorderColor: function(point, index) {
7524 var borderColor = this.chart.options.elements.point.borderColor;
7525 var dataset = this.getDataset();
7526 var custom = point.custom || {};
7527
7528 if (custom.borderColor) {
7529 borderColor = custom.borderColor;
7530 } else if (dataset.pointBorderColor) {
7531 borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
7532 } else if (dataset.borderColor) {
7533 borderColor = dataset.borderColor;
7534 }
7535
7536 return borderColor;
7537 },
7538
7539 getPointBorderWidth: function(point, index) {
7540 var borderWidth = this.chart.options.elements.point.borderWidth;
7541 var dataset = this.getDataset();
7542 var custom = point.custom || {};
7543
7544 if (!isNaN(custom.borderWidth)) {
7545 borderWidth = custom.borderWidth;
7546 } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
7547 borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
7548 } else if (!isNaN(dataset.borderWidth)) {
7549 borderWidth = dataset.borderWidth;
7550 }
7551
7552 return borderWidth;
7553 },
7554
7555 updateElement: function(point, index, reset) {
7556 var me = this;
7557 var meta = me.getMeta();
7558 var custom = point.custom || {};
7559 var dataset = me.getDataset();
7560 var datasetIndex = me.index;
7561 var value = dataset.data[index];
7562 var yScale = me.getScaleForId(meta.yAxisID);
7563 var xScale = me.getScaleForId(meta.xAxisID);
7564 var pointOptions = me.chart.options.elements.point;
7565 var x, y;
7566
7567 // Compatibility: If the properties are defined with only the old name, use those values
7568 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
7569 dataset.pointRadius = dataset.radius;
7570 }
7571 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
7572 dataset.pointHitRadius = dataset.hitRadius;
7573 }
7574
7575 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
7576 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
7577
7578 // Utility
7579 point._xScale = xScale;
7580 point._yScale = yScale;
7581 point._datasetIndex = datasetIndex;
7582 point._index = index;
7583
7584 // Desired view properties
7585 point._model = {
7586 x: x,
7587 y: y,
7588 skip: custom.skip || isNaN(x) || isNaN(y),
7589 // Appearance
7590 radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
7591 pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
7592 backgroundColor: me.getPointBackgroundColor(point, index),
7593 borderColor: me.getPointBorderColor(point, index),
7594 borderWidth: me.getPointBorderWidth(point, index),
7595 tension: meta.dataset._model ? meta.dataset._model.tension : 0,
7596 steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
7597 // Tooltip
7598 hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
7599 };
7600 },
7601
7602 calculatePointY: function(value, index, datasetIndex) {
7603 var me = this;
7604 var chart = me.chart;
7605 var meta = me.getMeta();
7606 var yScale = me.getScaleForId(meta.yAxisID);
7607 var sumPos = 0;
7608 var sumNeg = 0;
7609 var i, ds, dsMeta;
7610
7611 if (yScale.options.stacked) {
7612 for (i = 0; i < datasetIndex; i++) {
7613 ds = chart.data.datasets[i];
7614 dsMeta = chart.getDatasetMeta(i);
7615 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
7616 var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
7617 if (stackedRightValue < 0) {
7618 sumNeg += stackedRightValue || 0;
7619 } else {
7620 sumPos += stackedRightValue || 0;
7621 }
7622 }
7623 }
7624
7625 var rightValue = Number(yScale.getRightValue(value));
7626 if (rightValue < 0) {
7627 return yScale.getPixelForValue(sumNeg + rightValue);
7628 }
7629 return yScale.getPixelForValue(sumPos + rightValue);
7630 }
7631
7632 return yScale.getPixelForValue(value);
7633 },
7634
7635 updateBezierControlPoints: function() {
7636 var me = this;
7637 var meta = me.getMeta();
7638 var area = me.chart.chartArea;
7639 var points = (meta.data || []);
7640 var i, ilen, point, model, controlPoints;
7641
7642 // Only consider points that are drawn in case the spanGaps option is used
7643 if (meta.dataset._model.spanGaps) {
7644 points = points.filter(function(pt) {
7645 return !pt._model.skip;
7646 });
7647 }
7648
7649 function capControlPoint(pt, min, max) {
7650 return Math.max(Math.min(pt, max), min);
7651 }
7652
7653 if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
7654 helpers.splineCurveMonotone(points);
7655 } else {
7656 for (i = 0, ilen = points.length; i < ilen; ++i) {
7657 point = points[i];
7658 model = point._model;
7659 controlPoints = helpers.splineCurve(
7660 helpers.previousItem(points, i)._model,
7661 model,
7662 helpers.nextItem(points, i)._model,
7663 meta.dataset._model.tension
7664 );
7665 model.controlPointPreviousX = controlPoints.previous.x;
7666 model.controlPointPreviousY = controlPoints.previous.y;
7667 model.controlPointNextX = controlPoints.next.x;
7668 model.controlPointNextY = controlPoints.next.y;
7669 }
7670 }
7671
7672 if (me.chart.options.elements.line.capBezierPoints) {
7673 for (i = 0, ilen = points.length; i < ilen; ++i) {
7674 model = points[i]._model;
7675 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
7676 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
7677 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
7678 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
7679 }
7680 }
7681 },
7682
7683 draw: function() {
7684 var me = this;
7685 var chart = me.chart;
7686 var meta = me.getMeta();
7687 var points = meta.data || [];
7688 var area = chart.chartArea;
7689 var ilen = points.length;
7690 var i = 0;
7691
7692 helpers.canvas.clipArea(chart.ctx, area);
7693
7694 if (lineEnabled(me.getDataset(), chart.options)) {
7695 meta.dataset.draw();
7696 }
7697
7698 helpers.canvas.unclipArea(chart.ctx);
7699
7700 // Draw the points
7701 for (; i < ilen; ++i) {
7702 points[i].draw(area);
7703 }
7704 },
7705
7706 setHoverStyle: function(point) {
7707 // Point
7708 var dataset = this.chart.data.datasets[point._datasetIndex];
7709 var index = point._index;
7710 var custom = point.custom || {};
7711 var model = point._model;
7712
7713 model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
7714 model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
7715 model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
7716 model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
7717 },
7718
7719 removeHoverStyle: function(point) {
7720 var me = this;
7721 var dataset = me.chart.data.datasets[point._datasetIndex];
7722 var index = point._index;
7723 var custom = point.custom || {};
7724 var model = point._model;
7725
7726 // Compatibility: If the properties are defined with only the old name, use those values
7727 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
7728 dataset.pointRadius = dataset.radius;
7729 }
7730
7731 model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
7732 model.backgroundColor = me.getPointBackgroundColor(point, index);
7733 model.borderColor = me.getPointBorderColor(point, index);
7734 model.borderWidth = me.getPointBorderWidth(point, index);
7735 }
7736 });
7737};
7738
7739},{"25":25,"40":40,"45":45}],19:[function(require,module,exports){
7740'use strict';
7741
7742var defaults = require(25);
7743var elements = require(40);
7744var helpers = require(45);
7745
7746defaults._set('polarArea', {
7747 scale: {
7748 type: 'radialLinear',
7749 angleLines: {
7750 display: false
7751 },
7752 gridLines: {
7753 circular: true
7754 },
7755 pointLabels: {
7756 display: false
7757 },
7758 ticks: {
7759 beginAtZero: true
7760 }
7761 },
7762
7763 // Boolean - Whether to animate the rotation of the chart
7764 animation: {
7765 animateRotate: true,
7766 animateScale: true
7767 },
7768
7769 startAngle: -0.5 * Math.PI,
7770 legendCallback: function(chart) {
7771 var text = [];
7772 text.push('<ul class="' + chart.id + '-legend">');
7773
7774 var data = chart.data;
7775 var datasets = data.datasets;
7776 var labels = data.labels;
7777
7778 if (datasets.length) {
7779 for (var i = 0; i < datasets[0].data.length; ++i) {
7780 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
7781 if (labels[i]) {
7782 text.push(labels[i]);
7783 }
7784 text.push('</li>');
7785 }
7786 }
7787
7788 text.push('</ul>');
7789 return text.join('');
7790 },
7791 legend: {
7792 labels: {
7793 generateLabels: function(chart) {
7794 var data = chart.data;
7795 if (data.labels.length && data.datasets.length) {
7796 return data.labels.map(function(label, i) {
7797 var meta = chart.getDatasetMeta(0);
7798 var ds = data.datasets[0];
7799 var arc = meta.data[i];
7800 var custom = arc.custom || {};
7801 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
7802 var arcOpts = chart.options.elements.arc;
7803 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
7804 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
7805 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
7806
7807 return {
7808 text: label,
7809 fillStyle: fill,
7810 strokeStyle: stroke,
7811 lineWidth: bw,
7812 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
7813
7814 // Extra data used for toggling the correct item
7815 index: i
7816 };
7817 });
7818 }
7819 return [];
7820 }
7821 },
7822
7823 onClick: function(e, legendItem) {
7824 var index = legendItem.index;
7825 var chart = this.chart;
7826 var i, ilen, meta;
7827
7828 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
7829 meta = chart.getDatasetMeta(i);
7830 meta.data[index].hidden = !meta.data[index].hidden;
7831 }
7832
7833 chart.update();
7834 }
7835 },
7836
7837 // Need to override these to give a nice default
7838 tooltips: {
7839 callbacks: {
7840 title: function() {
7841 return '';
7842 },
7843 label: function(item, data) {
7844 return data.labels[item.index] + ': ' + item.yLabel;
7845 }
7846 }
7847 }
7848});
7849
7850module.exports = function(Chart) {
7851
7852 Chart.controllers.polarArea = Chart.DatasetController.extend({
7853
7854 dataElementType: elements.Arc,
7855
7856 linkScales: helpers.noop,
7857
7858 update: function(reset) {
7859 var me = this;
7860 var chart = me.chart;
7861 var chartArea = chart.chartArea;
7862 var meta = me.getMeta();
7863 var opts = chart.options;
7864 var arcOpts = opts.elements.arc;
7865 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
7866 chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
7867 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
7868 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
7869
7870 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
7871 me.innerRadius = me.outerRadius - chart.radiusLength;
7872
7873 meta.count = me.countVisibleElements();
7874
7875 helpers.each(meta.data, function(arc, index) {
7876 me.updateElement(arc, index, reset);
7877 });
7878 },
7879
7880 updateElement: function(arc, index, reset) {
7881 var me = this;
7882 var chart = me.chart;
7883 var dataset = me.getDataset();
7884 var opts = chart.options;
7885 var animationOpts = opts.animation;
7886 var scale = chart.scale;
7887 var labels = chart.data.labels;
7888
7889 var circumference = me.calculateCircumference(dataset.data[index]);
7890 var centerX = scale.xCenter;
7891 var centerY = scale.yCenter;
7892
7893 // If there is NaN data before us, we need to calculate the starting angle correctly.
7894 // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
7895 var visibleCount = 0;
7896 var meta = me.getMeta();
7897 for (var i = 0; i < index; ++i) {
7898 if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
7899 ++visibleCount;
7900 }
7901 }
7902
7903 // var negHalfPI = -0.5 * Math.PI;
7904 var datasetStartAngle = opts.startAngle;
7905 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
7906 var startAngle = datasetStartAngle + (circumference * visibleCount);
7907 var endAngle = startAngle + (arc.hidden ? 0 : circumference);
7908
7909 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
7910
7911 helpers.extend(arc, {
7912 // Utility
7913 _datasetIndex: me.index,
7914 _index: index,
7915 _scale: scale,
7916
7917 // Desired view properties
7918 _model: {
7919 x: centerX,
7920 y: centerY,
7921 innerRadius: 0,
7922 outerRadius: reset ? resetRadius : distance,
7923 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
7924 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
7925 label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
7926 }
7927 });
7928
7929 // Apply border and fill style
7930 me.removeHoverStyle(arc);
7931
7932 arc.pivot();
7933 },
7934
7935 removeHoverStyle: function(arc) {
7936 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
7937 },
7938
7939 countVisibleElements: function() {
7940 var dataset = this.getDataset();
7941 var meta = this.getMeta();
7942 var count = 0;
7943
7944 helpers.each(meta.data, function(element, index) {
7945 if (!isNaN(dataset.data[index]) && !element.hidden) {
7946 count++;
7947 }
7948 });
7949
7950 return count;
7951 },
7952
7953 calculateCircumference: function(value) {
7954 var count = this.getMeta().count;
7955 if (count > 0 && !isNaN(value)) {
7956 return (2 * Math.PI) / count;
7957 }
7958 return 0;
7959 }
7960 });
7961};
7962
7963},{"25":25,"40":40,"45":45}],20:[function(require,module,exports){
7964'use strict';
7965
7966var defaults = require(25);
7967var elements = require(40);
7968var helpers = require(45);
7969
7970defaults._set('radar', {
7971 scale: {
7972 type: 'radialLinear'
7973 },
7974 elements: {
7975 line: {
7976 tension: 0 // no bezier in radar
7977 }
7978 }
7979});
7980
7981module.exports = function(Chart) {
7982
7983 Chart.controllers.radar = Chart.DatasetController.extend({
7984
7985 datasetElementType: elements.Line,
7986
7987 dataElementType: elements.Point,
7988
7989 linkScales: helpers.noop,
7990
7991 update: function(reset) {
7992 var me = this;
7993 var meta = me.getMeta();
7994 var line = meta.dataset;
7995 var points = meta.data;
7996 var custom = line.custom || {};
7997 var dataset = me.getDataset();
7998 var lineElementOptions = me.chart.options.elements.line;
7999 var scale = me.chart.scale;
8000
8001 // Compatibility: If the properties are defined with only the old name, use those values
8002 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
8003 dataset.lineTension = dataset.tension;
8004 }
8005
8006 helpers.extend(meta.dataset, {
8007 // Utility
8008 _datasetIndex: me.index,
8009 _scale: scale,
8010 // Data
8011 _children: points,
8012 _loop: true,
8013 // Model
8014 _model: {
8015 // Appearance
8016 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
8017 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
8018 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
8019 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
8020 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
8021 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
8022 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
8023 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
8024 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
8025 }
8026 });
8027
8028 meta.dataset.pivot();
8029
8030 // Update Points
8031 helpers.each(points, function(point, index) {
8032 me.updateElement(point, index, reset);
8033 }, me);
8034
8035 // Update bezier control points
8036 me.updateBezierControlPoints();
8037 },
8038 updateElement: function(point, index, reset) {
8039 var me = this;
8040 var custom = point.custom || {};
8041 var dataset = me.getDataset();
8042 var scale = me.chart.scale;
8043 var pointElementOptions = me.chart.options.elements.point;
8044 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
8045
8046 // Compatibility: If the properties are defined with only the old name, use those values
8047 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
8048 dataset.pointRadius = dataset.radius;
8049 }
8050 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
8051 dataset.pointHitRadius = dataset.hitRadius;
8052 }
8053
8054 helpers.extend(point, {
8055 // Utility
8056 _datasetIndex: me.index,
8057 _index: index,
8058 _scale: scale,
8059
8060 // Desired view properties
8061 _model: {
8062 x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
8063 y: reset ? scale.yCenter : pointPosition.y,
8064
8065 // Appearance
8066 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
8067 radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
8068 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
8069 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
8070 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
8071 pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
8072
8073 // Tooltip
8074 hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
8075 }
8076 });
8077
8078 point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
8079 },
8080 updateBezierControlPoints: function() {
8081 var chartArea = this.chart.chartArea;
8082 var meta = this.getMeta();
8083
8084 helpers.each(meta.data, function(point, index) {
8085 var model = point._model;
8086 var controlPoints = helpers.splineCurve(
8087 helpers.previousItem(meta.data, index, true)._model,
8088 model,
8089 helpers.nextItem(meta.data, index, true)._model,
8090 model.tension
8091 );
8092
8093 // Prevent the bezier going outside of the bounds of the graph
8094 model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
8095 model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
8096
8097 model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
8098 model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
8099
8100 // Now pivot the point for animation
8101 point.pivot();
8102 });
8103 },
8104
8105 setHoverStyle: function(point) {
8106 // Point
8107 var dataset = this.chart.data.datasets[point._datasetIndex];
8108 var custom = point.custom || {};
8109 var index = point._index;
8110 var model = point._model;
8111
8112 model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
8113 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
8114 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
8115 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
8116 },
8117
8118 removeHoverStyle: function(point) {
8119 var dataset = this.chart.data.datasets[point._datasetIndex];
8120 var custom = point.custom || {};
8121 var index = point._index;
8122 var model = point._model;
8123 var pointElementOptions = this.chart.options.elements.point;
8124
8125 model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius);
8126 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
8127 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
8128 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
8129 }
8130 });
8131};
8132
8133},{"25":25,"40":40,"45":45}],21:[function(require,module,exports){
8134'use strict';
8135
8136var defaults = require(25);
8137
8138defaults._set('scatter', {
8139 hover: {
8140 mode: 'single'
8141 },
8142
8143 scales: {
8144 xAxes: [{
8145 id: 'x-axis-1', // need an ID so datasets can reference the scale
8146 type: 'linear', // scatter should not use a category axis
8147 position: 'bottom'
8148 }],
8149 yAxes: [{
8150 id: 'y-axis-1',
8151 type: 'linear',
8152 position: 'left'
8153 }]
8154 },
8155
8156 showLines: false,
8157
8158 tooltips: {
8159 callbacks: {
8160 title: function() {
8161 return ''; // doesn't make sense for scatter since data are formatted as a point
8162 },
8163 label: function(item) {
8164 return '(' + item.xLabel + ', ' + item.yLabel + ')';
8165 }
8166 }
8167 }
8168});
8169
8170module.exports = function(Chart) {
8171
8172 // Scatter charts use line controllers
8173 Chart.controllers.scatter = Chart.controllers.line;
8174
8175};
8176
8177},{"25":25}],22:[function(require,module,exports){
8178/* global window: false */
8179'use strict';
8180
8181var defaults = require(25);
8182var Element = require(26);
8183var helpers = require(45);
8184
8185defaults._set('global', {
8186 animation: {
8187 duration: 1000,
8188 easing: 'easeOutQuart',
8189 onProgress: helpers.noop,
8190 onComplete: helpers.noop
8191 }
8192});
8193
8194module.exports = function(Chart) {
8195
8196 Chart.Animation = Element.extend({
8197 chart: null, // the animation associated chart instance
8198 currentStep: 0, // the current animation step
8199 numSteps: 60, // default number of steps
8200 easing: '', // the easing to use for this animation
8201 render: null, // render function used by the animation service
8202
8203 onAnimationProgress: null, // user specified callback to fire on each step of the animation
8204 onAnimationComplete: null, // user specified callback to fire when the animation finishes
8205 });
8206
8207 Chart.animationService = {
8208 frameDuration: 17,
8209 animations: [],
8210 dropFrames: 0,
8211 request: null,
8212
8213 /**
8214 * @param {Chart} chart - The chart to animate.
8215 * @param {Chart.Animation} animation - The animation that we will animate.
8216 * @param {Number} duration - The animation duration in ms.
8217 * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
8218 */
8219 addAnimation: function(chart, animation, duration, lazy) {
8220 var animations = this.animations;
8221 var i, ilen;
8222
8223 animation.chart = chart;
8224
8225 if (!lazy) {
8226 chart.animating = true;
8227 }
8228
8229 for (i = 0, ilen = animations.length; i < ilen; ++i) {
8230 if (animations[i].chart === chart) {
8231 animations[i] = animation;
8232 return;
8233 }
8234 }
8235
8236 animations.push(animation);
8237
8238 // If there are no animations queued, manually kickstart a digest, for lack of a better word
8239 if (animations.length === 1) {
8240 this.requestAnimationFrame();
8241 }
8242 },
8243
8244 cancelAnimation: function(chart) {
8245 var index = helpers.findIndex(this.animations, function(animation) {
8246 return animation.chart === chart;
8247 });
8248
8249 if (index !== -1) {
8250 this.animations.splice(index, 1);
8251 chart.animating = false;
8252 }
8253 },
8254
8255 requestAnimationFrame: function() {
8256 var me = this;
8257 if (me.request === null) {
8258 // Skip animation frame requests until the active one is executed.
8259 // This can happen when processing mouse events, e.g. 'mousemove'
8260 // and 'mouseout' events will trigger multiple renders.
8261 me.request = helpers.requestAnimFrame.call(window, function() {
8262 me.request = null;
8263 me.startDigest();
8264 });
8265 }
8266 },
8267
8268 /**
8269 * @private
8270 */
8271 startDigest: function() {
8272 var me = this;
8273 var startTime = Date.now();
8274 var framesToDrop = 0;
8275
8276 if (me.dropFrames > 1) {
8277 framesToDrop = Math.floor(me.dropFrames);
8278 me.dropFrames = me.dropFrames % 1;
8279 }
8280
8281 me.advance(1 + framesToDrop);
8282
8283 var endTime = Date.now();
8284
8285 me.dropFrames += (endTime - startTime) / me.frameDuration;
8286
8287 // Do we have more stuff to animate?
8288 if (me.animations.length > 0) {
8289 me.requestAnimationFrame();
8290 }
8291 },
8292
8293 /**
8294 * @private
8295 */
8296 advance: function(count) {
8297 var animations = this.animations;
8298 var animation, chart;
8299 var i = 0;
8300
8301 while (i < animations.length) {
8302 animation = animations[i];
8303 chart = animation.chart;
8304
8305 animation.currentStep = (animation.currentStep || 0) + count;
8306 animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
8307
8308 helpers.callback(animation.render, [chart, animation], chart);
8309 helpers.callback(animation.onAnimationProgress, [animation], chart);
8310
8311 if (animation.currentStep >= animation.numSteps) {
8312 helpers.callback(animation.onAnimationComplete, [animation], chart);
8313 chart.animating = false;
8314 animations.splice(i, 1);
8315 } else {
8316 ++i;
8317 }
8318 }
8319 }
8320 };
8321
8322 /**
8323 * Provided for backward compatibility, use Chart.Animation instead
8324 * @prop Chart.Animation#animationObject
8325 * @deprecated since version 2.6.0
8326 * @todo remove at version 3
8327 */
8328 Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
8329 get: function() {
8330 return this;
8331 }
8332 });
8333
8334 /**
8335 * Provided for backward compatibility, use Chart.Animation#chart instead
8336 * @prop Chart.Animation#chartInstance
8337 * @deprecated since version 2.6.0
8338 * @todo remove at version 3
8339 */
8340 Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
8341 get: function() {
8342 return this.chart;
8343 },
8344 set: function(value) {
8345 this.chart = value;
8346 }
8347 });
8348
8349};
8350
8351},{"25":25,"26":26,"45":45}],23:[function(require,module,exports){
8352'use strict';
8353
8354var defaults = require(25);
8355var helpers = require(45);
8356var Interaction = require(28);
8357var layouts = require(30);
8358var platform = require(48);
8359var plugins = require(31);
8360
8361module.exports = function(Chart) {
8362
8363 // Create a dictionary of chart types, to allow for extension of existing types
8364 Chart.types = {};
8365
8366 // Store a reference to each instance - allowing us to globally resize chart instances on window resize.
8367 // Destroy method on the chart will remove the instance of the chart from this reference.
8368 Chart.instances = {};
8369
8370 // Controllers available for dataset visualization eg. bar, line, slice, etc.
8371 Chart.controllers = {};
8372
8373 /**
8374 * Initializes the given config with global and chart default values.
8375 */
8376 function initConfig(config) {
8377 config = config || {};
8378
8379 // Do NOT use configMerge() for the data object because this method merges arrays
8380 // and so would change references to labels and datasets, preventing data updates.
8381 var data = config.data = config.data || {};
8382 data.datasets = data.datasets || [];
8383 data.labels = data.labels || [];
8384
8385 config.options = helpers.configMerge(
8386 defaults.global,
8387 defaults[config.type],
8388 config.options || {});
8389
8390 return config;
8391 }
8392
8393 /**
8394 * Updates the config of the chart
8395 * @param chart {Chart} chart to update the options for
8396 */
8397 function updateConfig(chart) {
8398 var newOptions = chart.options;
8399
8400 helpers.each(chart.scales, function(scale) {
8401 layouts.removeBox(chart, scale);
8402 });
8403
8404 newOptions = helpers.configMerge(
8405 Chart.defaults.global,
8406 Chart.defaults[chart.config.type],
8407 newOptions);
8408
8409 chart.options = chart.config.options = newOptions;
8410 chart.ensureScalesHaveIDs();
8411 chart.buildOrUpdateScales();
8412 // Tooltip
8413 chart.tooltip._options = newOptions.tooltips;
8414 chart.tooltip.initialize();
8415 }
8416
8417 function positionIsHorizontal(position) {
8418 return position === 'top' || position === 'bottom';
8419 }
8420
8421 helpers.extend(Chart.prototype, /** @lends Chart */ {
8422 /**
8423 * @private
8424 */
8425 construct: function(item, config) {
8426 var me = this;
8427
8428 config = initConfig(config);
8429
8430 var context = platform.acquireContext(item, config);
8431 var canvas = context && context.canvas;
8432 var height = canvas && canvas.height;
8433 var width = canvas && canvas.width;
8434
8435 me.id = helpers.uid();
8436 me.ctx = context;
8437 me.canvas = canvas;
8438 me.config = config;
8439 me.width = width;
8440 me.height = height;
8441 me.aspectRatio = height ? width / height : null;
8442 me.options = config.options;
8443 me._bufferedRender = false;
8444
8445 /**
8446 * Provided for backward compatibility, Chart and Chart.Controller have been merged,
8447 * the "instance" still need to be defined since it might be called from plugins.
8448 * @prop Chart#chart
8449 * @deprecated since version 2.6.0
8450 * @todo remove at version 3
8451 * @private
8452 */
8453 me.chart = me;
8454 me.controller = me; // chart.chart.controller #inception
8455
8456 // Add the chart instance to the global namespace
8457 Chart.instances[me.id] = me;
8458
8459 // Define alias to the config data: `chart.data === chart.config.data`
8460 Object.defineProperty(me, 'data', {
8461 get: function() {
8462 return me.config.data;
8463 },
8464 set: function(value) {
8465 me.config.data = value;
8466 }
8467 });
8468
8469 if (!context || !canvas) {
8470 // The given item is not a compatible context2d element, let's return before finalizing
8471 // the chart initialization but after setting basic chart / controller properties that
8472 // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
8473 // https://github.com/chartjs/Chart.js/issues/2807
8474 console.error("Failed to create chart: can't acquire context from the given item");
8475 return;
8476 }
8477
8478 me.initialize();
8479 me.update();
8480 },
8481
8482 /**
8483 * @private
8484 */
8485 initialize: function() {
8486 var me = this;
8487
8488 // Before init plugin notification
8489 plugins.notify(me, 'beforeInit');
8490
8491 helpers.retinaScale(me, me.options.devicePixelRatio);
8492
8493 me.bindEvents();
8494
8495 if (me.options.responsive) {
8496 // Initial resize before chart draws (must be silent to preserve initial animations).
8497 me.resize(true);
8498 }
8499
8500 // Make sure scales have IDs and are built before we build any controllers.
8501 me.ensureScalesHaveIDs();
8502 me.buildOrUpdateScales();
8503 me.initToolTip();
8504
8505 // After init plugin notification
8506 plugins.notify(me, 'afterInit');
8507
8508 return me;
8509 },
8510
8511 clear: function() {
8512 helpers.canvas.clear(this);
8513 return this;
8514 },
8515
8516 stop: function() {
8517 // Stops any current animation loop occurring
8518 Chart.animationService.cancelAnimation(this);
8519 return this;
8520 },
8521
8522 resize: function(silent) {
8523 var me = this;
8524 var options = me.options;
8525 var canvas = me.canvas;
8526 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
8527
8528 // the canvas render width and height will be casted to integers so make sure that
8529 // the canvas display style uses the same integer values to avoid blurring effect.
8530
8531 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased
8532 var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
8533 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
8534
8535 if (me.width === newWidth && me.height === newHeight) {
8536 return;
8537 }
8538
8539 canvas.width = me.width = newWidth;
8540 canvas.height = me.height = newHeight;
8541 canvas.style.width = newWidth + 'px';
8542 canvas.style.height = newHeight + 'px';
8543
8544 helpers.retinaScale(me, options.devicePixelRatio);
8545
8546 if (!silent) {
8547 // Notify any plugins about the resize
8548 var newSize = {width: newWidth, height: newHeight};
8549 plugins.notify(me, 'resize', [newSize]);
8550
8551 // Notify of resize
8552 if (me.options.onResize) {
8553 me.options.onResize(me, newSize);
8554 }
8555
8556 me.stop();
8557 me.update(me.options.responsiveAnimationDuration);
8558 }
8559 },
8560
8561 ensureScalesHaveIDs: function() {
8562 var options = this.options;
8563 var scalesOptions = options.scales || {};
8564 var scaleOptions = options.scale;
8565
8566 helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
8567 xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
8568 });
8569
8570 helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
8571 yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
8572 });
8573
8574 if (scaleOptions) {
8575 scaleOptions.id = scaleOptions.id || 'scale';
8576 }
8577 },
8578
8579 /**
8580 * Builds a map of scale ID to scale object for future lookup.
8581 */
8582 buildOrUpdateScales: function() {
8583 var me = this;
8584 var options = me.options;
8585 var scales = me.scales || {};
8586 var items = [];
8587 var updated = Object.keys(scales).reduce(function(obj, id) {
8588 obj[id] = false;
8589 return obj;
8590 }, {});
8591
8592 if (options.scales) {
8593 items = items.concat(
8594 (options.scales.xAxes || []).map(function(xAxisOptions) {
8595 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
8596 }),
8597 (options.scales.yAxes || []).map(function(yAxisOptions) {
8598 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
8599 })
8600 );
8601 }
8602
8603 if (options.scale) {
8604 items.push({
8605 options: options.scale,
8606 dtype: 'radialLinear',
8607 isDefault: true,
8608 dposition: 'chartArea'
8609 });
8610 }
8611
8612 helpers.each(items, function(item) {
8613 var scaleOptions = item.options;
8614 var id = scaleOptions.id;
8615 var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype);
8616
8617 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
8618 scaleOptions.position = item.dposition;
8619 }
8620
8621 updated[id] = true;
8622 var scale = null;
8623 if (id in scales && scales[id].type === scaleType) {
8624 scale = scales[id];
8625 scale.options = scaleOptions;
8626 scale.ctx = me.ctx;
8627 scale.chart = me;
8628 } else {
8629 var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
8630 if (!scaleClass) {
8631 return;
8632 }
8633 scale = new scaleClass({
8634 id: id,
8635 type: scaleType,
8636 options: scaleOptions,
8637 ctx: me.ctx,
8638 chart: me
8639 });
8640 scales[scale.id] = scale;
8641 }
8642
8643 scale.mergeTicksOptions();
8644
8645 // TODO(SB): I think we should be able to remove this custom case (options.scale)
8646 // and consider it as a regular scale part of the "scales"" map only! This would
8647 // make the logic easier and remove some useless? custom code.
8648 if (item.isDefault) {
8649 me.scale = scale;
8650 }
8651 });
8652 // clear up discarded scales
8653 helpers.each(updated, function(hasUpdated, id) {
8654 if (!hasUpdated) {
8655 delete scales[id];
8656 }
8657 });
8658
8659 me.scales = scales;
8660
8661 Chart.scaleService.addScalesToLayout(this);
8662 },
8663
8664 buildOrUpdateControllers: function() {
8665 var me = this;
8666 var types = [];
8667 var newControllers = [];
8668
8669 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
8670 var meta = me.getDatasetMeta(datasetIndex);
8671 var type = dataset.type || me.config.type;
8672
8673 if (meta.type && meta.type !== type) {
8674 me.destroyDatasetMeta(datasetIndex);
8675 meta = me.getDatasetMeta(datasetIndex);
8676 }
8677 meta.type = type;
8678
8679 types.push(meta.type);
8680
8681 if (meta.controller) {
8682 meta.controller.updateIndex(datasetIndex);
8683 meta.controller.linkScales();
8684 } else {
8685 var ControllerClass = Chart.controllers[meta.type];
8686 if (ControllerClass === undefined) {
8687 throw new Error('"' + meta.type + '" is not a chart type.');
8688 }
8689
8690 meta.controller = new ControllerClass(me, datasetIndex);
8691 newControllers.push(meta.controller);
8692 }
8693 }, me);
8694
8695 return newControllers;
8696 },
8697
8698 /**
8699 * Reset the elements of all datasets
8700 * @private
8701 */
8702 resetElements: function() {
8703 var me = this;
8704 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
8705 me.getDatasetMeta(datasetIndex).controller.reset();
8706 }, me);
8707 },
8708
8709 /**
8710 * Resets the chart back to it's state before the initial animation
8711 */
8712 reset: function() {
8713 this.resetElements();
8714 this.tooltip.initialize();
8715 },
8716
8717 update: function(config) {
8718 var me = this;
8719
8720 if (!config || typeof config !== 'object') {
8721 // backwards compatibility
8722 config = {
8723 duration: config,
8724 lazy: arguments[1]
8725 };
8726 }
8727
8728 updateConfig(me);
8729
8730 // plugins options references might have change, let's invalidate the cache
8731 // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
8732 plugins._invalidate(me);
8733
8734 if (plugins.notify(me, 'beforeUpdate') === false) {
8735 return;
8736 }
8737
8738 // In case the entire data object changed
8739 me.tooltip._data = me.data;
8740
8741 // Make sure dataset controllers are updated and new controllers are reset
8742 var newControllers = me.buildOrUpdateControllers();
8743
8744 // Make sure all dataset controllers have correct meta data counts
8745 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
8746 me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
8747 }, me);
8748
8749 me.updateLayout();
8750
8751 // Can only reset the new controllers after the scales have been updated
8752 if (me.options.animation && me.options.animation.duration) {
8753 helpers.each(newControllers, function(controller) {
8754 controller.reset();
8755 });
8756 }
8757
8758 me.updateDatasets();
8759
8760 // Need to reset tooltip in case it is displayed with elements that are removed
8761 // after update.
8762 me.tooltip.initialize();
8763
8764 // Last active contains items that were previously in the tooltip.
8765 // When we reset the tooltip, we need to clear it
8766 me.lastActive = [];
8767
8768 // Do this before render so that any plugins that need final scale updates can use it
8769 plugins.notify(me, 'afterUpdate');
8770
8771 if (me._bufferedRender) {
8772 me._bufferedRequest = {
8773 duration: config.duration,
8774 easing: config.easing,
8775 lazy: config.lazy
8776 };
8777 } else {
8778 me.render(config);
8779 }
8780 },
8781
8782 /**
8783 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
8784 * hook, in which case, plugins will not be called on `afterLayout`.
8785 * @private
8786 */
8787 updateLayout: function() {
8788 var me = this;
8789
8790 if (plugins.notify(me, 'beforeLayout') === false) {
8791 return;
8792 }
8793
8794 layouts.update(this, this.width, this.height);
8795
8796 /**
8797 * Provided for backward compatibility, use `afterLayout` instead.
8798 * @method IPlugin#afterScaleUpdate
8799 * @deprecated since version 2.5.0
8800 * @todo remove at version 3
8801 * @private
8802 */
8803 plugins.notify(me, 'afterScaleUpdate');
8804 plugins.notify(me, 'afterLayout');
8805 },
8806
8807 /**
8808 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
8809 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
8810 * @private
8811 */
8812 updateDatasets: function() {
8813 var me = this;
8814
8815 if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
8816 return;
8817 }
8818
8819 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
8820 me.updateDataset(i);
8821 }
8822
8823 plugins.notify(me, 'afterDatasetsUpdate');
8824 },
8825
8826 /**
8827 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
8828 * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
8829 * @private
8830 */
8831 updateDataset: function(index) {
8832 var me = this;
8833 var meta = me.getDatasetMeta(index);
8834 var args = {
8835 meta: meta,
8836 index: index
8837 };
8838
8839 if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
8840 return;
8841 }
8842
8843 meta.controller.update();
8844
8845 plugins.notify(me, 'afterDatasetUpdate', [args]);
8846 },
8847
8848 render: function(config) {
8849 var me = this;
8850
8851 if (!config || typeof config !== 'object') {
8852 // backwards compatibility
8853 config = {
8854 duration: config,
8855 lazy: arguments[1]
8856 };
8857 }
8858
8859 var duration = config.duration;
8860 var lazy = config.lazy;
8861
8862 if (plugins.notify(me, 'beforeRender') === false) {
8863 return;
8864 }
8865
8866 var animationOptions = me.options.animation;
8867 var onComplete = function(animation) {
8868 plugins.notify(me, 'afterRender');
8869 helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
8870 };
8871
8872 if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
8873 var animation = new Chart.Animation({
8874 numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
8875 easing: config.easing || animationOptions.easing,
8876
8877 render: function(chart, animationObject) {
8878 var easingFunction = helpers.easing.effects[animationObject.easing];
8879 var currentStep = animationObject.currentStep;
8880 var stepDecimal = currentStep / animationObject.numSteps;
8881
8882 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
8883 },
8884
8885 onAnimationProgress: animationOptions.onProgress,
8886 onAnimationComplete: onComplete
8887 });
8888
8889 Chart.animationService.addAnimation(me, animation, duration, lazy);
8890 } else {
8891 me.draw();
8892
8893 // See https://github.com/chartjs/Chart.js/issues/3781
8894 onComplete(new Chart.Animation({numSteps: 0, chart: me}));
8895 }
8896
8897 return me;
8898 },
8899
8900 draw: function(easingValue) {
8901 var me = this;
8902
8903 me.clear();
8904
8905 if (helpers.isNullOrUndef(easingValue)) {
8906 easingValue = 1;
8907 }
8908
8909 me.transition(easingValue);
8910
8911 if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
8912 return;
8913 }
8914
8915 // Draw all the scales
8916 helpers.each(me.boxes, function(box) {
8917 box.draw(me.chartArea);
8918 }, me);
8919
8920 if (me.scale) {
8921 me.scale.draw();
8922 }
8923
8924 me.drawDatasets(easingValue);
8925 me._drawTooltip(easingValue);
8926
8927 plugins.notify(me, 'afterDraw', [easingValue]);
8928 },
8929
8930 /**
8931 * @private
8932 */
8933 transition: function(easingValue) {
8934 var me = this;
8935
8936 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
8937 if (me.isDatasetVisible(i)) {
8938 me.getDatasetMeta(i).controller.transition(easingValue);
8939 }
8940 }
8941
8942 me.tooltip.transition(easingValue);
8943 },
8944
8945 /**
8946 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
8947 * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
8948 * @private
8949 */
8950 drawDatasets: function(easingValue) {
8951 var me = this;
8952
8953 if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
8954 return;
8955 }
8956
8957 // Draw datasets reversed to support proper line stacking
8958 for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
8959 if (me.isDatasetVisible(i)) {
8960 me.drawDataset(i, easingValue);
8961 }
8962 }
8963
8964 plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
8965 },
8966
8967 /**
8968 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
8969 * hook, in which case, plugins will not be called on `afterDatasetDraw`.
8970 * @private
8971 */
8972 drawDataset: function(index, easingValue) {
8973 var me = this;
8974 var meta = me.getDatasetMeta(index);
8975 var args = {
8976 meta: meta,
8977 index: index,
8978 easingValue: easingValue
8979 };
8980
8981 if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
8982 return;
8983 }
8984
8985 meta.controller.draw(easingValue);
8986
8987 plugins.notify(me, 'afterDatasetDraw', [args]);
8988 },
8989
8990 /**
8991 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
8992 * hook, in which case, plugins will not be called on `afterTooltipDraw`.
8993 * @private
8994 */
8995 _drawTooltip: function(easingValue) {
8996 var me = this;
8997 var tooltip = me.tooltip;
8998 var args = {
8999 tooltip: tooltip,
9000 easingValue: easingValue
9001 };
9002
9003 if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
9004 return;
9005 }
9006
9007 tooltip.draw();
9008
9009 plugins.notify(me, 'afterTooltipDraw', [args]);
9010 },
9011
9012 // Get the single element that was clicked on
9013 // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
9014 getElementAtEvent: function(e) {
9015 return Interaction.modes.single(this, e);
9016 },
9017
9018 getElementsAtEvent: function(e) {
9019 return Interaction.modes.label(this, e, {intersect: true});
9020 },
9021
9022 getElementsAtXAxis: function(e) {
9023 return Interaction.modes['x-axis'](this, e, {intersect: true});
9024 },
9025
9026 getElementsAtEventForMode: function(e, mode, options) {
9027 var method = Interaction.modes[mode];
9028 if (typeof method === 'function') {
9029 return method(this, e, options);
9030 }
9031
9032 return [];
9033 },
9034
9035 getDatasetAtEvent: function(e) {
9036 return Interaction.modes.dataset(this, e, {intersect: true});
9037 },
9038
9039 getDatasetMeta: function(datasetIndex) {
9040 var me = this;
9041 var dataset = me.data.datasets[datasetIndex];
9042 if (!dataset._meta) {
9043 dataset._meta = {};
9044 }
9045
9046 var meta = dataset._meta[me.id];
9047 if (!meta) {
9048 meta = dataset._meta[me.id] = {
9049 type: null,
9050 data: [],
9051 dataset: null,
9052 controller: null,
9053 hidden: null, // See isDatasetVisible() comment
9054 xAxisID: null,
9055 yAxisID: null
9056 };
9057 }
9058
9059 return meta;
9060 },
9061
9062 getVisibleDatasetCount: function() {
9063 var count = 0;
9064 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
9065 if (this.isDatasetVisible(i)) {
9066 count++;
9067 }
9068 }
9069 return count;
9070 },
9071
9072 isDatasetVisible: function(datasetIndex) {
9073 var meta = this.getDatasetMeta(datasetIndex);
9074
9075 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
9076 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
9077 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
9078 },
9079
9080 generateLegend: function() {
9081 return this.options.legendCallback(this);
9082 },
9083
9084 /**
9085 * @private
9086 */
9087 destroyDatasetMeta: function(datasetIndex) {
9088 var id = this.id;
9089 var dataset = this.data.datasets[datasetIndex];
9090 var meta = dataset._meta && dataset._meta[id];
9091
9092 if (meta) {
9093 meta.controller.destroy();
9094 delete dataset._meta[id];
9095 }
9096 },
9097
9098 destroy: function() {
9099 var me = this;
9100 var canvas = me.canvas;
9101 var i, ilen;
9102
9103 me.stop();
9104
9105 // dataset controllers need to cleanup associated data
9106 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
9107 me.destroyDatasetMeta(i);
9108 }
9109
9110 if (canvas) {
9111 me.unbindEvents();
9112 helpers.canvas.clear(me);
9113 platform.releaseContext(me.ctx);
9114 me.canvas = null;
9115 me.ctx = null;
9116 }
9117
9118 plugins.notify(me, 'destroy');
9119
9120 delete Chart.instances[me.id];
9121 },
9122
9123 toBase64Image: function() {
9124 return this.canvas.toDataURL.apply(this.canvas, arguments);
9125 },
9126
9127 initToolTip: function() {
9128 var me = this;
9129 me.tooltip = new Chart.Tooltip({
9130 _chart: me,
9131 _chartInstance: me, // deprecated, backward compatibility
9132 _data: me.data,
9133 _options: me.options.tooltips
9134 }, me);
9135 },
9136
9137 /**
9138 * @private
9139 */
9140 bindEvents: function() {
9141 var me = this;
9142 var listeners = me._listeners = {};
9143 var listener = function() {
9144 me.eventHandler.apply(me, arguments);
9145 };
9146
9147 helpers.each(me.options.events, function(type) {
9148 platform.addEventListener(me, type, listener);
9149 listeners[type] = listener;
9150 });
9151
9152 // Elements used to detect size change should not be injected for non responsive charts.
9153 // See https://github.com/chartjs/Chart.js/issues/2210
9154 if (me.options.responsive) {
9155 listener = function() {
9156 me.resize();
9157 };
9158
9159 platform.addEventListener(me, 'resize', listener);
9160 listeners.resize = listener;
9161 }
9162 },
9163
9164 /**
9165 * @private
9166 */
9167 unbindEvents: function() {
9168 var me = this;
9169 var listeners = me._listeners;
9170 if (!listeners) {
9171 return;
9172 }
9173
9174 delete me._listeners;
9175 helpers.each(listeners, function(listener, type) {
9176 platform.removeEventListener(me, type, listener);
9177 });
9178 },
9179
9180 updateHoverStyle: function(elements, mode, enabled) {
9181 var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
9182 var element, i, ilen;
9183
9184 for (i = 0, ilen = elements.length; i < ilen; ++i) {
9185 element = elements[i];
9186 if (element) {
9187 this.getDatasetMeta(element._datasetIndex).controller[method](element);
9188 }
9189 }
9190 },
9191
9192 /**
9193 * @private
9194 */
9195 eventHandler: function(e) {
9196 var me = this;
9197 var tooltip = me.tooltip;
9198
9199 if (plugins.notify(me, 'beforeEvent', [e]) === false) {
9200 return;
9201 }
9202
9203 // Buffer any update calls so that renders do not occur
9204 me._bufferedRender = true;
9205 me._bufferedRequest = null;
9206
9207 var changed = me.handleEvent(e);
9208 // for smooth tooltip animations issue #4989
9209 // the tooltip should be the source of change
9210 // Animation check workaround:
9211 // tooltip._start will be null when tooltip isn't animating
9212 if (tooltip) {
9213 changed = tooltip._start
9214 ? tooltip.handleEvent(e)
9215 : changed | tooltip.handleEvent(e);
9216 }
9217
9218 plugins.notify(me, 'afterEvent', [e]);
9219
9220 var bufferedRequest = me._bufferedRequest;
9221 if (bufferedRequest) {
9222 // If we have an update that was triggered, we need to do a normal render
9223 me.render(bufferedRequest);
9224 } else if (changed && !me.animating) {
9225 // If entering, leaving, or changing elements, animate the change via pivot
9226 me.stop();
9227
9228 // We only need to render at this point. Updating will cause scales to be
9229 // recomputed generating flicker & using more memory than necessary.
9230 me.render(me.options.hover.animationDuration, true);
9231 }
9232
9233 me._bufferedRender = false;
9234 me._bufferedRequest = null;
9235
9236 return me;
9237 },
9238
9239 /**
9240 * Handle an event
9241 * @private
9242 * @param {IEvent} event the event to handle
9243 * @return {Boolean} true if the chart needs to re-render
9244 */
9245 handleEvent: function(e) {
9246 var me = this;
9247 var options = me.options || {};
9248 var hoverOptions = options.hover;
9249 var changed = false;
9250
9251 me.lastActive = me.lastActive || [];
9252
9253 // Find Active Elements for hover and tooltips
9254 if (e.type === 'mouseout') {
9255 me.active = [];
9256 } else {
9257 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
9258 }
9259
9260 // Invoke onHover hook
9261 // Need to call with native event here to not break backwards compatibility
9262 helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
9263
9264 if (e.type === 'mouseup' || e.type === 'click') {
9265 if (options.onClick) {
9266 // Use e.native here for backwards compatibility
9267 options.onClick.call(me, e.native, me.active);
9268 }
9269 }
9270
9271 // Remove styling for last active (even if it may still be active)
9272 if (me.lastActive.length) {
9273 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
9274 }
9275
9276 // Built in hover styling
9277 if (me.active.length && hoverOptions.mode) {
9278 me.updateHoverStyle(me.active, hoverOptions.mode, true);
9279 }
9280
9281 changed = !helpers.arrayEquals(me.active, me.lastActive);
9282
9283 // Remember Last Actives
9284 me.lastActive = me.active;
9285
9286 return changed;
9287 }
9288 });
9289
9290 /**
9291 * Provided for backward compatibility, use Chart instead.
9292 * @class Chart.Controller
9293 * @deprecated since version 2.6.0
9294 * @todo remove at version 3
9295 * @private
9296 */
9297 Chart.Controller = Chart;
9298};
9299
9300},{"25":25,"28":28,"30":30,"31":31,"45":45,"48":48}],24:[function(require,module,exports){
9301'use strict';
9302
9303var helpers = require(45);
9304
9305module.exports = function(Chart) {
9306
9307 var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
9308
9309 /**
9310 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
9311 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
9312 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
9313 */
9314 function listenArrayEvents(array, listener) {
9315 if (array._chartjs) {
9316 array._chartjs.listeners.push(listener);
9317 return;
9318 }
9319
9320 Object.defineProperty(array, '_chartjs', {
9321 configurable: true,
9322 enumerable: false,
9323 value: {
9324 listeners: [listener]
9325 }
9326 });
9327
9328 arrayEvents.forEach(function(key) {
9329 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
9330 var base = array[key];
9331
9332 Object.defineProperty(array, key, {
9333 configurable: true,
9334 enumerable: false,
9335 value: function() {
9336 var args = Array.prototype.slice.call(arguments);
9337 var res = base.apply(this, args);
9338
9339 helpers.each(array._chartjs.listeners, function(object) {
9340 if (typeof object[method] === 'function') {
9341 object[method].apply(object, args);
9342 }
9343 });
9344
9345 return res;
9346 }
9347 });
9348 });
9349 }
9350
9351 /**
9352 * Removes the given array event listener and cleanup extra attached properties (such as
9353 * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
9354 */
9355 function unlistenArrayEvents(array, listener) {
9356 var stub = array._chartjs;
9357 if (!stub) {
9358 return;
9359 }
9360
9361 var listeners = stub.listeners;
9362 var index = listeners.indexOf(listener);
9363 if (index !== -1) {
9364 listeners.splice(index, 1);
9365 }
9366
9367 if (listeners.length > 0) {
9368 return;
9369 }
9370
9371 arrayEvents.forEach(function(key) {
9372 delete array[key];
9373 });
9374
9375 delete array._chartjs;
9376 }
9377
9378 // Base class for all dataset controllers (line, bar, etc)
9379 Chart.DatasetController = function(chart, datasetIndex) {
9380 this.initialize(chart, datasetIndex);
9381 };
9382
9383 helpers.extend(Chart.DatasetController.prototype, {
9384
9385 /**
9386 * Element type used to generate a meta dataset (e.g. Chart.element.Line).
9387 * @type {Chart.core.element}
9388 */
9389 datasetElementType: null,
9390
9391 /**
9392 * Element type used to generate a meta data (e.g. Chart.element.Point).
9393 * @type {Chart.core.element}
9394 */
9395 dataElementType: null,
9396
9397 initialize: function(chart, datasetIndex) {
9398 var me = this;
9399 me.chart = chart;
9400 me.index = datasetIndex;
9401 me.linkScales();
9402 me.addElements();
9403 },
9404
9405 updateIndex: function(datasetIndex) {
9406 this.index = datasetIndex;
9407 },
9408
9409 linkScales: function() {
9410 var me = this;
9411 var meta = me.getMeta();
9412 var dataset = me.getDataset();
9413
9414 if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) {
9415 meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
9416 }
9417 if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) {
9418 meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
9419 }
9420 },
9421
9422 getDataset: function() {
9423 return this.chart.data.datasets[this.index];
9424 },
9425
9426 getMeta: function() {
9427 return this.chart.getDatasetMeta(this.index);
9428 },
9429
9430 getScaleForId: function(scaleID) {
9431 return this.chart.scales[scaleID];
9432 },
9433
9434 reset: function() {
9435 this.update(true);
9436 },
9437
9438 /**
9439 * @private
9440 */
9441 destroy: function() {
9442 if (this._data) {
9443 unlistenArrayEvents(this._data, this);
9444 }
9445 },
9446
9447 createMetaDataset: function() {
9448 var me = this;
9449 var type = me.datasetElementType;
9450 return type && new type({
9451 _chart: me.chart,
9452 _datasetIndex: me.index
9453 });
9454 },
9455
9456 createMetaData: function(index) {
9457 var me = this;
9458 var type = me.dataElementType;
9459 return type && new type({
9460 _chart: me.chart,
9461 _datasetIndex: me.index,
9462 _index: index
9463 });
9464 },
9465
9466 addElements: function() {
9467 var me = this;
9468 var meta = me.getMeta();
9469 var data = me.getDataset().data || [];
9470 var metaData = meta.data;
9471 var i, ilen;
9472
9473 for (i = 0, ilen = data.length; i < ilen; ++i) {
9474 metaData[i] = metaData[i] || me.createMetaData(i);
9475 }
9476
9477 meta.dataset = meta.dataset || me.createMetaDataset();
9478 },
9479
9480 addElementAndReset: function(index) {
9481 var element = this.createMetaData(index);
9482 this.getMeta().data.splice(index, 0, element);
9483 this.updateElement(element, index, true);
9484 },
9485
9486 buildOrUpdateElements: function() {
9487 var me = this;
9488 var dataset = me.getDataset();
9489 var data = dataset.data || (dataset.data = []);
9490
9491 // In order to correctly handle data addition/deletion animation (an thus simulate
9492 // real-time charts), we need to monitor these data modifications and synchronize
9493 // the internal meta data accordingly.
9494 if (me._data !== data) {
9495 if (me._data) {
9496 // This case happens when the user replaced the data array instance.
9497 unlistenArrayEvents(me._data, me);
9498 }
9499
9500 listenArrayEvents(data, me);
9501 me._data = data;
9502 }
9503
9504 // Re-sync meta data in case the user replaced the data array or if we missed
9505 // any updates and so make sure that we handle number of datapoints changing.
9506 me.resyncElements();
9507 },
9508
9509 update: helpers.noop,
9510
9511 transition: function(easingValue) {
9512 var meta = this.getMeta();
9513 var elements = meta.data || [];
9514 var ilen = elements.length;
9515 var i = 0;
9516
9517 for (; i < ilen; ++i) {
9518 elements[i].transition(easingValue);
9519 }
9520
9521 if (meta.dataset) {
9522 meta.dataset.transition(easingValue);
9523 }
9524 },
9525
9526 draw: function() {
9527 var meta = this.getMeta();
9528 var elements = meta.data || [];
9529 var ilen = elements.length;
9530 var i = 0;
9531
9532 if (meta.dataset) {
9533 meta.dataset.draw();
9534 }
9535
9536 for (; i < ilen; ++i) {
9537 elements[i].draw();
9538 }
9539 },
9540
9541 removeHoverStyle: function(element, elementOpts) {
9542 var dataset = this.chart.data.datasets[element._datasetIndex];
9543 var index = element._index;
9544 var custom = element.custom || {};
9545 var valueOrDefault = helpers.valueAtIndexOrDefault;
9546 var model = element._model;
9547
9548 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
9549 model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
9550 model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
9551 },
9552
9553 setHoverStyle: function(element) {
9554 var dataset = this.chart.data.datasets[element._datasetIndex];
9555 var index = element._index;
9556 var custom = element.custom || {};
9557 var valueOrDefault = helpers.valueAtIndexOrDefault;
9558 var getHoverColor = helpers.getHoverColor;
9559 var model = element._model;
9560
9561 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
9562 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
9563 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
9564 },
9565
9566 /**
9567 * @private
9568 */
9569 resyncElements: function() {
9570 var me = this;
9571 var meta = me.getMeta();
9572 var data = me.getDataset().data;
9573 var numMeta = meta.data.length;
9574 var numData = data.length;
9575
9576 if (numData < numMeta) {
9577 meta.data.splice(numData, numMeta - numData);
9578 } else if (numData > numMeta) {
9579 me.insertElements(numMeta, numData - numMeta);
9580 }
9581 },
9582
9583 /**
9584 * @private
9585 */
9586 insertElements: function(start, count) {
9587 for (var i = 0; i < count; ++i) {
9588 this.addElementAndReset(start + i);
9589 }
9590 },
9591
9592 /**
9593 * @private
9594 */
9595 onDataPush: function() {
9596 this.insertElements(this.getDataset().data.length - 1, arguments.length);
9597 },
9598
9599 /**
9600 * @private
9601 */
9602 onDataPop: function() {
9603 this.getMeta().data.pop();
9604 },
9605
9606 /**
9607 * @private
9608 */
9609 onDataShift: function() {
9610 this.getMeta().data.shift();
9611 },
9612
9613 /**
9614 * @private
9615 */
9616 onDataSplice: function(start, count) {
9617 this.getMeta().data.splice(start, count);
9618 this.insertElements(start, arguments.length - 2);
9619 },
9620
9621 /**
9622 * @private
9623 */
9624 onDataUnshift: function() {
9625 this.insertElements(0, arguments.length);
9626 }
9627 });
9628
9629 Chart.DatasetController.extend = helpers.inherits;
9630};
9631
9632},{"45":45}],25:[function(require,module,exports){
9633'use strict';
9634
9635var helpers = require(45);
9636
9637module.exports = {
9638 /**
9639 * @private
9640 */
9641 _set: function(scope, values) {
9642 return helpers.merge(this[scope] || (this[scope] = {}), values);
9643 }
9644};
9645
9646},{"45":45}],26:[function(require,module,exports){
9647'use strict';
9648
9649var color = require(2);
9650var helpers = require(45);
9651
9652function interpolate(start, view, model, ease) {
9653 var keys = Object.keys(model);
9654 var i, ilen, key, actual, origin, target, type, c0, c1;
9655
9656 for (i = 0, ilen = keys.length; i < ilen; ++i) {
9657 key = keys[i];
9658
9659 target = model[key];
9660
9661 // if a value is added to the model after pivot() has been called, the view
9662 // doesn't contain it, so let's initialize the view to the target value.
9663 if (!view.hasOwnProperty(key)) {
9664 view[key] = target;
9665 }
9666
9667 actual = view[key];
9668
9669 if (actual === target || key[0] === '_') {
9670 continue;
9671 }
9672
9673 if (!start.hasOwnProperty(key)) {
9674 start[key] = actual;
9675 }
9676
9677 origin = start[key];
9678
9679 type = typeof target;
9680
9681 if (type === typeof origin) {
9682 if (type === 'string') {
9683 c0 = color(origin);
9684 if (c0.valid) {
9685 c1 = color(target);
9686 if (c1.valid) {
9687 view[key] = c1.mix(c0, ease).rgbString();
9688 continue;
9689 }
9690 }
9691 } else if (type === 'number' && isFinite(origin) && isFinite(target)) {
9692 view[key] = origin + (target - origin) * ease;
9693 continue;
9694 }
9695 }
9696
9697 view[key] = target;
9698 }
9699}
9700
9701var Element = function(configuration) {
9702 helpers.extend(this, configuration);
9703 this.initialize.apply(this, arguments);
9704};
9705
9706helpers.extend(Element.prototype, {
9707
9708 initialize: function() {
9709 this.hidden = false;
9710 },
9711
9712 pivot: function() {
9713 var me = this;
9714 if (!me._view) {
9715 me._view = helpers.clone(me._model);
9716 }
9717 me._start = {};
9718 return me;
9719 },
9720
9721 transition: function(ease) {
9722 var me = this;
9723 var model = me._model;
9724 var start = me._start;
9725 var view = me._view;
9726
9727 // No animation -> No Transition
9728 if (!model || ease === 1) {
9729 me._view = model;
9730 me._start = null;
9731 return me;
9732 }
9733
9734 if (!view) {
9735 view = me._view = {};
9736 }
9737
9738 if (!start) {
9739 start = me._start = {};
9740 }
9741
9742 interpolate(start, view, model, ease);
9743
9744 return me;
9745 },
9746
9747 tooltipPosition: function() {
9748 return {
9749 x: this._model.x,
9750 y: this._model.y
9751 };
9752 },
9753
9754 hasValue: function() {
9755 return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
9756 }
9757});
9758
9759Element.extend = helpers.inherits;
9760
9761module.exports = Element;
9762
9763},{"2":2,"45":45}],27:[function(require,module,exports){
9764/* global window: false */
9765/* global document: false */
9766'use strict';
9767
9768var color = require(2);
9769var defaults = require(25);
9770var helpers = require(45);
9771
9772module.exports = function(Chart) {
9773
9774 // -- Basic js utility methods
9775
9776 helpers.configMerge = function(/* objects ... */) {
9777 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
9778 merger: function(key, target, source, options) {
9779 var tval = target[key] || {};
9780 var sval = source[key];
9781
9782 if (key === 'scales') {
9783 // scale config merging is complex. Add our own function here for that
9784 target[key] = helpers.scaleMerge(tval, sval);
9785 } else if (key === 'scale') {
9786 // used in polar area & radar charts since there is only one scale
9787 target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]);
9788 } else {
9789 helpers._merger(key, target, source, options);
9790 }
9791 }
9792 });
9793 };
9794
9795 helpers.scaleMerge = function(/* objects ... */) {
9796 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
9797 merger: function(key, target, source, options) {
9798 if (key === 'xAxes' || key === 'yAxes') {
9799 var slen = source[key].length;
9800 var i, type, scale;
9801
9802 if (!target[key]) {
9803 target[key] = [];
9804 }
9805
9806 for (i = 0; i < slen; ++i) {
9807 scale = source[key][i];
9808 type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear');
9809
9810 if (i >= target[key].length) {
9811 target[key].push({});
9812 }
9813
9814 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
9815 // new/untyped scale or type changed: let's apply the new defaults
9816 // then merge source scale to correctly overwrite the defaults.
9817 helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]);
9818 } else {
9819 // scales type are the same
9820 helpers.merge(target[key][i], scale);
9821 }
9822 }
9823 } else {
9824 helpers._merger(key, target, source, options);
9825 }
9826 }
9827 });
9828 };
9829
9830 helpers.where = function(collection, filterCallback) {
9831 if (helpers.isArray(collection) && Array.prototype.filter) {
9832 return collection.filter(filterCallback);
9833 }
9834 var filtered = [];
9835
9836 helpers.each(collection, function(item) {
9837 if (filterCallback(item)) {
9838 filtered.push(item);
9839 }
9840 });
9841
9842 return filtered;
9843 };
9844 helpers.findIndex = Array.prototype.findIndex ?
9845 function(array, callback, scope) {
9846 return array.findIndex(callback, scope);
9847 } :
9848 function(array, callback, scope) {
9849 scope = scope === undefined ? array : scope;
9850 for (var i = 0, ilen = array.length; i < ilen; ++i) {
9851 if (callback.call(scope, array[i], i, array)) {
9852 return i;
9853 }
9854 }
9855 return -1;
9856 };
9857 helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
9858 // Default to start of the array
9859 if (helpers.isNullOrUndef(startIndex)) {
9860 startIndex = -1;
9861 }
9862 for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
9863 var currentItem = arrayToSearch[i];
9864 if (filterCallback(currentItem)) {
9865 return currentItem;
9866 }
9867 }
9868 };
9869 helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
9870 // Default to end of the array
9871 if (helpers.isNullOrUndef(startIndex)) {
9872 startIndex = arrayToSearch.length;
9873 }
9874 for (var i = startIndex - 1; i >= 0; i--) {
9875 var currentItem = arrayToSearch[i];
9876 if (filterCallback(currentItem)) {
9877 return currentItem;
9878 }
9879 }
9880 };
9881
9882 // -- Math methods
9883 helpers.isNumber = function(n) {
9884 return !isNaN(parseFloat(n)) && isFinite(n);
9885 };
9886 helpers.almostEquals = function(x, y, epsilon) {
9887 return Math.abs(x - y) < epsilon;
9888 };
9889 helpers.almostWhole = function(x, epsilon) {
9890 var rounded = Math.round(x);
9891 return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
9892 };
9893 helpers.max = function(array) {
9894 return array.reduce(function(max, value) {
9895 if (!isNaN(value)) {
9896 return Math.max(max, value);
9897 }
9898 return max;
9899 }, Number.NEGATIVE_INFINITY);
9900 };
9901 helpers.min = function(array) {
9902 return array.reduce(function(min, value) {
9903 if (!isNaN(value)) {
9904 return Math.min(min, value);
9905 }
9906 return min;
9907 }, Number.POSITIVE_INFINITY);
9908 };
9909 helpers.sign = Math.sign ?
9910 function(x) {
9911 return Math.sign(x);
9912 } :
9913 function(x) {
9914 x = +x; // convert to a number
9915 if (x === 0 || isNaN(x)) {
9916 return x;
9917 }
9918 return x > 0 ? 1 : -1;
9919 };
9920 helpers.log10 = Math.log10 ?
9921 function(x) {
9922 return Math.log10(x);
9923 } :
9924 function(x) {
9925 return Math.log(x) / Math.LN10;
9926 };
9927 helpers.toRadians = function(degrees) {
9928 return degrees * (Math.PI / 180);
9929 };
9930 helpers.toDegrees = function(radians) {
9931 return radians * (180 / Math.PI);
9932 };
9933 // Gets the angle from vertical upright to the point about a centre.
9934 helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
9935 var distanceFromXCenter = anglePoint.x - centrePoint.x;
9936 var distanceFromYCenter = anglePoint.y - centrePoint.y;
9937 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
9938
9939 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
9940
9941 if (angle < (-0.5 * Math.PI)) {
9942 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
9943 }
9944
9945 return {
9946 angle: angle,
9947 distance: radialDistanceFromCenter
9948 };
9949 };
9950 helpers.distanceBetweenPoints = function(pt1, pt2) {
9951 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
9952 };
9953 helpers.aliasPixel = function(pixelWidth) {
9954 return (pixelWidth % 2 === 0) ? 0 : 0.5;
9955 };
9956 helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
9957 // Props to Rob Spencer at scaled innovation for his post on splining between points
9958 // http://scaledinnovation.com/analytics/splines/aboutSplines.html
9959
9960 // This function must also respect "skipped" points
9961
9962 var previous = firstPoint.skip ? middlePoint : firstPoint;
9963 var current = middlePoint;
9964 var next = afterPoint.skip ? middlePoint : afterPoint;
9965
9966 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
9967 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
9968
9969 var s01 = d01 / (d01 + d12);
9970 var s12 = d12 / (d01 + d12);
9971
9972 // If all points are the same, s01 & s02 will be inf
9973 s01 = isNaN(s01) ? 0 : s01;
9974 s12 = isNaN(s12) ? 0 : s12;
9975
9976 var fa = t * s01; // scaling factor for triangle Ta
9977 var fb = t * s12;
9978
9979 return {
9980 previous: {
9981 x: current.x - fa * (next.x - previous.x),
9982 y: current.y - fa * (next.y - previous.y)
9983 },
9984 next: {
9985 x: current.x + fb * (next.x - previous.x),
9986 y: current.y + fb * (next.y - previous.y)
9987 }
9988 };
9989 };
9990 helpers.EPSILON = Number.EPSILON || 1e-14;
9991 helpers.splineCurveMonotone = function(points) {
9992 // This function calculates Bézier control points in a similar way than |splineCurve|,
9993 // but preserves monotonicity of the provided data and ensures no local extremums are added
9994 // between the dataset discrete points due to the interpolation.
9995 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
9996
9997 var pointsWithTangents = (points || []).map(function(point) {
9998 return {
9999 model: point._model,
10000 deltaK: 0,
10001 mK: 0
10002 };
10003 });
10004
10005 // Calculate slopes (deltaK) and initialize tangents (mK)
10006 var pointsLen = pointsWithTangents.length;
10007 var i, pointBefore, pointCurrent, pointAfter;
10008 for (i = 0; i < pointsLen; ++i) {
10009 pointCurrent = pointsWithTangents[i];
10010 if (pointCurrent.model.skip) {
10011 continue;
10012 }
10013
10014 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
10015 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
10016 if (pointAfter && !pointAfter.model.skip) {
10017 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
10018
10019 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
10020 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
10021 }
10022
10023 if (!pointBefore || pointBefore.model.skip) {
10024 pointCurrent.mK = pointCurrent.deltaK;
10025 } else if (!pointAfter || pointAfter.model.skip) {
10026 pointCurrent.mK = pointBefore.deltaK;
10027 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
10028 pointCurrent.mK = 0;
10029 } else {
10030 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
10031 }
10032 }
10033
10034 // Adjust tangents to ensure monotonic properties
10035 var alphaK, betaK, tauK, squaredMagnitude;
10036 for (i = 0; i < pointsLen - 1; ++i) {
10037 pointCurrent = pointsWithTangents[i];
10038 pointAfter = pointsWithTangents[i + 1];
10039 if (pointCurrent.model.skip || pointAfter.model.skip) {
10040 continue;
10041 }
10042
10043 if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
10044 pointCurrent.mK = pointAfter.mK = 0;
10045 continue;
10046 }
10047
10048 alphaK = pointCurrent.mK / pointCurrent.deltaK;
10049 betaK = pointAfter.mK / pointCurrent.deltaK;
10050 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
10051 if (squaredMagnitude <= 9) {
10052 continue;
10053 }
10054
10055 tauK = 3 / Math.sqrt(squaredMagnitude);
10056 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
10057 pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
10058 }
10059
10060 // Compute control points
10061 var deltaX;
10062 for (i = 0; i < pointsLen; ++i) {
10063 pointCurrent = pointsWithTangents[i];
10064 if (pointCurrent.model.skip) {
10065 continue;
10066 }
10067
10068 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
10069 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
10070 if (pointBefore && !pointBefore.model.skip) {
10071 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
10072 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
10073 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
10074 }
10075 if (pointAfter && !pointAfter.model.skip) {
10076 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
10077 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
10078 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
10079 }
10080 }
10081 };
10082 helpers.nextItem = function(collection, index, loop) {
10083 if (loop) {
10084 return index >= collection.length - 1 ? collection[0] : collection[index + 1];
10085 }
10086 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
10087 };
10088 helpers.previousItem = function(collection, index, loop) {
10089 if (loop) {
10090 return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
10091 }
10092 return index <= 0 ? collection[0] : collection[index - 1];
10093 };
10094 // Implementation of the nice number algorithm used in determining where axis labels will go
10095 helpers.niceNum = function(range, round) {
10096 var exponent = Math.floor(helpers.log10(range));
10097 var fraction = range / Math.pow(10, exponent);
10098 var niceFraction;
10099
10100 if (round) {
10101 if (fraction < 1.5) {
10102 niceFraction = 1;
10103 } else if (fraction < 3) {
10104 niceFraction = 2;
10105 } else if (fraction < 7) {
10106 niceFraction = 5;
10107 } else {
10108 niceFraction = 10;
10109 }
10110 } else if (fraction <= 1.0) {
10111 niceFraction = 1;
10112 } else if (fraction <= 2) {
10113 niceFraction = 2;
10114 } else if (fraction <= 5) {
10115 niceFraction = 5;
10116 } else {
10117 niceFraction = 10;
10118 }
10119
10120 return niceFraction * Math.pow(10, exponent);
10121 };
10122 // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
10123 helpers.requestAnimFrame = (function() {
10124 if (typeof window === 'undefined') {
10125 return function(callback) {
10126 callback();
10127 };
10128 }
10129 return window.requestAnimationFrame ||
10130 window.webkitRequestAnimationFrame ||
10131 window.mozRequestAnimationFrame ||
10132 window.oRequestAnimationFrame ||
10133 window.msRequestAnimationFrame ||
10134 function(callback) {
10135 return window.setTimeout(callback, 1000 / 60);
10136 };
10137 }());
10138 // -- DOM methods
10139 helpers.getRelativePosition = function(evt, chart) {
10140 var mouseX, mouseY;
10141 var e = evt.originalEvent || evt;
10142 var canvas = evt.currentTarget || evt.srcElement;
10143 var boundingRect = canvas.getBoundingClientRect();
10144
10145 var touches = e.touches;
10146 if (touches && touches.length > 0) {
10147 mouseX = touches[0].clientX;
10148 mouseY = touches[0].clientY;
10149
10150 } else {
10151 mouseX = e.clientX;
10152 mouseY = e.clientY;
10153 }
10154
10155 // Scale mouse coordinates into canvas coordinates
10156 // by following the pattern laid out by 'jerryj' in the comments of
10157 // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
10158 var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
10159 var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
10160 var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
10161 var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
10162 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
10163 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
10164
10165 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
10166 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
10167 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
10168 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
10169
10170 return {
10171 x: mouseX,
10172 y: mouseY
10173 };
10174
10175 };
10176
10177 // Private helper function to convert max-width/max-height values that may be percentages into a number
10178 function parseMaxStyle(styleValue, node, parentProperty) {
10179 var valueInPixels;
10180 if (typeof styleValue === 'string') {
10181 valueInPixels = parseInt(styleValue, 10);
10182
10183 if (styleValue.indexOf('%') !== -1) {
10184 // percentage * size in dimension
10185 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
10186 }
10187 } else {
10188 valueInPixels = styleValue;
10189 }
10190
10191 return valueInPixels;
10192 }
10193
10194 /**
10195 * Returns if the given value contains an effective constraint.
10196 * @private
10197 */
10198 function isConstrainedValue(value) {
10199 return value !== undefined && value !== null && value !== 'none';
10200 }
10201
10202 // Private helper to get a constraint dimension
10203 // @param domNode : the node to check the constraint on
10204 // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
10205 // @param percentageProperty : property of parent to use when calculating width as a percentage
10206 // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
10207 function getConstraintDimension(domNode, maxStyle, percentageProperty) {
10208 var view = document.defaultView;
10209 var parentNode = domNode.parentNode;
10210 var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
10211 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
10212 var hasCNode = isConstrainedValue(constrainedNode);
10213 var hasCContainer = isConstrainedValue(constrainedContainer);
10214 var infinity = Number.POSITIVE_INFINITY;
10215
10216 if (hasCNode || hasCContainer) {
10217 return Math.min(
10218 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
10219 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
10220 }
10221
10222 return 'none';
10223 }
10224 // returns Number or undefined if no constraint
10225 helpers.getConstraintWidth = function(domNode) {
10226 return getConstraintDimension(domNode, 'max-width', 'clientWidth');
10227 };
10228 // returns Number or undefined if no constraint
10229 helpers.getConstraintHeight = function(domNode) {
10230 return getConstraintDimension(domNode, 'max-height', 'clientHeight');
10231 };
10232 helpers.getMaximumWidth = function(domNode) {
10233 var container = domNode.parentNode;
10234 if (!container) {
10235 return domNode.clientWidth;
10236 }
10237
10238 var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
10239 var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
10240 var w = container.clientWidth - paddingLeft - paddingRight;
10241 var cw = helpers.getConstraintWidth(domNode);
10242 return isNaN(cw) ? w : Math.min(w, cw);
10243 };
10244 helpers.getMaximumHeight = function(domNode) {
10245 var container = domNode.parentNode;
10246 if (!container) {
10247 return domNode.clientHeight;
10248 }
10249
10250 var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
10251 var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
10252 var h = container.clientHeight - paddingTop - paddingBottom;
10253 var ch = helpers.getConstraintHeight(domNode);
10254 return isNaN(ch) ? h : Math.min(h, ch);
10255 };
10256 helpers.getStyle = function(el, property) {
10257 return el.currentStyle ?
10258 el.currentStyle[property] :
10259 document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
10260 };
10261 helpers.retinaScale = function(chart, forceRatio) {
10262 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1;
10263 if (pixelRatio === 1) {
10264 return;
10265 }
10266
10267 var canvas = chart.canvas;
10268 var height = chart.height;
10269 var width = chart.width;
10270
10271 canvas.height = height * pixelRatio;
10272 canvas.width = width * pixelRatio;
10273 chart.ctx.scale(pixelRatio, pixelRatio);
10274
10275 // If no style has been set on the canvas, the render size is used as display size,
10276 // making the chart visually bigger, so let's enforce it to the "correct" values.
10277 // See https://github.com/chartjs/Chart.js/issues/3575
10278 if (!canvas.style.height && !canvas.style.width) {
10279 canvas.style.height = height + 'px';
10280 canvas.style.width = width + 'px';
10281 }
10282 };
10283 // -- Canvas methods
10284 helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
10285 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
10286 };
10287 helpers.longestText = function(ctx, font, arrayOfThings, cache) {
10288 cache = cache || {};
10289 var data = cache.data = cache.data || {};
10290 var gc = cache.garbageCollect = cache.garbageCollect || [];
10291
10292 if (cache.font !== font) {
10293 data = cache.data = {};
10294 gc = cache.garbageCollect = [];
10295 cache.font = font;
10296 }
10297
10298 ctx.font = font;
10299 var longest = 0;
10300 helpers.each(arrayOfThings, function(thing) {
10301 // Undefined strings and arrays should not be measured
10302 if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
10303 longest = helpers.measureText(ctx, data, gc, longest, thing);
10304 } else if (helpers.isArray(thing)) {
10305 // if it is an array lets measure each element
10306 // to do maybe simplify this function a bit so we can do this more recursively?
10307 helpers.each(thing, function(nestedThing) {
10308 // Undefined strings and arrays should not be measured
10309 if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
10310 longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
10311 }
10312 });
10313 }
10314 });
10315
10316 var gcLen = gc.length / 2;
10317 if (gcLen > arrayOfThings.length) {
10318 for (var i = 0; i < gcLen; i++) {
10319 delete data[gc[i]];
10320 }
10321 gc.splice(0, gcLen);
10322 }
10323 return longest;
10324 };
10325 helpers.measureText = function(ctx, data, gc, longest, string) {
10326 var textWidth = data[string];
10327 if (!textWidth) {
10328 textWidth = data[string] = ctx.measureText(string).width;
10329 gc.push(string);
10330 }
10331 if (textWidth > longest) {
10332 longest = textWidth;
10333 }
10334 return longest;
10335 };
10336 helpers.numberOfLabelLines = function(arrayOfThings) {
10337 var numberOfLines = 1;
10338 helpers.each(arrayOfThings, function(thing) {
10339 if (helpers.isArray(thing)) {
10340 if (thing.length > numberOfLines) {
10341 numberOfLines = thing.length;
10342 }
10343 }
10344 });
10345 return numberOfLines;
10346 };
10347
10348 helpers.color = !color ?
10349 function(value) {
10350 console.error('Color.js not found!');
10351 return value;
10352 } :
10353 function(value) {
10354 /* global CanvasGradient */
10355 if (value instanceof CanvasGradient) {
10356 value = defaults.global.defaultColor;
10357 }
10358
10359 return color(value);
10360 };
10361
10362 helpers.getHoverColor = function(colorValue) {
10363 /* global CanvasPattern */
10364 return (colorValue instanceof CanvasPattern) ?
10365 colorValue :
10366 helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
10367 };
10368};
10369
10370},{"2":2,"25":25,"45":45}],28:[function(require,module,exports){
10371'use strict';
10372
10373var helpers = require(45);
10374
10375/**
10376 * Helper function to get relative position for an event
10377 * @param {Event|IEvent} event - The event to get the position for
10378 * @param {Chart} chart - The chart
10379 * @returns {Point} the event position
10380 */
10381function getRelativePosition(e, chart) {
10382 if (e.native) {
10383 return {
10384 x: e.x,
10385 y: e.y
10386 };
10387 }
10388
10389 return helpers.getRelativePosition(e, chart);
10390}
10391
10392/**
10393 * Helper function to traverse all of the visible elements in the chart
10394 * @param chart {chart} the chart
10395 * @param handler {Function} the callback to execute for each visible item
10396 */
10397function parseVisibleItems(chart, handler) {
10398 var datasets = chart.data.datasets;
10399 var meta, i, j, ilen, jlen;
10400
10401 for (i = 0, ilen = datasets.length; i < ilen; ++i) {
10402 if (!chart.isDatasetVisible(i)) {
10403 continue;
10404 }
10405
10406 meta = chart.getDatasetMeta(i);
10407 for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
10408 var element = meta.data[j];
10409 if (!element._view.skip) {
10410 handler(element);
10411 }
10412 }
10413 }
10414}
10415
10416/**
10417 * Helper function to get the items that intersect the event position
10418 * @param items {ChartElement[]} elements to filter
10419 * @param position {Point} the point to be nearest to
10420 * @return {ChartElement[]} the nearest items
10421 */
10422function getIntersectItems(chart, position) {
10423 var elements = [];
10424
10425 parseVisibleItems(chart, function(element) {
10426 if (element.inRange(position.x, position.y)) {
10427 elements.push(element);
10428 }
10429 });
10430
10431 return elements;
10432}
10433
10434/**
10435 * Helper function to get the items nearest to the event position considering all visible items in teh chart
10436 * @param chart {Chart} the chart to look at elements from
10437 * @param position {Point} the point to be nearest to
10438 * @param intersect {Boolean} if true, only consider items that intersect the position
10439 * @param distanceMetric {Function} function to provide the distance between points
10440 * @return {ChartElement[]} the nearest items
10441 */
10442function getNearestItems(chart, position, intersect, distanceMetric) {
10443 var minDistance = Number.POSITIVE_INFINITY;
10444 var nearestItems = [];
10445
10446 parseVisibleItems(chart, function(element) {
10447 if (intersect && !element.inRange(position.x, position.y)) {
10448 return;
10449 }
10450
10451 var center = element.getCenterPoint();
10452 var distance = distanceMetric(position, center);
10453
10454 if (distance < minDistance) {
10455 nearestItems = [element];
10456 minDistance = distance;
10457 } else if (distance === minDistance) {
10458 // Can have multiple items at the same distance in which case we sort by size
10459 nearestItems.push(element);
10460 }
10461 });
10462
10463 return nearestItems;
10464}
10465
10466/**
10467 * Get a distance metric function for two points based on the
10468 * axis mode setting
10469 * @param {String} axis the axis mode. x|y|xy
10470 */
10471function getDistanceMetricForAxis(axis) {
10472 var useX = axis.indexOf('x') !== -1;
10473 var useY = axis.indexOf('y') !== -1;
10474
10475 return function(pt1, pt2) {
10476 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
10477 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
10478 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
10479 };
10480}
10481
10482function indexMode(chart, e, options) {
10483 var position = getRelativePosition(e, chart);
10484 // Default axis for index mode is 'x' to match old behaviour
10485 options.axis = options.axis || 'x';
10486 var distanceMetric = getDistanceMetricForAxis(options.axis);
10487 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
10488 var elements = [];
10489
10490 if (!items.length) {
10491 return [];
10492 }
10493
10494 chart.data.datasets.forEach(function(dataset, datasetIndex) {
10495 if (chart.isDatasetVisible(datasetIndex)) {
10496 var meta = chart.getDatasetMeta(datasetIndex);
10497 var element = meta.data[items[0]._index];
10498
10499 // don't count items that are skipped (null data)
10500 if (element && !element._view.skip) {
10501 elements.push(element);
10502 }
10503 }
10504 });
10505
10506 return elements;
10507}
10508
10509/**
10510 * @interface IInteractionOptions
10511 */
10512/**
10513 * If true, only consider items that intersect the point
10514 * @name IInterfaceOptions#boolean
10515 * @type Boolean
10516 */
10517
10518/**
10519 * Contains interaction related functions
10520 * @namespace Chart.Interaction
10521 */
10522module.exports = {
10523 // Helper function for different modes
10524 modes: {
10525 single: function(chart, e) {
10526 var position = getRelativePosition(e, chart);
10527 var elements = [];
10528
10529 parseVisibleItems(chart, function(element) {
10530 if (element.inRange(position.x, position.y)) {
10531 elements.push(element);
10532 return elements;
10533 }
10534 });
10535
10536 return elements.slice(0, 1);
10537 },
10538
10539 /**
10540 * @function Chart.Interaction.modes.label
10541 * @deprecated since version 2.4.0
10542 * @todo remove at version 3
10543 * @private
10544 */
10545 label: indexMode,
10546
10547 /**
10548 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
10549 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
10550 * @function Chart.Interaction.modes.index
10551 * @since v2.4.0
10552 * @param chart {chart} the chart we are returning items from
10553 * @param e {Event} the event we are find things at
10554 * @param options {IInteractionOptions} options to use during interaction
10555 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
10556 */
10557 index: indexMode,
10558
10559 /**
10560 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
10561 * If the options.intersect is false, we find the nearest item and return the items in that dataset
10562 * @function Chart.Interaction.modes.dataset
10563 * @param chart {chart} the chart we are returning items from
10564 * @param e {Event} the event we are find things at
10565 * @param options {IInteractionOptions} options to use during interaction
10566 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
10567 */
10568 dataset: function(chart, e, options) {
10569 var position = getRelativePosition(e, chart);
10570 options.axis = options.axis || 'xy';
10571 var distanceMetric = getDistanceMetricForAxis(options.axis);
10572 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
10573
10574 if (items.length > 0) {
10575 items = chart.getDatasetMeta(items[0]._datasetIndex).data;
10576 }
10577
10578 return items;
10579 },
10580
10581 /**
10582 * @function Chart.Interaction.modes.x-axis
10583 * @deprecated since version 2.4.0. Use index mode and intersect == true
10584 * @todo remove at version 3
10585 * @private
10586 */
10587 'x-axis': function(chart, e) {
10588 return indexMode(chart, e, {intersect: false});
10589 },
10590
10591 /**
10592 * Point mode returns all elements that hit test based on the event position
10593 * of the event
10594 * @function Chart.Interaction.modes.intersect
10595 * @param chart {chart} the chart we are returning items from
10596 * @param e {Event} the event we are find things at
10597 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
10598 */
10599 point: function(chart, e) {
10600 var position = getRelativePosition(e, chart);
10601 return getIntersectItems(chart, position);
10602 },
10603
10604 /**
10605 * nearest mode returns the element closest to the point
10606 * @function Chart.Interaction.modes.intersect
10607 * @param chart {chart} the chart we are returning items from
10608 * @param e {Event} the event we are find things at
10609 * @param options {IInteractionOptions} options to use
10610 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
10611 */
10612 nearest: function(chart, e, options) {
10613 var position = getRelativePosition(e, chart);
10614 options.axis = options.axis || 'xy';
10615 var distanceMetric = getDistanceMetricForAxis(options.axis);
10616 var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric);
10617
10618 // We have multiple items at the same distance from the event. Now sort by smallest
10619 if (nearestItems.length > 1) {
10620 nearestItems.sort(function(a, b) {
10621 var sizeA = a.getArea();
10622 var sizeB = b.getArea();
10623 var ret = sizeA - sizeB;
10624
10625 if (ret === 0) {
10626 // if equal sort by dataset index
10627 ret = a._datasetIndex - b._datasetIndex;
10628 }
10629
10630 return ret;
10631 });
10632 }
10633
10634 // Return only 1 item
10635 return nearestItems.slice(0, 1);
10636 },
10637
10638 /**
10639 * x mode returns the elements that hit-test at the current x coordinate
10640 * @function Chart.Interaction.modes.x
10641 * @param chart {chart} the chart we are returning items from
10642 * @param e {Event} the event we are find things at
10643 * @param options {IInteractionOptions} options to use
10644 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
10645 */
10646 x: function(chart, e, options) {
10647 var position = getRelativePosition(e, chart);
10648 var items = [];
10649 var intersectsItem = false;
10650
10651 parseVisibleItems(chart, function(element) {
10652 if (element.inXRange(position.x)) {
10653 items.push(element);
10654 }
10655
10656 if (element.inRange(position.x, position.y)) {
10657 intersectsItem = true;
10658 }
10659 });
10660
10661 // If we want to trigger on an intersect and we don't have any items
10662 // that intersect the position, return nothing
10663 if (options.intersect && !intersectsItem) {
10664 items = [];
10665 }
10666 return items;
10667 },
10668
10669 /**
10670 * y mode returns the elements that hit-test at the current y coordinate
10671 * @function Chart.Interaction.modes.y
10672 * @param chart {chart} the chart we are returning items from
10673 * @param e {Event} the event we are find things at
10674 * @param options {IInteractionOptions} options to use
10675 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
10676 */
10677 y: function(chart, e, options) {
10678 var position = getRelativePosition(e, chart);
10679 var items = [];
10680 var intersectsItem = false;
10681
10682 parseVisibleItems(chart, function(element) {
10683 if (element.inYRange(position.y)) {
10684 items.push(element);
10685 }
10686
10687 if (element.inRange(position.x, position.y)) {
10688 intersectsItem = true;
10689 }
10690 });
10691
10692 // If we want to trigger on an intersect and we don't have any items
10693 // that intersect the position, return nothing
10694 if (options.intersect && !intersectsItem) {
10695 items = [];
10696 }
10697 return items;
10698 }
10699 }
10700};
10701
10702},{"45":45}],29:[function(require,module,exports){
10703'use strict';
10704
10705var defaults = require(25);
10706
10707defaults._set('global', {
10708 responsive: true,
10709 responsiveAnimationDuration: 0,
10710 maintainAspectRatio: true,
10711 events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
10712 hover: {
10713 onHover: null,
10714 mode: 'nearest',
10715 intersect: true,
10716 animationDuration: 400
10717 },
10718 onClick: null,
10719 defaultColor: 'rgba(0,0,0,0.1)',
10720 defaultFontColor: '#666',
10721 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
10722 defaultFontSize: 12,
10723 defaultFontStyle: 'normal',
10724 showLines: true,
10725
10726 // Element defaults defined in element extensions
10727 elements: {},
10728
10729 // Layout options such as padding
10730 layout: {
10731 padding: {
10732 top: 0,
10733 right: 0,
10734 bottom: 0,
10735 left: 0
10736 }
10737 }
10738});
10739
10740module.exports = function() {
10741
10742 // Occupy the global variable of Chart, and create a simple base class
10743 var Chart = function(item, config) {
10744 this.construct(item, config);
10745 return this;
10746 };
10747
10748 Chart.Chart = Chart;
10749
10750 return Chart;
10751};
10752
10753},{"25":25}],30:[function(require,module,exports){
10754'use strict';
10755
10756var helpers = require(45);
10757
10758function filterByPosition(array, position) {
10759 return helpers.where(array, function(v) {
10760 return v.position === position;
10761 });
10762}
10763
10764function sortByWeight(array, reverse) {
10765 array.forEach(function(v, i) {
10766 v._tmpIndex_ = i;
10767 return v;
10768 });
10769 array.sort(function(a, b) {
10770 var v0 = reverse ? b : a;
10771 var v1 = reverse ? a : b;
10772 return v0.weight === v1.weight ?
10773 v0._tmpIndex_ - v1._tmpIndex_ :
10774 v0.weight - v1.weight;
10775 });
10776 array.forEach(function(v) {
10777 delete v._tmpIndex_;
10778 });
10779}
10780
10781/**
10782 * @interface ILayoutItem
10783 * @prop {String} position - The position of the item in the chart layout. Possible values are
10784 * 'left', 'top', 'right', 'bottom', and 'chartArea'
10785 * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
10786 * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
10787 * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
10788 * @prop {Function} update - Takes two parameters: width and height. Returns size of item
10789 * @prop {Function} getPadding - Returns an object with padding on the edges
10790 * @prop {Number} width - Width of item. Must be valid after update()
10791 * @prop {Number} height - Height of item. Must be valid after update()
10792 * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
10793 * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
10794 * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
10795 * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
10796 */
10797
10798// The layout service is very self explanatory. It's responsible for the layout within a chart.
10799// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
10800// It is this service's responsibility of carrying out that layout.
10801module.exports = {
10802 defaults: {},
10803
10804 /**
10805 * Register a box to a chart.
10806 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
10807 * @param {Chart} chart - the chart to use
10808 * @param {ILayoutItem} item - the item to add to be layed out
10809 */
10810 addBox: function(chart, item) {
10811 if (!chart.boxes) {
10812 chart.boxes = [];
10813 }
10814
10815 // initialize item with default values
10816 item.fullWidth = item.fullWidth || false;
10817 item.position = item.position || 'top';
10818 item.weight = item.weight || 0;
10819
10820 chart.boxes.push(item);
10821 },
10822
10823 /**
10824 * Remove a layoutItem from a chart
10825 * @param {Chart} chart - the chart to remove the box from
10826 * @param {Object} layoutItem - the item to remove from the layout
10827 */
10828 removeBox: function(chart, layoutItem) {
10829 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
10830 if (index !== -1) {
10831 chart.boxes.splice(index, 1);
10832 }
10833 },
10834
10835 /**
10836 * Sets (or updates) options on the given `item`.
10837 * @param {Chart} chart - the chart in which the item lives (or will be added to)
10838 * @param {Object} item - the item to configure with the given options
10839 * @param {Object} options - the new item options.
10840 */
10841 configure: function(chart, item, options) {
10842 var props = ['fullWidth', 'position', 'weight'];
10843 var ilen = props.length;
10844 var i = 0;
10845 var prop;
10846
10847 for (; i < ilen; ++i) {
10848 prop = props[i];
10849 if (options.hasOwnProperty(prop)) {
10850 item[prop] = options[prop];
10851 }
10852 }
10853 },
10854
10855 /**
10856 * Fits boxes of the given chart into the given size by having each box measure itself
10857 * then running a fitting algorithm
10858 * @param {Chart} chart - the chart
10859 * @param {Number} width - the width to fit into
10860 * @param {Number} height - the height to fit into
10861 */
10862 update: function(chart, width, height) {
10863 if (!chart) {
10864 return;
10865 }
10866
10867 var layoutOptions = chart.options.layout || {};
10868 var padding = helpers.options.toPadding(layoutOptions.padding);
10869 var leftPadding = padding.left;
10870 var rightPadding = padding.right;
10871 var topPadding = padding.top;
10872 var bottomPadding = padding.bottom;
10873
10874 var leftBoxes = filterByPosition(chart.boxes, 'left');
10875 var rightBoxes = filterByPosition(chart.boxes, 'right');
10876 var topBoxes = filterByPosition(chart.boxes, 'top');
10877 var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
10878 var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
10879
10880 // Sort boxes by weight. A higher weight is further away from the chart area
10881 sortByWeight(leftBoxes, true);
10882 sortByWeight(rightBoxes, false);
10883 sortByWeight(topBoxes, true);
10884 sortByWeight(bottomBoxes, false);
10885
10886 // Essentially we now have any number of boxes on each of the 4 sides.
10887 // Our canvas looks like the following.
10888 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
10889 // B1 is the bottom axis
10890 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
10891 // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
10892 // an error will be thrown.
10893 //
10894 // |----------------------------------------------------|
10895 // | T1 (Full Width) |
10896 // |----------------------------------------------------|
10897 // | | | T2 | |
10898 // | |----|-------------------------------------|----|
10899 // | | | C1 | | C2 | |
10900 // | | |----| |----| |
10901 // | | | | |
10902 // | L1 | L2 | ChartArea (C0) | R1 |
10903 // | | | | |
10904 // | | |----| |----| |
10905 // | | | C3 | | C4 | |
10906 // | |----|-------------------------------------|----|
10907 // | | | B1 | |
10908 // |----------------------------------------------------|
10909 // | B2 (Full Width) |
10910 // |----------------------------------------------------|
10911 //
10912 // What we do to find the best sizing, we do the following
10913 // 1. Determine the minimum size of the chart area.
10914 // 2. Split the remaining width equally between each vertical axis
10915 // 3. Split the remaining height equally between each horizontal axis
10916 // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
10917 // 5. Adjust the sizes of each axis based on it's minimum reported size.
10918 // 6. Refit each axis
10919 // 7. Position each axis in the final location
10920 // 8. Tell the chart the final location of the chart area
10921 // 9. Tell any axes that overlay the chart area the positions of the chart area
10922
10923 // Step 1
10924 var chartWidth = width - leftPadding - rightPadding;
10925 var chartHeight = height - topPadding - bottomPadding;
10926 var chartAreaWidth = chartWidth / 2; // min 50%
10927 var chartAreaHeight = chartHeight / 2; // min 50%
10928
10929 // Step 2
10930 var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
10931
10932 // Step 3
10933 var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
10934
10935 // Step 4
10936 var maxChartAreaWidth = chartWidth;
10937 var maxChartAreaHeight = chartHeight;
10938 var minBoxSizes = [];
10939
10940 function getMinimumBoxSize(box) {
10941 var minSize;
10942 var isHorizontal = box.isHorizontal();
10943
10944 if (isHorizontal) {
10945 minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
10946 maxChartAreaHeight -= minSize.height;
10947 } else {
10948 minSize = box.update(verticalBoxWidth, maxChartAreaHeight);
10949 maxChartAreaWidth -= minSize.width;
10950 }
10951
10952 minBoxSizes.push({
10953 horizontal: isHorizontal,
10954 minSize: minSize,
10955 box: box,
10956 });
10957 }
10958
10959 helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
10960
10961 // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
10962 var maxHorizontalLeftPadding = 0;
10963 var maxHorizontalRightPadding = 0;
10964 var maxVerticalTopPadding = 0;
10965 var maxVerticalBottomPadding = 0;
10966
10967 helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
10968 if (horizontalBox.getPadding) {
10969 var boxPadding = horizontalBox.getPadding();
10970 maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
10971 maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
10972 }
10973 });
10974
10975 helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
10976 if (verticalBox.getPadding) {
10977 var boxPadding = verticalBox.getPadding();
10978 maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
10979 maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
10980 }
10981 });
10982
10983 // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
10984 // be if the axes are drawn at their minimum sizes.
10985 // Steps 5 & 6
10986 var totalLeftBoxesWidth = leftPadding;
10987 var totalRightBoxesWidth = rightPadding;
10988 var totalTopBoxesHeight = topPadding;
10989 var totalBottomBoxesHeight = bottomPadding;
10990
10991 // Function to fit a box
10992 function fitBox(box) {
10993 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
10994 return minBox.box === box;
10995 });
10996
10997 if (minBoxSize) {
10998 if (box.isHorizontal()) {
10999 var scaleMargin = {
11000 left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
11001 right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
11002 top: 0,
11003 bottom: 0
11004 };
11005
11006 // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
11007 // on the margin. Sometimes they need to increase in size slightly
11008 box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
11009 } else {
11010 box.update(minBoxSize.minSize.width, maxChartAreaHeight);
11011 }
11012 }
11013 }
11014
11015 // Update, and calculate the left and right margins for the horizontal boxes
11016 helpers.each(leftBoxes.concat(rightBoxes), fitBox);
11017
11018 helpers.each(leftBoxes, function(box) {
11019 totalLeftBoxesWidth += box.width;
11020 });
11021
11022 helpers.each(rightBoxes, function(box) {
11023 totalRightBoxesWidth += box.width;
11024 });
11025
11026 // Set the Left and Right margins for the horizontal boxes
11027 helpers.each(topBoxes.concat(bottomBoxes), fitBox);
11028
11029 // Figure out how much margin is on the top and bottom of the vertical boxes
11030 helpers.each(topBoxes, function(box) {
11031 totalTopBoxesHeight += box.height;
11032 });
11033
11034 helpers.each(bottomBoxes, function(box) {
11035 totalBottomBoxesHeight += box.height;
11036 });
11037
11038 function finalFitVerticalBox(box) {
11039 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
11040 return minSize.box === box;
11041 });
11042
11043 var scaleMargin = {
11044 left: 0,
11045 right: 0,
11046 top: totalTopBoxesHeight,
11047 bottom: totalBottomBoxesHeight
11048 };
11049
11050 if (minBoxSize) {
11051 box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
11052 }
11053 }
11054
11055 // Let the left layout know the final margin
11056 helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
11057
11058 // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
11059 totalLeftBoxesWidth = leftPadding;
11060 totalRightBoxesWidth = rightPadding;
11061 totalTopBoxesHeight = topPadding;
11062 totalBottomBoxesHeight = bottomPadding;
11063
11064 helpers.each(leftBoxes, function(box) {
11065 totalLeftBoxesWidth += box.width;
11066 });
11067
11068 helpers.each(rightBoxes, function(box) {
11069 totalRightBoxesWidth += box.width;
11070 });
11071
11072 helpers.each(topBoxes, function(box) {
11073 totalTopBoxesHeight += box.height;
11074 });
11075 helpers.each(bottomBoxes, function(box) {
11076 totalBottomBoxesHeight += box.height;
11077 });
11078
11079 // We may be adding some padding to account for rotated x axis labels
11080 var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
11081 totalLeftBoxesWidth += leftPaddingAddition;
11082 totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
11083
11084 var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
11085 totalTopBoxesHeight += topPaddingAddition;
11086 totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
11087
11088 // Figure out if our chart area changed. This would occur if the dataset layout label rotation
11089 // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
11090 // without calling `fit` again
11091 var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
11092 var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
11093
11094 if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
11095 helpers.each(leftBoxes, function(box) {
11096 box.height = newMaxChartAreaHeight;
11097 });
11098
11099 helpers.each(rightBoxes, function(box) {
11100 box.height = newMaxChartAreaHeight;
11101 });
11102
11103 helpers.each(topBoxes, function(box) {
11104 if (!box.fullWidth) {
11105 box.width = newMaxChartAreaWidth;
11106 }
11107 });
11108
11109 helpers.each(bottomBoxes, function(box) {
11110 if (!box.fullWidth) {
11111 box.width = newMaxChartAreaWidth;
11112 }
11113 });
11114
11115 maxChartAreaHeight = newMaxChartAreaHeight;
11116 maxChartAreaWidth = newMaxChartAreaWidth;
11117 }
11118
11119 // Step 7 - Position the boxes
11120 var left = leftPadding + leftPaddingAddition;
11121 var top = topPadding + topPaddingAddition;
11122
11123 function placeBox(box) {
11124 if (box.isHorizontal()) {
11125 box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
11126 box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
11127 box.top = top;
11128 box.bottom = top + box.height;
11129
11130 // Move to next point
11131 top = box.bottom;
11132
11133 } else {
11134
11135 box.left = left;
11136 box.right = left + box.width;
11137 box.top = totalTopBoxesHeight;
11138 box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
11139
11140 // Move to next point
11141 left = box.right;
11142 }
11143 }
11144
11145 helpers.each(leftBoxes.concat(topBoxes), placeBox);
11146
11147 // Account for chart width and height
11148 left += maxChartAreaWidth;
11149 top += maxChartAreaHeight;
11150
11151 helpers.each(rightBoxes, placeBox);
11152 helpers.each(bottomBoxes, placeBox);
11153
11154 // Step 8
11155 chart.chartArea = {
11156 left: totalLeftBoxesWidth,
11157 top: totalTopBoxesHeight,
11158 right: totalLeftBoxesWidth + maxChartAreaWidth,
11159 bottom: totalTopBoxesHeight + maxChartAreaHeight
11160 };
11161
11162 // Step 9
11163 helpers.each(chartAreaBoxes, function(box) {
11164 box.left = chart.chartArea.left;
11165 box.top = chart.chartArea.top;
11166 box.right = chart.chartArea.right;
11167 box.bottom = chart.chartArea.bottom;
11168
11169 box.update(maxChartAreaWidth, maxChartAreaHeight);
11170 });
11171 }
11172};
11173
11174},{"45":45}],31:[function(require,module,exports){
11175'use strict';
11176
11177var defaults = require(25);
11178var helpers = require(45);
11179
11180defaults._set('global', {
11181 plugins: {}
11182});
11183
11184/**
11185 * The plugin service singleton
11186 * @namespace Chart.plugins
11187 * @since 2.1.0
11188 */
11189module.exports = {
11190 /**
11191 * Globally registered plugins.
11192 * @private
11193 */
11194 _plugins: [],
11195
11196 /**
11197 * This identifier is used to invalidate the descriptors cache attached to each chart
11198 * when a global plugin is registered or unregistered. In this case, the cache ID is
11199 * incremented and descriptors are regenerated during following API calls.
11200 * @private
11201 */
11202 _cacheId: 0,
11203
11204 /**
11205 * Registers the given plugin(s) if not already registered.
11206 * @param {Array|Object} plugins plugin instance(s).
11207 */
11208 register: function(plugins) {
11209 var p = this._plugins;
11210 ([]).concat(plugins).forEach(function(plugin) {
11211 if (p.indexOf(plugin) === -1) {
11212 p.push(plugin);
11213 }
11214 });
11215
11216 this._cacheId++;
11217 },
11218
11219 /**
11220 * Unregisters the given plugin(s) only if registered.
11221 * @param {Array|Object} plugins plugin instance(s).
11222 */
11223 unregister: function(plugins) {
11224 var p = this._plugins;
11225 ([]).concat(plugins).forEach(function(plugin) {
11226 var idx = p.indexOf(plugin);
11227 if (idx !== -1) {
11228 p.splice(idx, 1);
11229 }
11230 });
11231
11232 this._cacheId++;
11233 },
11234
11235 /**
11236 * Remove all registered plugins.
11237 * @since 2.1.5
11238 */
11239 clear: function() {
11240 this._plugins = [];
11241 this._cacheId++;
11242 },
11243
11244 /**
11245 * Returns the number of registered plugins?
11246 * @returns {Number}
11247 * @since 2.1.5
11248 */
11249 count: function() {
11250 return this._plugins.length;
11251 },
11252
11253 /**
11254 * Returns all registered plugin instances.
11255 * @returns {Array} array of plugin objects.
11256 * @since 2.1.5
11257 */
11258 getAll: function() {
11259 return this._plugins;
11260 },
11261
11262 /**
11263 * Calls enabled plugins for `chart` on the specified hook and with the given args.
11264 * This method immediately returns as soon as a plugin explicitly returns false. The
11265 * returned value can be used, for instance, to interrupt the current action.
11266 * @param {Object} chart - The chart instance for which plugins should be called.
11267 * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
11268 * @param {Array} [args] - Extra arguments to apply to the hook call.
11269 * @returns {Boolean} false if any of the plugins return false, else returns true.
11270 */
11271 notify: function(chart, hook, args) {
11272 var descriptors = this.descriptors(chart);
11273 var ilen = descriptors.length;
11274 var i, descriptor, plugin, params, method;
11275
11276 for (i = 0; i < ilen; ++i) {
11277 descriptor = descriptors[i];
11278 plugin = descriptor.plugin;
11279 method = plugin[hook];
11280 if (typeof method === 'function') {
11281 params = [chart].concat(args || []);
11282 params.push(descriptor.options);
11283 if (method.apply(plugin, params) === false) {
11284 return false;
11285 }
11286 }
11287 }
11288
11289 return true;
11290 },
11291
11292 /**
11293 * Returns descriptors of enabled plugins for the given chart.
11294 * @returns {Array} [{ plugin, options }]
11295 * @private
11296 */
11297 descriptors: function(chart) {
11298 var cache = chart.$plugins || (chart.$plugins = {});
11299 if (cache.id === this._cacheId) {
11300 return cache.descriptors;
11301 }
11302
11303 var plugins = [];
11304 var descriptors = [];
11305 var config = (chart && chart.config) || {};
11306 var options = (config.options && config.options.plugins) || {};
11307
11308 this._plugins.concat(config.plugins || []).forEach(function(plugin) {
11309 var idx = plugins.indexOf(plugin);
11310 if (idx !== -1) {
11311 return;
11312 }
11313
11314 var id = plugin.id;
11315 var opts = options[id];
11316 if (opts === false) {
11317 return;
11318 }
11319
11320 if (opts === true) {
11321 opts = helpers.clone(defaults.global.plugins[id]);
11322 }
11323
11324 plugins.push(plugin);
11325 descriptors.push({
11326 plugin: plugin,
11327 options: opts || {}
11328 });
11329 });
11330
11331 cache.descriptors = descriptors;
11332 cache.id = this._cacheId;
11333 return descriptors;
11334 },
11335
11336 /**
11337 * Invalidates cache for the given chart: descriptors hold a reference on plugin option,
11338 * but in some cases, this reference can be changed by the user when updating options.
11339 * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
11340 * @private
11341 */
11342 _invalidate: function(chart) {
11343 delete chart.$plugins;
11344 }
11345};
11346
11347/**
11348 * Plugin extension hooks.
11349 * @interface IPlugin
11350 * @since 2.1.0
11351 */
11352/**
11353 * @method IPlugin#beforeInit
11354 * @desc Called before initializing `chart`.
11355 * @param {Chart.Controller} chart - The chart instance.
11356 * @param {Object} options - The plugin options.
11357 */
11358/**
11359 * @method IPlugin#afterInit
11360 * @desc Called after `chart` has been initialized and before the first update.
11361 * @param {Chart.Controller} chart - The chart instance.
11362 * @param {Object} options - The plugin options.
11363 */
11364/**
11365 * @method IPlugin#beforeUpdate
11366 * @desc Called before updating `chart`. If any plugin returns `false`, the update
11367 * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
11368 * @param {Chart.Controller} chart - The chart instance.
11369 * @param {Object} options - The plugin options.
11370 * @returns {Boolean} `false` to cancel the chart update.
11371 */
11372/**
11373 * @method IPlugin#afterUpdate
11374 * @desc Called after `chart` has been updated and before rendering. Note that this
11375 * hook will not be called if the chart update has been previously cancelled.
11376 * @param {Chart.Controller} chart - The chart instance.
11377 * @param {Object} options - The plugin options.
11378 */
11379/**
11380 * @method IPlugin#beforeDatasetsUpdate
11381 * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
11382 * the datasets update is cancelled until another `update` is triggered.
11383 * @param {Chart.Controller} chart - The chart instance.
11384 * @param {Object} options - The plugin options.
11385 * @returns {Boolean} false to cancel the datasets update.
11386 * @since version 2.1.5
11387*/
11388/**
11389 * @method IPlugin#afterDatasetsUpdate
11390 * @desc Called after the `chart` datasets have been updated. Note that this hook
11391 * will not be called if the datasets update has been previously cancelled.
11392 * @param {Chart.Controller} chart - The chart instance.
11393 * @param {Object} options - The plugin options.
11394 * @since version 2.1.5
11395 */
11396/**
11397 * @method IPlugin#beforeDatasetUpdate
11398 * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
11399 * returns `false`, the datasets update is cancelled until another `update` is triggered.
11400 * @param {Chart} chart - The chart instance.
11401 * @param {Object} args - The call arguments.
11402 * @param {Number} args.index - The dataset index.
11403 * @param {Object} args.meta - The dataset metadata.
11404 * @param {Object} options - The plugin options.
11405 * @returns {Boolean} `false` to cancel the chart datasets drawing.
11406 */
11407/**
11408 * @method IPlugin#afterDatasetUpdate
11409 * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
11410 * that this hook will not be called if the datasets update has been previously cancelled.
11411 * @param {Chart} chart - The chart instance.
11412 * @param {Object} args - The call arguments.
11413 * @param {Number} args.index - The dataset index.
11414 * @param {Object} args.meta - The dataset metadata.
11415 * @param {Object} options - The plugin options.
11416 */
11417/**
11418 * @method IPlugin#beforeLayout
11419 * @desc Called before laying out `chart`. If any plugin returns `false`,
11420 * the layout update is cancelled until another `update` is triggered.
11421 * @param {Chart.Controller} chart - The chart instance.
11422 * @param {Object} options - The plugin options.
11423 * @returns {Boolean} `false` to cancel the chart layout.
11424 */
11425/**
11426 * @method IPlugin#afterLayout
11427 * @desc Called after the `chart` has been layed out. Note that this hook will not
11428 * be called if the layout update has been previously cancelled.
11429 * @param {Chart.Controller} chart - The chart instance.
11430 * @param {Object} options - The plugin options.
11431 */
11432/**
11433 * @method IPlugin#beforeRender
11434 * @desc Called before rendering `chart`. If any plugin returns `false`,
11435 * the rendering is cancelled until another `render` is triggered.
11436 * @param {Chart.Controller} chart - The chart instance.
11437 * @param {Object} options - The plugin options.
11438 * @returns {Boolean} `false` to cancel the chart rendering.
11439 */
11440/**
11441 * @method IPlugin#afterRender
11442 * @desc Called after the `chart` has been fully rendered (and animation completed). Note
11443 * that this hook will not be called if the rendering has been previously cancelled.
11444 * @param {Chart.Controller} chart - The chart instance.
11445 * @param {Object} options - The plugin options.
11446 */
11447/**
11448 * @method IPlugin#beforeDraw
11449 * @desc Called before drawing `chart` at every animation frame specified by the given
11450 * easing value. If any plugin returns `false`, the frame drawing is cancelled until
11451 * another `render` is triggered.
11452 * @param {Chart.Controller} chart - The chart instance.
11453 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
11454 * @param {Object} options - The plugin options.
11455 * @returns {Boolean} `false` to cancel the chart drawing.
11456 */
11457/**
11458 * @method IPlugin#afterDraw
11459 * @desc Called after the `chart` has been drawn for the specific easing value. Note
11460 * that this hook will not be called if the drawing has been previously cancelled.
11461 * @param {Chart.Controller} chart - The chart instance.
11462 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
11463 * @param {Object} options - The plugin options.
11464 */
11465/**
11466 * @method IPlugin#beforeDatasetsDraw
11467 * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
11468 * the datasets drawing is cancelled until another `render` is triggered.
11469 * @param {Chart.Controller} chart - The chart instance.
11470 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
11471 * @param {Object} options - The plugin options.
11472 * @returns {Boolean} `false` to cancel the chart datasets drawing.
11473 */
11474/**
11475 * @method IPlugin#afterDatasetsDraw
11476 * @desc Called after the `chart` datasets have been drawn. Note that this hook
11477 * will not be called if the datasets drawing has been previously cancelled.
11478 * @param {Chart.Controller} chart - The chart instance.
11479 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
11480 * @param {Object} options - The plugin options.
11481 */
11482/**
11483 * @method IPlugin#beforeDatasetDraw
11484 * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
11485 * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
11486 * is cancelled until another `render` is triggered.
11487 * @param {Chart} chart - The chart instance.
11488 * @param {Object} args - The call arguments.
11489 * @param {Number} args.index - The dataset index.
11490 * @param {Object} args.meta - The dataset metadata.
11491 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
11492 * @param {Object} options - The plugin options.
11493 * @returns {Boolean} `false` to cancel the chart datasets drawing.
11494 */
11495/**
11496 * @method IPlugin#afterDatasetDraw
11497 * @desc Called after the `chart` datasets at the given `args.index` have been drawn
11498 * (datasets are drawn in the reverse order). Note that this hook will not be called
11499 * if the datasets drawing has been previously cancelled.
11500 * @param {Chart} chart - The chart instance.
11501 * @param {Object} args - The call arguments.
11502 * @param {Number} args.index - The dataset index.
11503 * @param {Object} args.meta - The dataset metadata.
11504 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
11505 * @param {Object} options - The plugin options.
11506 */
11507/**
11508 * @method IPlugin#beforeTooltipDraw
11509 * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
11510 * the tooltip drawing is cancelled until another `render` is triggered.
11511 * @param {Chart} chart - The chart instance.
11512 * @param {Object} args - The call arguments.
11513 * @param {Object} args.tooltip - The tooltip.
11514 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
11515 * @param {Object} options - The plugin options.
11516 * @returns {Boolean} `false` to cancel the chart tooltip drawing.
11517 */
11518/**
11519 * @method IPlugin#afterTooltipDraw
11520 * @desc Called after drawing the `tooltip`. Note that this hook will not
11521 * be called if the tooltip drawing has been previously cancelled.
11522 * @param {Chart} chart - The chart instance.
11523 * @param {Object} args - The call arguments.
11524 * @param {Object} args.tooltip - The tooltip.
11525 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
11526 * @param {Object} options - The plugin options.
11527 */
11528/**
11529 * @method IPlugin#beforeEvent
11530 * @desc Called before processing the specified `event`. If any plugin returns `false`,
11531 * the event will be discarded.
11532 * @param {Chart.Controller} chart - The chart instance.
11533 * @param {IEvent} event - The event object.
11534 * @param {Object} options - The plugin options.
11535 */
11536/**
11537 * @method IPlugin#afterEvent
11538 * @desc Called after the `event` has been consumed. Note that this hook
11539 * will not be called if the `event` has been previously discarded.
11540 * @param {Chart.Controller} chart - The chart instance.
11541 * @param {IEvent} event - The event object.
11542 * @param {Object} options - The plugin options.
11543 */
11544/**
11545 * @method IPlugin#resize
11546 * @desc Called after the chart as been resized.
11547 * @param {Chart.Controller} chart - The chart instance.
11548 * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
11549 * @param {Object} options - The plugin options.
11550 */
11551/**
11552 * @method IPlugin#destroy
11553 * @desc Called after the chart as been destroyed.
11554 * @param {Chart.Controller} chart - The chart instance.
11555 * @param {Object} options - The plugin options.
11556 */
11557
11558},{"25":25,"45":45}],32:[function(require,module,exports){
11559'use strict';
11560
11561var defaults = require(25);
11562var Element = require(26);
11563var helpers = require(45);
11564var Ticks = require(34);
11565
11566defaults._set('scale', {
11567 display: true,
11568 position: 'left',
11569 offset: false,
11570
11571 // grid line settings
11572 gridLines: {
11573 display: true,
11574 color: 'rgba(0, 0, 0, 0.1)',
11575 lineWidth: 1,
11576 drawBorder: true,
11577 drawOnChartArea: true,
11578 drawTicks: true,
11579 tickMarkLength: 10,
11580 zeroLineWidth: 1,
11581 zeroLineColor: 'rgba(0,0,0,0.25)',
11582 zeroLineBorderDash: [],
11583 zeroLineBorderDashOffset: 0.0,
11584 offsetGridLines: false,
11585 borderDash: [],
11586 borderDashOffset: 0.0
11587 },
11588
11589 // scale label
11590 scaleLabel: {
11591 // display property
11592 display: false,
11593
11594 // actual label
11595 labelString: '',
11596
11597 // line height
11598 lineHeight: 1.2,
11599
11600 // top/bottom padding
11601 padding: {
11602 top: 4,
11603 bottom: 4
11604 }
11605 },
11606
11607 // label settings
11608 ticks: {
11609 beginAtZero: false,
11610 minRotation: 0,
11611 maxRotation: 50,
11612 mirror: false,
11613 padding: 0,
11614 reverse: false,
11615 display: true,
11616 autoSkip: true,
11617 autoSkipPadding: 0,
11618 labelOffset: 0,
11619 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
11620 callback: Ticks.formatters.values,
11621 minor: {},
11622 major: {}
11623 }
11624});
11625
11626function labelsFromTicks(ticks) {
11627 var labels = [];
11628 var i, ilen;
11629
11630 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
11631 labels.push(ticks[i].label);
11632 }
11633
11634 return labels;
11635}
11636
11637function getLineValue(scale, index, offsetGridLines) {
11638 var lineValue = scale.getPixelForTick(index);
11639
11640 if (offsetGridLines) {
11641 if (index === 0) {
11642 lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
11643 } else {
11644 lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
11645 }
11646 }
11647 return lineValue;
11648}
11649
11650module.exports = function(Chart) {
11651
11652 function computeTextSize(context, tick, font) {
11653 return helpers.isArray(tick) ?
11654 helpers.longestText(context, font, tick) :
11655 context.measureText(tick).width;
11656 }
11657
11658 function parseFontOptions(options) {
11659 var valueOrDefault = helpers.valueOrDefault;
11660 var globalDefaults = defaults.global;
11661 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
11662 var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
11663 var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
11664
11665 return {
11666 size: size,
11667 style: style,
11668 family: family,
11669 font: helpers.fontString(size, style, family)
11670 };
11671 }
11672
11673 function parseLineHeight(options) {
11674 return helpers.options.toLineHeight(
11675 helpers.valueOrDefault(options.lineHeight, 1.2),
11676 helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize));
11677 }
11678
11679 Chart.Scale = Element.extend({
11680 /**
11681 * Get the padding needed for the scale
11682 * @method getPadding
11683 * @private
11684 * @returns {Padding} the necessary padding
11685 */
11686 getPadding: function() {
11687 var me = this;
11688 return {
11689 left: me.paddingLeft || 0,
11690 top: me.paddingTop || 0,
11691 right: me.paddingRight || 0,
11692 bottom: me.paddingBottom || 0
11693 };
11694 },
11695
11696 /**
11697 * Returns the scale tick objects ({label, major})
11698 * @since 2.7
11699 */
11700 getTicks: function() {
11701 return this._ticks;
11702 },
11703
11704 // These methods are ordered by lifecyle. Utilities then follow.
11705 // Any function defined here is inherited by all scale types.
11706 // Any function can be extended by the scale type
11707
11708 mergeTicksOptions: function() {
11709 var ticks = this.options.ticks;
11710 if (ticks.minor === false) {
11711 ticks.minor = {
11712 display: false
11713 };
11714 }
11715 if (ticks.major === false) {
11716 ticks.major = {
11717 display: false
11718 };
11719 }
11720 for (var key in ticks) {
11721 if (key !== 'major' && key !== 'minor') {
11722 if (typeof ticks.minor[key] === 'undefined') {
11723 ticks.minor[key] = ticks[key];
11724 }
11725 if (typeof ticks.major[key] === 'undefined') {
11726 ticks.major[key] = ticks[key];
11727 }
11728 }
11729 }
11730 },
11731 beforeUpdate: function() {
11732 helpers.callback(this.options.beforeUpdate, [this]);
11733 },
11734 update: function(maxWidth, maxHeight, margins) {
11735 var me = this;
11736 var i, ilen, labels, label, ticks, tick;
11737
11738 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11739 me.beforeUpdate();
11740
11741 // Absorb the master measurements
11742 me.maxWidth = maxWidth;
11743 me.maxHeight = maxHeight;
11744 me.margins = helpers.extend({
11745 left: 0,
11746 right: 0,
11747 top: 0,
11748 bottom: 0
11749 }, margins);
11750 me.longestTextCache = me.longestTextCache || {};
11751
11752 // Dimensions
11753 me.beforeSetDimensions();
11754 me.setDimensions();
11755 me.afterSetDimensions();
11756
11757 // Data min/max
11758 me.beforeDataLimits();
11759 me.determineDataLimits();
11760 me.afterDataLimits();
11761
11762 // Ticks - `this.ticks` is now DEPRECATED!
11763 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
11764 // and must not be accessed directly from outside this class. `this.ticks` being
11765 // around for long time and not marked as private, we can't change its structure
11766 // without unexpected breaking changes. If you need to access the scale ticks,
11767 // use scale.getTicks() instead.
11768
11769 me.beforeBuildTicks();
11770
11771 // New implementations should return an array of objects but for BACKWARD COMPAT,
11772 // we still support no return (`this.ticks` internally set by calling this method).
11773 ticks = me.buildTicks() || [];
11774
11775 me.afterBuildTicks();
11776
11777 me.beforeTickToLabelConversion();
11778
11779 // New implementations should return the formatted tick labels but for BACKWARD
11780 // COMPAT, we still support no return (`this.ticks` internally changed by calling
11781 // this method and supposed to contain only string values).
11782 labels = me.convertTicksToLabels(ticks) || me.ticks;
11783
11784 me.afterTickToLabelConversion();
11785
11786 me.ticks = labels; // BACKWARD COMPATIBILITY
11787
11788 // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
11789
11790 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
11791 for (i = 0, ilen = labels.length; i < ilen; ++i) {
11792 label = labels[i];
11793 tick = ticks[i];
11794 if (!tick) {
11795 ticks.push(tick = {
11796 label: label,
11797 major: false
11798 });
11799 } else {
11800 tick.label = label;
11801 }
11802 }
11803
11804 me._ticks = ticks;
11805
11806 // Tick Rotation
11807 me.beforeCalculateTickRotation();
11808 me.calculateTickRotation();
11809 me.afterCalculateTickRotation();
11810 // Fit
11811 me.beforeFit();
11812 me.fit();
11813 me.afterFit();
11814 //
11815 me.afterUpdate();
11816
11817 return me.minSize;
11818
11819 },
11820 afterUpdate: function() {
11821 helpers.callback(this.options.afterUpdate, [this]);
11822 },
11823
11824 //
11825
11826 beforeSetDimensions: function() {
11827 helpers.callback(this.options.beforeSetDimensions, [this]);
11828 },
11829 setDimensions: function() {
11830 var me = this;
11831 // Set the unconstrained dimension before label rotation
11832 if (me.isHorizontal()) {
11833 // Reset position before calculating rotation
11834 me.width = me.maxWidth;
11835 me.left = 0;
11836 me.right = me.width;
11837 } else {
11838 me.height = me.maxHeight;
11839
11840 // Reset position before calculating rotation
11841 me.top = 0;
11842 me.bottom = me.height;
11843 }
11844
11845 // Reset padding
11846 me.paddingLeft = 0;
11847 me.paddingTop = 0;
11848 me.paddingRight = 0;
11849 me.paddingBottom = 0;
11850 },
11851 afterSetDimensions: function() {
11852 helpers.callback(this.options.afterSetDimensions, [this]);
11853 },
11854
11855 // Data limits
11856 beforeDataLimits: function() {
11857 helpers.callback(this.options.beforeDataLimits, [this]);
11858 },
11859 determineDataLimits: helpers.noop,
11860 afterDataLimits: function() {
11861 helpers.callback(this.options.afterDataLimits, [this]);
11862 },
11863
11864 //
11865 beforeBuildTicks: function() {
11866 helpers.callback(this.options.beforeBuildTicks, [this]);
11867 },
11868 buildTicks: helpers.noop,
11869 afterBuildTicks: function() {
11870 helpers.callback(this.options.afterBuildTicks, [this]);
11871 },
11872
11873 beforeTickToLabelConversion: function() {
11874 helpers.callback(this.options.beforeTickToLabelConversion, [this]);
11875 },
11876 convertTicksToLabels: function() {
11877 var me = this;
11878 // Convert ticks to strings
11879 var tickOpts = me.options.ticks;
11880 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
11881 },
11882 afterTickToLabelConversion: function() {
11883 helpers.callback(this.options.afterTickToLabelConversion, [this]);
11884 },
11885
11886 //
11887
11888 beforeCalculateTickRotation: function() {
11889 helpers.callback(this.options.beforeCalculateTickRotation, [this]);
11890 },
11891 calculateTickRotation: function() {
11892 var me = this;
11893 var context = me.ctx;
11894 var tickOpts = me.options.ticks;
11895 var labels = labelsFromTicks(me._ticks);
11896
11897 // Get the width of each grid by calculating the difference
11898 // between x offsets between 0 and 1.
11899 var tickFont = parseFontOptions(tickOpts);
11900 context.font = tickFont.font;
11901
11902 var labelRotation = tickOpts.minRotation || 0;
11903
11904 if (labels.length && me.options.display && me.isHorizontal()) {
11905 var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache);
11906 var labelWidth = originalLabelWidth;
11907 var cosRotation, sinRotation;
11908
11909 // Allow 3 pixels x2 padding either side for label readability
11910 var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
11911
11912 // Max label rotation can be set or default to 90 - also act as a loop counter
11913 while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
11914 var angleRadians = helpers.toRadians(labelRotation);
11915 cosRotation = Math.cos(angleRadians);
11916 sinRotation = Math.sin(angleRadians);
11917
11918 if (sinRotation * originalLabelWidth > me.maxHeight) {
11919 // go back one step
11920 labelRotation--;
11921 break;
11922 }
11923
11924 labelRotation++;
11925 labelWidth = cosRotation * originalLabelWidth;
11926 }
11927 }
11928
11929 me.labelRotation = labelRotation;
11930 },
11931 afterCalculateTickRotation: function() {
11932 helpers.callback(this.options.afterCalculateTickRotation, [this]);
11933 },
11934
11935 //
11936
11937 beforeFit: function() {
11938 helpers.callback(this.options.beforeFit, [this]);
11939 },
11940 fit: function() {
11941 var me = this;
11942 // Reset
11943 var minSize = me.minSize = {
11944 width: 0,
11945 height: 0
11946 };
11947
11948 var labels = labelsFromTicks(me._ticks);
11949
11950 var opts = me.options;
11951 var tickOpts = opts.ticks;
11952 var scaleLabelOpts = opts.scaleLabel;
11953 var gridLineOpts = opts.gridLines;
11954 var display = opts.display;
11955 var isHorizontal = me.isHorizontal();
11956
11957 var tickFont = parseFontOptions(tickOpts);
11958 var tickMarkLength = opts.gridLines.tickMarkLength;
11959
11960 // Width
11961 if (isHorizontal) {
11962 // subtract the margins to line up with the chartArea if we are a full width scale
11963 minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
11964 } else {
11965 minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
11966 }
11967
11968 // height
11969 if (isHorizontal) {
11970 minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
11971 } else {
11972 minSize.height = me.maxHeight; // fill all the height
11973 }
11974
11975 // Are we showing a title for the scale?
11976 if (scaleLabelOpts.display && display) {
11977 var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts);
11978 var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding);
11979 var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height;
11980
11981 if (isHorizontal) {
11982 minSize.height += deltaHeight;
11983 } else {
11984 minSize.width += deltaHeight;
11985 }
11986 }
11987
11988 // Don't bother fitting the ticks if we are not showing them
11989 if (tickOpts.display && display) {
11990 var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache);
11991 var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels);
11992 var lineSpace = tickFont.size * 0.5;
11993 var tickPadding = me.options.ticks.padding;
11994
11995 if (isHorizontal) {
11996 // A horizontal axis is more constrained by the height.
11997 me.longestLabelWidth = largestTextWidth;
11998
11999 var angleRadians = helpers.toRadians(me.labelRotation);
12000 var cosRotation = Math.cos(angleRadians);
12001 var sinRotation = Math.sin(angleRadians);
12002
12003 // TODO - improve this calculation
12004 var labelHeight = (sinRotation * largestTextWidth)
12005 + (tickFont.size * tallestLabelHeightInLines)
12006 + (lineSpace * (tallestLabelHeightInLines - 1))
12007 + lineSpace; // padding
12008
12009 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
12010
12011 me.ctx.font = tickFont.font;
12012 var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font);
12013 var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font);
12014
12015 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
12016 // which means that the right padding is dominated by the font height
12017 if (me.labelRotation !== 0) {
12018 me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges
12019 me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3;
12020 } else {
12021 me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
12022 me.paddingRight = lastLabelWidth / 2 + 3;
12023 }
12024 } else {
12025 // A vertical axis is more constrained by the width. Labels are the
12026 // dominant factor here, so get that length first and account for padding
12027 if (tickOpts.mirror) {
12028 largestTextWidth = 0;
12029 } else {
12030 // use lineSpace for consistency with horizontal axis
12031 // tickPadding is not implemented for horizontal
12032 largestTextWidth += tickPadding + lineSpace;
12033 }
12034
12035 minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
12036
12037 me.paddingTop = tickFont.size / 2;
12038 me.paddingBottom = tickFont.size / 2;
12039 }
12040 }
12041
12042 me.handleMargins();
12043
12044 me.width = minSize.width;
12045 me.height = minSize.height;
12046 },
12047
12048 /**
12049 * Handle margins and padding interactions
12050 * @private
12051 */
12052 handleMargins: function() {
12053 var me = this;
12054 if (me.margins) {
12055 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
12056 me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
12057 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
12058 me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
12059 }
12060 },
12061
12062 afterFit: function() {
12063 helpers.callback(this.options.afterFit, [this]);
12064 },
12065
12066 // Shared Methods
12067 isHorizontal: function() {
12068 return this.options.position === 'top' || this.options.position === 'bottom';
12069 },
12070 isFullWidth: function() {
12071 return (this.options.fullWidth);
12072 },
12073
12074 // 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
12075 getRightValue: function(rawValue) {
12076 // Null and undefined values first
12077 if (helpers.isNullOrUndef(rawValue)) {
12078 return NaN;
12079 }
12080 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
12081 if (typeof rawValue === 'number' && !isFinite(rawValue)) {
12082 return NaN;
12083 }
12084 // If it is in fact an object, dive in one more level
12085 if (rawValue) {
12086 if (this.isHorizontal()) {
12087 if (rawValue.x !== undefined) {
12088 return this.getRightValue(rawValue.x);
12089 }
12090 } else if (rawValue.y !== undefined) {
12091 return this.getRightValue(rawValue.y);
12092 }
12093 }
12094
12095 // Value is good, return it
12096 return rawValue;
12097 },
12098
12099 /**
12100 * Used to get the value to display in the tooltip for the data at the given index
12101 * @param index
12102 * @param datasetIndex
12103 */
12104 getLabelForIndex: helpers.noop,
12105
12106 /**
12107 * Returns the location of the given data point. Value can either be an index or a numerical value
12108 * The coordinate (0, 0) is at the upper-left corner of the canvas
12109 * @param value
12110 * @param index
12111 * @param datasetIndex
12112 */
12113 getPixelForValue: helpers.noop,
12114
12115 /**
12116 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
12117 * The coordinate (0, 0) is at the upper-left corner of the canvas
12118 * @param pixel
12119 */
12120 getValueForPixel: helpers.noop,
12121
12122 /**
12123 * Returns the location of the tick at the given index
12124 * The coordinate (0, 0) is at the upper-left corner of the canvas
12125 */
12126 getPixelForTick: function(index) {
12127 var me = this;
12128 var offset = me.options.offset;
12129 if (me.isHorizontal()) {
12130 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
12131 var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
12132 var pixel = (tickWidth * index) + me.paddingLeft;
12133
12134 if (offset) {
12135 pixel += tickWidth / 2;
12136 }
12137
12138 var finalVal = me.left + Math.round(pixel);
12139 finalVal += me.isFullWidth() ? me.margins.left : 0;
12140 return finalVal;
12141 }
12142 var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
12143 return me.top + (index * (innerHeight / (me._ticks.length - 1)));
12144 },
12145
12146 /**
12147 * Utility for getting the pixel location of a percentage of scale
12148 * The coordinate (0, 0) is at the upper-left corner of the canvas
12149 */
12150 getPixelForDecimal: function(decimal) {
12151 var me = this;
12152 if (me.isHorizontal()) {
12153 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
12154 var valueOffset = (innerWidth * decimal) + me.paddingLeft;
12155
12156 var finalVal = me.left + Math.round(valueOffset);
12157 finalVal += me.isFullWidth() ? me.margins.left : 0;
12158 return finalVal;
12159 }
12160 return me.top + (decimal * me.height);
12161 },
12162
12163 /**
12164 * Returns the pixel for the minimum chart value
12165 * The coordinate (0, 0) is at the upper-left corner of the canvas
12166 */
12167 getBasePixel: function() {
12168 return this.getPixelForValue(this.getBaseValue());
12169 },
12170
12171 getBaseValue: function() {
12172 var me = this;
12173 var min = me.min;
12174 var max = me.max;
12175
12176 return me.beginAtZero ? 0 :
12177 min < 0 && max < 0 ? max :
12178 min > 0 && max > 0 ? min :
12179 0;
12180 },
12181
12182 /**
12183 * Returns a subset of ticks to be plotted to avoid overlapping labels.
12184 * @private
12185 */
12186 _autoSkip: function(ticks) {
12187 var skipRatio;
12188 var me = this;
12189 var isHorizontal = me.isHorizontal();
12190 var optionTicks = me.options.ticks.minor;
12191 var tickCount = ticks.length;
12192 var labelRotationRadians = helpers.toRadians(me.labelRotation);
12193 var cosRotation = Math.cos(labelRotationRadians);
12194 var longestRotatedLabel = me.longestLabelWidth * cosRotation;
12195 var result = [];
12196 var i, tick, shouldSkip;
12197
12198 // figure out the maximum number of gridlines to show
12199 var maxTicks;
12200 if (optionTicks.maxTicksLimit) {
12201 maxTicks = optionTicks.maxTicksLimit;
12202 }
12203
12204 if (isHorizontal) {
12205 skipRatio = false;
12206
12207 if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) {
12208 skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight)));
12209 }
12210
12211 // if they defined a max number of optionTicks,
12212 // increase skipRatio until that number is met
12213 if (maxTicks && tickCount > maxTicks) {
12214 skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks));
12215 }
12216 }
12217
12218 for (i = 0; i < tickCount; i++) {
12219 tick = ticks[i];
12220
12221 // Since we always show the last tick,we need may need to hide the last shown one before
12222 shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount);
12223 if (shouldSkip && i !== tickCount - 1) {
12224 // leave tick in place but make sure it's not displayed (#4635)
12225 delete tick.label;
12226 }
12227 result.push(tick);
12228 }
12229 return result;
12230 },
12231
12232 // Actually draw the scale on the canvas
12233 // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
12234 draw: function(chartArea) {
12235 var me = this;
12236 var options = me.options;
12237 if (!options.display) {
12238 return;
12239 }
12240
12241 var context = me.ctx;
12242 var globalDefaults = defaults.global;
12243 var optionTicks = options.ticks.minor;
12244 var optionMajorTicks = options.ticks.major || optionTicks;
12245 var gridLines = options.gridLines;
12246 var scaleLabel = options.scaleLabel;
12247
12248 var isRotated = me.labelRotation !== 0;
12249 var isHorizontal = me.isHorizontal();
12250
12251 var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
12252 var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
12253 var tickFont = parseFontOptions(optionTicks);
12254 var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
12255 var majorTickFont = parseFontOptions(optionMajorTicks);
12256
12257 var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
12258
12259 var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
12260 var scaleLabelFont = parseFontOptions(scaleLabel);
12261 var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
12262 var labelRotationRadians = helpers.toRadians(me.labelRotation);
12263
12264 var itemsToDraw = [];
12265
12266 var xTickStart = options.position === 'right' ? me.left : me.right - tl;
12267 var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
12268 var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
12269 var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
12270
12271 helpers.each(ticks, function(tick, index) {
12272 // autoskipper skipped this tick (#4635)
12273 if (helpers.isNullOrUndef(tick.label)) {
12274 return;
12275 }
12276
12277 var label = tick.label;
12278 var lineWidth, lineColor, borderDash, borderDashOffset;
12279 if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
12280 // Draw the first index specially
12281 lineWidth = gridLines.zeroLineWidth;
12282 lineColor = gridLines.zeroLineColor;
12283 borderDash = gridLines.zeroLineBorderDash;
12284 borderDashOffset = gridLines.zeroLineBorderDashOffset;
12285 } else {
12286 lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index);
12287 lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index);
12288 borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
12289 borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
12290 }
12291
12292 // Common properties
12293 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
12294 var textAlign = 'middle';
12295 var textBaseline = 'middle';
12296 var tickPadding = optionTicks.padding;
12297
12298 if (isHorizontal) {
12299 var labelYOffset = tl + tickPadding;
12300
12301 if (options.position === 'bottom') {
12302 // bottom
12303 textBaseline = !isRotated ? 'top' : 'middle';
12304 textAlign = !isRotated ? 'center' : 'right';
12305 labelY = me.top + labelYOffset;
12306 } else {
12307 // top
12308 textBaseline = !isRotated ? 'bottom' : 'middle';
12309 textAlign = !isRotated ? 'center' : 'left';
12310 labelY = me.bottom - labelYOffset;
12311 }
12312
12313 var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
12314 if (xLineValue < me.left) {
12315 lineColor = 'rgba(0,0,0,0)';
12316 }
12317 xLineValue += helpers.aliasPixel(lineWidth);
12318
12319 labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
12320
12321 tx1 = tx2 = x1 = x2 = xLineValue;
12322 ty1 = yTickStart;
12323 ty2 = yTickEnd;
12324 y1 = chartArea.top;
12325 y2 = chartArea.bottom;
12326 } else {
12327 var isLeft = options.position === 'left';
12328 var labelXOffset;
12329
12330 if (optionTicks.mirror) {
12331 textAlign = isLeft ? 'left' : 'right';
12332 labelXOffset = tickPadding;
12333 } else {
12334 textAlign = isLeft ? 'right' : 'left';
12335 labelXOffset = tl + tickPadding;
12336 }
12337
12338 labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
12339
12340 var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
12341 if (yLineValue < me.top) {
12342 lineColor = 'rgba(0,0,0,0)';
12343 }
12344 yLineValue += helpers.aliasPixel(lineWidth);
12345
12346 labelY = me.getPixelForTick(index) + optionTicks.labelOffset;
12347
12348 tx1 = xTickStart;
12349 tx2 = xTickEnd;
12350 x1 = chartArea.left;
12351 x2 = chartArea.right;
12352 ty1 = ty2 = y1 = y2 = yLineValue;
12353 }
12354
12355 itemsToDraw.push({
12356 tx1: tx1,
12357 ty1: ty1,
12358 tx2: tx2,
12359 ty2: ty2,
12360 x1: x1,
12361 y1: y1,
12362 x2: x2,
12363 y2: y2,
12364 labelX: labelX,
12365 labelY: labelY,
12366 glWidth: lineWidth,
12367 glColor: lineColor,
12368 glBorderDash: borderDash,
12369 glBorderDashOffset: borderDashOffset,
12370 rotation: -1 * labelRotationRadians,
12371 label: label,
12372 major: tick.major,
12373 textBaseline: textBaseline,
12374 textAlign: textAlign
12375 });
12376 });
12377
12378 // Draw all of the tick labels, tick marks, and grid lines at the correct places
12379 helpers.each(itemsToDraw, function(itemToDraw) {
12380 if (gridLines.display) {
12381 context.save();
12382 context.lineWidth = itemToDraw.glWidth;
12383 context.strokeStyle = itemToDraw.glColor;
12384 if (context.setLineDash) {
12385 context.setLineDash(itemToDraw.glBorderDash);
12386 context.lineDashOffset = itemToDraw.glBorderDashOffset;
12387 }
12388
12389 context.beginPath();
12390
12391 if (gridLines.drawTicks) {
12392 context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
12393 context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
12394 }
12395
12396 if (gridLines.drawOnChartArea) {
12397 context.moveTo(itemToDraw.x1, itemToDraw.y1);
12398 context.lineTo(itemToDraw.x2, itemToDraw.y2);
12399 }
12400
12401 context.stroke();
12402 context.restore();
12403 }
12404
12405 if (optionTicks.display) {
12406 // Make sure we draw text in the correct color and font
12407 context.save();
12408 context.translate(itemToDraw.labelX, itemToDraw.labelY);
12409 context.rotate(itemToDraw.rotation);
12410 context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
12411 context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
12412 context.textBaseline = itemToDraw.textBaseline;
12413 context.textAlign = itemToDraw.textAlign;
12414
12415 var label = itemToDraw.label;
12416 if (helpers.isArray(label)) {
12417 for (var i = 0, y = 0; i < label.length; ++i) {
12418 // We just make sure the multiline element is a string here..
12419 context.fillText('' + label[i], 0, y);
12420 // apply same lineSpacing as calculated @ L#320
12421 y += (tickFont.size * 1.5);
12422 }
12423 } else {
12424 context.fillText(label, 0, 0);
12425 }
12426 context.restore();
12427 }
12428 });
12429
12430 if (scaleLabel.display) {
12431 // Draw the scale label
12432 var scaleLabelX;
12433 var scaleLabelY;
12434 var rotation = 0;
12435 var halfLineHeight = parseLineHeight(scaleLabel) / 2;
12436
12437 if (isHorizontal) {
12438 scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
12439 scaleLabelY = options.position === 'bottom'
12440 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
12441 : me.top + halfLineHeight + scaleLabelPadding.top;
12442 } else {
12443 var isLeft = options.position === 'left';
12444 scaleLabelX = isLeft
12445 ? me.left + halfLineHeight + scaleLabelPadding.top
12446 : me.right - halfLineHeight - scaleLabelPadding.top;
12447 scaleLabelY = me.top + ((me.bottom - me.top) / 2);
12448 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
12449 }
12450
12451 context.save();
12452 context.translate(scaleLabelX, scaleLabelY);
12453 context.rotate(rotation);
12454 context.textAlign = 'center';
12455 context.textBaseline = 'middle';
12456 context.fillStyle = scaleLabelFontColor; // render in correct colour
12457 context.font = scaleLabelFont.font;
12458 context.fillText(scaleLabel.labelString, 0, 0);
12459 context.restore();
12460 }
12461
12462 if (gridLines.drawBorder) {
12463 // Draw the line at the edge of the axis
12464 context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0);
12465 context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0);
12466 var x1 = me.left;
12467 var x2 = me.right;
12468 var y1 = me.top;
12469 var y2 = me.bottom;
12470
12471 var aliasPixel = helpers.aliasPixel(context.lineWidth);
12472 if (isHorizontal) {
12473 y1 = y2 = options.position === 'top' ? me.bottom : me.top;
12474 y1 += aliasPixel;
12475 y2 += aliasPixel;
12476 } else {
12477 x1 = x2 = options.position === 'left' ? me.right : me.left;
12478 x1 += aliasPixel;
12479 x2 += aliasPixel;
12480 }
12481
12482 context.beginPath();
12483 context.moveTo(x1, y1);
12484 context.lineTo(x2, y2);
12485 context.stroke();
12486 }
12487 }
12488 });
12489};
12490
12491},{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){
12492'use strict';
12493
12494var defaults = require(25);
12495var helpers = require(45);
12496var layouts = require(30);
12497
12498module.exports = function(Chart) {
12499
12500 Chart.scaleService = {
12501 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
12502 // use the new chart options to grab the correct scale
12503 constructors: {},
12504 // Use a registration function so that we can move to an ES6 map when we no longer need to support
12505 // old browsers
12506
12507 // Scale config defaults
12508 defaults: {},
12509 registerScaleType: function(type, scaleConstructor, scaleDefaults) {
12510 this.constructors[type] = scaleConstructor;
12511 this.defaults[type] = helpers.clone(scaleDefaults);
12512 },
12513 getScaleConstructor: function(type) {
12514 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
12515 },
12516 getScaleDefaults: function(type) {
12517 // Return the scale defaults merged with the global settings so that we always use the latest ones
12518 return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {};
12519 },
12520 updateScaleDefaults: function(type, additions) {
12521 var me = this;
12522 if (me.defaults.hasOwnProperty(type)) {
12523 me.defaults[type] = helpers.extend(me.defaults[type], additions);
12524 }
12525 },
12526 addScalesToLayout: function(chart) {
12527 // Adds each scale to the chart.boxes array to be sized accordingly
12528 helpers.each(chart.scales, function(scale) {
12529 // Set ILayoutItem parameters for backwards compatibility
12530 scale.fullWidth = scale.options.fullWidth;
12531 scale.position = scale.options.position;
12532 scale.weight = scale.options.weight;
12533 layouts.addBox(chart, scale);
12534 });
12535 }
12536 };
12537};
12538
12539},{"25":25,"30":30,"45":45}],34:[function(require,module,exports){
12540'use strict';
12541
12542var helpers = require(45);
12543
12544/**
12545 * Namespace to hold static tick generation functions
12546 * @namespace Chart.Ticks
12547 */
12548module.exports = {
12549 /**
12550 * Namespace to hold formatters for different types of ticks
12551 * @namespace Chart.Ticks.formatters
12552 */
12553 formatters: {
12554 /**
12555 * Formatter for value labels
12556 * @method Chart.Ticks.formatters.values
12557 * @param value the value to display
12558 * @return {String|Array} the label to display
12559 */
12560 values: function(value) {
12561 return helpers.isArray(value) ? value : '' + value;
12562 },
12563
12564 /**
12565 * Formatter for linear numeric ticks
12566 * @method Chart.Ticks.formatters.linear
12567 * @param tickValue {Number} the value to be formatted
12568 * @param index {Number} the position of the tickValue parameter in the ticks array
12569 * @param ticks {Array<Number>} the list of ticks being converted
12570 * @return {String} string representation of the tickValue parameter
12571 */
12572 linear: function(tickValue, index, ticks) {
12573 // If we have lots of ticks, don't use the ones
12574 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
12575
12576 // If we have a number like 2.5 as the delta, figure out how many decimal places we need
12577 if (Math.abs(delta) > 1) {
12578 if (tickValue !== Math.floor(tickValue)) {
12579 // not an integer
12580 delta = tickValue - Math.floor(tickValue);
12581 }
12582 }
12583
12584 var logDelta = helpers.log10(Math.abs(delta));
12585 var tickString = '';
12586
12587 if (tickValue !== 0) {
12588 var numDecimal = -1 * Math.floor(logDelta);
12589 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
12590 tickString = tickValue.toFixed(numDecimal);
12591 } else {
12592 tickString = '0'; // never show decimal places for 0
12593 }
12594
12595 return tickString;
12596 },
12597
12598 logarithmic: function(tickValue, index, ticks) {
12599 var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));
12600
12601 if (tickValue === 0) {
12602 return '0';
12603 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
12604 return tickValue.toExponential();
12605 }
12606 return '';
12607 }
12608 }
12609};
12610
12611},{"45":45}],35:[function(require,module,exports){
12612'use strict';
12613
12614var defaults = require(25);
12615var Element = require(26);
12616var helpers = require(45);
12617
12618defaults._set('global', {
12619 tooltips: {
12620 enabled: true,
12621 custom: null,
12622 mode: 'nearest',
12623 position: 'average',
12624 intersect: true,
12625 backgroundColor: 'rgba(0,0,0,0.8)',
12626 titleFontStyle: 'bold',
12627 titleSpacing: 2,
12628 titleMarginBottom: 6,
12629 titleFontColor: '#fff',
12630 titleAlign: 'left',
12631 bodySpacing: 2,
12632 bodyFontColor: '#fff',
12633 bodyAlign: 'left',
12634 footerFontStyle: 'bold',
12635 footerSpacing: 2,
12636 footerMarginTop: 6,
12637 footerFontColor: '#fff',
12638 footerAlign: 'left',
12639 yPadding: 6,
12640 xPadding: 6,
12641 caretPadding: 2,
12642 caretSize: 5,
12643 cornerRadius: 6,
12644 multiKeyBackground: '#fff',
12645 displayColors: true,
12646 borderColor: 'rgba(0,0,0,0)',
12647 borderWidth: 0,
12648 callbacks: {
12649 // Args are: (tooltipItems, data)
12650 beforeTitle: helpers.noop,
12651 title: function(tooltipItems, data) {
12652 // Pick first xLabel for now
12653 var title = '';
12654 var labels = data.labels;
12655 var labelCount = labels ? labels.length : 0;
12656
12657 if (tooltipItems.length > 0) {
12658 var item = tooltipItems[0];
12659
12660 if (item.xLabel) {
12661 title = item.xLabel;
12662 } else if (labelCount > 0 && item.index < labelCount) {
12663 title = labels[item.index];
12664 }
12665 }
12666
12667 return title;
12668 },
12669 afterTitle: helpers.noop,
12670
12671 // Args are: (tooltipItems, data)
12672 beforeBody: helpers.noop,
12673
12674 // Args are: (tooltipItem, data)
12675 beforeLabel: helpers.noop,
12676 label: function(tooltipItem, data) {
12677 var label = data.datasets[tooltipItem.datasetIndex].label || '';
12678
12679 if (label) {
12680 label += ': ';
12681 }
12682 label += tooltipItem.yLabel;
12683 return label;
12684 },
12685 labelColor: function(tooltipItem, chart) {
12686 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
12687 var activeElement = meta.data[tooltipItem.index];
12688 var view = activeElement._view;
12689 return {
12690 borderColor: view.borderColor,
12691 backgroundColor: view.backgroundColor
12692 };
12693 },
12694 labelTextColor: function() {
12695 return this._options.bodyFontColor;
12696 },
12697 afterLabel: helpers.noop,
12698
12699 // Args are: (tooltipItems, data)
12700 afterBody: helpers.noop,
12701
12702 // Args are: (tooltipItems, data)
12703 beforeFooter: helpers.noop,
12704 footer: helpers.noop,
12705 afterFooter: helpers.noop
12706 }
12707 }
12708});
12709
12710module.exports = function(Chart) {
12711
12712 /**
12713 * Helper method to merge the opacity into a color
12714 */
12715 function mergeOpacity(colorString, opacity) {
12716 var color = helpers.color(colorString);
12717 return color.alpha(opacity * color.alpha()).rgbaString();
12718 }
12719
12720 // Helper to push or concat based on if the 2nd parameter is an array or not
12721 function pushOrConcat(base, toPush) {
12722 if (toPush) {
12723 if (helpers.isArray(toPush)) {
12724 // base = base.concat(toPush);
12725 Array.prototype.push.apply(base, toPush);
12726 } else {
12727 base.push(toPush);
12728 }
12729 }
12730
12731 return base;
12732 }
12733
12734 // Private helper to create a tooltip item model
12735 // @param element : the chart element (point, arc, bar) to create the tooltip item for
12736 // @return : new tooltip item
12737 function createTooltipItem(element) {
12738 var xScale = element._xScale;
12739 var yScale = element._yScale || element._scale; // handle radar || polarArea charts
12740 var index = element._index;
12741 var datasetIndex = element._datasetIndex;
12742
12743 return {
12744 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
12745 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
12746 index: index,
12747 datasetIndex: datasetIndex,
12748 x: element._model.x,
12749 y: element._model.y
12750 };
12751 }
12752
12753 /**
12754 * Helper to get the reset model for the tooltip
12755 * @param tooltipOpts {Object} the tooltip options
12756 */
12757 function getBaseModel(tooltipOpts) {
12758 var globalDefaults = defaults.global;
12759 var valueOrDefault = helpers.valueOrDefault;
12760
12761 return {
12762 // Positioning
12763 xPadding: tooltipOpts.xPadding,
12764 yPadding: tooltipOpts.yPadding,
12765 xAlign: tooltipOpts.xAlign,
12766 yAlign: tooltipOpts.yAlign,
12767
12768 // Body
12769 bodyFontColor: tooltipOpts.bodyFontColor,
12770 _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
12771 _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
12772 _bodyAlign: tooltipOpts.bodyAlign,
12773 bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
12774 bodySpacing: tooltipOpts.bodySpacing,
12775
12776 // Title
12777 titleFontColor: tooltipOpts.titleFontColor,
12778 _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
12779 _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
12780 titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
12781 _titleAlign: tooltipOpts.titleAlign,
12782 titleSpacing: tooltipOpts.titleSpacing,
12783 titleMarginBottom: tooltipOpts.titleMarginBottom,
12784
12785 // Footer
12786 footerFontColor: tooltipOpts.footerFontColor,
12787 _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
12788 _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
12789 footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
12790 _footerAlign: tooltipOpts.footerAlign,
12791 footerSpacing: tooltipOpts.footerSpacing,
12792 footerMarginTop: tooltipOpts.footerMarginTop,
12793
12794 // Appearance
12795 caretSize: tooltipOpts.caretSize,
12796 cornerRadius: tooltipOpts.cornerRadius,
12797 backgroundColor: tooltipOpts.backgroundColor,
12798 opacity: 0,
12799 legendColorBackground: tooltipOpts.multiKeyBackground,
12800 displayColors: tooltipOpts.displayColors,
12801 borderColor: tooltipOpts.borderColor,
12802 borderWidth: tooltipOpts.borderWidth
12803 };
12804 }
12805
12806 /**
12807 * Get the size of the tooltip
12808 */
12809 function getTooltipSize(tooltip, model) {
12810 var ctx = tooltip._chart.ctx;
12811
12812 var height = model.yPadding * 2; // Tooltip Padding
12813 var width = 0;
12814
12815 // Count of all lines in the body
12816 var body = model.body;
12817 var combinedBodyLength = body.reduce(function(count, bodyItem) {
12818 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
12819 }, 0);
12820 combinedBodyLength += model.beforeBody.length + model.afterBody.length;
12821
12822 var titleLineCount = model.title.length;
12823 var footerLineCount = model.footer.length;
12824 var titleFontSize = model.titleFontSize;
12825 var bodyFontSize = model.bodyFontSize;
12826 var footerFontSize = model.footerFontSize;
12827
12828 height += titleLineCount * titleFontSize; // Title Lines
12829 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
12830 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
12831 height += combinedBodyLength * bodyFontSize; // Body Lines
12832 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
12833 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
12834 height += footerLineCount * (footerFontSize); // Footer Lines
12835 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
12836
12837 // Title width
12838 var widthPadding = 0;
12839 var maxLineWidth = function(line) {
12840 width = Math.max(width, ctx.measureText(line).width + widthPadding);
12841 };
12842
12843 ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
12844 helpers.each(model.title, maxLineWidth);
12845
12846 // Body width
12847 ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
12848 helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
12849
12850 // Body lines may include some extra width due to the color box
12851 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
12852 helpers.each(body, function(bodyItem) {
12853 helpers.each(bodyItem.before, maxLineWidth);
12854 helpers.each(bodyItem.lines, maxLineWidth);
12855 helpers.each(bodyItem.after, maxLineWidth);
12856 });
12857
12858 // Reset back to 0
12859 widthPadding = 0;
12860
12861 // Footer width
12862 ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
12863 helpers.each(model.footer, maxLineWidth);
12864
12865 // Add padding
12866 width += 2 * model.xPadding;
12867
12868 return {
12869 width: width,
12870 height: height
12871 };
12872 }
12873
12874 /**
12875 * Helper to get the alignment of a tooltip given the size
12876 */
12877 function determineAlignment(tooltip, size) {
12878 var model = tooltip._model;
12879 var chart = tooltip._chart;
12880 var chartArea = tooltip._chart.chartArea;
12881 var xAlign = 'center';
12882 var yAlign = 'center';
12883
12884 if (model.y < size.height) {
12885 yAlign = 'top';
12886 } else if (model.y > (chart.height - size.height)) {
12887 yAlign = 'bottom';
12888 }
12889
12890 var lf, rf; // functions to determine left, right alignment
12891 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
12892 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
12893 var midX = (chartArea.left + chartArea.right) / 2;
12894 var midY = (chartArea.top + chartArea.bottom) / 2;
12895
12896 if (yAlign === 'center') {
12897 lf = function(x) {
12898 return x <= midX;
12899 };
12900 rf = function(x) {
12901 return x > midX;
12902 };
12903 } else {
12904 lf = function(x) {
12905 return x <= (size.width / 2);
12906 };
12907 rf = function(x) {
12908 return x >= (chart.width - (size.width / 2));
12909 };
12910 }
12911
12912 olf = function(x) {
12913 return x + size.width + model.caretSize + model.caretPadding > chart.width;
12914 };
12915 orf = function(x) {
12916 return x - size.width - model.caretSize - model.caretPadding < 0;
12917 };
12918 yf = function(y) {
12919 return y <= midY ? 'top' : 'bottom';
12920 };
12921
12922 if (lf(model.x)) {
12923 xAlign = 'left';
12924
12925 // Is tooltip too wide and goes over the right side of the chart.?
12926 if (olf(model.x)) {
12927 xAlign = 'center';
12928 yAlign = yf(model.y);
12929 }
12930 } else if (rf(model.x)) {
12931 xAlign = 'right';
12932
12933 // Is tooltip too wide and goes outside left edge of canvas?
12934 if (orf(model.x)) {
12935 xAlign = 'center';
12936 yAlign = yf(model.y);
12937 }
12938 }
12939
12940 var opts = tooltip._options;
12941 return {
12942 xAlign: opts.xAlign ? opts.xAlign : xAlign,
12943 yAlign: opts.yAlign ? opts.yAlign : yAlign
12944 };
12945 }
12946
12947 /**
12948 * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
12949 */
12950 function getBackgroundPoint(vm, size, alignment, chart) {
12951 // Background Position
12952 var x = vm.x;
12953 var y = vm.y;
12954
12955 var caretSize = vm.caretSize;
12956 var caretPadding = vm.caretPadding;
12957 var cornerRadius = vm.cornerRadius;
12958 var xAlign = alignment.xAlign;
12959 var yAlign = alignment.yAlign;
12960 var paddingAndSize = caretSize + caretPadding;
12961 var radiusAndPadding = cornerRadius + caretPadding;
12962
12963 if (xAlign === 'right') {
12964 x -= size.width;
12965 } else if (xAlign === 'center') {
12966 x -= (size.width / 2);
12967 if (x + size.width > chart.width) {
12968 x = chart.width - size.width;
12969 }
12970 if (x < 0) {
12971 x = 0;
12972 }
12973 }
12974
12975 if (yAlign === 'top') {
12976 y += paddingAndSize;
12977 } else if (yAlign === 'bottom') {
12978 y -= size.height + paddingAndSize;
12979 } else {
12980 y -= (size.height / 2);
12981 }
12982
12983 if (yAlign === 'center') {
12984 if (xAlign === 'left') {
12985 x += paddingAndSize;
12986 } else if (xAlign === 'right') {
12987 x -= paddingAndSize;
12988 }
12989 } else if (xAlign === 'left') {
12990 x -= radiusAndPadding;
12991 } else if (xAlign === 'right') {
12992 x += radiusAndPadding;
12993 }
12994
12995 return {
12996 x: x,
12997 y: y
12998 };
12999 }
13000
13001 Chart.Tooltip = Element.extend({
13002 initialize: function() {
13003 this._model = getBaseModel(this._options);
13004 this._lastActive = [];
13005 },
13006
13007 // Get the title
13008 // Args are: (tooltipItem, data)
13009 getTitle: function() {
13010 var me = this;
13011 var opts = me._options;
13012 var callbacks = opts.callbacks;
13013
13014 var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
13015 var title = callbacks.title.apply(me, arguments);
13016 var afterTitle = callbacks.afterTitle.apply(me, arguments);
13017
13018 var lines = [];
13019 lines = pushOrConcat(lines, beforeTitle);
13020 lines = pushOrConcat(lines, title);
13021 lines = pushOrConcat(lines, afterTitle);
13022
13023 return lines;
13024 },
13025
13026 // Args are: (tooltipItem, data)
13027 getBeforeBody: function() {
13028 var lines = this._options.callbacks.beforeBody.apply(this, arguments);
13029 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
13030 },
13031
13032 // Args are: (tooltipItem, data)
13033 getBody: function(tooltipItems, data) {
13034 var me = this;
13035 var callbacks = me._options.callbacks;
13036 var bodyItems = [];
13037
13038 helpers.each(tooltipItems, function(tooltipItem) {
13039 var bodyItem = {
13040 before: [],
13041 lines: [],
13042 after: []
13043 };
13044 pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
13045 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
13046 pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
13047
13048 bodyItems.push(bodyItem);
13049 });
13050
13051 return bodyItems;
13052 },
13053
13054 // Args are: (tooltipItem, data)
13055 getAfterBody: function() {
13056 var lines = this._options.callbacks.afterBody.apply(this, arguments);
13057 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
13058 },
13059
13060 // Get the footer and beforeFooter and afterFooter lines
13061 // Args are: (tooltipItem, data)
13062 getFooter: function() {
13063 var me = this;
13064 var callbacks = me._options.callbacks;
13065
13066 var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
13067 var footer = callbacks.footer.apply(me, arguments);
13068 var afterFooter = callbacks.afterFooter.apply(me, arguments);
13069
13070 var lines = [];
13071 lines = pushOrConcat(lines, beforeFooter);
13072 lines = pushOrConcat(lines, footer);
13073 lines = pushOrConcat(lines, afterFooter);
13074
13075 return lines;
13076 },
13077
13078 update: function(changed) {
13079 var me = this;
13080 var opts = me._options;
13081
13082 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
13083 // 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
13084 // which breaks any animations.
13085 var existingModel = me._model;
13086 var model = me._model = getBaseModel(opts);
13087 var active = me._active;
13088
13089 var data = me._data;
13090
13091 // In the case where active.length === 0 we need to keep these at existing values for good animations
13092 var alignment = {
13093 xAlign: existingModel.xAlign,
13094 yAlign: existingModel.yAlign
13095 };
13096 var backgroundPoint = {
13097 x: existingModel.x,
13098 y: existingModel.y
13099 };
13100 var tooltipSize = {
13101 width: existingModel.width,
13102 height: existingModel.height
13103 };
13104 var tooltipPosition = {
13105 x: existingModel.caretX,
13106 y: existingModel.caretY
13107 };
13108
13109 var i, len;
13110
13111 if (active.length) {
13112 model.opacity = 1;
13113
13114 var labelColors = [];
13115 var labelTextColors = [];
13116 tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition);
13117
13118 var tooltipItems = [];
13119 for (i = 0, len = active.length; i < len; ++i) {
13120 tooltipItems.push(createTooltipItem(active[i]));
13121 }
13122
13123 // If the user provided a filter function, use it to modify the tooltip items
13124 if (opts.filter) {
13125 tooltipItems = tooltipItems.filter(function(a) {
13126 return opts.filter(a, data);
13127 });
13128 }
13129
13130 // If the user provided a sorting function, use it to modify the tooltip items
13131 if (opts.itemSort) {
13132 tooltipItems = tooltipItems.sort(function(a, b) {
13133 return opts.itemSort(a, b, data);
13134 });
13135 }
13136
13137 // Determine colors for boxes
13138 helpers.each(tooltipItems, function(tooltipItem) {
13139 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
13140 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
13141 });
13142
13143
13144 // Build the Text Lines
13145 model.title = me.getTitle(tooltipItems, data);
13146 model.beforeBody = me.getBeforeBody(tooltipItems, data);
13147 model.body = me.getBody(tooltipItems, data);
13148 model.afterBody = me.getAfterBody(tooltipItems, data);
13149 model.footer = me.getFooter(tooltipItems, data);
13150
13151 // Initial positioning and colors
13152 model.x = Math.round(tooltipPosition.x);
13153 model.y = Math.round(tooltipPosition.y);
13154 model.caretPadding = opts.caretPadding;
13155 model.labelColors = labelColors;
13156 model.labelTextColors = labelTextColors;
13157
13158 // data points
13159 model.dataPoints = tooltipItems;
13160
13161 // We need to determine alignment of the tooltip
13162 tooltipSize = getTooltipSize(this, model);
13163 alignment = determineAlignment(this, tooltipSize);
13164 // Final Size and Position
13165 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
13166 } else {
13167 model.opacity = 0;
13168 }
13169
13170 model.xAlign = alignment.xAlign;
13171 model.yAlign = alignment.yAlign;
13172 model.x = backgroundPoint.x;
13173 model.y = backgroundPoint.y;
13174 model.width = tooltipSize.width;
13175 model.height = tooltipSize.height;
13176
13177 // Point where the caret on the tooltip points to
13178 model.caretX = tooltipPosition.x;
13179 model.caretY = tooltipPosition.y;
13180
13181 me._model = model;
13182
13183 if (changed && opts.custom) {
13184 opts.custom.call(me, model);
13185 }
13186
13187 return me;
13188 },
13189 drawCaret: function(tooltipPoint, size) {
13190 var ctx = this._chart.ctx;
13191 var vm = this._view;
13192 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
13193
13194 ctx.lineTo(caretPosition.x1, caretPosition.y1);
13195 ctx.lineTo(caretPosition.x2, caretPosition.y2);
13196 ctx.lineTo(caretPosition.x3, caretPosition.y3);
13197 },
13198 getCaretPosition: function(tooltipPoint, size, vm) {
13199 var x1, x2, x3, y1, y2, y3;
13200 var caretSize = vm.caretSize;
13201 var cornerRadius = vm.cornerRadius;
13202 var xAlign = vm.xAlign;
13203 var yAlign = vm.yAlign;
13204 var ptX = tooltipPoint.x;
13205 var ptY = tooltipPoint.y;
13206 var width = size.width;
13207 var height = size.height;
13208
13209 if (yAlign === 'center') {
13210 y2 = ptY + (height / 2);
13211
13212 if (xAlign === 'left') {
13213 x1 = ptX;
13214 x2 = x1 - caretSize;
13215 x3 = x1;
13216
13217 y1 = y2 + caretSize;
13218 y3 = y2 - caretSize;
13219 } else {
13220 x1 = ptX + width;
13221 x2 = x1 + caretSize;
13222 x3 = x1;
13223
13224 y1 = y2 - caretSize;
13225 y3 = y2 + caretSize;
13226 }
13227 } else {
13228 if (xAlign === 'left') {
13229 x2 = ptX + cornerRadius + (caretSize);
13230 x1 = x2 - caretSize;
13231 x3 = x2 + caretSize;
13232 } else if (xAlign === 'right') {
13233 x2 = ptX + width - cornerRadius - caretSize;
13234 x1 = x2 - caretSize;
13235 x3 = x2 + caretSize;
13236 } else {
13237 x2 = vm.caretX;
13238 x1 = x2 - caretSize;
13239 x3 = x2 + caretSize;
13240 }
13241 if (yAlign === 'top') {
13242 y1 = ptY;
13243 y2 = y1 - caretSize;
13244 y3 = y1;
13245 } else {
13246 y1 = ptY + height;
13247 y2 = y1 + caretSize;
13248 y3 = y1;
13249 // invert drawing order
13250 var tmp = x3;
13251 x3 = x1;
13252 x1 = tmp;
13253 }
13254 }
13255 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
13256 },
13257 drawTitle: function(pt, vm, ctx, opacity) {
13258 var title = vm.title;
13259
13260 if (title.length) {
13261 ctx.textAlign = vm._titleAlign;
13262 ctx.textBaseline = 'top';
13263
13264 var titleFontSize = vm.titleFontSize;
13265 var titleSpacing = vm.titleSpacing;
13266
13267 ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
13268 ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
13269
13270 var i, len;
13271 for (i = 0, len = title.length; i < len; ++i) {
13272 ctx.fillText(title[i], pt.x, pt.y);
13273 pt.y += titleFontSize + titleSpacing; // Line Height and spacing
13274
13275 if (i + 1 === title.length) {
13276 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
13277 }
13278 }
13279 }
13280 },
13281 drawBody: function(pt, vm, ctx, opacity) {
13282 var bodyFontSize = vm.bodyFontSize;
13283 var bodySpacing = vm.bodySpacing;
13284 var body = vm.body;
13285
13286 ctx.textAlign = vm._bodyAlign;
13287 ctx.textBaseline = 'top';
13288 ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
13289
13290 // Before Body
13291 var xLinePadding = 0;
13292 var fillLineOfText = function(line) {
13293 ctx.fillText(line, pt.x + xLinePadding, pt.y);
13294 pt.y += bodyFontSize + bodySpacing;
13295 };
13296
13297 // Before body lines
13298 ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
13299 helpers.each(vm.beforeBody, fillLineOfText);
13300
13301 var drawColorBoxes = vm.displayColors;
13302 xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
13303
13304 // Draw body lines now
13305 helpers.each(body, function(bodyItem, i) {
13306 var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
13307 ctx.fillStyle = textColor;
13308 helpers.each(bodyItem.before, fillLineOfText);
13309
13310 helpers.each(bodyItem.lines, function(line) {
13311 // Draw Legend-like boxes if needed
13312 if (drawColorBoxes) {
13313 // Fill a white rect so that colours merge nicely if the opacity is < 1
13314 ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
13315 ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
13316
13317 // Border
13318 ctx.lineWidth = 1;
13319 ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
13320 ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
13321
13322 // Inner square
13323 ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
13324 ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
13325 ctx.fillStyle = textColor;
13326 }
13327
13328 fillLineOfText(line);
13329 });
13330
13331 helpers.each(bodyItem.after, fillLineOfText);
13332 });
13333
13334 // Reset back to 0 for after body
13335 xLinePadding = 0;
13336
13337 // After body lines
13338 helpers.each(vm.afterBody, fillLineOfText);
13339 pt.y -= bodySpacing; // Remove last body spacing
13340 },
13341 drawFooter: function(pt, vm, ctx, opacity) {
13342 var footer = vm.footer;
13343
13344 if (footer.length) {
13345 pt.y += vm.footerMarginTop;
13346
13347 ctx.textAlign = vm._footerAlign;
13348 ctx.textBaseline = 'top';
13349
13350 ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
13351 ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
13352
13353 helpers.each(footer, function(line) {
13354 ctx.fillText(line, pt.x, pt.y);
13355 pt.y += vm.footerFontSize + vm.footerSpacing;
13356 });
13357 }
13358 },
13359 drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
13360 ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
13361 ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
13362 ctx.lineWidth = vm.borderWidth;
13363 var xAlign = vm.xAlign;
13364 var yAlign = vm.yAlign;
13365 var x = pt.x;
13366 var y = pt.y;
13367 var width = tooltipSize.width;
13368 var height = tooltipSize.height;
13369 var radius = vm.cornerRadius;
13370
13371 ctx.beginPath();
13372 ctx.moveTo(x + radius, y);
13373 if (yAlign === 'top') {
13374 this.drawCaret(pt, tooltipSize);
13375 }
13376 ctx.lineTo(x + width - radius, y);
13377 ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
13378 if (yAlign === 'center' && xAlign === 'right') {
13379 this.drawCaret(pt, tooltipSize);
13380 }
13381 ctx.lineTo(x + width, y + height - radius);
13382 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
13383 if (yAlign === 'bottom') {
13384 this.drawCaret(pt, tooltipSize);
13385 }
13386 ctx.lineTo(x + radius, y + height);
13387 ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
13388 if (yAlign === 'center' && xAlign === 'left') {
13389 this.drawCaret(pt, tooltipSize);
13390 }
13391 ctx.lineTo(x, y + radius);
13392 ctx.quadraticCurveTo(x, y, x + radius, y);
13393 ctx.closePath();
13394
13395 ctx.fill();
13396
13397 if (vm.borderWidth > 0) {
13398 ctx.stroke();
13399 }
13400 },
13401 draw: function() {
13402 var ctx = this._chart.ctx;
13403 var vm = this._view;
13404
13405 if (vm.opacity === 0) {
13406 return;
13407 }
13408
13409 var tooltipSize = {
13410 width: vm.width,
13411 height: vm.height
13412 };
13413 var pt = {
13414 x: vm.x,
13415 y: vm.y
13416 };
13417
13418 // IE11/Edge does not like very small opacities, so snap to 0
13419 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
13420
13421 // Truthy/falsey value for empty tooltip
13422 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
13423
13424 if (this._options.enabled && hasTooltipContent) {
13425 // Draw Background
13426 this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
13427
13428 // Draw Title, Body, and Footer
13429 pt.x += vm.xPadding;
13430 pt.y += vm.yPadding;
13431
13432 // Titles
13433 this.drawTitle(pt, vm, ctx, opacity);
13434
13435 // Body
13436 this.drawBody(pt, vm, ctx, opacity);
13437
13438 // Footer
13439 this.drawFooter(pt, vm, ctx, opacity);
13440 }
13441 },
13442
13443 /**
13444 * Handle an event
13445 * @private
13446 * @param {IEvent} event - The event to handle
13447 * @returns {Boolean} true if the tooltip changed
13448 */
13449 handleEvent: function(e) {
13450 var me = this;
13451 var options = me._options;
13452 var changed = false;
13453
13454 me._lastActive = me._lastActive || [];
13455
13456 // Find Active Elements for tooltips
13457 if (e.type === 'mouseout') {
13458 me._active = [];
13459 } else {
13460 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
13461 }
13462
13463 // Remember Last Actives
13464 changed = !helpers.arrayEquals(me._active, me._lastActive);
13465
13466 // Only handle target event on tooltip change
13467 if (changed) {
13468 me._lastActive = me._active;
13469
13470 if (options.enabled || options.custom) {
13471 me._eventPosition = {
13472 x: e.x,
13473 y: e.y
13474 };
13475
13476 me.update(true);
13477 me.pivot();
13478 }
13479 }
13480
13481 return changed;
13482 }
13483 });
13484
13485 /**
13486 * @namespace Chart.Tooltip.positioners
13487 */
13488 Chart.Tooltip.positioners = {
13489 /**
13490 * Average mode places the tooltip at the average position of the elements shown
13491 * @function Chart.Tooltip.positioners.average
13492 * @param elements {ChartElement[]} the elements being displayed in the tooltip
13493 * @returns {Point} tooltip position
13494 */
13495 average: function(elements) {
13496 if (!elements.length) {
13497 return false;
13498 }
13499
13500 var i, len;
13501 var x = 0;
13502 var y = 0;
13503 var count = 0;
13504
13505 for (i = 0, len = elements.length; i < len; ++i) {
13506 var el = elements[i];
13507 if (el && el.hasValue()) {
13508 var pos = el.tooltipPosition();
13509 x += pos.x;
13510 y += pos.y;
13511 ++count;
13512 }
13513 }
13514
13515 return {
13516 x: Math.round(x / count),
13517 y: Math.round(y / count)
13518 };
13519 },
13520
13521 /**
13522 * Gets the tooltip position nearest of the item nearest to the event position
13523 * @function Chart.Tooltip.positioners.nearest
13524 * @param elements {Chart.Element[]} the tooltip elements
13525 * @param eventPosition {Point} the position of the event in canvas coordinates
13526 * @returns {Point} the tooltip position
13527 */
13528 nearest: function(elements, eventPosition) {
13529 var x = eventPosition.x;
13530 var y = eventPosition.y;
13531 var minDistance = Number.POSITIVE_INFINITY;
13532 var i, len, nearestElement;
13533
13534 for (i = 0, len = elements.length; i < len; ++i) {
13535 var el = elements[i];
13536 if (el && el.hasValue()) {
13537 var center = el.getCenterPoint();
13538 var d = helpers.distanceBetweenPoints(eventPosition, center);
13539
13540 if (d < minDistance) {
13541 minDistance = d;
13542 nearestElement = el;
13543 }
13544 }
13545 }
13546
13547 if (nearestElement) {
13548 var tp = nearestElement.tooltipPosition();
13549 x = tp.x;
13550 y = tp.y;
13551 }
13552
13553 return {
13554 x: x,
13555 y: y
13556 };
13557 }
13558 };
13559};
13560
13561},{"25":25,"26":26,"45":45}],36:[function(require,module,exports){
13562'use strict';
13563
13564var defaults = require(25);
13565var Element = require(26);
13566var helpers = require(45);
13567
13568defaults._set('global', {
13569 elements: {
13570 arc: {
13571 backgroundColor: defaults.global.defaultColor,
13572 borderColor: '#fff',
13573 borderWidth: 2
13574 }
13575 }
13576});
13577
13578module.exports = Element.extend({
13579 inLabelRange: function(mouseX) {
13580 var vm = this._view;
13581
13582 if (vm) {
13583 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
13584 }
13585 return false;
13586 },
13587
13588 inRange: function(chartX, chartY) {
13589 var vm = this._view;
13590
13591 if (vm) {
13592 var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
13593 var angle = pointRelativePosition.angle;
13594 var distance = pointRelativePosition.distance;
13595
13596 // Sanitise angle range
13597 var startAngle = vm.startAngle;
13598 var endAngle = vm.endAngle;
13599 while (endAngle < startAngle) {
13600 endAngle += 2.0 * Math.PI;
13601 }
13602 while (angle > endAngle) {
13603 angle -= 2.0 * Math.PI;
13604 }
13605 while (angle < startAngle) {
13606 angle += 2.0 * Math.PI;
13607 }
13608
13609 // Check if within the range of the open/close angle
13610 var betweenAngles = (angle >= startAngle && angle <= endAngle);
13611 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
13612
13613 return (betweenAngles && withinRadius);
13614 }
13615 return false;
13616 },
13617
13618 getCenterPoint: function() {
13619 var vm = this._view;
13620 var halfAngle = (vm.startAngle + vm.endAngle) / 2;
13621 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
13622 return {
13623 x: vm.x + Math.cos(halfAngle) * halfRadius,
13624 y: vm.y + Math.sin(halfAngle) * halfRadius
13625 };
13626 },
13627
13628 getArea: function() {
13629 var vm = this._view;
13630 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
13631 },
13632
13633 tooltipPosition: function() {
13634 var vm = this._view;
13635 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
13636 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
13637
13638 return {
13639 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
13640 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
13641 };
13642 },
13643
13644 draw: function() {
13645 var ctx = this._chart.ctx;
13646 var vm = this._view;
13647 var sA = vm.startAngle;
13648 var eA = vm.endAngle;
13649
13650 ctx.beginPath();
13651
13652 ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
13653 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
13654
13655 ctx.closePath();
13656 ctx.strokeStyle = vm.borderColor;
13657 ctx.lineWidth = vm.borderWidth;
13658
13659 ctx.fillStyle = vm.backgroundColor;
13660
13661 ctx.fill();
13662 ctx.lineJoin = 'bevel';
13663
13664 if (vm.borderWidth) {
13665 ctx.stroke();
13666 }
13667 }
13668});
13669
13670},{"25":25,"26":26,"45":45}],37:[function(require,module,exports){
13671'use strict';
13672
13673var defaults = require(25);
13674var Element = require(26);
13675var helpers = require(45);
13676
13677var globalDefaults = defaults.global;
13678
13679defaults._set('global', {
13680 elements: {
13681 line: {
13682 tension: 0.4,
13683 backgroundColor: globalDefaults.defaultColor,
13684 borderWidth: 3,
13685 borderColor: globalDefaults.defaultColor,
13686 borderCapStyle: 'butt',
13687 borderDash: [],
13688 borderDashOffset: 0.0,
13689 borderJoinStyle: 'miter',
13690 capBezierPoints: true,
13691 fill: true, // do we fill in the area between the line and its base axis
13692 }
13693 }
13694});
13695
13696module.exports = Element.extend({
13697 draw: function() {
13698 var me = this;
13699 var vm = me._view;
13700 var ctx = me._chart.ctx;
13701 var spanGaps = vm.spanGaps;
13702 var points = me._children.slice(); // clone array
13703 var globalOptionLineElements = globalDefaults.elements.line;
13704 var lastDrawnIndex = -1;
13705 var index, current, previous, currentVM;
13706
13707 // If we are looping, adding the first point again
13708 if (me._loop && points.length) {
13709 points.push(points[0]);
13710 }
13711
13712 ctx.save();
13713
13714 // Stroke Line Options
13715 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
13716
13717 // IE 9 and 10 do not support line dash
13718 if (ctx.setLineDash) {
13719 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
13720 }
13721
13722 ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
13723 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
13724 ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
13725 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
13726
13727 // Stroke Line
13728 ctx.beginPath();
13729 lastDrawnIndex = -1;
13730
13731 for (index = 0; index < points.length; ++index) {
13732 current = points[index];
13733 previous = helpers.previousItem(points, index);
13734 currentVM = current._view;
13735
13736 // First point moves to it's starting position no matter what
13737 if (index === 0) {
13738 if (!currentVM.skip) {
13739 ctx.moveTo(currentVM.x, currentVM.y);
13740 lastDrawnIndex = index;
13741 }
13742 } else {
13743 previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
13744
13745 if (!currentVM.skip) {
13746 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
13747 // There was a gap and this is the first point after the gap
13748 ctx.moveTo(currentVM.x, currentVM.y);
13749 } else {
13750 // Line to next point
13751 helpers.canvas.lineTo(ctx, previous._view, current._view);
13752 }
13753 lastDrawnIndex = index;
13754 }
13755 }
13756 }
13757
13758 ctx.stroke();
13759 ctx.restore();
13760 }
13761});
13762
13763},{"25":25,"26":26,"45":45}],38:[function(require,module,exports){
13764'use strict';
13765
13766var defaults = require(25);
13767var Element = require(26);
13768var helpers = require(45);
13769
13770var defaultColor = defaults.global.defaultColor;
13771
13772defaults._set('global', {
13773 elements: {
13774 point: {
13775 radius: 3,
13776 pointStyle: 'circle',
13777 backgroundColor: defaultColor,
13778 borderColor: defaultColor,
13779 borderWidth: 1,
13780 // Hover
13781 hitRadius: 1,
13782 hoverRadius: 4,
13783 hoverBorderWidth: 1
13784 }
13785 }
13786});
13787
13788function xRange(mouseX) {
13789 var vm = this._view;
13790 return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false;
13791}
13792
13793function yRange(mouseY) {
13794 var vm = this._view;
13795 return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false;
13796}
13797
13798module.exports = Element.extend({
13799 inRange: function(mouseX, mouseY) {
13800 var vm = this._view;
13801 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
13802 },
13803
13804 inLabelRange: xRange,
13805 inXRange: xRange,
13806 inYRange: yRange,
13807
13808 getCenterPoint: function() {
13809 var vm = this._view;
13810 return {
13811 x: vm.x,
13812 y: vm.y
13813 };
13814 },
13815
13816 getArea: function() {
13817 return Math.PI * Math.pow(this._view.radius, 2);
13818 },
13819
13820 tooltipPosition: function() {
13821 var vm = this._view;
13822 return {
13823 x: vm.x,
13824 y: vm.y,
13825 padding: vm.radius + vm.borderWidth
13826 };
13827 },
13828
13829 draw: function(chartArea) {
13830 var vm = this._view;
13831 var model = this._model;
13832 var ctx = this._chart.ctx;
13833 var pointStyle = vm.pointStyle;
13834 var radius = vm.radius;
13835 var x = vm.x;
13836 var y = vm.y;
13837 var color = helpers.color;
13838 var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
13839 var ratio = 0;
13840
13841 if (vm.skip) {
13842 return;
13843 }
13844
13845 ctx.strokeStyle = vm.borderColor || defaultColor;
13846 ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth);
13847 ctx.fillStyle = vm.backgroundColor || defaultColor;
13848
13849 // Cliping for Points.
13850 // going out from inner charArea?
13851 if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) {
13852 // Point fade out
13853 if (model.x < chartArea.left) {
13854 ratio = (x - model.x) / (chartArea.left - model.x);
13855 } else if (chartArea.right * errMargin < model.x) {
13856 ratio = (model.x - x) / (model.x - chartArea.right);
13857 } else if (model.y < chartArea.top) {
13858 ratio = (y - model.y) / (chartArea.top - model.y);
13859 } else if (chartArea.bottom * errMargin < model.y) {
13860 ratio = (model.y - y) / (model.y - chartArea.bottom);
13861 }
13862 ratio = Math.round(ratio * 100) / 100;
13863 ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();
13864 ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
13865 }
13866
13867 helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y);
13868 }
13869});
13870
13871},{"25":25,"26":26,"45":45}],39:[function(require,module,exports){
13872'use strict';
13873
13874var defaults = require(25);
13875var Element = require(26);
13876
13877defaults._set('global', {
13878 elements: {
13879 rectangle: {
13880 backgroundColor: defaults.global.defaultColor,
13881 borderColor: defaults.global.defaultColor,
13882 borderSkipped: 'bottom',
13883 borderWidth: 0
13884 }
13885 }
13886});
13887
13888function isVertical(bar) {
13889 return bar._view.width !== undefined;
13890}
13891
13892/**
13893 * Helper function to get the bounds of the bar regardless of the orientation
13894 * @param bar {Chart.Element.Rectangle} the bar
13895 * @return {Bounds} bounds of the bar
13896 * @private
13897 */
13898function getBarBounds(bar) {
13899 var vm = bar._view;
13900 var x1, x2, y1, y2;
13901
13902 if (isVertical(bar)) {
13903 // vertical
13904 var halfWidth = vm.width / 2;
13905 x1 = vm.x - halfWidth;
13906 x2 = vm.x + halfWidth;
13907 y1 = Math.min(vm.y, vm.base);
13908 y2 = Math.max(vm.y, vm.base);
13909 } else {
13910 // horizontal bar
13911 var halfHeight = vm.height / 2;
13912 x1 = Math.min(vm.x, vm.base);
13913 x2 = Math.max(vm.x, vm.base);
13914 y1 = vm.y - halfHeight;
13915 y2 = vm.y + halfHeight;
13916 }
13917
13918 return {
13919 left: x1,
13920 top: y1,
13921 right: x2,
13922 bottom: y2
13923 };
13924}
13925
13926module.exports = Element.extend({
13927 draw: function() {
13928 var ctx = this._chart.ctx;
13929 var vm = this._view;
13930 var left, right, top, bottom, signX, signY, borderSkipped;
13931 var borderWidth = vm.borderWidth;
13932
13933 if (!vm.horizontal) {
13934 // bar
13935 left = vm.x - vm.width / 2;
13936 right = vm.x + vm.width / 2;
13937 top = vm.y;
13938 bottom = vm.base;
13939 signX = 1;
13940 signY = bottom > top ? 1 : -1;
13941 borderSkipped = vm.borderSkipped || 'bottom';
13942 } else {
13943 // horizontal bar
13944 left = vm.base;
13945 right = vm.x;
13946 top = vm.y - vm.height / 2;
13947 bottom = vm.y + vm.height / 2;
13948 signX = right > left ? 1 : -1;
13949 signY = 1;
13950 borderSkipped = vm.borderSkipped || 'left';
13951 }
13952
13953 // Canvas doesn't allow us to stroke inside the width so we can
13954 // adjust the sizes to fit if we're setting a stroke on the line
13955 if (borderWidth) {
13956 // borderWidth shold be less than bar width and bar height.
13957 var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
13958 borderWidth = borderWidth > barSize ? barSize : borderWidth;
13959 var halfStroke = borderWidth / 2;
13960 // Adjust borderWidth when bar top position is near vm.base(zero).
13961 var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
13962 var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
13963 var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
13964 var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
13965 // not become a vertical line?
13966 if (borderLeft !== borderRight) {
13967 top = borderTop;
13968 bottom = borderBottom;
13969 }
13970 // not become a horizontal line?
13971 if (borderTop !== borderBottom) {
13972 left = borderLeft;
13973 right = borderRight;
13974 }
13975 }
13976
13977 ctx.beginPath();
13978 ctx.fillStyle = vm.backgroundColor;
13979 ctx.strokeStyle = vm.borderColor;
13980 ctx.lineWidth = borderWidth;
13981
13982 // Corner points, from bottom-left to bottom-right clockwise
13983 // | 1 2 |
13984 // | 0 3 |
13985 var corners = [
13986 [left, bottom],
13987 [left, top],
13988 [right, top],
13989 [right, bottom]
13990 ];
13991
13992 // Find first (starting) corner with fallback to 'bottom'
13993 var borders = ['bottom', 'left', 'top', 'right'];
13994 var startCorner = borders.indexOf(borderSkipped, 0);
13995 if (startCorner === -1) {
13996 startCorner = 0;
13997 }
13998
13999 function cornerAt(index) {
14000 return corners[(startCorner + index) % 4];
14001 }
14002
14003 // Draw rectangle from 'startCorner'
14004 var corner = cornerAt(0);
14005 ctx.moveTo(corner[0], corner[1]);
14006
14007 for (var i = 1; i < 4; i++) {
14008 corner = cornerAt(i);
14009 ctx.lineTo(corner[0], corner[1]);
14010 }
14011
14012 ctx.fill();
14013 if (borderWidth) {
14014 ctx.stroke();
14015 }
14016 },
14017
14018 height: function() {
14019 var vm = this._view;
14020 return vm.base - vm.y;
14021 },
14022
14023 inRange: function(mouseX, mouseY) {
14024 var inRange = false;
14025
14026 if (this._view) {
14027 var bounds = getBarBounds(this);
14028 inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
14029 }
14030
14031 return inRange;
14032 },
14033
14034 inLabelRange: function(mouseX, mouseY) {
14035 var me = this;
14036 if (!me._view) {
14037 return false;
14038 }
14039
14040 var inRange = false;
14041 var bounds = getBarBounds(me);
14042
14043 if (isVertical(me)) {
14044 inRange = mouseX >= bounds.left && mouseX <= bounds.right;
14045 } else {
14046 inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
14047 }
14048
14049 return inRange;
14050 },
14051
14052 inXRange: function(mouseX) {
14053 var bounds = getBarBounds(this);
14054 return mouseX >= bounds.left && mouseX <= bounds.right;
14055 },
14056
14057 inYRange: function(mouseY) {
14058 var bounds = getBarBounds(this);
14059 return mouseY >= bounds.top && mouseY <= bounds.bottom;
14060 },
14061
14062 getCenterPoint: function() {
14063 var vm = this._view;
14064 var x, y;
14065 if (isVertical(this)) {
14066 x = vm.x;
14067 y = (vm.y + vm.base) / 2;
14068 } else {
14069 x = (vm.x + vm.base) / 2;
14070 y = vm.y;
14071 }
14072
14073 return {x: x, y: y};
14074 },
14075
14076 getArea: function() {
14077 var vm = this._view;
14078 return vm.width * Math.abs(vm.y - vm.base);
14079 },
14080
14081 tooltipPosition: function() {
14082 var vm = this._view;
14083 return {
14084 x: vm.x,
14085 y: vm.y
14086 };
14087 }
14088});
14089
14090},{"25":25,"26":26}],40:[function(require,module,exports){
14091'use strict';
14092
14093module.exports = {};
14094module.exports.Arc = require(36);
14095module.exports.Line = require(37);
14096module.exports.Point = require(38);
14097module.exports.Rectangle = require(39);
14098
14099},{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){
14100'use strict';
14101
14102var helpers = require(42);
14103
14104/**
14105 * @namespace Chart.helpers.canvas
14106 */
14107var exports = module.exports = {
14108 /**
14109 * Clears the entire canvas associated to the given `chart`.
14110 * @param {Chart} chart - The chart for which to clear the canvas.
14111 */
14112 clear: function(chart) {
14113 chart.ctx.clearRect(0, 0, chart.width, chart.height);
14114 },
14115
14116 /**
14117 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
14118 * given size (width, height) and the same `radius` for all corners.
14119 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
14120 * @param {Number} x - The x axis of the coordinate for the rectangle starting point.
14121 * @param {Number} y - The y axis of the coordinate for the rectangle starting point.
14122 * @param {Number} width - The rectangle's width.
14123 * @param {Number} height - The rectangle's height.
14124 * @param {Number} radius - The rounded amount (in pixels) for the four corners.
14125 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
14126 */
14127 roundedRect: function(ctx, x, y, width, height, radius) {
14128 if (radius) {
14129 var rx = Math.min(radius, width / 2);
14130 var ry = Math.min(radius, height / 2);
14131
14132 ctx.moveTo(x + rx, y);
14133 ctx.lineTo(x + width - rx, y);
14134 ctx.quadraticCurveTo(x + width, y, x + width, y + ry);
14135 ctx.lineTo(x + width, y + height - ry);
14136 ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
14137 ctx.lineTo(x + rx, y + height);
14138 ctx.quadraticCurveTo(x, y + height, x, y + height - ry);
14139 ctx.lineTo(x, y + ry);
14140 ctx.quadraticCurveTo(x, y, x + rx, y);
14141 } else {
14142 ctx.rect(x, y, width, height);
14143 }
14144 },
14145
14146 drawPoint: function(ctx, style, radius, x, y) {
14147 var type, edgeLength, xOffset, yOffset, height, size;
14148
14149 if (style && typeof style === 'object') {
14150 type = style.toString();
14151 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
14152 ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
14153 return;
14154 }
14155 }
14156
14157 if (isNaN(radius) || radius <= 0) {
14158 return;
14159 }
14160
14161 switch (style) {
14162 // Default includes circle
14163 default:
14164 ctx.beginPath();
14165 ctx.arc(x, y, radius, 0, Math.PI * 2);
14166 ctx.closePath();
14167 ctx.fill();
14168 break;
14169 case 'triangle':
14170 ctx.beginPath();
14171 edgeLength = 3 * radius / Math.sqrt(3);
14172 height = edgeLength * Math.sqrt(3) / 2;
14173 ctx.moveTo(x - edgeLength / 2, y + height / 3);
14174 ctx.lineTo(x + edgeLength / 2, y + height / 3);
14175 ctx.lineTo(x, y - 2 * height / 3);
14176 ctx.closePath();
14177 ctx.fill();
14178 break;
14179 case 'rect':
14180 size = 1 / Math.SQRT2 * radius;
14181 ctx.beginPath();
14182 ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
14183 ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
14184 break;
14185 case 'rectRounded':
14186 var offset = radius / Math.SQRT2;
14187 var leftX = x - offset;
14188 var topY = y - offset;
14189 var sideSize = Math.SQRT2 * radius;
14190 ctx.beginPath();
14191 this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2);
14192 ctx.closePath();
14193 ctx.fill();
14194 break;
14195 case 'rectRot':
14196 size = 1 / Math.SQRT2 * radius;
14197 ctx.beginPath();
14198 ctx.moveTo(x - size, y);
14199 ctx.lineTo(x, y + size);
14200 ctx.lineTo(x + size, y);
14201 ctx.lineTo(x, y - size);
14202 ctx.closePath();
14203 ctx.fill();
14204 break;
14205 case 'cross':
14206 ctx.beginPath();
14207 ctx.moveTo(x, y + radius);
14208 ctx.lineTo(x, y - radius);
14209 ctx.moveTo(x - radius, y);
14210 ctx.lineTo(x + radius, y);
14211 ctx.closePath();
14212 break;
14213 case 'crossRot':
14214 ctx.beginPath();
14215 xOffset = Math.cos(Math.PI / 4) * radius;
14216 yOffset = Math.sin(Math.PI / 4) * radius;
14217 ctx.moveTo(x - xOffset, y - yOffset);
14218 ctx.lineTo(x + xOffset, y + yOffset);
14219 ctx.moveTo(x - xOffset, y + yOffset);
14220 ctx.lineTo(x + xOffset, y - yOffset);
14221 ctx.closePath();
14222 break;
14223 case 'star':
14224 ctx.beginPath();
14225 ctx.moveTo(x, y + radius);
14226 ctx.lineTo(x, y - radius);
14227 ctx.moveTo(x - radius, y);
14228 ctx.lineTo(x + radius, y);
14229 xOffset = Math.cos(Math.PI / 4) * radius;
14230 yOffset = Math.sin(Math.PI / 4) * radius;
14231 ctx.moveTo(x - xOffset, y - yOffset);
14232 ctx.lineTo(x + xOffset, y + yOffset);
14233 ctx.moveTo(x - xOffset, y + yOffset);
14234 ctx.lineTo(x + xOffset, y - yOffset);
14235 ctx.closePath();
14236 break;
14237 case 'line':
14238 ctx.beginPath();
14239 ctx.moveTo(x - radius, y);
14240 ctx.lineTo(x + radius, y);
14241 ctx.closePath();
14242 break;
14243 case 'dash':
14244 ctx.beginPath();
14245 ctx.moveTo(x, y);
14246 ctx.lineTo(x + radius, y);
14247 ctx.closePath();
14248 break;
14249 }
14250
14251 ctx.stroke();
14252 },
14253
14254 clipArea: function(ctx, area) {
14255 ctx.save();
14256 ctx.beginPath();
14257 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
14258 ctx.clip();
14259 },
14260
14261 unclipArea: function(ctx) {
14262 ctx.restore();
14263 },
14264
14265 lineTo: function(ctx, previous, target, flip) {
14266 if (target.steppedLine) {
14267 if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) {
14268 ctx.lineTo(previous.x, target.y);
14269 } else {
14270 ctx.lineTo(target.x, previous.y);
14271 }
14272 ctx.lineTo(target.x, target.y);
14273 return;
14274 }
14275
14276 if (!target.tension) {
14277 ctx.lineTo(target.x, target.y);
14278 return;
14279 }
14280
14281 ctx.bezierCurveTo(
14282 flip ? previous.controlPointPreviousX : previous.controlPointNextX,
14283 flip ? previous.controlPointPreviousY : previous.controlPointNextY,
14284 flip ? target.controlPointNextX : target.controlPointPreviousX,
14285 flip ? target.controlPointNextY : target.controlPointPreviousY,
14286 target.x,
14287 target.y);
14288 }
14289};
14290
14291// DEPRECATIONS
14292
14293/**
14294 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
14295 * @namespace Chart.helpers.clear
14296 * @deprecated since version 2.7.0
14297 * @todo remove at version 3
14298 * @private
14299 */
14300helpers.clear = exports.clear;
14301
14302/**
14303 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
14304 * @namespace Chart.helpers.drawRoundedRectangle
14305 * @deprecated since version 2.7.0
14306 * @todo remove at version 3
14307 * @private
14308 */
14309helpers.drawRoundedRectangle = function(ctx) {
14310 ctx.beginPath();
14311 exports.roundedRect.apply(exports, arguments);
14312 ctx.closePath();
14313};
14314
14315},{"42":42}],42:[function(require,module,exports){
14316'use strict';
14317
14318/**
14319 * @namespace Chart.helpers
14320 */
14321var helpers = {
14322 /**
14323 * An empty function that can be used, for example, for optional callback.
14324 */
14325 noop: function() {},
14326
14327 /**
14328 * Returns a unique id, sequentially generated from a global variable.
14329 * @returns {Number}
14330 * @function
14331 */
14332 uid: (function() {
14333 var id = 0;
14334 return function() {
14335 return id++;
14336 };
14337 }()),
14338
14339 /**
14340 * Returns true if `value` is neither null nor undefined, else returns false.
14341 * @param {*} value - The value to test.
14342 * @returns {Boolean}
14343 * @since 2.7.0
14344 */
14345 isNullOrUndef: function(value) {
14346 return value === null || typeof value === 'undefined';
14347 },
14348
14349 /**
14350 * Returns true if `value` is an array, else returns false.
14351 * @param {*} value - The value to test.
14352 * @returns {Boolean}
14353 * @function
14354 */
14355 isArray: Array.isArray ? Array.isArray : function(value) {
14356 return Object.prototype.toString.call(value) === '[object Array]';
14357 },
14358
14359 /**
14360 * Returns true if `value` is an object (excluding null), else returns false.
14361 * @param {*} value - The value to test.
14362 * @returns {Boolean}
14363 * @since 2.7.0
14364 */
14365 isObject: function(value) {
14366 return value !== null && Object.prototype.toString.call(value) === '[object Object]';
14367 },
14368
14369 /**
14370 * Returns `value` if defined, else returns `defaultValue`.
14371 * @param {*} value - The value to return if defined.
14372 * @param {*} defaultValue - The value to return if `value` is undefined.
14373 * @returns {*}
14374 */
14375 valueOrDefault: function(value, defaultValue) {
14376 return typeof value === 'undefined' ? defaultValue : value;
14377 },
14378
14379 /**
14380 * Returns value at the given `index` in array if defined, else returns `defaultValue`.
14381 * @param {Array} value - The array to lookup for value at `index`.
14382 * @param {Number} index - The index in `value` to lookup for value.
14383 * @param {*} defaultValue - The value to return if `value[index]` is undefined.
14384 * @returns {*}
14385 */
14386 valueAtIndexOrDefault: function(value, index, defaultValue) {
14387 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
14388 },
14389
14390 /**
14391 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
14392 * value returned by `fn`. If `fn` is not a function, this method returns undefined.
14393 * @param {Function} fn - The function to call.
14394 * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
14395 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
14396 * @returns {*}
14397 */
14398 callback: function(fn, args, thisArg) {
14399 if (fn && typeof fn.call === 'function') {
14400 return fn.apply(thisArg, args);
14401 }
14402 },
14403
14404 /**
14405 * Note(SB) for performance sake, this method should only be used when loopable type
14406 * is unknown or in none intensive code (not called often and small loopable). Else
14407 * it's preferable to use a regular for() loop and save extra function calls.
14408 * @param {Object|Array} loopable - The object or array to be iterated.
14409 * @param {Function} fn - The function to call for each item.
14410 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
14411 * @param {Boolean} [reverse] - If true, iterates backward on the loopable.
14412 */
14413 each: function(loopable, fn, thisArg, reverse) {
14414 var i, len, keys;
14415 if (helpers.isArray(loopable)) {
14416 len = loopable.length;
14417 if (reverse) {
14418 for (i = len - 1; i >= 0; i--) {
14419 fn.call(thisArg, loopable[i], i);
14420 }
14421 } else {
14422 for (i = 0; i < len; i++) {
14423 fn.call(thisArg, loopable[i], i);
14424 }
14425 }
14426 } else if (helpers.isObject(loopable)) {
14427 keys = Object.keys(loopable);
14428 len = keys.length;
14429 for (i = 0; i < len; i++) {
14430 fn.call(thisArg, loopable[keys[i]], keys[i]);
14431 }
14432 }
14433 },
14434
14435 /**
14436 * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
14437 * @see http://stackoverflow.com/a/14853974
14438 * @param {Array} a0 - The array to compare
14439 * @param {Array} a1 - The array to compare
14440 * @returns {Boolean}
14441 */
14442 arrayEquals: function(a0, a1) {
14443 var i, ilen, v0, v1;
14444
14445 if (!a0 || !a1 || a0.length !== a1.length) {
14446 return false;
14447 }
14448
14449 for (i = 0, ilen = a0.length; i < ilen; ++i) {
14450 v0 = a0[i];
14451 v1 = a1[i];
14452
14453 if (v0 instanceof Array && v1 instanceof Array) {
14454 if (!helpers.arrayEquals(v0, v1)) {
14455 return false;
14456 }
14457 } else if (v0 !== v1) {
14458 // NOTE: two different object instances will never be equal: {x:20} != {x:20}
14459 return false;
14460 }
14461 }
14462
14463 return true;
14464 },
14465
14466 /**
14467 * Returns a deep copy of `source` without keeping references on objects and arrays.
14468 * @param {*} source - The value to clone.
14469 * @returns {*}
14470 */
14471 clone: function(source) {
14472 if (helpers.isArray(source)) {
14473 return source.map(helpers.clone);
14474 }
14475
14476 if (helpers.isObject(source)) {
14477 var target = {};
14478 var keys = Object.keys(source);
14479 var klen = keys.length;
14480 var k = 0;
14481
14482 for (; k < klen; ++k) {
14483 target[keys[k]] = helpers.clone(source[keys[k]]);
14484 }
14485
14486 return target;
14487 }
14488
14489 return source;
14490 },
14491
14492 /**
14493 * The default merger when Chart.helpers.merge is called without merger option.
14494 * Note(SB): this method is also used by configMerge and scaleMerge as fallback.
14495 * @private
14496 */
14497 _merger: function(key, target, source, options) {
14498 var tval = target[key];
14499 var sval = source[key];
14500
14501 if (helpers.isObject(tval) && helpers.isObject(sval)) {
14502 helpers.merge(tval, sval, options);
14503 } else {
14504 target[key] = helpers.clone(sval);
14505 }
14506 },
14507
14508 /**
14509 * Merges source[key] in target[key] only if target[key] is undefined.
14510 * @private
14511 */
14512 _mergerIf: function(key, target, source) {
14513 var tval = target[key];
14514 var sval = source[key];
14515
14516 if (helpers.isObject(tval) && helpers.isObject(sval)) {
14517 helpers.mergeIf(tval, sval);
14518 } else if (!target.hasOwnProperty(key)) {
14519 target[key] = helpers.clone(sval);
14520 }
14521 },
14522
14523 /**
14524 * Recursively deep copies `source` properties into `target` with the given `options`.
14525 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
14526 * @param {Object} target - The target object in which all sources are merged into.
14527 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
14528 * @param {Object} [options] - Merging options:
14529 * @param {Function} [options.merger] - The merge method (key, target, source, options)
14530 * @returns {Object} The `target` object.
14531 */
14532 merge: function(target, source, options) {
14533 var sources = helpers.isArray(source) ? source : [source];
14534 var ilen = sources.length;
14535 var merge, i, keys, klen, k;
14536
14537 if (!helpers.isObject(target)) {
14538 return target;
14539 }
14540
14541 options = options || {};
14542 merge = options.merger || helpers._merger;
14543
14544 for (i = 0; i < ilen; ++i) {
14545 source = sources[i];
14546 if (!helpers.isObject(source)) {
14547 continue;
14548 }
14549
14550 keys = Object.keys(source);
14551 for (k = 0, klen = keys.length; k < klen; ++k) {
14552 merge(keys[k], target, source, options);
14553 }
14554 }
14555
14556 return target;
14557 },
14558
14559 /**
14560 * Recursively deep copies `source` properties into `target` *only* if not defined in target.
14561 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
14562 * @param {Object} target - The target object in which all sources are merged into.
14563 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
14564 * @returns {Object} The `target` object.
14565 */
14566 mergeIf: function(target, source) {
14567 return helpers.merge(target, source, {merger: helpers._mergerIf});
14568 },
14569
14570 /**
14571 * Applies the contents of two or more objects together into the first object.
14572 * @param {Object} target - The target object in which all objects are merged into.
14573 * @param {Object} arg1 - Object containing additional properties to merge in target.
14574 * @param {Object} argN - Additional objects containing properties to merge in target.
14575 * @returns {Object} The `target` object.
14576 */
14577 extend: function(target) {
14578 var setFn = function(value, key) {
14579 target[key] = value;
14580 };
14581 for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
14582 helpers.each(arguments[i], setFn);
14583 }
14584 return target;
14585 },
14586
14587 /**
14588 * Basic javascript inheritance based on the model created in Backbone.js
14589 */
14590 inherits: function(extensions) {
14591 var me = this;
14592 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
14593 return me.apply(this, arguments);
14594 };
14595
14596 var Surrogate = function() {
14597 this.constructor = ChartElement;
14598 };
14599
14600 Surrogate.prototype = me.prototype;
14601 ChartElement.prototype = new Surrogate();
14602 ChartElement.extend = helpers.inherits;
14603
14604 if (extensions) {
14605 helpers.extend(ChartElement.prototype, extensions);
14606 }
14607
14608 ChartElement.__super__ = me.prototype;
14609 return ChartElement;
14610 }
14611};
14612
14613module.exports = helpers;
14614
14615// DEPRECATIONS
14616
14617/**
14618 * Provided for backward compatibility, use Chart.helpers.callback instead.
14619 * @function Chart.helpers.callCallback
14620 * @deprecated since version 2.6.0
14621 * @todo remove at version 3
14622 * @private
14623 */
14624helpers.callCallback = helpers.callback;
14625
14626/**
14627 * Provided for backward compatibility, use Array.prototype.indexOf instead.
14628 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
14629 * @function Chart.helpers.indexOf
14630 * @deprecated since version 2.7.0
14631 * @todo remove at version 3
14632 * @private
14633 */
14634helpers.indexOf = function(array, item, fromIndex) {
14635 return Array.prototype.indexOf.call(array, item, fromIndex);
14636};
14637
14638/**
14639 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
14640 * @function Chart.helpers.getValueOrDefault
14641 * @deprecated since version 2.7.0
14642 * @todo remove at version 3
14643 * @private
14644 */
14645helpers.getValueOrDefault = helpers.valueOrDefault;
14646
14647/**
14648 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
14649 * @function Chart.helpers.getValueAtIndexOrDefault
14650 * @deprecated since version 2.7.0
14651 * @todo remove at version 3
14652 * @private
14653 */
14654helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
14655
14656},{}],43:[function(require,module,exports){
14657'use strict';
14658
14659var helpers = require(42);
14660
14661/**
14662 * Easing functions adapted from Robert Penner's easing equations.
14663 * @namespace Chart.helpers.easingEffects
14664 * @see http://www.robertpenner.com/easing/
14665 */
14666var effects = {
14667 linear: function(t) {
14668 return t;
14669 },
14670
14671 easeInQuad: function(t) {
14672 return t * t;
14673 },
14674
14675 easeOutQuad: function(t) {
14676 return -t * (t - 2);
14677 },
14678
14679 easeInOutQuad: function(t) {
14680 if ((t /= 0.5) < 1) {
14681 return 0.5 * t * t;
14682 }
14683 return -0.5 * ((--t) * (t - 2) - 1);
14684 },
14685
14686 easeInCubic: function(t) {
14687 return t * t * t;
14688 },
14689
14690 easeOutCubic: function(t) {
14691 return (t = t - 1) * t * t + 1;
14692 },
14693
14694 easeInOutCubic: function(t) {
14695 if ((t /= 0.5) < 1) {
14696 return 0.5 * t * t * t;
14697 }
14698 return 0.5 * ((t -= 2) * t * t + 2);
14699 },
14700
14701 easeInQuart: function(t) {
14702 return t * t * t * t;
14703 },
14704
14705 easeOutQuart: function(t) {
14706 return -((t = t - 1) * t * t * t - 1);
14707 },
14708
14709 easeInOutQuart: function(t) {
14710 if ((t /= 0.5) < 1) {
14711 return 0.5 * t * t * t * t;
14712 }
14713 return -0.5 * ((t -= 2) * t * t * t - 2);
14714 },
14715
14716 easeInQuint: function(t) {
14717 return t * t * t * t * t;
14718 },
14719
14720 easeOutQuint: function(t) {
14721 return (t = t - 1) * t * t * t * t + 1;
14722 },
14723
14724 easeInOutQuint: function(t) {
14725 if ((t /= 0.5) < 1) {
14726 return 0.5 * t * t * t * t * t;
14727 }
14728 return 0.5 * ((t -= 2) * t * t * t * t + 2);
14729 },
14730
14731 easeInSine: function(t) {
14732 return -Math.cos(t * (Math.PI / 2)) + 1;
14733 },
14734
14735 easeOutSine: function(t) {
14736 return Math.sin(t * (Math.PI / 2));
14737 },
14738
14739 easeInOutSine: function(t) {
14740 return -0.5 * (Math.cos(Math.PI * t) - 1);
14741 },
14742
14743 easeInExpo: function(t) {
14744 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
14745 },
14746
14747 easeOutExpo: function(t) {
14748 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
14749 },
14750
14751 easeInOutExpo: function(t) {
14752 if (t === 0) {
14753 return 0;
14754 }
14755 if (t === 1) {
14756 return 1;
14757 }
14758 if ((t /= 0.5) < 1) {
14759 return 0.5 * Math.pow(2, 10 * (t - 1));
14760 }
14761 return 0.5 * (-Math.pow(2, -10 * --t) + 2);
14762 },
14763
14764 easeInCirc: function(t) {
14765 if (t >= 1) {
14766 return t;
14767 }
14768 return -(Math.sqrt(1 - t * t) - 1);
14769 },
14770
14771 easeOutCirc: function(t) {
14772 return Math.sqrt(1 - (t = t - 1) * t);
14773 },
14774
14775 easeInOutCirc: function(t) {
14776 if ((t /= 0.5) < 1) {
14777 return -0.5 * (Math.sqrt(1 - t * t) - 1);
14778 }
14779 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
14780 },
14781
14782 easeInElastic: function(t) {
14783 var s = 1.70158;
14784 var p = 0;
14785 var a = 1;
14786 if (t === 0) {
14787 return 0;
14788 }
14789 if (t === 1) {
14790 return 1;
14791 }
14792 if (!p) {
14793 p = 0.3;
14794 }
14795 if (a < 1) {
14796 a = 1;
14797 s = p / 4;
14798 } else {
14799 s = p / (2 * Math.PI) * Math.asin(1 / a);
14800 }
14801 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
14802 },
14803
14804 easeOutElastic: function(t) {
14805 var s = 1.70158;
14806 var p = 0;
14807 var a = 1;
14808 if (t === 0) {
14809 return 0;
14810 }
14811 if (t === 1) {
14812 return 1;
14813 }
14814 if (!p) {
14815 p = 0.3;
14816 }
14817 if (a < 1) {
14818 a = 1;
14819 s = p / 4;
14820 } else {
14821 s = p / (2 * Math.PI) * Math.asin(1 / a);
14822 }
14823 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
14824 },
14825
14826 easeInOutElastic: function(t) {
14827 var s = 1.70158;
14828 var p = 0;
14829 var a = 1;
14830 if (t === 0) {
14831 return 0;
14832 }
14833 if ((t /= 0.5) === 2) {
14834 return 1;
14835 }
14836 if (!p) {
14837 p = 0.45;
14838 }
14839 if (a < 1) {
14840 a = 1;
14841 s = p / 4;
14842 } else {
14843 s = p / (2 * Math.PI) * Math.asin(1 / a);
14844 }
14845 if (t < 1) {
14846 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
14847 }
14848 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
14849 },
14850 easeInBack: function(t) {
14851 var s = 1.70158;
14852 return t * t * ((s + 1) * t - s);
14853 },
14854
14855 easeOutBack: function(t) {
14856 var s = 1.70158;
14857 return (t = t - 1) * t * ((s + 1) * t + s) + 1;
14858 },
14859
14860 easeInOutBack: function(t) {
14861 var s = 1.70158;
14862 if ((t /= 0.5) < 1) {
14863 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
14864 }
14865 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
14866 },
14867
14868 easeInBounce: function(t) {
14869 return 1 - effects.easeOutBounce(1 - t);
14870 },
14871
14872 easeOutBounce: function(t) {
14873 if (t < (1 / 2.75)) {
14874 return 7.5625 * t * t;
14875 }
14876 if (t < (2 / 2.75)) {
14877 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
14878 }
14879 if (t < (2.5 / 2.75)) {
14880 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
14881 }
14882 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
14883 },
14884
14885 easeInOutBounce: function(t) {
14886 if (t < 0.5) {
14887 return effects.easeInBounce(t * 2) * 0.5;
14888 }
14889 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
14890 }
14891};
14892
14893module.exports = {
14894 effects: effects
14895};
14896
14897// DEPRECATIONS
14898
14899/**
14900 * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
14901 * @function Chart.helpers.easingEffects
14902 * @deprecated since version 2.7.0
14903 * @todo remove at version 3
14904 * @private
14905 */
14906helpers.easingEffects = effects;
14907
14908},{"42":42}],44:[function(require,module,exports){
14909'use strict';
14910
14911var helpers = require(42);
14912
14913/**
14914 * @alias Chart.helpers.options
14915 * @namespace
14916 */
14917module.exports = {
14918 /**
14919 * Converts the given line height `value` in pixels for a specific font `size`.
14920 * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
14921 * @param {Number} size - The font size (in pixels) used to resolve relative `value`.
14922 * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid).
14923 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
14924 * @since 2.7.0
14925 */
14926 toLineHeight: function(value, size) {
14927 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
14928 if (!matches || matches[1] === 'normal') {
14929 return size * 1.2;
14930 }
14931
14932 value = +matches[2];
14933
14934 switch (matches[3]) {
14935 case 'px':
14936 return value;
14937 case '%':
14938 value /= 100;
14939 break;
14940 default:
14941 break;
14942 }
14943
14944 return size * value;
14945 },
14946
14947 /**
14948 * Converts the given value into a padding object with pre-computed width/height.
14949 * @param {Number|Object} value - If a number, set the value to all TRBL component,
14950 * else, if and object, use defined properties and sets undefined ones to 0.
14951 * @returns {Object} The padding values (top, right, bottom, left, width, height)
14952 * @since 2.7.0
14953 */
14954 toPadding: function(value) {
14955 var t, r, b, l;
14956
14957 if (helpers.isObject(value)) {
14958 t = +value.top || 0;
14959 r = +value.right || 0;
14960 b = +value.bottom || 0;
14961 l = +value.left || 0;
14962 } else {
14963 t = r = b = l = +value || 0;
14964 }
14965
14966 return {
14967 top: t,
14968 right: r,
14969 bottom: b,
14970 left: l,
14971 height: t + b,
14972 width: l + r
14973 };
14974 },
14975
14976 /**
14977 * Evaluates the given `inputs` sequentially and returns the first defined value.
14978 * @param {Array[]} inputs - An array of values, falling back to the last value.
14979 * @param {Object} [context] - If defined and the current value is a function, the value
14980 * is called with `context` as first argument and the result becomes the new input.
14981 * @param {Number} [index] - If defined and the current value is an array, the value
14982 * at `index` become the new input.
14983 * @since 2.7.0
14984 */
14985 resolve: function(inputs, context, index) {
14986 var i, ilen, value;
14987
14988 for (i = 0, ilen = inputs.length; i < ilen; ++i) {
14989 value = inputs[i];
14990 if (value === undefined) {
14991 continue;
14992 }
14993 if (context !== undefined && typeof value === 'function') {
14994 value = value(context);
14995 }
14996 if (index !== undefined && helpers.isArray(value)) {
14997 value = value[index];
14998 }
14999 if (value !== undefined) {
15000 return value;
15001 }
15002 }
15003 }
15004};
15005
15006},{"42":42}],45:[function(require,module,exports){
15007'use strict';
15008
15009module.exports = require(42);
15010module.exports.easing = require(43);
15011module.exports.canvas = require(41);
15012module.exports.options = require(44);
15013
15014},{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){
15015/**
15016 * Platform fallback implementation (minimal).
15017 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
15018 */
15019
15020module.exports = {
15021 acquireContext: function(item) {
15022 if (item && item.canvas) {
15023 // Support for any object associated to a canvas (including a context2d)
15024 item = item.canvas;
15025 }
15026
15027 return item && item.getContext('2d') || null;
15028 }
15029};
15030
15031},{}],47:[function(require,module,exports){
15032/**
15033 * Chart.Platform implementation for targeting a web browser
15034 */
15035
15036'use strict';
15037
15038var helpers = require(45);
15039
15040var EXPANDO_KEY = '$chartjs';
15041var CSS_PREFIX = 'chartjs-';
15042var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
15043var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
15044var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
15045
15046/**
15047 * DOM event types -> Chart.js event types.
15048 * Note: only events with different types are mapped.
15049 * @see https://developer.mozilla.org/en-US/docs/Web/Events
15050 */
15051var EVENT_TYPES = {
15052 touchstart: 'mousedown',
15053 touchmove: 'mousemove',
15054 touchend: 'mouseup',
15055 pointerenter: 'mouseenter',
15056 pointerdown: 'mousedown',
15057 pointermove: 'mousemove',
15058 pointerup: 'mouseup',
15059 pointerleave: 'mouseout',
15060 pointerout: 'mouseout'
15061};
15062
15063/**
15064 * The "used" size is the final value of a dimension property after all calculations have
15065 * been performed. This method uses the computed style of `element` but returns undefined
15066 * if the computed style is not expressed in pixels. That can happen in some cases where
15067 * `element` has a size relative to its parent and this last one is not yet displayed,
15068 * for example because of `display: none` on a parent node.
15069 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
15070 * @returns {Number} Size in pixels or undefined if unknown.
15071 */
15072function readUsedSize(element, property) {
15073 var value = helpers.getStyle(element, property);
15074 var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
15075 return matches ? Number(matches[1]) : undefined;
15076}
15077
15078/**
15079 * Initializes the canvas style and render size without modifying the canvas display size,
15080 * since responsiveness is handled by the controller.resize() method. The config is used
15081 * to determine the aspect ratio to apply in case no explicit height has been specified.
15082 */
15083function initCanvas(canvas, config) {
15084 var style = canvas.style;
15085
15086 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
15087 // returns null or '' if no explicit value has been set to the canvas attribute.
15088 var renderHeight = canvas.getAttribute('height');
15089 var renderWidth = canvas.getAttribute('width');
15090
15091 // Chart.js modifies some canvas values that we want to restore on destroy
15092 canvas[EXPANDO_KEY] = {
15093 initial: {
15094 height: renderHeight,
15095 width: renderWidth,
15096 style: {
15097 display: style.display,
15098 height: style.height,
15099 width: style.width
15100 }
15101 }
15102 };
15103
15104 // Force canvas to display as block to avoid extra space caused by inline
15105 // elements, which would interfere with the responsive resize process.
15106 // https://github.com/chartjs/Chart.js/issues/2538
15107 style.display = style.display || 'block';
15108
15109 if (renderWidth === null || renderWidth === '') {
15110 var displayWidth = readUsedSize(canvas, 'width');
15111 if (displayWidth !== undefined) {
15112 canvas.width = displayWidth;
15113 }
15114 }
15115
15116 if (renderHeight === null || renderHeight === '') {
15117 if (canvas.style.height === '') {
15118 // If no explicit render height and style height, let's apply the aspect ratio,
15119 // which one can be specified by the user but also by charts as default option
15120 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
15121 canvas.height = canvas.width / (config.options.aspectRatio || 2);
15122 } else {
15123 var displayHeight = readUsedSize(canvas, 'height');
15124 if (displayWidth !== undefined) {
15125 canvas.height = displayHeight;
15126 }
15127 }
15128 }
15129
15130 return canvas;
15131}
15132
15133/**
15134 * Detects support for options object argument in addEventListener.
15135 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
15136 * @private
15137 */
15138var supportsEventListenerOptions = (function() {
15139 var supports = false;
15140 try {
15141 var options = Object.defineProperty({}, 'passive', {
15142 get: function() {
15143 supports = true;
15144 }
15145 });
15146 window.addEventListener('e', null, options);
15147 } catch (e) {
15148 // continue regardless of error
15149 }
15150 return supports;
15151}());
15152
15153// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
15154// https://github.com/chartjs/Chart.js/issues/4287
15155var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
15156
15157function addEventListener(node, type, listener) {
15158 node.addEventListener(type, listener, eventListenerOptions);
15159}
15160
15161function removeEventListener(node, type, listener) {
15162 node.removeEventListener(type, listener, eventListenerOptions);
15163}
15164
15165function createEvent(type, chart, x, y, nativeEvent) {
15166 return {
15167 type: type,
15168 chart: chart,
15169 native: nativeEvent || null,
15170 x: x !== undefined ? x : null,
15171 y: y !== undefined ? y : null,
15172 };
15173}
15174
15175function fromNativeEvent(event, chart) {
15176 var type = EVENT_TYPES[event.type] || event.type;
15177 var pos = helpers.getRelativePosition(event, chart);
15178 return createEvent(type, chart, pos.x, pos.y, event);
15179}
15180
15181function throttled(fn, thisArg) {
15182 var ticking = false;
15183 var args = [];
15184
15185 return function() {
15186 args = Array.prototype.slice.call(arguments);
15187 thisArg = thisArg || this;
15188
15189 if (!ticking) {
15190 ticking = true;
15191 helpers.requestAnimFrame.call(window, function() {
15192 ticking = false;
15193 fn.apply(thisArg, args);
15194 });
15195 }
15196 };
15197}
15198
15199// Implementation based on https://github.com/marcj/css-element-queries
15200function createResizer(handler) {
15201 var resizer = document.createElement('div');
15202 var cls = CSS_PREFIX + 'size-monitor';
15203 var maxSize = 1000000;
15204 var style =
15205 'position:absolute;' +
15206 'left:0;' +
15207 'top:0;' +
15208 'right:0;' +
15209 'bottom:0;' +
15210 'overflow:hidden;' +
15211 'pointer-events:none;' +
15212 'visibility:hidden;' +
15213 'z-index:-1;';
15214
15215 resizer.style.cssText = style;
15216 resizer.className = cls;
15217 resizer.innerHTML =
15218 '<div class="' + cls + '-expand" style="' + style + '">' +
15219 '<div style="' +
15220 'position:absolute;' +
15221 'width:' + maxSize + 'px;' +
15222 'height:' + maxSize + 'px;' +
15223 'left:0;' +
15224 'top:0">' +
15225 '</div>' +
15226 '</div>' +
15227 '<div class="' + cls + '-shrink" style="' + style + '">' +
15228 '<div style="' +
15229 'position:absolute;' +
15230 'width:200%;' +
15231 'height:200%;' +
15232 'left:0; ' +
15233 'top:0">' +
15234 '</div>' +
15235 '</div>';
15236
15237 var expand = resizer.childNodes[0];
15238 var shrink = resizer.childNodes[1];
15239
15240 resizer._reset = function() {
15241 expand.scrollLeft = maxSize;
15242 expand.scrollTop = maxSize;
15243 shrink.scrollLeft = maxSize;
15244 shrink.scrollTop = maxSize;
15245 };
15246 var onScroll = function() {
15247 resizer._reset();
15248 handler();
15249 };
15250
15251 addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
15252 addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
15253
15254 return resizer;
15255}
15256
15257// https://davidwalsh.name/detect-node-insertion
15258function watchForRender(node, handler) {
15259 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
15260 var proxy = expando.renderProxy = function(e) {
15261 if (e.animationName === CSS_RENDER_ANIMATION) {
15262 handler();
15263 }
15264 };
15265
15266 helpers.each(ANIMATION_START_EVENTS, function(type) {
15267 addEventListener(node, type, proxy);
15268 });
15269
15270 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
15271 // is removed then added back immediately (same animation frame?). Accessing the
15272 // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
15273 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
15274 // https://github.com/chartjs/Chart.js/issues/4737
15275 expando.reflow = !!node.offsetParent;
15276
15277 node.classList.add(CSS_RENDER_MONITOR);
15278}
15279
15280function unwatchForRender(node) {
15281 var expando = node[EXPANDO_KEY] || {};
15282 var proxy = expando.renderProxy;
15283
15284 if (proxy) {
15285 helpers.each(ANIMATION_START_EVENTS, function(type) {
15286 removeEventListener(node, type, proxy);
15287 });
15288
15289 delete expando.renderProxy;
15290 }
15291
15292 node.classList.remove(CSS_RENDER_MONITOR);
15293}
15294
15295function addResizeListener(node, listener, chart) {
15296 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
15297
15298 // Let's keep track of this added resizer and thus avoid DOM query when removing it.
15299 var resizer = expando.resizer = createResizer(throttled(function() {
15300 if (expando.resizer) {
15301 return listener(createEvent('resize', chart));
15302 }
15303 }));
15304
15305 // The resizer needs to be attached to the node parent, so we first need to be
15306 // sure that `node` is attached to the DOM before injecting the resizer element.
15307 watchForRender(node, function() {
15308 if (expando.resizer) {
15309 var container = node.parentNode;
15310 if (container && container !== resizer.parentNode) {
15311 container.insertBefore(resizer, container.firstChild);
15312 }
15313
15314 // The container size might have changed, let's reset the resizer state.
15315 resizer._reset();
15316 }
15317 });
15318}
15319
15320function removeResizeListener(node) {
15321 var expando = node[EXPANDO_KEY] || {};
15322 var resizer = expando.resizer;
15323
15324 delete expando.resizer;
15325 unwatchForRender(node);
15326
15327 if (resizer && resizer.parentNode) {
15328 resizer.parentNode.removeChild(resizer);
15329 }
15330}
15331
15332function injectCSS(platform, css) {
15333 // http://stackoverflow.com/q/3922139
15334 var style = platform._style || document.createElement('style');
15335 if (!platform._style) {
15336 platform._style = style;
15337 css = '/* Chart.js */\n' + css;
15338 style.setAttribute('type', 'text/css');
15339 document.getElementsByTagName('head')[0].appendChild(style);
15340 }
15341
15342 style.appendChild(document.createTextNode(css));
15343}
15344
15345module.exports = {
15346 /**
15347 * This property holds whether this platform is enabled for the current environment.
15348 * Currently used by platform.js to select the proper implementation.
15349 * @private
15350 */
15351 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
15352
15353 initialize: function() {
15354 var keyframes = 'from{opacity:0.99}to{opacity:1}';
15355
15356 injectCSS(this,
15357 // DOM rendering detection
15358 // https://davidwalsh.name/detect-node-insertion
15359 '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
15360 '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
15361 '.' + CSS_RENDER_MONITOR + '{' +
15362 '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
15363 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
15364 '}'
15365 );
15366 },
15367
15368 acquireContext: function(item, config) {
15369 if (typeof item === 'string') {
15370 item = document.getElementById(item);
15371 } else if (item.length) {
15372 // Support for array based queries (such as jQuery)
15373 item = item[0];
15374 }
15375
15376 if (item && item.canvas) {
15377 // Support for any object associated to a canvas (including a context2d)
15378 item = item.canvas;
15379 }
15380
15381 // To prevent canvas fingerprinting, some add-ons undefine the getContext
15382 // method, for example: https://github.com/kkapsner/CanvasBlocker
15383 // https://github.com/chartjs/Chart.js/issues/2807
15384 var context = item && item.getContext && item.getContext('2d');
15385
15386 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
15387 // inside an iframe or when running in a protected environment. We could guess the
15388 // types from their toString() value but let's keep things flexible and assume it's
15389 // a sufficient condition if the item has a context2D which has item as `canvas`.
15390 // https://github.com/chartjs/Chart.js/issues/3887
15391 // https://github.com/chartjs/Chart.js/issues/4102
15392 // https://github.com/chartjs/Chart.js/issues/4152
15393 if (context && context.canvas === item) {
15394 initCanvas(item, config);
15395 return context;
15396 }
15397
15398 return null;
15399 },
15400
15401 releaseContext: function(context) {
15402 var canvas = context.canvas;
15403 if (!canvas[EXPANDO_KEY]) {
15404 return;
15405 }
15406
15407 var initial = canvas[EXPANDO_KEY].initial;
15408 ['height', 'width'].forEach(function(prop) {
15409 var value = initial[prop];
15410 if (helpers.isNullOrUndef(value)) {
15411 canvas.removeAttribute(prop);
15412 } else {
15413 canvas.setAttribute(prop, value);
15414 }
15415 });
15416
15417 helpers.each(initial.style || {}, function(value, key) {
15418 canvas.style[key] = value;
15419 });
15420
15421 // The canvas render size might have been changed (and thus the state stack discarded),
15422 // we can't use save() and restore() to restore the initial state. So make sure that at
15423 // least the canvas context is reset to the default state by setting the canvas width.
15424 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
15425 canvas.width = canvas.width;
15426
15427 delete canvas[EXPANDO_KEY];
15428 },
15429
15430 addEventListener: function(chart, type, listener) {
15431 var canvas = chart.canvas;
15432 if (type === 'resize') {
15433 // Note: the resize event is not supported on all browsers.
15434 addResizeListener(canvas, listener, chart);
15435 return;
15436 }
15437
15438 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
15439 var proxies = expando.proxies || (expando.proxies = {});
15440 var proxy = proxies[chart.id + '_' + type] = function(event) {
15441 listener(fromNativeEvent(event, chart));
15442 };
15443
15444 addEventListener(canvas, type, proxy);
15445 },
15446
15447 removeEventListener: function(chart, type, listener) {
15448 var canvas = chart.canvas;
15449 if (type === 'resize') {
15450 // Note: the resize event is not supported on all browsers.
15451 removeResizeListener(canvas, listener);
15452 return;
15453 }
15454
15455 var expando = listener[EXPANDO_KEY] || {};
15456 var proxies = expando.proxies || {};
15457 var proxy = proxies[chart.id + '_' + type];
15458 if (!proxy) {
15459 return;
15460 }
15461
15462 removeEventListener(canvas, type, proxy);
15463 }
15464};
15465
15466// DEPRECATIONS
15467
15468/**
15469 * Provided for backward compatibility, use EventTarget.addEventListener instead.
15470 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
15471 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
15472 * @function Chart.helpers.addEvent
15473 * @deprecated since version 2.7.0
15474 * @todo remove at version 3
15475 * @private
15476 */
15477helpers.addEvent = addEventListener;
15478
15479/**
15480 * Provided for backward compatibility, use EventTarget.removeEventListener instead.
15481 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
15482 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
15483 * @function Chart.helpers.removeEvent
15484 * @deprecated since version 2.7.0
15485 * @todo remove at version 3
15486 * @private
15487 */
15488helpers.removeEvent = removeEventListener;
15489
15490},{"45":45}],48:[function(require,module,exports){
15491'use strict';
15492
15493var helpers = require(45);
15494var basic = require(46);
15495var dom = require(47);
15496
15497// @TODO Make possible to select another platform at build time.
15498var implementation = dom._enabled ? dom : basic;
15499
15500/**
15501 * @namespace Chart.platform
15502 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
15503 * @since 2.4.0
15504 */
15505module.exports = helpers.extend({
15506 /**
15507 * @since 2.7.0
15508 */
15509 initialize: function() {},
15510
15511 /**
15512 * Called at chart construction time, returns a context2d instance implementing
15513 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
15514 * @param {*} item - The native item from which to acquire context (platform specific)
15515 * @param {Object} options - The chart options
15516 * @returns {CanvasRenderingContext2D} context2d instance
15517 */
15518 acquireContext: function() {},
15519
15520 /**
15521 * Called at chart destruction time, releases any resources associated to the context
15522 * previously returned by the acquireContext() method.
15523 * @param {CanvasRenderingContext2D} context - The context2d instance
15524 * @returns {Boolean} true if the method succeeded, else false
15525 */
15526 releaseContext: function() {},
15527
15528 /**
15529 * Registers the specified listener on the given chart.
15530 * @param {Chart} chart - Chart from which to listen for event
15531 * @param {String} type - The ({@link IEvent}) type to listen for
15532 * @param {Function} listener - Receives a notification (an object that implements
15533 * the {@link IEvent} interface) when an event of the specified type occurs.
15534 */
15535 addEventListener: function() {},
15536
15537 /**
15538 * Removes the specified listener previously registered with addEventListener.
15539 * @param {Chart} chart -Chart from which to remove the listener
15540 * @param {String} type - The ({@link IEvent}) type to remove
15541 * @param {Function} listener - The listener function to remove from the event target.
15542 */
15543 removeEventListener: function() {}
15544
15545}, implementation);
15546
15547/**
15548 * @interface IPlatform
15549 * Allows abstracting platform dependencies away from the chart
15550 * @borrows Chart.platform.acquireContext as acquireContext
15551 * @borrows Chart.platform.releaseContext as releaseContext
15552 * @borrows Chart.platform.addEventListener as addEventListener
15553 * @borrows Chart.platform.removeEventListener as removeEventListener
15554 */
15555
15556/**
15557 * @interface IEvent
15558 * @prop {String} type - The event type name, possible values are:
15559 * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',
15560 * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'
15561 * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')
15562 * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)
15563 * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)
15564 */
15565
15566},{"45":45,"46":46,"47":47}],49:[function(require,module,exports){
15567'use strict';
15568
15569module.exports = {};
15570module.exports.filler = require(50);
15571module.exports.legend = require(51);
15572module.exports.title = require(52);
15573
15574},{"50":50,"51":51,"52":52}],50:[function(require,module,exports){
15575/**
15576 * Plugin based on discussion from the following Chart.js issues:
15577 * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569
15578 * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897
15579 */
15580
15581'use strict';
15582
15583var defaults = require(25);
15584var elements = require(40);
15585var helpers = require(45);
15586
15587defaults._set('global', {
15588 plugins: {
15589 filler: {
15590 propagate: true
15591 }
15592 }
15593});
15594
15595var mappers = {
15596 dataset: function(source) {
15597 var index = source.fill;
15598 var chart = source.chart;
15599 var meta = chart.getDatasetMeta(index);
15600 var visible = meta && chart.isDatasetVisible(index);
15601 var points = (visible && meta.dataset._children) || [];
15602 var length = points.length || 0;
15603
15604 return !length ? null : function(point, i) {
15605 return (i < length && points[i]._view) || null;
15606 };
15607 },
15608
15609 boundary: function(source) {
15610 var boundary = source.boundary;
15611 var x = boundary ? boundary.x : null;
15612 var y = boundary ? boundary.y : null;
15613
15614 return function(point) {
15615 return {
15616 x: x === null ? point.x : x,
15617 y: y === null ? point.y : y,
15618 };
15619 };
15620 }
15621};
15622
15623// @todo if (fill[0] === '#')
15624function decodeFill(el, index, count) {
15625 var model = el._model || {};
15626 var fill = model.fill;
15627 var target;
15628
15629 if (fill === undefined) {
15630 fill = !!model.backgroundColor;
15631 }
15632
15633 if (fill === false || fill === null) {
15634 return false;
15635 }
15636
15637 if (fill === true) {
15638 return 'origin';
15639 }
15640
15641 target = parseFloat(fill, 10);
15642 if (isFinite(target) && Math.floor(target) === target) {
15643 if (fill[0] === '-' || fill[0] === '+') {
15644 target = index + target;
15645 }
15646
15647 if (target === index || target < 0 || target >= count) {
15648 return false;
15649 }
15650
15651 return target;
15652 }
15653
15654 switch (fill) {
15655 // compatibility
15656 case 'bottom':
15657 return 'start';
15658 case 'top':
15659 return 'end';
15660 case 'zero':
15661 return 'origin';
15662 // supported boundaries
15663 case 'origin':
15664 case 'start':
15665 case 'end':
15666 return fill;
15667 // invalid fill values
15668 default:
15669 return false;
15670 }
15671}
15672
15673function computeBoundary(source) {
15674 var model = source.el._model || {};
15675 var scale = source.el._scale || {};
15676 var fill = source.fill;
15677 var target = null;
15678 var horizontal;
15679
15680 if (isFinite(fill)) {
15681 return null;
15682 }
15683
15684 // Backward compatibility: until v3, we still need to support boundary values set on
15685 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
15686 // controllers might still use it (e.g. the Smith chart).
15687
15688 if (fill === 'start') {
15689 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
15690 } else if (fill === 'end') {
15691 target = model.scaleTop === undefined ? scale.top : model.scaleTop;
15692 } else if (model.scaleZero !== undefined) {
15693 target = model.scaleZero;
15694 } else if (scale.getBasePosition) {
15695 target = scale.getBasePosition();
15696 } else if (scale.getBasePixel) {
15697 target = scale.getBasePixel();
15698 }
15699
15700 if (target !== undefined && target !== null) {
15701 if (target.x !== undefined && target.y !== undefined) {
15702 return target;
15703 }
15704
15705 if (typeof target === 'number' && isFinite(target)) {
15706 horizontal = scale.isHorizontal();
15707 return {
15708 x: horizontal ? target : null,
15709 y: horizontal ? null : target
15710 };
15711 }
15712 }
15713
15714 return null;
15715}
15716
15717function resolveTarget(sources, index, propagate) {
15718 var source = sources[index];
15719 var fill = source.fill;
15720 var visited = [index];
15721 var target;
15722
15723 if (!propagate) {
15724 return fill;
15725 }
15726
15727 while (fill !== false && visited.indexOf(fill) === -1) {
15728 if (!isFinite(fill)) {
15729 return fill;
15730 }
15731
15732 target = sources[fill];
15733 if (!target) {
15734 return false;
15735 }
15736
15737 if (target.visible) {
15738 return fill;
15739 }
15740
15741 visited.push(fill);
15742 fill = target.fill;
15743 }
15744
15745 return false;
15746}
15747
15748function createMapper(source) {
15749 var fill = source.fill;
15750 var type = 'dataset';
15751
15752 if (fill === false) {
15753 return null;
15754 }
15755
15756 if (!isFinite(fill)) {
15757 type = 'boundary';
15758 }
15759
15760 return mappers[type](source);
15761}
15762
15763function isDrawable(point) {
15764 return point && !point.skip;
15765}
15766
15767function drawArea(ctx, curve0, curve1, len0, len1) {
15768 var i;
15769
15770 if (!len0 || !len1) {
15771 return;
15772 }
15773
15774 // building first area curve (normal)
15775 ctx.moveTo(curve0[0].x, curve0[0].y);
15776 for (i = 1; i < len0; ++i) {
15777 helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
15778 }
15779
15780 // joining the two area curves
15781 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
15782
15783 // building opposite area curve (reverse)
15784 for (i = len1 - 1; i > 0; --i) {
15785 helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
15786 }
15787}
15788
15789function doFill(ctx, points, mapper, view, color, loop) {
15790 var count = points.length;
15791 var span = view.spanGaps;
15792 var curve0 = [];
15793 var curve1 = [];
15794 var len0 = 0;
15795 var len1 = 0;
15796 var i, ilen, index, p0, p1, d0, d1;
15797
15798 ctx.beginPath();
15799
15800 for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
15801 index = i % count;
15802 p0 = points[index]._view;
15803 p1 = mapper(p0, index, view);
15804 d0 = isDrawable(p0);
15805 d1 = isDrawable(p1);
15806
15807 if (d0 && d1) {
15808 len0 = curve0.push(p0);
15809 len1 = curve1.push(p1);
15810 } else if (len0 && len1) {
15811 if (!span) {
15812 drawArea(ctx, curve0, curve1, len0, len1);
15813 len0 = len1 = 0;
15814 curve0 = [];
15815 curve1 = [];
15816 } else {
15817 if (d0) {
15818 curve0.push(p0);
15819 }
15820 if (d1) {
15821 curve1.push(p1);
15822 }
15823 }
15824 }
15825 }
15826
15827 drawArea(ctx, curve0, curve1, len0, len1);
15828
15829 ctx.closePath();
15830 ctx.fillStyle = color;
15831 ctx.fill();
15832}
15833
15834module.exports = {
15835 id: 'filler',
15836
15837 afterDatasetsUpdate: function(chart, options) {
15838 var count = (chart.data.datasets || []).length;
15839 var propagate = options.propagate;
15840 var sources = [];
15841 var meta, i, el, source;
15842
15843 for (i = 0; i < count; ++i) {
15844 meta = chart.getDatasetMeta(i);
15845 el = meta.dataset;
15846 source = null;
15847
15848 if (el && el._model && el instanceof elements.Line) {
15849 source = {
15850 visible: chart.isDatasetVisible(i),
15851 fill: decodeFill(el, i, count),
15852 chart: chart,
15853 el: el
15854 };
15855 }
15856
15857 meta.$filler = source;
15858 sources.push(source);
15859 }
15860
15861 for (i = 0; i < count; ++i) {
15862 source = sources[i];
15863 if (!source) {
15864 continue;
15865 }
15866
15867 source.fill = resolveTarget(sources, i, propagate);
15868 source.boundary = computeBoundary(source);
15869 source.mapper = createMapper(source);
15870 }
15871 },
15872
15873 beforeDatasetDraw: function(chart, args) {
15874 var meta = args.meta.$filler;
15875 if (!meta) {
15876 return;
15877 }
15878
15879 var ctx = chart.ctx;
15880 var el = meta.el;
15881 var view = el._view;
15882 var points = el._children || [];
15883 var mapper = meta.mapper;
15884 var color = view.backgroundColor || defaults.global.defaultColor;
15885
15886 if (mapper && color && points.length) {
15887 helpers.canvas.clipArea(ctx, chart.chartArea);
15888 doFill(ctx, points, mapper, view, color, el._loop);
15889 helpers.canvas.unclipArea(ctx);
15890 }
15891 }
15892};
15893
15894},{"25":25,"40":40,"45":45}],51:[function(require,module,exports){
15895'use strict';
15896
15897var defaults = require(25);
15898var Element = require(26);
15899var helpers = require(45);
15900var layouts = require(30);
15901
15902var noop = helpers.noop;
15903
15904defaults._set('global', {
15905 legend: {
15906 display: true,
15907 position: 'top',
15908 fullWidth: true,
15909 reverse: false,
15910 weight: 1000,
15911
15912 // a callback that will handle
15913 onClick: function(e, legendItem) {
15914 var index = legendItem.datasetIndex;
15915 var ci = this.chart;
15916 var meta = ci.getDatasetMeta(index);
15917
15918 // See controller.isDatasetVisible comment
15919 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
15920
15921 // We hid a dataset ... rerender the chart
15922 ci.update();
15923 },
15924
15925 onHover: null,
15926
15927 labels: {
15928 boxWidth: 40,
15929 padding: 10,
15930 // Generates labels shown in the legend
15931 // Valid properties to return:
15932 // text : text to display
15933 // fillStyle : fill of coloured box
15934 // strokeStyle: stroke of coloured box
15935 // hidden : if this legend item refers to a hidden item
15936 // lineCap : cap style for line
15937 // lineDash
15938 // lineDashOffset :
15939 // lineJoin :
15940 // lineWidth :
15941 generateLabels: function(chart) {
15942 var data = chart.data;
15943 return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
15944 return {
15945 text: dataset.label,
15946 fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
15947 hidden: !chart.isDatasetVisible(i),
15948 lineCap: dataset.borderCapStyle,
15949 lineDash: dataset.borderDash,
15950 lineDashOffset: dataset.borderDashOffset,
15951 lineJoin: dataset.borderJoinStyle,
15952 lineWidth: dataset.borderWidth,
15953 strokeStyle: dataset.borderColor,
15954 pointStyle: dataset.pointStyle,
15955
15956 // Below is extra data used for toggling the datasets
15957 datasetIndex: i
15958 };
15959 }, this) : [];
15960 }
15961 }
15962 },
15963
15964 legendCallback: function(chart) {
15965 var text = [];
15966 text.push('<ul class="' + chart.id + '-legend">');
15967 for (var i = 0; i < chart.data.datasets.length; i++) {
15968 text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
15969 if (chart.data.datasets[i].label) {
15970 text.push(chart.data.datasets[i].label);
15971 }
15972 text.push('</li>');
15973 }
15974 text.push('</ul>');
15975 return text.join('');
15976 }
15977});
15978
15979/**
15980 * Helper function to get the box width based on the usePointStyle option
15981 * @param labelopts {Object} the label options on the legend
15982 * @param fontSize {Number} the label font size
15983 * @return {Number} width of the color box area
15984 */
15985function getBoxWidth(labelOpts, fontSize) {
15986 return labelOpts.usePointStyle ?
15987 fontSize * Math.SQRT2 :
15988 labelOpts.boxWidth;
15989}
15990
15991/**
15992 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
15993 */
15994var Legend = Element.extend({
15995
15996 initialize: function(config) {
15997 helpers.extend(this, config);
15998
15999 // Contains hit boxes for each dataset (in dataset order)
16000 this.legendHitBoxes = [];
16001
16002 // Are we in doughnut mode which has a different data type
16003 this.doughnutMode = false;
16004 },
16005
16006 // These methods are ordered by lifecycle. Utilities then follow.
16007 // Any function defined here is inherited by all legend types.
16008 // Any function can be extended by the legend type
16009
16010 beforeUpdate: noop,
16011 update: function(maxWidth, maxHeight, margins) {
16012 var me = this;
16013
16014 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
16015 me.beforeUpdate();
16016
16017 // Absorb the master measurements
16018 me.maxWidth = maxWidth;
16019 me.maxHeight = maxHeight;
16020 me.margins = margins;
16021
16022 // Dimensions
16023 me.beforeSetDimensions();
16024 me.setDimensions();
16025 me.afterSetDimensions();
16026 // Labels
16027 me.beforeBuildLabels();
16028 me.buildLabels();
16029 me.afterBuildLabels();
16030
16031 // Fit
16032 me.beforeFit();
16033 me.fit();
16034 me.afterFit();
16035 //
16036 me.afterUpdate();
16037
16038 return me.minSize;
16039 },
16040 afterUpdate: noop,
16041
16042 //
16043
16044 beforeSetDimensions: noop,
16045 setDimensions: function() {
16046 var me = this;
16047 // Set the unconstrained dimension before label rotation
16048 if (me.isHorizontal()) {
16049 // Reset position before calculating rotation
16050 me.width = me.maxWidth;
16051 me.left = 0;
16052 me.right = me.width;
16053 } else {
16054 me.height = me.maxHeight;
16055
16056 // Reset position before calculating rotation
16057 me.top = 0;
16058 me.bottom = me.height;
16059 }
16060
16061 // Reset padding
16062 me.paddingLeft = 0;
16063 me.paddingTop = 0;
16064 me.paddingRight = 0;
16065 me.paddingBottom = 0;
16066
16067 // Reset minSize
16068 me.minSize = {
16069 width: 0,
16070 height: 0
16071 };
16072 },
16073 afterSetDimensions: noop,
16074
16075 //
16076
16077 beforeBuildLabels: noop,
16078 buildLabels: function() {
16079 var me = this;
16080 var labelOpts = me.options.labels || {};
16081 var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
16082
16083 if (labelOpts.filter) {
16084 legendItems = legendItems.filter(function(item) {
16085 return labelOpts.filter(item, me.chart.data);
16086 });
16087 }
16088
16089 if (me.options.reverse) {
16090 legendItems.reverse();
16091 }
16092
16093 me.legendItems = legendItems;
16094 },
16095 afterBuildLabels: noop,
16096
16097 //
16098
16099 beforeFit: noop,
16100 fit: function() {
16101 var me = this;
16102 var opts = me.options;
16103 var labelOpts = opts.labels;
16104 var display = opts.display;
16105
16106 var ctx = me.ctx;
16107
16108 var globalDefault = defaults.global;
16109 var valueOrDefault = helpers.valueOrDefault;
16110 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
16111 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
16112 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
16113 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
16114
16115 // Reset hit boxes
16116 var hitboxes = me.legendHitBoxes = [];
16117
16118 var minSize = me.minSize;
16119 var isHorizontal = me.isHorizontal();
16120
16121 if (isHorizontal) {
16122 minSize.width = me.maxWidth; // fill all the width
16123 minSize.height = display ? 10 : 0;
16124 } else {
16125 minSize.width = display ? 10 : 0;
16126 minSize.height = me.maxHeight; // fill all the height
16127 }
16128
16129 // Increase sizes here
16130 if (display) {
16131 ctx.font = labelFont;
16132
16133 if (isHorizontal) {
16134 // Labels
16135
16136 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
16137 var lineWidths = me.lineWidths = [0];
16138 var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
16139
16140 ctx.textAlign = 'left';
16141 ctx.textBaseline = 'top';
16142
16143 helpers.each(me.legendItems, function(legendItem, i) {
16144 var boxWidth = getBoxWidth(labelOpts, fontSize);
16145 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
16146
16147 if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
16148 totalHeight += fontSize + (labelOpts.padding);
16149 lineWidths[lineWidths.length] = me.left;
16150 }
16151
16152 // Store the hitbox width and height here. Final position will be updated in `draw`
16153 hitboxes[i] = {
16154 left: 0,
16155 top: 0,
16156 width: width,
16157 height: fontSize
16158 };
16159
16160 lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
16161 });
16162
16163 minSize.height += totalHeight;
16164
16165 } else {
16166 var vPadding = labelOpts.padding;
16167 var columnWidths = me.columnWidths = [];
16168 var totalWidth = labelOpts.padding;
16169 var currentColWidth = 0;
16170 var currentColHeight = 0;
16171 var itemHeight = fontSize + vPadding;
16172
16173 helpers.each(me.legendItems, function(legendItem, i) {
16174 var boxWidth = getBoxWidth(labelOpts, fontSize);
16175 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
16176
16177 // If too tall, go to new column
16178 if (currentColHeight + itemHeight > minSize.height) {
16179 totalWidth += currentColWidth + labelOpts.padding;
16180 columnWidths.push(currentColWidth); // previous column width
16181
16182 currentColWidth = 0;
16183 currentColHeight = 0;
16184 }
16185
16186 // Get max width
16187 currentColWidth = Math.max(currentColWidth, itemWidth);
16188 currentColHeight += itemHeight;
16189
16190 // Store the hitbox width and height here. Final position will be updated in `draw`
16191 hitboxes[i] = {
16192 left: 0,
16193 top: 0,
16194 width: itemWidth,
16195 height: fontSize
16196 };
16197 });
16198
16199 totalWidth += currentColWidth;
16200 columnWidths.push(currentColWidth);
16201 minSize.width += totalWidth;
16202 }
16203 }
16204
16205 me.width = minSize.width;
16206 me.height = minSize.height;
16207 },
16208 afterFit: noop,
16209
16210 // Shared Methods
16211 isHorizontal: function() {
16212 return this.options.position === 'top' || this.options.position === 'bottom';
16213 },
16214
16215 // Actually draw the legend on the canvas
16216 draw: function() {
16217 var me = this;
16218 var opts = me.options;
16219 var labelOpts = opts.labels;
16220 var globalDefault = defaults.global;
16221 var lineDefault = globalDefault.elements.line;
16222 var legendWidth = me.width;
16223 var lineWidths = me.lineWidths;
16224
16225 if (opts.display) {
16226 var ctx = me.ctx;
16227 var valueOrDefault = helpers.valueOrDefault;
16228 var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
16229 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
16230 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
16231 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
16232 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
16233 var cursor;
16234
16235 // Canvas setup
16236 ctx.textAlign = 'left';
16237 ctx.textBaseline = 'middle';
16238 ctx.lineWidth = 0.5;
16239 ctx.strokeStyle = fontColor; // for strikethrough effect
16240 ctx.fillStyle = fontColor; // render in correct colour
16241 ctx.font = labelFont;
16242
16243 var boxWidth = getBoxWidth(labelOpts, fontSize);
16244 var hitboxes = me.legendHitBoxes;
16245
16246 // current position
16247 var drawLegendBox = function(x, y, legendItem) {
16248 if (isNaN(boxWidth) || boxWidth <= 0) {
16249 return;
16250 }
16251
16252 // Set the ctx for the box
16253 ctx.save();
16254
16255 ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
16256 ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
16257 ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
16258 ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
16259 ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
16260 ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
16261 var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
16262
16263 if (ctx.setLineDash) {
16264 // IE 9 and 10 do not support line dash
16265 ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
16266 }
16267
16268 if (opts.labels && opts.labels.usePointStyle) {
16269 // Recalculate x and y for drawPoint() because its expecting
16270 // x and y to be center of figure (instead of top left)
16271 var radius = fontSize * Math.SQRT2 / 2;
16272 var offSet = radius / Math.SQRT2;
16273 var centerX = x + offSet;
16274 var centerY = y + offSet;
16275
16276 // Draw pointStyle as legend symbol
16277 helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
16278 } else {
16279 // Draw box as legend symbol
16280 if (!isLineWidthZero) {
16281 ctx.strokeRect(x, y, boxWidth, fontSize);
16282 }
16283 ctx.fillRect(x, y, boxWidth, fontSize);
16284 }
16285
16286 ctx.restore();
16287 };
16288 var fillText = function(x, y, legendItem, textWidth) {
16289 var halfFontSize = fontSize / 2;
16290 var xLeft = boxWidth + halfFontSize + x;
16291 var yMiddle = y + halfFontSize;
16292
16293 ctx.fillText(legendItem.text, xLeft, yMiddle);
16294
16295 if (legendItem.hidden) {
16296 // Strikethrough the text if hidden
16297 ctx.beginPath();
16298 ctx.lineWidth = 2;
16299 ctx.moveTo(xLeft, yMiddle);
16300 ctx.lineTo(xLeft + textWidth, yMiddle);
16301 ctx.stroke();
16302 }
16303 };
16304
16305 // Horizontal
16306 var isHorizontal = me.isHorizontal();
16307 if (isHorizontal) {
16308 cursor = {
16309 x: me.left + ((legendWidth - lineWidths[0]) / 2),
16310 y: me.top + labelOpts.padding,
16311 line: 0
16312 };
16313 } else {
16314 cursor = {
16315 x: me.left + labelOpts.padding,
16316 y: me.top + labelOpts.padding,
16317 line: 0
16318 };
16319 }
16320
16321 var itemHeight = fontSize + labelOpts.padding;
16322 helpers.each(me.legendItems, function(legendItem, i) {
16323 var textWidth = ctx.measureText(legendItem.text).width;
16324 var width = boxWidth + (fontSize / 2) + textWidth;
16325 var x = cursor.x;
16326 var y = cursor.y;
16327
16328 if (isHorizontal) {
16329 if (x + width >= legendWidth) {
16330 y = cursor.y += itemHeight;
16331 cursor.line++;
16332 x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
16333 }
16334 } else if (y + itemHeight > me.bottom) {
16335 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
16336 y = cursor.y = me.top + labelOpts.padding;
16337 cursor.line++;
16338 }
16339
16340 drawLegendBox(x, y, legendItem);
16341
16342 hitboxes[i].left = x;
16343 hitboxes[i].top = y;
16344
16345 // Fill the actual label
16346 fillText(x, y, legendItem, textWidth);
16347
16348 if (isHorizontal) {
16349 cursor.x += width + (labelOpts.padding);
16350 } else {
16351 cursor.y += itemHeight;
16352 }
16353
16354 });
16355 }
16356 },
16357
16358 /**
16359 * Handle an event
16360 * @private
16361 * @param {IEvent} event - The event to handle
16362 * @return {Boolean} true if a change occured
16363 */
16364 handleEvent: function(e) {
16365 var me = this;
16366 var opts = me.options;
16367 var type = e.type === 'mouseup' ? 'click' : e.type;
16368 var changed = false;
16369
16370 if (type === 'mousemove') {
16371 if (!opts.onHover) {
16372 return;
16373 }
16374 } else if (type === 'click') {
16375 if (!opts.onClick) {
16376 return;
16377 }
16378 } else {
16379 return;
16380 }
16381
16382 // Chart event already has relative position in it
16383 var x = e.x;
16384 var y = e.y;
16385
16386 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
16387 // See if we are touching one of the dataset boxes
16388 var lh = me.legendHitBoxes;
16389 for (var i = 0; i < lh.length; ++i) {
16390 var hitBox = lh[i];
16391
16392 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
16393 // Touching an element
16394 if (type === 'click') {
16395 // use e.native for backwards compatibility
16396 opts.onClick.call(me, e.native, me.legendItems[i]);
16397 changed = true;
16398 break;
16399 } else if (type === 'mousemove') {
16400 // use e.native for backwards compatibility
16401 opts.onHover.call(me, e.native, me.legendItems[i]);
16402 changed = true;
16403 break;
16404 }
16405 }
16406 }
16407 }
16408
16409 return changed;
16410 }
16411});
16412
16413function createNewLegendAndAttach(chart, legendOpts) {
16414 var legend = new Legend({
16415 ctx: chart.ctx,
16416 options: legendOpts,
16417 chart: chart
16418 });
16419
16420 layouts.configure(chart, legend, legendOpts);
16421 layouts.addBox(chart, legend);
16422 chart.legend = legend;
16423}
16424
16425module.exports = {
16426 id: 'legend',
16427
16428 /**
16429 * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making
16430 * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of
16431 * the plugin, which one will be re-exposed in the chart.js file.
16432 * https://github.com/chartjs/Chart.js/pull/2640
16433 * @private
16434 */
16435 _element: Legend,
16436
16437 beforeInit: function(chart) {
16438 var legendOpts = chart.options.legend;
16439
16440 if (legendOpts) {
16441 createNewLegendAndAttach(chart, legendOpts);
16442 }
16443 },
16444
16445 beforeUpdate: function(chart) {
16446 var legendOpts = chart.options.legend;
16447 var legend = chart.legend;
16448
16449 if (legendOpts) {
16450 helpers.mergeIf(legendOpts, defaults.global.legend);
16451
16452 if (legend) {
16453 layouts.configure(chart, legend, legendOpts);
16454 legend.options = legendOpts;
16455 } else {
16456 createNewLegendAndAttach(chart, legendOpts);
16457 }
16458 } else if (legend) {
16459 layouts.removeBox(chart, legend);
16460 delete chart.legend;
16461 }
16462 },
16463
16464 afterEvent: function(chart, e) {
16465 var legend = chart.legend;
16466 if (legend) {
16467 legend.handleEvent(e);
16468 }
16469 }
16470};
16471
16472},{"25":25,"26":26,"30":30,"45":45}],52:[function(require,module,exports){
16473'use strict';
16474
16475var defaults = require(25);
16476var Element = require(26);
16477var helpers = require(45);
16478var layouts = require(30);
16479
16480var noop = helpers.noop;
16481
16482defaults._set('global', {
16483 title: {
16484 display: false,
16485 fontStyle: 'bold',
16486 fullWidth: true,
16487 lineHeight: 1.2,
16488 padding: 10,
16489 position: 'top',
16490 text: '',
16491 weight: 2000 // by default greater than legend (1000) to be above
16492 }
16493});
16494
16495/**
16496 * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
16497 */
16498var Title = Element.extend({
16499 initialize: function(config) {
16500 var me = this;
16501 helpers.extend(me, config);
16502
16503 // Contains hit boxes for each dataset (in dataset order)
16504 me.legendHitBoxes = [];
16505 },
16506
16507 // These methods are ordered by lifecycle. Utilities then follow.
16508
16509 beforeUpdate: noop,
16510 update: function(maxWidth, maxHeight, margins) {
16511 var me = this;
16512
16513 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
16514 me.beforeUpdate();
16515
16516 // Absorb the master measurements
16517 me.maxWidth = maxWidth;
16518 me.maxHeight = maxHeight;
16519 me.margins = margins;
16520
16521 // Dimensions
16522 me.beforeSetDimensions();
16523 me.setDimensions();
16524 me.afterSetDimensions();
16525 // Labels
16526 me.beforeBuildLabels();
16527 me.buildLabels();
16528 me.afterBuildLabels();
16529
16530 // Fit
16531 me.beforeFit();
16532 me.fit();
16533 me.afterFit();
16534 //
16535 me.afterUpdate();
16536
16537 return me.minSize;
16538
16539 },
16540 afterUpdate: noop,
16541
16542 //
16543
16544 beforeSetDimensions: noop,
16545 setDimensions: function() {
16546 var me = this;
16547 // Set the unconstrained dimension before label rotation
16548 if (me.isHorizontal()) {
16549 // Reset position before calculating rotation
16550 me.width = me.maxWidth;
16551 me.left = 0;
16552 me.right = me.width;
16553 } else {
16554 me.height = me.maxHeight;
16555
16556 // Reset position before calculating rotation
16557 me.top = 0;
16558 me.bottom = me.height;
16559 }
16560
16561 // Reset padding
16562 me.paddingLeft = 0;
16563 me.paddingTop = 0;
16564 me.paddingRight = 0;
16565 me.paddingBottom = 0;
16566
16567 // Reset minSize
16568 me.minSize = {
16569 width: 0,
16570 height: 0
16571 };
16572 },
16573 afterSetDimensions: noop,
16574
16575 //
16576
16577 beforeBuildLabels: noop,
16578 buildLabels: noop,
16579 afterBuildLabels: noop,
16580
16581 //
16582
16583 beforeFit: noop,
16584 fit: function() {
16585 var me = this;
16586 var valueOrDefault = helpers.valueOrDefault;
16587 var opts = me.options;
16588 var display = opts.display;
16589 var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
16590 var minSize = me.minSize;
16591 var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
16592 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
16593 var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
16594
16595 if (me.isHorizontal()) {
16596 minSize.width = me.maxWidth; // fill all the width
16597 minSize.height = textSize;
16598 } else {
16599 minSize.width = textSize;
16600 minSize.height = me.maxHeight; // fill all the height
16601 }
16602
16603 me.width = minSize.width;
16604 me.height = minSize.height;
16605
16606 },
16607 afterFit: noop,
16608
16609 // Shared Methods
16610 isHorizontal: function() {
16611 var pos = this.options.position;
16612 return pos === 'top' || pos === 'bottom';
16613 },
16614
16615 // Actually draw the title block on the canvas
16616 draw: function() {
16617 var me = this;
16618 var ctx = me.ctx;
16619 var valueOrDefault = helpers.valueOrDefault;
16620 var opts = me.options;
16621 var globalDefaults = defaults.global;
16622
16623 if (opts.display) {
16624 var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
16625 var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
16626 var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
16627 var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
16628 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
16629 var offset = lineHeight / 2 + opts.padding;
16630 var rotation = 0;
16631 var top = me.top;
16632 var left = me.left;
16633 var bottom = me.bottom;
16634 var right = me.right;
16635 var maxWidth, titleX, titleY;
16636
16637 ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
16638 ctx.font = titleFont;
16639
16640 // Horizontal
16641 if (me.isHorizontal()) {
16642 titleX = left + ((right - left) / 2); // midpoint of the width
16643 titleY = top + offset;
16644 maxWidth = right - left;
16645 } else {
16646 titleX = opts.position === 'left' ? left + offset : right - offset;
16647 titleY = top + ((bottom - top) / 2);
16648 maxWidth = bottom - top;
16649 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
16650 }
16651
16652 ctx.save();
16653 ctx.translate(titleX, titleY);
16654 ctx.rotate(rotation);
16655 ctx.textAlign = 'center';
16656 ctx.textBaseline = 'middle';
16657
16658 var text = opts.text;
16659 if (helpers.isArray(text)) {
16660 var y = 0;
16661 for (var i = 0; i < text.length; ++i) {
16662 ctx.fillText(text[i], 0, y, maxWidth);
16663 y += lineHeight;
16664 }
16665 } else {
16666 ctx.fillText(text, 0, 0, maxWidth);
16667 }
16668
16669 ctx.restore();
16670 }
16671 }
16672});
16673
16674function createNewTitleBlockAndAttach(chart, titleOpts) {
16675 var title = new Title({
16676 ctx: chart.ctx,
16677 options: titleOpts,
16678 chart: chart
16679 });
16680
16681 layouts.configure(chart, title, titleOpts);
16682 layouts.addBox(chart, title);
16683 chart.titleBlock = title;
16684}
16685
16686module.exports = {
16687 id: 'title',
16688
16689 /**
16690 * Backward compatibility: since 2.1.5, the title is registered as a plugin, making
16691 * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of
16692 * the plugin, which one will be re-exposed in the chart.js file.
16693 * https://github.com/chartjs/Chart.js/pull/2640
16694 * @private
16695 */
16696 _element: Title,
16697
16698 beforeInit: function(chart) {
16699 var titleOpts = chart.options.title;
16700
16701 if (titleOpts) {
16702 createNewTitleBlockAndAttach(chart, titleOpts);
16703 }
16704 },
16705
16706 beforeUpdate: function(chart) {
16707 var titleOpts = chart.options.title;
16708 var titleBlock = chart.titleBlock;
16709
16710 if (titleOpts) {
16711 helpers.mergeIf(titleOpts, defaults.global.title);
16712
16713 if (titleBlock) {
16714 layouts.configure(chart, titleBlock, titleOpts);
16715 titleBlock.options = titleOpts;
16716 } else {
16717 createNewTitleBlockAndAttach(chart, titleOpts);
16718 }
16719 } else if (titleBlock) {
16720 layouts.removeBox(chart, titleBlock);
16721 delete chart.titleBlock;
16722 }
16723 }
16724};
16725
16726},{"25":25,"26":26,"30":30,"45":45}],53:[function(require,module,exports){
16727'use strict';
16728
16729module.exports = function(Chart) {
16730
16731 // Default config for a category scale
16732 var defaultConfig = {
16733 position: 'bottom'
16734 };
16735
16736 var DatasetScale = Chart.Scale.extend({
16737 /**
16738 * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
16739 * else fall back to data.labels
16740 * @private
16741 */
16742 getLabels: function() {
16743 var data = this.chart.data;
16744 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
16745 },
16746
16747 determineDataLimits: function() {
16748 var me = this;
16749 var labels = me.getLabels();
16750 me.minIndex = 0;
16751 me.maxIndex = labels.length - 1;
16752 var findIndex;
16753
16754 if (me.options.ticks.min !== undefined) {
16755 // user specified min value
16756 findIndex = labels.indexOf(me.options.ticks.min);
16757 me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
16758 }
16759
16760 if (me.options.ticks.max !== undefined) {
16761 // user specified max value
16762 findIndex = labels.indexOf(me.options.ticks.max);
16763 me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
16764 }
16765
16766 me.min = labels[me.minIndex];
16767 me.max = labels[me.maxIndex];
16768 },
16769
16770 buildTicks: function() {
16771 var me = this;
16772 var labels = me.getLabels();
16773 // If we are viewing some subset of labels, slice the original array
16774 me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
16775 },
16776
16777 getLabelForIndex: function(index, datasetIndex) {
16778 var me = this;
16779 var data = me.chart.data;
16780 var isHorizontal = me.isHorizontal();
16781
16782 if (data.yLabels && !isHorizontal) {
16783 return me.getRightValue(data.datasets[datasetIndex].data[index]);
16784 }
16785 return me.ticks[index - me.minIndex];
16786 },
16787
16788 // Used to get data value locations. Value can either be an index or a numerical value
16789 getPixelForValue: function(value, index) {
16790 var me = this;
16791 var offset = me.options.offset;
16792 // 1 is added because we need the length but we have the indexes
16793 var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
16794
16795 // If value is a data object, then index is the index in the data array,
16796 // not the index of the scale. We need to change that.
16797 var valueCategory;
16798 if (value !== undefined && value !== null) {
16799 valueCategory = me.isHorizontal() ? value.x : value.y;
16800 }
16801 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
16802 var labels = me.getLabels();
16803 value = valueCategory || value;
16804 var idx = labels.indexOf(value);
16805 index = idx !== -1 ? idx : index;
16806 }
16807
16808 if (me.isHorizontal()) {
16809 var valueWidth = me.width / offsetAmt;
16810 var widthOffset = (valueWidth * (index - me.minIndex));
16811
16812 if (offset) {
16813 widthOffset += (valueWidth / 2);
16814 }
16815
16816 return me.left + Math.round(widthOffset);
16817 }
16818 var valueHeight = me.height / offsetAmt;
16819 var heightOffset = (valueHeight * (index - me.minIndex));
16820
16821 if (offset) {
16822 heightOffset += (valueHeight / 2);
16823 }
16824
16825 return me.top + Math.round(heightOffset);
16826 },
16827 getPixelForTick: function(index) {
16828 return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
16829 },
16830 getValueForPixel: function(pixel) {
16831 var me = this;
16832 var offset = me.options.offset;
16833 var value;
16834 var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
16835 var horz = me.isHorizontal();
16836 var valueDimension = (horz ? me.width : me.height) / offsetAmt;
16837
16838 pixel -= horz ? me.left : me.top;
16839
16840 if (offset) {
16841 pixel -= (valueDimension / 2);
16842 }
16843
16844 if (pixel <= 0) {
16845 value = 0;
16846 } else {
16847 value = Math.round(pixel / valueDimension);
16848 }
16849
16850 return value + me.minIndex;
16851 },
16852 getBasePixel: function() {
16853 return this.bottom;
16854 }
16855 });
16856
16857 Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
16858
16859};
16860
16861},{}],54:[function(require,module,exports){
16862'use strict';
16863
16864var defaults = require(25);
16865var helpers = require(45);
16866var Ticks = require(34);
16867
16868module.exports = function(Chart) {
16869
16870 var defaultConfig = {
16871 position: 'left',
16872 ticks: {
16873 callback: Ticks.formatters.linear
16874 }
16875 };
16876
16877 var LinearScale = Chart.LinearScaleBase.extend({
16878
16879 determineDataLimits: function() {
16880 var me = this;
16881 var opts = me.options;
16882 var chart = me.chart;
16883 var data = chart.data;
16884 var datasets = data.datasets;
16885 var isHorizontal = me.isHorizontal();
16886 var DEFAULT_MIN = 0;
16887 var DEFAULT_MAX = 1;
16888
16889 function IDMatches(meta) {
16890 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
16891 }
16892
16893 // First Calculate the range
16894 me.min = null;
16895 me.max = null;
16896
16897 var hasStacks = opts.stacked;
16898 if (hasStacks === undefined) {
16899 helpers.each(datasets, function(dataset, datasetIndex) {
16900 if (hasStacks) {
16901 return;
16902 }
16903
16904 var meta = chart.getDatasetMeta(datasetIndex);
16905 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
16906 meta.stack !== undefined) {
16907 hasStacks = true;
16908 }
16909 });
16910 }
16911
16912 if (opts.stacked || hasStacks) {
16913 var valuesPerStack = {};
16914
16915 helpers.each(datasets, function(dataset, datasetIndex) {
16916 var meta = chart.getDatasetMeta(datasetIndex);
16917 var key = [
16918 meta.type,
16919 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
16920 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
16921 meta.stack
16922 ].join('.');
16923
16924 if (valuesPerStack[key] === undefined) {
16925 valuesPerStack[key] = {
16926 positiveValues: [],
16927 negativeValues: []
16928 };
16929 }
16930
16931 // Store these per type
16932 var positiveValues = valuesPerStack[key].positiveValues;
16933 var negativeValues = valuesPerStack[key].negativeValues;
16934
16935 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
16936 helpers.each(dataset.data, function(rawValue, index) {
16937 var value = +me.getRightValue(rawValue);
16938 if (isNaN(value) || meta.data[index].hidden) {
16939 return;
16940 }
16941
16942 positiveValues[index] = positiveValues[index] || 0;
16943 negativeValues[index] = negativeValues[index] || 0;
16944
16945 if (opts.relativePoints) {
16946 positiveValues[index] = 100;
16947 } else if (value < 0) {
16948 negativeValues[index] += value;
16949 } else {
16950 positiveValues[index] += value;
16951 }
16952 });
16953 }
16954 });
16955
16956 helpers.each(valuesPerStack, function(valuesForType) {
16957 var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
16958 var minVal = helpers.min(values);
16959 var maxVal = helpers.max(values);
16960 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
16961 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
16962 });
16963
16964 } else {
16965 helpers.each(datasets, function(dataset, datasetIndex) {
16966 var meta = chart.getDatasetMeta(datasetIndex);
16967 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
16968 helpers.each(dataset.data, function(rawValue, index) {
16969 var value = +me.getRightValue(rawValue);
16970 if (isNaN(value) || meta.data[index].hidden) {
16971 return;
16972 }
16973
16974 if (me.min === null) {
16975 me.min = value;
16976 } else if (value < me.min) {
16977 me.min = value;
16978 }
16979
16980 if (me.max === null) {
16981 me.max = value;
16982 } else if (value > me.max) {
16983 me.max = value;
16984 }
16985 });
16986 }
16987 });
16988 }
16989
16990 me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
16991 me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
16992
16993 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
16994 this.handleTickRangeOptions();
16995 },
16996 getTickLimit: function() {
16997 var maxTicks;
16998 var me = this;
16999 var tickOpts = me.options.ticks;
17000
17001 if (me.isHorizontal()) {
17002 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
17003 } else {
17004 // The factor of 2 used to scale the font size has been experimentally determined.
17005 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize);
17006 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
17007 }
17008
17009 return maxTicks;
17010 },
17011 // Called after the ticks are built. We need
17012 handleDirectionalChanges: function() {
17013 if (!this.isHorizontal()) {
17014 // We are in a vertical orientation. The top value is the highest. So reverse the array
17015 this.ticks.reverse();
17016 }
17017 },
17018 getLabelForIndex: function(index, datasetIndex) {
17019 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
17020 },
17021 // Utils
17022 getPixelForValue: function(value) {
17023 // This must be called after fit has been run so that
17024 // this.left, this.top, this.right, and this.bottom have been defined
17025 var me = this;
17026 var start = me.start;
17027
17028 var rightValue = +me.getRightValue(value);
17029 var pixel;
17030 var range = me.end - start;
17031
17032 if (me.isHorizontal()) {
17033 pixel = me.left + (me.width / range * (rightValue - start));
17034 } else {
17035 pixel = me.bottom - (me.height / range * (rightValue - start));
17036 }
17037 return pixel;
17038 },
17039 getValueForPixel: function(pixel) {
17040 var me = this;
17041 var isHorizontal = me.isHorizontal();
17042 var innerDimension = isHorizontal ? me.width : me.height;
17043 var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
17044 return me.start + ((me.end - me.start) * offset);
17045 },
17046 getPixelForTick: function(index) {
17047 return this.getPixelForValue(this.ticksAsNumbers[index]);
17048 }
17049 });
17050 Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
17051
17052};
17053
17054},{"25":25,"34":34,"45":45}],55:[function(require,module,exports){
17055'use strict';
17056
17057var helpers = require(45);
17058
17059/**
17060 * Generate a set of linear ticks
17061 * @param generationOptions the options used to generate the ticks
17062 * @param dataRange the range of the data
17063 * @returns {Array<Number>} array of tick values
17064 */
17065function generateTicks(generationOptions, dataRange) {
17066 var ticks = [];
17067 // To get a "nice" value for the tick spacing, we will use the appropriately named
17068 // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
17069 // for details.
17070
17071 var spacing;
17072 if (generationOptions.stepSize && generationOptions.stepSize > 0) {
17073 spacing = generationOptions.stepSize;
17074 } else {
17075 var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
17076 spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
17077 }
17078 var niceMin = Math.floor(dataRange.min / spacing) * spacing;
17079 var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
17080
17081 // If min, max and stepSize is set and they make an evenly spaced scale use it.
17082 if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
17083 // If very close to our whole number, use it.
17084 if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
17085 niceMin = generationOptions.min;
17086 niceMax = generationOptions.max;
17087 }
17088 }
17089
17090 var numSpaces = (niceMax - niceMin) / spacing;
17091 // If very close to our rounded value, use it.
17092 if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
17093 numSpaces = Math.round(numSpaces);
17094 } else {
17095 numSpaces = Math.ceil(numSpaces);
17096 }
17097
17098 var precision = 1;
17099 if (spacing < 1) {
17100 precision = Math.pow(10, spacing.toString().length - 2);
17101 niceMin = Math.round(niceMin * precision) / precision;
17102 niceMax = Math.round(niceMax * precision) / precision;
17103 }
17104 ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
17105 for (var j = 1; j < numSpaces; ++j) {
17106 ticks.push(Math.round((niceMin + j * spacing) * precision) / precision);
17107 }
17108 ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
17109
17110 return ticks;
17111}
17112
17113
17114module.exports = function(Chart) {
17115
17116 var noop = helpers.noop;
17117
17118 Chart.LinearScaleBase = Chart.Scale.extend({
17119 getRightValue: function(value) {
17120 if (typeof value === 'string') {
17121 return +value;
17122 }
17123 return Chart.Scale.prototype.getRightValue.call(this, value);
17124 },
17125
17126 handleTickRangeOptions: function() {
17127 var me = this;
17128 var opts = me.options;
17129 var tickOpts = opts.ticks;
17130
17131 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
17132 // do nothing since that would make the chart weird. If the user really wants a weird chart
17133 // axis, they can manually override it
17134 if (tickOpts.beginAtZero) {
17135 var minSign = helpers.sign(me.min);
17136 var maxSign = helpers.sign(me.max);
17137
17138 if (minSign < 0 && maxSign < 0) {
17139 // move the top up to 0
17140 me.max = 0;
17141 } else if (minSign > 0 && maxSign > 0) {
17142 // move the bottom down to 0
17143 me.min = 0;
17144 }
17145 }
17146
17147 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
17148 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
17149
17150 if (tickOpts.min !== undefined) {
17151 me.min = tickOpts.min;
17152 } else if (tickOpts.suggestedMin !== undefined) {
17153 if (me.min === null) {
17154 me.min = tickOpts.suggestedMin;
17155 } else {
17156 me.min = Math.min(me.min, tickOpts.suggestedMin);
17157 }
17158 }
17159
17160 if (tickOpts.max !== undefined) {
17161 me.max = tickOpts.max;
17162 } else if (tickOpts.suggestedMax !== undefined) {
17163 if (me.max === null) {
17164 me.max = tickOpts.suggestedMax;
17165 } else {
17166 me.max = Math.max(me.max, tickOpts.suggestedMax);
17167 }
17168 }
17169
17170 if (setMin !== setMax) {
17171 // We set the min or the max but not both.
17172 // So ensure that our range is good
17173 // Inverted or 0 length range can happen when
17174 // ticks.min is set, and no datasets are visible
17175 if (me.min >= me.max) {
17176 if (setMin) {
17177 me.max = me.min + 1;
17178 } else {
17179 me.min = me.max - 1;
17180 }
17181 }
17182 }
17183
17184 if (me.min === me.max) {
17185 me.max++;
17186
17187 if (!tickOpts.beginAtZero) {
17188 me.min--;
17189 }
17190 }
17191 },
17192 getTickLimit: noop,
17193 handleDirectionalChanges: noop,
17194
17195 buildTicks: function() {
17196 var me = this;
17197 var opts = me.options;
17198 var tickOpts = opts.ticks;
17199
17200 // Figure out what the max number of ticks we can support it is based on the size of
17201 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
17202 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
17203 // the graph. Make sure we always have at least 2 ticks
17204 var maxTicks = me.getTickLimit();
17205 maxTicks = Math.max(2, maxTicks);
17206
17207 var numericGeneratorOptions = {
17208 maxTicks: maxTicks,
17209 min: tickOpts.min,
17210 max: tickOpts.max,
17211 stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
17212 };
17213 var ticks = me.ticks = generateTicks(numericGeneratorOptions, me);
17214
17215 me.handleDirectionalChanges();
17216
17217 // At this point, we need to update our max and min given the tick values since we have expanded the
17218 // range of the scale
17219 me.max = helpers.max(ticks);
17220 me.min = helpers.min(ticks);
17221
17222 if (tickOpts.reverse) {
17223 ticks.reverse();
17224
17225 me.start = me.max;
17226 me.end = me.min;
17227 } else {
17228 me.start = me.min;
17229 me.end = me.max;
17230 }
17231 },
17232 convertTicksToLabels: function() {
17233 var me = this;
17234 me.ticksAsNumbers = me.ticks.slice();
17235 me.zeroLineIndex = me.ticks.indexOf(0);
17236
17237 Chart.Scale.prototype.convertTicksToLabels.call(me);
17238 }
17239 });
17240};
17241
17242},{"45":45}],56:[function(require,module,exports){
17243'use strict';
17244
17245var helpers = require(45);
17246var Ticks = require(34);
17247
17248/**
17249 * Generate a set of logarithmic ticks
17250 * @param generationOptions the options used to generate the ticks
17251 * @param dataRange the range of the data
17252 * @returns {Array<Number>} array of tick values
17253 */
17254function generateTicks(generationOptions, dataRange) {
17255 var ticks = [];
17256 var valueOrDefault = helpers.valueOrDefault;
17257
17258 // Figure out what the max number of ticks we can support it is based on the size of
17259 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
17260 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
17261 // the graph
17262 var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
17263
17264 var endExp = Math.floor(helpers.log10(dataRange.max));
17265 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
17266 var exp, significand;
17267
17268 if (tickVal === 0) {
17269 exp = Math.floor(helpers.log10(dataRange.minNotZero));
17270 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
17271
17272 ticks.push(tickVal);
17273 tickVal = significand * Math.pow(10, exp);
17274 } else {
17275 exp = Math.floor(helpers.log10(tickVal));
17276 significand = Math.floor(tickVal / Math.pow(10, exp));
17277 }
17278 var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
17279
17280 do {
17281 ticks.push(tickVal);
17282
17283 ++significand;
17284 if (significand === 10) {
17285 significand = 1;
17286 ++exp;
17287 precision = exp >= 0 ? 1 : precision;
17288 }
17289
17290 tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
17291 } while (exp < endExp || (exp === endExp && significand < endSignificand));
17292
17293 var lastTick = valueOrDefault(generationOptions.max, tickVal);
17294 ticks.push(lastTick);
17295
17296 return ticks;
17297}
17298
17299
17300module.exports = function(Chart) {
17301
17302 var defaultConfig = {
17303 position: 'left',
17304
17305 // label settings
17306 ticks: {
17307 callback: Ticks.formatters.logarithmic
17308 }
17309 };
17310
17311 var LogarithmicScale = Chart.Scale.extend({
17312 determineDataLimits: function() {
17313 var me = this;
17314 var opts = me.options;
17315 var chart = me.chart;
17316 var data = chart.data;
17317 var datasets = data.datasets;
17318 var isHorizontal = me.isHorizontal();
17319 function IDMatches(meta) {
17320 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
17321 }
17322
17323 // Calculate Range
17324 me.min = null;
17325 me.max = null;
17326 me.minNotZero = null;
17327
17328 var hasStacks = opts.stacked;
17329 if (hasStacks === undefined) {
17330 helpers.each(datasets, function(dataset, datasetIndex) {
17331 if (hasStacks) {
17332 return;
17333 }
17334
17335 var meta = chart.getDatasetMeta(datasetIndex);
17336 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
17337 meta.stack !== undefined) {
17338 hasStacks = true;
17339 }
17340 });
17341 }
17342
17343 if (opts.stacked || hasStacks) {
17344 var valuesPerStack = {};
17345
17346 helpers.each(datasets, function(dataset, datasetIndex) {
17347 var meta = chart.getDatasetMeta(datasetIndex);
17348 var key = [
17349 meta.type,
17350 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
17351 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
17352 meta.stack
17353 ].join('.');
17354
17355 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
17356 if (valuesPerStack[key] === undefined) {
17357 valuesPerStack[key] = [];
17358 }
17359
17360 helpers.each(dataset.data, function(rawValue, index) {
17361 var values = valuesPerStack[key];
17362 var value = +me.getRightValue(rawValue);
17363 // invalid, hidden and negative values are ignored
17364 if (isNaN(value) || meta.data[index].hidden || value < 0) {
17365 return;
17366 }
17367 values[index] = values[index] || 0;
17368 values[index] += value;
17369 });
17370 }
17371 });
17372
17373 helpers.each(valuesPerStack, function(valuesForType) {
17374 if (valuesForType.length > 0) {
17375 var minVal = helpers.min(valuesForType);
17376 var maxVal = helpers.max(valuesForType);
17377 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
17378 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
17379 }
17380 });
17381
17382 } else {
17383 helpers.each(datasets, function(dataset, datasetIndex) {
17384 var meta = chart.getDatasetMeta(datasetIndex);
17385 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
17386 helpers.each(dataset.data, function(rawValue, index) {
17387 var value = +me.getRightValue(rawValue);
17388 // invalid, hidden and negative values are ignored
17389 if (isNaN(value) || meta.data[index].hidden || value < 0) {
17390 return;
17391 }
17392
17393 if (me.min === null) {
17394 me.min = value;
17395 } else if (value < me.min) {
17396 me.min = value;
17397 }
17398
17399 if (me.max === null) {
17400 me.max = value;
17401 } else if (value > me.max) {
17402 me.max = value;
17403 }
17404
17405 if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
17406 me.minNotZero = value;
17407 }
17408 });
17409 }
17410 });
17411 }
17412
17413 // Common base implementation to handle ticks.min, ticks.max
17414 this.handleTickRangeOptions();
17415 },
17416 handleTickRangeOptions: function() {
17417 var me = this;
17418 var opts = me.options;
17419 var tickOpts = opts.ticks;
17420 var valueOrDefault = helpers.valueOrDefault;
17421 var DEFAULT_MIN = 1;
17422 var DEFAULT_MAX = 10;
17423
17424 me.min = valueOrDefault(tickOpts.min, me.min);
17425 me.max = valueOrDefault(tickOpts.max, me.max);
17426
17427 if (me.min === me.max) {
17428 if (me.min !== 0 && me.min !== null) {
17429 me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
17430 me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
17431 } else {
17432 me.min = DEFAULT_MIN;
17433 me.max = DEFAULT_MAX;
17434 }
17435 }
17436 if (me.min === null) {
17437 me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1);
17438 }
17439 if (me.max === null) {
17440 me.max = me.min !== 0
17441 ? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1)
17442 : DEFAULT_MAX;
17443 }
17444 if (me.minNotZero === null) {
17445 if (me.min > 0) {
17446 me.minNotZero = me.min;
17447 } else if (me.max < 1) {
17448 me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max)));
17449 } else {
17450 me.minNotZero = DEFAULT_MIN;
17451 }
17452 }
17453 },
17454 buildTicks: function() {
17455 var me = this;
17456 var opts = me.options;
17457 var tickOpts = opts.ticks;
17458 var reverse = !me.isHorizontal();
17459
17460 var generationOptions = {
17461 min: tickOpts.min,
17462 max: tickOpts.max
17463 };
17464 var ticks = me.ticks = generateTicks(generationOptions, me);
17465
17466 // At this point, we need to update our max and min given the tick values since we have expanded the
17467 // range of the scale
17468 me.max = helpers.max(ticks);
17469 me.min = helpers.min(ticks);
17470
17471 if (tickOpts.reverse) {
17472 reverse = !reverse;
17473 me.start = me.max;
17474 me.end = me.min;
17475 } else {
17476 me.start = me.min;
17477 me.end = me.max;
17478 }
17479 if (reverse) {
17480 ticks.reverse();
17481 }
17482 },
17483 convertTicksToLabels: function() {
17484 this.tickValues = this.ticks.slice();
17485
17486 Chart.Scale.prototype.convertTicksToLabels.call(this);
17487 },
17488 // Get the correct tooltip label
17489 getLabelForIndex: function(index, datasetIndex) {
17490 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
17491 },
17492 getPixelForTick: function(index) {
17493 return this.getPixelForValue(this.tickValues[index]);
17494 },
17495 /**
17496 * Returns the value of the first tick.
17497 * @param {Number} value - The minimum not zero value.
17498 * @return {Number} The first tick value.
17499 * @private
17500 */
17501 _getFirstTickValue: function(value) {
17502 var exp = Math.floor(helpers.log10(value));
17503 var significand = Math.floor(value / Math.pow(10, exp));
17504
17505 return significand * Math.pow(10, exp);
17506 },
17507 getPixelForValue: function(value) {
17508 var me = this;
17509 var reverse = me.options.ticks.reverse;
17510 var log10 = helpers.log10;
17511 var firstTickValue = me._getFirstTickValue(me.minNotZero);
17512 var offset = 0;
17513 var innerDimension, pixel, start, end, sign;
17514
17515 value = +me.getRightValue(value);
17516 if (reverse) {
17517 start = me.end;
17518 end = me.start;
17519 sign = -1;
17520 } else {
17521 start = me.start;
17522 end = me.end;
17523 sign = 1;
17524 }
17525 if (me.isHorizontal()) {
17526 innerDimension = me.width;
17527 pixel = reverse ? me.right : me.left;
17528 } else {
17529 innerDimension = me.height;
17530 sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0)
17531 pixel = reverse ? me.top : me.bottom;
17532 }
17533 if (value !== start) {
17534 if (start === 0) { // include zero tick
17535 offset = helpers.getValueOrDefault(
17536 me.options.ticks.fontSize,
17537 Chart.defaults.global.defaultFontSize
17538 );
17539 innerDimension -= offset;
17540 start = firstTickValue;
17541 }
17542 if (value !== 0) {
17543 offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start));
17544 }
17545 pixel += sign * offset;
17546 }
17547 return pixel;
17548 },
17549 getValueForPixel: function(pixel) {
17550 var me = this;
17551 var reverse = me.options.ticks.reverse;
17552 var log10 = helpers.log10;
17553 var firstTickValue = me._getFirstTickValue(me.minNotZero);
17554 var innerDimension, start, end, value;
17555
17556 if (reverse) {
17557 start = me.end;
17558 end = me.start;
17559 } else {
17560 start = me.start;
17561 end = me.end;
17562 }
17563 if (me.isHorizontal()) {
17564 innerDimension = me.width;
17565 value = reverse ? me.right - pixel : pixel - me.left;
17566 } else {
17567 innerDimension = me.height;
17568 value = reverse ? pixel - me.top : me.bottom - pixel;
17569 }
17570 if (value !== start) {
17571 if (start === 0) { // include zero tick
17572 var offset = helpers.getValueOrDefault(
17573 me.options.ticks.fontSize,
17574 Chart.defaults.global.defaultFontSize
17575 );
17576 value -= offset;
17577 innerDimension -= offset;
17578 start = firstTickValue;
17579 }
17580 value *= log10(end) - log10(start);
17581 value /= innerDimension;
17582 value = Math.pow(10, log10(start) + value);
17583 }
17584 return value;
17585 }
17586 });
17587 Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
17588
17589};
17590
17591},{"34":34,"45":45}],57:[function(require,module,exports){
17592'use strict';
17593
17594var defaults = require(25);
17595var helpers = require(45);
17596var Ticks = require(34);
17597
17598module.exports = function(Chart) {
17599
17600 var globalDefaults = defaults.global;
17601
17602 var defaultConfig = {
17603 display: true,
17604
17605 // Boolean - Whether to animate scaling the chart from the centre
17606 animate: true,
17607 position: 'chartArea',
17608
17609 angleLines: {
17610 display: true,
17611 color: 'rgba(0, 0, 0, 0.1)',
17612 lineWidth: 1
17613 },
17614
17615 gridLines: {
17616 circular: false
17617 },
17618
17619 // label settings
17620 ticks: {
17621 // Boolean - Show a backdrop to the scale label
17622 showLabelBackdrop: true,
17623
17624 // String - The colour of the label backdrop
17625 backdropColor: 'rgba(255,255,255,0.75)',
17626
17627 // Number - The backdrop padding above & below the label in pixels
17628 backdropPaddingY: 2,
17629
17630 // Number - The backdrop padding to the side of the label in pixels
17631 backdropPaddingX: 2,
17632
17633 callback: Ticks.formatters.linear
17634 },
17635
17636 pointLabels: {
17637 // Boolean - if true, show point labels
17638 display: true,
17639
17640 // Number - Point label font size in pixels
17641 fontSize: 10,
17642
17643 // Function - Used to convert point labels
17644 callback: function(label) {
17645 return label;
17646 }
17647 }
17648 };
17649
17650 function getValueCount(scale) {
17651 var opts = scale.options;
17652 return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
17653 }
17654
17655 function getPointLabelFontOptions(scale) {
17656 var pointLabelOptions = scale.options.pointLabels;
17657 var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
17658 var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
17659 var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
17660 var font = helpers.fontString(fontSize, fontStyle, fontFamily);
17661
17662 return {
17663 size: fontSize,
17664 style: fontStyle,
17665 family: fontFamily,
17666 font: font
17667 };
17668 }
17669
17670 function measureLabelSize(ctx, fontSize, label) {
17671 if (helpers.isArray(label)) {
17672 return {
17673 w: helpers.longestText(ctx, ctx.font, label),
17674 h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
17675 };
17676 }
17677
17678 return {
17679 w: ctx.measureText(label).width,
17680 h: fontSize
17681 };
17682 }
17683
17684 function determineLimits(angle, pos, size, min, max) {
17685 if (angle === min || angle === max) {
17686 return {
17687 start: pos - (size / 2),
17688 end: pos + (size / 2)
17689 };
17690 } else if (angle < min || angle > max) {
17691 return {
17692 start: pos - size - 5,
17693 end: pos
17694 };
17695 }
17696
17697 return {
17698 start: pos,
17699 end: pos + size + 5
17700 };
17701 }
17702
17703 /**
17704 * Helper function to fit a radial linear scale with point labels
17705 */
17706 function fitWithPointLabels(scale) {
17707 /*
17708 * Right, this is really confusing and there is a lot of maths going on here
17709 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
17710 *
17711 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
17712 *
17713 * Solution:
17714 *
17715 * We assume the radius of the polygon is half the size of the canvas at first
17716 * at each index we check if the text overlaps.
17717 *
17718 * Where it does, we store that angle and that index.
17719 *
17720 * After finding the largest index and angle we calculate how much we need to remove
17721 * from the shape radius to move the point inwards by that x.
17722 *
17723 * We average the left and right distances to get the maximum shape radius that can fit in the box
17724 * along with labels.
17725 *
17726 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
17727 * on each side, removing that from the size, halving it and adding the left x protrusion width.
17728 *
17729 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
17730 * and position it in the most space efficient manner
17731 *
17732 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
17733 */
17734
17735 var plFont = getPointLabelFontOptions(scale);
17736
17737 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
17738 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
17739 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
17740 var furthestLimits = {
17741 r: scale.width,
17742 l: 0,
17743 t: scale.height,
17744 b: 0
17745 };
17746 var furthestAngles = {};
17747 var i, textSize, pointPosition;
17748
17749 scale.ctx.font = plFont.font;
17750 scale._pointLabelSizes = [];
17751
17752 var valueCount = getValueCount(scale);
17753 for (i = 0; i < valueCount; i++) {
17754 pointPosition = scale.getPointPosition(i, largestPossibleRadius);
17755 textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
17756 scale._pointLabelSizes[i] = textSize;
17757
17758 // Add quarter circle to make degree 0 mean top of circle
17759 var angleRadians = scale.getIndexAngle(i);
17760 var angle = helpers.toDegrees(angleRadians) % 360;
17761 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
17762 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
17763
17764 if (hLimits.start < furthestLimits.l) {
17765 furthestLimits.l = hLimits.start;
17766 furthestAngles.l = angleRadians;
17767 }
17768
17769 if (hLimits.end > furthestLimits.r) {
17770 furthestLimits.r = hLimits.end;
17771 furthestAngles.r = angleRadians;
17772 }
17773
17774 if (vLimits.start < furthestLimits.t) {
17775 furthestLimits.t = vLimits.start;
17776 furthestAngles.t = angleRadians;
17777 }
17778
17779 if (vLimits.end > furthestLimits.b) {
17780 furthestLimits.b = vLimits.end;
17781 furthestAngles.b = angleRadians;
17782 }
17783 }
17784
17785 scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
17786 }
17787
17788 /**
17789 * Helper function to fit a radial linear scale with no point labels
17790 */
17791 function fit(scale) {
17792 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
17793 scale.drawingArea = Math.round(largestPossibleRadius);
17794 scale.setCenterPoint(0, 0, 0, 0);
17795 }
17796
17797 function getTextAlignForAngle(angle) {
17798 if (angle === 0 || angle === 180) {
17799 return 'center';
17800 } else if (angle < 180) {
17801 return 'left';
17802 }
17803
17804 return 'right';
17805 }
17806
17807 function fillText(ctx, text, position, fontSize) {
17808 if (helpers.isArray(text)) {
17809 var y = position.y;
17810 var spacing = 1.5 * fontSize;
17811
17812 for (var i = 0; i < text.length; ++i) {
17813 ctx.fillText(text[i], position.x, y);
17814 y += spacing;
17815 }
17816 } else {
17817 ctx.fillText(text, position.x, position.y);
17818 }
17819 }
17820
17821 function adjustPointPositionForLabelHeight(angle, textSize, position) {
17822 if (angle === 90 || angle === 270) {
17823 position.y -= (textSize.h / 2);
17824 } else if (angle > 270 || angle < 90) {
17825 position.y -= textSize.h;
17826 }
17827 }
17828
17829 function drawPointLabels(scale) {
17830 var ctx = scale.ctx;
17831 var valueOrDefault = helpers.valueOrDefault;
17832 var opts = scale.options;
17833 var angleLineOpts = opts.angleLines;
17834 var pointLabelOpts = opts.pointLabels;
17835
17836 ctx.lineWidth = angleLineOpts.lineWidth;
17837 ctx.strokeStyle = angleLineOpts.color;
17838
17839 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
17840
17841 // Point Label Font
17842 var plFont = getPointLabelFontOptions(scale);
17843
17844 ctx.textBaseline = 'top';
17845
17846 for (var i = getValueCount(scale) - 1; i >= 0; i--) {
17847 if (angleLineOpts.display) {
17848 var outerPosition = scale.getPointPosition(i, outerDistance);
17849 ctx.beginPath();
17850 ctx.moveTo(scale.xCenter, scale.yCenter);
17851 ctx.lineTo(outerPosition.x, outerPosition.y);
17852 ctx.stroke();
17853 ctx.closePath();
17854 }
17855
17856 if (pointLabelOpts.display) {
17857 // Extra 3px out for some label spacing
17858 var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
17859
17860 // Keep this in loop since we may support array properties here
17861 var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
17862 ctx.font = plFont.font;
17863 ctx.fillStyle = pointLabelFontColor;
17864
17865 var angleRadians = scale.getIndexAngle(i);
17866 var angle = helpers.toDegrees(angleRadians);
17867 ctx.textAlign = getTextAlignForAngle(angle);
17868 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
17869 fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
17870 }
17871 }
17872 }
17873
17874 function drawRadiusLine(scale, gridLineOpts, radius, index) {
17875 var ctx = scale.ctx;
17876 ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1);
17877 ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
17878
17879 if (scale.options.gridLines.circular) {
17880 // Draw circular arcs between the points
17881 ctx.beginPath();
17882 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
17883 ctx.closePath();
17884 ctx.stroke();
17885 } else {
17886 // Draw straight lines connecting each index
17887 var valueCount = getValueCount(scale);
17888
17889 if (valueCount === 0) {
17890 return;
17891 }
17892
17893 ctx.beginPath();
17894 var pointPosition = scale.getPointPosition(0, radius);
17895 ctx.moveTo(pointPosition.x, pointPosition.y);
17896
17897 for (var i = 1; i < valueCount; i++) {
17898 pointPosition = scale.getPointPosition(i, radius);
17899 ctx.lineTo(pointPosition.x, pointPosition.y);
17900 }
17901
17902 ctx.closePath();
17903 ctx.stroke();
17904 }
17905 }
17906
17907 function numberOrZero(param) {
17908 return helpers.isNumber(param) ? param : 0;
17909 }
17910
17911 var LinearRadialScale = Chart.LinearScaleBase.extend({
17912 setDimensions: function() {
17913 var me = this;
17914 var opts = me.options;
17915 var tickOpts = opts.ticks;
17916 // Set the unconstrained dimension before label rotation
17917 me.width = me.maxWidth;
17918 me.height = me.maxHeight;
17919 me.xCenter = Math.round(me.width / 2);
17920 me.yCenter = Math.round(me.height / 2);
17921
17922 var minSize = helpers.min([me.height, me.width]);
17923 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
17924 me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
17925 },
17926 determineDataLimits: function() {
17927 var me = this;
17928 var chart = me.chart;
17929 var min = Number.POSITIVE_INFINITY;
17930 var max = Number.NEGATIVE_INFINITY;
17931
17932 helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
17933 if (chart.isDatasetVisible(datasetIndex)) {
17934 var meta = chart.getDatasetMeta(datasetIndex);
17935
17936 helpers.each(dataset.data, function(rawValue, index) {
17937 var value = +me.getRightValue(rawValue);
17938 if (isNaN(value) || meta.data[index].hidden) {
17939 return;
17940 }
17941
17942 min = Math.min(value, min);
17943 max = Math.max(value, max);
17944 });
17945 }
17946 });
17947
17948 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
17949 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
17950
17951 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
17952 me.handleTickRangeOptions();
17953 },
17954 getTickLimit: function() {
17955 var tickOpts = this.options.ticks;
17956 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
17957 return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
17958 },
17959 convertTicksToLabels: function() {
17960 var me = this;
17961
17962 Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
17963
17964 // Point labels
17965 me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
17966 },
17967 getLabelForIndex: function(index, datasetIndex) {
17968 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
17969 },
17970 fit: function() {
17971 if (this.options.pointLabels.display) {
17972 fitWithPointLabels(this);
17973 } else {
17974 fit(this);
17975 }
17976 },
17977 /**
17978 * Set radius reductions and determine new radius and center point
17979 * @private
17980 */
17981 setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
17982 var me = this;
17983 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
17984 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
17985 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
17986 var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
17987
17988 radiusReductionLeft = numberOrZero(radiusReductionLeft);
17989 radiusReductionRight = numberOrZero(radiusReductionRight);
17990 radiusReductionTop = numberOrZero(radiusReductionTop);
17991 radiusReductionBottom = numberOrZero(radiusReductionBottom);
17992
17993 me.drawingArea = Math.min(
17994 Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
17995 Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
17996 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
17997 },
17998 setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
17999 var me = this;
18000 var maxRight = me.width - rightMovement - me.drawingArea;
18001 var maxLeft = leftMovement + me.drawingArea;
18002 var maxTop = topMovement + me.drawingArea;
18003 var maxBottom = me.height - bottomMovement - me.drawingArea;
18004
18005 me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
18006 me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
18007 },
18008
18009 getIndexAngle: function(index) {
18010 var angleMultiplier = (Math.PI * 2) / getValueCount(this);
18011 var startAngle = this.chart.options && this.chart.options.startAngle ?
18012 this.chart.options.startAngle :
18013 0;
18014
18015 var startAngleRadians = startAngle * Math.PI * 2 / 360;
18016
18017 // Start from the top instead of right, so remove a quarter of the circle
18018 return index * angleMultiplier + startAngleRadians;
18019 },
18020 getDistanceFromCenterForValue: function(value) {
18021 var me = this;
18022
18023 if (value === null) {
18024 return 0; // null always in center
18025 }
18026
18027 // Take into account half font size + the yPadding of the top value
18028 var scalingFactor = me.drawingArea / (me.max - me.min);
18029 if (me.options.ticks.reverse) {
18030 return (me.max - value) * scalingFactor;
18031 }
18032 return (value - me.min) * scalingFactor;
18033 },
18034 getPointPosition: function(index, distanceFromCenter) {
18035 var me = this;
18036 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
18037 return {
18038 x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
18039 y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
18040 };
18041 },
18042 getPointPositionForValue: function(index, value) {
18043 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
18044 },
18045
18046 getBasePosition: function() {
18047 var me = this;
18048 var min = me.min;
18049 var max = me.max;
18050
18051 return me.getPointPositionForValue(0,
18052 me.beginAtZero ? 0 :
18053 min < 0 && max < 0 ? max :
18054 min > 0 && max > 0 ? min :
18055 0);
18056 },
18057
18058 draw: function() {
18059 var me = this;
18060 var opts = me.options;
18061 var gridLineOpts = opts.gridLines;
18062 var tickOpts = opts.ticks;
18063 var valueOrDefault = helpers.valueOrDefault;
18064
18065 if (opts.display) {
18066 var ctx = me.ctx;
18067 var startAngle = this.getIndexAngle(0);
18068
18069 // Tick Font
18070 var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
18071 var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
18072 var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
18073 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
18074
18075 helpers.each(me.ticks, function(label, index) {
18076 // Don't draw a centre value (if it is minimum)
18077 if (index > 0 || tickOpts.reverse) {
18078 var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
18079
18080 // Draw circular lines around the scale
18081 if (gridLineOpts.display && index !== 0) {
18082 drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
18083 }
18084
18085 if (tickOpts.display) {
18086 var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
18087 ctx.font = tickLabelFont;
18088
18089 ctx.save();
18090 ctx.translate(me.xCenter, me.yCenter);
18091 ctx.rotate(startAngle);
18092
18093 if (tickOpts.showLabelBackdrop) {
18094 var labelWidth = ctx.measureText(label).width;
18095 ctx.fillStyle = tickOpts.backdropColor;
18096 ctx.fillRect(
18097 -labelWidth / 2 - tickOpts.backdropPaddingX,
18098 -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY,
18099 labelWidth + tickOpts.backdropPaddingX * 2,
18100 tickFontSize + tickOpts.backdropPaddingY * 2
18101 );
18102 }
18103
18104 ctx.textAlign = 'center';
18105 ctx.textBaseline = 'middle';
18106 ctx.fillStyle = tickFontColor;
18107 ctx.fillText(label, 0, -yCenterOffset);
18108 ctx.restore();
18109 }
18110 }
18111 });
18112
18113 if (opts.angleLines.display || opts.pointLabels.display) {
18114 drawPointLabels(me);
18115 }
18116 }
18117 }
18118 });
18119 Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
18120
18121};
18122
18123},{"25":25,"34":34,"45":45}],58:[function(require,module,exports){
18124/* global window: false */
18125'use strict';
18126
18127var moment = require(6);
18128moment = typeof moment === 'function' ? moment : window.moment;
18129
18130var defaults = require(25);
18131var helpers = require(45);
18132
18133// Integer constants are from the ES6 spec.
18134var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
18135var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
18136
18137var INTERVALS = {
18138 millisecond: {
18139 common: true,
18140 size: 1,
18141 steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
18142 },
18143 second: {
18144 common: true,
18145 size: 1000,
18146 steps: [1, 2, 5, 10, 30]
18147 },
18148 minute: {
18149 common: true,
18150 size: 60000,
18151 steps: [1, 2, 5, 10, 30]
18152 },
18153 hour: {
18154 common: true,
18155 size: 3600000,
18156 steps: [1, 2, 3, 6, 12]
18157 },
18158 day: {
18159 common: true,
18160 size: 86400000,
18161 steps: [1, 2, 5]
18162 },
18163 week: {
18164 common: false,
18165 size: 604800000,
18166 steps: [1, 2, 3, 4]
18167 },
18168 month: {
18169 common: true,
18170 size: 2.628e9,
18171 steps: [1, 2, 3]
18172 },
18173 quarter: {
18174 common: false,
18175 size: 7.884e9,
18176 steps: [1, 2, 3, 4]
18177 },
18178 year: {
18179 common: true,
18180 size: 3.154e10
18181 }
18182};
18183
18184var UNITS = Object.keys(INTERVALS);
18185
18186function sorter(a, b) {
18187 return a - b;
18188}
18189
18190function arrayUnique(items) {
18191 var hash = {};
18192 var out = [];
18193 var i, ilen, item;
18194
18195 for (i = 0, ilen = items.length; i < ilen; ++i) {
18196 item = items[i];
18197 if (!hash[item]) {
18198 hash[item] = true;
18199 out.push(item);
18200 }
18201 }
18202
18203 return out;
18204}
18205
18206/**
18207 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
18208 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
18209 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
18210 * extremity (left + width or top + height). Note that it would be more optimized to directly
18211 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
18212 * to create the lookup table. The table ALWAYS contains at least two items: min and max.
18213 *
18214 * @param {Number[]} timestamps - timestamps sorted from lowest to highest.
18215 * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
18216 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
18217 * If 'series', timestamps will be positioned at the same distance from each other. In this
18218 * case, only timestamps that break the time linearity are registered, meaning that in the
18219 * best case, all timestamps are linear, the table contains only min and max.
18220 */
18221function buildLookupTable(timestamps, min, max, distribution) {
18222 if (distribution === 'linear' || !timestamps.length) {
18223 return [
18224 {time: min, pos: 0},
18225 {time: max, pos: 1}
18226 ];
18227 }
18228
18229 var table = [];
18230 var items = [min];
18231 var i, ilen, prev, curr, next;
18232
18233 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
18234 curr = timestamps[i];
18235 if (curr > min && curr < max) {
18236 items.push(curr);
18237 }
18238 }
18239
18240 items.push(max);
18241
18242 for (i = 0, ilen = items.length; i < ilen; ++i) {
18243 next = items[i + 1];
18244 prev = items[i - 1];
18245 curr = items[i];
18246
18247 // only add points that breaks the scale linearity
18248 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
18249 table.push({time: curr, pos: i / (ilen - 1)});
18250 }
18251 }
18252
18253 return table;
18254}
18255
18256// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
18257function lookup(table, key, value) {
18258 var lo = 0;
18259 var hi = table.length - 1;
18260 var mid, i0, i1;
18261
18262 while (lo >= 0 && lo <= hi) {
18263 mid = (lo + hi) >> 1;
18264 i0 = table[mid - 1] || null;
18265 i1 = table[mid];
18266
18267 if (!i0) {
18268 // given value is outside table (before first item)
18269 return {lo: null, hi: i1};
18270 } else if (i1[key] < value) {
18271 lo = mid + 1;
18272 } else if (i0[key] > value) {
18273 hi = mid - 1;
18274 } else {
18275 return {lo: i0, hi: i1};
18276 }
18277 }
18278
18279 // given value is outside table (after last item)
18280 return {lo: i1, hi: null};
18281}
18282
18283/**
18284 * Linearly interpolates the given source `value` using the table items `skey` values and
18285 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
18286 * returns the position for a timestamp equal to 42. If value is out of bounds, values at
18287 * index [0, 1] or [n - 1, n] are used for the interpolation.
18288 */
18289function interpolate(table, skey, sval, tkey) {
18290 var range = lookup(table, skey, sval);
18291
18292 // Note: the lookup table ALWAYS contains at least 2 items (min and max)
18293 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
18294 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
18295
18296 var span = next[skey] - prev[skey];
18297 var ratio = span ? (sval - prev[skey]) / span : 0;
18298 var offset = (next[tkey] - prev[tkey]) * ratio;
18299
18300 return prev[tkey] + offset;
18301}
18302
18303/**
18304 * Convert the given value to a moment object using the given time options.
18305 * @see http://momentjs.com/docs/#/parsing/
18306 */
18307function momentify(value, options) {
18308 var parser = options.parser;
18309 var format = options.parser || options.format;
18310
18311 if (typeof parser === 'function') {
18312 return parser(value);
18313 }
18314
18315 if (typeof value === 'string' && typeof format === 'string') {
18316 return moment(value, format);
18317 }
18318
18319 if (!(value instanceof moment)) {
18320 value = moment(value);
18321 }
18322
18323 if (value.isValid()) {
18324 return value;
18325 }
18326
18327 // Labels are in an incompatible moment format and no `parser` has been provided.
18328 // The user might still use the deprecated `format` option to convert his inputs.
18329 if (typeof format === 'function') {
18330 return format(value);
18331 }
18332
18333 return value;
18334}
18335
18336function parse(input, scale) {
18337 if (helpers.isNullOrUndef(input)) {
18338 return null;
18339 }
18340
18341 var options = scale.options.time;
18342 var value = momentify(scale.getRightValue(input), options);
18343 if (!value.isValid()) {
18344 return null;
18345 }
18346
18347 if (options.round) {
18348 value.startOf(options.round);
18349 }
18350
18351 return value.valueOf();
18352}
18353
18354/**
18355 * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
18356 * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
18357 */
18358function determineStepSize(min, max, unit, capacity) {
18359 var range = max - min;
18360 var interval = INTERVALS[unit];
18361 var milliseconds = interval.size;
18362 var steps = interval.steps;
18363 var i, ilen, factor;
18364
18365 if (!steps) {
18366 return Math.ceil(range / (capacity * milliseconds));
18367 }
18368
18369 for (i = 0, ilen = steps.length; i < ilen; ++i) {
18370 factor = steps[i];
18371 if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
18372 break;
18373 }
18374 }
18375
18376 return factor;
18377}
18378
18379/**
18380 * Figures out what unit results in an appropriate number of auto-generated ticks
18381 */
18382function determineUnitForAutoTicks(minUnit, min, max, capacity) {
18383 var ilen = UNITS.length;
18384 var i, interval, factor;
18385
18386 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
18387 interval = INTERVALS[UNITS[i]];
18388 factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
18389
18390 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
18391 return UNITS[i];
18392 }
18393 }
18394
18395 return UNITS[ilen - 1];
18396}
18397
18398/**
18399 * Figures out what unit to format a set of ticks with
18400 */
18401function determineUnitForFormatting(ticks, minUnit, min, max) {
18402 var duration = moment.duration(moment(max).diff(moment(min)));
18403 var ilen = UNITS.length;
18404 var i, unit;
18405
18406 for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
18407 unit = UNITS[i];
18408 if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) {
18409 return unit;
18410 }
18411 }
18412
18413 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
18414}
18415
18416function determineMajorUnit(unit) {
18417 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
18418 if (INTERVALS[UNITS[i]].common) {
18419 return UNITS[i];
18420 }
18421 }
18422}
18423
18424/**
18425 * Generates a maximum of `capacity` timestamps between min and max, rounded to the
18426 * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
18427 * Important: this method can return ticks outside the min and max range, it's the
18428 * responsibility of the calling code to clamp values if needed.
18429 */
18430function generate(min, max, capacity, options) {
18431 var timeOpts = options.time;
18432 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
18433 var major = determineMajorUnit(minor);
18434 var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
18435 var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
18436 var majorTicksEnabled = options.ticks.major.enabled;
18437 var interval = INTERVALS[minor];
18438 var first = moment(min);
18439 var last = moment(max);
18440 var ticks = [];
18441 var time;
18442
18443 if (!stepSize) {
18444 stepSize = determineStepSize(min, max, minor, capacity);
18445 }
18446
18447 // For 'week' unit, handle the first day of week option
18448 if (weekday) {
18449 first = first.isoWeekday(weekday);
18450 last = last.isoWeekday(weekday);
18451 }
18452
18453 // Align first/last ticks on unit
18454 first = first.startOf(weekday ? 'day' : minor);
18455 last = last.startOf(weekday ? 'day' : minor);
18456
18457 // Make sure that the last tick include max
18458 if (last < max) {
18459 last.add(1, minor);
18460 }
18461
18462 time = moment(first);
18463
18464 if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
18465 // Align the first tick on the previous `minor` unit aligned on the `major` unit:
18466 // we first aligned time on the previous `major` unit then add the number of full
18467 // stepSize there is between first and the previous major time.
18468 time.startOf(major);
18469 time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
18470 }
18471
18472 for (; time < last; time.add(stepSize, minor)) {
18473 ticks.push(+time);
18474 }
18475
18476 ticks.push(+time);
18477
18478 return ticks;
18479}
18480
18481/**
18482 * Returns the right and left offsets from edges in the form of {left, right}.
18483 * Offsets are added when the `offset` option is true.
18484 */
18485function computeOffsets(table, ticks, min, max, options) {
18486 var left = 0;
18487 var right = 0;
18488 var upper, lower;
18489
18490 if (options.offset && ticks.length) {
18491 if (!options.time.min) {
18492 upper = ticks.length > 1 ? ticks[1] : max;
18493 lower = ticks[0];
18494 left = (
18495 interpolate(table, 'time', upper, 'pos') -
18496 interpolate(table, 'time', lower, 'pos')
18497 ) / 2;
18498 }
18499 if (!options.time.max) {
18500 upper = ticks[ticks.length - 1];
18501 lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
18502 right = (
18503 interpolate(table, 'time', upper, 'pos') -
18504 interpolate(table, 'time', lower, 'pos')
18505 ) / 2;
18506 }
18507 }
18508
18509 return {left: left, right: right};
18510}
18511
18512function ticksFromTimestamps(values, majorUnit) {
18513 var ticks = [];
18514 var i, ilen, value, major;
18515
18516 for (i = 0, ilen = values.length; i < ilen; ++i) {
18517 value = values[i];
18518 major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
18519
18520 ticks.push({
18521 value: value,
18522 major: major
18523 });
18524 }
18525
18526 return ticks;
18527}
18528
18529function determineLabelFormat(data, timeOpts) {
18530 var i, momentDate, hasTime;
18531 var ilen = data.length;
18532
18533 // find the label with the most parts (milliseconds, minutes, etc.)
18534 // format all labels with the same level of detail as the most specific label
18535 for (i = 0; i < ilen; i++) {
18536 momentDate = momentify(data[i], timeOpts);
18537 if (momentDate.millisecond() !== 0) {
18538 return 'MMM D, YYYY h:mm:ss.SSS a';
18539 }
18540 if (momentDate.second() !== 0 || momentDate.minute() !== 0 || momentDate.hour() !== 0) {
18541 hasTime = true;
18542 }
18543 }
18544 if (hasTime) {
18545 return 'MMM D, YYYY h:mm:ss a';
18546 }
18547 return 'MMM D, YYYY';
18548}
18549
18550module.exports = function(Chart) {
18551
18552 var defaultConfig = {
18553 position: 'bottom',
18554
18555 /**
18556 * Data distribution along the scale:
18557 * - 'linear': data are spread according to their time (distances can vary),
18558 * - 'series': data are spread at the same distance from each other.
18559 * @see https://github.com/chartjs/Chart.js/pull/4507
18560 * @since 2.7.0
18561 */
18562 distribution: 'linear',
18563
18564 /**
18565 * Scale boundary strategy (bypassed by min/max time options)
18566 * - `data`: make sure data are fully visible, ticks outside are removed
18567 * - `ticks`: make sure ticks are fully visible, data outside are truncated
18568 * @see https://github.com/chartjs/Chart.js/pull/4556
18569 * @since 2.7.0
18570 */
18571 bounds: 'data',
18572
18573 time: {
18574 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
18575 format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
18576 unit: false, // false == automatic or override with week, month, year, etc.
18577 round: false, // none, or override with week, month, year, etc.
18578 displayFormat: false, // DEPRECATED
18579 isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
18580 minUnit: 'millisecond',
18581
18582 // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
18583 displayFormats: {
18584 millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
18585 second: 'h:mm:ss a', // 11:20:01 AM
18586 minute: 'h:mm a', // 11:20 AM
18587 hour: 'hA', // 5PM
18588 day: 'MMM D', // Sep 4
18589 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
18590 month: 'MMM YYYY', // Sept 2015
18591 quarter: '[Q]Q - YYYY', // Q3
18592 year: 'YYYY' // 2015
18593 },
18594 },
18595 ticks: {
18596 autoSkip: false,
18597
18598 /**
18599 * Ticks generation input values:
18600 * - 'auto': generates "optimal" ticks based on scale size and time options.
18601 * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
18602 * - 'labels': generates ticks from user given `data.labels` values ONLY.
18603 * @see https://github.com/chartjs/Chart.js/pull/4507
18604 * @since 2.7.0
18605 */
18606 source: 'auto',
18607
18608 major: {
18609 enabled: false
18610 }
18611 }
18612 };
18613
18614 var TimeScale = Chart.Scale.extend({
18615 initialize: function() {
18616 if (!moment) {
18617 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');
18618 }
18619
18620 this.mergeTicksOptions();
18621
18622 Chart.Scale.prototype.initialize.call(this);
18623 },
18624
18625 update: function() {
18626 var me = this;
18627 var options = me.options;
18628
18629 // DEPRECATIONS: output a message only one time per update
18630 if (options.time && options.time.format) {
18631 console.warn('options.time.format is deprecated and replaced by options.time.parser.');
18632 }
18633
18634 return Chart.Scale.prototype.update.apply(me, arguments);
18635 },
18636
18637 /**
18638 * Allows data to be referenced via 't' attribute
18639 */
18640 getRightValue: function(rawValue) {
18641 if (rawValue && rawValue.t !== undefined) {
18642 rawValue = rawValue.t;
18643 }
18644 return Chart.Scale.prototype.getRightValue.call(this, rawValue);
18645 },
18646
18647 determineDataLimits: function() {
18648 var me = this;
18649 var chart = me.chart;
18650 var timeOpts = me.options.time;
18651 var unit = timeOpts.unit || 'day';
18652 var min = MAX_INTEGER;
18653 var max = MIN_INTEGER;
18654 var timestamps = [];
18655 var datasets = [];
18656 var labels = [];
18657 var i, j, ilen, jlen, data, timestamp;
18658
18659 // Convert labels to timestamps
18660 for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) {
18661 labels.push(parse(chart.data.labels[i], me));
18662 }
18663
18664 // Convert data to timestamps
18665 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
18666 if (chart.isDatasetVisible(i)) {
18667 data = chart.data.datasets[i].data;
18668
18669 // Let's consider that all data have the same format.
18670 if (helpers.isObject(data[0])) {
18671 datasets[i] = [];
18672
18673 for (j = 0, jlen = data.length; j < jlen; ++j) {
18674 timestamp = parse(data[j], me);
18675 timestamps.push(timestamp);
18676 datasets[i][j] = timestamp;
18677 }
18678 } else {
18679 timestamps.push.apply(timestamps, labels);
18680 datasets[i] = labels.slice(0);
18681 }
18682 } else {
18683 datasets[i] = [];
18684 }
18685 }
18686
18687 if (labels.length) {
18688 // Sort labels **after** data have been converted
18689 labels = arrayUnique(labels).sort(sorter);
18690 min = Math.min(min, labels[0]);
18691 max = Math.max(max, labels[labels.length - 1]);
18692 }
18693
18694 if (timestamps.length) {
18695 timestamps = arrayUnique(timestamps).sort(sorter);
18696 min = Math.min(min, timestamps[0]);
18697 max = Math.max(max, timestamps[timestamps.length - 1]);
18698 }
18699
18700 min = parse(timeOpts.min, me) || min;
18701 max = parse(timeOpts.max, me) || max;
18702
18703 // In case there is no valid min/max, set limits based on unit time option
18704 min = min === MAX_INTEGER ? +moment().startOf(unit) : min;
18705 max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max;
18706
18707 // Make sure that max is strictly higher than min (required by the lookup table)
18708 me.min = Math.min(min, max);
18709 me.max = Math.max(min + 1, max);
18710
18711 // PRIVATE
18712 me._horizontal = me.isHorizontal();
18713 me._table = [];
18714 me._timestamps = {
18715 data: timestamps,
18716 datasets: datasets,
18717 labels: labels
18718 };
18719 },
18720
18721 buildTicks: function() {
18722 var me = this;
18723 var min = me.min;
18724 var max = me.max;
18725 var options = me.options;
18726 var timeOpts = options.time;
18727 var timestamps = [];
18728 var ticks = [];
18729 var i, ilen, timestamp;
18730
18731 switch (options.ticks.source) {
18732 case 'data':
18733 timestamps = me._timestamps.data;
18734 break;
18735 case 'labels':
18736 timestamps = me._timestamps.labels;
18737 break;
18738 case 'auto':
18739 default:
18740 timestamps = generate(min, max, me.getLabelCapacity(min), options);
18741 }
18742
18743 if (options.bounds === 'ticks' && timestamps.length) {
18744 min = timestamps[0];
18745 max = timestamps[timestamps.length - 1];
18746 }
18747
18748 // Enforce limits with user min/max options
18749 min = parse(timeOpts.min, me) || min;
18750 max = parse(timeOpts.max, me) || max;
18751
18752 // Remove ticks outside the min/max range
18753 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
18754 timestamp = timestamps[i];
18755 if (timestamp >= min && timestamp <= max) {
18756 ticks.push(timestamp);
18757 }
18758 }
18759
18760 me.min = min;
18761 me.max = max;
18762
18763 // PRIVATE
18764 me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max);
18765 me._majorUnit = determineMajorUnit(me._unit);
18766 me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
18767 me._offsets = computeOffsets(me._table, ticks, min, max, options);
18768 me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts);
18769
18770 return ticksFromTimestamps(ticks, me._majorUnit);
18771 },
18772
18773 getLabelForIndex: function(index, datasetIndex) {
18774 var me = this;
18775 var data = me.chart.data;
18776 var timeOpts = me.options.time;
18777 var label = data.labels && index < data.labels.length ? data.labels[index] : '';
18778 var value = data.datasets[datasetIndex].data[index];
18779
18780 if (helpers.isObject(value)) {
18781 label = me.getRightValue(value);
18782 }
18783 if (timeOpts.tooltipFormat) {
18784 return momentify(label, timeOpts).format(timeOpts.tooltipFormat);
18785 }
18786 if (typeof label === 'string') {
18787 return label;
18788 }
18789
18790 return momentify(label, timeOpts).format(me._labelFormat);
18791 },
18792
18793 /**
18794 * Function to format an individual tick mark
18795 * @private
18796 */
18797 tickFormatFunction: function(tick, index, ticks, formatOverride) {
18798 var me = this;
18799 var options = me.options;
18800 var time = tick.valueOf();
18801 var formats = options.time.displayFormats;
18802 var minorFormat = formats[me._unit];
18803 var majorUnit = me._majorUnit;
18804 var majorFormat = formats[majorUnit];
18805 var majorTime = tick.clone().startOf(majorUnit).valueOf();
18806 var majorTickOpts = options.ticks.major;
18807 var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
18808 var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat);
18809 var tickOpts = major ? majorTickOpts : options.ticks.minor;
18810 var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback);
18811
18812 return formatter ? formatter(label, index, ticks) : label;
18813 },
18814
18815 convertTicksToLabels: function(ticks) {
18816 var labels = [];
18817 var i, ilen;
18818
18819 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
18820 labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
18821 }
18822
18823 return labels;
18824 },
18825
18826 /**
18827 * @private
18828 */
18829 getPixelForOffset: function(time) {
18830 var me = this;
18831 var size = me._horizontal ? me.width : me.height;
18832 var start = me._horizontal ? me.left : me.top;
18833 var pos = interpolate(me._table, 'time', time, 'pos');
18834
18835 return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
18836 },
18837
18838 getPixelForValue: function(value, index, datasetIndex) {
18839 var me = this;
18840 var time = null;
18841
18842 if (index !== undefined && datasetIndex !== undefined) {
18843 time = me._timestamps.datasets[datasetIndex][index];
18844 }
18845
18846 if (time === null) {
18847 time = parse(value, me);
18848 }
18849
18850 if (time !== null) {
18851 return me.getPixelForOffset(time);
18852 }
18853 },
18854
18855 getPixelForTick: function(index) {
18856 var ticks = this.getTicks();
18857 return index >= 0 && index < ticks.length ?
18858 this.getPixelForOffset(ticks[index].value) :
18859 null;
18860 },
18861
18862 getValueForPixel: function(pixel) {
18863 var me = this;
18864 var size = me._horizontal ? me.width : me.height;
18865 var start = me._horizontal ? me.left : me.top;
18866 var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
18867 var time = interpolate(me._table, 'pos', pos, 'time');
18868
18869 return moment(time);
18870 },
18871
18872 /**
18873 * Crude approximation of what the label width might be
18874 * @private
18875 */
18876 getLabelWidth: function(label) {
18877 var me = this;
18878 var ticksOpts = me.options.ticks;
18879 var tickLabelWidth = me.ctx.measureText(label).width;
18880 var angle = helpers.toRadians(ticksOpts.maxRotation);
18881 var cosRotation = Math.cos(angle);
18882 var sinRotation = Math.sin(angle);
18883 var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
18884
18885 return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
18886 },
18887
18888 /**
18889 * @private
18890 */
18891 getLabelCapacity: function(exampleTime) {
18892 var me = this;
18893
18894 var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
18895
18896 var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride);
18897 var tickLabelWidth = me.getLabelWidth(exampleLabel);
18898 var innerWidth = me.isHorizontal() ? me.width : me.height;
18899
18900 var capacity = Math.floor(innerWidth / tickLabelWidth);
18901 return capacity > 0 ? capacity : 1;
18902 }
18903 });
18904
18905 Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
18906};
18907
18908},{"25":25,"45":45,"6":6}]},{},[7])(7)
18909});