PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.8
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.8
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / assets / libs / chartjs / chart.js

chart.js in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.8, at assets/libs/chartjs/chart.js

14,145 lines 388.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*!
2 * Chart.js
3 * http://chartjs.org/
4 *
5 * Copyright 2017 Nick Downie
6 * Released under the MIT license
7 * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
8 */
9 (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){
10
11 },{}],2:[function(require,module,exports){
12 /* MIT license */
13 var colorNames = require(6);
14
15 module.exports = {
16 getRgba: getRgba,
17 getHsla: getHsla,
18 getRgb: getRgb,
19 getHsl: getHsl,
20 getHwb: getHwb,
21 getAlpha: getAlpha,
22
23 hexString: hexString,
24 rgbString: rgbString,
25 rgbaString: rgbaString,
26 percentString: percentString,
27 percentaString: percentaString,
28 hslString: hslString,
29 hslaString: hslaString,
30 hwbString: hwbString,
31 keyword: keyword
32 }
33
34 function getRgba(string) {
35 if (!string) {
36 return;
37 }
38 var abbr = /^#([a-fA-F0-9]{3})$/i,
39 hex = /^#([a-fA-F0-9]{6})$/i,
40 rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
41 per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
42 keyword = /(\w+)/;
43
44 var rgb = [0, 0, 0],
45 a = 1,
46 match = string.match(abbr);
47 if (match) {
48 match = match[1];
49 for (var i = 0; i < rgb.length; i++) {
50 rgb[i] = parseInt(match[i] + match[i], 16);
51 }
52 }
53 else if (match = string.match(hex)) {
54 match = match[1];
55 for (var i = 0; i < rgb.length; i++) {
56 rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
57 }
58 }
59 else if (match = string.match(rgba)) {
60 for (var i = 0; i < rgb.length; i++) {
61 rgb[i] = parseInt(match[i + 1]);
62 }
63 a = parseFloat(match[4]);
64 }
65 else if (match = string.match(per)) {
66 for (var i = 0; i < rgb.length; i++) {
67 rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
68 }
69 a = parseFloat(match[4]);
70 }
71 else if (match = string.match(keyword)) {
72 if (match[1] == "transparent") {
73 return [0, 0, 0, 0];
74 }
75 rgb = colorNames[match[1]];
76 if (!rgb) {
77 return;
78 }
79 }
80
81 for (var i = 0; i < rgb.length; i++) {
82 rgb[i] = scale(rgb[i], 0, 255);
83 }
84 if (!a && a != 0) {
85 a = 1;
86 }
87 else {
88 a = scale(a, 0, 1);
89 }
90 rgb[3] = a;
91 return rgb;
92 }
93
94 function getHsla(string) {
95 if (!string) {
96 return;
97 }
98 var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
99 var match = string.match(hsl);
100 if (match) {
101 var alpha = parseFloat(match[4]);
102 var h = scale(parseInt(match[1]), 0, 360),
103 s = scale(parseFloat(match[2]), 0, 100),
104 l = scale(parseFloat(match[3]), 0, 100),
105 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
106 return [h, s, l, a];
107 }
108 }
109
110 function getHwb(string) {
111 if (!string) {
112 return;
113 }
114 var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
115 var match = string.match(hwb);
116 if (match) {
117 var alpha = parseFloat(match[4]);
118 var h = scale(parseInt(match[1]), 0, 360),
119 w = scale(parseFloat(match[2]), 0, 100),
120 b = scale(parseFloat(match[3]), 0, 100),
121 a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
122 return [h, w, b, a];
123 }
124 }
125
126 function getRgb(string) {
127 var rgba = getRgba(string);
128 return rgba && rgba.slice(0, 3);
129 }
130
131 function getHsl(string) {
132 var hsla = getHsla(string);
133 return hsla && hsla.slice(0, 3);
134 }
135
136 function getAlpha(string) {
137 var vals = getRgba(string);
138 if (vals) {
139 return vals[3];
140 }
141 else if (vals = getHsla(string)) {
142 return vals[3];
143 }
144 else if (vals = getHwb(string)) {
145 return vals[3];
146 }
147 }
148
149 // generators
150 function hexString(rgb) {
151 return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
152 + hexDouble(rgb[2]);
153 }
154
155 function rgbString(rgba, alpha) {
156 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
157 return rgbaString(rgba, alpha);
158 }
159 return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
160 }
161
162 function rgbaString(rgba, alpha) {
163 if (alpha === undefined) {
164 alpha = (rgba[3] !== undefined ? rgba[3] : 1);
165 }
166 return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
167 + ", " + alpha + ")";
168 }
169
170 function percentString(rgba, alpha) {
171 if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
172 return percentaString(rgba, alpha);
173 }
174 var r = Math.round(rgba[0]/255 * 100),
175 g = Math.round(rgba[1]/255 * 100),
176 b = Math.round(rgba[2]/255 * 100);
177
178 return "rgb(" + r + "%, " + g + "%, " + b + "%)";
179 }
180
181 function percentaString(rgba, alpha) {
182 var r = Math.round(rgba[0]/255 * 100),
183 g = Math.round(rgba[1]/255 * 100),
184 b = Math.round(rgba[2]/255 * 100);
185 return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
186 }
187
188 function hslString(hsla, alpha) {
189 if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
190 return hslaString(hsla, alpha);
191 }
192 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
193 }
194
195 function hslaString(hsla, alpha) {
196 if (alpha === undefined) {
197 alpha = (hsla[3] !== undefined ? hsla[3] : 1);
198 }
199 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
200 + alpha + ")";
201 }
202
203 // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
204 // (hwb have alpha optional & 1 is default value)
205 function hwbString(hwb, alpha) {
206 if (alpha === undefined) {
207 alpha = (hwb[3] !== undefined ? hwb[3] : 1);
208 }
209 return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
210 + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
211 }
212
213 function keyword(rgb) {
214 return reverseNames[rgb.slice(0, 3)];
215 }
216
217 // helpers
218 function scale(num, min, max) {
219 return Math.min(Math.max(min, num), max);
220 }
221
222 function hexDouble(num) {
223 var str = num.toString(16).toUpperCase();
224 return (str.length < 2) ? "0" + str : str;
225 }
226
227
228 //create a list of reverse color names
229 var reverseNames = {};
230 for (var name in colorNames) {
231 reverseNames[colorNames[name]] = name;
232 }
233
234 },{"6":6}],3:[function(require,module,exports){
235 /* MIT license */
236 var convert = require(5);
237 var string = require(2);
238
239 var Color = function (obj) {
240 if (obj instanceof Color) {
241 return obj;
242 }
243 if (!(this instanceof Color)) {
244 return new Color(obj);
245 }
246
247 this.valid = false;
248 this.values = {
249 rgb: [0, 0, 0],
250 hsl: [0, 0, 0],
251 hsv: [0, 0, 0],
252 hwb: [0, 0, 0],
253 cmyk: [0, 0, 0, 0],
254 alpha: 1
255 };
256
257 // parse Color() argument
258 var vals;
259 if (typeof obj === 'string') {
260 vals = string.getRgba(obj);
261 if (vals) {
262 this.setValues('rgb', vals);
263 } else if (vals = string.getHsla(obj)) {
264 this.setValues('hsl', vals);
265 } else if (vals = string.getHwb(obj)) {
266 this.setValues('hwb', vals);
267 }
268 } else if (typeof obj === 'object') {
269 vals = obj;
270 if (vals.r !== undefined || vals.red !== undefined) {
271 this.setValues('rgb', vals);
272 } else if (vals.l !== undefined || vals.lightness !== undefined) {
273 this.setValues('hsl', vals);
274 } else if (vals.v !== undefined || vals.value !== undefined) {
275 this.setValues('hsv', vals);
276 } else if (vals.w !== undefined || vals.whiteness !== undefined) {
277 this.setValues('hwb', vals);
278 } else if (vals.c !== undefined || vals.cyan !== undefined) {
279 this.setValues('cmyk', vals);
280 }
281 }
282 };
283
284 Color.prototype = {
285 isValid: function () {
286 return this.valid;
287 },
288 rgb: function () {
289 return this.setSpace('rgb', arguments);
290 },
291 hsl: function () {
292 return this.setSpace('hsl', arguments);
293 },
294 hsv: function () {
295 return this.setSpace('hsv', arguments);
296 },
297 hwb: function () {
298 return this.setSpace('hwb', arguments);
299 },
300 cmyk: function () {
301 return this.setSpace('cmyk', arguments);
302 },
303
304 rgbArray: function () {
305 return this.values.rgb;
306 },
307 hslArray: function () {
308 return this.values.hsl;
309 },
310 hsvArray: function () {
311 return this.values.hsv;
312 },
313 hwbArray: function () {
314 var values = this.values;
315 if (values.alpha !== 1) {
316 return values.hwb.concat([values.alpha]);
317 }
318 return values.hwb;
319 },
320 cmykArray: function () {
321 return this.values.cmyk;
322 },
323 rgbaArray: function () {
324 var values = this.values;
325 return values.rgb.concat([values.alpha]);
326 },
327 hslaArray: function () {
328 var values = this.values;
329 return values.hsl.concat([values.alpha]);
330 },
331 alpha: function (val) {
332 if (val === undefined) {
333 return this.values.alpha;
334 }
335 this.setValues('alpha', val);
336 return this;
337 },
338
339 red: function (val) {
340 return this.setChannel('rgb', 0, val);
341 },
342 green: function (val) {
343 return this.setChannel('rgb', 1, val);
344 },
345 blue: function (val) {
346 return this.setChannel('rgb', 2, val);
347 },
348 hue: function (val) {
349 if (val) {
350 val %= 360;
351 val = val < 0 ? 360 + val : val;
352 }
353 return this.setChannel('hsl', 0, val);
354 },
355 saturation: function (val) {
356 return this.setChannel('hsl', 1, val);
357 },
358 lightness: function (val) {
359 return this.setChannel('hsl', 2, val);
360 },
361 saturationv: function (val) {
362 return this.setChannel('hsv', 1, val);
363 },
364 whiteness: function (val) {
365 return this.setChannel('hwb', 1, val);
366 },
367 blackness: function (val) {
368 return this.setChannel('hwb', 2, val);
369 },
370 value: function (val) {
371 return this.setChannel('hsv', 2, val);
372 },
373 cyan: function (val) {
374 return this.setChannel('cmyk', 0, val);
375 },
376 magenta: function (val) {
377 return this.setChannel('cmyk', 1, val);
378 },
379 yellow: function (val) {
380 return this.setChannel('cmyk', 2, val);
381 },
382 black: function (val) {
383 return this.setChannel('cmyk', 3, val);
384 },
385
386 hexString: function () {
387 return string.hexString(this.values.rgb);
388 },
389 rgbString: function () {
390 return string.rgbString(this.values.rgb, this.values.alpha);
391 },
392 rgbaString: function () {
393 return string.rgbaString(this.values.rgb, this.values.alpha);
394 },
395 percentString: function () {
396 return string.percentString(this.values.rgb, this.values.alpha);
397 },
398 hslString: function () {
399 return string.hslString(this.values.hsl, this.values.alpha);
400 },
401 hslaString: function () {
402 return string.hslaString(this.values.hsl, this.values.alpha);
403 },
404 hwbString: function () {
405 return string.hwbString(this.values.hwb, this.values.alpha);
406 },
407 keyword: function () {
408 return string.keyword(this.values.rgb, this.values.alpha);
409 },
410
411 rgbNumber: function () {
412 var rgb = this.values.rgb;
413 return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
414 },
415
416 luminosity: function () {
417 // http://www.w3.org/TR/WCAG20/#relativeluminancedef
418 var rgb = this.values.rgb;
419 var lum = [];
420 for (var i = 0; i < rgb.length; i++) {
421 var chan = rgb[i] / 255;
422 lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
423 }
424 return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
425 },
426
427 contrast: function (color2) {
428 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
429 var lum1 = this.luminosity();
430 var lum2 = color2.luminosity();
431 if (lum1 > lum2) {
432 return (lum1 + 0.05) / (lum2 + 0.05);
433 }
434 return (lum2 + 0.05) / (lum1 + 0.05);
435 },
436
437 level: function (color2) {
438 var contrastRatio = this.contrast(color2);
439 if (contrastRatio >= 7.1) {
440 return 'AAA';
441 }
442
443 return (contrastRatio >= 4.5) ? 'AA' : '';
444 },
445
446 dark: function () {
447 // YIQ equation from http://24ways.org/2010/calculating-color-contrast
448 var rgb = this.values.rgb;
449 var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
450 return yiq < 128;
451 },
452
453 light: function () {
454 return !this.dark();
455 },
456
457 negate: function () {
458 var rgb = [];
459 for (var i = 0; i < 3; i++) {
460 rgb[i] = 255 - this.values.rgb[i];
461 }
462 this.setValues('rgb', rgb);
463 return this;
464 },
465
466 lighten: function (ratio) {
467 var hsl = this.values.hsl;
468 hsl[2] += hsl[2] * ratio;
469 this.setValues('hsl', hsl);
470 return this;
471 },
472
473 darken: function (ratio) {
474 var hsl = this.values.hsl;
475 hsl[2] -= hsl[2] * ratio;
476 this.setValues('hsl', hsl);
477 return this;
478 },
479
480 saturate: function (ratio) {
481 var hsl = this.values.hsl;
482 hsl[1] += hsl[1] * ratio;
483 this.setValues('hsl', hsl);
484 return this;
485 },
486
487 desaturate: function (ratio) {
488 var hsl = this.values.hsl;
489 hsl[1] -= hsl[1] * ratio;
490 this.setValues('hsl', hsl);
491 return this;
492 },
493
494 whiten: function (ratio) {
495 var hwb = this.values.hwb;
496 hwb[1] += hwb[1] * ratio;
497 this.setValues('hwb', hwb);
498 return this;
499 },
500
501 blacken: function (ratio) {
502 var hwb = this.values.hwb;
503 hwb[2] += hwb[2] * ratio;
504 this.setValues('hwb', hwb);
505 return this;
506 },
507
508 greyscale: function () {
509 var rgb = this.values.rgb;
510 // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
511 var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
512 this.setValues('rgb', [val, val, val]);
513 return this;
514 },
515
516 clearer: function (ratio) {
517 var alpha = this.values.alpha;
518 this.setValues('alpha', alpha - (alpha * ratio));
519 return this;
520 },
521
522 opaquer: function (ratio) {
523 var alpha = this.values.alpha;
524 this.setValues('alpha', alpha + (alpha * ratio));
525 return this;
526 },
527
528 rotate: function (degrees) {
529 var hsl = this.values.hsl;
530 var hue = (hsl[0] + degrees) % 360;
531 hsl[0] = hue < 0 ? 360 + hue : hue;
532 this.setValues('hsl', hsl);
533 return this;
534 },
535
536 /**
537 * Ported from sass implementation in C
538 * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
539 */
540 mix: function (mixinColor, weight) {
541 var color1 = this;
542 var color2 = mixinColor;
543 var p = weight === undefined ? 0.5 : weight;
544
545 var w = 2 * p - 1;
546 var a = color1.alpha() - color2.alpha();
547
548 var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
549 var w2 = 1 - w1;
550
551 return this
552 .rgb(
553 w1 * color1.red() + w2 * color2.red(),
554 w1 * color1.green() + w2 * color2.green(),
555 w1 * color1.blue() + w2 * color2.blue()
556 )
557 .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
558 },
559
560 toJSON: function () {
561 return this.rgb();
562 },
563
564 clone: function () {
565 // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
566 // making the final build way to big to embed in Chart.js. So let's do it manually,
567 // assuming that values to clone are 1 dimension arrays containing only numbers,
568 // except 'alpha' which is a number.
569 var result = new Color();
570 var source = this.values;
571 var target = result.values;
572 var value, type;
573
574 for (var prop in source) {
575 if (source.hasOwnProperty(prop)) {
576 value = source[prop];
577 type = ({}).toString.call(value);
578 if (type === '[object Array]') {
579 target[prop] = value.slice(0);
580 } else if (type === '[object Number]') {
581 target[prop] = value;
582 } else {
583 console.error('unexpected color value:', value);
584 }
585 }
586 }
587
588 return result;
589 }
590 };
591
592 Color.prototype.spaces = {
593 rgb: ['red', 'green', 'blue'],
594 hsl: ['hue', 'saturation', 'lightness'],
595 hsv: ['hue', 'saturation', 'value'],
596 hwb: ['hue', 'whiteness', 'blackness'],
597 cmyk: ['cyan', 'magenta', 'yellow', 'black']
598 };
599
600 Color.prototype.maxes = {
601 rgb: [255, 255, 255],
602 hsl: [360, 100, 100],
603 hsv: [360, 100, 100],
604 hwb: [360, 100, 100],
605 cmyk: [100, 100, 100, 100]
606 };
607
608 Color.prototype.getValues = function (space) {
609 var values = this.values;
610 var vals = {};
611
612 for (var i = 0; i < space.length; i++) {
613 vals[space.charAt(i)] = values[space][i];
614 }
615
616 if (values.alpha !== 1) {
617 vals.a = values.alpha;
618 }
619
620 // {r: 255, g: 255, b: 255, a: 0.4}
621 return vals;
622 };
623
624 Color.prototype.setValues = function (space, vals) {
625 var values = this.values;
626 var spaces = this.spaces;
627 var maxes = this.maxes;
628 var alpha = 1;
629 var i;
630
631 this.valid = true;
632
633 if (space === 'alpha') {
634 alpha = vals;
635 } else if (vals.length) {
636 // [10, 10, 10]
637 values[space] = vals.slice(0, space.length);
638 alpha = vals[space.length];
639 } else if (vals[space.charAt(0)] !== undefined) {
640 // {r: 10, g: 10, b: 10}
641 for (i = 0; i < space.length; i++) {
642 values[space][i] = vals[space.charAt(i)];
643 }
644
645 alpha = vals.a;
646 } else if (vals[spaces[space][0]] !== undefined) {
647 // {red: 10, green: 10, blue: 10}
648 var chans = spaces[space];
649
650 for (i = 0; i < space.length; i++) {
651 values[space][i] = vals[chans[i]];
652 }
653
654 alpha = vals.alpha;
655 }
656
657 values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
658
659 if (space === 'alpha') {
660 return false;
661 }
662
663 var capped;
664
665 // cap values of the space prior converting all values
666 for (i = 0; i < space.length; i++) {
667 capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
668 values[space][i] = Math.round(capped);
669 }
670
671 // convert to all the other color spaces
672 for (var sname in spaces) {
673 if (sname !== space) {
674 values[sname] = convert[space][sname](values[space]);
675 }
676 }
677
678 return true;
679 };
680
681 Color.prototype.setSpace = function (space, args) {
682 var vals = args[0];
683
684 if (vals === undefined) {
685 // color.rgb()
686 return this.getValues(space);
687 }
688
689 // color.rgb(10, 10, 10)
690 if (typeof vals === 'number') {
691 vals = Array.prototype.slice.call(args);
692 }
693
694 this.setValues(space, vals);
695 return this;
696 };
697
698 Color.prototype.setChannel = function (space, index, val) {
699 var svalues = this.values[space];
700 if (val === undefined) {
701 // color.red()
702 return svalues[index];
703 } else if (val === svalues[index]) {
704 // color.red(color.red())
705 return this;
706 }
707
708 // color.red(100)
709 svalues[index] = val;
710 this.setValues(space, svalues);
711
712 return this;
713 };
714
715 if (typeof window !== 'undefined') {
716 window.Color = Color;
717 }
718
719 module.exports = Color;
720
721 },{"2":2,"5":5}],4:[function(require,module,exports){
722 /* MIT license */
723
724 module.exports = {
725 rgb2hsl: rgb2hsl,
726 rgb2hsv: rgb2hsv,
727 rgb2hwb: rgb2hwb,
728 rgb2cmyk: rgb2cmyk,
729 rgb2keyword: rgb2keyword,
730 rgb2xyz: rgb2xyz,
731 rgb2lab: rgb2lab,
732 rgb2lch: rgb2lch,
733
734 hsl2rgb: hsl2rgb,
735 hsl2hsv: hsl2hsv,
736 hsl2hwb: hsl2hwb,
737 hsl2cmyk: hsl2cmyk,
738 hsl2keyword: hsl2keyword,
739
740 hsv2rgb: hsv2rgb,
741 hsv2hsl: hsv2hsl,
742 hsv2hwb: hsv2hwb,
743 hsv2cmyk: hsv2cmyk,
744 hsv2keyword: hsv2keyword,
745
746 hwb2rgb: hwb2rgb,
747 hwb2hsl: hwb2hsl,
748 hwb2hsv: hwb2hsv,
749 hwb2cmyk: hwb2cmyk,
750 hwb2keyword: hwb2keyword,
751
752 cmyk2rgb: cmyk2rgb,
753 cmyk2hsl: cmyk2hsl,
754 cmyk2hsv: cmyk2hsv,
755 cmyk2hwb: cmyk2hwb,
756 cmyk2keyword: cmyk2keyword,
757
758 keyword2rgb: keyword2rgb,
759 keyword2hsl: keyword2hsl,
760 keyword2hsv: keyword2hsv,
761 keyword2hwb: keyword2hwb,
762 keyword2cmyk: keyword2cmyk,
763 keyword2lab: keyword2lab,
764 keyword2xyz: keyword2xyz,
765
766 xyz2rgb: xyz2rgb,
767 xyz2lab: xyz2lab,
768 xyz2lch: xyz2lch,
769
770 lab2xyz: lab2xyz,
771 lab2rgb: lab2rgb,
772 lab2lch: lab2lch,
773
774 lch2lab: lch2lab,
775 lch2xyz: lch2xyz,
776 lch2rgb: lch2rgb
777 }
778
779
780 function rgb2hsl(rgb) {
781 var r = rgb[0]/255,
782 g = rgb[1]/255,
783 b = rgb[2]/255,
784 min = Math.min(r, g, b),
785 max = Math.max(r, g, b),
786 delta = max - min,
787 h, s, l;
788
789 if (max == min)
790 h = 0;
791 else if (r == max)
792 h = (g - b) / delta;
793 else if (g == max)
794 h = 2 + (b - r) / delta;
795 else if (b == max)
796 h = 4 + (r - g)/ delta;
797
798 h = Math.min(h * 60, 360);
799
800 if (h < 0)
801 h += 360;
802
803 l = (min + max) / 2;
804
805 if (max == min)
806 s = 0;
807 else if (l <= 0.5)
808 s = delta / (max + min);
809 else
810 s = delta / (2 - max - min);
811
812 return [h, s * 100, l * 100];
813 }
814
815 function rgb2hsv(rgb) {
816 var r = rgb[0],
817 g = rgb[1],
818 b = rgb[2],
819 min = Math.min(r, g, b),
820 max = Math.max(r, g, b),
821 delta = max - min,
822 h, s, v;
823
824 if (max == 0)
825 s = 0;
826 else
827 s = (delta/max * 1000)/10;
828
829 if (max == min)
830 h = 0;
831 else if (r == max)
832 h = (g - b) / delta;
833 else if (g == max)
834 h = 2 + (b - r) / delta;
835 else if (b == max)
836 h = 4 + (r - g) / delta;
837
838 h = Math.min(h * 60, 360);
839
840 if (h < 0)
841 h += 360;
842
843 v = ((max / 255) * 1000) / 10;
844
845 return [h, s, v];
846 }
847
848 function rgb2hwb(rgb) {
849 var r = rgb[0],
850 g = rgb[1],
851 b = rgb[2],
852 h = rgb2hsl(rgb)[0],
853 w = 1/255 * Math.min(r, Math.min(g, b)),
854 b = 1 - 1/255 * Math.max(r, Math.max(g, b));
855
856 return [h, w * 100, b * 100];
857 }
858
859 function rgb2cmyk(rgb) {
860 var r = rgb[0] / 255,
861 g = rgb[1] / 255,
862 b = rgb[2] / 255,
863 c, m, y, k;
864
865 k = Math.min(1 - r, 1 - g, 1 - b);
866 c = (1 - r - k) / (1 - k) || 0;
867 m = (1 - g - k) / (1 - k) || 0;
868 y = (1 - b - k) / (1 - k) || 0;
869 return [c * 100, m * 100, y * 100, k * 100];
870 }
871
872 function rgb2keyword(rgb) {
873 return reverseKeywords[JSON.stringify(rgb)];
874 }
875
876 function rgb2xyz(rgb) {
877 var r = rgb[0] / 255,
878 g = rgb[1] / 255,
879 b = rgb[2] / 255;
880
881 // assume sRGB
882 r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
883 g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
884 b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
885
886 var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
887 var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
888 var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
889
890 return [x * 100, y *100, z * 100];
891 }
892
893 function rgb2lab(rgb) {
894 var xyz = rgb2xyz(rgb),
895 x = xyz[0],
896 y = xyz[1],
897 z = xyz[2],
898 l, a, b;
899
900 x /= 95.047;
901 y /= 100;
902 z /= 108.883;
903
904 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
905 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
906 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
907
908 l = (116 * y) - 16;
909 a = 500 * (x - y);
910 b = 200 * (y - z);
911
912 return [l, a, b];
913 }
914
915 function rgb2lch(args) {
916 return lab2lch(rgb2lab(args));
917 }
918
919 function hsl2rgb(hsl) {
920 var h = hsl[0] / 360,
921 s = hsl[1] / 100,
922 l = hsl[2] / 100,
923 t1, t2, t3, rgb, val;
924
925 if (s == 0) {
926 val = l * 255;
927 return [val, val, val];
928 }
929
930 if (l < 0.5)
931 t2 = l * (1 + s);
932 else
933 t2 = l + s - l * s;
934 t1 = 2 * l - t2;
935
936 rgb = [0, 0, 0];
937 for (var i = 0; i < 3; i++) {
938 t3 = h + 1 / 3 * - (i - 1);
939 t3 < 0 && t3++;
940 t3 > 1 && t3--;
941
942 if (6 * t3 < 1)
943 val = t1 + (t2 - t1) * 6 * t3;
944 else if (2 * t3 < 1)
945 val = t2;
946 else if (3 * t3 < 2)
947 val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
948 else
949 val = t1;
950
951 rgb[i] = val * 255;
952 }
953
954 return rgb;
955 }
956
957 function hsl2hsv(hsl) {
958 var h = hsl[0],
959 s = hsl[1] / 100,
960 l = hsl[2] / 100,
961 sv, v;
962
963 if(l === 0) {
964 // no need to do calc on black
965 // also avoids divide by 0 error
966 return [0, 0, 0];
967 }
968
969 l *= 2;
970 s *= (l <= 1) ? l : 2 - l;
971 v = (l + s) / 2;
972 sv = (2 * s) / (l + s);
973 return [h, sv * 100, v * 100];
974 }
975
976 function hsl2hwb(args) {
977 return rgb2hwb(hsl2rgb(args));
978 }
979
980 function hsl2cmyk(args) {
981 return rgb2cmyk(hsl2rgb(args));
982 }
983
984 function hsl2keyword(args) {
985 return rgb2keyword(hsl2rgb(args));
986 }
987
988
989 function hsv2rgb(hsv) {
990 var h = hsv[0] / 60,
991 s = hsv[1] / 100,
992 v = hsv[2] / 100,
993 hi = Math.floor(h) % 6;
994
995 var f = h - Math.floor(h),
996 p = 255 * v * (1 - s),
997 q = 255 * v * (1 - (s * f)),
998 t = 255 * v * (1 - (s * (1 - f))),
999 v = 255 * v;
1000
1001 switch(hi) {
1002 case 0:
1003 return [v, t, p];
1004 case 1:
1005 return [q, v, p];
1006 case 2:
1007 return [p, v, t];
1008 case 3:
1009 return [p, q, v];
1010 case 4:
1011 return [t, p, v];
1012 case 5:
1013 return [v, p, q];
1014 }
1015 }
1016
1017 function hsv2hsl(hsv) {
1018 var h = hsv[0],
1019 s = hsv[1] / 100,
1020 v = hsv[2] / 100,
1021 sl, l;
1022
1023 l = (2 - s) * v;
1024 sl = s * v;
1025 sl /= (l <= 1) ? l : 2 - l;
1026 sl = sl || 0;
1027 l /= 2;
1028 return [h, sl * 100, l * 100];
1029 }
1030
1031 function hsv2hwb(args) {
1032 return rgb2hwb(hsv2rgb(args))
1033 }
1034
1035 function hsv2cmyk(args) {
1036 return rgb2cmyk(hsv2rgb(args));
1037 }
1038
1039 function hsv2keyword(args) {
1040 return rgb2keyword(hsv2rgb(args));
1041 }
1042
1043 // http://dev.w3.org/csswg/css-color/#hwb-to-rgb
1044 function hwb2rgb(hwb) {
1045 var h = hwb[0] / 360,
1046 wh = hwb[1] / 100,
1047 bl = hwb[2] / 100,
1048 ratio = wh + bl,
1049 i, v, f, n;
1050
1051 // wh + bl cant be > 1
1052 if (ratio > 1) {
1053 wh /= ratio;
1054 bl /= ratio;
1055 }
1056
1057 i = Math.floor(6 * h);
1058 v = 1 - bl;
1059 f = 6 * h - i;
1060 if ((i & 0x01) != 0) {
1061 f = 1 - f;
1062 }
1063 n = wh + f * (v - wh); // linear interpolation
1064
1065 switch (i) {
1066 default:
1067 case 6:
1068 case 0: r = v; g = n; b = wh; break;
1069 case 1: r = n; g = v; b = wh; break;
1070 case 2: r = wh; g = v; b = n; break;
1071 case 3: r = wh; g = n; b = v; break;
1072 case 4: r = n; g = wh; b = v; break;
1073 case 5: r = v; g = wh; b = n; break;
1074 }
1075
1076 return [r * 255, g * 255, b * 255];
1077 }
1078
1079 function hwb2hsl(args) {
1080 return rgb2hsl(hwb2rgb(args));
1081 }
1082
1083 function hwb2hsv(args) {
1084 return rgb2hsv(hwb2rgb(args));
1085 }
1086
1087 function hwb2cmyk(args) {
1088 return rgb2cmyk(hwb2rgb(args));
1089 }
1090
1091 function hwb2keyword(args) {
1092 return rgb2keyword(hwb2rgb(args));
1093 }
1094
1095 function cmyk2rgb(cmyk) {
1096 var c = cmyk[0] / 100,
1097 m = cmyk[1] / 100,
1098 y = cmyk[2] / 100,
1099 k = cmyk[3] / 100,
1100 r, g, b;
1101
1102 r = 1 - Math.min(1, c * (1 - k) + k);
1103 g = 1 - Math.min(1, m * (1 - k) + k);
1104 b = 1 - Math.min(1, y * (1 - k) + k);
1105 return [r * 255, g * 255, b * 255];
1106 }
1107
1108 function cmyk2hsl(args) {
1109 return rgb2hsl(cmyk2rgb(args));
1110 }
1111
1112 function cmyk2hsv(args) {
1113 return rgb2hsv(cmyk2rgb(args));
1114 }
1115
1116 function cmyk2hwb(args) {
1117 return rgb2hwb(cmyk2rgb(args));
1118 }
1119
1120 function cmyk2keyword(args) {
1121 return rgb2keyword(cmyk2rgb(args));
1122 }
1123
1124
1125 function xyz2rgb(xyz) {
1126 var x = xyz[0] / 100,
1127 y = xyz[1] / 100,
1128 z = xyz[2] / 100,
1129 r, g, b;
1130
1131 r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
1132 g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
1133 b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
1134
1135 // assume sRGB
1136 r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
1137 : r = (r * 12.92);
1138
1139 g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
1140 : g = (g * 12.92);
1141
1142 b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
1143 : b = (b * 12.92);
1144
1145 r = Math.min(Math.max(0, r), 1);
1146 g = Math.min(Math.max(0, g), 1);
1147 b = Math.min(Math.max(0, b), 1);
1148
1149 return [r * 255, g * 255, b * 255];
1150 }
1151
1152 function xyz2lab(xyz) {
1153 var x = xyz[0],
1154 y = xyz[1],
1155 z = xyz[2],
1156 l, a, b;
1157
1158 x /= 95.047;
1159 y /= 100;
1160 z /= 108.883;
1161
1162 x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
1163 y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
1164 z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
1165
1166 l = (116 * y) - 16;
1167 a = 500 * (x - y);
1168 b = 200 * (y - z);
1169
1170 return [l, a, b];
1171 }
1172
1173 function xyz2lch(args) {
1174 return lab2lch(xyz2lab(args));
1175 }
1176
1177 function lab2xyz(lab) {
1178 var l = lab[0],
1179 a = lab[1],
1180 b = lab[2],
1181 x, y, z, y2;
1182
1183 if (l <= 8) {
1184 y = (l * 100) / 903.3;
1185 y2 = (7.787 * (y / 100)) + (16 / 116);
1186 } else {
1187 y = 100 * Math.pow((l + 16) / 116, 3);
1188 y2 = Math.pow(y / 100, 1/3);
1189 }
1190
1191 x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
1192
1193 z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
1194
1195 return [x, y, z];
1196 }
1197
1198 function lab2lch(lab) {
1199 var l = lab[0],
1200 a = lab[1],
1201 b = lab[2],
1202 hr, h, c;
1203
1204 hr = Math.atan2(b, a);
1205 h = hr * 360 / 2 / Math.PI;
1206 if (h < 0) {
1207 h += 360;
1208 }
1209 c = Math.sqrt(a * a + b * b);
1210 return [l, c, h];
1211 }
1212
1213 function lab2rgb(args) {
1214 return xyz2rgb(lab2xyz(args));
1215 }
1216
1217 function lch2lab(lch) {
1218 var l = lch[0],
1219 c = lch[1],
1220 h = lch[2],
1221 a, b, hr;
1222
1223 hr = h / 360 * 2 * Math.PI;
1224 a = c * Math.cos(hr);
1225 b = c * Math.sin(hr);
1226 return [l, a, b];
1227 }
1228
1229 function lch2xyz(args) {
1230 return lab2xyz(lch2lab(args));
1231 }
1232
1233 function lch2rgb(args) {
1234 return lab2rgb(lch2lab(args));
1235 }
1236
1237 function keyword2rgb(keyword) {
1238 return cssKeywords[keyword];
1239 }
1240
1241 function keyword2hsl(args) {
1242 return rgb2hsl(keyword2rgb(args));
1243 }
1244
1245 function keyword2hsv(args) {
1246 return rgb2hsv(keyword2rgb(args));
1247 }
1248
1249 function keyword2hwb(args) {
1250 return rgb2hwb(keyword2rgb(args));
1251 }
1252
1253 function keyword2cmyk(args) {
1254 return rgb2cmyk(keyword2rgb(args));
1255 }
1256
1257 function keyword2lab(args) {
1258 return rgb2lab(keyword2rgb(args));
1259 }
1260
1261 function keyword2xyz(args) {
1262 return rgb2xyz(keyword2rgb(args));
1263 }
1264
1265 var cssKeywords = {
1266 aliceblue: [240,248,255],
1267 antiquewhite: [250,235,215],
1268 aqua: [0,255,255],
1269 aquamarine: [127,255,212],
1270 azure: [240,255,255],
1271 beige: [245,245,220],
1272 bisque: [255,228,196],
1273 black: [0,0,0],
1274 blanchedalmond: [255,235,205],
1275 blue: [0,0,255],
1276 blueviolet: [138,43,226],
1277 brown: [165,42,42],
1278 burlywood: [222,184,135],
1279 cadetblue: [95,158,160],
1280 chartreuse: [127,255,0],
1281 chocolate: [210,105,30],
1282 coral: [255,127,80],
1283 cornflowerblue: [100,149,237],
1284 cornsilk: [255,248,220],
1285 crimson: [220,20,60],
1286 cyan: [0,255,255],
1287 darkblue: [0,0,139],
1288 darkcyan: [0,139,139],
1289 darkgoldenrod: [184,134,11],
1290 darkgray: [169,169,169],
1291 darkgreen: [0,100,0],
1292 darkgrey: [169,169,169],
1293 darkkhaki: [189,183,107],
1294 darkmagenta: [139,0,139],
1295 darkolivegreen: [85,107,47],
1296 darkorange: [255,140,0],
1297 darkorchid: [153,50,204],
1298 darkred: [139,0,0],
1299 darksalmon: [233,150,122],
1300 darkseagreen: [143,188,143],
1301 darkslateblue: [72,61,139],
1302 darkslategray: [47,79,79],
1303 darkslategrey: [47,79,79],
1304 darkturquoise: [0,206,209],
1305 darkviolet: [148,0,211],
1306 deeppink: [255,20,147],
1307 deepskyblue: [0,191,255],
1308 dimgray: [105,105,105],
1309 dimgrey: [105,105,105],
1310 dodgerblue: [30,144,255],
1311 firebrick: [178,34,34],
1312 floralwhite: [255,250,240],
1313 forestgreen: [34,139,34],
1314 fuchsia: [255,0,255],
1315 gainsboro: [220,220,220],
1316 ghostwhite: [248,248,255],
1317 gold: [255,215,0],
1318 goldenrod: [218,165,32],
1319 gray: [128,128,128],
1320 green: [0,128,0],
1321 greenyellow: [173,255,47],
1322 grey: [128,128,128],
1323 honeydew: [240,255,240],
1324 hotpink: [255,105,180],
1325 indianred: [205,92,92],
1326 indigo: [75,0,130],
1327 ivory: [255,255,240],
1328 khaki: [240,230,140],
1329 lavender: [230,230,250],
1330 lavenderblush: [255,240,245],
1331 lawngreen: [124,252,0],
1332 lemonchiffon: [255,250,205],
1333 lightblue: [173,216,230],
1334 lightcoral: [240,128,128],
1335 lightcyan: [224,255,255],
1336 lightgoldenrodyellow: [250,250,210],
1337 lightgray: [211,211,211],
1338 lightgreen: [144,238,144],
1339 lightgrey: [211,211,211],
1340 lightpink: [255,182,193],
1341 lightsalmon: [255,160,122],
1342 lightseagreen: [32,178,170],
1343 lightskyblue: [135,206,250],
1344 lightslategray: [119,136,153],
1345 lightslategrey: [119,136,153],
1346 lightsteelblue: [176,196,222],
1347 lightyellow: [255,255,224],
1348 lime: [0,255,0],
1349 limegreen: [50,205,50],
1350 linen: [250,240,230],
1351 magenta: [255,0,255],
1352 maroon: [128,0,0],
1353 mediumaquamarine: [102,205,170],
1354 mediumblue: [0,0,205],
1355 mediumorchid: [186,85,211],
1356 mediumpurple: [147,112,219],
1357 mediumseagreen: [60,179,113],
1358 mediumslateblue: [123,104,238],
1359 mediumspringgreen: [0,250,154],
1360 mediumturquoise: [72,209,204],
1361 mediumvioletred: [199,21,133],
1362 midnightblue: [25,25,112],
1363 mintcream: [245,255,250],
1364 mistyrose: [255,228,225],
1365 moccasin: [255,228,181],
1366 navajowhite: [255,222,173],
1367 navy: [0,0,128],
1368 oldlace: [253,245,230],
1369 olive: [128,128,0],
1370 olivedrab: [107,142,35],
1371 orange: [255,165,0],
1372 orangered: [255,69,0],
1373 orchid: [218,112,214],
1374 palegoldenrod: [238,232,170],
1375 palegreen: [152,251,152],
1376 paleturquoise: [175,238,238],
1377 palevioletred: [219,112,147],
1378 papayawhip: [255,239,213],
1379 peachpuff: [255,218,185],
1380 peru: [205,133,63],
1381 pink: [255,192,203],
1382 plum: [221,160,221],
1383 powderblue: [176,224,230],
1384 purple: [128,0,128],
1385 rebeccapurple: [102, 51, 153],
1386 red: [255,0,0],
1387 rosybrown: [188,143,143],
1388 royalblue: [65,105,225],
1389 saddlebrown: [139,69,19],
1390 salmon: [250,128,114],
1391 sandybrown: [244,164,96],
1392 seagreen: [46,139,87],
1393 seashell: [255,245,238],
1394 sienna: [160,82,45],
1395 silver: [192,192,192],
1396 skyblue: [135,206,235],
1397 slateblue: [106,90,205],
1398 slategray: [112,128,144],
1399 slategrey: [112,128,144],
1400 snow: [255,250,250],
1401 springgreen: [0,255,127],
1402 steelblue: [70,130,180],
1403 tan: [210,180,140],
1404 teal: [0,128,128],
1405 thistle: [216,191,216],
1406 tomato: [255,99,71],
1407 turquoise: [64,224,208],
1408 violet: [238,130,238],
1409 wheat: [245,222,179],
1410 white: [255,255,255],
1411 whitesmoke: [245,245,245],
1412 yellow: [255,255,0],
1413 yellowgreen: [154,205,50]
1414 };
1415
1416 var reverseKeywords = {};
1417 for (var key in cssKeywords) {
1418 reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
1419 }
1420
1421 },{}],5:[function(require,module,exports){
1422 var conversions = require(4);
1423
1424 var convert = function() {
1425 return new Converter();
1426 }
1427
1428 for (var func in conversions) {
1429 // export Raw versions
1430 convert[func + "Raw"] = (function(func) {
1431 // accept array or plain args
1432 return function(arg) {
1433 if (typeof arg == "number")
1434 arg = Array.prototype.slice.call(arguments);
1435 return conversions[func](arg);
1436 }
1437 })(func);
1438
1439 var pair = /(\w+)2(\w+)/.exec(func),
1440 from = pair[1],
1441 to = pair[2];
1442
1443 // export rgb2hsl and ["rgb"]["hsl"]
1444 convert[from] = convert[from] || {};
1445
1446 convert[from][to] = convert[func] = (function(func) {
1447 return function(arg) {
1448 if (typeof arg == "number")
1449 arg = Array.prototype.slice.call(arguments);
1450
1451 var val = conversions[func](arg);
1452 if (typeof val == "string" || val === undefined)
1453 return val; // keyword
1454
1455 for (var i = 0; i < val.length; i++)
1456 val[i] = Math.round(val[i]);
1457 return val;
1458 }
1459 })(func);
1460 }
1461
1462
1463 /* Converter does lazy conversion and caching */
1464 var Converter = function() {
1465 this.convs = {};
1466 };
1467
1468 /* Either get the values for a space or
1469 set the values for a space, depending on args */
1470 Converter.prototype.routeSpace = function(space, args) {
1471 var values = args[0];
1472 if (values === undefined) {
1473 // color.rgb()
1474 return this.getValues(space);
1475 }
1476 // color.rgb(10, 10, 10)
1477 if (typeof values == "number") {
1478 values = Array.prototype.slice.call(args);
1479 }
1480
1481 return this.setValues(space, values);
1482 };
1483
1484 /* Set the values for a space, invalidating cache */
1485 Converter.prototype.setValues = function(space, values) {
1486 this.space = space;
1487 this.convs = {};
1488 this.convs[space] = values;
1489 return this;
1490 };
1491
1492 /* Get the values for a space. If there's already
1493 a conversion for the space, fetch it, otherwise
1494 compute it */
1495 Converter.prototype.getValues = function(space) {
1496 var vals = this.convs[space];
1497 if (!vals) {
1498 var fspace = this.space,
1499 from = this.convs[fspace];
1500 vals = convert[fspace][space](from);
1501
1502 this.convs[space] = vals;
1503 }
1504 return vals;
1505 };
1506
1507 ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
1508 Converter.prototype[space] = function(vals) {
1509 return this.routeSpace(space, arguments);
1510 }
1511 });
1512
1513 module.exports = convert;
1514 },{"4":4}],6:[function(require,module,exports){
1515 'use strict'
1516
1517 module.exports = {
1518 "aliceblue": [240, 248, 255],
1519 "antiquewhite": [250, 235, 215],
1520 "aqua": [0, 255, 255],
1521 "aquamarine": [127, 255, 212],
1522 "azure": [240, 255, 255],
1523 "beige": [245, 245, 220],
1524 "bisque": [255, 228, 196],
1525 "black": [0, 0, 0],
1526 "blanchedalmond": [255, 235, 205],
1527 "blue": [0, 0, 255],
1528 "blueviolet": [138, 43, 226],
1529 "brown": [165, 42, 42],
1530 "burlywood": [222, 184, 135],
1531 "cadetblue": [95, 158, 160],
1532 "chartreuse": [127, 255, 0],
1533 "chocolate": [210, 105, 30],
1534 "coral": [255, 127, 80],
1535 "cornflowerblue": [100, 149, 237],
1536 "cornsilk": [255, 248, 220],
1537 "crimson": [220, 20, 60],
1538 "cyan": [0, 255, 255],
1539 "darkblue": [0, 0, 139],
1540 "darkcyan": [0, 139, 139],
1541 "darkgoldenrod": [184, 134, 11],
1542 "darkgray": [169, 169, 169],
1543 "darkgreen": [0, 100, 0],
1544 "darkgrey": [169, 169, 169],
1545 "darkkhaki": [189, 183, 107],
1546 "darkmagenta": [139, 0, 139],
1547 "darkolivegreen": [85, 107, 47],
1548 "darkorange": [255, 140, 0],
1549 "darkorchid": [153, 50, 204],
1550 "darkred": [139, 0, 0],
1551 "darksalmon": [233, 150, 122],
1552 "darkseagreen": [143, 188, 143],
1553 "darkslateblue": [72, 61, 139],
1554 "darkslategray": [47, 79, 79],
1555 "darkslategrey": [47, 79, 79],
1556 "darkturquoise": [0, 206, 209],
1557 "darkviolet": [148, 0, 211],
1558 "deeppink": [255, 20, 147],
1559 "deepskyblue": [0, 191, 255],
1560 "dimgray": [105, 105, 105],
1561 "dimgrey": [105, 105, 105],
1562 "dodgerblue": [30, 144, 255],
1563 "firebrick": [178, 34, 34],
1564 "floralwhite": [255, 250, 240],
1565 "forestgreen": [34, 139, 34],
1566 "fuchsia": [255, 0, 255],
1567 "gainsboro": [220, 220, 220],
1568 "ghostwhite": [248, 248, 255],
1569 "gold": [255, 215, 0],
1570 "goldenrod": [218, 165, 32],
1571 "gray": [128, 128, 128],
1572 "green": [0, 128, 0],
1573 "greenyellow": [173, 255, 47],
1574 "grey": [128, 128, 128],
1575 "honeydew": [240, 255, 240],
1576 "hotpink": [255, 105, 180],
1577 "indianred": [205, 92, 92],
1578 "indigo": [75, 0, 130],
1579 "ivory": [255, 255, 240],
1580 "khaki": [240, 230, 140],
1581 "lavender": [230, 230, 250],
1582 "lavenderblush": [255, 240, 245],
1583 "lawngreen": [124, 252, 0],
1584 "lemonchiffon": [255, 250, 205],
1585 "lightblue": [173, 216, 230],
1586 "lightcoral": [240, 128, 128],
1587 "lightcyan": [224, 255, 255],
1588 "lightgoldenrodyellow": [250, 250, 210],
1589 "lightgray": [211, 211, 211],
1590 "lightgreen": [144, 238, 144],
1591 "lightgrey": [211, 211, 211],
1592 "lightpink": [255, 182, 193],
1593 "lightsalmon": [255, 160, 122],
1594 "lightseagreen": [32, 178, 170],
1595 "lightskyblue": [135, 206, 250],
1596 "lightslategray": [119, 136, 153],
1597 "lightslategrey": [119, 136, 153],
1598 "lightsteelblue": [176, 196, 222],
1599 "lightyellow": [255, 255, 224],
1600 "lime": [0, 255, 0],
1601 "limegreen": [50, 205, 50],
1602 "linen": [250, 240, 230],
1603 "magenta": [255, 0, 255],
1604 "maroon": [128, 0, 0],
1605 "mediumaquamarine": [102, 205, 170],
1606 "mediumblue": [0, 0, 205],
1607 "mediumorchid": [186, 85, 211],
1608 "mediumpurple": [147, 112, 219],
1609 "mediumseagreen": [60, 179, 113],
1610 "mediumslateblue": [123, 104, 238],
1611 "mediumspringgreen": [0, 250, 154],
1612 "mediumturquoise": [72, 209, 204],
1613 "mediumvioletred": [199, 21, 133],
1614 "midnightblue": [25, 25, 112],
1615 "mintcream": [245, 255, 250],
1616 "mistyrose": [255, 228, 225],
1617 "moccasin": [255, 228, 181],
1618 "navajowhite": [255, 222, 173],
1619 "navy": [0, 0, 128],
1620 "oldlace": [253, 245, 230],
1621 "olive": [128, 128, 0],
1622 "olivedrab": [107, 142, 35],
1623 "orange": [255, 165, 0],
1624 "orangered": [255, 69, 0],
1625 "orchid": [218, 112, 214],
1626 "palegoldenrod": [238, 232, 170],
1627 "palegreen": [152, 251, 152],
1628 "paleturquoise": [175, 238, 238],
1629 "palevioletred": [219, 112, 147],
1630 "papayawhip": [255, 239, 213],
1631 "peachpuff": [255, 218, 185],
1632 "peru": [205, 133, 63],
1633 "pink": [255, 192, 203],
1634 "plum": [221, 160, 221],
1635 "powderblue": [176, 224, 230],
1636 "purple": [128, 0, 128],
1637 "rebeccapurple": [102, 51, 153],
1638 "red": [255, 0, 0],
1639 "rosybrown": [188, 143, 143],
1640 "royalblue": [65, 105, 225],
1641 "saddlebrown": [139, 69, 19],
1642 "salmon": [250, 128, 114],
1643 "sandybrown": [244, 164, 96],
1644 "seagreen": [46, 139, 87],
1645 "seashell": [255, 245, 238],
1646 "sienna": [160, 82, 45],
1647 "silver": [192, 192, 192],
1648 "skyblue": [135, 206, 235],
1649 "slateblue": [106, 90, 205],
1650 "slategray": [112, 128, 144],
1651 "slategrey": [112, 128, 144],
1652 "snow": [255, 250, 250],
1653 "springgreen": [0, 255, 127],
1654 "steelblue": [70, 130, 180],
1655 "tan": [210, 180, 140],
1656 "teal": [0, 128, 128],
1657 "thistle": [216, 191, 216],
1658 "tomato": [255, 99, 71],
1659 "turquoise": [64, 224, 208],
1660 "violet": [238, 130, 238],
1661 "wheat": [245, 222, 179],
1662 "white": [255, 255, 255],
1663 "whitesmoke": [245, 245, 245],
1664 "yellow": [255, 255, 0],
1665 "yellowgreen": [154, 205, 50]
1666 };
1667
1668 },{}],7:[function(require,module,exports){
1669 /**
1670 * @namespace Chart
1671 */
1672 var Chart = require(29)();
1673
1674 Chart.helpers = require(45);
1675
1676 // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
1677 require(27)(Chart);
1678
1679 Chart.defaults = require(25);
1680 Chart.Element = require(26);
1681 Chart.elements = require(40);
1682 Chart.Interaction = require(28);
1683 Chart.platform = require(48);
1684
1685 require(31)(Chart);
1686 require(22)(Chart);
1687 require(23)(Chart);
1688 require(24)(Chart);
1689 require(30)(Chart);
1690 require(33)(Chart);
1691 require(32)(Chart);
1692 require(35)(Chart);
1693
1694 require(54)(Chart);
1695 require(52)(Chart);
1696 require(53)(Chart);
1697 require(55)(Chart);
1698 require(56)(Chart);
1699 require(57)(Chart);
1700
1701 // Controllers must be loaded after elements
1702 // See Chart.core.datasetController.dataElementType
1703 require(15)(Chart);
1704 require(16)(Chart);
1705 require(17)(Chart);
1706 require(18)(Chart);
1707 require(19)(Chart);
1708 require(20)(Chart);
1709 require(21)(Chart);
1710
1711 require(8)(Chart);
1712 require(9)(Chart);
1713 require(10)(Chart);
1714 require(11)(Chart);
1715 require(12)(Chart);
1716 require(13)(Chart);
1717 require(14)(Chart);
1718
1719 // Loading built-it plugins
1720 var plugins = [];
1721
1722 plugins.push(
1723 require(49)(Chart),
1724 require(50)(Chart),
1725 require(51)(Chart)
1726 );
1727
1728 Chart.plugins.register(plugins);
1729
1730 Chart.platform.initialize();
1731
1732 module.exports = Chart;
1733 if (typeof window !== 'undefined') {
1734 window.Chart = Chart;
1735 }
1736
1737 // DEPRECATIONS
1738
1739 /**
1740 * Provided for backward compatibility, use Chart.helpers.canvas instead.
1741 * @namespace Chart.canvasHelpers
1742 * @deprecated since version 2.6.0
1743 * @todo remove at version 3
1744 * @private
1745 */
1746 Chart.canvasHelpers = Chart.helpers.canvas;
1747
1748 },{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"35":35,"40":40,"45":45,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"8":8,"9":9}],8:[function(require,module,exports){
1749 'use strict';
1750
1751 module.exports = function(Chart) {
1752
1753 Chart.Bar = function(context, config) {
1754 config.type = 'bar';
1755
1756 return new Chart(context, config);
1757 };
1758
1759 };
1760
1761 },{}],9:[function(require,module,exports){
1762 'use strict';
1763
1764 module.exports = function(Chart) {
1765
1766 Chart.Bubble = function(context, config) {
1767 config.type = 'bubble';
1768 return new Chart(context, config);
1769 };
1770
1771 };
1772
1773 },{}],10:[function(require,module,exports){
1774 'use strict';
1775
1776 module.exports = function(Chart) {
1777
1778 Chart.Doughnut = function(context, config) {
1779 config.type = 'doughnut';
1780
1781 return new Chart(context, config);
1782 };
1783
1784 };
1785
1786 },{}],11:[function(require,module,exports){
1787 'use strict';
1788
1789 module.exports = function(Chart) {
1790
1791 Chart.Line = function(context, config) {
1792 config.type = 'line';
1793
1794 return new Chart(context, config);
1795 };
1796
1797 };
1798
1799 },{}],12:[function(require,module,exports){
1800 'use strict';
1801
1802 module.exports = function(Chart) {
1803
1804 Chart.PolarArea = function(context, config) {
1805 config.type = 'polarArea';
1806
1807 return new Chart(context, config);
1808 };
1809
1810 };
1811
1812 },{}],13:[function(require,module,exports){
1813 'use strict';
1814
1815 module.exports = function(Chart) {
1816
1817 Chart.Radar = function(context, config) {
1818 config.type = 'radar';
1819
1820 return new Chart(context, config);
1821 };
1822
1823 };
1824
1825 },{}],14:[function(require,module,exports){
1826 'use strict';
1827
1828 module.exports = function(Chart) {
1829 Chart.Scatter = function(context, config) {
1830 config.type = 'scatter';
1831 return new Chart(context, config);
1832 };
1833 };
1834
1835 },{}],15:[function(require,module,exports){
1836 'use strict';
1837
1838 var defaults = require(25);
1839 var elements = require(40);
1840 var helpers = require(45);
1841
1842 defaults._set('bar', {
1843 hover: {
1844 mode: 'label'
1845 },
1846
1847 scales: {
1848 xAxes: [{
1849 type: 'category',
1850
1851 // Specific to Bar Controller
1852 categoryPercentage: 0.8,
1853 barPercentage: 0.9,
1854
1855 // offset settings
1856 offset: true,
1857
1858 // grid line settings
1859 gridLines: {
1860 offsetGridLines: true
1861 }
1862 }],
1863
1864 yAxes: [{
1865 type: 'linear'
1866 }]
1867 }
1868 });
1869
1870 defaults._set('horizontalBar', {
1871 hover: {
1872 mode: 'index',
1873 axis: 'y'
1874 },
1875
1876 scales: {
1877 xAxes: [{
1878 type: 'linear',
1879 position: 'bottom'
1880 }],
1881
1882 yAxes: [{
1883 position: 'left',
1884 type: 'category',
1885
1886 // Specific to Horizontal Bar Controller
1887 categoryPercentage: 0.8,
1888 barPercentage: 0.9,
1889
1890 // offset settings
1891 offset: true,
1892
1893 // grid line settings
1894 gridLines: {
1895 offsetGridLines: true
1896 }
1897 }]
1898 },
1899
1900 elements: {
1901 rectangle: {
1902 borderSkipped: 'left'
1903 }
1904 },
1905
1906 tooltips: {
1907 callbacks: {
1908 title: function(item, data) {
1909 // Pick first xLabel for now
1910 var title = '';
1911
1912 if (item.length > 0) {
1913 if (item[0].yLabel) {
1914 title = item[0].yLabel;
1915 } else if (data.labels.length > 0 && item[0].index < data.labels.length) {
1916 title = data.labels[item[0].index];
1917 }
1918 }
1919
1920 return title;
1921 },
1922
1923 label: function(item, data) {
1924 var datasetLabel = data.datasets[item.datasetIndex].label || '';
1925 return datasetLabel + ': ' + item.xLabel;
1926 }
1927 },
1928 mode: 'index',
1929 axis: 'y'
1930 }
1931 });
1932
1933 module.exports = function(Chart) {
1934
1935 Chart.controllers.bar = Chart.DatasetController.extend({
1936
1937 dataElementType: elements.Rectangle,
1938
1939 initialize: function() {
1940 var me = this;
1941 var meta;
1942
1943 Chart.DatasetController.prototype.initialize.apply(me, arguments);
1944
1945 meta = me.getMeta();
1946 meta.stack = me.getDataset().stack;
1947 meta.bar = true;
1948 },
1949
1950 update: function(reset) {
1951 var me = this;
1952 var rects = me.getMeta().data;
1953 var i, ilen;
1954
1955 me._ruler = me.getRuler();
1956
1957 for (i = 0, ilen = rects.length; i < ilen; ++i) {
1958 me.updateElement(rects[i], i, reset);
1959 }
1960 },
1961
1962 updateElement: function(rectangle, index, reset) {
1963 var me = this;
1964 var chart = me.chart;
1965 var meta = me.getMeta();
1966 var dataset = me.getDataset();
1967 var custom = rectangle.custom || {};
1968 var rectangleOptions = chart.options.elements.rectangle;
1969
1970 rectangle._xScale = me.getScaleForId(meta.xAxisID);
1971 rectangle._yScale = me.getScaleForId(meta.yAxisID);
1972 rectangle._datasetIndex = me.index;
1973 rectangle._index = index;
1974
1975 rectangle._model = {
1976 datasetLabel: dataset.label,
1977 label: chart.data.labels[index],
1978 borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped,
1979 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor),
1980 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor),
1981 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth)
1982 };
1983
1984 me.updateElementGeometry(rectangle, index, reset);
1985
1986 rectangle.pivot();
1987 },
1988
1989 /**
1990 * @private
1991 */
1992 updateElementGeometry: function(rectangle, index, reset) {
1993 var me = this;
1994 var model = rectangle._model;
1995 var vscale = me.getValueScale();
1996 var base = vscale.getBasePixel();
1997 var horizontal = vscale.isHorizontal();
1998 var ruler = me._ruler || me.getRuler();
1999 var vpixels = me.calculateBarValuePixels(me.index, index);
2000 var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
2001
2002 model.horizontal = horizontal;
2003 model.base = reset ? base : vpixels.base;
2004 model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
2005 model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
2006 model.height = horizontal ? ipixels.size : undefined;
2007 model.width = horizontal ? undefined : ipixels.size;
2008 },
2009
2010 /**
2011 * @private
2012 */
2013 getValueScaleId: function() {
2014 return this.getMeta().yAxisID;
2015 },
2016
2017 /**
2018 * @private
2019 */
2020 getIndexScaleId: function() {
2021 return this.getMeta().xAxisID;
2022 },
2023
2024 /**
2025 * @private
2026 */
2027 getValueScale: function() {
2028 return this.getScaleForId(this.getValueScaleId());
2029 },
2030
2031 /**
2032 * @private
2033 */
2034 getIndexScale: function() {
2035 return this.getScaleForId(this.getIndexScaleId());
2036 },
2037
2038 /**
2039 * Returns the effective number of stacks based on groups and bar visibility.
2040 * @private
2041 */
2042 getStackCount: function(last) {
2043 var me = this;
2044 var chart = me.chart;
2045 var scale = me.getIndexScale();
2046 var stacked = scale.options.stacked;
2047 var ilen = last === undefined ? chart.data.datasets.length : last + 1;
2048 var stacks = [];
2049 var i, meta;
2050
2051 for (i = 0; i < ilen; ++i) {
2052 meta = chart.getDatasetMeta(i);
2053 if (meta.bar && chart.isDatasetVisible(i) &&
2054 (stacked === false ||
2055 (stacked === true && stacks.indexOf(meta.stack) === -1) ||
2056 (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
2057 stacks.push(meta.stack);
2058 }
2059 }
2060
2061 return stacks.length;
2062 },
2063
2064 /**
2065 * Returns the stack index for the given dataset based on groups and bar visibility.
2066 * @private
2067 */
2068 getStackIndex: function(datasetIndex) {
2069 return this.getStackCount(datasetIndex) - 1;
2070 },
2071
2072 /**
2073 * @private
2074 */
2075 getRuler: function() {
2076 var me = this;
2077 var scale = me.getIndexScale();
2078 var stackCount = me.getStackCount();
2079 var datasetIndex = me.index;
2080 var pixels = [];
2081 var isHorizontal = scale.isHorizontal();
2082 var start = isHorizontal ? scale.left : scale.top;
2083 var end = start + (isHorizontal ? scale.width : scale.height);
2084 var i, ilen;
2085
2086 for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
2087 pixels.push(scale.getPixelForValue(null, i, datasetIndex));
2088 }
2089
2090 return {
2091 pixels: pixels,
2092 start: start,
2093 end: end,
2094 stackCount: stackCount,
2095 scale: scale
2096 };
2097 },
2098
2099 /**
2100 * Note: pixel values are not clamped to the scale area.
2101 * @private
2102 */
2103 calculateBarValuePixels: function(datasetIndex, index) {
2104 var me = this;
2105 var chart = me.chart;
2106 var meta = me.getMeta();
2107 var scale = me.getValueScale();
2108 var datasets = chart.data.datasets;
2109 var value = scale.getRightValue(datasets[datasetIndex].data[index]);
2110 var stacked = scale.options.stacked;
2111 var stack = meta.stack;
2112 var start = 0;
2113 var i, imeta, ivalue, base, head, size;
2114
2115 if (stacked || (stacked === undefined && stack !== undefined)) {
2116 for (i = 0; i < datasetIndex; ++i) {
2117 imeta = chart.getDatasetMeta(i);
2118
2119 if (imeta.bar &&
2120 imeta.stack === stack &&
2121 imeta.controller.getValueScaleId() === scale.id &&
2122 chart.isDatasetVisible(i)) {
2123
2124 ivalue = scale.getRightValue(datasets[i].data[index]);
2125 if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
2126 start += ivalue;
2127 }
2128 }
2129 }
2130 }
2131
2132 base = scale.getPixelForValue(start);
2133 head = scale.getPixelForValue(start + value);
2134 size = (head - base) / 2;
2135
2136 return {
2137 size: size,
2138 base: base,
2139 head: head,
2140 center: head + size / 2
2141 };
2142 },
2143
2144 /**
2145 * @private
2146 */
2147 calculateBarIndexPixels: function(datasetIndex, index, ruler) {
2148 var me = this;
2149 var options = ruler.scale.options;
2150 var stackIndex = me.getStackIndex(datasetIndex);
2151 var pixels = ruler.pixels;
2152 var base = pixels[index];
2153 var length = pixels.length;
2154 var start = ruler.start;
2155 var end = ruler.end;
2156 var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size;
2157
2158 if (length === 1) {
2159 leftSampleSize = base > start ? base - start : end - base;
2160 rightSampleSize = base < end ? end - base : base - start;
2161 } else {
2162 if (index > 0) {
2163 leftSampleSize = (base - pixels[index - 1]) / 2;
2164 if (index === length - 1) {
2165 rightSampleSize = leftSampleSize;
2166 }
2167 }
2168 if (index < length - 1) {
2169 rightSampleSize = (pixels[index + 1] - base) / 2;
2170 if (index === 0) {
2171 leftSampleSize = rightSampleSize;
2172 }
2173 }
2174 }
2175
2176 leftCategorySize = leftSampleSize * options.categoryPercentage;
2177 rightCategorySize = rightSampleSize * options.categoryPercentage;
2178 fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount;
2179 size = fullBarSize * options.barPercentage;
2180
2181 size = Math.min(
2182 helpers.valueOrDefault(options.barThickness, size),
2183 helpers.valueOrDefault(options.maxBarThickness, Infinity));
2184
2185 base -= leftCategorySize;
2186 base += fullBarSize * stackIndex;
2187 base += (fullBarSize - size) / 2;
2188
2189 return {
2190 size: size,
2191 base: base,
2192 head: base + size,
2193 center: base + size / 2
2194 };
2195 },
2196
2197 draw: function() {
2198 var me = this;
2199 var chart = me.chart;
2200 var scale = me.getValueScale();
2201 var rects = me.getMeta().data;
2202 var dataset = me.getDataset();
2203 var ilen = rects.length;
2204 var i = 0;
2205
2206 helpers.canvas.clipArea(chart.ctx, chart.chartArea);
2207
2208 for (; i < ilen; ++i) {
2209 if (!isNaN(scale.getRightValue(dataset.data[i]))) {
2210 rects[i].draw();
2211 }
2212 }
2213
2214 helpers.canvas.unclipArea(chart.ctx);
2215 },
2216
2217 setHoverStyle: function(rectangle) {
2218 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2219 var index = rectangle._index;
2220 var custom = rectangle.custom || {};
2221 var model = rectangle._model;
2222
2223 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
2224 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
2225 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
2226 },
2227
2228 removeHoverStyle: function(rectangle) {
2229 var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2230 var index = rectangle._index;
2231 var custom = rectangle.custom || {};
2232 var model = rectangle._model;
2233 var rectangleElementOptions = this.chart.options.elements.rectangle;
2234
2235 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
2236 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
2237 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
2238 }
2239 });
2240
2241 Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
2242 /**
2243 * @private
2244 */
2245 getValueScaleId: function() {
2246 return this.getMeta().xAxisID;
2247 },
2248
2249 /**
2250 * @private
2251 */
2252 getIndexScaleId: function() {
2253 return this.getMeta().yAxisID;
2254 }
2255 });
2256 };
2257
2258 },{"25":25,"40":40,"45":45}],16:[function(require,module,exports){
2259 'use strict';
2260
2261 var defaults = require(25);
2262 var elements = require(40);
2263 var helpers = require(45);
2264
2265 defaults._set('bubble', {
2266 hover: {
2267 mode: 'single'
2268 },
2269
2270 scales: {
2271 xAxes: [{
2272 type: 'linear', // bubble should probably use a linear scale by default
2273 position: 'bottom',
2274 id: 'x-axis-0' // need an ID so datasets can reference the scale
2275 }],
2276 yAxes: [{
2277 type: 'linear',
2278 position: 'left',
2279 id: 'y-axis-0'
2280 }]
2281 },
2282
2283 tooltips: {
2284 callbacks: {
2285 title: function() {
2286 // Title doesn't make sense for scatter since we format the data as a point
2287 return '';
2288 },
2289 label: function(item, data) {
2290 var datasetLabel = data.datasets[item.datasetIndex].label || '';
2291 var dataPoint = data.datasets[item.datasetIndex].data[item.index];
2292 return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
2293 }
2294 }
2295 }
2296 });
2297
2298
2299 module.exports = function(Chart) {
2300
2301 Chart.controllers.bubble = Chart.DatasetController.extend({
2302 /**
2303 * @protected
2304 */
2305 dataElementType: elements.Point,
2306
2307 /**
2308 * @protected
2309 */
2310 update: function(reset) {
2311 var me = this;
2312 var meta = me.getMeta();
2313 var points = meta.data;
2314
2315 // Update Points
2316 helpers.each(points, function(point, index) {
2317 me.updateElement(point, index, reset);
2318 });
2319 },
2320
2321 /**
2322 * @protected
2323 */
2324 updateElement: function(point, index, reset) {
2325 var me = this;
2326 var meta = me.getMeta();
2327 var custom = point.custom || {};
2328 var xScale = me.getScaleForId(meta.xAxisID);
2329 var yScale = me.getScaleForId(meta.yAxisID);
2330 var options = me._resolveElementOptions(point, index);
2331 var data = me.getDataset().data[index];
2332 var dsIndex = me.index;
2333
2334 var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
2335 var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
2336
2337 point._xScale = xScale;
2338 point._yScale = yScale;
2339 point._options = options;
2340 point._datasetIndex = dsIndex;
2341 point._index = index;
2342 point._model = {
2343 backgroundColor: options.backgroundColor,
2344 borderColor: options.borderColor,
2345 borderWidth: options.borderWidth,
2346 hitRadius: options.hitRadius,
2347 pointStyle: options.pointStyle,
2348 radius: reset ? 0 : options.radius,
2349 skip: custom.skip || isNaN(x) || isNaN(y),
2350 x: x,
2351 y: y,
2352 };
2353
2354 point.pivot();
2355 },
2356
2357 /**
2358 * @protected
2359 */
2360 setHoverStyle: function(point) {
2361 var model = point._model;
2362 var options = point._options;
2363
2364 model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
2365 model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
2366 model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
2367 model.radius = options.radius + options.hoverRadius;
2368 },
2369
2370 /**
2371 * @protected
2372 */
2373 removeHoverStyle: function(point) {
2374 var model = point._model;
2375 var options = point._options;
2376
2377 model.backgroundColor = options.backgroundColor;
2378 model.borderColor = options.borderColor;
2379 model.borderWidth = options.borderWidth;
2380 model.radius = options.radius;
2381 },
2382
2383 /**
2384 * @private
2385 */
2386 _resolveElementOptions: function(point, index) {
2387 var me = this;
2388 var chart = me.chart;
2389 var datasets = chart.data.datasets;
2390 var dataset = datasets[me.index];
2391 var custom = point.custom || {};
2392 var options = chart.options.elements.point;
2393 var resolve = helpers.options.resolve;
2394 var data = dataset.data[index];
2395 var values = {};
2396 var i, ilen, key;
2397
2398 // Scriptable options
2399 var context = {
2400 chart: chart,
2401 dataIndex: index,
2402 dataset: dataset,
2403 datasetIndex: me.index
2404 };
2405
2406 var keys = [
2407 'backgroundColor',
2408 'borderColor',
2409 'borderWidth',
2410 'hoverBackgroundColor',
2411 'hoverBorderColor',
2412 'hoverBorderWidth',
2413 'hoverRadius',
2414 'hitRadius',
2415 'pointStyle'
2416 ];
2417
2418 for (i = 0, ilen = keys.length; i < ilen; ++i) {
2419 key = keys[i];
2420 values[key] = resolve([
2421 custom[key],
2422 dataset[key],
2423 options[key]
2424 ], context, index);
2425 }
2426
2427 // Custom radius resolution
2428 values.radius = resolve([
2429 custom.radius,
2430 data ? data.r : undefined,
2431 dataset.radius,
2432 options.radius
2433 ], context, index);
2434
2435 return values;
2436 }
2437 });
2438 };
2439
2440 },{"25":25,"40":40,"45":45}],17:[function(require,module,exports){
2441 'use strict';
2442
2443 var defaults = require(25);
2444 var elements = require(40);
2445 var helpers = require(45);
2446
2447 defaults._set('doughnut', {
2448 animation: {
2449 // Boolean - Whether we animate the rotation of the Doughnut
2450 animateRotate: true,
2451 // Boolean - Whether we animate scaling the Doughnut from the centre
2452 animateScale: false
2453 },
2454 hover: {
2455 mode: 'single'
2456 },
2457 legendCallback: function(chart) {
2458 var text = [];
2459 text.push('<ul class="' + chart.id + '-legend">');
2460
2461 var data = chart.data;
2462 var datasets = data.datasets;
2463 var labels = data.labels;
2464
2465 if (datasets.length) {
2466 for (var i = 0; i < datasets[0].data.length; ++i) {
2467 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
2468 if (labels[i]) {
2469 text.push(labels[i]);
2470 }
2471 text.push('</li>');
2472 }
2473 }
2474
2475 text.push('</ul>');
2476 return text.join('');
2477 },
2478 legend: {
2479 labels: {
2480 generateLabels: function(chart) {
2481 var data = chart.data;
2482 if (data.labels.length && data.datasets.length) {
2483 return data.labels.map(function(label, i) {
2484 var meta = chart.getDatasetMeta(0);
2485 var ds = data.datasets[0];
2486 var arc = meta.data[i];
2487 var custom = arc && arc.custom || {};
2488 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2489 var arcOpts = chart.options.elements.arc;
2490 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
2491 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
2492 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
2493
2494 return {
2495 text: label,
2496 fillStyle: fill,
2497 strokeStyle: stroke,
2498 lineWidth: bw,
2499 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
2500
2501 // Extra data used for toggling the correct item
2502 index: i
2503 };
2504 });
2505 }
2506 return [];
2507 }
2508 },
2509
2510 onClick: function(e, legendItem) {
2511 var index = legendItem.index;
2512 var chart = this.chart;
2513 var i, ilen, meta;
2514
2515 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
2516 meta = chart.getDatasetMeta(i);
2517 // toggle visibility of index if exists
2518 if (meta.data[index]) {
2519 meta.data[index].hidden = !meta.data[index].hidden;
2520 }
2521 }
2522
2523 chart.update();
2524 }
2525 },
2526
2527 // The percentage of the chart that we cut out of the middle.
2528 cutoutPercentage: 50,
2529
2530 // The rotation of the chart, where the first data arc begins.
2531 rotation: Math.PI * -0.5,
2532
2533 // The total circumference of the chart.
2534 circumference: Math.PI * 2.0,
2535
2536 // Need to override these to give a nice default
2537 tooltips: {
2538 callbacks: {
2539 title: function() {
2540 return '';
2541 },
2542 label: function(tooltipItem, data) {
2543 var dataLabel = data.labels[tooltipItem.index];
2544 var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2545
2546 if (helpers.isArray(dataLabel)) {
2547 // show value on first line of multiline label
2548 // need to clone because we are changing the value
2549 dataLabel = dataLabel.slice();
2550 dataLabel[0] += value;
2551 } else {
2552 dataLabel += value;
2553 }
2554
2555 return dataLabel;
2556 }
2557 }
2558 }
2559 });
2560
2561 defaults._set('pie', helpers.clone(defaults.doughnut));
2562 defaults._set('pie', {
2563 cutoutPercentage: 0
2564 });
2565
2566 module.exports = function(Chart) {
2567
2568 Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
2569
2570 dataElementType: elements.Arc,
2571
2572 linkScales: helpers.noop,
2573
2574 // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
2575 getRingIndex: function(datasetIndex) {
2576 var ringIndex = 0;
2577
2578 for (var j = 0; j < datasetIndex; ++j) {
2579 if (this.chart.isDatasetVisible(j)) {
2580 ++ringIndex;
2581 }
2582 }
2583
2584 return ringIndex;
2585 },
2586
2587 update: function(reset) {
2588 var me = this;
2589 var chart = me.chart;
2590 var chartArea = chart.chartArea;
2591 var opts = chart.options;
2592 var arcOpts = opts.elements.arc;
2593 var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
2594 var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
2595 var minSize = Math.min(availableWidth, availableHeight);
2596 var offset = {x: 0, y: 0};
2597 var meta = me.getMeta();
2598 var cutoutPercentage = opts.cutoutPercentage;
2599 var circumference = opts.circumference;
2600
2601 // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
2602 if (circumference < Math.PI * 2.0) {
2603 var startAngle = opts.rotation % (Math.PI * 2.0);
2604 startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
2605 var endAngle = startAngle + circumference;
2606 var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
2607 var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
2608 var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
2609 var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
2610 var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
2611 var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
2612 var cutout = cutoutPercentage / 100.0;
2613 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))};
2614 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))};
2615 var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
2616 minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
2617 offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
2618 }
2619
2620 chart.borderWidth = me.getMaxBorderWidth(meta.data);
2621 chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
2622 chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
2623 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
2624 chart.offsetX = offset.x * chart.outerRadius;
2625 chart.offsetY = offset.y * chart.outerRadius;
2626
2627 meta.total = me.calculateTotal();
2628
2629 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
2630 me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
2631
2632 helpers.each(meta.data, function(arc, index) {
2633 me.updateElement(arc, index, reset);
2634 });
2635 },
2636
2637 updateElement: function(arc, index, reset) {
2638 var me = this;
2639 var chart = me.chart;
2640 var chartArea = chart.chartArea;
2641 var opts = chart.options;
2642 var animationOpts = opts.animation;
2643 var centerX = (chartArea.left + chartArea.right) / 2;
2644 var centerY = (chartArea.top + chartArea.bottom) / 2;
2645 var startAngle = opts.rotation; // non reset case handled later
2646 var endAngle = opts.rotation; // non reset case handled later
2647 var dataset = me.getDataset();
2648 var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
2649 var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
2650 var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
2651 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2652
2653 helpers.extend(arc, {
2654 // Utility
2655 _datasetIndex: me.index,
2656 _index: index,
2657
2658 // Desired view properties
2659 _model: {
2660 x: centerX + chart.offsetX,
2661 y: centerY + chart.offsetY,
2662 startAngle: startAngle,
2663 endAngle: endAngle,
2664 circumference: circumference,
2665 outerRadius: outerRadius,
2666 innerRadius: innerRadius,
2667 label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
2668 }
2669 });
2670
2671 var model = arc._model;
2672 // Resets the visual styles
2673 this.removeHoverStyle(arc);
2674
2675 // Set correct angles if not resetting
2676 if (!reset || !animationOpts.animateRotate) {
2677 if (index === 0) {
2678 model.startAngle = opts.rotation;
2679 } else {
2680 model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
2681 }
2682
2683 model.endAngle = model.startAngle + model.circumference;
2684 }
2685
2686 arc.pivot();
2687 },
2688
2689 removeHoverStyle: function(arc) {
2690 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
2691 },
2692
2693 calculateTotal: function() {
2694 var dataset = this.getDataset();
2695 var meta = this.getMeta();
2696 var total = 0;
2697 var value;
2698
2699 helpers.each(meta.data, function(element, index) {
2700 value = dataset.data[index];
2701 if (!isNaN(value) && !element.hidden) {
2702 total += Math.abs(value);
2703 }
2704 });
2705
2706 /* if (total === 0) {
2707 total = NaN;
2708 }*/
2709
2710 return total;
2711 },
2712
2713 calculateCircumference: function(value) {
2714 var total = this.getMeta().total;
2715 if (total > 0 && !isNaN(value)) {
2716 return (Math.PI * 2.0) * (value / total);
2717 }
2718 return 0;
2719 },
2720
2721 // gets the max border or hover width to properly scale pie charts
2722 getMaxBorderWidth: function(arcs) {
2723 var max = 0;
2724 var index = this.index;
2725 var length = arcs.length;
2726 var borderWidth;
2727 var hoverWidth;
2728
2729 for (var i = 0; i < length; i++) {
2730 borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
2731 hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
2732
2733 max = borderWidth > max ? borderWidth : max;
2734 max = hoverWidth > max ? hoverWidth : max;
2735 }
2736 return max;
2737 }
2738 });
2739 };
2740
2741 },{"25":25,"40":40,"45":45}],18:[function(require,module,exports){
2742 'use strict';
2743
2744 var defaults = require(25);
2745 var elements = require(40);
2746 var helpers = require(45);
2747
2748 defaults._set('line', {
2749 showLines: true,
2750 spanGaps: false,
2751
2752 hover: {
2753 mode: 'label'
2754 },
2755
2756 scales: {
2757 xAxes: [{
2758 type: 'category',
2759 id: 'x-axis-0'
2760 }],
2761 yAxes: [{
2762 type: 'linear',
2763 id: 'y-axis-0'
2764 }]
2765 }
2766 });
2767
2768 module.exports = function(Chart) {
2769
2770 function lineEnabled(dataset, options) {
2771 return helpers.valueOrDefault(dataset.showLine, options.showLines);
2772 }
2773
2774 Chart.controllers.line = Chart.DatasetController.extend({
2775
2776 datasetElementType: elements.Line,
2777
2778 dataElementType: elements.Point,
2779
2780 update: function(reset) {
2781 var me = this;
2782 var meta = me.getMeta();
2783 var line = meta.dataset;
2784 var points = meta.data || [];
2785 var options = me.chart.options;
2786 var lineElementOptions = options.elements.line;
2787 var scale = me.getScaleForId(meta.yAxisID);
2788 var i, ilen, custom;
2789 var dataset = me.getDataset();
2790 var showLine = lineEnabled(dataset, options);
2791
2792 // Update Line
2793 if (showLine) {
2794 custom = line.custom || {};
2795
2796 // Compatibility: If the properties are defined with only the old name, use those values
2797 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
2798 dataset.lineTension = dataset.tension;
2799 }
2800
2801 // Utility
2802 line._scale = scale;
2803 line._datasetIndex = me.index;
2804 // Data
2805 line._children = points;
2806 // Model
2807 line._model = {
2808 // Appearance
2809 // The default behavior of lines is to break at null values, according
2810 // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
2811 // This option gives lines the ability to span gaps
2812 spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
2813 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
2814 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
2815 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
2816 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
2817 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
2818 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
2819 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
2820 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
2821 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
2822 steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
2823 cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
2824 };
2825
2826 line.pivot();
2827 }
2828
2829 // Update Points
2830 for (i = 0, ilen = points.length; i < ilen; ++i) {
2831 me.updateElement(points[i], i, reset);
2832 }
2833
2834 if (showLine && line._model.tension !== 0) {
2835 me.updateBezierControlPoints();
2836 }
2837
2838 // Now pivot the point for animation
2839 for (i = 0, ilen = points.length; i < ilen; ++i) {
2840 points[i].pivot();
2841 }
2842 },
2843
2844 getPointBackgroundColor: function(point, index) {
2845 var backgroundColor = this.chart.options.elements.point.backgroundColor;
2846 var dataset = this.getDataset();
2847 var custom = point.custom || {};
2848
2849 if (custom.backgroundColor) {
2850 backgroundColor = custom.backgroundColor;
2851 } else if (dataset.pointBackgroundColor) {
2852 backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
2853 } else if (dataset.backgroundColor) {
2854 backgroundColor = dataset.backgroundColor;
2855 }
2856
2857 return backgroundColor;
2858 },
2859
2860 getPointBorderColor: function(point, index) {
2861 var borderColor = this.chart.options.elements.point.borderColor;
2862 var dataset = this.getDataset();
2863 var custom = point.custom || {};
2864
2865 if (custom.borderColor) {
2866 borderColor = custom.borderColor;
2867 } else if (dataset.pointBorderColor) {
2868 borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
2869 } else if (dataset.borderColor) {
2870 borderColor = dataset.borderColor;
2871 }
2872
2873 return borderColor;
2874 },
2875
2876 getPointBorderWidth: function(point, index) {
2877 var borderWidth = this.chart.options.elements.point.borderWidth;
2878 var dataset = this.getDataset();
2879 var custom = point.custom || {};
2880
2881 if (!isNaN(custom.borderWidth)) {
2882 borderWidth = custom.borderWidth;
2883 } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
2884 borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
2885 } else if (!isNaN(dataset.borderWidth)) {
2886 borderWidth = dataset.borderWidth;
2887 }
2888
2889 return borderWidth;
2890 },
2891
2892 updateElement: function(point, index, reset) {
2893 var me = this;
2894 var meta = me.getMeta();
2895 var custom = point.custom || {};
2896 var dataset = me.getDataset();
2897 var datasetIndex = me.index;
2898 var value = dataset.data[index];
2899 var yScale = me.getScaleForId(meta.yAxisID);
2900 var xScale = me.getScaleForId(meta.xAxisID);
2901 var pointOptions = me.chart.options.elements.point;
2902 var x, y;
2903
2904 // Compatibility: If the properties are defined with only the old name, use those values
2905 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
2906 dataset.pointRadius = dataset.radius;
2907 }
2908 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
2909 dataset.pointHitRadius = dataset.hitRadius;
2910 }
2911
2912 x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
2913 y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
2914
2915 // Utility
2916 point._xScale = xScale;
2917 point._yScale = yScale;
2918 point._datasetIndex = datasetIndex;
2919 point._index = index;
2920
2921 // Desired view properties
2922 point._model = {
2923 x: x,
2924 y: y,
2925 skip: custom.skip || isNaN(x) || isNaN(y),
2926 // Appearance
2927 radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
2928 pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
2929 backgroundColor: me.getPointBackgroundColor(point, index),
2930 borderColor: me.getPointBorderColor(point, index),
2931 borderWidth: me.getPointBorderWidth(point, index),
2932 tension: meta.dataset._model ? meta.dataset._model.tension : 0,
2933 steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
2934 // Tooltip
2935 hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
2936 };
2937 },
2938
2939 calculatePointY: function(value, index, datasetIndex) {
2940 var me = this;
2941 var chart = me.chart;
2942 var meta = me.getMeta();
2943 var yScale = me.getScaleForId(meta.yAxisID);
2944 var sumPos = 0;
2945 var sumNeg = 0;
2946 var i, ds, dsMeta;
2947
2948 if (yScale.options.stacked) {
2949 for (i = 0; i < datasetIndex; i++) {
2950 ds = chart.data.datasets[i];
2951 dsMeta = chart.getDatasetMeta(i);
2952 if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
2953 var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
2954 if (stackedRightValue < 0) {
2955 sumNeg += stackedRightValue || 0;
2956 } else {
2957 sumPos += stackedRightValue || 0;
2958 }
2959 }
2960 }
2961
2962 var rightValue = Number(yScale.getRightValue(value));
2963 if (rightValue < 0) {
2964 return yScale.getPixelForValue(sumNeg + rightValue);
2965 }
2966 return yScale.getPixelForValue(sumPos + rightValue);
2967 }
2968
2969 return yScale.getPixelForValue(value);
2970 },
2971
2972 updateBezierControlPoints: function() {
2973 var me = this;
2974 var meta = me.getMeta();
2975 var area = me.chart.chartArea;
2976 var points = (meta.data || []);
2977 var i, ilen, point, model, controlPoints;
2978
2979 // Only consider points that are drawn in case the spanGaps option is used
2980 if (meta.dataset._model.spanGaps) {
2981 points = points.filter(function(pt) {
2982 return !pt._model.skip;
2983 });
2984 }
2985
2986 function capControlPoint(pt, min, max) {
2987 return Math.max(Math.min(pt, max), min);
2988 }
2989
2990 if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
2991 helpers.splineCurveMonotone(points);
2992 } else {
2993 for (i = 0, ilen = points.length; i < ilen; ++i) {
2994 point = points[i];
2995 model = point._model;
2996 controlPoints = helpers.splineCurve(
2997 helpers.previousItem(points, i)._model,
2998 model,
2999 helpers.nextItem(points, i)._model,
3000 meta.dataset._model.tension
3001 );
3002 model.controlPointPreviousX = controlPoints.previous.x;
3003 model.controlPointPreviousY = controlPoints.previous.y;
3004 model.controlPointNextX = controlPoints.next.x;
3005 model.controlPointNextY = controlPoints.next.y;
3006 }
3007 }
3008
3009 if (me.chart.options.elements.line.capBezierPoints) {
3010 for (i = 0, ilen = points.length; i < ilen; ++i) {
3011 model = points[i]._model;
3012 model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
3013 model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
3014 model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
3015 model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
3016 }
3017 }
3018 },
3019
3020 draw: function() {
3021 var me = this;
3022 var chart = me.chart;
3023 var meta = me.getMeta();
3024 var points = meta.data || [];
3025 var area = chart.chartArea;
3026 var ilen = points.length;
3027 var i = 0;
3028
3029 helpers.canvas.clipArea(chart.ctx, area);
3030
3031 if (lineEnabled(me.getDataset(), chart.options)) {
3032 meta.dataset.draw();
3033 }
3034
3035 helpers.canvas.unclipArea(chart.ctx);
3036
3037 // Draw the points
3038 for (; i < ilen; ++i) {
3039 points[i].draw(area);
3040 }
3041 },
3042
3043 setHoverStyle: function(point) {
3044 // Point
3045 var dataset = this.chart.data.datasets[point._datasetIndex];
3046 var index = point._index;
3047 var custom = point.custom || {};
3048 var model = point._model;
3049
3050 model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3051 model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3052 model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3053 model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3054 },
3055
3056 removeHoverStyle: function(point) {
3057 var me = this;
3058 var dataset = me.chart.data.datasets[point._datasetIndex];
3059 var index = point._index;
3060 var custom = point.custom || {};
3061 var model = point._model;
3062
3063 // Compatibility: If the properties are defined with only the old name, use those values
3064 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3065 dataset.pointRadius = dataset.radius;
3066 }
3067
3068 model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
3069 model.backgroundColor = me.getPointBackgroundColor(point, index);
3070 model.borderColor = me.getPointBorderColor(point, index);
3071 model.borderWidth = me.getPointBorderWidth(point, index);
3072 }
3073 });
3074 };
3075
3076 },{"25":25,"40":40,"45":45}],19:[function(require,module,exports){
3077 'use strict';
3078
3079 var defaults = require(25);
3080 var elements = require(40);
3081 var helpers = require(45);
3082
3083 defaults._set('polarArea', {
3084 scale: {
3085 type: 'radialLinear',
3086 angleLines: {
3087 display: false
3088 },
3089 gridLines: {
3090 circular: true
3091 },
3092 pointLabels: {
3093 display: false
3094 },
3095 ticks: {
3096 beginAtZero: true
3097 }
3098 },
3099
3100 // Boolean - Whether to animate the rotation of the chart
3101 animation: {
3102 animateRotate: true,
3103 animateScale: true
3104 },
3105
3106 startAngle: -0.5 * Math.PI,
3107 legendCallback: function(chart) {
3108 var text = [];
3109 text.push('<ul class="' + chart.id + '-legend">');
3110
3111 var data = chart.data;
3112 var datasets = data.datasets;
3113 var labels = data.labels;
3114
3115 if (datasets.length) {
3116 for (var i = 0; i < datasets[0].data.length; ++i) {
3117 text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
3118 if (labels[i]) {
3119 text.push(labels[i]);
3120 }
3121 text.push('</li>');
3122 }
3123 }
3124
3125 text.push('</ul>');
3126 return text.join('');
3127 },
3128 legend: {
3129 labels: {
3130 generateLabels: function(chart) {
3131 var data = chart.data;
3132 if (data.labels.length && data.datasets.length) {
3133 return data.labels.map(function(label, i) {
3134 var meta = chart.getDatasetMeta(0);
3135 var ds = data.datasets[0];
3136 var arc = meta.data[i];
3137 var custom = arc.custom || {};
3138 var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
3139 var arcOpts = chart.options.elements.arc;
3140 var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
3141 var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
3142 var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
3143
3144 return {
3145 text: label,
3146 fillStyle: fill,
3147 strokeStyle: stroke,
3148 lineWidth: bw,
3149 hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
3150
3151 // Extra data used for toggling the correct item
3152 index: i
3153 };
3154 });
3155 }
3156 return [];
3157 }
3158 },
3159
3160 onClick: function(e, legendItem) {
3161 var index = legendItem.index;
3162 var chart = this.chart;
3163 var i, ilen, meta;
3164
3165 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
3166 meta = chart.getDatasetMeta(i);
3167 meta.data[index].hidden = !meta.data[index].hidden;
3168 }
3169
3170 chart.update();
3171 }
3172 },
3173
3174 // Need to override these to give a nice default
3175 tooltips: {
3176 callbacks: {
3177 title: function() {
3178 return '';
3179 },
3180 label: function(item, data) {
3181 return data.labels[item.index] + ': ' + item.yLabel;
3182 }
3183 }
3184 }
3185 });
3186
3187 module.exports = function(Chart) {
3188
3189 Chart.controllers.polarArea = Chart.DatasetController.extend({
3190
3191 dataElementType: elements.Arc,
3192
3193 linkScales: helpers.noop,
3194
3195 update: function(reset) {
3196 var me = this;
3197 var chart = me.chart;
3198 var chartArea = chart.chartArea;
3199 var meta = me.getMeta();
3200 var opts = chart.options;
3201 var arcOpts = opts.elements.arc;
3202 var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
3203 chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
3204 chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
3205 chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
3206
3207 me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
3208 me.innerRadius = me.outerRadius - chart.radiusLength;
3209
3210 meta.count = me.countVisibleElements();
3211
3212 helpers.each(meta.data, function(arc, index) {
3213 me.updateElement(arc, index, reset);
3214 });
3215 },
3216
3217 updateElement: function(arc, index, reset) {
3218 var me = this;
3219 var chart = me.chart;
3220 var dataset = me.getDataset();
3221 var opts = chart.options;
3222 var animationOpts = opts.animation;
3223 var scale = chart.scale;
3224 var labels = chart.data.labels;
3225
3226 var circumference = me.calculateCircumference(dataset.data[index]);
3227 var centerX = scale.xCenter;
3228 var centerY = scale.yCenter;
3229
3230 // If there is NaN data before us, we need to calculate the starting angle correctly.
3231 // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
3232 var visibleCount = 0;
3233 var meta = me.getMeta();
3234 for (var i = 0; i < index; ++i) {
3235 if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
3236 ++visibleCount;
3237 }
3238 }
3239
3240 // var negHalfPI = -0.5 * Math.PI;
3241 var datasetStartAngle = opts.startAngle;
3242 var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3243 var startAngle = datasetStartAngle + (circumference * visibleCount);
3244 var endAngle = startAngle + (arc.hidden ? 0 : circumference);
3245
3246 var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3247
3248 helpers.extend(arc, {
3249 // Utility
3250 _datasetIndex: me.index,
3251 _index: index,
3252 _scale: scale,
3253
3254 // Desired view properties
3255 _model: {
3256 x: centerX,
3257 y: centerY,
3258 innerRadius: 0,
3259 outerRadius: reset ? resetRadius : distance,
3260 startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
3261 endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
3262 label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
3263 }
3264 });
3265
3266 // Apply border and fill style
3267 me.removeHoverStyle(arc);
3268
3269 arc.pivot();
3270 },
3271
3272 removeHoverStyle: function(arc) {
3273 Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
3274 },
3275
3276 countVisibleElements: function() {
3277 var dataset = this.getDataset();
3278 var meta = this.getMeta();
3279 var count = 0;
3280
3281 helpers.each(meta.data, function(element, index) {
3282 if (!isNaN(dataset.data[index]) && !element.hidden) {
3283 count++;
3284 }
3285 });
3286
3287 return count;
3288 },
3289
3290 calculateCircumference: function(value) {
3291 var count = this.getMeta().count;
3292 if (count > 0 && !isNaN(value)) {
3293 return (2 * Math.PI) / count;
3294 }
3295 return 0;
3296 }
3297 });
3298 };
3299
3300 },{"25":25,"40":40,"45":45}],20:[function(require,module,exports){
3301 'use strict';
3302
3303 var defaults = require(25);
3304 var elements = require(40);
3305 var helpers = require(45);
3306
3307 defaults._set('radar', {
3308 scale: {
3309 type: 'radialLinear'
3310 },
3311 elements: {
3312 line: {
3313 tension: 0 // no bezier in radar
3314 }
3315 }
3316 });
3317
3318 module.exports = function(Chart) {
3319
3320 Chart.controllers.radar = Chart.DatasetController.extend({
3321
3322 datasetElementType: elements.Line,
3323
3324 dataElementType: elements.Point,
3325
3326 linkScales: helpers.noop,
3327
3328 update: function(reset) {
3329 var me = this;
3330 var meta = me.getMeta();
3331 var line = meta.dataset;
3332 var points = meta.data;
3333 var custom = line.custom || {};
3334 var dataset = me.getDataset();
3335 var lineElementOptions = me.chart.options.elements.line;
3336 var scale = me.chart.scale;
3337
3338 // Compatibility: If the properties are defined with only the old name, use those values
3339 if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
3340 dataset.lineTension = dataset.tension;
3341 }
3342
3343 helpers.extend(meta.dataset, {
3344 // Utility
3345 _datasetIndex: me.index,
3346 _scale: scale,
3347 // Data
3348 _children: points,
3349 _loop: true,
3350 // Model
3351 _model: {
3352 // Appearance
3353 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
3354 backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
3355 borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
3356 borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
3357 fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
3358 borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
3359 borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
3360 borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
3361 borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
3362 }
3363 });
3364
3365 meta.dataset.pivot();
3366
3367 // Update Points
3368 helpers.each(points, function(point, index) {
3369 me.updateElement(point, index, reset);
3370 }, me);
3371
3372 // Update bezier control points
3373 me.updateBezierControlPoints();
3374 },
3375 updateElement: function(point, index, reset) {
3376 var me = this;
3377 var custom = point.custom || {};
3378 var dataset = me.getDataset();
3379 var scale = me.chart.scale;
3380 var pointElementOptions = me.chart.options.elements.point;
3381 var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
3382
3383 // Compatibility: If the properties are defined with only the old name, use those values
3384 if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3385 dataset.pointRadius = dataset.radius;
3386 }
3387 if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
3388 dataset.pointHitRadius = dataset.hitRadius;
3389 }
3390
3391 helpers.extend(point, {
3392 // Utility
3393 _datasetIndex: me.index,
3394 _index: index,
3395 _scale: scale,
3396
3397 // Desired view properties
3398 _model: {
3399 x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
3400 y: reset ? scale.yCenter : pointPosition.y,
3401
3402 // Appearance
3403 tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
3404 radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
3405 backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
3406 borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
3407 borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
3408 pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
3409
3410 // Tooltip
3411 hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
3412 }
3413 });
3414
3415 point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
3416 },
3417 updateBezierControlPoints: function() {
3418 var chartArea = this.chart.chartArea;
3419 var meta = this.getMeta();
3420
3421 helpers.each(meta.data, function(point, index) {
3422 var model = point._model;
3423 var controlPoints = helpers.splineCurve(
3424 helpers.previousItem(meta.data, index, true)._model,
3425 model,
3426 helpers.nextItem(meta.data, index, true)._model,
3427 model.tension
3428 );
3429
3430 // Prevent the bezier going outside of the bounds of the graph
3431 model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
3432 model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
3433
3434 model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
3435 model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
3436
3437 // Now pivot the point for animation
3438 point.pivot();
3439 });
3440 },
3441
3442 setHoverStyle: function(point) {
3443 // Point
3444 var dataset = this.chart.data.datasets[point._datasetIndex];
3445 var custom = point.custom || {};
3446 var index = point._index;
3447 var model = point._model;
3448
3449 model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3450 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3451 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3452 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3453 },
3454
3455 removeHoverStyle: function(point) {
3456 var dataset = this.chart.data.datasets[point._datasetIndex];
3457 var custom = point.custom || {};
3458 var index = point._index;
3459 var model = point._model;
3460 var pointElementOptions = this.chart.options.elements.point;
3461
3462 model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius);
3463 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
3464 model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
3465 model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
3466 }
3467 });
3468 };
3469
3470 },{"25":25,"40":40,"45":45}],21:[function(require,module,exports){
3471 'use strict';
3472
3473 var defaults = require(25);
3474
3475 defaults._set('scatter', {
3476 hover: {
3477 mode: 'single'
3478 },
3479
3480 scales: {
3481 xAxes: [{
3482 id: 'x-axis-1', // need an ID so datasets can reference the scale
3483 type: 'linear', // scatter should not use a category axis
3484 position: 'bottom'
3485 }],
3486 yAxes: [{
3487 id: 'y-axis-1',
3488 type: 'linear',
3489 position: 'left'
3490 }]
3491 },
3492
3493 showLines: false,
3494
3495 tooltips: {
3496 callbacks: {
3497 title: function() {
3498 return ''; // doesn't make sense for scatter since data are formatted as a point
3499 },
3500 label: function(item) {
3501 return '(' + item.xLabel + ', ' + item.yLabel + ')';
3502 }
3503 }
3504 }
3505 });
3506
3507 module.exports = function(Chart) {
3508
3509 // Scatter charts use line controllers
3510 Chart.controllers.scatter = Chart.controllers.line;
3511
3512 };
3513
3514 },{"25":25}],22:[function(require,module,exports){
3515 /* global window: false */
3516 'use strict';
3517
3518 var defaults = require(25);
3519 var Element = require(26);
3520 var helpers = require(45);
3521
3522 defaults._set('global', {
3523 animation: {
3524 duration: 1000,
3525 easing: 'easeOutQuart',
3526 onProgress: helpers.noop,
3527 onComplete: helpers.noop
3528 }
3529 });
3530
3531 module.exports = function(Chart) {
3532
3533 Chart.Animation = Element.extend({
3534 chart: null, // the animation associated chart instance
3535 currentStep: 0, // the current animation step
3536 numSteps: 60, // default number of steps
3537 easing: '', // the easing to use for this animation
3538 render: null, // render function used by the animation service
3539
3540 onAnimationProgress: null, // user specified callback to fire on each step of the animation
3541 onAnimationComplete: null, // user specified callback to fire when the animation finishes
3542 });
3543
3544 Chart.animationService = {
3545 frameDuration: 17,
3546 animations: [],
3547 dropFrames: 0,
3548 request: null,
3549
3550 /**
3551 * @param {Chart} chart - The chart to animate.
3552 * @param {Chart.Animation} animation - The animation that we will animate.
3553 * @param {Number} duration - The animation duration in ms.
3554 * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
3555 */
3556 addAnimation: function(chart, animation, duration, lazy) {
3557 var animations = this.animations;
3558 var i, ilen;
3559
3560 animation.chart = chart;
3561
3562 if (!lazy) {
3563 chart.animating = true;
3564 }
3565
3566 for (i = 0, ilen = animations.length; i < ilen; ++i) {
3567 if (animations[i].chart === chart) {
3568 animations[i] = animation;
3569 return;
3570 }
3571 }
3572
3573 animations.push(animation);
3574
3575 // If there are no animations queued, manually kickstart a digest, for lack of a better word
3576 if (animations.length === 1) {
3577 this.requestAnimationFrame();
3578 }
3579 },
3580
3581 cancelAnimation: function(chart) {
3582 var index = helpers.findIndex(this.animations, function(animation) {
3583 return animation.chart === chart;
3584 });
3585
3586 if (index !== -1) {
3587 this.animations.splice(index, 1);
3588 chart.animating = false;
3589 }
3590 },
3591
3592 requestAnimationFrame: function() {
3593 var me = this;
3594 if (me.request === null) {
3595 // Skip animation frame requests until the active one is executed.
3596 // This can happen when processing mouse events, e.g. 'mousemove'
3597 // and 'mouseout' events will trigger multiple renders.
3598 me.request = helpers.requestAnimFrame.call(window, function() {
3599 me.request = null;
3600 me.startDigest();
3601 });
3602 }
3603 },
3604
3605 /**
3606 * @private
3607 */
3608 startDigest: function() {
3609 var me = this;
3610 var startTime = Date.now();
3611 var framesToDrop = 0;
3612
3613 if (me.dropFrames > 1) {
3614 framesToDrop = Math.floor(me.dropFrames);
3615 me.dropFrames = me.dropFrames % 1;
3616 }
3617
3618 me.advance(1 + framesToDrop);
3619
3620 var endTime = Date.now();
3621
3622 me.dropFrames += (endTime - startTime) / me.frameDuration;
3623
3624 // Do we have more stuff to animate?
3625 if (me.animations.length > 0) {
3626 me.requestAnimationFrame();
3627 }
3628 },
3629
3630 /**
3631 * @private
3632 */
3633 advance: function(count) {
3634 var animations = this.animations;
3635 var animation, chart;
3636 var i = 0;
3637
3638 while (i < animations.length) {
3639 animation = animations[i];
3640 chart = animation.chart;
3641
3642 animation.currentStep = (animation.currentStep || 0) + count;
3643 animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
3644
3645 helpers.callback(animation.render, [chart, animation], chart);
3646 helpers.callback(animation.onAnimationProgress, [animation], chart);
3647
3648 if (animation.currentStep >= animation.numSteps) {
3649 helpers.callback(animation.onAnimationComplete, [animation], chart);
3650 chart.animating = false;
3651 animations.splice(i, 1);
3652 } else {
3653 ++i;
3654 }
3655 }
3656 }
3657 };
3658
3659 /**
3660 * Provided for backward compatibility, use Chart.Animation instead
3661 * @prop Chart.Animation#animationObject
3662 * @deprecated since version 2.6.0
3663 * @todo remove at version 3
3664 */
3665 Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
3666 get: function() {
3667 return this;
3668 }
3669 });
3670
3671 /**
3672 * Provided for backward compatibility, use Chart.Animation#chart instead
3673 * @prop Chart.Animation#chartInstance
3674 * @deprecated since version 2.6.0
3675 * @todo remove at version 3
3676 */
3677 Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
3678 get: function() {
3679 return this.chart;
3680 },
3681 set: function(value) {
3682 this.chart = value;
3683 }
3684 });
3685
3686 };
3687
3688 },{"25":25,"26":26,"45":45}],23:[function(require,module,exports){
3689 'use strict';
3690
3691 var defaults = require(25);
3692 var helpers = require(45);
3693 var Interaction = require(28);
3694 var platform = require(48);
3695
3696 module.exports = function(Chart) {
3697 var plugins = Chart.plugins;
3698
3699 // Create a dictionary of chart types, to allow for extension of existing types
3700 Chart.types = {};
3701
3702 // Store a reference to each instance - allowing us to globally resize chart instances on window resize.
3703 // Destroy method on the chart will remove the instance of the chart from this reference.
3704 Chart.instances = {};
3705
3706 // Controllers available for dataset visualization eg. bar, line, slice, etc.
3707 Chart.controllers = {};
3708
3709 /**
3710 * Initializes the given config with global and chart default values.
3711 */
3712 function initConfig(config) {
3713 config = config || {};
3714
3715 // Do NOT use configMerge() for the data object because this method merges arrays
3716 // and so would change references to labels and datasets, preventing data updates.
3717 var data = config.data = config.data || {};
3718 data.datasets = data.datasets || [];
3719 data.labels = data.labels || [];
3720
3721 config.options = helpers.configMerge(
3722 defaults.global,
3723 defaults[config.type],
3724 config.options || {});
3725
3726 return config;
3727 }
3728
3729 /**
3730 * Updates the config of the chart
3731 * @param chart {Chart} chart to update the options for
3732 */
3733 function updateConfig(chart) {
3734 var newOptions = chart.options;
3735
3736 // Update Scale(s) with options
3737 if (newOptions.scale) {
3738 chart.scale.options = newOptions.scale;
3739 } else if (newOptions.scales) {
3740 newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) {
3741 chart.scales[scaleOptions.id].options = scaleOptions;
3742 });
3743 }
3744
3745 // Tooltip
3746 chart.tooltip._options = newOptions.tooltips;
3747 }
3748
3749 function positionIsHorizontal(position) {
3750 return position === 'top' || position === 'bottom';
3751 }
3752
3753 helpers.extend(Chart.prototype, /** @lends Chart */ {
3754 /**
3755 * @private
3756 */
3757 construct: function(item, config) {
3758 var me = this;
3759
3760 config = initConfig(config);
3761
3762 var context = platform.acquireContext(item, config);
3763 var canvas = context && context.canvas;
3764 var height = canvas && canvas.height;
3765 var width = canvas && canvas.width;
3766
3767 me.id = helpers.uid();
3768 me.ctx = context;
3769 me.canvas = canvas;
3770 me.config = config;
3771 me.width = width;
3772 me.height = height;
3773 me.aspectRatio = height ? width / height : null;
3774 me.options = config.options;
3775 me._bufferedRender = false;
3776
3777 /**
3778 * Provided for backward compatibility, Chart and Chart.Controller have been merged,
3779 * the "instance" still need to be defined since it might be called from plugins.
3780 * @prop Chart#chart
3781 * @deprecated since version 2.6.0
3782 * @todo remove at version 3
3783 * @private
3784 */
3785 me.chart = me;
3786 me.controller = me; // chart.chart.controller #inception
3787
3788 // Add the chart instance to the global namespace
3789 Chart.instances[me.id] = me;
3790
3791 // Define alias to the config data: `chart.data === chart.config.data`
3792 Object.defineProperty(me, 'data', {
3793 get: function() {
3794 return me.config.data;
3795 },
3796 set: function(value) {
3797 me.config.data = value;
3798 }
3799 });
3800
3801 if (!context || !canvas) {
3802 // The given item is not a compatible context2d element, let's return before finalizing
3803 // the chart initialization but after setting basic chart / controller properties that
3804 // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
3805 // https://github.com/chartjs/Chart.js/issues/2807
3806 console.error("Failed to create chart: can't acquire context from the given item");
3807 return;
3808 }
3809
3810 me.initialize();
3811 me.update();
3812 },
3813
3814 /**
3815 * @private
3816 */
3817 initialize: function() {
3818 var me = this;
3819
3820 // Before init plugin notification
3821 plugins.notify(me, 'beforeInit');
3822
3823 helpers.retinaScale(me, me.options.devicePixelRatio);
3824
3825 me.bindEvents();
3826
3827 if (me.options.responsive) {
3828 // Initial resize before chart draws (must be silent to preserve initial animations).
3829 me.resize(true);
3830 }
3831
3832 // Make sure scales have IDs and are built before we build any controllers.
3833 me.ensureScalesHaveIDs();
3834 me.buildScales();
3835 me.initToolTip();
3836
3837 // After init plugin notification
3838 plugins.notify(me, 'afterInit');
3839
3840 return me;
3841 },
3842
3843 clear: function() {
3844 helpers.canvas.clear(this);
3845 return this;
3846 },
3847
3848 stop: function() {
3849 // Stops any current animation loop occurring
3850 Chart.animationService.cancelAnimation(this);
3851 return this;
3852 },
3853
3854 resize: function(silent) {
3855 var me = this;
3856 var options = me.options;
3857 var canvas = me.canvas;
3858 var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
3859
3860 // the canvas render width and height will be casted to integers so make sure that
3861 // the canvas display style uses the same integer values to avoid blurring effect.
3862
3863 // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased
3864 var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
3865 var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
3866
3867 if (me.width === newWidth && me.height === newHeight) {
3868 return;
3869 }
3870
3871 canvas.width = me.width = newWidth;
3872 canvas.height = me.height = newHeight;
3873 canvas.style.width = newWidth + 'px';
3874 canvas.style.height = newHeight + 'px';
3875
3876 helpers.retinaScale(me, options.devicePixelRatio);
3877
3878 if (!silent) {
3879 // Notify any plugins about the resize
3880 var newSize = {width: newWidth, height: newHeight};
3881 plugins.notify(me, 'resize', [newSize]);
3882
3883 // Notify of resize
3884 if (me.options.onResize) {
3885 me.options.onResize(me, newSize);
3886 }
3887
3888 me.stop();
3889 me.update(me.options.responsiveAnimationDuration);
3890 }
3891 },
3892
3893 ensureScalesHaveIDs: function() {
3894 var options = this.options;
3895 var scalesOptions = options.scales || {};
3896 var scaleOptions = options.scale;
3897
3898 helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
3899 xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
3900 });
3901
3902 helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
3903 yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
3904 });
3905
3906 if (scaleOptions) {
3907 scaleOptions.id = scaleOptions.id || 'scale';
3908 }
3909 },
3910
3911 /**
3912 * Builds a map of scale ID to scale object for future lookup.
3913 */
3914 buildScales: function() {
3915 var me = this;
3916 var options = me.options;
3917 var scales = me.scales = {};
3918 var items = [];
3919
3920 if (options.scales) {
3921 items = items.concat(
3922 (options.scales.xAxes || []).map(function(xAxisOptions) {
3923 return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
3924 }),
3925 (options.scales.yAxes || []).map(function(yAxisOptions) {
3926 return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
3927 })
3928 );
3929 }
3930
3931 if (options.scale) {
3932 items.push({
3933 options: options.scale,
3934 dtype: 'radialLinear',
3935 isDefault: true,
3936 dposition: 'chartArea'
3937 });
3938 }
3939
3940 helpers.each(items, function(item) {
3941 var scaleOptions = item.options;
3942 var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype);
3943 var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
3944 if (!scaleClass) {
3945 return;
3946 }
3947
3948 if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
3949 scaleOptions.position = item.dposition;
3950 }
3951
3952 var scale = new scaleClass({
3953 id: scaleOptions.id,
3954 options: scaleOptions,
3955 ctx: me.ctx,
3956 chart: me
3957 });
3958
3959 scales[scale.id] = scale;
3960 scale.mergeTicksOptions();
3961
3962 // TODO(SB): I think we should be able to remove this custom case (options.scale)
3963 // and consider it as a regular scale part of the "scales"" map only! This would
3964 // make the logic easier and remove some useless? custom code.
3965 if (item.isDefault) {
3966 me.scale = scale;
3967 }
3968 });
3969
3970 Chart.scaleService.addScalesToLayout(this);
3971 },
3972
3973 buildOrUpdateControllers: function() {
3974 var me = this;
3975 var types = [];
3976 var newControllers = [];
3977
3978 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
3979 var meta = me.getDatasetMeta(datasetIndex);
3980 var type = dataset.type || me.config.type;
3981
3982 if (meta.type && meta.type !== type) {
3983 me.destroyDatasetMeta(datasetIndex);
3984 meta = me.getDatasetMeta(datasetIndex);
3985 }
3986 meta.type = type;
3987
3988 types.push(meta.type);
3989
3990 if (meta.controller) {
3991 meta.controller.updateIndex(datasetIndex);
3992 } else {
3993 var ControllerClass = Chart.controllers[meta.type];
3994 if (ControllerClass === undefined) {
3995 throw new Error('"' + meta.type + '" is not a chart type.');
3996 }
3997
3998 meta.controller = new ControllerClass(me, datasetIndex);
3999 newControllers.push(meta.controller);
4000 }
4001 }, me);
4002
4003 return newControllers;
4004 },
4005
4006 /**
4007 * Reset the elements of all datasets
4008 * @private
4009 */
4010 resetElements: function() {
4011 var me = this;
4012 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4013 me.getDatasetMeta(datasetIndex).controller.reset();
4014 }, me);
4015 },
4016
4017 /**
4018 * Resets the chart back to it's state before the initial animation
4019 */
4020 reset: function() {
4021 this.resetElements();
4022 this.tooltip.initialize();
4023 },
4024
4025 update: function(config) {
4026 var me = this;
4027
4028 if (!config || typeof config !== 'object') {
4029 // backwards compatibility
4030 config = {
4031 duration: config,
4032 lazy: arguments[1]
4033 };
4034 }
4035
4036 updateConfig(me);
4037
4038 if (plugins.notify(me, 'beforeUpdate') === false) {
4039 return;
4040 }
4041
4042 // In case the entire data object changed
4043 me.tooltip._data = me.data;
4044
4045 // Make sure dataset controllers are updated and new controllers are reset
4046 var newControllers = me.buildOrUpdateControllers();
4047
4048 // Make sure all dataset controllers have correct meta data counts
4049 helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4050 me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
4051 }, me);
4052
4053 me.updateLayout();
4054
4055 // Can only reset the new controllers after the scales have been updated
4056 helpers.each(newControllers, function(controller) {
4057 controller.reset();
4058 });
4059
4060 me.updateDatasets();
4061
4062 // Need to reset tooltip in case it is displayed with elements that are removed
4063 // after update.
4064 me.tooltip.initialize();
4065
4066 // Last active contains items that were previously in the tooltip.
4067 // When we reset the tooltip, we need to clear it
4068 me.lastActive = [];
4069
4070 // Do this before render so that any plugins that need final scale updates can use it
4071 plugins.notify(me, 'afterUpdate');
4072
4073 if (me._bufferedRender) {
4074 me._bufferedRequest = {
4075 duration: config.duration,
4076 easing: config.easing,
4077 lazy: config.lazy
4078 };
4079 } else {
4080 me.render(config);
4081 }
4082 },
4083
4084 /**
4085 * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
4086 * hook, in which case, plugins will not be called on `afterLayout`.
4087 * @private
4088 */
4089 updateLayout: function() {
4090 var me = this;
4091
4092 if (plugins.notify(me, 'beforeLayout') === false) {
4093 return;
4094 }
4095
4096 Chart.layoutService.update(this, this.width, this.height);
4097
4098 /**
4099 * Provided for backward compatibility, use `afterLayout` instead.
4100 * @method IPlugin#afterScaleUpdate
4101 * @deprecated since version 2.5.0
4102 * @todo remove at version 3
4103 * @private
4104 */
4105 plugins.notify(me, 'afterScaleUpdate');
4106 plugins.notify(me, 'afterLayout');
4107 },
4108
4109 /**
4110 * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
4111 * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
4112 * @private
4113 */
4114 updateDatasets: function() {
4115 var me = this;
4116
4117 if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
4118 return;
4119 }
4120
4121 for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4122 me.updateDataset(i);
4123 }
4124
4125 plugins.notify(me, 'afterDatasetsUpdate');
4126 },
4127
4128 /**
4129 * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
4130 * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
4131 * @private
4132 */
4133 updateDataset: function(index) {
4134 var me = this;
4135 var meta = me.getDatasetMeta(index);
4136 var args = {
4137 meta: meta,
4138 index: index
4139 };
4140
4141 if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
4142 return;
4143 }
4144
4145 meta.controller.update();
4146
4147 plugins.notify(me, 'afterDatasetUpdate', [args]);
4148 },
4149
4150 render: function(config) {
4151 var me = this;
4152
4153 if (!config || typeof config !== 'object') {
4154 // backwards compatibility
4155 config = {
4156 duration: config,
4157 lazy: arguments[1]
4158 };
4159 }
4160
4161 var duration = config.duration;
4162 var lazy = config.lazy;
4163
4164 if (plugins.notify(me, 'beforeRender') === false) {
4165 return;
4166 }
4167
4168 var animationOptions = me.options.animation;
4169 var onComplete = function(animation) {
4170 plugins.notify(me, 'afterRender');
4171 helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
4172 };
4173
4174 if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
4175 var animation = new Chart.Animation({
4176 numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
4177 easing: config.easing || animationOptions.easing,
4178
4179 render: function(chart, animationObject) {
4180 var easingFunction = helpers.easing.effects[animationObject.easing];
4181 var currentStep = animationObject.currentStep;
4182 var stepDecimal = currentStep / animationObject.numSteps;
4183
4184 chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
4185 },
4186
4187 onAnimationProgress: animationOptions.onProgress,
4188 onAnimationComplete: onComplete
4189 });
4190
4191 Chart.animationService.addAnimation(me, animation, duration, lazy);
4192 } else {
4193 me.draw();
4194
4195 // See https://github.com/chartjs/Chart.js/issues/3781
4196 onComplete(new Chart.Animation({numSteps: 0, chart: me}));
4197 }
4198
4199 return me;
4200 },
4201
4202 draw: function(easingValue) {
4203 var me = this;
4204
4205 me.clear();
4206
4207 if (helpers.isNullOrUndef(easingValue)) {
4208 easingValue = 1;
4209 }
4210
4211 me.transition(easingValue);
4212
4213 if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
4214 return;
4215 }
4216
4217 // Draw all the scales
4218 helpers.each(me.boxes, function(box) {
4219 box.draw(me.chartArea);
4220 }, me);
4221
4222 if (me.scale) {
4223 me.scale.draw();
4224 }
4225
4226 me.drawDatasets(easingValue);
4227 me._drawTooltip(easingValue);
4228
4229 plugins.notify(me, 'afterDraw', [easingValue]);
4230 },
4231
4232 /**
4233 * @private
4234 */
4235 transition: function(easingValue) {
4236 var me = this;
4237
4238 for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
4239 if (me.isDatasetVisible(i)) {
4240 me.getDatasetMeta(i).controller.transition(easingValue);
4241 }
4242 }
4243
4244 me.tooltip.transition(easingValue);
4245 },
4246
4247 /**
4248 * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
4249 * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
4250 * @private
4251 */
4252 drawDatasets: function(easingValue) {
4253 var me = this;
4254
4255 if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
4256 return;
4257 }
4258
4259 // Draw datasets reversed to support proper line stacking
4260 for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
4261 if (me.isDatasetVisible(i)) {
4262 me.drawDataset(i, easingValue);
4263 }
4264 }
4265
4266 plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
4267 },
4268
4269 /**
4270 * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
4271 * hook, in which case, plugins will not be called on `afterDatasetDraw`.
4272 * @private
4273 */
4274 drawDataset: function(index, easingValue) {
4275 var me = this;
4276 var meta = me.getDatasetMeta(index);
4277 var args = {
4278 meta: meta,
4279 index: index,
4280 easingValue: easingValue
4281 };
4282
4283 if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
4284 return;
4285 }
4286
4287 meta.controller.draw(easingValue);
4288
4289 plugins.notify(me, 'afterDatasetDraw', [args]);
4290 },
4291
4292 /**
4293 * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
4294 * hook, in which case, plugins will not be called on `afterTooltipDraw`.
4295 * @private
4296 */
4297 _drawTooltip: function(easingValue) {
4298 var me = this;
4299 var tooltip = me.tooltip;
4300 var args = {
4301 tooltip: tooltip,
4302 easingValue: easingValue
4303 };
4304
4305 if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
4306 return;
4307 }
4308
4309 tooltip.draw();
4310
4311 plugins.notify(me, 'afterTooltipDraw', [args]);
4312 },
4313
4314 // Get the single element that was clicked on
4315 // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
4316 getElementAtEvent: function(e) {
4317 return Interaction.modes.single(this, e);
4318 },
4319
4320 getElementsAtEvent: function(e) {
4321 return Interaction.modes.label(this, e, {intersect: true});
4322 },
4323
4324 getElementsAtXAxis: function(e) {
4325 return Interaction.modes['x-axis'](this, e, {intersect: true});
4326 },
4327
4328 getElementsAtEventForMode: function(e, mode, options) {
4329 var method = Interaction.modes[mode];
4330 if (typeof method === 'function') {
4331 return method(this, e, options);
4332 }
4333
4334 return [];
4335 },
4336
4337 getDatasetAtEvent: function(e) {
4338 return Interaction.modes.dataset(this, e, {intersect: true});
4339 },
4340
4341 getDatasetMeta: function(datasetIndex) {
4342 var me = this;
4343 var dataset = me.data.datasets[datasetIndex];
4344 if (!dataset._meta) {
4345 dataset._meta = {};
4346 }
4347
4348 var meta = dataset._meta[me.id];
4349 if (!meta) {
4350 meta = dataset._meta[me.id] = {
4351 type: null,
4352 data: [],
4353 dataset: null,
4354 controller: null,
4355 hidden: null, // See isDatasetVisible() comment
4356 xAxisID: null,
4357 yAxisID: null
4358 };
4359 }
4360
4361 return meta;
4362 },
4363
4364 getVisibleDatasetCount: function() {
4365 var count = 0;
4366 for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
4367 if (this.isDatasetVisible(i)) {
4368 count++;
4369 }
4370 }
4371 return count;
4372 },
4373
4374 isDatasetVisible: function(datasetIndex) {
4375 var meta = this.getDatasetMeta(datasetIndex);
4376
4377 // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
4378 // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
4379 return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
4380 },
4381
4382 generateLegend: function() {
4383 return this.options.legendCallback(this);
4384 },
4385
4386 /**
4387 * @private
4388 */
4389 destroyDatasetMeta: function(datasetIndex) {
4390 var id = this.id;
4391 var dataset = this.data.datasets[datasetIndex];
4392 var meta = dataset._meta && dataset._meta[id];
4393
4394 if (meta) {
4395 meta.controller.destroy();
4396 delete dataset._meta[id];
4397 }
4398 },
4399
4400 destroy: function() {
4401 var me = this;
4402 var canvas = me.canvas;
4403 var i, ilen;
4404
4405 me.stop();
4406
4407 // dataset controllers need to cleanup associated data
4408 for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4409 me.destroyDatasetMeta(i);
4410 }
4411
4412 if (canvas) {
4413 me.unbindEvents();
4414 helpers.canvas.clear(me);
4415 platform.releaseContext(me.ctx);
4416 me.canvas = null;
4417 me.ctx = null;
4418 }
4419
4420 plugins.notify(me, 'destroy');
4421
4422 delete Chart.instances[me.id];
4423 },
4424
4425 toBase64Image: function() {
4426 return this.canvas.toDataURL.apply(this.canvas, arguments);
4427 },
4428
4429 initToolTip: function() {
4430 var me = this;
4431 me.tooltip = new Chart.Tooltip({
4432 _chart: me,
4433 _chartInstance: me, // deprecated, backward compatibility
4434 _data: me.data,
4435 _options: me.options.tooltips
4436 }, me);
4437 },
4438
4439 /**
4440 * @private
4441 */
4442 bindEvents: function() {
4443 var me = this;
4444 var listeners = me._listeners = {};
4445 var listener = function() {
4446 me.eventHandler.apply(me, arguments);
4447 };
4448
4449 helpers.each(me.options.events, function(type) {
4450 platform.addEventListener(me, type, listener);
4451 listeners[type] = listener;
4452 });
4453
4454 // Elements used to detect size change should not be injected for non responsive charts.
4455 // See https://github.com/chartjs/Chart.js/issues/2210
4456 if (me.options.responsive) {
4457 listener = function() {
4458 me.resize();
4459 };
4460
4461 platform.addEventListener(me, 'resize', listener);
4462 listeners.resize = listener;
4463 }
4464 },
4465
4466 /**
4467 * @private
4468 */
4469 unbindEvents: function() {
4470 var me = this;
4471 var listeners = me._listeners;
4472 if (!listeners) {
4473 return;
4474 }
4475
4476 delete me._listeners;
4477 helpers.each(listeners, function(listener, type) {
4478 platform.removeEventListener(me, type, listener);
4479 });
4480 },
4481
4482 updateHoverStyle: function(elements, mode, enabled) {
4483 var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
4484 var element, i, ilen;
4485
4486 for (i = 0, ilen = elements.length; i < ilen; ++i) {
4487 element = elements[i];
4488 if (element) {
4489 this.getDatasetMeta(element._datasetIndex).controller[method](element);
4490 }
4491 }
4492 },
4493
4494 /**
4495 * @private
4496 */
4497 eventHandler: function(e) {
4498 var me = this;
4499 var tooltip = me.tooltip;
4500
4501 if (plugins.notify(me, 'beforeEvent', [e]) === false) {
4502 return;
4503 }
4504
4505 // Buffer any update calls so that renders do not occur
4506 me._bufferedRender = true;
4507 me._bufferedRequest = null;
4508
4509 var changed = me.handleEvent(e);
4510 changed |= tooltip && tooltip.handleEvent(e);
4511
4512 plugins.notify(me, 'afterEvent', [e]);
4513
4514 var bufferedRequest = me._bufferedRequest;
4515 if (bufferedRequest) {
4516 // If we have an update that was triggered, we need to do a normal render
4517 me.render(bufferedRequest);
4518 } else if (changed && !me.animating) {
4519 // If entering, leaving, or changing elements, animate the change via pivot
4520 me.stop();
4521
4522 // We only need to render at this point. Updating will cause scales to be
4523 // recomputed generating flicker & using more memory than necessary.
4524 me.render(me.options.hover.animationDuration, true);
4525 }
4526
4527 me._bufferedRender = false;
4528 me._bufferedRequest = null;
4529
4530 return me;
4531 },
4532
4533 /**
4534 * Handle an event
4535 * @private
4536 * @param {IEvent} event the event to handle
4537 * @return {Boolean} true if the chart needs to re-render
4538 */
4539 handleEvent: function(e) {
4540 var me = this;
4541 var options = me.options || {};
4542 var hoverOptions = options.hover;
4543 var changed = false;
4544
4545 me.lastActive = me.lastActive || [];
4546
4547 // Find Active Elements for hover and tooltips
4548 if (e.type === 'mouseout') {
4549 me.active = [];
4550 } else {
4551 me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
4552 }
4553
4554 // Invoke onHover hook
4555 // Need to call with native event here to not break backwards compatibility
4556 helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
4557
4558 if (e.type === 'mouseup' || e.type === 'click') {
4559 if (options.onClick) {
4560 // Use e.native here for backwards compatibility
4561 options.onClick.call(me, e.native, me.active);
4562 }
4563 }
4564
4565 // Remove styling for last active (even if it may still be active)
4566 if (me.lastActive.length) {
4567 me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
4568 }
4569
4570 // Built in hover styling
4571 if (me.active.length && hoverOptions.mode) {
4572 me.updateHoverStyle(me.active, hoverOptions.mode, true);
4573 }
4574
4575 changed = !helpers.arrayEquals(me.active, me.lastActive);
4576
4577 // Remember Last Actives
4578 me.lastActive = me.active;
4579
4580 return changed;
4581 }
4582 });
4583
4584 /**
4585 * Provided for backward compatibility, use Chart instead.
4586 * @class Chart.Controller
4587 * @deprecated since version 2.6.0
4588 * @todo remove at version 3
4589 * @private
4590 */
4591 Chart.Controller = Chart;
4592 };
4593
4594 },{"25":25,"28":28,"45":45,"48":48}],24:[function(require,module,exports){
4595 'use strict';
4596
4597 var helpers = require(45);
4598
4599 module.exports = function(Chart) {
4600
4601 var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
4602
4603 /**
4604 * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
4605 * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
4606 * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
4607 */
4608 function listenArrayEvents(array, listener) {
4609 if (array._chartjs) {
4610 array._chartjs.listeners.push(listener);
4611 return;
4612 }
4613
4614 Object.defineProperty(array, '_chartjs', {
4615 configurable: true,
4616 enumerable: false,
4617 value: {
4618 listeners: [listener]
4619 }
4620 });
4621
4622 arrayEvents.forEach(function(key) {
4623 var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
4624 var base = array[key];
4625
4626 Object.defineProperty(array, key, {
4627 configurable: true,
4628 enumerable: false,
4629 value: function() {
4630 var args = Array.prototype.slice.call(arguments);
4631 var res = base.apply(this, args);
4632
4633 helpers.each(array._chartjs.listeners, function(object) {
4634 if (typeof object[method] === 'function') {
4635 object[method].apply(object, args);
4636 }
4637 });
4638
4639 return res;
4640 }
4641 });
4642 });
4643 }
4644
4645 /**
4646 * Removes the given array event listener and cleanup extra attached properties (such as
4647 * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
4648 */
4649 function unlistenArrayEvents(array, listener) {
4650 var stub = array._chartjs;
4651 if (!stub) {
4652 return;
4653 }
4654
4655 var listeners = stub.listeners;
4656 var index = listeners.indexOf(listener);
4657 if (index !== -1) {
4658 listeners.splice(index, 1);
4659 }
4660
4661 if (listeners.length > 0) {
4662 return;
4663 }
4664
4665 arrayEvents.forEach(function(key) {
4666 delete array[key];
4667 });
4668
4669 delete array._chartjs;
4670 }
4671
4672 // Base class for all dataset controllers (line, bar, etc)
4673 Chart.DatasetController = function(chart, datasetIndex) {
4674 this.initialize(chart, datasetIndex);
4675 };
4676
4677 helpers.extend(Chart.DatasetController.prototype, {
4678
4679 /**
4680 * Element type used to generate a meta dataset (e.g. Chart.element.Line).
4681 * @type {Chart.core.element}
4682 */
4683 datasetElementType: null,
4684
4685 /**
4686 * Element type used to generate a meta data (e.g. Chart.element.Point).
4687 * @type {Chart.core.element}
4688 */
4689 dataElementType: null,
4690
4691 initialize: function(chart, datasetIndex) {
4692 var me = this;
4693 me.chart = chart;
4694 me.index = datasetIndex;
4695 me.linkScales();
4696 me.addElements();
4697 },
4698
4699 updateIndex: function(datasetIndex) {
4700 this.index = datasetIndex;
4701 },
4702
4703 linkScales: function() {
4704 var me = this;
4705 var meta = me.getMeta();
4706 var dataset = me.getDataset();
4707
4708 if (meta.xAxisID === null) {
4709 meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
4710 }
4711 if (meta.yAxisID === null) {
4712 meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
4713 }
4714 },
4715
4716 getDataset: function() {
4717 return this.chart.data.datasets[this.index];
4718 },
4719
4720 getMeta: function() {
4721 return this.chart.getDatasetMeta(this.index);
4722 },
4723
4724 getScaleForId: function(scaleID) {
4725 return this.chart.scales[scaleID];
4726 },
4727
4728 reset: function() {
4729 this.update(true);
4730 },
4731
4732 /**
4733 * @private
4734 */
4735 destroy: function() {
4736 if (this._data) {
4737 unlistenArrayEvents(this._data, this);
4738 }
4739 },
4740
4741 createMetaDataset: function() {
4742 var me = this;
4743 var type = me.datasetElementType;
4744 return type && new type({
4745 _chart: me.chart,
4746 _datasetIndex: me.index
4747 });
4748 },
4749
4750 createMetaData: function(index) {
4751 var me = this;
4752 var type = me.dataElementType;
4753 return type && new type({
4754 _chart: me.chart,
4755 _datasetIndex: me.index,
4756 _index: index
4757 });
4758 },
4759
4760 addElements: function() {
4761 var me = this;
4762 var meta = me.getMeta();
4763 var data = me.getDataset().data || [];
4764 var metaData = meta.data;
4765 var i, ilen;
4766
4767 for (i = 0, ilen = data.length; i < ilen; ++i) {
4768 metaData[i] = metaData[i] || me.createMetaData(i);
4769 }
4770
4771 meta.dataset = meta.dataset || me.createMetaDataset();
4772 },
4773
4774 addElementAndReset: function(index) {
4775 var element = this.createMetaData(index);
4776 this.getMeta().data.splice(index, 0, element);
4777 this.updateElement(element, index, true);
4778 },
4779
4780 buildOrUpdateElements: function() {
4781 var me = this;
4782 var dataset = me.getDataset();
4783 var data = dataset.data || (dataset.data = []);
4784
4785 // In order to correctly handle data addition/deletion animation (an thus simulate
4786 // real-time charts), we need to monitor these data modifications and synchronize
4787 // the internal meta data accordingly.
4788 if (me._data !== data) {
4789 if (me._data) {
4790 // This case happens when the user replaced the data array instance.
4791 unlistenArrayEvents(me._data, me);
4792 }
4793
4794 listenArrayEvents(data, me);
4795 me._data = data;
4796 }
4797
4798 // Re-sync meta data in case the user replaced the data array or if we missed
4799 // any updates and so make sure that we handle number of datapoints changing.
4800 me.resyncElements();
4801 },
4802
4803 update: helpers.noop,
4804
4805 transition: function(easingValue) {
4806 var meta = this.getMeta();
4807 var elements = meta.data || [];
4808 var ilen = elements.length;
4809 var i = 0;
4810
4811 for (; i < ilen; ++i) {
4812 elements[i].transition(easingValue);
4813 }
4814
4815 if (meta.dataset) {
4816 meta.dataset.transition(easingValue);
4817 }
4818 },
4819
4820 draw: function() {
4821 var meta = this.getMeta();
4822 var elements = meta.data || [];
4823 var ilen = elements.length;
4824 var i = 0;
4825
4826 if (meta.dataset) {
4827 meta.dataset.draw();
4828 }
4829
4830 for (; i < ilen; ++i) {
4831 elements[i].draw();
4832 }
4833 },
4834
4835 removeHoverStyle: function(element, elementOpts) {
4836 var dataset = this.chart.data.datasets[element._datasetIndex];
4837 var index = element._index;
4838 var custom = element.custom || {};
4839 var valueOrDefault = helpers.valueAtIndexOrDefault;
4840 var model = element._model;
4841
4842 model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
4843 model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
4844 model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
4845 },
4846
4847 setHoverStyle: function(element) {
4848 var dataset = this.chart.data.datasets[element._datasetIndex];
4849 var index = element._index;
4850 var custom = element.custom || {};
4851 var valueOrDefault = helpers.valueAtIndexOrDefault;
4852 var getHoverColor = helpers.getHoverColor;
4853 var model = element._model;
4854
4855 model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
4856 model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
4857 model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
4858 },
4859
4860 /**
4861 * @private
4862 */
4863 resyncElements: function() {
4864 var me = this;
4865 var meta = me.getMeta();
4866 var data = me.getDataset().data;
4867 var numMeta = meta.data.length;
4868 var numData = data.length;
4869
4870 if (numData < numMeta) {
4871 meta.data.splice(numData, numMeta - numData);
4872 } else if (numData > numMeta) {
4873 me.insertElements(numMeta, numData - numMeta);
4874 }
4875 },
4876
4877 /**
4878 * @private
4879 */
4880 insertElements: function(start, count) {
4881 for (var i = 0; i < count; ++i) {
4882 this.addElementAndReset(start + i);
4883 }
4884 },
4885
4886 /**
4887 * @private
4888 */
4889 onDataPush: function() {
4890 this.insertElements(this.getDataset().data.length - 1, arguments.length);
4891 },
4892
4893 /**
4894 * @private
4895 */
4896 onDataPop: function() {
4897 this.getMeta().data.pop();
4898 },
4899
4900 /**
4901 * @private
4902 */
4903 onDataShift: function() {
4904 this.getMeta().data.shift();
4905 },
4906
4907 /**
4908 * @private
4909 */
4910 onDataSplice: function(start, count) {
4911 this.getMeta().data.splice(start, count);
4912 this.insertElements(start, arguments.length - 2);
4913 },
4914
4915 /**
4916 * @private
4917 */
4918 onDataUnshift: function() {
4919 this.insertElements(0, arguments.length);
4920 }
4921 });
4922
4923 Chart.DatasetController.extend = helpers.inherits;
4924 };
4925
4926 },{"45":45}],25:[function(require,module,exports){
4927 'use strict';
4928
4929 var helpers = require(45);
4930
4931 module.exports = {
4932 /**
4933 * @private
4934 */
4935 _set: function(scope, values) {
4936 return helpers.merge(this[scope] || (this[scope] = {}), values);
4937 }
4938 };
4939
4940 },{"45":45}],26:[function(require,module,exports){
4941 'use strict';
4942
4943 var color = require(3);
4944 var helpers = require(45);
4945
4946 function interpolate(start, view, model, ease) {
4947 var keys = Object.keys(model);
4948 var i, ilen, key, actual, origin, target, type, c0, c1;
4949
4950 for (i = 0, ilen = keys.length; i < ilen; ++i) {
4951 key = keys[i];
4952
4953 target = model[key];
4954
4955 // if a value is added to the model after pivot() has been called, the view
4956 // doesn't contain it, so let's initialize the view to the target value.
4957 if (!view.hasOwnProperty(key)) {
4958 view[key] = target;
4959 }
4960
4961 actual = view[key];
4962
4963 if (actual === target || key[0] === '_') {
4964 continue;
4965 }
4966
4967 if (!start.hasOwnProperty(key)) {
4968 start[key] = actual;
4969 }
4970
4971 origin = start[key];
4972
4973 type = typeof target;
4974
4975 if (type === typeof origin) {
4976 if (type === 'string') {
4977 c0 = color(origin);
4978 if (c0.valid) {
4979 c1 = color(target);
4980 if (c1.valid) {
4981 view[key] = c1.mix(c0, ease).rgbString();
4982 continue;
4983 }
4984 }
4985 } else if (type === 'number' && isFinite(origin) && isFinite(target)) {
4986 view[key] = origin + (target - origin) * ease;
4987 continue;
4988 }
4989 }
4990
4991 view[key] = target;
4992 }
4993 }
4994
4995 var Element = function(configuration) {
4996 helpers.extend(this, configuration);
4997 this.initialize.apply(this, arguments);
4998 };
4999
5000 helpers.extend(Element.prototype, {
5001
5002 initialize: function() {
5003 this.hidden = false;
5004 },
5005
5006 pivot: function() {
5007 var me = this;
5008 if (!me._view) {
5009 me._view = helpers.clone(me._model);
5010 }
5011 me._start = {};
5012 return me;
5013 },
5014
5015 transition: function(ease) {
5016 var me = this;
5017 var model = me._model;
5018 var start = me._start;
5019 var view = me._view;
5020
5021 // No animation -> No Transition
5022 if (!model || ease === 1) {
5023 me._view = model;
5024 me._start = null;
5025 return me;
5026 }
5027
5028 if (!view) {
5029 view = me._view = {};
5030 }
5031
5032 if (!start) {
5033 start = me._start = {};
5034 }
5035
5036 interpolate(start, view, model, ease);
5037
5038 return me;
5039 },
5040
5041 tooltipPosition: function() {
5042 return {
5043 x: this._model.x,
5044 y: this._model.y
5045 };
5046 },
5047
5048 hasValue: function() {
5049 return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
5050 }
5051 });
5052
5053 Element.extend = helpers.inherits;
5054
5055 module.exports = Element;
5056
5057 },{"3":3,"45":45}],27:[function(require,module,exports){
5058 /* global window: false */
5059 /* global document: false */
5060 'use strict';
5061
5062 var color = require(3);
5063 var defaults = require(25);
5064 var helpers = require(45);
5065
5066 module.exports = function(Chart) {
5067
5068 // -- Basic js utility methods
5069
5070 helpers.configMerge = function(/* objects ... */) {
5071 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5072 merger: function(key, target, source, options) {
5073 var tval = target[key] || {};
5074 var sval = source[key];
5075
5076 if (key === 'scales') {
5077 // scale config merging is complex. Add our own function here for that
5078 target[key] = helpers.scaleMerge(tval, sval);
5079 } else if (key === 'scale') {
5080 // used in polar area & radar charts since there is only one scale
5081 target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]);
5082 } else {
5083 helpers._merger(key, target, source, options);
5084 }
5085 }
5086 });
5087 };
5088
5089 helpers.scaleMerge = function(/* objects ... */) {
5090 return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5091 merger: function(key, target, source, options) {
5092 if (key === 'xAxes' || key === 'yAxes') {
5093 var slen = source[key].length;
5094 var i, type, scale;
5095
5096 if (!target[key]) {
5097 target[key] = [];
5098 }
5099
5100 for (i = 0; i < slen; ++i) {
5101 scale = source[key][i];
5102 type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear');
5103
5104 if (i >= target[key].length) {
5105 target[key].push({});
5106 }
5107
5108 if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
5109 // new/untyped scale or type changed: let's apply the new defaults
5110 // then merge source scale to correctly overwrite the defaults.
5111 helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]);
5112 } else {
5113 // scales type are the same
5114 helpers.merge(target[key][i], scale);
5115 }
5116 }
5117 } else {
5118 helpers._merger(key, target, source, options);
5119 }
5120 }
5121 });
5122 };
5123
5124 helpers.where = function(collection, filterCallback) {
5125 if (helpers.isArray(collection) && Array.prototype.filter) {
5126 return collection.filter(filterCallback);
5127 }
5128 var filtered = [];
5129
5130 helpers.each(collection, function(item) {
5131 if (filterCallback(item)) {
5132 filtered.push(item);
5133 }
5134 });
5135
5136 return filtered;
5137 };
5138 helpers.findIndex = Array.prototype.findIndex ?
5139 function(array, callback, scope) {
5140 return array.findIndex(callback, scope);
5141 } :
5142 function(array, callback, scope) {
5143 scope = scope === undefined ? array : scope;
5144 for (var i = 0, ilen = array.length; i < ilen; ++i) {
5145 if (callback.call(scope, array[i], i, array)) {
5146 return i;
5147 }
5148 }
5149 return -1;
5150 };
5151 helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
5152 // Default to start of the array
5153 if (helpers.isNullOrUndef(startIndex)) {
5154 startIndex = -1;
5155 }
5156 for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
5157 var currentItem = arrayToSearch[i];
5158 if (filterCallback(currentItem)) {
5159 return currentItem;
5160 }
5161 }
5162 };
5163 helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
5164 // Default to end of the array
5165 if (helpers.isNullOrUndef(startIndex)) {
5166 startIndex = arrayToSearch.length;
5167 }
5168 for (var i = startIndex - 1; i >= 0; i--) {
5169 var currentItem = arrayToSearch[i];
5170 if (filterCallback(currentItem)) {
5171 return currentItem;
5172 }
5173 }
5174 };
5175
5176 // -- Math methods
5177 helpers.isNumber = function(n) {
5178 return !isNaN(parseFloat(n)) && isFinite(n);
5179 };
5180 helpers.almostEquals = function(x, y, epsilon) {
5181 return Math.abs(x - y) < epsilon;
5182 };
5183 helpers.almostWhole = function(x, epsilon) {
5184 var rounded = Math.round(x);
5185 return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
5186 };
5187 helpers.max = function(array) {
5188 return array.reduce(function(max, value) {
5189 if (!isNaN(value)) {
5190 return Math.max(max, value);
5191 }
5192 return max;
5193 }, Number.NEGATIVE_INFINITY);
5194 };
5195 helpers.min = function(array) {
5196 return array.reduce(function(min, value) {
5197 if (!isNaN(value)) {
5198 return Math.min(min, value);
5199 }
5200 return min;
5201 }, Number.POSITIVE_INFINITY);
5202 };
5203 helpers.sign = Math.sign ?
5204 function(x) {
5205 return Math.sign(x);
5206 } :
5207 function(x) {
5208 x = +x; // convert to a number
5209 if (x === 0 || isNaN(x)) {
5210 return x;
5211 }
5212 return x > 0 ? 1 : -1;
5213 };
5214 helpers.log10 = Math.log10 ?
5215 function(x) {
5216 return Math.log10(x);
5217 } :
5218 function(x) {
5219 return Math.log(x) / Math.LN10;
5220 };
5221 helpers.toRadians = function(degrees) {
5222 return degrees * (Math.PI / 180);
5223 };
5224 helpers.toDegrees = function(radians) {
5225 return radians * (180 / Math.PI);
5226 };
5227 // Gets the angle from vertical upright to the point about a centre.
5228 helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
5229 var distanceFromXCenter = anglePoint.x - centrePoint.x;
5230 var distanceFromYCenter = anglePoint.y - centrePoint.y;
5231 var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
5232
5233 var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
5234
5235 if (angle < (-0.5 * Math.PI)) {
5236 angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
5237 }
5238
5239 return {
5240 angle: angle,
5241 distance: radialDistanceFromCenter
5242 };
5243 };
5244 helpers.distanceBetweenPoints = function(pt1, pt2) {
5245 return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
5246 };
5247 helpers.aliasPixel = function(pixelWidth) {
5248 return (pixelWidth % 2 === 0) ? 0 : 0.5;
5249 };
5250 helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
5251 // Props to Rob Spencer at scaled innovation for his post on splining between points
5252 // http://scaledinnovation.com/analytics/splines/aboutSplines.html
5253
5254 // This function must also respect "skipped" points
5255
5256 var previous = firstPoint.skip ? middlePoint : firstPoint;
5257 var current = middlePoint;
5258 var next = afterPoint.skip ? middlePoint : afterPoint;
5259
5260 var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
5261 var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
5262
5263 var s01 = d01 / (d01 + d12);
5264 var s12 = d12 / (d01 + d12);
5265
5266 // If all points are the same, s01 & s02 will be inf
5267 s01 = isNaN(s01) ? 0 : s01;
5268 s12 = isNaN(s12) ? 0 : s12;
5269
5270 var fa = t * s01; // scaling factor for triangle Ta
5271 var fb = t * s12;
5272
5273 return {
5274 previous: {
5275 x: current.x - fa * (next.x - previous.x),
5276 y: current.y - fa * (next.y - previous.y)
5277 },
5278 next: {
5279 x: current.x + fb * (next.x - previous.x),
5280 y: current.y + fb * (next.y - previous.y)
5281 }
5282 };
5283 };
5284 helpers.EPSILON = Number.EPSILON || 1e-14;
5285 helpers.splineCurveMonotone = function(points) {
5286 // This function calculates Bézier control points in a similar way than |splineCurve|,
5287 // but preserves monotonicity of the provided data and ensures no local extremums are added
5288 // between the dataset discrete points due to the interpolation.
5289 // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
5290
5291 var pointsWithTangents = (points || []).map(function(point) {
5292 return {
5293 model: point._model,
5294 deltaK: 0,
5295 mK: 0
5296 };
5297 });
5298
5299 // Calculate slopes (deltaK) and initialize tangents (mK)
5300 var pointsLen = pointsWithTangents.length;
5301 var i, pointBefore, pointCurrent, pointAfter;
5302 for (i = 0; i < pointsLen; ++i) {
5303 pointCurrent = pointsWithTangents[i];
5304 if (pointCurrent.model.skip) {
5305 continue;
5306 }
5307
5308 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5309 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5310 if (pointAfter && !pointAfter.model.skip) {
5311 var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
5312
5313 // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
5314 pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
5315 }
5316
5317 if (!pointBefore || pointBefore.model.skip) {
5318 pointCurrent.mK = pointCurrent.deltaK;
5319 } else if (!pointAfter || pointAfter.model.skip) {
5320 pointCurrent.mK = pointBefore.deltaK;
5321 } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
5322 pointCurrent.mK = 0;
5323 } else {
5324 pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
5325 }
5326 }
5327
5328 // Adjust tangents to ensure monotonic properties
5329 var alphaK, betaK, tauK, squaredMagnitude;
5330 for (i = 0; i < pointsLen - 1; ++i) {
5331 pointCurrent = pointsWithTangents[i];
5332 pointAfter = pointsWithTangents[i + 1];
5333 if (pointCurrent.model.skip || pointAfter.model.skip) {
5334 continue;
5335 }
5336
5337 if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
5338 pointCurrent.mK = pointAfter.mK = 0;
5339 continue;
5340 }
5341
5342 alphaK = pointCurrent.mK / pointCurrent.deltaK;
5343 betaK = pointAfter.mK / pointCurrent.deltaK;
5344 squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
5345 if (squaredMagnitude <= 9) {
5346 continue;
5347 }
5348
5349 tauK = 3 / Math.sqrt(squaredMagnitude);
5350 pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
5351 pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
5352 }
5353
5354 // Compute control points
5355 var deltaX;
5356 for (i = 0; i < pointsLen; ++i) {
5357 pointCurrent = pointsWithTangents[i];
5358 if (pointCurrent.model.skip) {
5359 continue;
5360 }
5361
5362 pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5363 pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5364 if (pointBefore && !pointBefore.model.skip) {
5365 deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
5366 pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
5367 pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
5368 }
5369 if (pointAfter && !pointAfter.model.skip) {
5370 deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
5371 pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
5372 pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
5373 }
5374 }
5375 };
5376 helpers.nextItem = function(collection, index, loop) {
5377 if (loop) {
5378 return index >= collection.length - 1 ? collection[0] : collection[index + 1];
5379 }
5380 return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
5381 };
5382 helpers.previousItem = function(collection, index, loop) {
5383 if (loop) {
5384 return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
5385 }
5386 return index <= 0 ? collection[0] : collection[index - 1];
5387 };
5388 // Implementation of the nice number algorithm used in determining where axis labels will go
5389 helpers.niceNum = function(range, round) {
5390 var exponent = Math.floor(helpers.log10(range));
5391 var fraction = range / Math.pow(10, exponent);
5392 var niceFraction;
5393
5394 if (round) {
5395 if (fraction < 1.5) {
5396 niceFraction = 1;
5397 } else if (fraction < 3) {
5398 niceFraction = 2;
5399 } else if (fraction < 7) {
5400 niceFraction = 5;
5401 } else {
5402 niceFraction = 10;
5403 }
5404 } else if (fraction <= 1.0) {
5405 niceFraction = 1;
5406 } else if (fraction <= 2) {
5407 niceFraction = 2;
5408 } else if (fraction <= 5) {
5409 niceFraction = 5;
5410 } else {
5411 niceFraction = 10;
5412 }
5413
5414 return niceFraction * Math.pow(10, exponent);
5415 };
5416 // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
5417 helpers.requestAnimFrame = (function() {
5418 if (typeof window === 'undefined') {
5419 return function(callback) {
5420 callback();
5421 };
5422 }
5423 return window.requestAnimationFrame ||
5424 window.webkitRequestAnimationFrame ||
5425 window.mozRequestAnimationFrame ||
5426 window.oRequestAnimationFrame ||
5427 window.msRequestAnimationFrame ||
5428 function(callback) {
5429 return window.setTimeout(callback, 1000 / 60);
5430 };
5431 }());
5432 // -- DOM methods
5433 helpers.getRelativePosition = function(evt, chart) {
5434 var mouseX, mouseY;
5435 var e = evt.originalEvent || evt;
5436 var canvas = evt.currentTarget || evt.srcElement;
5437 var boundingRect = canvas.getBoundingClientRect();
5438
5439 var touches = e.touches;
5440 if (touches && touches.length > 0) {
5441 mouseX = touches[0].clientX;
5442 mouseY = touches[0].clientY;
5443
5444 } else {
5445 mouseX = e.clientX;
5446 mouseY = e.clientY;
5447 }
5448
5449 // Scale mouse coordinates into canvas coordinates
5450 // by following the pattern laid out by 'jerryj' in the comments of
5451 // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
5452 var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
5453 var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
5454 var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
5455 var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
5456 var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
5457 var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
5458
5459 // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
5460 // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
5461 mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
5462 mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
5463
5464 return {
5465 x: mouseX,
5466 y: mouseY
5467 };
5468
5469 };
5470
5471 // Private helper function to convert max-width/max-height values that may be percentages into a number
5472 function parseMaxStyle(styleValue, node, parentProperty) {
5473 var valueInPixels;
5474 if (typeof styleValue === 'string') {
5475 valueInPixels = parseInt(styleValue, 10);
5476
5477 if (styleValue.indexOf('%') !== -1) {
5478 // percentage * size in dimension
5479 valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
5480 }
5481 } else {
5482 valueInPixels = styleValue;
5483 }
5484
5485 return valueInPixels;
5486 }
5487
5488 /**
5489 * Returns if the given value contains an effective constraint.
5490 * @private
5491 */
5492 function isConstrainedValue(value) {
5493 return value !== undefined && value !== null && value !== 'none';
5494 }
5495
5496 // Private helper to get a constraint dimension
5497 // @param domNode : the node to check the constraint on
5498 // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
5499 // @param percentageProperty : property of parent to use when calculating width as a percentage
5500 // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
5501 function getConstraintDimension(domNode, maxStyle, percentageProperty) {
5502 var view = document.defaultView;
5503 var parentNode = domNode.parentNode;
5504 var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
5505 var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
5506 var hasCNode = isConstrainedValue(constrainedNode);
5507 var hasCContainer = isConstrainedValue(constrainedContainer);
5508 var infinity = Number.POSITIVE_INFINITY;
5509
5510 if (hasCNode || hasCContainer) {
5511 return Math.min(
5512 hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
5513 hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
5514 }
5515
5516 return 'none';
5517 }
5518 // returns Number or undefined if no constraint
5519 helpers.getConstraintWidth = function(domNode) {
5520 return getConstraintDimension(domNode, 'max-width', 'clientWidth');
5521 };
5522 // returns Number or undefined if no constraint
5523 helpers.getConstraintHeight = function(domNode) {
5524 return getConstraintDimension(domNode, 'max-height', 'clientHeight');
5525 };
5526 helpers.getMaximumWidth = function(domNode) {
5527 var container = domNode.parentNode;
5528 if (!container) {
5529 return domNode.clientWidth;
5530 }
5531
5532 var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
5533 var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
5534 var w = container.clientWidth - paddingLeft - paddingRight;
5535 var cw = helpers.getConstraintWidth(domNode);
5536 return isNaN(cw) ? w : Math.min(w, cw);
5537 };
5538 helpers.getMaximumHeight = function(domNode) {
5539 var container = domNode.parentNode;
5540 if (!container) {
5541 return domNode.clientHeight;
5542 }
5543
5544 var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
5545 var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
5546 var h = container.clientHeight - paddingTop - paddingBottom;
5547 var ch = helpers.getConstraintHeight(domNode);
5548 return isNaN(ch) ? h : Math.min(h, ch);
5549 };
5550 helpers.getStyle = function(el, property) {
5551 return el.currentStyle ?
5552 el.currentStyle[property] :
5553 document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
5554 };
5555 helpers.retinaScale = function(chart, forceRatio) {
5556 var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1;
5557 if (pixelRatio === 1) {
5558 return;
5559 }
5560
5561 var canvas = chart.canvas;
5562 var height = chart.height;
5563 var width = chart.width;
5564
5565 canvas.height = height * pixelRatio;
5566 canvas.width = width * pixelRatio;
5567 chart.ctx.scale(pixelRatio, pixelRatio);
5568
5569 // If no style has been set on the canvas, the render size is used as display size,
5570 // making the chart visually bigger, so let's enforce it to the "correct" values.
5571 // See https://github.com/chartjs/Chart.js/issues/3575
5572 canvas.style.height = height + 'px';
5573 canvas.style.width = width + 'px';
5574 };
5575 // -- Canvas methods
5576 helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
5577 return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
5578 };
5579 helpers.longestText = function(ctx, font, arrayOfThings, cache) {
5580 cache = cache || {};
5581 var data = cache.data = cache.data || {};
5582 var gc = cache.garbageCollect = cache.garbageCollect || [];
5583
5584 if (cache.font !== font) {
5585 data = cache.data = {};
5586 gc = cache.garbageCollect = [];
5587 cache.font = font;
5588 }
5589
5590 ctx.font = font;
5591 var longest = 0;
5592 helpers.each(arrayOfThings, function(thing) {
5593 // Undefined strings and arrays should not be measured
5594 if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
5595 longest = helpers.measureText(ctx, data, gc, longest, thing);
5596 } else if (helpers.isArray(thing)) {
5597 // if it is an array lets measure each element
5598 // to do maybe simplify this function a bit so we can do this more recursively?
5599 helpers.each(thing, function(nestedThing) {
5600 // Undefined strings and arrays should not be measured
5601 if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
5602 longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
5603 }
5604 });
5605 }
5606 });
5607
5608 var gcLen = gc.length / 2;
5609 if (gcLen > arrayOfThings.length) {
5610 for (var i = 0; i < gcLen; i++) {
5611 delete data[gc[i]];
5612 }
5613 gc.splice(0, gcLen);
5614 }
5615 return longest;
5616 };
5617 helpers.measureText = function(ctx, data, gc, longest, string) {
5618 var textWidth = data[string];
5619 if (!textWidth) {
5620 textWidth = data[string] = ctx.measureText(string).width;
5621 gc.push(string);
5622 }
5623 if (textWidth > longest) {
5624 longest = textWidth;
5625 }
5626 return longest;
5627 };
5628 helpers.numberOfLabelLines = function(arrayOfThings) {
5629 var numberOfLines = 1;
5630 helpers.each(arrayOfThings, function(thing) {
5631 if (helpers.isArray(thing)) {
5632 if (thing.length > numberOfLines) {
5633 numberOfLines = thing.length;
5634 }
5635 }
5636 });
5637 return numberOfLines;
5638 };
5639
5640 helpers.color = !color ?
5641 function(value) {
5642 console.error('Color.js not found!');
5643 return value;
5644 } :
5645 function(value) {
5646 /* global CanvasGradient */
5647 if (value instanceof CanvasGradient) {
5648 value = defaults.global.defaultColor;
5649 }
5650
5651 return color(value);
5652 };
5653
5654 helpers.getHoverColor = function(colorValue) {
5655 /* global CanvasPattern */
5656 return (colorValue instanceof CanvasPattern) ?
5657 colorValue :
5658 helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
5659 };
5660 };
5661
5662 },{"25":25,"3":3,"45":45}],28:[function(require,module,exports){
5663 'use strict';
5664
5665 var helpers = require(45);
5666
5667 /**
5668 * Helper function to get relative position for an event
5669 * @param {Event|IEvent} event - The event to get the position for
5670 * @param {Chart} chart - The chart
5671 * @returns {Point} the event position
5672 */
5673 function getRelativePosition(e, chart) {
5674 if (e.native) {
5675 return {
5676 x: e.x,
5677 y: e.y
5678 };
5679 }
5680
5681 return helpers.getRelativePosition(e, chart);
5682 }
5683
5684 /**
5685 * Helper function to traverse all of the visible elements in the chart
5686 * @param chart {chart} the chart
5687 * @param handler {Function} the callback to execute for each visible item
5688 */
5689 function parseVisibleItems(chart, handler) {
5690 var datasets = chart.data.datasets;
5691 var meta, i, j, ilen, jlen;
5692
5693 for (i = 0, ilen = datasets.length; i < ilen; ++i) {
5694 if (!chart.isDatasetVisible(i)) {
5695 continue;
5696 }
5697
5698 meta = chart.getDatasetMeta(i);
5699 for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
5700 var element = meta.data[j];
5701 if (!element._view.skip) {
5702 handler(element);
5703 }
5704 }
5705 }
5706 }
5707
5708 /**
5709 * Helper function to get the items that intersect the event position
5710 * @param items {ChartElement[]} elements to filter
5711 * @param position {Point} the point to be nearest to
5712 * @return {ChartElement[]} the nearest items
5713 */
5714 function getIntersectItems(chart, position) {
5715 var elements = [];
5716
5717 parseVisibleItems(chart, function(element) {
5718 if (element.inRange(position.x, position.y)) {
5719 elements.push(element);
5720 }
5721 });
5722
5723 return elements;
5724 }
5725
5726 /**
5727 * Helper function to get the items nearest to the event position considering all visible items in teh chart
5728 * @param chart {Chart} the chart to look at elements from
5729 * @param position {Point} the point to be nearest to
5730 * @param intersect {Boolean} if true, only consider items that intersect the position
5731 * @param distanceMetric {Function} function to provide the distance between points
5732 * @return {ChartElement[]} the nearest items
5733 */
5734 function getNearestItems(chart, position, intersect, distanceMetric) {
5735 var minDistance = Number.POSITIVE_INFINITY;
5736 var nearestItems = [];
5737
5738 parseVisibleItems(chart, function(element) {
5739 if (intersect && !element.inRange(position.x, position.y)) {
5740 return;
5741 }
5742
5743 var center = element.getCenterPoint();
5744 var distance = distanceMetric(position, center);
5745
5746 if (distance < minDistance) {
5747 nearestItems = [element];
5748 minDistance = distance;
5749 } else if (distance === minDistance) {
5750 // Can have multiple items at the same distance in which case we sort by size
5751 nearestItems.push(element);
5752 }
5753 });
5754
5755 return nearestItems;
5756 }
5757
5758 /**
5759 * Get a distance metric function for two points based on the
5760 * axis mode setting
5761 * @param {String} axis the axis mode. x|y|xy
5762 */
5763 function getDistanceMetricForAxis(axis) {
5764 var useX = axis.indexOf('x') !== -1;
5765 var useY = axis.indexOf('y') !== -1;
5766
5767 return function(pt1, pt2) {
5768 var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
5769 var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
5770 return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
5771 };
5772 }
5773
5774 function indexMode(chart, e, options) {
5775 var position = getRelativePosition(e, chart);
5776 // Default axis for index mode is 'x' to match old behaviour
5777 options.axis = options.axis || 'x';
5778 var distanceMetric = getDistanceMetricForAxis(options.axis);
5779 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5780 var elements = [];
5781
5782 if (!items.length) {
5783 return [];
5784 }
5785
5786 chart.data.datasets.forEach(function(dataset, datasetIndex) {
5787 if (chart.isDatasetVisible(datasetIndex)) {
5788 var meta = chart.getDatasetMeta(datasetIndex);
5789 var element = meta.data[items[0]._index];
5790
5791 // don't count items that are skipped (null data)
5792 if (element && !element._view.skip) {
5793 elements.push(element);
5794 }
5795 }
5796 });
5797
5798 return elements;
5799 }
5800
5801 /**
5802 * @interface IInteractionOptions
5803 */
5804 /**
5805 * If true, only consider items that intersect the point
5806 * @name IInterfaceOptions#boolean
5807 * @type Boolean
5808 */
5809
5810 /**
5811 * Contains interaction related functions
5812 * @namespace Chart.Interaction
5813 */
5814 module.exports = {
5815 // Helper function for different modes
5816 modes: {
5817 single: function(chart, e) {
5818 var position = getRelativePosition(e, chart);
5819 var elements = [];
5820
5821 parseVisibleItems(chart, function(element) {
5822 if (element.inRange(position.x, position.y)) {
5823 elements.push(element);
5824 return elements;
5825 }
5826 });
5827
5828 return elements.slice(0, 1);
5829 },
5830
5831 /**
5832 * @function Chart.Interaction.modes.label
5833 * @deprecated since version 2.4.0
5834 * @todo remove at version 3
5835 * @private
5836 */
5837 label: indexMode,
5838
5839 /**
5840 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
5841 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
5842 * @function Chart.Interaction.modes.index
5843 * @since v2.4.0
5844 * @param chart {chart} the chart we are returning items from
5845 * @param e {Event} the event we are find things at
5846 * @param options {IInteractionOptions} options to use during interaction
5847 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5848 */
5849 index: indexMode,
5850
5851 /**
5852 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
5853 * If the options.intersect is false, we find the nearest item and return the items in that dataset
5854 * @function Chart.Interaction.modes.dataset
5855 * @param chart {chart} the chart we are returning items from
5856 * @param e {Event} the event we are find things at
5857 * @param options {IInteractionOptions} options to use during interaction
5858 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5859 */
5860 dataset: function(chart, e, options) {
5861 var position = getRelativePosition(e, chart);
5862 options.axis = options.axis || 'xy';
5863 var distanceMetric = getDistanceMetricForAxis(options.axis);
5864 var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5865
5866 if (items.length > 0) {
5867 items = chart.getDatasetMeta(items[0]._datasetIndex).data;
5868 }
5869
5870 return items;
5871 },
5872
5873 /**
5874 * @function Chart.Interaction.modes.x-axis
5875 * @deprecated since version 2.4.0. Use index mode and intersect == true
5876 * @todo remove at version 3
5877 * @private
5878 */
5879 'x-axis': function(chart, e) {
5880 return indexMode(chart, e, {intersect: false});
5881 },
5882
5883 /**
5884 * Point mode returns all elements that hit test based on the event position
5885 * of the event
5886 * @function Chart.Interaction.modes.intersect
5887 * @param chart {chart} the chart we are returning items from
5888 * @param e {Event} the event we are find things at
5889 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5890 */
5891 point: function(chart, e) {
5892 var position = getRelativePosition(e, chart);
5893 return getIntersectItems(chart, position);
5894 },
5895
5896 /**
5897 * nearest mode returns the element closest to the point
5898 * @function Chart.Interaction.modes.intersect
5899 * @param chart {chart} the chart we are returning items from
5900 * @param e {Event} the event we are find things at
5901 * @param options {IInteractionOptions} options to use
5902 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5903 */
5904 nearest: function(chart, e, options) {
5905 var position = getRelativePosition(e, chart);
5906 options.axis = options.axis || 'xy';
5907 var distanceMetric = getDistanceMetricForAxis(options.axis);
5908 var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric);
5909
5910 // We have multiple items at the same distance from the event. Now sort by smallest
5911 if (nearestItems.length > 1) {
5912 nearestItems.sort(function(a, b) {
5913 var sizeA = a.getArea();
5914 var sizeB = b.getArea();
5915 var ret = sizeA - sizeB;
5916
5917 if (ret === 0) {
5918 // if equal sort by dataset index
5919 ret = a._datasetIndex - b._datasetIndex;
5920 }
5921
5922 return ret;
5923 });
5924 }
5925
5926 // Return only 1 item
5927 return nearestItems.slice(0, 1);
5928 },
5929
5930 /**
5931 * x mode returns the elements that hit-test at the current x coordinate
5932 * @function Chart.Interaction.modes.x
5933 * @param chart {chart} the chart we are returning items from
5934 * @param e {Event} the event we are find things at
5935 * @param options {IInteractionOptions} options to use
5936 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5937 */
5938 x: function(chart, e, options) {
5939 var position = getRelativePosition(e, chart);
5940 var items = [];
5941 var intersectsItem = false;
5942
5943 parseVisibleItems(chart, function(element) {
5944 if (element.inXRange(position.x)) {
5945 items.push(element);
5946 }
5947
5948 if (element.inRange(position.x, position.y)) {
5949 intersectsItem = true;
5950 }
5951 });
5952
5953 // If we want to trigger on an intersect and we don't have any items
5954 // that intersect the position, return nothing
5955 if (options.intersect && !intersectsItem) {
5956 items = [];
5957 }
5958 return items;
5959 },
5960
5961 /**
5962 * y mode returns the elements that hit-test at the current y coordinate
5963 * @function Chart.Interaction.modes.y
5964 * @param chart {chart} the chart we are returning items from
5965 * @param e {Event} the event we are find things at
5966 * @param options {IInteractionOptions} options to use
5967 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5968 */
5969 y: function(chart, e, options) {
5970 var position = getRelativePosition(e, chart);
5971 var items = [];
5972 var intersectsItem = false;
5973
5974 parseVisibleItems(chart, function(element) {
5975 if (element.inYRange(position.y)) {
5976 items.push(element);
5977 }
5978
5979 if (element.inRange(position.x, position.y)) {
5980 intersectsItem = true;
5981 }
5982 });
5983
5984 // If we want to trigger on an intersect and we don't have any items
5985 // that intersect the position, return nothing
5986 if (options.intersect && !intersectsItem) {
5987 items = [];
5988 }
5989 return items;
5990 }
5991 }
5992 };
5993
5994 },{"45":45}],29:[function(require,module,exports){
5995 'use strict';
5996
5997 var defaults = require(25);
5998
5999 defaults._set('global', {
6000 responsive: true,
6001 responsiveAnimationDuration: 0,
6002 maintainAspectRatio: true,
6003 events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
6004 hover: {
6005 onHover: null,
6006 mode: 'nearest',
6007 intersect: true,
6008 animationDuration: 400
6009 },
6010 onClick: null,
6011 defaultColor: 'rgba(0,0,0,0.1)',
6012 defaultFontColor: '#666',
6013 defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
6014 defaultFontSize: 12,
6015 defaultFontStyle: 'normal',
6016 showLines: true,
6017
6018 // Element defaults defined in element extensions
6019 elements: {},
6020
6021 // Layout options such as padding
6022 layout: {
6023 padding: {
6024 top: 0,
6025 right: 0,
6026 bottom: 0,
6027 left: 0
6028 }
6029 }
6030 });
6031
6032 module.exports = function() {
6033
6034 // Occupy the global variable of Chart, and create a simple base class
6035 var Chart = function(item, config) {
6036 this.construct(item, config);
6037 return this;
6038 };
6039
6040 Chart.Chart = Chart;
6041
6042 return Chart;
6043 };
6044
6045 },{"25":25}],30:[function(require,module,exports){
6046 'use strict';
6047
6048 var helpers = require(45);
6049
6050 module.exports = function(Chart) {
6051
6052 function filterByPosition(array, position) {
6053 return helpers.where(array, function(v) {
6054 return v.position === position;
6055 });
6056 }
6057
6058 function sortByWeight(array, reverse) {
6059 array.forEach(function(v, i) {
6060 v._tmpIndex_ = i;
6061 return v;
6062 });
6063 array.sort(function(a, b) {
6064 var v0 = reverse ? b : a;
6065 var v1 = reverse ? a : b;
6066 return v0.weight === v1.weight ?
6067 v0._tmpIndex_ - v1._tmpIndex_ :
6068 v0.weight - v1.weight;
6069 });
6070 array.forEach(function(v) {
6071 delete v._tmpIndex_;
6072 });
6073 }
6074
6075 /**
6076 * @interface ILayoutItem
6077 * @prop {String} position - The position of the item in the chart layout. Possible values are
6078 * 'left', 'top', 'right', 'bottom', and 'chartArea'
6079 * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
6080 * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
6081 * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
6082 * @prop {Function} update - Takes two parameters: width and height. Returns size of item
6083 * @prop {Function} getPadding - Returns an object with padding on the edges
6084 * @prop {Number} width - Width of item. Must be valid after update()
6085 * @prop {Number} height - Height of item. Must be valid after update()
6086 * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
6087 * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
6088 * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
6089 * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
6090 */
6091
6092 // The layout service is very self explanatory. It's responsible for the layout within a chart.
6093 // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
6094 // It is this service's responsibility of carrying out that layout.
6095 Chart.layoutService = {
6096 defaults: {},
6097
6098 /**
6099 * Register a box to a chart.
6100 * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
6101 * @param {Chart} chart - the chart to use
6102 * @param {ILayoutItem} item - the item to add to be layed out
6103 */
6104 addBox: function(chart, item) {
6105 if (!chart.boxes) {
6106 chart.boxes = [];
6107 }
6108
6109 // initialize item with default values
6110 item.fullWidth = item.fullWidth || false;
6111 item.position = item.position || 'top';
6112 item.weight = item.weight || 0;
6113
6114 chart.boxes.push(item);
6115 },
6116
6117 /**
6118 * Remove a layoutItem from a chart
6119 * @param {Chart} chart - the chart to remove the box from
6120 * @param {Object} layoutItem - the item to remove from the layout
6121 */
6122 removeBox: function(chart, layoutItem) {
6123 var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
6124 if (index !== -1) {
6125 chart.boxes.splice(index, 1);
6126 }
6127 },
6128
6129 /**
6130 * Sets (or updates) options on the given `item`.
6131 * @param {Chart} chart - the chart in which the item lives (or will be added to)
6132 * @param {Object} item - the item to configure with the given options
6133 * @param {Object} options - the new item options.
6134 */
6135 configure: function(chart, item, options) {
6136 var props = ['fullWidth', 'position', 'weight'];
6137 var ilen = props.length;
6138 var i = 0;
6139 var prop;
6140
6141 for (; i < ilen; ++i) {
6142 prop = props[i];
6143 if (options.hasOwnProperty(prop)) {
6144 item[prop] = options[prop];
6145 }
6146 }
6147 },
6148
6149 /**
6150 * Fits boxes of the given chart into the given size by having each box measure itself
6151 * then running a fitting algorithm
6152 * @param {Chart} chart - the chart
6153 * @param {Number} width - the width to fit into
6154 * @param {Number} height - the height to fit into
6155 */
6156 update: function(chart, width, height) {
6157 if (!chart) {
6158 return;
6159 }
6160
6161 var layoutOptions = chart.options.layout || {};
6162 var padding = helpers.options.toPadding(layoutOptions.padding);
6163 var leftPadding = padding.left;
6164 var rightPadding = padding.right;
6165 var topPadding = padding.top;
6166 var bottomPadding = padding.bottom;
6167
6168 var leftBoxes = filterByPosition(chart.boxes, 'left');
6169 var rightBoxes = filterByPosition(chart.boxes, 'right');
6170 var topBoxes = filterByPosition(chart.boxes, 'top');
6171 var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
6172 var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
6173
6174 // Sort boxes by weight. A higher weight is further away from the chart area
6175 sortByWeight(leftBoxes, true);
6176 sortByWeight(rightBoxes, false);
6177 sortByWeight(topBoxes, true);
6178 sortByWeight(bottomBoxes, false);
6179
6180 // Essentially we now have any number of boxes on each of the 4 sides.
6181 // Our canvas looks like the following.
6182 // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
6183 // B1 is the bottom axis
6184 // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
6185 // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
6186 // an error will be thrown.
6187 //
6188 // |----------------------------------------------------|
6189 // | T1 (Full Width) |
6190 // |----------------------------------------------------|
6191 // | | | T2 | |
6192 // | |----|-------------------------------------|----|
6193 // | | | C1 | | C2 | |
6194 // | | |----| |----| |
6195 // | | | | |
6196 // | L1 | L2 | ChartArea (C0) | R1 |
6197 // | | | | |
6198 // | | |----| |----| |
6199 // | | | C3 | | C4 | |
6200 // | |----|-------------------------------------|----|
6201 // | | | B1 | |
6202 // |----------------------------------------------------|
6203 // | B2 (Full Width) |
6204 // |----------------------------------------------------|
6205 //
6206 // What we do to find the best sizing, we do the following
6207 // 1. Determine the minimum size of the chart area.
6208 // 2. Split the remaining width equally between each vertical axis
6209 // 3. Split the remaining height equally between each horizontal axis
6210 // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
6211 // 5. Adjust the sizes of each axis based on it's minimum reported size.
6212 // 6. Refit each axis
6213 // 7. Position each axis in the final location
6214 // 8. Tell the chart the final location of the chart area
6215 // 9. Tell any axes that overlay the chart area the positions of the chart area
6216
6217 // Step 1
6218 var chartWidth = width - leftPadding - rightPadding;
6219 var chartHeight = height - topPadding - bottomPadding;
6220 var chartAreaWidth = chartWidth / 2; // min 50%
6221 var chartAreaHeight = chartHeight / 2; // min 50%
6222
6223 // Step 2
6224 var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
6225
6226 // Step 3
6227 var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
6228
6229 // Step 4
6230 var maxChartAreaWidth = chartWidth;
6231 var maxChartAreaHeight = chartHeight;
6232 var minBoxSizes = [];
6233
6234 function getMinimumBoxSize(box) {
6235 var minSize;
6236 var isHorizontal = box.isHorizontal();
6237
6238 if (isHorizontal) {
6239 minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
6240 maxChartAreaHeight -= minSize.height;
6241 } else {
6242 minSize = box.update(verticalBoxWidth, chartAreaHeight);
6243 maxChartAreaWidth -= minSize.width;
6244 }
6245
6246 minBoxSizes.push({
6247 horizontal: isHorizontal,
6248 minSize: minSize,
6249 box: box,
6250 });
6251 }
6252
6253 helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
6254
6255 // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
6256 var maxHorizontalLeftPadding = 0;
6257 var maxHorizontalRightPadding = 0;
6258 var maxVerticalTopPadding = 0;
6259 var maxVerticalBottomPadding = 0;
6260
6261 helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
6262 if (horizontalBox.getPadding) {
6263 var boxPadding = horizontalBox.getPadding();
6264 maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
6265 maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
6266 }
6267 });
6268
6269 helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
6270 if (verticalBox.getPadding) {
6271 var boxPadding = verticalBox.getPadding();
6272 maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
6273 maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
6274 }
6275 });
6276
6277 // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
6278 // be if the axes are drawn at their minimum sizes.
6279 // Steps 5 & 6
6280 var totalLeftBoxesWidth = leftPadding;
6281 var totalRightBoxesWidth = rightPadding;
6282 var totalTopBoxesHeight = topPadding;
6283 var totalBottomBoxesHeight = bottomPadding;
6284
6285 // Function to fit a box
6286 function fitBox(box) {
6287 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
6288 return minBox.box === box;
6289 });
6290
6291 if (minBoxSize) {
6292 if (box.isHorizontal()) {
6293 var scaleMargin = {
6294 left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
6295 right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
6296 top: 0,
6297 bottom: 0
6298 };
6299
6300 // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
6301 // on the margin. Sometimes they need to increase in size slightly
6302 box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
6303 } else {
6304 box.update(minBoxSize.minSize.width, maxChartAreaHeight);
6305 }
6306 }
6307 }
6308
6309 // Update, and calculate the left and right margins for the horizontal boxes
6310 helpers.each(leftBoxes.concat(rightBoxes), fitBox);
6311
6312 helpers.each(leftBoxes, function(box) {
6313 totalLeftBoxesWidth += box.width;
6314 });
6315
6316 helpers.each(rightBoxes, function(box) {
6317 totalRightBoxesWidth += box.width;
6318 });
6319
6320 // Set the Left and Right margins for the horizontal boxes
6321 helpers.each(topBoxes.concat(bottomBoxes), fitBox);
6322
6323 // Figure out how much margin is on the top and bottom of the vertical boxes
6324 helpers.each(topBoxes, function(box) {
6325 totalTopBoxesHeight += box.height;
6326 });
6327
6328 helpers.each(bottomBoxes, function(box) {
6329 totalBottomBoxesHeight += box.height;
6330 });
6331
6332 function finalFitVerticalBox(box) {
6333 var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
6334 return minSize.box === box;
6335 });
6336
6337 var scaleMargin = {
6338 left: 0,
6339 right: 0,
6340 top: totalTopBoxesHeight,
6341 bottom: totalBottomBoxesHeight
6342 };
6343
6344 if (minBoxSize) {
6345 box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
6346 }
6347 }
6348
6349 // Let the left layout know the final margin
6350 helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
6351
6352 // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
6353 totalLeftBoxesWidth = leftPadding;
6354 totalRightBoxesWidth = rightPadding;
6355 totalTopBoxesHeight = topPadding;
6356 totalBottomBoxesHeight = bottomPadding;
6357
6358 helpers.each(leftBoxes, function(box) {
6359 totalLeftBoxesWidth += box.width;
6360 });
6361
6362 helpers.each(rightBoxes, function(box) {
6363 totalRightBoxesWidth += box.width;
6364 });
6365
6366 helpers.each(topBoxes, function(box) {
6367 totalTopBoxesHeight += box.height;
6368 });
6369 helpers.each(bottomBoxes, function(box) {
6370 totalBottomBoxesHeight += box.height;
6371 });
6372
6373 // We may be adding some padding to account for rotated x axis labels
6374 var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
6375 totalLeftBoxesWidth += leftPaddingAddition;
6376 totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
6377
6378 var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
6379 totalTopBoxesHeight += topPaddingAddition;
6380 totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
6381
6382 // Figure out if our chart area changed. This would occur if the dataset layout label rotation
6383 // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
6384 // without calling `fit` again
6385 var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
6386 var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
6387
6388 if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
6389 helpers.each(leftBoxes, function(box) {
6390 box.height = newMaxChartAreaHeight;
6391 });
6392
6393 helpers.each(rightBoxes, function(box) {
6394 box.height = newMaxChartAreaHeight;
6395 });
6396
6397 helpers.each(topBoxes, function(box) {
6398 if (!box.fullWidth) {
6399 box.width = newMaxChartAreaWidth;
6400 }
6401 });
6402
6403 helpers.each(bottomBoxes, function(box) {
6404 if (!box.fullWidth) {
6405 box.width = newMaxChartAreaWidth;
6406 }
6407 });
6408
6409 maxChartAreaHeight = newMaxChartAreaHeight;
6410 maxChartAreaWidth = newMaxChartAreaWidth;
6411 }
6412
6413 // Step 7 - Position the boxes
6414 var left = leftPadding + leftPaddingAddition;
6415 var top = topPadding + topPaddingAddition;
6416
6417 function placeBox(box) {
6418 if (box.isHorizontal()) {
6419 box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
6420 box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
6421 box.top = top;
6422 box.bottom = top + box.height;
6423
6424 // Move to next point
6425 top = box.bottom;
6426
6427 } else {
6428
6429 box.left = left;
6430 box.right = left + box.width;
6431 box.top = totalTopBoxesHeight;
6432 box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
6433
6434 // Move to next point
6435 left = box.right;
6436 }
6437 }
6438
6439 helpers.each(leftBoxes.concat(topBoxes), placeBox);
6440
6441 // Account for chart width and height
6442 left += maxChartAreaWidth;
6443 top += maxChartAreaHeight;
6444
6445 helpers.each(rightBoxes, placeBox);
6446 helpers.each(bottomBoxes, placeBox);
6447
6448 // Step 8
6449 chart.chartArea = {
6450 left: totalLeftBoxesWidth,
6451 top: totalTopBoxesHeight,
6452 right: totalLeftBoxesWidth + maxChartAreaWidth,
6453 bottom: totalTopBoxesHeight + maxChartAreaHeight
6454 };
6455
6456 // Step 9
6457 helpers.each(chartAreaBoxes, function(box) {
6458 box.left = chart.chartArea.left;
6459 box.top = chart.chartArea.top;
6460 box.right = chart.chartArea.right;
6461 box.bottom = chart.chartArea.bottom;
6462
6463 box.update(maxChartAreaWidth, maxChartAreaHeight);
6464 });
6465 }
6466 };
6467 };
6468
6469 },{"45":45}],31:[function(require,module,exports){
6470 'use strict';
6471
6472 var defaults = require(25);
6473 var Element = require(26);
6474 var helpers = require(45);
6475
6476 defaults._set('global', {
6477 plugins: {}
6478 });
6479
6480 module.exports = function(Chart) {
6481
6482 /**
6483 * The plugin service singleton
6484 * @namespace Chart.plugins
6485 * @since 2.1.0
6486 */
6487 Chart.plugins = {
6488 /**
6489 * Globally registered plugins.
6490 * @private
6491 */
6492 _plugins: [],
6493
6494 /**
6495 * This identifier is used to invalidate the descriptors cache attached to each chart
6496 * when a global plugin is registered or unregistered. In this case, the cache ID is
6497 * incremented and descriptors are regenerated during following API calls.
6498 * @private
6499 */
6500 _cacheId: 0,
6501
6502 /**
6503 * Registers the given plugin(s) if not already registered.
6504 * @param {Array|Object} plugins plugin instance(s).
6505 */
6506 register: function(plugins) {
6507 var p = this._plugins;
6508 ([]).concat(plugins).forEach(function(plugin) {
6509 if (p.indexOf(plugin) === -1) {
6510 p.push(plugin);
6511 }
6512 });
6513
6514 this._cacheId++;
6515 },
6516
6517 /**
6518 * Unregisters the given plugin(s) only if registered.
6519 * @param {Array|Object} plugins plugin instance(s).
6520 */
6521 unregister: function(plugins) {
6522 var p = this._plugins;
6523 ([]).concat(plugins).forEach(function(plugin) {
6524 var idx = p.indexOf(plugin);
6525 if (idx !== -1) {
6526 p.splice(idx, 1);
6527 }
6528 });
6529
6530 this._cacheId++;
6531 },
6532
6533 /**
6534 * Remove all registered plugins.
6535 * @since 2.1.5
6536 */
6537 clear: function() {
6538 this._plugins = [];
6539 this._cacheId++;
6540 },
6541
6542 /**
6543 * Returns the number of registered plugins?
6544 * @returns {Number}
6545 * @since 2.1.5
6546 */
6547 count: function() {
6548 return this._plugins.length;
6549 },
6550
6551 /**
6552 * Returns all registered plugin instances.
6553 * @returns {Array} array of plugin objects.
6554 * @since 2.1.5
6555 */
6556 getAll: function() {
6557 return this._plugins;
6558 },
6559
6560 /**
6561 * Calls enabled plugins for `chart` on the specified hook and with the given args.
6562 * This method immediately returns as soon as a plugin explicitly returns false. The
6563 * returned value can be used, for instance, to interrupt the current action.
6564 * @param {Object} chart - The chart instance for which plugins should be called.
6565 * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
6566 * @param {Array} [args] - Extra arguments to apply to the hook call.
6567 * @returns {Boolean} false if any of the plugins return false, else returns true.
6568 */
6569 notify: function(chart, hook, args) {
6570 var descriptors = this.descriptors(chart);
6571 var ilen = descriptors.length;
6572 var i, descriptor, plugin, params, method;
6573
6574 for (i = 0; i < ilen; ++i) {
6575 descriptor = descriptors[i];
6576 plugin = descriptor.plugin;
6577 method = plugin[hook];
6578 if (typeof method === 'function') {
6579 params = [chart].concat(args || []);
6580 params.push(descriptor.options);
6581 if (method.apply(plugin, params) === false) {
6582 return false;
6583 }
6584 }
6585 }
6586
6587 return true;
6588 },
6589
6590 /**
6591 * Returns descriptors of enabled plugins for the given chart.
6592 * @returns {Array} [{ plugin, options }]
6593 * @private
6594 */
6595 descriptors: function(chart) {
6596 var cache = chart._plugins || (chart._plugins = {});
6597 if (cache.id === this._cacheId) {
6598 return cache.descriptors;
6599 }
6600
6601 var plugins = [];
6602 var descriptors = [];
6603 var config = (chart && chart.config) || {};
6604 var options = (config.options && config.options.plugins) || {};
6605
6606 this._plugins.concat(config.plugins || []).forEach(function(plugin) {
6607 var idx = plugins.indexOf(plugin);
6608 if (idx !== -1) {
6609 return;
6610 }
6611
6612 var id = plugin.id;
6613 var opts = options[id];
6614 if (opts === false) {
6615 return;
6616 }
6617
6618 if (opts === true) {
6619 opts = helpers.clone(defaults.global.plugins[id]);
6620 }
6621
6622 plugins.push(plugin);
6623 descriptors.push({
6624 plugin: plugin,
6625 options: opts || {}
6626 });
6627 });
6628
6629 cache.descriptors = descriptors;
6630 cache.id = this._cacheId;
6631 return descriptors;
6632 }
6633 };
6634
6635 /**
6636 * Plugin extension hooks.
6637 * @interface IPlugin
6638 * @since 2.1.0
6639 */
6640 /**
6641 * @method IPlugin#beforeInit
6642 * @desc Called before initializing `chart`.
6643 * @param {Chart.Controller} chart - The chart instance.
6644 * @param {Object} options - The plugin options.
6645 */
6646 /**
6647 * @method IPlugin#afterInit
6648 * @desc Called after `chart` has been initialized and before the first update.
6649 * @param {Chart.Controller} chart - The chart instance.
6650 * @param {Object} options - The plugin options.
6651 */
6652 /**
6653 * @method IPlugin#beforeUpdate
6654 * @desc Called before updating `chart`. If any plugin returns `false`, the update
6655 * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
6656 * @param {Chart.Controller} chart - The chart instance.
6657 * @param {Object} options - The plugin options.
6658 * @returns {Boolean} `false` to cancel the chart update.
6659 */
6660 /**
6661 * @method IPlugin#afterUpdate
6662 * @desc Called after `chart` has been updated and before rendering. Note that this
6663 * hook will not be called if the chart update has been previously cancelled.
6664 * @param {Chart.Controller} chart - The chart instance.
6665 * @param {Object} options - The plugin options.
6666 */
6667 /**
6668 * @method IPlugin#beforeDatasetsUpdate
6669 * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
6670 * the datasets update is cancelled until another `update` is triggered.
6671 * @param {Chart.Controller} chart - The chart instance.
6672 * @param {Object} options - The plugin options.
6673 * @returns {Boolean} false to cancel the datasets update.
6674 * @since version 2.1.5
6675 */
6676 /**
6677 * @method IPlugin#afterDatasetsUpdate
6678 * @desc Called after the `chart` datasets have been updated. Note that this hook
6679 * will not be called if the datasets update has been previously cancelled.
6680 * @param {Chart.Controller} chart - The chart instance.
6681 * @param {Object} options - The plugin options.
6682 * @since version 2.1.5
6683 */
6684 /**
6685 * @method IPlugin#beforeDatasetUpdate
6686 * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
6687 * returns `false`, the datasets update is cancelled until another `update` is triggered.
6688 * @param {Chart} chart - The chart instance.
6689 * @param {Object} args - The call arguments.
6690 * @param {Number} args.index - The dataset index.
6691 * @param {Object} args.meta - The dataset metadata.
6692 * @param {Object} options - The plugin options.
6693 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6694 */
6695 /**
6696 * @method IPlugin#afterDatasetUpdate
6697 * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
6698 * that this hook will not be called if the datasets update has been previously cancelled.
6699 * @param {Chart} chart - The chart instance.
6700 * @param {Object} args - The call arguments.
6701 * @param {Number} args.index - The dataset index.
6702 * @param {Object} args.meta - The dataset metadata.
6703 * @param {Object} options - The plugin options.
6704 */
6705 /**
6706 * @method IPlugin#beforeLayout
6707 * @desc Called before laying out `chart`. If any plugin returns `false`,
6708 * the layout update is cancelled until another `update` is triggered.
6709 * @param {Chart.Controller} chart - The chart instance.
6710 * @param {Object} options - The plugin options.
6711 * @returns {Boolean} `false` to cancel the chart layout.
6712 */
6713 /**
6714 * @method IPlugin#afterLayout
6715 * @desc Called after the `chart` has been layed out. Note that this hook will not
6716 * be called if the layout update has been previously cancelled.
6717 * @param {Chart.Controller} chart - The chart instance.
6718 * @param {Object} options - The plugin options.
6719 */
6720 /**
6721 * @method IPlugin#beforeRender
6722 * @desc Called before rendering `chart`. If any plugin returns `false`,
6723 * the rendering is cancelled until another `render` is triggered.
6724 * @param {Chart.Controller} chart - The chart instance.
6725 * @param {Object} options - The plugin options.
6726 * @returns {Boolean} `false` to cancel the chart rendering.
6727 */
6728 /**
6729 * @method IPlugin#afterRender
6730 * @desc Called after the `chart` has been fully rendered (and animation completed). Note
6731 * that this hook will not be called if the rendering has been previously cancelled.
6732 * @param {Chart.Controller} chart - The chart instance.
6733 * @param {Object} options - The plugin options.
6734 */
6735 /**
6736 * @method IPlugin#beforeDraw
6737 * @desc Called before drawing `chart` at every animation frame specified by the given
6738 * easing value. If any plugin returns `false`, the frame drawing is cancelled until
6739 * another `render` is triggered.
6740 * @param {Chart.Controller} chart - The chart instance.
6741 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6742 * @param {Object} options - The plugin options.
6743 * @returns {Boolean} `false` to cancel the chart drawing.
6744 */
6745 /**
6746 * @method IPlugin#afterDraw
6747 * @desc Called after the `chart` has been drawn for the specific easing value. Note
6748 * that this hook will not be called if the drawing has been previously cancelled.
6749 * @param {Chart.Controller} chart - The chart instance.
6750 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6751 * @param {Object} options - The plugin options.
6752 */
6753 /**
6754 * @method IPlugin#beforeDatasetsDraw
6755 * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
6756 * the datasets drawing is cancelled until another `render` is triggered.
6757 * @param {Chart.Controller} chart - The chart instance.
6758 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6759 * @param {Object} options - The plugin options.
6760 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6761 */
6762 /**
6763 * @method IPlugin#afterDatasetsDraw
6764 * @desc Called after the `chart` datasets have been drawn. Note that this hook
6765 * will not be called if the datasets drawing has been previously cancelled.
6766 * @param {Chart.Controller} chart - The chart instance.
6767 * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6768 * @param {Object} options - The plugin options.
6769 */
6770 /**
6771 * @method IPlugin#beforeDatasetDraw
6772 * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
6773 * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
6774 * is cancelled until another `render` is triggered.
6775 * @param {Chart} chart - The chart instance.
6776 * @param {Object} args - The call arguments.
6777 * @param {Number} args.index - The dataset index.
6778 * @param {Object} args.meta - The dataset metadata.
6779 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6780 * @param {Object} options - The plugin options.
6781 * @returns {Boolean} `false` to cancel the chart datasets drawing.
6782 */
6783 /**
6784 * @method IPlugin#afterDatasetDraw
6785 * @desc Called after the `chart` datasets at the given `args.index` have been drawn
6786 * (datasets are drawn in the reverse order). Note that this hook will not be called
6787 * if the datasets drawing has been previously cancelled.
6788 * @param {Chart} chart - The chart instance.
6789 * @param {Object} args - The call arguments.
6790 * @param {Number} args.index - The dataset index.
6791 * @param {Object} args.meta - The dataset metadata.
6792 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6793 * @param {Object} options - The plugin options.
6794 */
6795 /**
6796 * @method IPlugin#beforeTooltipDraw
6797 * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
6798 * the tooltip drawing is cancelled until another `render` is triggered.
6799 * @param {Chart} chart - The chart instance.
6800 * @param {Object} args - The call arguments.
6801 * @param {Object} args.tooltip - The tooltip.
6802 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6803 * @param {Object} options - The plugin options.
6804 * @returns {Boolean} `false` to cancel the chart tooltip drawing.
6805 */
6806 /**
6807 * @method IPlugin#afterTooltipDraw
6808 * @desc Called after drawing the `tooltip`. Note that this hook will not
6809 * be called if the tooltip drawing has been previously cancelled.
6810 * @param {Chart} chart - The chart instance.
6811 * @param {Object} args - The call arguments.
6812 * @param {Object} args.tooltip - The tooltip.
6813 * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6814 * @param {Object} options - The plugin options.
6815 */
6816 /**
6817 * @method IPlugin#beforeEvent
6818 * @desc Called before processing the specified `event`. If any plugin returns `false`,
6819 * the event will be discarded.
6820 * @param {Chart.Controller} chart - The chart instance.
6821 * @param {IEvent} event - The event object.
6822 * @param {Object} options - The plugin options.
6823 */
6824 /**
6825 * @method IPlugin#afterEvent
6826 * @desc Called after the `event` has been consumed. Note that this hook
6827 * will not be called if the `event` has been previously discarded.
6828 * @param {Chart.Controller} chart - The chart instance.
6829 * @param {IEvent} event - The event object.
6830 * @param {Object} options - The plugin options.
6831 */
6832 /**
6833 * @method IPlugin#resize
6834 * @desc Called after the chart as been resized.
6835 * @param {Chart.Controller} chart - The chart instance.
6836 * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
6837 * @param {Object} options - The plugin options.
6838 */
6839 /**
6840 * @method IPlugin#destroy
6841 * @desc Called after the chart as been destroyed.
6842 * @param {Chart.Controller} chart - The chart instance.
6843 * @param {Object} options - The plugin options.
6844 */
6845
6846 /**
6847 * Provided for backward compatibility, use Chart.plugins instead
6848 * @namespace Chart.pluginService
6849 * @deprecated since version 2.1.5
6850 * @todo remove at version 3
6851 * @private
6852 */
6853 Chart.pluginService = Chart.plugins;
6854
6855 /**
6856 * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
6857 * effect, instead simply create/register plugins via plain JavaScript objects.
6858 * @interface Chart.PluginBase
6859 * @deprecated since version 2.5.0
6860 * @todo remove at version 3
6861 * @private
6862 */
6863 Chart.PluginBase = Element.extend({});
6864 };
6865
6866 },{"25":25,"26":26,"45":45}],32:[function(require,module,exports){
6867 'use strict';
6868
6869 var defaults = require(25);
6870 var Element = require(26);
6871 var helpers = require(45);
6872 var Ticks = require(34);
6873
6874 defaults._set('scale', {
6875 display: true,
6876 position: 'left',
6877 offset: false,
6878
6879 // grid line settings
6880 gridLines: {
6881 display: true,
6882 color: 'rgba(0, 0, 0, 0.1)',
6883 lineWidth: 1,
6884 drawBorder: true,
6885 drawOnChartArea: true,
6886 drawTicks: true,
6887 tickMarkLength: 10,
6888 zeroLineWidth: 1,
6889 zeroLineColor: 'rgba(0,0,0,0.25)',
6890 zeroLineBorderDash: [],
6891 zeroLineBorderDashOffset: 0.0,
6892 offsetGridLines: false,
6893 borderDash: [],
6894 borderDashOffset: 0.0
6895 },
6896
6897 // scale label
6898 scaleLabel: {
6899 // display property
6900 display: false,
6901
6902 // actual label
6903 labelString: '',
6904
6905 // line height
6906 lineHeight: 1.2,
6907
6908 // top/bottom padding
6909 padding: {
6910 top: 4,
6911 bottom: 4
6912 }
6913 },
6914
6915 // label settings
6916 ticks: {
6917 beginAtZero: false,
6918 minRotation: 0,
6919 maxRotation: 50,
6920 mirror: false,
6921 padding: 0,
6922 reverse: false,
6923 display: true,
6924 autoSkip: true,
6925 autoSkipPadding: 0,
6926 labelOffset: 0,
6927 // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
6928 callback: Ticks.formatters.values,
6929 minor: {},
6930 major: {}
6931 }
6932 });
6933
6934 function labelsFromTicks(ticks) {
6935 var labels = [];
6936 var i, ilen;
6937
6938 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
6939 labels.push(ticks[i].label);
6940 }
6941
6942 return labels;
6943 }
6944
6945 function getLineValue(scale, index, offsetGridLines) {
6946 var lineValue = scale.getPixelForTick(index);
6947
6948 if (offsetGridLines) {
6949 if (index === 0) {
6950 lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
6951 } else {
6952 lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
6953 }
6954 }
6955 return lineValue;
6956 }
6957
6958 module.exports = function(Chart) {
6959
6960 function computeTextSize(context, tick, font) {
6961 return helpers.isArray(tick) ?
6962 helpers.longestText(context, font, tick) :
6963 context.measureText(tick).width;
6964 }
6965
6966 function parseFontOptions(options) {
6967 var valueOrDefault = helpers.valueOrDefault;
6968 var globalDefaults = defaults.global;
6969 var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
6970 var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
6971 var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
6972
6973 return {
6974 size: size,
6975 style: style,
6976 family: family,
6977 font: helpers.fontString(size, style, family)
6978 };
6979 }
6980
6981 function parseLineHeight(options) {
6982 return helpers.options.toLineHeight(
6983 helpers.valueOrDefault(options.lineHeight, 1.2),
6984 helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize));
6985 }
6986
6987 Chart.Scale = Element.extend({
6988 /**
6989 * Get the padding needed for the scale
6990 * @method getPadding
6991 * @private
6992 * @returns {Padding} the necessary padding
6993 */
6994 getPadding: function() {
6995 var me = this;
6996 return {
6997 left: me.paddingLeft || 0,
6998 top: me.paddingTop || 0,
6999 right: me.paddingRight || 0,
7000 bottom: me.paddingBottom || 0
7001 };
7002 },
7003
7004 /**
7005 * Returns the scale tick objects ({label, major})
7006 * @since 2.7
7007 */
7008 getTicks: function() {
7009 return this._ticks;
7010 },
7011
7012 // These methods are ordered by lifecyle. Utilities then follow.
7013 // Any function defined here is inherited by all scale types.
7014 // Any function can be extended by the scale type
7015
7016 mergeTicksOptions: function() {
7017 var ticks = this.options.ticks;
7018 if (ticks.minor === false) {
7019 ticks.minor = {
7020 display: false
7021 };
7022 }
7023 if (ticks.major === false) {
7024 ticks.major = {
7025 display: false
7026 };
7027 }
7028 for (var key in ticks) {
7029 if (key !== 'major' && key !== 'minor') {
7030 if (typeof ticks.minor[key] === 'undefined') {
7031 ticks.minor[key] = ticks[key];
7032 }
7033 if (typeof ticks.major[key] === 'undefined') {
7034 ticks.major[key] = ticks[key];
7035 }
7036 }
7037 }
7038 },
7039 beforeUpdate: function() {
7040 helpers.callback(this.options.beforeUpdate, [this]);
7041 },
7042 update: function(maxWidth, maxHeight, margins) {
7043 var me = this;
7044 var i, ilen, labels, label, ticks, tick;
7045
7046 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
7047 me.beforeUpdate();
7048
7049 // Absorb the master measurements
7050 me.maxWidth = maxWidth;
7051 me.maxHeight = maxHeight;
7052 me.margins = helpers.extend({
7053 left: 0,
7054 right: 0,
7055 top: 0,
7056 bottom: 0
7057 }, margins);
7058 me.longestTextCache = me.longestTextCache || {};
7059
7060 // Dimensions
7061 me.beforeSetDimensions();
7062 me.setDimensions();
7063 me.afterSetDimensions();
7064
7065 // Data min/max
7066 me.beforeDataLimits();
7067 me.determineDataLimits();
7068 me.afterDataLimits();
7069
7070 // Ticks - `this.ticks` is now DEPRECATED!
7071 // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
7072 // and must not be accessed directly from outside this class. `this.ticks` being
7073 // around for long time and not marked as private, we can't change its structure
7074 // without unexpected breaking changes. If you need to access the scale ticks,
7075 // use scale.getTicks() instead.
7076
7077 me.beforeBuildTicks();
7078
7079 // New implementations should return an array of objects but for BACKWARD COMPAT,
7080 // we still support no return (`this.ticks` internally set by calling this method).
7081 ticks = me.buildTicks() || [];
7082
7083 me.afterBuildTicks();
7084
7085 me.beforeTickToLabelConversion();
7086
7087 // New implementations should return the formatted tick labels but for BACKWARD
7088 // COMPAT, we still support no return (`this.ticks` internally changed by calling
7089 // this method and supposed to contain only string values).
7090 labels = me.convertTicksToLabels(ticks) || me.ticks;
7091
7092 me.afterTickToLabelConversion();
7093
7094 me.ticks = labels; // BACKWARD COMPATIBILITY
7095
7096 // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
7097
7098 // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
7099 for (i = 0, ilen = labels.length; i < ilen; ++i) {
7100 label = labels[i];
7101 tick = ticks[i];
7102 if (!tick) {
7103 ticks.push(tick = {
7104 label: label,
7105 major: false
7106 });
7107 } else {
7108 tick.label = label;
7109 }
7110 }
7111
7112 me._ticks = ticks;
7113
7114 // Tick Rotation
7115 me.beforeCalculateTickRotation();
7116 me.calculateTickRotation();
7117 me.afterCalculateTickRotation();
7118 // Fit
7119 me.beforeFit();
7120 me.fit();
7121 me.afterFit();
7122 //
7123 me.afterUpdate();
7124
7125 return me.minSize;
7126
7127 },
7128 afterUpdate: function() {
7129 helpers.callback(this.options.afterUpdate, [this]);
7130 },
7131
7132 //
7133
7134 beforeSetDimensions: function() {
7135 helpers.callback(this.options.beforeSetDimensions, [this]);
7136 },
7137 setDimensions: function() {
7138 var me = this;
7139 // Set the unconstrained dimension before label rotation
7140 if (me.isHorizontal()) {
7141 // Reset position before calculating rotation
7142 me.width = me.maxWidth;
7143 me.left = 0;
7144 me.right = me.width;
7145 } else {
7146 me.height = me.maxHeight;
7147
7148 // Reset position before calculating rotation
7149 me.top = 0;
7150 me.bottom = me.height;
7151 }
7152
7153 // Reset padding
7154 me.paddingLeft = 0;
7155 me.paddingTop = 0;
7156 me.paddingRight = 0;
7157 me.paddingBottom = 0;
7158 },
7159 afterSetDimensions: function() {
7160 helpers.callback(this.options.afterSetDimensions, [this]);
7161 },
7162
7163 // Data limits
7164 beforeDataLimits: function() {
7165 helpers.callback(this.options.beforeDataLimits, [this]);
7166 },
7167 determineDataLimits: helpers.noop,
7168 afterDataLimits: function() {
7169 helpers.callback(this.options.afterDataLimits, [this]);
7170 },
7171
7172 //
7173 beforeBuildTicks: function() {
7174 helpers.callback(this.options.beforeBuildTicks, [this]);
7175 },
7176 buildTicks: helpers.noop,
7177 afterBuildTicks: function() {
7178 helpers.callback(this.options.afterBuildTicks, [this]);
7179 },
7180
7181 beforeTickToLabelConversion: function() {
7182 helpers.callback(this.options.beforeTickToLabelConversion, [this]);
7183 },
7184 convertTicksToLabels: function() {
7185 var me = this;
7186 // Convert ticks to strings
7187 var tickOpts = me.options.ticks;
7188 me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
7189 },
7190 afterTickToLabelConversion: function() {
7191 helpers.callback(this.options.afterTickToLabelConversion, [this]);
7192 },
7193
7194 //
7195
7196 beforeCalculateTickRotation: function() {
7197 helpers.callback(this.options.beforeCalculateTickRotation, [this]);
7198 },
7199 calculateTickRotation: function() {
7200 var me = this;
7201 var context = me.ctx;
7202 var tickOpts = me.options.ticks;
7203 var labels = labelsFromTicks(me._ticks);
7204
7205 // Get the width of each grid by calculating the difference
7206 // between x offsets between 0 and 1.
7207 var tickFont = parseFontOptions(tickOpts);
7208 context.font = tickFont.font;
7209
7210 var labelRotation = tickOpts.minRotation || 0;
7211
7212 if (labels.length && me.options.display && me.isHorizontal()) {
7213 var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache);
7214 var labelWidth = originalLabelWidth;
7215 var cosRotation, sinRotation;
7216
7217 // Allow 3 pixels x2 padding either side for label readability
7218 var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
7219
7220 // Max label rotation can be set or default to 90 - also act as a loop counter
7221 while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
7222 var angleRadians = helpers.toRadians(labelRotation);
7223 cosRotation = Math.cos(angleRadians);
7224 sinRotation = Math.sin(angleRadians);
7225
7226 if (sinRotation * originalLabelWidth > me.maxHeight) {
7227 // go back one step
7228 labelRotation--;
7229 break;
7230 }
7231
7232 labelRotation++;
7233 labelWidth = cosRotation * originalLabelWidth;
7234 }
7235 }
7236
7237 me.labelRotation = labelRotation;
7238 },
7239 afterCalculateTickRotation: function() {
7240 helpers.callback(this.options.afterCalculateTickRotation, [this]);
7241 },
7242
7243 //
7244
7245 beforeFit: function() {
7246 helpers.callback(this.options.beforeFit, [this]);
7247 },
7248 fit: function() {
7249 var me = this;
7250 // Reset
7251 var minSize = me.minSize = {
7252 width: 0,
7253 height: 0
7254 };
7255
7256 var labels = labelsFromTicks(me._ticks);
7257
7258 var opts = me.options;
7259 var tickOpts = opts.ticks;
7260 var scaleLabelOpts = opts.scaleLabel;
7261 var gridLineOpts = opts.gridLines;
7262 var display = opts.display;
7263 var isHorizontal = me.isHorizontal();
7264
7265 var tickFont = parseFontOptions(tickOpts);
7266 var tickMarkLength = opts.gridLines.tickMarkLength;
7267
7268 // Width
7269 if (isHorizontal) {
7270 // subtract the margins to line up with the chartArea if we are a full width scale
7271 minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
7272 } else {
7273 minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7274 }
7275
7276 // height
7277 if (isHorizontal) {
7278 minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7279 } else {
7280 minSize.height = me.maxHeight; // fill all the height
7281 }
7282
7283 // Are we showing a title for the scale?
7284 if (scaleLabelOpts.display && display) {
7285 var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts);
7286 var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding);
7287 var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height;
7288
7289 if (isHorizontal) {
7290 minSize.height += deltaHeight;
7291 } else {
7292 minSize.width += deltaHeight;
7293 }
7294 }
7295
7296 // Don't bother fitting the ticks if we are not showing them
7297 if (tickOpts.display && display) {
7298 var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache);
7299 var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels);
7300 var lineSpace = tickFont.size * 0.5;
7301 var tickPadding = me.options.ticks.padding;
7302
7303 if (isHorizontal) {
7304 // A horizontal axis is more constrained by the height.
7305 me.longestLabelWidth = largestTextWidth;
7306
7307 var angleRadians = helpers.toRadians(me.labelRotation);
7308 var cosRotation = Math.cos(angleRadians);
7309 var sinRotation = Math.sin(angleRadians);
7310
7311 // TODO - improve this calculation
7312 var labelHeight = (sinRotation * largestTextWidth)
7313 + (tickFont.size * tallestLabelHeightInLines)
7314 + (lineSpace * (tallestLabelHeightInLines - 1))
7315 + lineSpace; // padding
7316
7317 minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
7318
7319 me.ctx.font = tickFont.font;
7320 var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font);
7321 var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font);
7322
7323 // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
7324 // which means that the right padding is dominated by the font height
7325 if (me.labelRotation !== 0) {
7326 me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges
7327 me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3;
7328 } else {
7329 me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
7330 me.paddingRight = lastLabelWidth / 2 + 3;
7331 }
7332 } else {
7333 // A vertical axis is more constrained by the width. Labels are the
7334 // dominant factor here, so get that length first and account for padding
7335 if (tickOpts.mirror) {
7336 largestTextWidth = 0;
7337 } else {
7338 // use lineSpace for consistency with horizontal axis
7339 // tickPadding is not implemented for horizontal
7340 largestTextWidth += tickPadding + lineSpace;
7341 }
7342
7343 minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
7344
7345 me.paddingTop = tickFont.size / 2;
7346 me.paddingBottom = tickFont.size / 2;
7347 }
7348 }
7349
7350 me.handleMargins();
7351
7352 me.width = minSize.width;
7353 me.height = minSize.height;
7354 },
7355
7356 /**
7357 * Handle margins and padding interactions
7358 * @private
7359 */
7360 handleMargins: function() {
7361 var me = this;
7362 if (me.margins) {
7363 me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7364 me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
7365 me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7366 me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
7367 }
7368 },
7369
7370 afterFit: function() {
7371 helpers.callback(this.options.afterFit, [this]);
7372 },
7373
7374 // Shared Methods
7375 isHorizontal: function() {
7376 return this.options.position === 'top' || this.options.position === 'bottom';
7377 },
7378 isFullWidth: function() {
7379 return (this.options.fullWidth);
7380 },
7381
7382 // 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
7383 getRightValue: function(rawValue) {
7384 // Null and undefined values first
7385 if (helpers.isNullOrUndef(rawValue)) {
7386 return NaN;
7387 }
7388 // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
7389 if (typeof rawValue === 'number' && !isFinite(rawValue)) {
7390 return NaN;
7391 }
7392 // If it is in fact an object, dive in one more level
7393 if (rawValue) {
7394 if (this.isHorizontal()) {
7395 if (rawValue.x !== undefined) {
7396 return this.getRightValue(rawValue.x);
7397 }
7398 } else if (rawValue.y !== undefined) {
7399 return this.getRightValue(rawValue.y);
7400 }
7401 }
7402
7403 // Value is good, return it
7404 return rawValue;
7405 },
7406
7407 /**
7408 * Used to get the value to display in the tooltip for the data at the given index
7409 * @param index
7410 * @param datasetIndex
7411 */
7412 getLabelForIndex: helpers.noop,
7413
7414 /**
7415 * Returns the location of the given data point. Value can either be an index or a numerical value
7416 * The coordinate (0, 0) is at the upper-left corner of the canvas
7417 * @param value
7418 * @param index
7419 * @param datasetIndex
7420 */
7421 getPixelForValue: helpers.noop,
7422
7423 /**
7424 * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
7425 * The coordinate (0, 0) is at the upper-left corner of the canvas
7426 * @param pixel
7427 */
7428 getValueForPixel: helpers.noop,
7429
7430 /**
7431 * Returns the location of the tick at the given index
7432 * The coordinate (0, 0) is at the upper-left corner of the canvas
7433 */
7434 getPixelForTick: function(index) {
7435 var me = this;
7436 var offset = me.options.offset;
7437 if (me.isHorizontal()) {
7438 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7439 var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
7440 var pixel = (tickWidth * index) + me.paddingLeft;
7441
7442 if (offset) {
7443 pixel += tickWidth / 2;
7444 }
7445
7446 var finalVal = me.left + Math.round(pixel);
7447 finalVal += me.isFullWidth() ? me.margins.left : 0;
7448 return finalVal;
7449 }
7450 var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
7451 return me.top + (index * (innerHeight / (me._ticks.length - 1)));
7452 },
7453
7454 /**
7455 * Utility for getting the pixel location of a percentage of scale
7456 * The coordinate (0, 0) is at the upper-left corner of the canvas
7457 */
7458 getPixelForDecimal: function(decimal) {
7459 var me = this;
7460 if (me.isHorizontal()) {
7461 var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7462 var valueOffset = (innerWidth * decimal) + me.paddingLeft;
7463
7464 var finalVal = me.left + Math.round(valueOffset);
7465 finalVal += me.isFullWidth() ? me.margins.left : 0;
7466 return finalVal;
7467 }
7468 return me.top + (decimal * me.height);
7469 },
7470
7471 /**
7472 * Returns the pixel for the minimum chart value
7473 * The coordinate (0, 0) is at the upper-left corner of the canvas
7474 */
7475 getBasePixel: function() {
7476 return this.getPixelForValue(this.getBaseValue());
7477 },
7478
7479 getBaseValue: function() {
7480 var me = this;
7481 var min = me.min;
7482 var max = me.max;
7483
7484 return me.beginAtZero ? 0 :
7485 min < 0 && max < 0 ? max :
7486 min > 0 && max > 0 ? min :
7487 0;
7488 },
7489
7490 /**
7491 * Returns a subset of ticks to be plotted to avoid overlapping labels.
7492 * @private
7493 */
7494 _autoSkip: function(ticks) {
7495 var skipRatio;
7496 var me = this;
7497 var isHorizontal = me.isHorizontal();
7498 var optionTicks = me.options.ticks.minor;
7499 var tickCount = ticks.length;
7500 var labelRotationRadians = helpers.toRadians(me.labelRotation);
7501 var cosRotation = Math.cos(labelRotationRadians);
7502 var longestRotatedLabel = me.longestLabelWidth * cosRotation;
7503 var result = [];
7504 var i, tick, shouldSkip;
7505
7506 // figure out the maximum number of gridlines to show
7507 var maxTicks;
7508 if (optionTicks.maxTicksLimit) {
7509 maxTicks = optionTicks.maxTicksLimit;
7510 }
7511
7512 if (isHorizontal) {
7513 skipRatio = false;
7514
7515 if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) {
7516 skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight)));
7517 }
7518
7519 // if they defined a max number of optionTicks,
7520 // increase skipRatio until that number is met
7521 if (maxTicks && tickCount > maxTicks) {
7522 skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks));
7523 }
7524 }
7525
7526 for (i = 0; i < tickCount; i++) {
7527 tick = ticks[i];
7528
7529 // Since we always show the last tick,we need may need to hide the last shown one before
7530 shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount);
7531 if (shouldSkip && i !== tickCount - 1) {
7532 // leave tick in place but make sure it's not displayed (#4635)
7533 delete tick.label;
7534 }
7535 result.push(tick);
7536 }
7537 return result;
7538 },
7539
7540 // Actually draw the scale on the canvas
7541 // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
7542 draw: function(chartArea) {
7543 var me = this;
7544 var options = me.options;
7545 if (!options.display) {
7546 return;
7547 }
7548
7549 var context = me.ctx;
7550 var globalDefaults = defaults.global;
7551 var optionTicks = options.ticks.minor;
7552 var optionMajorTicks = options.ticks.major || optionTicks;
7553 var gridLines = options.gridLines;
7554 var scaleLabel = options.scaleLabel;
7555
7556 var isRotated = me.labelRotation !== 0;
7557 var isHorizontal = me.isHorizontal();
7558
7559 var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
7560 var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
7561 var tickFont = parseFontOptions(optionTicks);
7562 var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
7563 var majorTickFont = parseFontOptions(optionMajorTicks);
7564
7565 var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
7566
7567 var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
7568 var scaleLabelFont = parseFontOptions(scaleLabel);
7569 var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
7570 var labelRotationRadians = helpers.toRadians(me.labelRotation);
7571
7572 var itemsToDraw = [];
7573
7574 var xTickStart = options.position === 'right' ? me.left : me.right - tl;
7575 var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
7576 var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
7577 var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
7578
7579 helpers.each(ticks, function(tick, index) {
7580 // autoskipper skipped this tick (#4635)
7581 if (helpers.isNullOrUndef(tick.label)) {
7582 return;
7583 }
7584
7585 var label = tick.label;
7586 var lineWidth, lineColor, borderDash, borderDashOffset;
7587 if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
7588 // Draw the first index specially
7589 lineWidth = gridLines.zeroLineWidth;
7590 lineColor = gridLines.zeroLineColor;
7591 borderDash = gridLines.zeroLineBorderDash;
7592 borderDashOffset = gridLines.zeroLineBorderDashOffset;
7593 } else {
7594 lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index);
7595 lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index);
7596 borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
7597 borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
7598 }
7599
7600 // Common properties
7601 var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
7602 var textAlign = 'middle';
7603 var textBaseline = 'middle';
7604 var tickPadding = optionTicks.padding;
7605
7606 if (isHorizontal) {
7607 var labelYOffset = tl + tickPadding;
7608
7609 if (options.position === 'bottom') {
7610 // bottom
7611 textBaseline = !isRotated ? 'top' : 'middle';
7612 textAlign = !isRotated ? 'center' : 'right';
7613 labelY = me.top + labelYOffset;
7614 } else {
7615 // top
7616 textBaseline = !isRotated ? 'bottom' : 'middle';
7617 textAlign = !isRotated ? 'center' : 'left';
7618 labelY = me.bottom - labelYOffset;
7619 }
7620
7621 var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7622 if (xLineValue < me.left) {
7623 lineColor = 'rgba(0,0,0,0)';
7624 }
7625 xLineValue += helpers.aliasPixel(lineWidth);
7626
7627 labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
7628
7629 tx1 = tx2 = x1 = x2 = xLineValue;
7630 ty1 = yTickStart;
7631 ty2 = yTickEnd;
7632 y1 = chartArea.top;
7633 y2 = chartArea.bottom;
7634 } else {
7635 var isLeft = options.position === 'left';
7636 var labelXOffset;
7637
7638 if (optionTicks.mirror) {
7639 textAlign = isLeft ? 'left' : 'right';
7640 labelXOffset = tickPadding;
7641 } else {
7642 textAlign = isLeft ? 'right' : 'left';
7643 labelXOffset = tl + tickPadding;
7644 }
7645
7646 labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
7647
7648 var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7649 if (yLineValue < me.top) {
7650 lineColor = 'rgba(0,0,0,0)';
7651 }
7652 yLineValue += helpers.aliasPixel(lineWidth);
7653
7654 labelY = me.getPixelForTick(index) + optionTicks.labelOffset;
7655
7656 tx1 = xTickStart;
7657 tx2 = xTickEnd;
7658 x1 = chartArea.left;
7659 x2 = chartArea.right;
7660 ty1 = ty2 = y1 = y2 = yLineValue;
7661 }
7662
7663 itemsToDraw.push({
7664 tx1: tx1,
7665 ty1: ty1,
7666 tx2: tx2,
7667 ty2: ty2,
7668 x1: x1,
7669 y1: y1,
7670 x2: x2,
7671 y2: y2,
7672 labelX: labelX,
7673 labelY: labelY,
7674 glWidth: lineWidth,
7675 glColor: lineColor,
7676 glBorderDash: borderDash,
7677 glBorderDashOffset: borderDashOffset,
7678 rotation: -1 * labelRotationRadians,
7679 label: label,
7680 major: tick.major,
7681 textBaseline: textBaseline,
7682 textAlign: textAlign
7683 });
7684 });
7685
7686 // Draw all of the tick labels, tick marks, and grid lines at the correct places
7687 helpers.each(itemsToDraw, function(itemToDraw) {
7688 if (gridLines.display) {
7689 context.save();
7690 context.lineWidth = itemToDraw.glWidth;
7691 context.strokeStyle = itemToDraw.glColor;
7692 if (context.setLineDash) {
7693 context.setLineDash(itemToDraw.glBorderDash);
7694 context.lineDashOffset = itemToDraw.glBorderDashOffset;
7695 }
7696
7697 context.beginPath();
7698
7699 if (gridLines.drawTicks) {
7700 context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
7701 context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
7702 }
7703
7704 if (gridLines.drawOnChartArea) {
7705 context.moveTo(itemToDraw.x1, itemToDraw.y1);
7706 context.lineTo(itemToDraw.x2, itemToDraw.y2);
7707 }
7708
7709 context.stroke();
7710 context.restore();
7711 }
7712
7713 if (optionTicks.display) {
7714 // Make sure we draw text in the correct color and font
7715 context.save();
7716 context.translate(itemToDraw.labelX, itemToDraw.labelY);
7717 context.rotate(itemToDraw.rotation);
7718 context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
7719 context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
7720 context.textBaseline = itemToDraw.textBaseline;
7721 context.textAlign = itemToDraw.textAlign;
7722
7723 var label = itemToDraw.label;
7724 if (helpers.isArray(label)) {
7725 for (var i = 0, y = 0; i < label.length; ++i) {
7726 // We just make sure the multiline element is a string here..
7727 context.fillText('' + label[i], 0, y);
7728 // apply same lineSpacing as calculated @ L#320
7729 y += (tickFont.size * 1.5);
7730 }
7731 } else {
7732 context.fillText(label, 0, 0);
7733 }
7734 context.restore();
7735 }
7736 });
7737
7738 if (scaleLabel.display) {
7739 // Draw the scale label
7740 var scaleLabelX;
7741 var scaleLabelY;
7742 var rotation = 0;
7743 var halfLineHeight = parseLineHeight(scaleLabel) / 2;
7744
7745 if (isHorizontal) {
7746 scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
7747 scaleLabelY = options.position === 'bottom'
7748 ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
7749 : me.top + halfLineHeight + scaleLabelPadding.top;
7750 } else {
7751 var isLeft = options.position === 'left';
7752 scaleLabelX = isLeft
7753 ? me.left + halfLineHeight + scaleLabelPadding.top
7754 : me.right - halfLineHeight - scaleLabelPadding.top;
7755 scaleLabelY = me.top + ((me.bottom - me.top) / 2);
7756 rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
7757 }
7758
7759 context.save();
7760 context.translate(scaleLabelX, scaleLabelY);
7761 context.rotate(rotation);
7762 context.textAlign = 'center';
7763 context.textBaseline = 'middle';
7764 context.fillStyle = scaleLabelFontColor; // render in correct colour
7765 context.font = scaleLabelFont.font;
7766 context.fillText(scaleLabel.labelString, 0, 0);
7767 context.restore();
7768 }
7769
7770 if (gridLines.drawBorder) {
7771 // Draw the line at the edge of the axis
7772 context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0);
7773 context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0);
7774 var x1 = me.left;
7775 var x2 = me.right;
7776 var y1 = me.top;
7777 var y2 = me.bottom;
7778
7779 var aliasPixel = helpers.aliasPixel(context.lineWidth);
7780 if (isHorizontal) {
7781 y1 = y2 = options.position === 'top' ? me.bottom : me.top;
7782 y1 += aliasPixel;
7783 y2 += aliasPixel;
7784 } else {
7785 x1 = x2 = options.position === 'left' ? me.right : me.left;
7786 x1 += aliasPixel;
7787 x2 += aliasPixel;
7788 }
7789
7790 context.beginPath();
7791 context.moveTo(x1, y1);
7792 context.lineTo(x2, y2);
7793 context.stroke();
7794 }
7795 }
7796 });
7797 };
7798
7799 },{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){
7800 'use strict';
7801
7802 var defaults = require(25);
7803 var helpers = require(45);
7804
7805 module.exports = function(Chart) {
7806
7807 Chart.scaleService = {
7808 // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
7809 // use the new chart options to grab the correct scale
7810 constructors: {},
7811 // Use a registration function so that we can move to an ES6 map when we no longer need to support
7812 // old browsers
7813
7814 // Scale config defaults
7815 defaults: {},
7816 registerScaleType: function(type, scaleConstructor, scaleDefaults) {
7817 this.constructors[type] = scaleConstructor;
7818 this.defaults[type] = helpers.clone(scaleDefaults);
7819 },
7820 getScaleConstructor: function(type) {
7821 return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
7822 },
7823 getScaleDefaults: function(type) {
7824 // Return the scale defaults merged with the global settings so that we always use the latest ones
7825 return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {};
7826 },
7827 updateScaleDefaults: function(type, additions) {
7828 var me = this;
7829 if (me.defaults.hasOwnProperty(type)) {
7830 me.defaults[type] = helpers.extend(me.defaults[type], additions);
7831 }
7832 },
7833 addScalesToLayout: function(chart) {
7834 // Adds each scale to the chart.boxes array to be sized accordingly
7835 helpers.each(chart.scales, function(scale) {
7836 // Set ILayoutItem parameters for backwards compatibility
7837 scale.fullWidth = scale.options.fullWidth;
7838 scale.position = scale.options.position;
7839 scale.weight = scale.options.weight;
7840 Chart.layoutService.addBox(chart, scale);
7841 });
7842 }
7843 };
7844 };
7845
7846 },{"25":25,"45":45}],34:[function(require,module,exports){
7847 'use strict';
7848
7849 var helpers = require(45);
7850
7851 /**
7852 * Namespace to hold static tick generation functions
7853 * @namespace Chart.Ticks
7854 */
7855 module.exports = {
7856 /**
7857 * Namespace to hold generators for different types of ticks
7858 * @namespace Chart.Ticks.generators
7859 */
7860 generators: {
7861 /**
7862 * Interface for the options provided to the numeric tick generator
7863 * @interface INumericTickGenerationOptions
7864 */
7865 /**
7866 * The maximum number of ticks to display
7867 * @name INumericTickGenerationOptions#maxTicks
7868 * @type Number
7869 */
7870 /**
7871 * The distance between each tick.
7872 * @name INumericTickGenerationOptions#stepSize
7873 * @type Number
7874 * @optional
7875 */
7876 /**
7877 * Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum
7878 * @name INumericTickGenerationOptions#min
7879 * @type Number
7880 * @optional
7881 */
7882 /**
7883 * The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum
7884 * @name INumericTickGenerationOptions#max
7885 * @type Number
7886 * @optional
7887 */
7888
7889 /**
7890 * Generate a set of linear ticks
7891 * @method Chart.Ticks.generators.linear
7892 * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7893 * @param dataRange {IRange} the range of the data
7894 * @returns {Array<Number>} array of tick values
7895 */
7896 linear: function(generationOptions, dataRange) {
7897 var ticks = [];
7898 // To get a "nice" value for the tick spacing, we will use the appropriately named
7899 // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
7900 // for details.
7901
7902 var spacing;
7903 if (generationOptions.stepSize && generationOptions.stepSize > 0) {
7904 spacing = generationOptions.stepSize;
7905 } else {
7906 var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
7907 spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
7908 }
7909 var niceMin = Math.floor(dataRange.min / spacing) * spacing;
7910 var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
7911
7912 // If min, max and stepSize is set and they make an evenly spaced scale use it.
7913 if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
7914 // If very close to our whole number, use it.
7915 if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
7916 niceMin = generationOptions.min;
7917 niceMax = generationOptions.max;
7918 }
7919 }
7920
7921 var numSpaces = (niceMax - niceMin) / spacing;
7922 // If very close to our rounded value, use it.
7923 if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
7924 numSpaces = Math.round(numSpaces);
7925 } else {
7926 numSpaces = Math.ceil(numSpaces);
7927 }
7928
7929 // Put the values into the ticks array
7930 ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
7931 for (var j = 1; j < numSpaces; ++j) {
7932 ticks.push(niceMin + (j * spacing));
7933 }
7934 ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
7935
7936 return ticks;
7937 },
7938
7939 /**
7940 * Generate a set of logarithmic ticks
7941 * @method Chart.Ticks.generators.logarithmic
7942 * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7943 * @param dataRange {IRange} the range of the data
7944 * @returns {Array<Number>} array of tick values
7945 */
7946 logarithmic: function(generationOptions, dataRange) {
7947 var ticks = [];
7948 var valueOrDefault = helpers.valueOrDefault;
7949
7950 // Figure out what the max number of ticks we can support it is based on the size of
7951 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
7952 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
7953 // the graph
7954 var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
7955
7956 var endExp = Math.floor(helpers.log10(dataRange.max));
7957 var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
7958 var exp, significand;
7959
7960 if (tickVal === 0) {
7961 exp = Math.floor(helpers.log10(dataRange.minNotZero));
7962 significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
7963
7964 ticks.push(tickVal);
7965 tickVal = significand * Math.pow(10, exp);
7966 } else {
7967 exp = Math.floor(helpers.log10(tickVal));
7968 significand = Math.floor(tickVal / Math.pow(10, exp));
7969 }
7970
7971 do {
7972 ticks.push(tickVal);
7973
7974 ++significand;
7975 if (significand === 10) {
7976 significand = 1;
7977 ++exp;
7978 }
7979
7980 tickVal = significand * Math.pow(10, exp);
7981 } while (exp < endExp || (exp === endExp && significand < endSignificand));
7982
7983 var lastTick = valueOrDefault(generationOptions.max, tickVal);
7984 ticks.push(lastTick);
7985
7986 return ticks;
7987 }
7988 },
7989
7990 /**
7991 * Namespace to hold formatters for different types of ticks
7992 * @namespace Chart.Ticks.formatters
7993 */
7994 formatters: {
7995 /**
7996 * Formatter for value labels
7997 * @method Chart.Ticks.formatters.values
7998 * @param value the value to display
7999 * @return {String|Array} the label to display
8000 */
8001 values: function(value) {
8002 return helpers.isArray(value) ? value : '' + value;
8003 },
8004
8005 /**
8006 * Formatter for linear numeric ticks
8007 * @method Chart.Ticks.formatters.linear
8008 * @param tickValue {Number} the value to be formatted
8009 * @param index {Number} the position of the tickValue parameter in the ticks array
8010 * @param ticks {Array<Number>} the list of ticks being converted
8011 * @return {String} string representation of the tickValue parameter
8012 */
8013 linear: function(tickValue, index, ticks) {
8014 // If we have lots of ticks, don't use the ones
8015 var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
8016
8017 // If we have a number like 2.5 as the delta, figure out how many decimal places we need
8018 if (Math.abs(delta) > 1) {
8019 if (tickValue !== Math.floor(tickValue)) {
8020 // not an integer
8021 delta = tickValue - Math.floor(tickValue);
8022 }
8023 }
8024
8025 var logDelta = helpers.log10(Math.abs(delta));
8026 var tickString = '';
8027
8028 if (tickValue !== 0) {
8029 var numDecimal = -1 * Math.floor(logDelta);
8030 numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
8031 tickString = tickValue.toFixed(numDecimal);
8032 } else {
8033 tickString = '0'; // never show decimal places for 0
8034 }
8035
8036 return tickString;
8037 },
8038
8039 logarithmic: function(tickValue, index, ticks) {
8040 var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));
8041
8042 if (tickValue === 0) {
8043 return '0';
8044 } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
8045 return tickValue.toExponential();
8046 }
8047 return '';
8048 }
8049 }
8050 };
8051
8052 },{"45":45}],35:[function(require,module,exports){
8053 'use strict';
8054
8055 var defaults = require(25);
8056 var Element = require(26);
8057 var helpers = require(45);
8058
8059 defaults._set('global', {
8060 tooltips: {
8061 enabled: true,
8062 custom: null,
8063 mode: 'nearest',
8064 position: 'average',
8065 intersect: true,
8066 backgroundColor: 'rgba(0,0,0,0.8)',
8067 titleFontStyle: 'bold',
8068 titleSpacing: 2,
8069 titleMarginBottom: 6,
8070 titleFontColor: '#fff',
8071 titleAlign: 'left',
8072 bodySpacing: 2,
8073 bodyFontColor: '#fff',
8074 bodyAlign: 'left',
8075 footerFontStyle: 'bold',
8076 footerSpacing: 2,
8077 footerMarginTop: 6,
8078 footerFontColor: '#fff',
8079 footerAlign: 'left',
8080 yPadding: 6,
8081 xPadding: 6,
8082 caretPadding: 2,
8083 caretSize: 5,
8084 cornerRadius: 6,
8085 multiKeyBackground: '#fff',
8086 displayColors: true,
8087 borderColor: 'rgba(0,0,0,0)',
8088 borderWidth: 0,
8089 callbacks: {
8090 // Args are: (tooltipItems, data)
8091 beforeTitle: helpers.noop,
8092 title: function(tooltipItems, data) {
8093 // Pick first xLabel for now
8094 var title = '';
8095 var labels = data.labels;
8096 var labelCount = labels ? labels.length : 0;
8097
8098 if (tooltipItems.length > 0) {
8099 var item = tooltipItems[0];
8100
8101 if (item.xLabel) {
8102 title = item.xLabel;
8103 } else if (labelCount > 0 && item.index < labelCount) {
8104 title = labels[item.index];
8105 }
8106 }
8107
8108 return title;
8109 },
8110 afterTitle: helpers.noop,
8111
8112 // Args are: (tooltipItems, data)
8113 beforeBody: helpers.noop,
8114
8115 // Args are: (tooltipItem, data)
8116 beforeLabel: helpers.noop,
8117 label: function(tooltipItem, data) {
8118 var label = data.datasets[tooltipItem.datasetIndex].label || '';
8119
8120 if (label) {
8121 label += ': ';
8122 }
8123 label += tooltipItem.yLabel;
8124 return label;
8125 },
8126 labelColor: function(tooltipItem, chart) {
8127 var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
8128 var activeElement = meta.data[tooltipItem.index];
8129 var view = activeElement._view;
8130 return {
8131 borderColor: view.borderColor,
8132 backgroundColor: view.backgroundColor
8133 };
8134 },
8135 labelTextColor: function() {
8136 return this._options.bodyFontColor;
8137 },
8138 afterLabel: helpers.noop,
8139
8140 // Args are: (tooltipItems, data)
8141 afterBody: helpers.noop,
8142
8143 // Args are: (tooltipItems, data)
8144 beforeFooter: helpers.noop,
8145 footer: helpers.noop,
8146 afterFooter: helpers.noop
8147 }
8148 }
8149 });
8150
8151 module.exports = function(Chart) {
8152
8153 /**
8154 * Helper method to merge the opacity into a color
8155 */
8156 function mergeOpacity(colorString, opacity) {
8157 var color = helpers.color(colorString);
8158 return color.alpha(opacity * color.alpha()).rgbaString();
8159 }
8160
8161 // Helper to push or concat based on if the 2nd parameter is an array or not
8162 function pushOrConcat(base, toPush) {
8163 if (toPush) {
8164 if (helpers.isArray(toPush)) {
8165 // base = base.concat(toPush);
8166 Array.prototype.push.apply(base, toPush);
8167 } else {
8168 base.push(toPush);
8169 }
8170 }
8171
8172 return base;
8173 }
8174
8175 // Private helper to create a tooltip item model
8176 // @param element : the chart element (point, arc, bar) to create the tooltip item for
8177 // @return : new tooltip item
8178 function createTooltipItem(element) {
8179 var xScale = element._xScale;
8180 var yScale = element._yScale || element._scale; // handle radar || polarArea charts
8181 var index = element._index;
8182 var datasetIndex = element._datasetIndex;
8183
8184 return {
8185 xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
8186 yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
8187 index: index,
8188 datasetIndex: datasetIndex,
8189 x: element._model.x,
8190 y: element._model.y
8191 };
8192 }
8193
8194 /**
8195 * Helper to get the reset model for the tooltip
8196 * @param tooltipOpts {Object} the tooltip options
8197 */
8198 function getBaseModel(tooltipOpts) {
8199 var globalDefaults = defaults.global;
8200 var valueOrDefault = helpers.valueOrDefault;
8201
8202 return {
8203 // Positioning
8204 xPadding: tooltipOpts.xPadding,
8205 yPadding: tooltipOpts.yPadding,
8206 xAlign: tooltipOpts.xAlign,
8207 yAlign: tooltipOpts.yAlign,
8208
8209 // Body
8210 bodyFontColor: tooltipOpts.bodyFontColor,
8211 _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
8212 _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
8213 _bodyAlign: tooltipOpts.bodyAlign,
8214 bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
8215 bodySpacing: tooltipOpts.bodySpacing,
8216
8217 // Title
8218 titleFontColor: tooltipOpts.titleFontColor,
8219 _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
8220 _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
8221 titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
8222 _titleAlign: tooltipOpts.titleAlign,
8223 titleSpacing: tooltipOpts.titleSpacing,
8224 titleMarginBottom: tooltipOpts.titleMarginBottom,
8225
8226 // Footer
8227 footerFontColor: tooltipOpts.footerFontColor,
8228 _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
8229 _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
8230 footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
8231 _footerAlign: tooltipOpts.footerAlign,
8232 footerSpacing: tooltipOpts.footerSpacing,
8233 footerMarginTop: tooltipOpts.footerMarginTop,
8234
8235 // Appearance
8236 caretSize: tooltipOpts.caretSize,
8237 cornerRadius: tooltipOpts.cornerRadius,
8238 backgroundColor: tooltipOpts.backgroundColor,
8239 opacity: 0,
8240 legendColorBackground: tooltipOpts.multiKeyBackground,
8241 displayColors: tooltipOpts.displayColors,
8242 borderColor: tooltipOpts.borderColor,
8243 borderWidth: tooltipOpts.borderWidth
8244 };
8245 }
8246
8247 /**
8248 * Get the size of the tooltip
8249 */
8250 function getTooltipSize(tooltip, model) {
8251 var ctx = tooltip._chart.ctx;
8252
8253 var height = model.yPadding * 2; // Tooltip Padding
8254 var width = 0;
8255
8256 // Count of all lines in the body
8257 var body = model.body;
8258 var combinedBodyLength = body.reduce(function(count, bodyItem) {
8259 return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
8260 }, 0);
8261 combinedBodyLength += model.beforeBody.length + model.afterBody.length;
8262
8263 var titleLineCount = model.title.length;
8264 var footerLineCount = model.footer.length;
8265 var titleFontSize = model.titleFontSize;
8266 var bodyFontSize = model.bodyFontSize;
8267 var footerFontSize = model.footerFontSize;
8268
8269 height += titleLineCount * titleFontSize; // Title Lines
8270 height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
8271 height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
8272 height += combinedBodyLength * bodyFontSize; // Body Lines
8273 height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
8274 height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
8275 height += footerLineCount * (footerFontSize); // Footer Lines
8276 height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
8277
8278 // Title width
8279 var widthPadding = 0;
8280 var maxLineWidth = function(line) {
8281 width = Math.max(width, ctx.measureText(line).width + widthPadding);
8282 };
8283
8284 ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
8285 helpers.each(model.title, maxLineWidth);
8286
8287 // Body width
8288 ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
8289 helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
8290
8291 // Body lines may include some extra width due to the color box
8292 widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
8293 helpers.each(body, function(bodyItem) {
8294 helpers.each(bodyItem.before, maxLineWidth);
8295 helpers.each(bodyItem.lines, maxLineWidth);
8296 helpers.each(bodyItem.after, maxLineWidth);
8297 });
8298
8299 // Reset back to 0
8300 widthPadding = 0;
8301
8302 // Footer width
8303 ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
8304 helpers.each(model.footer, maxLineWidth);
8305
8306 // Add padding
8307 width += 2 * model.xPadding;
8308
8309 return {
8310 width: width,
8311 height: height
8312 };
8313 }
8314
8315 /**
8316 * Helper to get the alignment of a tooltip given the size
8317 */
8318 function determineAlignment(tooltip, size) {
8319 var model = tooltip._model;
8320 var chart = tooltip._chart;
8321 var chartArea = tooltip._chart.chartArea;
8322 var xAlign = 'center';
8323 var yAlign = 'center';
8324
8325 if (model.y < size.height) {
8326 yAlign = 'top';
8327 } else if (model.y > (chart.height - size.height)) {
8328 yAlign = 'bottom';
8329 }
8330
8331 var lf, rf; // functions to determine left, right alignment
8332 var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
8333 var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
8334 var midX = (chartArea.left + chartArea.right) / 2;
8335 var midY = (chartArea.top + chartArea.bottom) / 2;
8336
8337 if (yAlign === 'center') {
8338 lf = function(x) {
8339 return x <= midX;
8340 };
8341 rf = function(x) {
8342 return x > midX;
8343 };
8344 } else {
8345 lf = function(x) {
8346 return x <= (size.width / 2);
8347 };
8348 rf = function(x) {
8349 return x >= (chart.width - (size.width / 2));
8350 };
8351 }
8352
8353 olf = function(x) {
8354 return x + size.width > chart.width;
8355 };
8356 orf = function(x) {
8357 return x - size.width < 0;
8358 };
8359 yf = function(y) {
8360 return y <= midY ? 'top' : 'bottom';
8361 };
8362
8363 if (lf(model.x)) {
8364 xAlign = 'left';
8365
8366 // Is tooltip too wide and goes over the right side of the chart.?
8367 if (olf(model.x)) {
8368 xAlign = 'center';
8369 yAlign = yf(model.y);
8370 }
8371 } else if (rf(model.x)) {
8372 xAlign = 'right';
8373
8374 // Is tooltip too wide and goes outside left edge of canvas?
8375 if (orf(model.x)) {
8376 xAlign = 'center';
8377 yAlign = yf(model.y);
8378 }
8379 }
8380
8381 var opts = tooltip._options;
8382 return {
8383 xAlign: opts.xAlign ? opts.xAlign : xAlign,
8384 yAlign: opts.yAlign ? opts.yAlign : yAlign
8385 };
8386 }
8387
8388 /**
8389 * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
8390 */
8391 function getBackgroundPoint(vm, size, alignment) {
8392 // Background Position
8393 var x = vm.x;
8394 var y = vm.y;
8395
8396 var caretSize = vm.caretSize;
8397 var caretPadding = vm.caretPadding;
8398 var cornerRadius = vm.cornerRadius;
8399 var xAlign = alignment.xAlign;
8400 var yAlign = alignment.yAlign;
8401 var paddingAndSize = caretSize + caretPadding;
8402 var radiusAndPadding = cornerRadius + caretPadding;
8403
8404 if (xAlign === 'right') {
8405 x -= size.width;
8406 } else if (xAlign === 'center') {
8407 x -= (size.width / 2);
8408 }
8409
8410 if (yAlign === 'top') {
8411 y += paddingAndSize;
8412 } else if (yAlign === 'bottom') {
8413 y -= size.height + paddingAndSize;
8414 } else {
8415 y -= (size.height / 2);
8416 }
8417
8418 if (yAlign === 'center') {
8419 if (xAlign === 'left') {
8420 x += paddingAndSize;
8421 } else if (xAlign === 'right') {
8422 x -= paddingAndSize;
8423 }
8424 } else if (xAlign === 'left') {
8425 x -= radiusAndPadding;
8426 } else if (xAlign === 'right') {
8427 x += radiusAndPadding;
8428 }
8429
8430 return {
8431 x: x,
8432 y: y
8433 };
8434 }
8435
8436 Chart.Tooltip = Element.extend({
8437 initialize: function() {
8438 this._model = getBaseModel(this._options);
8439 this._lastActive = [];
8440 },
8441
8442 // Get the title
8443 // Args are: (tooltipItem, data)
8444 getTitle: function() {
8445 var me = this;
8446 var opts = me._options;
8447 var callbacks = opts.callbacks;
8448
8449 var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
8450 var title = callbacks.title.apply(me, arguments);
8451 var afterTitle = callbacks.afterTitle.apply(me, arguments);
8452
8453 var lines = [];
8454 lines = pushOrConcat(lines, beforeTitle);
8455 lines = pushOrConcat(lines, title);
8456 lines = pushOrConcat(lines, afterTitle);
8457
8458 return lines;
8459 },
8460
8461 // Args are: (tooltipItem, data)
8462 getBeforeBody: function() {
8463 var lines = this._options.callbacks.beforeBody.apply(this, arguments);
8464 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8465 },
8466
8467 // Args are: (tooltipItem, data)
8468 getBody: function(tooltipItems, data) {
8469 var me = this;
8470 var callbacks = me._options.callbacks;
8471 var bodyItems = [];
8472
8473 helpers.each(tooltipItems, function(tooltipItem) {
8474 var bodyItem = {
8475 before: [],
8476 lines: [],
8477 after: []
8478 };
8479 pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
8480 pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
8481 pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
8482
8483 bodyItems.push(bodyItem);
8484 });
8485
8486 return bodyItems;
8487 },
8488
8489 // Args are: (tooltipItem, data)
8490 getAfterBody: function() {
8491 var lines = this._options.callbacks.afterBody.apply(this, arguments);
8492 return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8493 },
8494
8495 // Get the footer and beforeFooter and afterFooter lines
8496 // Args are: (tooltipItem, data)
8497 getFooter: function() {
8498 var me = this;
8499 var callbacks = me._options.callbacks;
8500
8501 var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
8502 var footer = callbacks.footer.apply(me, arguments);
8503 var afterFooter = callbacks.afterFooter.apply(me, arguments);
8504
8505 var lines = [];
8506 lines = pushOrConcat(lines, beforeFooter);
8507 lines = pushOrConcat(lines, footer);
8508 lines = pushOrConcat(lines, afterFooter);
8509
8510 return lines;
8511 },
8512
8513 update: function(changed) {
8514 var me = this;
8515 var opts = me._options;
8516
8517 // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
8518 // 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
8519 // which breaks any animations.
8520 var existingModel = me._model;
8521 var model = me._model = getBaseModel(opts);
8522 var active = me._active;
8523
8524 var data = me._data;
8525
8526 // In the case where active.length === 0 we need to keep these at existing values for good animations
8527 var alignment = {
8528 xAlign: existingModel.xAlign,
8529 yAlign: existingModel.yAlign
8530 };
8531 var backgroundPoint = {
8532 x: existingModel.x,
8533 y: existingModel.y
8534 };
8535 var tooltipSize = {
8536 width: existingModel.width,
8537 height: existingModel.height
8538 };
8539 var tooltipPosition = {
8540 x: existingModel.caretX,
8541 y: existingModel.caretY
8542 };
8543
8544 var i, len;
8545
8546 if (active.length) {
8547 model.opacity = 1;
8548
8549 var labelColors = [];
8550 var labelTextColors = [];
8551 tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition);
8552
8553 var tooltipItems = [];
8554 for (i = 0, len = active.length; i < len; ++i) {
8555 tooltipItems.push(createTooltipItem(active[i]));
8556 }
8557
8558 // If the user provided a filter function, use it to modify the tooltip items
8559 if (opts.filter) {
8560 tooltipItems = tooltipItems.filter(function(a) {
8561 return opts.filter(a, data);
8562 });
8563 }
8564
8565 // If the user provided a sorting function, use it to modify the tooltip items
8566 if (opts.itemSort) {
8567 tooltipItems = tooltipItems.sort(function(a, b) {
8568 return opts.itemSort(a, b, data);
8569 });
8570 }
8571
8572 // Determine colors for boxes
8573 helpers.each(tooltipItems, function(tooltipItem) {
8574 labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
8575 labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
8576 });
8577
8578
8579 // Build the Text Lines
8580 model.title = me.getTitle(tooltipItems, data);
8581 model.beforeBody = me.getBeforeBody(tooltipItems, data);
8582 model.body = me.getBody(tooltipItems, data);
8583 model.afterBody = me.getAfterBody(tooltipItems, data);
8584 model.footer = me.getFooter(tooltipItems, data);
8585
8586 // Initial positioning and colors
8587 model.x = Math.round(tooltipPosition.x);
8588 model.y = Math.round(tooltipPosition.y);
8589 model.caretPadding = opts.caretPadding;
8590 model.labelColors = labelColors;
8591 model.labelTextColors = labelTextColors;
8592
8593 // data points
8594 model.dataPoints = tooltipItems;
8595
8596 // We need to determine alignment of the tooltip
8597 tooltipSize = getTooltipSize(this, model);
8598 alignment = determineAlignment(this, tooltipSize);
8599 // Final Size and Position
8600 backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment);
8601 } else {
8602 model.opacity = 0;
8603 }
8604
8605 model.xAlign = alignment.xAlign;
8606 model.yAlign = alignment.yAlign;
8607 model.x = backgroundPoint.x;
8608 model.y = backgroundPoint.y;
8609 model.width = tooltipSize.width;
8610 model.height = tooltipSize.height;
8611
8612 // Point where the caret on the tooltip points to
8613 model.caretX = tooltipPosition.x;
8614 model.caretY = tooltipPosition.y;
8615
8616 me._model = model;
8617
8618 if (changed && opts.custom) {
8619 opts.custom.call(me, model);
8620 }
8621
8622 return me;
8623 },
8624 drawCaret: function(tooltipPoint, size) {
8625 var ctx = this._chart.ctx;
8626 var vm = this._view;
8627 var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
8628
8629 ctx.lineTo(caretPosition.x1, caretPosition.y1);
8630 ctx.lineTo(caretPosition.x2, caretPosition.y2);
8631 ctx.lineTo(caretPosition.x3, caretPosition.y3);
8632 },
8633 getCaretPosition: function(tooltipPoint, size, vm) {
8634 var x1, x2, x3, y1, y2, y3;
8635 var caretSize = vm.caretSize;
8636 var cornerRadius = vm.cornerRadius;
8637 var xAlign = vm.xAlign;
8638 var yAlign = vm.yAlign;
8639 var ptX = tooltipPoint.x;
8640 var ptY = tooltipPoint.y;
8641 var width = size.width;
8642 var height = size.height;
8643
8644 if (yAlign === 'center') {
8645 y2 = ptY + (height / 2);
8646
8647 if (xAlign === 'left') {
8648 x1 = ptX;
8649 x2 = x1 - caretSize;
8650 x3 = x1;
8651
8652 y1 = y2 + caretSize;
8653 y3 = y2 - caretSize;
8654 } else {
8655 x1 = ptX + width;
8656 x2 = x1 + caretSize;
8657 x3 = x1;
8658
8659 y1 = y2 - caretSize;
8660 y3 = y2 + caretSize;
8661 }
8662 } else {
8663 if (xAlign === 'left') {
8664 x2 = ptX + cornerRadius + (caretSize);
8665 x1 = x2 - caretSize;
8666 x3 = x2 + caretSize;
8667 } else if (xAlign === 'right') {
8668 x2 = ptX + width - cornerRadius - caretSize;
8669 x1 = x2 - caretSize;
8670 x3 = x2 + caretSize;
8671 } else {
8672 x2 = ptX + (width / 2);
8673 x1 = x2 - caretSize;
8674 x3 = x2 + caretSize;
8675 }
8676 if (yAlign === 'top') {
8677 y1 = ptY;
8678 y2 = y1 - caretSize;
8679 y3 = y1;
8680 } else {
8681 y1 = ptY + height;
8682 y2 = y1 + caretSize;
8683 y3 = y1;
8684 // invert drawing order
8685 var tmp = x3;
8686 x3 = x1;
8687 x1 = tmp;
8688 }
8689 }
8690 return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
8691 },
8692 drawTitle: function(pt, vm, ctx, opacity) {
8693 var title = vm.title;
8694
8695 if (title.length) {
8696 ctx.textAlign = vm._titleAlign;
8697 ctx.textBaseline = 'top';
8698
8699 var titleFontSize = vm.titleFontSize;
8700 var titleSpacing = vm.titleSpacing;
8701
8702 ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
8703 ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8704
8705 var i, len;
8706 for (i = 0, len = title.length; i < len; ++i) {
8707 ctx.fillText(title[i], pt.x, pt.y);
8708 pt.y += titleFontSize + titleSpacing; // Line Height and spacing
8709
8710 if (i + 1 === title.length) {
8711 pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
8712 }
8713 }
8714 }
8715 },
8716 drawBody: function(pt, vm, ctx, opacity) {
8717 var bodyFontSize = vm.bodyFontSize;
8718 var bodySpacing = vm.bodySpacing;
8719 var body = vm.body;
8720
8721 ctx.textAlign = vm._bodyAlign;
8722 ctx.textBaseline = 'top';
8723 ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8724
8725 // Before Body
8726 var xLinePadding = 0;
8727 var fillLineOfText = function(line) {
8728 ctx.fillText(line, pt.x + xLinePadding, pt.y);
8729 pt.y += bodyFontSize + bodySpacing;
8730 };
8731
8732 // Before body lines
8733 ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
8734 helpers.each(vm.beforeBody, fillLineOfText);
8735
8736 var drawColorBoxes = vm.displayColors;
8737 xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
8738
8739 // Draw body lines now
8740 helpers.each(body, function(bodyItem, i) {
8741 var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
8742 ctx.fillStyle = textColor;
8743 helpers.each(bodyItem.before, fillLineOfText);
8744
8745 helpers.each(bodyItem.lines, function(line) {
8746 // Draw Legend-like boxes if needed
8747 if (drawColorBoxes) {
8748 // Fill a white rect so that colours merge nicely if the opacity is < 1
8749 ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
8750 ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8751
8752 // Border
8753 ctx.lineWidth = 1;
8754 ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
8755 ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8756
8757 // Inner square
8758 ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
8759 ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
8760 ctx.fillStyle = textColor;
8761 }
8762
8763 fillLineOfText(line);
8764 });
8765
8766 helpers.each(bodyItem.after, fillLineOfText);
8767 });
8768
8769 // Reset back to 0 for after body
8770 xLinePadding = 0;
8771
8772 // After body lines
8773 helpers.each(vm.afterBody, fillLineOfText);
8774 pt.y -= bodySpacing; // Remove last body spacing
8775 },
8776 drawFooter: function(pt, vm, ctx, opacity) {
8777 var footer = vm.footer;
8778
8779 if (footer.length) {
8780 pt.y += vm.footerMarginTop;
8781
8782 ctx.textAlign = vm._footerAlign;
8783 ctx.textBaseline = 'top';
8784
8785 ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
8786 ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8787
8788 helpers.each(footer, function(line) {
8789 ctx.fillText(line, pt.x, pt.y);
8790 pt.y += vm.footerFontSize + vm.footerSpacing;
8791 });
8792 }
8793 },
8794 drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
8795 ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
8796 ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
8797 ctx.lineWidth = vm.borderWidth;
8798 var xAlign = vm.xAlign;
8799 var yAlign = vm.yAlign;
8800 var x = pt.x;
8801 var y = pt.y;
8802 var width = tooltipSize.width;
8803 var height = tooltipSize.height;
8804 var radius = vm.cornerRadius;
8805
8806 ctx.beginPath();
8807 ctx.moveTo(x + radius, y);
8808 if (yAlign === 'top') {
8809 this.drawCaret(pt, tooltipSize);
8810 }
8811 ctx.lineTo(x + width - radius, y);
8812 ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
8813 if (yAlign === 'center' && xAlign === 'right') {
8814 this.drawCaret(pt, tooltipSize);
8815 }
8816 ctx.lineTo(x + width, y + height - radius);
8817 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
8818 if (yAlign === 'bottom') {
8819 this.drawCaret(pt, tooltipSize);
8820 }
8821 ctx.lineTo(x + radius, y + height);
8822 ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
8823 if (yAlign === 'center' && xAlign === 'left') {
8824 this.drawCaret(pt, tooltipSize);
8825 }
8826 ctx.lineTo(x, y + radius);
8827 ctx.quadraticCurveTo(x, y, x + radius, y);
8828 ctx.closePath();
8829
8830 ctx.fill();
8831
8832 if (vm.borderWidth > 0) {
8833 ctx.stroke();
8834 }
8835 },
8836 draw: function() {
8837 var ctx = this._chart.ctx;
8838 var vm = this._view;
8839
8840 if (vm.opacity === 0) {
8841 return;
8842 }
8843
8844 var tooltipSize = {
8845 width: vm.width,
8846 height: vm.height
8847 };
8848 var pt = {
8849 x: vm.x,
8850 y: vm.y
8851 };
8852
8853 // IE11/Edge does not like very small opacities, so snap to 0
8854 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
8855
8856 // Truthy/falsey value for empty tooltip
8857 var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
8858
8859 if (this._options.enabled && hasTooltipContent) {
8860 // Draw Background
8861 this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
8862
8863 // Draw Title, Body, and Footer
8864 pt.x += vm.xPadding;
8865 pt.y += vm.yPadding;
8866
8867 // Titles
8868 this.drawTitle(pt, vm, ctx, opacity);
8869
8870 // Body
8871 this.drawBody(pt, vm, ctx, opacity);
8872
8873 // Footer
8874 this.drawFooter(pt, vm, ctx, opacity);
8875 }
8876 },
8877
8878 /**
8879 * Handle an event
8880 * @private
8881 * @param {IEvent} event - The event to handle
8882 * @returns {Boolean} true if the tooltip changed
8883 */
8884 handleEvent: function(e) {
8885 var me = this;
8886 var options = me._options;
8887 var changed = false;
8888
8889 me._lastActive = me._lastActive || [];
8890
8891 // Find Active Elements for tooltips
8892 if (e.type === 'mouseout') {
8893 me._active = [];
8894 } else {
8895 me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
8896 }
8897
8898 // Remember Last Actives
8899 changed = !helpers.arrayEquals(me._active, me._lastActive);
8900
8901 // If tooltip didn't change, do not handle the target event
8902 if (!changed) {
8903 return false;
8904 }
8905
8906 me._lastActive = me._active;
8907
8908 if (options.enabled || options.custom) {
8909 me._eventPosition = {
8910 x: e.x,
8911 y: e.y
8912 };
8913
8914 var model = me._model;
8915 me.update(true);
8916 me.pivot();
8917
8918 // See if our tooltip position changed
8919 changed |= (model.x !== me._model.x) || (model.y !== me._model.y);
8920 }
8921
8922 return changed;
8923 }
8924 });
8925
8926 /**
8927 * @namespace Chart.Tooltip.positioners
8928 */
8929 Chart.Tooltip.positioners = {
8930 /**
8931 * Average mode places the tooltip at the average position of the elements shown
8932 * @function Chart.Tooltip.positioners.average
8933 * @param elements {ChartElement[]} the elements being displayed in the tooltip
8934 * @returns {Point} tooltip position
8935 */
8936 average: function(elements) {
8937 if (!elements.length) {
8938 return false;
8939 }
8940
8941 var i, len;
8942 var x = 0;
8943 var y = 0;
8944 var count = 0;
8945
8946 for (i = 0, len = elements.length; i < len; ++i) {
8947 var el = elements[i];
8948 if (el && el.hasValue()) {
8949 var pos = el.tooltipPosition();
8950 x += pos.x;
8951 y += pos.y;
8952 ++count;
8953 }
8954 }
8955
8956 return {
8957 x: Math.round(x / count),
8958 y: Math.round(y / count)
8959 };
8960 },
8961
8962 /**
8963 * Gets the tooltip position nearest of the item nearest to the event position
8964 * @function Chart.Tooltip.positioners.nearest
8965 * @param elements {Chart.Element[]} the tooltip elements
8966 * @param eventPosition {Point} the position of the event in canvas coordinates
8967 * @returns {Point} the tooltip position
8968 */
8969 nearest: function(elements, eventPosition) {
8970 var x = eventPosition.x;
8971 var y = eventPosition.y;
8972 var minDistance = Number.POSITIVE_INFINITY;
8973 var i, len, nearestElement;
8974
8975 for (i = 0, len = elements.length; i < len; ++i) {
8976 var el = elements[i];
8977 if (el && el.hasValue()) {
8978 var center = el.getCenterPoint();
8979 var d = helpers.distanceBetweenPoints(eventPosition, center);
8980
8981 if (d < minDistance) {
8982 minDistance = d;
8983 nearestElement = el;
8984 }
8985 }
8986 }
8987
8988 if (nearestElement) {
8989 var tp = nearestElement.tooltipPosition();
8990 x = tp.x;
8991 y = tp.y;
8992 }
8993
8994 return {
8995 x: x,
8996 y: y
8997 };
8998 }
8999 };
9000 };
9001
9002 },{"25":25,"26":26,"45":45}],36:[function(require,module,exports){
9003 'use strict';
9004
9005 var defaults = require(25);
9006 var Element = require(26);
9007 var helpers = require(45);
9008
9009 defaults._set('global', {
9010 elements: {
9011 arc: {
9012 backgroundColor: defaults.global.defaultColor,
9013 borderColor: '#fff',
9014 borderWidth: 2
9015 }
9016 }
9017 });
9018
9019 module.exports = Element.extend({
9020 inLabelRange: function(mouseX) {
9021 var vm = this._view;
9022
9023 if (vm) {
9024 return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
9025 }
9026 return false;
9027 },
9028
9029 inRange: function(chartX, chartY) {
9030 var vm = this._view;
9031
9032 if (vm) {
9033 var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
9034 var angle = pointRelativePosition.angle;
9035 var distance = pointRelativePosition.distance;
9036
9037 // Sanitise angle range
9038 var startAngle = vm.startAngle;
9039 var endAngle = vm.endAngle;
9040 while (endAngle < startAngle) {
9041 endAngle += 2.0 * Math.PI;
9042 }
9043 while (angle > endAngle) {
9044 angle -= 2.0 * Math.PI;
9045 }
9046 while (angle < startAngle) {
9047 angle += 2.0 * Math.PI;
9048 }
9049
9050 // Check if within the range of the open/close angle
9051 var betweenAngles = (angle >= startAngle && angle <= endAngle);
9052 var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
9053
9054 return (betweenAngles && withinRadius);
9055 }
9056 return false;
9057 },
9058
9059 getCenterPoint: function() {
9060 var vm = this._view;
9061 var halfAngle = (vm.startAngle + vm.endAngle) / 2;
9062 var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
9063 return {
9064 x: vm.x + Math.cos(halfAngle) * halfRadius,
9065 y: vm.y + Math.sin(halfAngle) * halfRadius
9066 };
9067 },
9068
9069 getArea: function() {
9070 var vm = this._view;
9071 return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
9072 },
9073
9074 tooltipPosition: function() {
9075 var vm = this._view;
9076 var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
9077 var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
9078
9079 return {
9080 x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
9081 y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
9082 };
9083 },
9084
9085 draw: function() {
9086 var ctx = this._chart.ctx;
9087 var vm = this._view;
9088 var sA = vm.startAngle;
9089 var eA = vm.endAngle;
9090
9091 ctx.beginPath();
9092
9093 ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
9094 ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
9095
9096 ctx.closePath();
9097 ctx.strokeStyle = vm.borderColor;
9098 ctx.lineWidth = vm.borderWidth;
9099
9100 ctx.fillStyle = vm.backgroundColor;
9101
9102 ctx.fill();
9103 ctx.lineJoin = 'bevel';
9104
9105 if (vm.borderWidth) {
9106 ctx.stroke();
9107 }
9108 }
9109 });
9110
9111 },{"25":25,"26":26,"45":45}],37:[function(require,module,exports){
9112 'use strict';
9113
9114 var defaults = require(25);
9115 var Element = require(26);
9116 var helpers = require(45);
9117
9118 var globalDefaults = defaults.global;
9119
9120 defaults._set('global', {
9121 elements: {
9122 line: {
9123 tension: 0.4,
9124 backgroundColor: globalDefaults.defaultColor,
9125 borderWidth: 3,
9126 borderColor: globalDefaults.defaultColor,
9127 borderCapStyle: 'butt',
9128 borderDash: [],
9129 borderDashOffset: 0.0,
9130 borderJoinStyle: 'miter',
9131 capBezierPoints: true,
9132 fill: true, // do we fill in the area between the line and its base axis
9133 }
9134 }
9135 });
9136
9137 module.exports = Element.extend({
9138 draw: function() {
9139 var me = this;
9140 var vm = me._view;
9141 var ctx = me._chart.ctx;
9142 var spanGaps = vm.spanGaps;
9143 var points = me._children.slice(); // clone array
9144 var globalOptionLineElements = globalDefaults.elements.line;
9145 var lastDrawnIndex = -1;
9146 var index, current, previous, currentVM;
9147
9148 // If we are looping, adding the first point again
9149 if (me._loop && points.length) {
9150 points.push(points[0]);
9151 }
9152
9153 ctx.save();
9154
9155 // Stroke Line Options
9156 ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
9157
9158 // IE 9 and 10 do not support line dash
9159 if (ctx.setLineDash) {
9160 ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
9161 }
9162
9163 ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
9164 ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
9165 ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
9166 ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
9167
9168 // Stroke Line
9169 ctx.beginPath();
9170 lastDrawnIndex = -1;
9171
9172 for (index = 0; index < points.length; ++index) {
9173 current = points[index];
9174 previous = helpers.previousItem(points, index);
9175 currentVM = current._view;
9176
9177 // First point moves to it's starting position no matter what
9178 if (index === 0) {
9179 if (!currentVM.skip) {
9180 ctx.moveTo(currentVM.x, currentVM.y);
9181 lastDrawnIndex = index;
9182 }
9183 } else {
9184 previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
9185
9186 if (!currentVM.skip) {
9187 if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
9188 // There was a gap and this is the first point after the gap
9189 ctx.moveTo(currentVM.x, currentVM.y);
9190 } else {
9191 // Line to next point
9192 helpers.canvas.lineTo(ctx, previous._view, current._view);
9193 }
9194 lastDrawnIndex = index;
9195 }
9196 }
9197 }
9198
9199 ctx.stroke();
9200 ctx.restore();
9201 }
9202 });
9203
9204 },{"25":25,"26":26,"45":45}],38:[function(require,module,exports){
9205 'use strict';
9206
9207 var defaults = require(25);
9208 var Element = require(26);
9209 var helpers = require(45);
9210
9211 var defaultColor = defaults.global.defaultColor;
9212
9213 defaults._set('global', {
9214 elements: {
9215 point: {
9216 radius: 3,
9217 pointStyle: 'circle',
9218 backgroundColor: defaultColor,
9219 borderColor: defaultColor,
9220 borderWidth: 1,
9221 // Hover
9222 hitRadius: 1,
9223 hoverRadius: 4,
9224 hoverBorderWidth: 1
9225 }
9226 }
9227 });
9228
9229 function xRange(mouseX) {
9230 var vm = this._view;
9231 return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9232 }
9233
9234 function yRange(mouseY) {
9235 var vm = this._view;
9236 return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9237 }
9238
9239 module.exports = Element.extend({
9240 inRange: function(mouseX, mouseY) {
9241 var vm = this._view;
9242 return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
9243 },
9244
9245 inLabelRange: xRange,
9246 inXRange: xRange,
9247 inYRange: yRange,
9248
9249 getCenterPoint: function() {
9250 var vm = this._view;
9251 return {
9252 x: vm.x,
9253 y: vm.y
9254 };
9255 },
9256
9257 getArea: function() {
9258 return Math.PI * Math.pow(this._view.radius, 2);
9259 },
9260
9261 tooltipPosition: function() {
9262 var vm = this._view;
9263 return {
9264 x: vm.x,
9265 y: vm.y,
9266 padding: vm.radius + vm.borderWidth
9267 };
9268 },
9269
9270 draw: function(chartArea) {
9271 var vm = this._view;
9272 var model = this._model;
9273 var ctx = this._chart.ctx;
9274 var pointStyle = vm.pointStyle;
9275 var radius = vm.radius;
9276 var x = vm.x;
9277 var y = vm.y;
9278 var color = helpers.color;
9279 var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
9280 var ratio = 0;
9281
9282 if (vm.skip) {
9283 return;
9284 }
9285
9286 ctx.strokeStyle = vm.borderColor || defaultColor;
9287 ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth);
9288 ctx.fillStyle = vm.backgroundColor || defaultColor;
9289
9290 // Cliping for Points.
9291 // going out from inner charArea?
9292 if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) {
9293 // Point fade out
9294 if (model.x < chartArea.left) {
9295 ratio = (x - model.x) / (chartArea.left - model.x);
9296 } else if (chartArea.right * errMargin < model.x) {
9297 ratio = (model.x - x) / (model.x - chartArea.right);
9298 } else if (model.y < chartArea.top) {
9299 ratio = (y - model.y) / (chartArea.top - model.y);
9300 } else if (chartArea.bottom * errMargin < model.y) {
9301 ratio = (model.y - y) / (model.y - chartArea.bottom);
9302 }
9303 ratio = Math.round(ratio * 100) / 100;
9304 ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();
9305 ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
9306 }
9307
9308 helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y);
9309 }
9310 });
9311
9312 },{"25":25,"26":26,"45":45}],39:[function(require,module,exports){
9313 'use strict';
9314
9315 var defaults = require(25);
9316 var Element = require(26);
9317
9318 defaults._set('global', {
9319 elements: {
9320 rectangle: {
9321 backgroundColor: defaults.global.defaultColor,
9322 borderColor: defaults.global.defaultColor,
9323 borderSkipped: 'bottom',
9324 borderWidth: 0
9325 }
9326 }
9327 });
9328
9329 function isVertical(bar) {
9330 return bar._view.width !== undefined;
9331 }
9332
9333 /**
9334 * Helper function to get the bounds of the bar regardless of the orientation
9335 * @param bar {Chart.Element.Rectangle} the bar
9336 * @return {Bounds} bounds of the bar
9337 * @private
9338 */
9339 function getBarBounds(bar) {
9340 var vm = bar._view;
9341 var x1, x2, y1, y2;
9342
9343 if (isVertical(bar)) {
9344 // vertical
9345 var halfWidth = vm.width / 2;
9346 x1 = vm.x - halfWidth;
9347 x2 = vm.x + halfWidth;
9348 y1 = Math.min(vm.y, vm.base);
9349 y2 = Math.max(vm.y, vm.base);
9350 } else {
9351 // horizontal bar
9352 var halfHeight = vm.height / 2;
9353 x1 = Math.min(vm.x, vm.base);
9354 x2 = Math.max(vm.x, vm.base);
9355 y1 = vm.y - halfHeight;
9356 y2 = vm.y + halfHeight;
9357 }
9358
9359 return {
9360 left: x1,
9361 top: y1,
9362 right: x2,
9363 bottom: y2
9364 };
9365 }
9366
9367 module.exports = Element.extend({
9368 draw: function() {
9369 var ctx = this._chart.ctx;
9370 var vm = this._view;
9371 var left, right, top, bottom, signX, signY, borderSkipped;
9372 var borderWidth = vm.borderWidth;
9373
9374 if (!vm.horizontal) {
9375 // bar
9376 left = vm.x - vm.width / 2;
9377 right = vm.x + vm.width / 2;
9378 top = vm.y;
9379 bottom = vm.base;
9380 signX = 1;
9381 signY = bottom > top ? 1 : -1;
9382 borderSkipped = vm.borderSkipped || 'bottom';
9383 } else {
9384 // horizontal bar
9385 left = vm.base;
9386 right = vm.x;
9387 top = vm.y - vm.height / 2;
9388 bottom = vm.y + vm.height / 2;
9389 signX = right > left ? 1 : -1;
9390 signY = 1;
9391 borderSkipped = vm.borderSkipped || 'left';
9392 }
9393
9394 // Canvas doesn't allow us to stroke inside the width so we can
9395 // adjust the sizes to fit if we're setting a stroke on the line
9396 if (borderWidth) {
9397 // borderWidth shold be less than bar width and bar height.
9398 var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
9399 borderWidth = borderWidth > barSize ? barSize : borderWidth;
9400 var halfStroke = borderWidth / 2;
9401 // Adjust borderWidth when bar top position is near vm.base(zero).
9402 var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
9403 var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
9404 var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
9405 var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
9406 // not become a vertical line?
9407 if (borderLeft !== borderRight) {
9408 top = borderTop;
9409 bottom = borderBottom;
9410 }
9411 // not become a horizontal line?
9412 if (borderTop !== borderBottom) {
9413 left = borderLeft;
9414 right = borderRight;
9415 }
9416 }
9417
9418 ctx.beginPath();
9419 ctx.fillStyle = vm.backgroundColor;
9420 ctx.strokeStyle = vm.borderColor;
9421 ctx.lineWidth = borderWidth;
9422
9423 // Corner points, from bottom-left to bottom-right clockwise
9424 // | 1 2 |
9425 // | 0 3 |
9426 var corners = [
9427 [left, bottom],
9428 [left, top],
9429 [right, top],
9430 [right, bottom]
9431 ];
9432
9433 // Find first (starting) corner with fallback to 'bottom'
9434 var borders = ['bottom', 'left', 'top', 'right'];
9435 var startCorner = borders.indexOf(borderSkipped, 0);
9436 if (startCorner === -1) {
9437 startCorner = 0;
9438 }
9439
9440 function cornerAt(index) {
9441 return corners[(startCorner + index) % 4];
9442 }
9443
9444 // Draw rectangle from 'startCorner'
9445 var corner = cornerAt(0);
9446 ctx.moveTo(corner[0], corner[1]);
9447
9448 for (var i = 1; i < 4; i++) {
9449 corner = cornerAt(i);
9450 ctx.lineTo(corner[0], corner[1]);
9451 }
9452
9453 ctx.fill();
9454 if (borderWidth) {
9455 ctx.stroke();
9456 }
9457 },
9458
9459 height: function() {
9460 var vm = this._view;
9461 return vm.base - vm.y;
9462 },
9463
9464 inRange: function(mouseX, mouseY) {
9465 var inRange = false;
9466
9467 if (this._view) {
9468 var bounds = getBarBounds(this);
9469 inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
9470 }
9471
9472 return inRange;
9473 },
9474
9475 inLabelRange: function(mouseX, mouseY) {
9476 var me = this;
9477 if (!me._view) {
9478 return false;
9479 }
9480
9481 var inRange = false;
9482 var bounds = getBarBounds(me);
9483
9484 if (isVertical(me)) {
9485 inRange = mouseX >= bounds.left && mouseX <= bounds.right;
9486 } else {
9487 inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
9488 }
9489
9490 return inRange;
9491 },
9492
9493 inXRange: function(mouseX) {
9494 var bounds = getBarBounds(this);
9495 return mouseX >= bounds.left && mouseX <= bounds.right;
9496 },
9497
9498 inYRange: function(mouseY) {
9499 var bounds = getBarBounds(this);
9500 return mouseY >= bounds.top && mouseY <= bounds.bottom;
9501 },
9502
9503 getCenterPoint: function() {
9504 var vm = this._view;
9505 var x, y;
9506 if (isVertical(this)) {
9507 x = vm.x;
9508 y = (vm.y + vm.base) / 2;
9509 } else {
9510 x = (vm.x + vm.base) / 2;
9511 y = vm.y;
9512 }
9513
9514 return {x: x, y: y};
9515 },
9516
9517 getArea: function() {
9518 var vm = this._view;
9519 return vm.width * Math.abs(vm.y - vm.base);
9520 },
9521
9522 tooltipPosition: function() {
9523 var vm = this._view;
9524 return {
9525 x: vm.x,
9526 y: vm.y
9527 };
9528 }
9529 });
9530
9531 },{"25":25,"26":26}],40:[function(require,module,exports){
9532 'use strict';
9533
9534 module.exports = {};
9535 module.exports.Arc = require(36);
9536 module.exports.Line = require(37);
9537 module.exports.Point = require(38);
9538 module.exports.Rectangle = require(39);
9539
9540 },{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){
9541 'use strict';
9542
9543 var helpers = require(42);
9544
9545 /**
9546 * @namespace Chart.helpers.canvas
9547 */
9548 var exports = module.exports = {
9549 /**
9550 * Clears the entire canvas associated to the given `chart`.
9551 * @param {Chart} chart - The chart for which to clear the canvas.
9552 */
9553 clear: function(chart) {
9554 chart.ctx.clearRect(0, 0, chart.width, chart.height);
9555 },
9556
9557 /**
9558 * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
9559 * given size (width, height) and the same `radius` for all corners.
9560 * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
9561 * @param {Number} x - The x axis of the coordinate for the rectangle starting point.
9562 * @param {Number} y - The y axis of the coordinate for the rectangle starting point.
9563 * @param {Number} width - The rectangle's width.
9564 * @param {Number} height - The rectangle's height.
9565 * @param {Number} radius - The rounded amount (in pixels) for the four corners.
9566 * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
9567 */
9568 roundedRect: function(ctx, x, y, width, height, radius) {
9569 if (radius) {
9570 var rx = Math.min(radius, width / 2);
9571 var ry = Math.min(radius, height / 2);
9572
9573 ctx.moveTo(x + rx, y);
9574 ctx.lineTo(x + width - rx, y);
9575 ctx.quadraticCurveTo(x + width, y, x + width, y + ry);
9576 ctx.lineTo(x + width, y + height - ry);
9577 ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
9578 ctx.lineTo(x + rx, y + height);
9579 ctx.quadraticCurveTo(x, y + height, x, y + height - ry);
9580 ctx.lineTo(x, y + ry);
9581 ctx.quadraticCurveTo(x, y, x + rx, y);
9582 } else {
9583 ctx.rect(x, y, width, height);
9584 }
9585 },
9586
9587 drawPoint: function(ctx, style, radius, x, y) {
9588 var type, edgeLength, xOffset, yOffset, height, size;
9589
9590 if (style && typeof style === 'object') {
9591 type = style.toString();
9592 if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
9593 ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
9594 return;
9595 }
9596 }
9597
9598 if (isNaN(radius) || radius <= 0) {
9599 return;
9600 }
9601
9602 switch (style) {
9603 // Default includes circle
9604 default:
9605 ctx.beginPath();
9606 ctx.arc(x, y, radius, 0, Math.PI * 2);
9607 ctx.closePath();
9608 ctx.fill();
9609 break;
9610 case 'triangle':
9611 ctx.beginPath();
9612 edgeLength = 3 * radius / Math.sqrt(3);
9613 height = edgeLength * Math.sqrt(3) / 2;
9614 ctx.moveTo(x - edgeLength / 2, y + height / 3);
9615 ctx.lineTo(x + edgeLength / 2, y + height / 3);
9616 ctx.lineTo(x, y - 2 * height / 3);
9617 ctx.closePath();
9618 ctx.fill();
9619 break;
9620 case 'rect':
9621 size = 1 / Math.SQRT2 * radius;
9622 ctx.beginPath();
9623 ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
9624 ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
9625 break;
9626 case 'rectRounded':
9627 var offset = radius / Math.SQRT2;
9628 var leftX = x - offset;
9629 var topY = y - offset;
9630 var sideSize = Math.SQRT2 * radius;
9631 ctx.beginPath();
9632 this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2);
9633 ctx.closePath();
9634 ctx.fill();
9635 break;
9636 case 'rectRot':
9637 size = 1 / Math.SQRT2 * radius;
9638 ctx.beginPath();
9639 ctx.moveTo(x - size, y);
9640 ctx.lineTo(x, y + size);
9641 ctx.lineTo(x + size, y);
9642 ctx.lineTo(x, y - size);
9643 ctx.closePath();
9644 ctx.fill();
9645 break;
9646 case 'cross':
9647 ctx.beginPath();
9648 ctx.moveTo(x, y + radius);
9649 ctx.lineTo(x, y - radius);
9650 ctx.moveTo(x - radius, y);
9651 ctx.lineTo(x + radius, y);
9652 ctx.closePath();
9653 break;
9654 case 'crossRot':
9655 ctx.beginPath();
9656 xOffset = Math.cos(Math.PI / 4) * radius;
9657 yOffset = Math.sin(Math.PI / 4) * radius;
9658 ctx.moveTo(x - xOffset, y - yOffset);
9659 ctx.lineTo(x + xOffset, y + yOffset);
9660 ctx.moveTo(x - xOffset, y + yOffset);
9661 ctx.lineTo(x + xOffset, y - yOffset);
9662 ctx.closePath();
9663 break;
9664 case 'star':
9665 ctx.beginPath();
9666 ctx.moveTo(x, y + radius);
9667 ctx.lineTo(x, y - radius);
9668 ctx.moveTo(x - radius, y);
9669 ctx.lineTo(x + radius, y);
9670 xOffset = Math.cos(Math.PI / 4) * radius;
9671 yOffset = Math.sin(Math.PI / 4) * radius;
9672 ctx.moveTo(x - xOffset, y - yOffset);
9673 ctx.lineTo(x + xOffset, y + yOffset);
9674 ctx.moveTo(x - xOffset, y + yOffset);
9675 ctx.lineTo(x + xOffset, y - yOffset);
9676 ctx.closePath();
9677 break;
9678 case 'line':
9679 ctx.beginPath();
9680 ctx.moveTo(x - radius, y);
9681 ctx.lineTo(x + radius, y);
9682 ctx.closePath();
9683 break;
9684 case 'dash':
9685 ctx.beginPath();
9686 ctx.moveTo(x, y);
9687 ctx.lineTo(x + radius, y);
9688 ctx.closePath();
9689 break;
9690 }
9691
9692 ctx.stroke();
9693 },
9694
9695 clipArea: function(ctx, area) {
9696 ctx.save();
9697 ctx.beginPath();
9698 ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
9699 ctx.clip();
9700 },
9701
9702 unclipArea: function(ctx) {
9703 ctx.restore();
9704 },
9705
9706 lineTo: function(ctx, previous, target, flip) {
9707 if (target.steppedLine) {
9708 if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) {
9709 ctx.lineTo(previous.x, target.y);
9710 } else {
9711 ctx.lineTo(target.x, previous.y);
9712 }
9713 ctx.lineTo(target.x, target.y);
9714 return;
9715 }
9716
9717 if (!target.tension) {
9718 ctx.lineTo(target.x, target.y);
9719 return;
9720 }
9721
9722 ctx.bezierCurveTo(
9723 flip ? previous.controlPointPreviousX : previous.controlPointNextX,
9724 flip ? previous.controlPointPreviousY : previous.controlPointNextY,
9725 flip ? target.controlPointNextX : target.controlPointPreviousX,
9726 flip ? target.controlPointNextY : target.controlPointPreviousY,
9727 target.x,
9728 target.y);
9729 }
9730 };
9731
9732 // DEPRECATIONS
9733
9734 /**
9735 * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
9736 * @namespace Chart.helpers.clear
9737 * @deprecated since version 2.7.0
9738 * @todo remove at version 3
9739 * @private
9740 */
9741 helpers.clear = exports.clear;
9742
9743 /**
9744 * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
9745 * @namespace Chart.helpers.drawRoundedRectangle
9746 * @deprecated since version 2.7.0
9747 * @todo remove at version 3
9748 * @private
9749 */
9750 helpers.drawRoundedRectangle = function(ctx) {
9751 ctx.beginPath();
9752 exports.roundedRect.apply(exports, arguments);
9753 ctx.closePath();
9754 };
9755
9756 },{"42":42}],42:[function(require,module,exports){
9757 'use strict';
9758
9759 /**
9760 * @namespace Chart.helpers
9761 */
9762 var helpers = {
9763 /**
9764 * An empty function that can be used, for example, for optional callback.
9765 */
9766 noop: function() {},
9767
9768 /**
9769 * Returns a unique id, sequentially generated from a global variable.
9770 * @returns {Number}
9771 * @function
9772 */
9773 uid: (function() {
9774 var id = 0;
9775 return function() {
9776 return id++;
9777 };
9778 }()),
9779
9780 /**
9781 * Returns true if `value` is neither null nor undefined, else returns false.
9782 * @param {*} value - The value to test.
9783 * @returns {Boolean}
9784 * @since 2.7.0
9785 */
9786 isNullOrUndef: function(value) {
9787 return value === null || typeof value === 'undefined';
9788 },
9789
9790 /**
9791 * Returns true if `value` is an array, else returns false.
9792 * @param {*} value - The value to test.
9793 * @returns {Boolean}
9794 * @function
9795 */
9796 isArray: Array.isArray ? Array.isArray : function(value) {
9797 return Object.prototype.toString.call(value) === '[object Array]';
9798 },
9799
9800 /**
9801 * Returns true if `value` is an object (excluding null), else returns false.
9802 * @param {*} value - The value to test.
9803 * @returns {Boolean}
9804 * @since 2.7.0
9805 */
9806 isObject: function(value) {
9807 return value !== null && Object.prototype.toString.call(value) === '[object Object]';
9808 },
9809
9810 /**
9811 * Returns `value` if defined, else returns `defaultValue`.
9812 * @param {*} value - The value to return if defined.
9813 * @param {*} defaultValue - The value to return if `value` is undefined.
9814 * @returns {*}
9815 */
9816 valueOrDefault: function(value, defaultValue) {
9817 return typeof value === 'undefined' ? defaultValue : value;
9818 },
9819
9820 /**
9821 * Returns value at the given `index` in array if defined, else returns `defaultValue`.
9822 * @param {Array} value - The array to lookup for value at `index`.
9823 * @param {Number} index - The index in `value` to lookup for value.
9824 * @param {*} defaultValue - The value to return if `value[index]` is undefined.
9825 * @returns {*}
9826 */
9827 valueAtIndexOrDefault: function(value, index, defaultValue) {
9828 return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
9829 },
9830
9831 /**
9832 * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
9833 * value returned by `fn`. If `fn` is not a function, this method returns undefined.
9834 * @param {Function} fn - The function to call.
9835 * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
9836 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9837 * @returns {*}
9838 */
9839 callback: function(fn, args, thisArg) {
9840 if (fn && typeof fn.call === 'function') {
9841 return fn.apply(thisArg, args);
9842 }
9843 },
9844
9845 /**
9846 * Note(SB) for performance sake, this method should only be used when loopable type
9847 * is unknown or in none intensive code (not called often and small loopable). Else
9848 * it's preferable to use a regular for() loop and save extra function calls.
9849 * @param {Object|Array} loopable - The object or array to be iterated.
9850 * @param {Function} fn - The function to call for each item.
9851 * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9852 * @param {Boolean} [reverse] - If true, iterates backward on the loopable.
9853 */
9854 each: function(loopable, fn, thisArg, reverse) {
9855 var i, len, keys;
9856 if (helpers.isArray(loopable)) {
9857 len = loopable.length;
9858 if (reverse) {
9859 for (i = len - 1; i >= 0; i--) {
9860 fn.call(thisArg, loopable[i], i);
9861 }
9862 } else {
9863 for (i = 0; i < len; i++) {
9864 fn.call(thisArg, loopable[i], i);
9865 }
9866 }
9867 } else if (helpers.isObject(loopable)) {
9868 keys = Object.keys(loopable);
9869 len = keys.length;
9870 for (i = 0; i < len; i++) {
9871 fn.call(thisArg, loopable[keys[i]], keys[i]);
9872 }
9873 }
9874 },
9875
9876 /**
9877 * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
9878 * @see http://stackoverflow.com/a/14853974
9879 * @param {Array} a0 - The array to compare
9880 * @param {Array} a1 - The array to compare
9881 * @returns {Boolean}
9882 */
9883 arrayEquals: function(a0, a1) {
9884 var i, ilen, v0, v1;
9885
9886 if (!a0 || !a1 || a0.length !== a1.length) {
9887 return false;
9888 }
9889
9890 for (i = 0, ilen = a0.length; i < ilen; ++i) {
9891 v0 = a0[i];
9892 v1 = a1[i];
9893
9894 if (v0 instanceof Array && v1 instanceof Array) {
9895 if (!helpers.arrayEquals(v0, v1)) {
9896 return false;
9897 }
9898 } else if (v0 !== v1) {
9899 // NOTE: two different object instances will never be equal: {x:20} != {x:20}
9900 return false;
9901 }
9902 }
9903
9904 return true;
9905 },
9906
9907 /**
9908 * Returns a deep copy of `source` without keeping references on objects and arrays.
9909 * @param {*} source - The value to clone.
9910 * @returns {*}
9911 */
9912 clone: function(source) {
9913 if (helpers.isArray(source)) {
9914 return source.map(helpers.clone);
9915 }
9916
9917 if (helpers.isObject(source)) {
9918 var target = {};
9919 var keys = Object.keys(source);
9920 var klen = keys.length;
9921 var k = 0;
9922
9923 for (; k < klen; ++k) {
9924 target[keys[k]] = helpers.clone(source[keys[k]]);
9925 }
9926
9927 return target;
9928 }
9929
9930 return source;
9931 },
9932
9933 /**
9934 * The default merger when Chart.helpers.merge is called without merger option.
9935 * Note(SB): this method is also used by configMerge and scaleMerge as fallback.
9936 * @private
9937 */
9938 _merger: function(key, target, source, options) {
9939 var tval = target[key];
9940 var sval = source[key];
9941
9942 if (helpers.isObject(tval) && helpers.isObject(sval)) {
9943 helpers.merge(tval, sval, options);
9944 } else {
9945 target[key] = helpers.clone(sval);
9946 }
9947 },
9948
9949 /**
9950 * Merges source[key] in target[key] only if target[key] is undefined.
9951 * @private
9952 */
9953 _mergerIf: function(key, target, source) {
9954 var tval = target[key];
9955 var sval = source[key];
9956
9957 if (helpers.isObject(tval) && helpers.isObject(sval)) {
9958 helpers.mergeIf(tval, sval);
9959 } else if (!target.hasOwnProperty(key)) {
9960 target[key] = helpers.clone(sval);
9961 }
9962 },
9963
9964 /**
9965 * Recursively deep copies `source` properties into `target` with the given `options`.
9966 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
9967 * @param {Object} target - The target object in which all sources are merged into.
9968 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
9969 * @param {Object} [options] - Merging options:
9970 * @param {Function} [options.merger] - The merge method (key, target, source, options)
9971 * @returns {Object} The `target` object.
9972 */
9973 merge: function(target, source, options) {
9974 var sources = helpers.isArray(source) ? source : [source];
9975 var ilen = sources.length;
9976 var merge, i, keys, klen, k;
9977
9978 if (!helpers.isObject(target)) {
9979 return target;
9980 }
9981
9982 options = options || {};
9983 merge = options.merger || helpers._merger;
9984
9985 for (i = 0; i < ilen; ++i) {
9986 source = sources[i];
9987 if (!helpers.isObject(source)) {
9988 continue;
9989 }
9990
9991 keys = Object.keys(source);
9992 for (k = 0, klen = keys.length; k < klen; ++k) {
9993 merge(keys[k], target, source, options);
9994 }
9995 }
9996
9997 return target;
9998 },
9999
10000 /**
10001 * Recursively deep copies `source` properties into `target` *only* if not defined in target.
10002 * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
10003 * @param {Object} target - The target object in which all sources are merged into.
10004 * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
10005 * @returns {Object} The `target` object.
10006 */
10007 mergeIf: function(target, source) {
10008 return helpers.merge(target, source, {merger: helpers._mergerIf});
10009 },
10010
10011 /**
10012 * Applies the contents of two or more objects together into the first object.
10013 * @param {Object} target - The target object in which all objects are merged into.
10014 * @param {Object} arg1 - Object containing additional properties to merge in target.
10015 * @param {Object} argN - Additional objects containing properties to merge in target.
10016 * @returns {Object} The `target` object.
10017 */
10018 extend: function(target) {
10019 var setFn = function(value, key) {
10020 target[key] = value;
10021 };
10022 for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
10023 helpers.each(arguments[i], setFn);
10024 }
10025 return target;
10026 },
10027
10028 /**
10029 * Basic javascript inheritance based on the model created in Backbone.js
10030 */
10031 inherits: function(extensions) {
10032 var me = this;
10033 var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
10034 return me.apply(this, arguments);
10035 };
10036
10037 var Surrogate = function() {
10038 this.constructor = ChartElement;
10039 };
10040
10041 Surrogate.prototype = me.prototype;
10042 ChartElement.prototype = new Surrogate();
10043 ChartElement.extend = helpers.inherits;
10044
10045 if (extensions) {
10046 helpers.extend(ChartElement.prototype, extensions);
10047 }
10048
10049 ChartElement.__super__ = me.prototype;
10050 return ChartElement;
10051 }
10052 };
10053
10054 module.exports = helpers;
10055
10056 // DEPRECATIONS
10057
10058 /**
10059 * Provided for backward compatibility, use Chart.helpers.callback instead.
10060 * @function Chart.helpers.callCallback
10061 * @deprecated since version 2.6.0
10062 * @todo remove at version 3
10063 * @private
10064 */
10065 helpers.callCallback = helpers.callback;
10066
10067 /**
10068 * Provided for backward compatibility, use Array.prototype.indexOf instead.
10069 * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
10070 * @function Chart.helpers.indexOf
10071 * @deprecated since version 2.7.0
10072 * @todo remove at version 3
10073 * @private
10074 */
10075 helpers.indexOf = function(array, item, fromIndex) {
10076 return Array.prototype.indexOf.call(array, item, fromIndex);
10077 };
10078
10079 /**
10080 * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
10081 * @function Chart.helpers.getValueOrDefault
10082 * @deprecated since version 2.7.0
10083 * @todo remove at version 3
10084 * @private
10085 */
10086 helpers.getValueOrDefault = helpers.valueOrDefault;
10087
10088 /**
10089 * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
10090 * @function Chart.helpers.getValueAtIndexOrDefault
10091 * @deprecated since version 2.7.0
10092 * @todo remove at version 3
10093 * @private
10094 */
10095 helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
10096
10097 },{}],43:[function(require,module,exports){
10098 'use strict';
10099
10100 var helpers = require(42);
10101
10102 /**
10103 * Easing functions adapted from Robert Penner's easing equations.
10104 * @namespace Chart.helpers.easingEffects
10105 * @see http://www.robertpenner.com/easing/
10106 */
10107 var effects = {
10108 linear: function(t) {
10109 return t;
10110 },
10111
10112 easeInQuad: function(t) {
10113 return t * t;
10114 },
10115
10116 easeOutQuad: function(t) {
10117 return -t * (t - 2);
10118 },
10119
10120 easeInOutQuad: function(t) {
10121 if ((t /= 0.5) < 1) {
10122 return 0.5 * t * t;
10123 }
10124 return -0.5 * ((--t) * (t - 2) - 1);
10125 },
10126
10127 easeInCubic: function(t) {
10128 return t * t * t;
10129 },
10130
10131 easeOutCubic: function(t) {
10132 return (t = t - 1) * t * t + 1;
10133 },
10134
10135 easeInOutCubic: function(t) {
10136 if ((t /= 0.5) < 1) {
10137 return 0.5 * t * t * t;
10138 }
10139 return 0.5 * ((t -= 2) * t * t + 2);
10140 },
10141
10142 easeInQuart: function(t) {
10143 return t * t * t * t;
10144 },
10145
10146 easeOutQuart: function(t) {
10147 return -((t = t - 1) * t * t * t - 1);
10148 },
10149
10150 easeInOutQuart: function(t) {
10151 if ((t /= 0.5) < 1) {
10152 return 0.5 * t * t * t * t;
10153 }
10154 return -0.5 * ((t -= 2) * t * t * t - 2);
10155 },
10156
10157 easeInQuint: function(t) {
10158 return t * t * t * t * t;
10159 },
10160
10161 easeOutQuint: function(t) {
10162 return (t = t - 1) * t * t * t * t + 1;
10163 },
10164
10165 easeInOutQuint: function(t) {
10166 if ((t /= 0.5) < 1) {
10167 return 0.5 * t * t * t * t * t;
10168 }
10169 return 0.5 * ((t -= 2) * t * t * t * t + 2);
10170 },
10171
10172 easeInSine: function(t) {
10173 return -Math.cos(t * (Math.PI / 2)) + 1;
10174 },
10175
10176 easeOutSine: function(t) {
10177 return Math.sin(t * (Math.PI / 2));
10178 },
10179
10180 easeInOutSine: function(t) {
10181 return -0.5 * (Math.cos(Math.PI * t) - 1);
10182 },
10183
10184 easeInExpo: function(t) {
10185 return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
10186 },
10187
10188 easeOutExpo: function(t) {
10189 return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
10190 },
10191
10192 easeInOutExpo: function(t) {
10193 if (t === 0) {
10194 return 0;
10195 }
10196 if (t === 1) {
10197 return 1;
10198 }
10199 if ((t /= 0.5) < 1) {
10200 return 0.5 * Math.pow(2, 10 * (t - 1));
10201 }
10202 return 0.5 * (-Math.pow(2, -10 * --t) + 2);
10203 },
10204
10205 easeInCirc: function(t) {
10206 if (t >= 1) {
10207 return t;
10208 }
10209 return -(Math.sqrt(1 - t * t) - 1);
10210 },
10211
10212 easeOutCirc: function(t) {
10213 return Math.sqrt(1 - (t = t - 1) * t);
10214 },
10215
10216 easeInOutCirc: function(t) {
10217 if ((t /= 0.5) < 1) {
10218 return -0.5 * (Math.sqrt(1 - t * t) - 1);
10219 }
10220 return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
10221 },
10222
10223 easeInElastic: function(t) {
10224 var s = 1.70158;
10225 var p = 0;
10226 var a = 1;
10227 if (t === 0) {
10228 return 0;
10229 }
10230 if (t === 1) {
10231 return 1;
10232 }
10233 if (!p) {
10234 p = 0.3;
10235 }
10236 if (a < 1) {
10237 a = 1;
10238 s = p / 4;
10239 } else {
10240 s = p / (2 * Math.PI) * Math.asin(1 / a);
10241 }
10242 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10243 },
10244
10245 easeOutElastic: function(t) {
10246 var s = 1.70158;
10247 var p = 0;
10248 var a = 1;
10249 if (t === 0) {
10250 return 0;
10251 }
10252 if (t === 1) {
10253 return 1;
10254 }
10255 if (!p) {
10256 p = 0.3;
10257 }
10258 if (a < 1) {
10259 a = 1;
10260 s = p / 4;
10261 } else {
10262 s = p / (2 * Math.PI) * Math.asin(1 / a);
10263 }
10264 return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
10265 },
10266
10267 easeInOutElastic: function(t) {
10268 var s = 1.70158;
10269 var p = 0;
10270 var a = 1;
10271 if (t === 0) {
10272 return 0;
10273 }
10274 if ((t /= 0.5) === 2) {
10275 return 1;
10276 }
10277 if (!p) {
10278 p = 0.45;
10279 }
10280 if (a < 1) {
10281 a = 1;
10282 s = p / 4;
10283 } else {
10284 s = p / (2 * Math.PI) * Math.asin(1 / a);
10285 }
10286 if (t < 1) {
10287 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10288 }
10289 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
10290 },
10291 easeInBack: function(t) {
10292 var s = 1.70158;
10293 return t * t * ((s + 1) * t - s);
10294 },
10295
10296 easeOutBack: function(t) {
10297 var s = 1.70158;
10298 return (t = t - 1) * t * ((s + 1) * t + s) + 1;
10299 },
10300
10301 easeInOutBack: function(t) {
10302 var s = 1.70158;
10303 if ((t /= 0.5) < 1) {
10304 return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
10305 }
10306 return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
10307 },
10308
10309 easeInBounce: function(t) {
10310 return 1 - effects.easeOutBounce(1 - t);
10311 },
10312
10313 easeOutBounce: function(t) {
10314 if (t < (1 / 2.75)) {
10315 return 7.5625 * t * t;
10316 }
10317 if (t < (2 / 2.75)) {
10318 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
10319 }
10320 if (t < (2.5 / 2.75)) {
10321 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
10322 }
10323 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
10324 },
10325
10326 easeInOutBounce: function(t) {
10327 if (t < 0.5) {
10328 return effects.easeInBounce(t * 2) * 0.5;
10329 }
10330 return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
10331 }
10332 };
10333
10334 module.exports = {
10335 effects: effects
10336 };
10337
10338 // DEPRECATIONS
10339
10340 /**
10341 * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
10342 * @function Chart.helpers.easingEffects
10343 * @deprecated since version 2.7.0
10344 * @todo remove at version 3
10345 * @private
10346 */
10347 helpers.easingEffects = effects;
10348
10349 },{"42":42}],44:[function(require,module,exports){
10350 'use strict';
10351
10352 var helpers = require(42);
10353
10354 /**
10355 * @alias Chart.helpers.options
10356 * @namespace
10357 */
10358 module.exports = {
10359 /**
10360 * Converts the given line height `value` in pixels for a specific font `size`.
10361 * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
10362 * @param {Number} size - The font size (in pixels) used to resolve relative `value`.
10363 * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid).
10364 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
10365 * @since 2.7.0
10366 */
10367 toLineHeight: function(value, size) {
10368 var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
10369 if (!matches || matches[1] === 'normal') {
10370 return size * 1.2;
10371 }
10372
10373 value = +matches[2];
10374
10375 switch (matches[3]) {
10376 case 'px':
10377 return value;
10378 case '%':
10379 value /= 100;
10380 break;
10381 default:
10382 break;
10383 }
10384
10385 return size * value;
10386 },
10387
10388 /**
10389 * Converts the given value into a padding object with pre-computed width/height.
10390 * @param {Number|Object} value - If a number, set the value to all TRBL component,
10391 * else, if and object, use defined properties and sets undefined ones to 0.
10392 * @returns {Object} The padding values (top, right, bottom, left, width, height)
10393 * @since 2.7.0
10394 */
10395 toPadding: function(value) {
10396 var t, r, b, l;
10397
10398 if (helpers.isObject(value)) {
10399 t = +value.top || 0;
10400 r = +value.right || 0;
10401 b = +value.bottom || 0;
10402 l = +value.left || 0;
10403 } else {
10404 t = r = b = l = +value || 0;
10405 }
10406
10407 return {
10408 top: t,
10409 right: r,
10410 bottom: b,
10411 left: l,
10412 height: t + b,
10413 width: l + r
10414 };
10415 },
10416
10417 /**
10418 * Evaluates the given `inputs` sequentially and returns the first defined value.
10419 * @param {Array[]} inputs - An array of values, falling back to the last value.
10420 * @param {Object} [context] - If defined and the current value is a function, the value
10421 * is called with `context` as first argument and the result becomes the new input.
10422 * @param {Number} [index] - If defined and the current value is an array, the value
10423 * at `index` become the new input.
10424 * @since 2.7.0
10425 */
10426 resolve: function(inputs, context, index) {
10427 var i, ilen, value;
10428
10429 for (i = 0, ilen = inputs.length; i < ilen; ++i) {
10430 value = inputs[i];
10431 if (value === undefined) {
10432 continue;
10433 }
10434 if (context !== undefined && typeof value === 'function') {
10435 value = value(context);
10436 }
10437 if (index !== undefined && helpers.isArray(value)) {
10438 value = value[index];
10439 }
10440 if (value !== undefined) {
10441 return value;
10442 }
10443 }
10444 }
10445 };
10446
10447 },{"42":42}],45:[function(require,module,exports){
10448 'use strict';
10449
10450 module.exports = require(42);
10451 module.exports.easing = require(43);
10452 module.exports.canvas = require(41);
10453 module.exports.options = require(44);
10454
10455 },{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){
10456 /**
10457 * Platform fallback implementation (minimal).
10458 * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
10459 */
10460
10461 module.exports = {
10462 acquireContext: function(item) {
10463 if (item && item.canvas) {
10464 // Support for any object associated to a canvas (including a context2d)
10465 item = item.canvas;
10466 }
10467
10468 return item && item.getContext('2d') || null;
10469 }
10470 };
10471
10472 },{}],47:[function(require,module,exports){
10473 /**
10474 * Chart.Platform implementation for targeting a web browser
10475 */
10476
10477 'use strict';
10478
10479 var helpers = require(45);
10480
10481 var EXPANDO_KEY = '$chartjs';
10482 var CSS_PREFIX = 'chartjs-';
10483 var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
10484 var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
10485 var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
10486
10487 /**
10488 * DOM event types -> Chart.js event types.
10489 * Note: only events with different types are mapped.
10490 * @see https://developer.mozilla.org/en-US/docs/Web/Events
10491 */
10492 var EVENT_TYPES = {
10493 touchstart: 'mousedown',
10494 touchmove: 'mousemove',
10495 touchend: 'mouseup',
10496 pointerenter: 'mouseenter',
10497 pointerdown: 'mousedown',
10498 pointermove: 'mousemove',
10499 pointerup: 'mouseup',
10500 pointerleave: 'mouseout',
10501 pointerout: 'mouseout'
10502 };
10503
10504 /**
10505 * The "used" size is the final value of a dimension property after all calculations have
10506 * been performed. This method uses the computed style of `element` but returns undefined
10507 * if the computed style is not expressed in pixels. That can happen in some cases where
10508 * `element` has a size relative to its parent and this last one is not yet displayed,
10509 * for example because of `display: none` on a parent node.
10510 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
10511 * @returns {Number} Size in pixels or undefined if unknown.
10512 */
10513 function readUsedSize(element, property) {
10514 var value = helpers.getStyle(element, property);
10515 var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
10516 return matches ? Number(matches[1]) : undefined;
10517 }
10518
10519 /**
10520 * Initializes the canvas style and render size without modifying the canvas display size,
10521 * since responsiveness is handled by the controller.resize() method. The config is used
10522 * to determine the aspect ratio to apply in case no explicit height has been specified.
10523 */
10524 function initCanvas(canvas, config) {
10525 var style = canvas.style;
10526
10527 // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
10528 // returns null or '' if no explicit value has been set to the canvas attribute.
10529 var renderHeight = canvas.getAttribute('height');
10530 var renderWidth = canvas.getAttribute('width');
10531
10532 // Chart.js modifies some canvas values that we want to restore on destroy
10533 canvas[EXPANDO_KEY] = {
10534 initial: {
10535 height: renderHeight,
10536 width: renderWidth,
10537 style: {
10538 display: style.display,
10539 height: style.height,
10540 width: style.width
10541 }
10542 }
10543 };
10544
10545 // Force canvas to display as block to avoid extra space caused by inline
10546 // elements, which would interfere with the responsive resize process.
10547 // https://github.com/chartjs/Chart.js/issues/2538
10548 style.display = style.display || 'block';
10549
10550 if (renderWidth === null || renderWidth === '') {
10551 var displayWidth = readUsedSize(canvas, 'width');
10552 if (displayWidth !== undefined) {
10553 canvas.width = displayWidth;
10554 }
10555 }
10556
10557 if (renderHeight === null || renderHeight === '') {
10558 if (canvas.style.height === '') {
10559 // If no explicit render height and style height, let's apply the aspect ratio,
10560 // which one can be specified by the user but also by charts as default option
10561 // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
10562 canvas.height = canvas.width / (config.options.aspectRatio || 2);
10563 } else {
10564 var displayHeight = readUsedSize(canvas, 'height');
10565 if (displayWidth !== undefined) {
10566 canvas.height = displayHeight;
10567 }
10568 }
10569 }
10570
10571 return canvas;
10572 }
10573
10574 /**
10575 * Detects support for options object argument in addEventListener.
10576 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
10577 * @private
10578 */
10579 var supportsEventListenerOptions = (function() {
10580 var supports = false;
10581 try {
10582 var options = Object.defineProperty({}, 'passive', {
10583 get: function() {
10584 supports = true;
10585 }
10586 });
10587 window.addEventListener('e', null, options);
10588 } catch (e) {
10589 // continue regardless of error
10590 }
10591 return supports;
10592 }());
10593
10594 // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
10595 // https://github.com/chartjs/Chart.js/issues/4287
10596 var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
10597
10598 function addEventListener(node, type, listener) {
10599 node.addEventListener(type, listener, eventListenerOptions);
10600 }
10601
10602 function removeEventListener(node, type, listener) {
10603 node.removeEventListener(type, listener, eventListenerOptions);
10604 }
10605
10606 function createEvent(type, chart, x, y, nativeEvent) {
10607 return {
10608 type: type,
10609 chart: chart,
10610 native: nativeEvent || null,
10611 x: x !== undefined ? x : null,
10612 y: y !== undefined ? y : null,
10613 };
10614 }
10615
10616 function fromNativeEvent(event, chart) {
10617 var type = EVENT_TYPES[event.type] || event.type;
10618 var pos = helpers.getRelativePosition(event, chart);
10619 return createEvent(type, chart, pos.x, pos.y, event);
10620 }
10621
10622 function throttled(fn, thisArg) {
10623 var ticking = false;
10624 var args = [];
10625
10626 return function() {
10627 args = Array.prototype.slice.call(arguments);
10628 thisArg = thisArg || this;
10629
10630 if (!ticking) {
10631 ticking = true;
10632 helpers.requestAnimFrame.call(window, function() {
10633 ticking = false;
10634 fn.apply(thisArg, args);
10635 });
10636 }
10637 };
10638 }
10639
10640 // Implementation based on https://github.com/marcj/css-element-queries
10641 function createResizer(handler) {
10642 var resizer = document.createElement('div');
10643 var cls = CSS_PREFIX + 'size-monitor';
10644 var maxSize = 1000000;
10645 var style =
10646 'position:absolute;' +
10647 'left:0;' +
10648 'top:0;' +
10649 'right:0;' +
10650 'bottom:0;' +
10651 'overflow:hidden;' +
10652 'pointer-events:none;' +
10653 'visibility:hidden;' +
10654 'z-index:-1;';
10655
10656 resizer.style.cssText = style;
10657 resizer.className = cls;
10658 resizer.innerHTML =
10659 '<div class="' + cls + '-expand" style="' + style + '">' +
10660 '<div style="' +
10661 'position:absolute;' +
10662 'width:' + maxSize + 'px;' +
10663 'height:' + maxSize + 'px;' +
10664 'left:0;' +
10665 'top:0">' +
10666 '</div>' +
10667 '</div>' +
10668 '<div class="' + cls + '-shrink" style="' + style + '">' +
10669 '<div style="' +
10670 'position:absolute;' +
10671 'width:200%;' +
10672 'height:200%;' +
10673 'left:0; ' +
10674 'top:0">' +
10675 '</div>' +
10676 '</div>';
10677
10678 var expand = resizer.childNodes[0];
10679 var shrink = resizer.childNodes[1];
10680
10681 resizer._reset = function() {
10682 expand.scrollLeft = maxSize;
10683 expand.scrollTop = maxSize;
10684 shrink.scrollLeft = maxSize;
10685 shrink.scrollTop = maxSize;
10686 };
10687 var onScroll = function() {
10688 resizer._reset();
10689 handler();
10690 };
10691
10692 addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
10693 addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
10694
10695 return resizer;
10696 }
10697
10698 // https://davidwalsh.name/detect-node-insertion
10699 function watchForRender(node, handler) {
10700 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10701 var proxy = expando.renderProxy = function(e) {
10702 if (e.animationName === CSS_RENDER_ANIMATION) {
10703 handler();
10704 }
10705 };
10706
10707 helpers.each(ANIMATION_START_EVENTS, function(type) {
10708 addEventListener(node, type, proxy);
10709 });
10710
10711 // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
10712 // is removed then added back immediately (same animation frame?). Accessing the
10713 // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
10714 // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
10715 // https://github.com/chartjs/Chart.js/issues/4737
10716 expando.reflow = !!node.offsetParent;
10717
10718 node.classList.add(CSS_RENDER_MONITOR);
10719 }
10720
10721 function unwatchForRender(node) {
10722 var expando = node[EXPANDO_KEY] || {};
10723 var proxy = expando.renderProxy;
10724
10725 if (proxy) {
10726 helpers.each(ANIMATION_START_EVENTS, function(type) {
10727 removeEventListener(node, type, proxy);
10728 });
10729
10730 delete expando.renderProxy;
10731 }
10732
10733 node.classList.remove(CSS_RENDER_MONITOR);
10734 }
10735
10736 function addResizeListener(node, listener, chart) {
10737 var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10738
10739 // Let's keep track of this added resizer and thus avoid DOM query when removing it.
10740 var resizer = expando.resizer = createResizer(throttled(function() {
10741 if (expando.resizer) {
10742 return listener(createEvent('resize', chart));
10743 }
10744 }));
10745
10746 // The resizer needs to be attached to the node parent, so we first need to be
10747 // sure that `node` is attached to the DOM before injecting the resizer element.
10748 watchForRender(node, function() {
10749 if (expando.resizer) {
10750 var container = node.parentNode;
10751 if (container && container !== resizer.parentNode) {
10752 container.insertBefore(resizer, container.firstChild);
10753 }
10754
10755 // The container size might have changed, let's reset the resizer state.
10756 resizer._reset();
10757 }
10758 });
10759 }
10760
10761 function removeResizeListener(node) {
10762 var expando = node[EXPANDO_KEY] || {};
10763 var resizer = expando.resizer;
10764
10765 delete expando.resizer;
10766 unwatchForRender(node);
10767
10768 if (resizer && resizer.parentNode) {
10769 resizer.parentNode.removeChild(resizer);
10770 }
10771 }
10772
10773 function injectCSS(platform, css) {
10774 // http://stackoverflow.com/q/3922139
10775 var style = platform._style || document.createElement('style');
10776 if (!platform._style) {
10777 platform._style = style;
10778 css = '/* Chart.js */\n' + css;
10779 style.setAttribute('type', 'text/css');
10780 document.getElementsByTagName('head')[0].appendChild(style);
10781 }
10782
10783 style.appendChild(document.createTextNode(css));
10784 }
10785
10786 module.exports = {
10787 /**
10788 * This property holds whether this platform is enabled for the current environment.
10789 * Currently used by platform.js to select the proper implementation.
10790 * @private
10791 */
10792 _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
10793
10794 initialize: function() {
10795 var keyframes = 'from{opacity:0.99}to{opacity:1}';
10796
10797 injectCSS(this,
10798 // DOM rendering detection
10799 // https://davidwalsh.name/detect-node-insertion
10800 '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10801 '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10802 '.' + CSS_RENDER_MONITOR + '{' +
10803 '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10804 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10805 '}'
10806 );
10807 },
10808
10809 acquireContext: function(item, config) {
10810 if (typeof item === 'string') {
10811 item = document.getElementById(item);
10812 } else if (item.length) {
10813 // Support for array based queries (such as jQuery)
10814 item = item[0];
10815 }
10816
10817 if (item && item.canvas) {
10818 // Support for any object associated to a canvas (including a context2d)
10819 item = item.canvas;
10820 }
10821
10822 // To prevent canvas fingerprinting, some add-ons undefine the getContext
10823 // method, for example: https://github.com/kkapsner/CanvasBlocker
10824 // https://github.com/chartjs/Chart.js/issues/2807
10825 var context = item && item.getContext && item.getContext('2d');
10826
10827 // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
10828 // inside an iframe or when running in a protected environment. We could guess the
10829 // types from their toString() value but let's keep things flexible and assume it's
10830 // a sufficient condition if the item has a context2D which has item as `canvas`.
10831 // https://github.com/chartjs/Chart.js/issues/3887
10832 // https://github.com/chartjs/Chart.js/issues/4102
10833 // https://github.com/chartjs/Chart.js/issues/4152
10834 if (context && context.canvas === item) {
10835 initCanvas(item, config);
10836 return context;
10837 }
10838
10839 return null;
10840 },
10841
10842 releaseContext: function(context) {
10843 var canvas = context.canvas;
10844 if (!canvas[EXPANDO_KEY]) {
10845 return;
10846 }
10847
10848 var initial = canvas[EXPANDO_KEY].initial;
10849 ['height', 'width'].forEach(function(prop) {
10850 var value = initial[prop];
10851 if (helpers.isNullOrUndef(value)) {
10852 canvas.removeAttribute(prop);
10853 } else {
10854 canvas.setAttribute(prop, value);
10855 }
10856 });
10857
10858 helpers.each(initial.style || {}, function(value, key) {
10859 canvas.style[key] = value;
10860 });
10861
10862 // The canvas render size might have been changed (and thus the state stack discarded),
10863 // we can't use save() and restore() to restore the initial state. So make sure that at
10864 // least the canvas context is reset to the default state by setting the canvas width.
10865 // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
10866 canvas.width = canvas.width;
10867
10868 delete canvas[EXPANDO_KEY];
10869 },
10870
10871 addEventListener: function(chart, type, listener) {
10872 var canvas = chart.canvas;
10873 if (type === 'resize') {
10874 // Note: the resize event is not supported on all browsers.
10875 addResizeListener(canvas, listener, chart);
10876 return;
10877 }
10878
10879 var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
10880 var proxies = expando.proxies || (expando.proxies = {});
10881 var proxy = proxies[chart.id + '_' + type] = function(event) {
10882 listener(fromNativeEvent(event, chart));
10883 };
10884
10885 addEventListener(canvas, type, proxy);
10886 },
10887
10888 removeEventListener: function(chart, type, listener) {
10889 var canvas = chart.canvas;
10890 if (type === 'resize') {
10891 // Note: the resize event is not supported on all browsers.
10892 removeResizeListener(canvas, listener);
10893 return;
10894 }
10895
10896 var expando = listener[EXPANDO_KEY] || {};
10897 var proxies = expando.proxies || {};
10898 var proxy = proxies[chart.id + '_' + type];
10899 if (!proxy) {
10900 return;
10901 }
10902
10903 removeEventListener(canvas, type, proxy);
10904 }
10905 };
10906
10907 // DEPRECATIONS
10908
10909 /**
10910 * Provided for backward compatibility, use EventTarget.addEventListener instead.
10911 * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10912 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
10913 * @function Chart.helpers.addEvent
10914 * @deprecated since version 2.7.0
10915 * @todo remove at version 3
10916 * @private
10917 */
10918 helpers.addEvent = addEventListener;
10919
10920 /**
10921 * Provided for backward compatibility, use EventTarget.removeEventListener instead.
10922 * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10923 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
10924 * @function Chart.helpers.removeEvent
10925 * @deprecated since version 2.7.0
10926 * @todo remove at version 3
10927 * @private
10928 */
10929 helpers.removeEvent = removeEventListener;
10930
10931 },{"45":45}],48:[function(require,module,exports){
10932 'use strict';
10933
10934 var helpers = require(45);
10935 var basic = require(46);
10936 var dom = require(47);
10937
10938 // @TODO Make possible to select another platform at build time.
10939 var implementation = dom._enabled ? dom : basic;
10940
10941 /**
10942 * @namespace Chart.platform
10943 * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
10944 * @since 2.4.0
10945 */
10946 module.exports = helpers.extend({
10947 /**
10948 * @since 2.7.0
10949 */
10950 initialize: function() {},
10951
10952 /**
10953 * Called at chart construction time, returns a context2d instance implementing
10954 * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
10955 * @param {*} item - The native item from which to acquire context (platform specific)
10956 * @param {Object} options - The chart options
10957 * @returns {CanvasRenderingContext2D} context2d instance
10958 */
10959 acquireContext: function() {},
10960
10961 /**
10962 * Called at chart destruction time, releases any resources associated to the context
10963 * previously returned by the acquireContext() method.
10964 * @param {CanvasRenderingContext2D} context - The context2d instance
10965 * @returns {Boolean} true if the method succeeded, else false
10966 */
10967 releaseContext: function() {},
10968
10969 /**
10970 * Registers the specified listener on the given chart.
10971 * @param {Chart} chart - Chart from which to listen for event
10972 * @param {String} type - The ({@link IEvent}) type to listen for
10973 * @param {Function} listener - Receives a notification (an object that implements
10974 * the {@link IEvent} interface) when an event of the specified type occurs.
10975 */
10976 addEventListener: function() {},
10977
10978 /**
10979 * Removes the specified listener previously registered with addEventListener.
10980 * @param {Chart} chart -Chart from which to remove the listener
10981 * @param {String} type - The ({@link IEvent}) type to remove
10982 * @param {Function} listener - The listener function to remove from the event target.
10983 */
10984 removeEventListener: function() {}
10985
10986 }, implementation);
10987
10988 /**
10989 * @interface IPlatform
10990 * Allows abstracting platform dependencies away from the chart
10991 * @borrows Chart.platform.acquireContext as acquireContext
10992 * @borrows Chart.platform.releaseContext as releaseContext
10993 * @borrows Chart.platform.addEventListener as addEventListener
10994 * @borrows Chart.platform.removeEventListener as removeEventListener
10995 */
10996
10997 /**
10998 * @interface IEvent
10999 * @prop {String} type - The event type name, possible values are:
11000 * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',
11001 * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'
11002 * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')
11003 * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)
11004 * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)
11005 */
11006
11007 },{"45":45,"46":46,"47":47}],49:[function(require,module,exports){
11008 /**
11009 * Plugin based on discussion from the following Chart.js issues:
11010 * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569
11011 * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897
11012 */
11013
11014 'use strict';
11015
11016 var defaults = require(25);
11017 var elements = require(40);
11018 var helpers = require(45);
11019
11020 defaults._set('global', {
11021 plugins: {
11022 filler: {
11023 propagate: true
11024 }
11025 }
11026 });
11027
11028 module.exports = function() {
11029
11030 var mappers = {
11031 dataset: function(source) {
11032 var index = source.fill;
11033 var chart = source.chart;
11034 var meta = chart.getDatasetMeta(index);
11035 var visible = meta && chart.isDatasetVisible(index);
11036 var points = (visible && meta.dataset._children) || [];
11037 var length = points.length || 0;
11038
11039 return !length ? null : function(point, i) {
11040 return (i < length && points[i]._view) || null;
11041 };
11042 },
11043
11044 boundary: function(source) {
11045 var boundary = source.boundary;
11046 var x = boundary ? boundary.x : null;
11047 var y = boundary ? boundary.y : null;
11048
11049 return function(point) {
11050 return {
11051 x: x === null ? point.x : x,
11052 y: y === null ? point.y : y,
11053 };
11054 };
11055 }
11056 };
11057
11058 // @todo if (fill[0] === '#')
11059 function decodeFill(el, index, count) {
11060 var model = el._model || {};
11061 var fill = model.fill;
11062 var target;
11063
11064 if (fill === undefined) {
11065 fill = !!model.backgroundColor;
11066 }
11067
11068 if (fill === false || fill === null) {
11069 return false;
11070 }
11071
11072 if (fill === true) {
11073 return 'origin';
11074 }
11075
11076 target = parseFloat(fill, 10);
11077 if (isFinite(target) && Math.floor(target) === target) {
11078 if (fill[0] === '-' || fill[0] === '+') {
11079 target = index + target;
11080 }
11081
11082 if (target === index || target < 0 || target >= count) {
11083 return false;
11084 }
11085
11086 return target;
11087 }
11088
11089 switch (fill) {
11090 // compatibility
11091 case 'bottom':
11092 return 'start';
11093 case 'top':
11094 return 'end';
11095 case 'zero':
11096 return 'origin';
11097 // supported boundaries
11098 case 'origin':
11099 case 'start':
11100 case 'end':
11101 return fill;
11102 // invalid fill values
11103 default:
11104 return false;
11105 }
11106 }
11107
11108 function computeBoundary(source) {
11109 var model = source.el._model || {};
11110 var scale = source.el._scale || {};
11111 var fill = source.fill;
11112 var target = null;
11113 var horizontal;
11114
11115 if (isFinite(fill)) {
11116 return null;
11117 }
11118
11119 // Backward compatibility: until v3, we still need to support boundary values set on
11120 // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
11121 // controllers might still use it (e.g. the Smith chart).
11122
11123 if (fill === 'start') {
11124 target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
11125 } else if (fill === 'end') {
11126 target = model.scaleTop === undefined ? scale.top : model.scaleTop;
11127 } else if (model.scaleZero !== undefined) {
11128 target = model.scaleZero;
11129 } else if (scale.getBasePosition) {
11130 target = scale.getBasePosition();
11131 } else if (scale.getBasePixel) {
11132 target = scale.getBasePixel();
11133 }
11134
11135 if (target !== undefined && target !== null) {
11136 if (target.x !== undefined && target.y !== undefined) {
11137 return target;
11138 }
11139
11140 if (typeof target === 'number' && isFinite(target)) {
11141 horizontal = scale.isHorizontal();
11142 return {
11143 x: horizontal ? target : null,
11144 y: horizontal ? null : target
11145 };
11146 }
11147 }
11148
11149 return null;
11150 }
11151
11152 function resolveTarget(sources, index, propagate) {
11153 var source = sources[index];
11154 var fill = source.fill;
11155 var visited = [index];
11156 var target;
11157
11158 if (!propagate) {
11159 return fill;
11160 }
11161
11162 while (fill !== false && visited.indexOf(fill) === -1) {
11163 if (!isFinite(fill)) {
11164 return fill;
11165 }
11166
11167 target = sources[fill];
11168 if (!target) {
11169 return false;
11170 }
11171
11172 if (target.visible) {
11173 return fill;
11174 }
11175
11176 visited.push(fill);
11177 fill = target.fill;
11178 }
11179
11180 return false;
11181 }
11182
11183 function createMapper(source) {
11184 var fill = source.fill;
11185 var type = 'dataset';
11186
11187 if (fill === false) {
11188 return null;
11189 }
11190
11191 if (!isFinite(fill)) {
11192 type = 'boundary';
11193 }
11194
11195 return mappers[type](source);
11196 }
11197
11198 function isDrawable(point) {
11199 return point && !point.skip;
11200 }
11201
11202 function drawArea(ctx, curve0, curve1, len0, len1) {
11203 var i;
11204
11205 if (!len0 || !len1) {
11206 return;
11207 }
11208
11209 // building first area curve (normal)
11210 ctx.moveTo(curve0[0].x, curve0[0].y);
11211 for (i = 1; i < len0; ++i) {
11212 helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
11213 }
11214
11215 // joining the two area curves
11216 ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
11217
11218 // building opposite area curve (reverse)
11219 for (i = len1 - 1; i > 0; --i) {
11220 helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
11221 }
11222 }
11223
11224 function doFill(ctx, points, mapper, view, color, loop) {
11225 var count = points.length;
11226 var span = view.spanGaps;
11227 var curve0 = [];
11228 var curve1 = [];
11229 var len0 = 0;
11230 var len1 = 0;
11231 var i, ilen, index, p0, p1, d0, d1;
11232
11233 ctx.beginPath();
11234
11235 for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
11236 index = i % count;
11237 p0 = points[index]._view;
11238 p1 = mapper(p0, index, view);
11239 d0 = isDrawable(p0);
11240 d1 = isDrawable(p1);
11241
11242 if (d0 && d1) {
11243 len0 = curve0.push(p0);
11244 len1 = curve1.push(p1);
11245 } else if (len0 && len1) {
11246 if (!span) {
11247 drawArea(ctx, curve0, curve1, len0, len1);
11248 len0 = len1 = 0;
11249 curve0 = [];
11250 curve1 = [];
11251 } else {
11252 if (d0) {
11253 curve0.push(p0);
11254 }
11255 if (d1) {
11256 curve1.push(p1);
11257 }
11258 }
11259 }
11260 }
11261
11262 drawArea(ctx, curve0, curve1, len0, len1);
11263
11264 ctx.closePath();
11265 ctx.fillStyle = color;
11266 ctx.fill();
11267 }
11268
11269 return {
11270 id: 'filler',
11271
11272 afterDatasetsUpdate: function(chart, options) {
11273 var count = (chart.data.datasets || []).length;
11274 var propagate = options.propagate;
11275 var sources = [];
11276 var meta, i, el, source;
11277
11278 for (i = 0; i < count; ++i) {
11279 meta = chart.getDatasetMeta(i);
11280 el = meta.dataset;
11281 source = null;
11282
11283 if (el && el._model && el instanceof elements.Line) {
11284 source = {
11285 visible: chart.isDatasetVisible(i),
11286 fill: decodeFill(el, i, count),
11287 chart: chart,
11288 el: el
11289 };
11290 }
11291
11292 meta.$filler = source;
11293 sources.push(source);
11294 }
11295
11296 for (i = 0; i < count; ++i) {
11297 source = sources[i];
11298 if (!source) {
11299 continue;
11300 }
11301
11302 source.fill = resolveTarget(sources, i, propagate);
11303 source.boundary = computeBoundary(source);
11304 source.mapper = createMapper(source);
11305 }
11306 },
11307
11308 beforeDatasetDraw: function(chart, args) {
11309 var meta = args.meta.$filler;
11310 if (!meta) {
11311 return;
11312 }
11313
11314 var ctx = chart.ctx;
11315 var el = meta.el;
11316 var view = el._view;
11317 var points = el._children || [];
11318 var mapper = meta.mapper;
11319 var color = view.backgroundColor || defaults.global.defaultColor;
11320
11321 if (mapper && color && points.length) {
11322 helpers.canvas.clipArea(ctx, chart.chartArea);
11323 doFill(ctx, points, mapper, view, color, el._loop);
11324 helpers.canvas.unclipArea(ctx);
11325 }
11326 }
11327 };
11328 };
11329
11330 },{"25":25,"40":40,"45":45}],50:[function(require,module,exports){
11331 'use strict';
11332
11333 var defaults = require(25);
11334 var Element = require(26);
11335 var helpers = require(45);
11336
11337 defaults._set('global', {
11338 legend: {
11339 display: true,
11340 position: 'top',
11341 fullWidth: true,
11342 reverse: false,
11343 weight: 1000,
11344
11345 // a callback that will handle
11346 onClick: function(e, legendItem) {
11347 var index = legendItem.datasetIndex;
11348 var ci = this.chart;
11349 var meta = ci.getDatasetMeta(index);
11350
11351 // See controller.isDatasetVisible comment
11352 meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
11353
11354 // We hid a dataset ... rerender the chart
11355 ci.update();
11356 },
11357
11358 onHover: null,
11359
11360 labels: {
11361 boxWidth: 40,
11362 padding: 10,
11363 // Generates labels shown in the legend
11364 // Valid properties to return:
11365 // text : text to display
11366 // fillStyle : fill of coloured box
11367 // strokeStyle: stroke of coloured box
11368 // hidden : if this legend item refers to a hidden item
11369 // lineCap : cap style for line
11370 // lineDash
11371 // lineDashOffset :
11372 // lineJoin :
11373 // lineWidth :
11374 generateLabels: function(chart) {
11375 var data = chart.data;
11376 return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
11377 return {
11378 text: dataset.label,
11379 fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
11380 hidden: !chart.isDatasetVisible(i),
11381 lineCap: dataset.borderCapStyle,
11382 lineDash: dataset.borderDash,
11383 lineDashOffset: dataset.borderDashOffset,
11384 lineJoin: dataset.borderJoinStyle,
11385 lineWidth: dataset.borderWidth,
11386 strokeStyle: dataset.borderColor,
11387 pointStyle: dataset.pointStyle,
11388
11389 // Below is extra data used for toggling the datasets
11390 datasetIndex: i
11391 };
11392 }, this) : [];
11393 }
11394 }
11395 },
11396
11397 legendCallback: function(chart) {
11398 var text = [];
11399 text.push('<ul class="' + chart.id + '-legend">');
11400 for (var i = 0; i < chart.data.datasets.length; i++) {
11401 text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
11402 if (chart.data.datasets[i].label) {
11403 text.push(chart.data.datasets[i].label);
11404 }
11405 text.push('</li>');
11406 }
11407 text.push('</ul>');
11408 return text.join('');
11409 }
11410 });
11411
11412 module.exports = function(Chart) {
11413
11414 var layout = Chart.layoutService;
11415 var noop = helpers.noop;
11416
11417 /**
11418 * Helper function to get the box width based on the usePointStyle option
11419 * @param labelopts {Object} the label options on the legend
11420 * @param fontSize {Number} the label font size
11421 * @return {Number} width of the color box area
11422 */
11423 function getBoxWidth(labelOpts, fontSize) {
11424 return labelOpts.usePointStyle ?
11425 fontSize * Math.SQRT2 :
11426 labelOpts.boxWidth;
11427 }
11428
11429 Chart.Legend = Element.extend({
11430
11431 initialize: function(config) {
11432 helpers.extend(this, config);
11433
11434 // Contains hit boxes for each dataset (in dataset order)
11435 this.legendHitBoxes = [];
11436
11437 // Are we in doughnut mode which has a different data type
11438 this.doughnutMode = false;
11439 },
11440
11441 // These methods are ordered by lifecycle. Utilities then follow.
11442 // Any function defined here is inherited by all legend types.
11443 // Any function can be extended by the legend type
11444
11445 beforeUpdate: noop,
11446 update: function(maxWidth, maxHeight, margins) {
11447 var me = this;
11448
11449 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11450 me.beforeUpdate();
11451
11452 // Absorb the master measurements
11453 me.maxWidth = maxWidth;
11454 me.maxHeight = maxHeight;
11455 me.margins = margins;
11456
11457 // Dimensions
11458 me.beforeSetDimensions();
11459 me.setDimensions();
11460 me.afterSetDimensions();
11461 // Labels
11462 me.beforeBuildLabels();
11463 me.buildLabels();
11464 me.afterBuildLabels();
11465
11466 // Fit
11467 me.beforeFit();
11468 me.fit();
11469 me.afterFit();
11470 //
11471 me.afterUpdate();
11472
11473 return me.minSize;
11474 },
11475 afterUpdate: noop,
11476
11477 //
11478
11479 beforeSetDimensions: noop,
11480 setDimensions: function() {
11481 var me = this;
11482 // Set the unconstrained dimension before label rotation
11483 if (me.isHorizontal()) {
11484 // Reset position before calculating rotation
11485 me.width = me.maxWidth;
11486 me.left = 0;
11487 me.right = me.width;
11488 } else {
11489 me.height = me.maxHeight;
11490
11491 // Reset position before calculating rotation
11492 me.top = 0;
11493 me.bottom = me.height;
11494 }
11495
11496 // Reset padding
11497 me.paddingLeft = 0;
11498 me.paddingTop = 0;
11499 me.paddingRight = 0;
11500 me.paddingBottom = 0;
11501
11502 // Reset minSize
11503 me.minSize = {
11504 width: 0,
11505 height: 0
11506 };
11507 },
11508 afterSetDimensions: noop,
11509
11510 //
11511
11512 beforeBuildLabels: noop,
11513 buildLabels: function() {
11514 var me = this;
11515 var labelOpts = me.options.labels || {};
11516 var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
11517
11518 if (labelOpts.filter) {
11519 legendItems = legendItems.filter(function(item) {
11520 return labelOpts.filter(item, me.chart.data);
11521 });
11522 }
11523
11524 if (me.options.reverse) {
11525 legendItems.reverse();
11526 }
11527
11528 me.legendItems = legendItems;
11529 },
11530 afterBuildLabels: noop,
11531
11532 //
11533
11534 beforeFit: noop,
11535 fit: function() {
11536 var me = this;
11537 var opts = me.options;
11538 var labelOpts = opts.labels;
11539 var display = opts.display;
11540
11541 var ctx = me.ctx;
11542
11543 var globalDefault = defaults.global;
11544 var valueOrDefault = helpers.valueOrDefault;
11545 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11546 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11547 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11548 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11549
11550 // Reset hit boxes
11551 var hitboxes = me.legendHitBoxes = [];
11552
11553 var minSize = me.minSize;
11554 var isHorizontal = me.isHorizontal();
11555
11556 if (isHorizontal) {
11557 minSize.width = me.maxWidth; // fill all the width
11558 minSize.height = display ? 10 : 0;
11559 } else {
11560 minSize.width = display ? 10 : 0;
11561 minSize.height = me.maxHeight; // fill all the height
11562 }
11563
11564 // Increase sizes here
11565 if (display) {
11566 ctx.font = labelFont;
11567
11568 if (isHorizontal) {
11569 // Labels
11570
11571 // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
11572 var lineWidths = me.lineWidths = [0];
11573 var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
11574
11575 ctx.textAlign = 'left';
11576 ctx.textBaseline = 'top';
11577
11578 helpers.each(me.legendItems, function(legendItem, i) {
11579 var boxWidth = getBoxWidth(labelOpts, fontSize);
11580 var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11581
11582 if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
11583 totalHeight += fontSize + (labelOpts.padding);
11584 lineWidths[lineWidths.length] = me.left;
11585 }
11586
11587 // Store the hitbox width and height here. Final position will be updated in `draw`
11588 hitboxes[i] = {
11589 left: 0,
11590 top: 0,
11591 width: width,
11592 height: fontSize
11593 };
11594
11595 lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
11596 });
11597
11598 minSize.height += totalHeight;
11599
11600 } else {
11601 var vPadding = labelOpts.padding;
11602 var columnWidths = me.columnWidths = [];
11603 var totalWidth = labelOpts.padding;
11604 var currentColWidth = 0;
11605 var currentColHeight = 0;
11606 var itemHeight = fontSize + vPadding;
11607
11608 helpers.each(me.legendItems, function(legendItem, i) {
11609 var boxWidth = getBoxWidth(labelOpts, fontSize);
11610 var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11611
11612 // If too tall, go to new column
11613 if (currentColHeight + itemHeight > minSize.height) {
11614 totalWidth += currentColWidth + labelOpts.padding;
11615 columnWidths.push(currentColWidth); // previous column width
11616
11617 currentColWidth = 0;
11618 currentColHeight = 0;
11619 }
11620
11621 // Get max width
11622 currentColWidth = Math.max(currentColWidth, itemWidth);
11623 currentColHeight += itemHeight;
11624
11625 // Store the hitbox width and height here. Final position will be updated in `draw`
11626 hitboxes[i] = {
11627 left: 0,
11628 top: 0,
11629 width: itemWidth,
11630 height: fontSize
11631 };
11632 });
11633
11634 totalWidth += currentColWidth;
11635 columnWidths.push(currentColWidth);
11636 minSize.width += totalWidth;
11637 }
11638 }
11639
11640 me.width = minSize.width;
11641 me.height = minSize.height;
11642 },
11643 afterFit: noop,
11644
11645 // Shared Methods
11646 isHorizontal: function() {
11647 return this.options.position === 'top' || this.options.position === 'bottom';
11648 },
11649
11650 // Actually draw the legend on the canvas
11651 draw: function() {
11652 var me = this;
11653 var opts = me.options;
11654 var labelOpts = opts.labels;
11655 var globalDefault = defaults.global;
11656 var lineDefault = globalDefault.elements.line;
11657 var legendWidth = me.width;
11658 var lineWidths = me.lineWidths;
11659
11660 if (opts.display) {
11661 var ctx = me.ctx;
11662 var valueOrDefault = helpers.valueOrDefault;
11663 var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
11664 var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11665 var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11666 var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11667 var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11668 var cursor;
11669
11670 // Canvas setup
11671 ctx.textAlign = 'left';
11672 ctx.textBaseline = 'middle';
11673 ctx.lineWidth = 0.5;
11674 ctx.strokeStyle = fontColor; // for strikethrough effect
11675 ctx.fillStyle = fontColor; // render in correct colour
11676 ctx.font = labelFont;
11677
11678 var boxWidth = getBoxWidth(labelOpts, fontSize);
11679 var hitboxes = me.legendHitBoxes;
11680
11681 // current position
11682 var drawLegendBox = function(x, y, legendItem) {
11683 if (isNaN(boxWidth) || boxWidth <= 0) {
11684 return;
11685 }
11686
11687 // Set the ctx for the box
11688 ctx.save();
11689
11690 ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
11691 ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
11692 ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
11693 ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
11694 ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
11695 ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
11696 var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
11697
11698 if (ctx.setLineDash) {
11699 // IE 9 and 10 do not support line dash
11700 ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
11701 }
11702
11703 if (opts.labels && opts.labels.usePointStyle) {
11704 // Recalculate x and y for drawPoint() because its expecting
11705 // x and y to be center of figure (instead of top left)
11706 var radius = fontSize * Math.SQRT2 / 2;
11707 var offSet = radius / Math.SQRT2;
11708 var centerX = x + offSet;
11709 var centerY = y + offSet;
11710
11711 // Draw pointStyle as legend symbol
11712 helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
11713 } else {
11714 // Draw box as legend symbol
11715 if (!isLineWidthZero) {
11716 ctx.strokeRect(x, y, boxWidth, fontSize);
11717 }
11718 ctx.fillRect(x, y, boxWidth, fontSize);
11719 }
11720
11721 ctx.restore();
11722 };
11723 var fillText = function(x, y, legendItem, textWidth) {
11724 var halfFontSize = fontSize / 2;
11725 var xLeft = boxWidth + halfFontSize + x;
11726 var yMiddle = y + halfFontSize;
11727
11728 ctx.fillText(legendItem.text, xLeft, yMiddle);
11729
11730 if (legendItem.hidden) {
11731 // Strikethrough the text if hidden
11732 ctx.beginPath();
11733 ctx.lineWidth = 2;
11734 ctx.moveTo(xLeft, yMiddle);
11735 ctx.lineTo(xLeft + textWidth, yMiddle);
11736 ctx.stroke();
11737 }
11738 };
11739
11740 // Horizontal
11741 var isHorizontal = me.isHorizontal();
11742 if (isHorizontal) {
11743 cursor = {
11744 x: me.left + ((legendWidth - lineWidths[0]) / 2),
11745 y: me.top + labelOpts.padding,
11746 line: 0
11747 };
11748 } else {
11749 cursor = {
11750 x: me.left + labelOpts.padding,
11751 y: me.top + labelOpts.padding,
11752 line: 0
11753 };
11754 }
11755
11756 var itemHeight = fontSize + labelOpts.padding;
11757 helpers.each(me.legendItems, function(legendItem, i) {
11758 var textWidth = ctx.measureText(legendItem.text).width;
11759 var width = boxWidth + (fontSize / 2) + textWidth;
11760 var x = cursor.x;
11761 var y = cursor.y;
11762
11763 if (isHorizontal) {
11764 if (x + width >= legendWidth) {
11765 y = cursor.y += itemHeight;
11766 cursor.line++;
11767 x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
11768 }
11769 } else if (y + itemHeight > me.bottom) {
11770 x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
11771 y = cursor.y = me.top + labelOpts.padding;
11772 cursor.line++;
11773 }
11774
11775 drawLegendBox(x, y, legendItem);
11776
11777 hitboxes[i].left = x;
11778 hitboxes[i].top = y;
11779
11780 // Fill the actual label
11781 fillText(x, y, legendItem, textWidth);
11782
11783 if (isHorizontal) {
11784 cursor.x += width + (labelOpts.padding);
11785 } else {
11786 cursor.y += itemHeight;
11787 }
11788
11789 });
11790 }
11791 },
11792
11793 /**
11794 * Handle an event
11795 * @private
11796 * @param {IEvent} event - The event to handle
11797 * @return {Boolean} true if a change occured
11798 */
11799 handleEvent: function(e) {
11800 var me = this;
11801 var opts = me.options;
11802 var type = e.type === 'mouseup' ? 'click' : e.type;
11803 var changed = false;
11804
11805 if (type === 'mousemove') {
11806 if (!opts.onHover) {
11807 return;
11808 }
11809 } else if (type === 'click') {
11810 if (!opts.onClick) {
11811 return;
11812 }
11813 } else {
11814 return;
11815 }
11816
11817 // Chart event already has relative position in it
11818 var x = e.x;
11819 var y = e.y;
11820
11821 if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
11822 // See if we are touching one of the dataset boxes
11823 var lh = me.legendHitBoxes;
11824 for (var i = 0; i < lh.length; ++i) {
11825 var hitBox = lh[i];
11826
11827 if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
11828 // Touching an element
11829 if (type === 'click') {
11830 // use e.native for backwards compatibility
11831 opts.onClick.call(me, e.native, me.legendItems[i]);
11832 changed = true;
11833 break;
11834 } else if (type === 'mousemove') {
11835 // use e.native for backwards compatibility
11836 opts.onHover.call(me, e.native, me.legendItems[i]);
11837 changed = true;
11838 break;
11839 }
11840 }
11841 }
11842 }
11843
11844 return changed;
11845 }
11846 });
11847
11848 function createNewLegendAndAttach(chart, legendOpts) {
11849 var legend = new Chart.Legend({
11850 ctx: chart.ctx,
11851 options: legendOpts,
11852 chart: chart
11853 });
11854
11855 layout.configure(chart, legend, legendOpts);
11856 layout.addBox(chart, legend);
11857 chart.legend = legend;
11858 }
11859
11860 return {
11861 id: 'legend',
11862
11863 beforeInit: function(chart) {
11864 var legendOpts = chart.options.legend;
11865
11866 if (legendOpts) {
11867 createNewLegendAndAttach(chart, legendOpts);
11868 }
11869 },
11870
11871 beforeUpdate: function(chart) {
11872 var legendOpts = chart.options.legend;
11873 var legend = chart.legend;
11874
11875 if (legendOpts) {
11876 helpers.mergeIf(legendOpts, defaults.global.legend);
11877
11878 if (legend) {
11879 layout.configure(chart, legend, legendOpts);
11880 legend.options = legendOpts;
11881 } else {
11882 createNewLegendAndAttach(chart, legendOpts);
11883 }
11884 } else if (legend) {
11885 layout.removeBox(chart, legend);
11886 delete chart.legend;
11887 }
11888 },
11889
11890 afterEvent: function(chart, e) {
11891 var legend = chart.legend;
11892 if (legend) {
11893 legend.handleEvent(e);
11894 }
11895 }
11896 };
11897 };
11898
11899 },{"25":25,"26":26,"45":45}],51:[function(require,module,exports){
11900 'use strict';
11901
11902 var defaults = require(25);
11903 var Element = require(26);
11904 var helpers = require(45);
11905
11906 defaults._set('global', {
11907 title: {
11908 display: false,
11909 fontStyle: 'bold',
11910 fullWidth: true,
11911 lineHeight: 1.2,
11912 padding: 10,
11913 position: 'top',
11914 text: '',
11915 weight: 2000 // by default greater than legend (1000) to be above
11916 }
11917 });
11918
11919 module.exports = function(Chart) {
11920
11921 var layout = Chart.layoutService;
11922 var noop = helpers.noop;
11923
11924 Chart.Title = Element.extend({
11925 initialize: function(config) {
11926 var me = this;
11927 helpers.extend(me, config);
11928
11929 // Contains hit boxes for each dataset (in dataset order)
11930 me.legendHitBoxes = [];
11931 },
11932
11933 // These methods are ordered by lifecycle. Utilities then follow.
11934
11935 beforeUpdate: noop,
11936 update: function(maxWidth, maxHeight, margins) {
11937 var me = this;
11938
11939 // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11940 me.beforeUpdate();
11941
11942 // Absorb the master measurements
11943 me.maxWidth = maxWidth;
11944 me.maxHeight = maxHeight;
11945 me.margins = margins;
11946
11947 // Dimensions
11948 me.beforeSetDimensions();
11949 me.setDimensions();
11950 me.afterSetDimensions();
11951 // Labels
11952 me.beforeBuildLabels();
11953 me.buildLabels();
11954 me.afterBuildLabels();
11955
11956 // Fit
11957 me.beforeFit();
11958 me.fit();
11959 me.afterFit();
11960 //
11961 me.afterUpdate();
11962
11963 return me.minSize;
11964
11965 },
11966 afterUpdate: noop,
11967
11968 //
11969
11970 beforeSetDimensions: noop,
11971 setDimensions: function() {
11972 var me = this;
11973 // Set the unconstrained dimension before label rotation
11974 if (me.isHorizontal()) {
11975 // Reset position before calculating rotation
11976 me.width = me.maxWidth;
11977 me.left = 0;
11978 me.right = me.width;
11979 } else {
11980 me.height = me.maxHeight;
11981
11982 // Reset position before calculating rotation
11983 me.top = 0;
11984 me.bottom = me.height;
11985 }
11986
11987 // Reset padding
11988 me.paddingLeft = 0;
11989 me.paddingTop = 0;
11990 me.paddingRight = 0;
11991 me.paddingBottom = 0;
11992
11993 // Reset minSize
11994 me.minSize = {
11995 width: 0,
11996 height: 0
11997 };
11998 },
11999 afterSetDimensions: noop,
12000
12001 //
12002
12003 beforeBuildLabels: noop,
12004 buildLabels: noop,
12005 afterBuildLabels: noop,
12006
12007 //
12008
12009 beforeFit: noop,
12010 fit: function() {
12011 var me = this;
12012 var valueOrDefault = helpers.valueOrDefault;
12013 var opts = me.options;
12014 var display = opts.display;
12015 var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
12016 var minSize = me.minSize;
12017 var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
12018 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12019 var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
12020
12021 if (me.isHorizontal()) {
12022 minSize.width = me.maxWidth; // fill all the width
12023 minSize.height = textSize;
12024 } else {
12025 minSize.width = textSize;
12026 minSize.height = me.maxHeight; // fill all the height
12027 }
12028
12029 me.width = minSize.width;
12030 me.height = minSize.height;
12031
12032 },
12033 afterFit: noop,
12034
12035 // Shared Methods
12036 isHorizontal: function() {
12037 var pos = this.options.position;
12038 return pos === 'top' || pos === 'bottom';
12039 },
12040
12041 // Actually draw the title block on the canvas
12042 draw: function() {
12043 var me = this;
12044 var ctx = me.ctx;
12045 var valueOrDefault = helpers.valueOrDefault;
12046 var opts = me.options;
12047 var globalDefaults = defaults.global;
12048
12049 if (opts.display) {
12050 var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
12051 var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
12052 var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
12053 var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
12054 var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12055 var offset = lineHeight / 2 + opts.padding;
12056 var rotation = 0;
12057 var top = me.top;
12058 var left = me.left;
12059 var bottom = me.bottom;
12060 var right = me.right;
12061 var maxWidth, titleX, titleY;
12062
12063 ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
12064 ctx.font = titleFont;
12065
12066 // Horizontal
12067 if (me.isHorizontal()) {
12068 titleX = left + ((right - left) / 2); // midpoint of the width
12069 titleY = top + offset;
12070 maxWidth = right - left;
12071 } else {
12072 titleX = opts.position === 'left' ? left + offset : right - offset;
12073 titleY = top + ((bottom - top) / 2);
12074 maxWidth = bottom - top;
12075 rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
12076 }
12077
12078 ctx.save();
12079 ctx.translate(titleX, titleY);
12080 ctx.rotate(rotation);
12081 ctx.textAlign = 'center';
12082 ctx.textBaseline = 'middle';
12083
12084 var text = opts.text;
12085 if (helpers.isArray(text)) {
12086 var y = 0;
12087 for (var i = 0; i < text.length; ++i) {
12088 ctx.fillText(text[i], 0, y, maxWidth);
12089 y += lineHeight;
12090 }
12091 } else {
12092 ctx.fillText(text, 0, 0, maxWidth);
12093 }
12094
12095 ctx.restore();
12096 }
12097 }
12098 });
12099
12100 function createNewTitleBlockAndAttach(chart, titleOpts) {
12101 var title = new Chart.Title({
12102 ctx: chart.ctx,
12103 options: titleOpts,
12104 chart: chart
12105 });
12106
12107 layout.configure(chart, title, titleOpts);
12108 layout.addBox(chart, title);
12109 chart.titleBlock = title;
12110 }
12111
12112 return {
12113 id: 'title',
12114
12115 beforeInit: function(chart) {
12116 var titleOpts = chart.options.title;
12117
12118 if (titleOpts) {
12119 createNewTitleBlockAndAttach(chart, titleOpts);
12120 }
12121 },
12122
12123 beforeUpdate: function(chart) {
12124 var titleOpts = chart.options.title;
12125 var titleBlock = chart.titleBlock;
12126
12127 if (titleOpts) {
12128 helpers.mergeIf(titleOpts, defaults.global.title);
12129
12130 if (titleBlock) {
12131 layout.configure(chart, titleBlock, titleOpts);
12132 titleBlock.options = titleOpts;
12133 } else {
12134 createNewTitleBlockAndAttach(chart, titleOpts);
12135 }
12136 } else if (titleBlock) {
12137 Chart.layoutService.removeBox(chart, titleBlock);
12138 delete chart.titleBlock;
12139 }
12140 }
12141 };
12142 };
12143
12144 },{"25":25,"26":26,"45":45}],52:[function(require,module,exports){
12145 'use strict';
12146
12147 module.exports = function(Chart) {
12148
12149 // Default config for a category scale
12150 var defaultConfig = {
12151 position: 'bottom'
12152 };
12153
12154 var DatasetScale = Chart.Scale.extend({
12155 /**
12156 * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
12157 * else fall back to data.labels
12158 * @private
12159 */
12160 getLabels: function() {
12161 var data = this.chart.data;
12162 return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
12163 },
12164
12165 determineDataLimits: function() {
12166 var me = this;
12167 var labels = me.getLabels();
12168 me.minIndex = 0;
12169 me.maxIndex = labels.length - 1;
12170 var findIndex;
12171
12172 if (me.options.ticks.min !== undefined) {
12173 // user specified min value
12174 findIndex = labels.indexOf(me.options.ticks.min);
12175 me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
12176 }
12177
12178 if (me.options.ticks.max !== undefined) {
12179 // user specified max value
12180 findIndex = labels.indexOf(me.options.ticks.max);
12181 me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
12182 }
12183
12184 me.min = labels[me.minIndex];
12185 me.max = labels[me.maxIndex];
12186 },
12187
12188 buildTicks: function() {
12189 var me = this;
12190 var labels = me.getLabels();
12191 // If we are viewing some subset of labels, slice the original array
12192 me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
12193 },
12194
12195 getLabelForIndex: function(index, datasetIndex) {
12196 var me = this;
12197 var data = me.chart.data;
12198 var isHorizontal = me.isHorizontal();
12199
12200 if (data.yLabels && !isHorizontal) {
12201 return me.getRightValue(data.datasets[datasetIndex].data[index]);
12202 }
12203 return me.ticks[index - me.minIndex];
12204 },
12205
12206 // Used to get data value locations. Value can either be an index or a numerical value
12207 getPixelForValue: function(value, index) {
12208 var me = this;
12209 var offset = me.options.offset;
12210 // 1 is added because we need the length but we have the indexes
12211 var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
12212
12213 // If value is a data object, then index is the index in the data array,
12214 // not the index of the scale. We need to change that.
12215 var valueCategory;
12216 if (value !== undefined && value !== null) {
12217 valueCategory = me.isHorizontal() ? value.x : value.y;
12218 }
12219 if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
12220 var labels = me.getLabels();
12221 value = valueCategory || value;
12222 var idx = labels.indexOf(value);
12223 index = idx !== -1 ? idx : index;
12224 }
12225
12226 if (me.isHorizontal()) {
12227 var valueWidth = me.width / offsetAmt;
12228 var widthOffset = (valueWidth * (index - me.minIndex));
12229
12230 if (offset) {
12231 widthOffset += (valueWidth / 2);
12232 }
12233
12234 return me.left + Math.round(widthOffset);
12235 }
12236 var valueHeight = me.height / offsetAmt;
12237 var heightOffset = (valueHeight * (index - me.minIndex));
12238
12239 if (offset) {
12240 heightOffset += (valueHeight / 2);
12241 }
12242
12243 return me.top + Math.round(heightOffset);
12244 },
12245 getPixelForTick: function(index) {
12246 return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
12247 },
12248 getValueForPixel: function(pixel) {
12249 var me = this;
12250 var offset = me.options.offset;
12251 var value;
12252 var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
12253 var horz = me.isHorizontal();
12254 var valueDimension = (horz ? me.width : me.height) / offsetAmt;
12255
12256 pixel -= horz ? me.left : me.top;
12257
12258 if (offset) {
12259 pixel -= (valueDimension / 2);
12260 }
12261
12262 if (pixel <= 0) {
12263 value = 0;
12264 } else {
12265 value = Math.round(pixel / valueDimension);
12266 }
12267
12268 return value + me.minIndex;
12269 },
12270 getBasePixel: function() {
12271 return this.bottom;
12272 }
12273 });
12274
12275 Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
12276
12277 };
12278
12279 },{}],53:[function(require,module,exports){
12280 'use strict';
12281
12282 var defaults = require(25);
12283 var helpers = require(45);
12284 var Ticks = require(34);
12285
12286 module.exports = function(Chart) {
12287
12288 var defaultConfig = {
12289 position: 'left',
12290 ticks: {
12291 callback: Ticks.formatters.linear
12292 }
12293 };
12294
12295 var LinearScale = Chart.LinearScaleBase.extend({
12296
12297 determineDataLimits: function() {
12298 var me = this;
12299 var opts = me.options;
12300 var chart = me.chart;
12301 var data = chart.data;
12302 var datasets = data.datasets;
12303 var isHorizontal = me.isHorizontal();
12304 var DEFAULT_MIN = 0;
12305 var DEFAULT_MAX = 1;
12306
12307 function IDMatches(meta) {
12308 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12309 }
12310
12311 // First Calculate the range
12312 me.min = null;
12313 me.max = null;
12314
12315 var hasStacks = opts.stacked;
12316 if (hasStacks === undefined) {
12317 helpers.each(datasets, function(dataset, datasetIndex) {
12318 if (hasStacks) {
12319 return;
12320 }
12321
12322 var meta = chart.getDatasetMeta(datasetIndex);
12323 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12324 meta.stack !== undefined) {
12325 hasStacks = true;
12326 }
12327 });
12328 }
12329
12330 if (opts.stacked || hasStacks) {
12331 var valuesPerStack = {};
12332
12333 helpers.each(datasets, function(dataset, datasetIndex) {
12334 var meta = chart.getDatasetMeta(datasetIndex);
12335 var key = [
12336 meta.type,
12337 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12338 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12339 meta.stack
12340 ].join('.');
12341
12342 if (valuesPerStack[key] === undefined) {
12343 valuesPerStack[key] = {
12344 positiveValues: [],
12345 negativeValues: []
12346 };
12347 }
12348
12349 // Store these per type
12350 var positiveValues = valuesPerStack[key].positiveValues;
12351 var negativeValues = valuesPerStack[key].negativeValues;
12352
12353 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12354 helpers.each(dataset.data, function(rawValue, index) {
12355 var value = +me.getRightValue(rawValue);
12356 if (isNaN(value) || meta.data[index].hidden) {
12357 return;
12358 }
12359
12360 positiveValues[index] = positiveValues[index] || 0;
12361 negativeValues[index] = negativeValues[index] || 0;
12362
12363 if (opts.relativePoints) {
12364 positiveValues[index] = 100;
12365 } else if (value < 0) {
12366 negativeValues[index] += value;
12367 } else {
12368 positiveValues[index] += value;
12369 }
12370 });
12371 }
12372 });
12373
12374 helpers.each(valuesPerStack, function(valuesForType) {
12375 var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
12376 var minVal = helpers.min(values);
12377 var maxVal = helpers.max(values);
12378 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12379 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12380 });
12381
12382 } else {
12383 helpers.each(datasets, function(dataset, datasetIndex) {
12384 var meta = chart.getDatasetMeta(datasetIndex);
12385 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12386 helpers.each(dataset.data, function(rawValue, index) {
12387 var value = +me.getRightValue(rawValue);
12388 if (isNaN(value) || meta.data[index].hidden) {
12389 return;
12390 }
12391
12392 if (me.min === null) {
12393 me.min = value;
12394 } else if (value < me.min) {
12395 me.min = value;
12396 }
12397
12398 if (me.max === null) {
12399 me.max = value;
12400 } else if (value > me.max) {
12401 me.max = value;
12402 }
12403 });
12404 }
12405 });
12406 }
12407
12408 me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
12409 me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
12410
12411 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
12412 this.handleTickRangeOptions();
12413 },
12414 getTickLimit: function() {
12415 var maxTicks;
12416 var me = this;
12417 var tickOpts = me.options.ticks;
12418
12419 if (me.isHorizontal()) {
12420 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
12421 } else {
12422 // The factor of 2 used to scale the font size has been experimentally determined.
12423 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize);
12424 maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
12425 }
12426
12427 return maxTicks;
12428 },
12429 // Called after the ticks are built. We need
12430 handleDirectionalChanges: function() {
12431 if (!this.isHorizontal()) {
12432 // We are in a vertical orientation. The top value is the highest. So reverse the array
12433 this.ticks.reverse();
12434 }
12435 },
12436 getLabelForIndex: function(index, datasetIndex) {
12437 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12438 },
12439 // Utils
12440 getPixelForValue: function(value) {
12441 // This must be called after fit has been run so that
12442 // this.left, this.top, this.right, and this.bottom have been defined
12443 var me = this;
12444 var start = me.start;
12445
12446 var rightValue = +me.getRightValue(value);
12447 var pixel;
12448 var range = me.end - start;
12449
12450 if (me.isHorizontal()) {
12451 pixel = me.left + (me.width / range * (rightValue - start));
12452 return Math.round(pixel);
12453 }
12454
12455 pixel = me.bottom - (me.height / range * (rightValue - start));
12456 return Math.round(pixel);
12457 },
12458 getValueForPixel: function(pixel) {
12459 var me = this;
12460 var isHorizontal = me.isHorizontal();
12461 var innerDimension = isHorizontal ? me.width : me.height;
12462 var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
12463 return me.start + ((me.end - me.start) * offset);
12464 },
12465 getPixelForTick: function(index) {
12466 return this.getPixelForValue(this.ticksAsNumbers[index]);
12467 }
12468 });
12469 Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
12470
12471 };
12472
12473 },{"25":25,"34":34,"45":45}],54:[function(require,module,exports){
12474 'use strict';
12475
12476 var helpers = require(45);
12477 var Ticks = require(34);
12478
12479 module.exports = function(Chart) {
12480
12481 var noop = helpers.noop;
12482
12483 Chart.LinearScaleBase = Chart.Scale.extend({
12484 getRightValue: function(value) {
12485 if (typeof value === 'string') {
12486 return +value;
12487 }
12488 return Chart.Scale.prototype.getRightValue.call(this, value);
12489 },
12490
12491 handleTickRangeOptions: function() {
12492 var me = this;
12493 var opts = me.options;
12494 var tickOpts = opts.ticks;
12495
12496 // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
12497 // do nothing since that would make the chart weird. If the user really wants a weird chart
12498 // axis, they can manually override it
12499 if (tickOpts.beginAtZero) {
12500 var minSign = helpers.sign(me.min);
12501 var maxSign = helpers.sign(me.max);
12502
12503 if (minSign < 0 && maxSign < 0) {
12504 // move the top up to 0
12505 me.max = 0;
12506 } else if (minSign > 0 && maxSign > 0) {
12507 // move the bottom down to 0
12508 me.min = 0;
12509 }
12510 }
12511
12512 var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
12513 var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
12514
12515 if (tickOpts.min !== undefined) {
12516 me.min = tickOpts.min;
12517 } else if (tickOpts.suggestedMin !== undefined) {
12518 if (me.min === null) {
12519 me.min = tickOpts.suggestedMin;
12520 } else {
12521 me.min = Math.min(me.min, tickOpts.suggestedMin);
12522 }
12523 }
12524
12525 if (tickOpts.max !== undefined) {
12526 me.max = tickOpts.max;
12527 } else if (tickOpts.suggestedMax !== undefined) {
12528 if (me.max === null) {
12529 me.max = tickOpts.suggestedMax;
12530 } else {
12531 me.max = Math.max(me.max, tickOpts.suggestedMax);
12532 }
12533 }
12534
12535 if (setMin !== setMax) {
12536 // We set the min or the max but not both.
12537 // So ensure that our range is good
12538 // Inverted or 0 length range can happen when
12539 // ticks.min is set, and no datasets are visible
12540 if (me.min >= me.max) {
12541 if (setMin) {
12542 me.max = me.min + 1;
12543 } else {
12544 me.min = me.max - 1;
12545 }
12546 }
12547 }
12548
12549 if (me.min === me.max) {
12550 me.max++;
12551
12552 if (!tickOpts.beginAtZero) {
12553 me.min--;
12554 }
12555 }
12556 },
12557 getTickLimit: noop,
12558 handleDirectionalChanges: noop,
12559
12560 buildTicks: function() {
12561 var me = this;
12562 var opts = me.options;
12563 var tickOpts = opts.ticks;
12564
12565 // Figure out what the max number of ticks we can support it is based on the size of
12566 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
12567 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
12568 // the graph. Make sure we always have at least 2 ticks
12569 var maxTicks = me.getTickLimit();
12570 maxTicks = Math.max(2, maxTicks);
12571
12572 var numericGeneratorOptions = {
12573 maxTicks: maxTicks,
12574 min: tickOpts.min,
12575 max: tickOpts.max,
12576 stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
12577 };
12578 var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me);
12579
12580 me.handleDirectionalChanges();
12581
12582 // At this point, we need to update our max and min given the tick values since we have expanded the
12583 // range of the scale
12584 me.max = helpers.max(ticks);
12585 me.min = helpers.min(ticks);
12586
12587 if (tickOpts.reverse) {
12588 ticks.reverse();
12589
12590 me.start = me.max;
12591 me.end = me.min;
12592 } else {
12593 me.start = me.min;
12594 me.end = me.max;
12595 }
12596 },
12597 convertTicksToLabels: function() {
12598 var me = this;
12599 me.ticksAsNumbers = me.ticks.slice();
12600 me.zeroLineIndex = me.ticks.indexOf(0);
12601
12602 Chart.Scale.prototype.convertTicksToLabels.call(me);
12603 }
12604 });
12605 };
12606
12607 },{"34":34,"45":45}],55:[function(require,module,exports){
12608 'use strict';
12609
12610 var helpers = require(45);
12611 var Ticks = require(34);
12612
12613 module.exports = function(Chart) {
12614
12615 var defaultConfig = {
12616 position: 'left',
12617
12618 // label settings
12619 ticks: {
12620 callback: Ticks.formatters.logarithmic
12621 }
12622 };
12623
12624 var LogarithmicScale = Chart.Scale.extend({
12625 determineDataLimits: function() {
12626 var me = this;
12627 var opts = me.options;
12628 var tickOpts = opts.ticks;
12629 var chart = me.chart;
12630 var data = chart.data;
12631 var datasets = data.datasets;
12632 var valueOrDefault = helpers.valueOrDefault;
12633 var isHorizontal = me.isHorizontal();
12634 function IDMatches(meta) {
12635 return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12636 }
12637
12638 // Calculate Range
12639 me.min = null;
12640 me.max = null;
12641 me.minNotZero = null;
12642
12643 var hasStacks = opts.stacked;
12644 if (hasStacks === undefined) {
12645 helpers.each(datasets, function(dataset, datasetIndex) {
12646 if (hasStacks) {
12647 return;
12648 }
12649
12650 var meta = chart.getDatasetMeta(datasetIndex);
12651 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12652 meta.stack !== undefined) {
12653 hasStacks = true;
12654 }
12655 });
12656 }
12657
12658 if (opts.stacked || hasStacks) {
12659 var valuesPerStack = {};
12660
12661 helpers.each(datasets, function(dataset, datasetIndex) {
12662 var meta = chart.getDatasetMeta(datasetIndex);
12663 var key = [
12664 meta.type,
12665 // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12666 ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12667 meta.stack
12668 ].join('.');
12669
12670 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12671 if (valuesPerStack[key] === undefined) {
12672 valuesPerStack[key] = [];
12673 }
12674
12675 helpers.each(dataset.data, function(rawValue, index) {
12676 var values = valuesPerStack[key];
12677 var value = +me.getRightValue(rawValue);
12678 if (isNaN(value) || meta.data[index].hidden) {
12679 return;
12680 }
12681
12682 values[index] = values[index] || 0;
12683
12684 if (opts.relativePoints) {
12685 values[index] = 100;
12686 } else {
12687 // Don't need to split positive and negative since the log scale can't handle a 0 crossing
12688 values[index] += value;
12689 }
12690 });
12691 }
12692 });
12693
12694 helpers.each(valuesPerStack, function(valuesForType) {
12695 var minVal = helpers.min(valuesForType);
12696 var maxVal = helpers.max(valuesForType);
12697 me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12698 me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12699 });
12700
12701 } else {
12702 helpers.each(datasets, function(dataset, datasetIndex) {
12703 var meta = chart.getDatasetMeta(datasetIndex);
12704 if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12705 helpers.each(dataset.data, function(rawValue, index) {
12706 var value = +me.getRightValue(rawValue);
12707 if (isNaN(value) || meta.data[index].hidden) {
12708 return;
12709 }
12710
12711 if (me.min === null) {
12712 me.min = value;
12713 } else if (value < me.min) {
12714 me.min = value;
12715 }
12716
12717 if (me.max === null) {
12718 me.max = value;
12719 } else if (value > me.max) {
12720 me.max = value;
12721 }
12722
12723 if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
12724 me.minNotZero = value;
12725 }
12726 });
12727 }
12728 });
12729 }
12730
12731 me.min = valueOrDefault(tickOpts.min, me.min);
12732 me.max = valueOrDefault(tickOpts.max, me.max);
12733
12734 if (me.min === me.max) {
12735 if (me.min !== 0 && me.min !== null) {
12736 me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
12737 me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
12738 } else {
12739 me.min = 1;
12740 me.max = 10;
12741 }
12742 }
12743 },
12744 buildTicks: function() {
12745 var me = this;
12746 var opts = me.options;
12747 var tickOpts = opts.ticks;
12748
12749 var generationOptions = {
12750 min: tickOpts.min,
12751 max: tickOpts.max
12752 };
12753 var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me);
12754
12755 if (!me.isHorizontal()) {
12756 // We are in a vertical orientation. The top value is the highest. So reverse the array
12757 ticks.reverse();
12758 }
12759
12760 // At this point, we need to update our max and min given the tick values since we have expanded the
12761 // range of the scale
12762 me.max = helpers.max(ticks);
12763 me.min = helpers.min(ticks);
12764
12765 if (tickOpts.reverse) {
12766 ticks.reverse();
12767
12768 me.start = me.max;
12769 me.end = me.min;
12770 } else {
12771 me.start = me.min;
12772 me.end = me.max;
12773 }
12774 },
12775 convertTicksToLabels: function() {
12776 this.tickValues = this.ticks.slice();
12777
12778 Chart.Scale.prototype.convertTicksToLabels.call(this);
12779 },
12780 // Get the correct tooltip label
12781 getLabelForIndex: function(index, datasetIndex) {
12782 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12783 },
12784 getPixelForTick: function(index) {
12785 return this.getPixelForValue(this.tickValues[index]);
12786 },
12787 getPixelForValue: function(value) {
12788 var me = this;
12789 var start = me.start;
12790 var newVal = +me.getRightValue(value);
12791 var opts = me.options;
12792 var tickOpts = opts.ticks;
12793 var innerDimension, pixel, range;
12794
12795 if (me.isHorizontal()) {
12796 range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
12797 if (newVal === 0) {
12798 pixel = me.left;
12799 } else {
12800 innerDimension = me.width;
12801 pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12802 }
12803 } else {
12804 // Bottom - top since pixels increase downward on a screen
12805 innerDimension = me.height;
12806 if (start === 0 && !tickOpts.reverse) {
12807 range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
12808 if (newVal === start) {
12809 pixel = me.bottom;
12810 } else if (newVal === me.minNotZero) {
12811 pixel = me.bottom - innerDimension * 0.02;
12812 } else {
12813 pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12814 }
12815 } else if (me.end === 0 && tickOpts.reverse) {
12816 range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
12817 if (newVal === me.end) {
12818 pixel = me.top;
12819 } else if (newVal === me.minNotZero) {
12820 pixel = me.top + innerDimension * 0.02;
12821 } else {
12822 pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12823 }
12824 } else if (newVal === 0) {
12825 pixel = tickOpts.reverse ? me.top : me.bottom;
12826 } else {
12827 range = helpers.log10(me.end) - helpers.log10(start);
12828 innerDimension = me.height;
12829 pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12830 }
12831 }
12832 return pixel;
12833 },
12834 getValueForPixel: function(pixel) {
12835 var me = this;
12836 var range = helpers.log10(me.end) - helpers.log10(me.start);
12837 var value, innerDimension;
12838
12839 if (me.isHorizontal()) {
12840 innerDimension = me.width;
12841 value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);
12842 } else { // todo: if start === 0
12843 innerDimension = me.height;
12844 value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;
12845 }
12846 return value;
12847 }
12848 });
12849 Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
12850
12851 };
12852
12853 },{"34":34,"45":45}],56:[function(require,module,exports){
12854 'use strict';
12855
12856 var defaults = require(25);
12857 var helpers = require(45);
12858 var Ticks = require(34);
12859
12860 module.exports = function(Chart) {
12861
12862 var globalDefaults = defaults.global;
12863
12864 var defaultConfig = {
12865 display: true,
12866
12867 // Boolean - Whether to animate scaling the chart from the centre
12868 animate: true,
12869 position: 'chartArea',
12870
12871 angleLines: {
12872 display: true,
12873 color: 'rgba(0, 0, 0, 0.1)',
12874 lineWidth: 1
12875 },
12876
12877 gridLines: {
12878 circular: false
12879 },
12880
12881 // label settings
12882 ticks: {
12883 // Boolean - Show a backdrop to the scale label
12884 showLabelBackdrop: true,
12885
12886 // String - The colour of the label backdrop
12887 backdropColor: 'rgba(255,255,255,0.75)',
12888
12889 // Number - The backdrop padding above & below the label in pixels
12890 backdropPaddingY: 2,
12891
12892 // Number - The backdrop padding to the side of the label in pixels
12893 backdropPaddingX: 2,
12894
12895 callback: Ticks.formatters.linear
12896 },
12897
12898 pointLabels: {
12899 // Boolean - if true, show point labels
12900 display: true,
12901
12902 // Number - Point label font size in pixels
12903 fontSize: 10,
12904
12905 // Function - Used to convert point labels
12906 callback: function(label) {
12907 return label;
12908 }
12909 }
12910 };
12911
12912 function getValueCount(scale) {
12913 var opts = scale.options;
12914 return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
12915 }
12916
12917 function getPointLabelFontOptions(scale) {
12918 var pointLabelOptions = scale.options.pointLabels;
12919 var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
12920 var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
12921 var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
12922 var font = helpers.fontString(fontSize, fontStyle, fontFamily);
12923
12924 return {
12925 size: fontSize,
12926 style: fontStyle,
12927 family: fontFamily,
12928 font: font
12929 };
12930 }
12931
12932 function measureLabelSize(ctx, fontSize, label) {
12933 if (helpers.isArray(label)) {
12934 return {
12935 w: helpers.longestText(ctx, ctx.font, label),
12936 h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
12937 };
12938 }
12939
12940 return {
12941 w: ctx.measureText(label).width,
12942 h: fontSize
12943 };
12944 }
12945
12946 function determineLimits(angle, pos, size, min, max) {
12947 if (angle === min || angle === max) {
12948 return {
12949 start: pos - (size / 2),
12950 end: pos + (size / 2)
12951 };
12952 } else if (angle < min || angle > max) {
12953 return {
12954 start: pos - size - 5,
12955 end: pos
12956 };
12957 }
12958
12959 return {
12960 start: pos,
12961 end: pos + size + 5
12962 };
12963 }
12964
12965 /**
12966 * Helper function to fit a radial linear scale with point labels
12967 */
12968 function fitWithPointLabels(scale) {
12969 /*
12970 * Right, this is really confusing and there is a lot of maths going on here
12971 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
12972 *
12973 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
12974 *
12975 * Solution:
12976 *
12977 * We assume the radius of the polygon is half the size of the canvas at first
12978 * at each index we check if the text overlaps.
12979 *
12980 * Where it does, we store that angle and that index.
12981 *
12982 * After finding the largest index and angle we calculate how much we need to remove
12983 * from the shape radius to move the point inwards by that x.
12984 *
12985 * We average the left and right distances to get the maximum shape radius that can fit in the box
12986 * along with labels.
12987 *
12988 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
12989 * on each side, removing that from the size, halving it and adding the left x protrusion width.
12990 *
12991 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
12992 * and position it in the most space efficient manner
12993 *
12994 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
12995 */
12996
12997 var plFont = getPointLabelFontOptions(scale);
12998
12999 // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
13000 // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
13001 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13002 var furthestLimits = {
13003 r: scale.width,
13004 l: 0,
13005 t: scale.height,
13006 b: 0
13007 };
13008 var furthestAngles = {};
13009 var i, textSize, pointPosition;
13010
13011 scale.ctx.font = plFont.font;
13012 scale._pointLabelSizes = [];
13013
13014 var valueCount = getValueCount(scale);
13015 for (i = 0; i < valueCount; i++) {
13016 pointPosition = scale.getPointPosition(i, largestPossibleRadius);
13017 textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
13018 scale._pointLabelSizes[i] = textSize;
13019
13020 // Add quarter circle to make degree 0 mean top of circle
13021 var angleRadians = scale.getIndexAngle(i);
13022 var angle = helpers.toDegrees(angleRadians) % 360;
13023 var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
13024 var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
13025
13026 if (hLimits.start < furthestLimits.l) {
13027 furthestLimits.l = hLimits.start;
13028 furthestAngles.l = angleRadians;
13029 }
13030
13031 if (hLimits.end > furthestLimits.r) {
13032 furthestLimits.r = hLimits.end;
13033 furthestAngles.r = angleRadians;
13034 }
13035
13036 if (vLimits.start < furthestLimits.t) {
13037 furthestLimits.t = vLimits.start;
13038 furthestAngles.t = angleRadians;
13039 }
13040
13041 if (vLimits.end > furthestLimits.b) {
13042 furthestLimits.b = vLimits.end;
13043 furthestAngles.b = angleRadians;
13044 }
13045 }
13046
13047 scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
13048 }
13049
13050 /**
13051 * Helper function to fit a radial linear scale with no point labels
13052 */
13053 function fit(scale) {
13054 var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13055 scale.drawingArea = Math.round(largestPossibleRadius);
13056 scale.setCenterPoint(0, 0, 0, 0);
13057 }
13058
13059 function getTextAlignForAngle(angle) {
13060 if (angle === 0 || angle === 180) {
13061 return 'center';
13062 } else if (angle < 180) {
13063 return 'left';
13064 }
13065
13066 return 'right';
13067 }
13068
13069 function fillText(ctx, text, position, fontSize) {
13070 if (helpers.isArray(text)) {
13071 var y = position.y;
13072 var spacing = 1.5 * fontSize;
13073
13074 for (var i = 0; i < text.length; ++i) {
13075 ctx.fillText(text[i], position.x, y);
13076 y += spacing;
13077 }
13078 } else {
13079 ctx.fillText(text, position.x, position.y);
13080 }
13081 }
13082
13083 function adjustPointPositionForLabelHeight(angle, textSize, position) {
13084 if (angle === 90 || angle === 270) {
13085 position.y -= (textSize.h / 2);
13086 } else if (angle > 270 || angle < 90) {
13087 position.y -= textSize.h;
13088 }
13089 }
13090
13091 function drawPointLabels(scale) {
13092 var ctx = scale.ctx;
13093 var valueOrDefault = helpers.valueOrDefault;
13094 var opts = scale.options;
13095 var angleLineOpts = opts.angleLines;
13096 var pointLabelOpts = opts.pointLabels;
13097
13098 ctx.lineWidth = angleLineOpts.lineWidth;
13099 ctx.strokeStyle = angleLineOpts.color;
13100
13101 var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
13102
13103 // Point Label Font
13104 var plFont = getPointLabelFontOptions(scale);
13105
13106 ctx.textBaseline = 'top';
13107
13108 for (var i = getValueCount(scale) - 1; i >= 0; i--) {
13109 if (angleLineOpts.display) {
13110 var outerPosition = scale.getPointPosition(i, outerDistance);
13111 ctx.beginPath();
13112 ctx.moveTo(scale.xCenter, scale.yCenter);
13113 ctx.lineTo(outerPosition.x, outerPosition.y);
13114 ctx.stroke();
13115 ctx.closePath();
13116 }
13117
13118 if (pointLabelOpts.display) {
13119 // Extra 3px out for some label spacing
13120 var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
13121
13122 // Keep this in loop since we may support array properties here
13123 var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
13124 ctx.font = plFont.font;
13125 ctx.fillStyle = pointLabelFontColor;
13126
13127 var angleRadians = scale.getIndexAngle(i);
13128 var angle = helpers.toDegrees(angleRadians);
13129 ctx.textAlign = getTextAlignForAngle(angle);
13130 adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
13131 fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
13132 }
13133 }
13134 }
13135
13136 function drawRadiusLine(scale, gridLineOpts, radius, index) {
13137 var ctx = scale.ctx;
13138 ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1);
13139 ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
13140
13141 if (scale.options.gridLines.circular) {
13142 // Draw circular arcs between the points
13143 ctx.beginPath();
13144 ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
13145 ctx.closePath();
13146 ctx.stroke();
13147 } else {
13148 // Draw straight lines connecting each index
13149 var valueCount = getValueCount(scale);
13150
13151 if (valueCount === 0) {
13152 return;
13153 }
13154
13155 ctx.beginPath();
13156 var pointPosition = scale.getPointPosition(0, radius);
13157 ctx.moveTo(pointPosition.x, pointPosition.y);
13158
13159 for (var i = 1; i < valueCount; i++) {
13160 pointPosition = scale.getPointPosition(i, radius);
13161 ctx.lineTo(pointPosition.x, pointPosition.y);
13162 }
13163
13164 ctx.closePath();
13165 ctx.stroke();
13166 }
13167 }
13168
13169 function numberOrZero(param) {
13170 return helpers.isNumber(param) ? param : 0;
13171 }
13172
13173 var LinearRadialScale = Chart.LinearScaleBase.extend({
13174 setDimensions: function() {
13175 var me = this;
13176 var opts = me.options;
13177 var tickOpts = opts.ticks;
13178 // Set the unconstrained dimension before label rotation
13179 me.width = me.maxWidth;
13180 me.height = me.maxHeight;
13181 me.xCenter = Math.round(me.width / 2);
13182 me.yCenter = Math.round(me.height / 2);
13183
13184 var minSize = helpers.min([me.height, me.width]);
13185 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13186 me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
13187 },
13188 determineDataLimits: function() {
13189 var me = this;
13190 var chart = me.chart;
13191 var min = Number.POSITIVE_INFINITY;
13192 var max = Number.NEGATIVE_INFINITY;
13193
13194 helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
13195 if (chart.isDatasetVisible(datasetIndex)) {
13196 var meta = chart.getDatasetMeta(datasetIndex);
13197
13198 helpers.each(dataset.data, function(rawValue, index) {
13199 var value = +me.getRightValue(rawValue);
13200 if (isNaN(value) || meta.data[index].hidden) {
13201 return;
13202 }
13203
13204 min = Math.min(value, min);
13205 max = Math.max(value, max);
13206 });
13207 }
13208 });
13209
13210 me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
13211 me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
13212
13213 // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13214 me.handleTickRangeOptions();
13215 },
13216 getTickLimit: function() {
13217 var tickOpts = this.options.ticks;
13218 var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13219 return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
13220 },
13221 convertTicksToLabels: function() {
13222 var me = this;
13223
13224 Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
13225
13226 // Point labels
13227 me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
13228 },
13229 getLabelForIndex: function(index, datasetIndex) {
13230 return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
13231 },
13232 fit: function() {
13233 if (this.options.pointLabels.display) {
13234 fitWithPointLabels(this);
13235 } else {
13236 fit(this);
13237 }
13238 },
13239 /**
13240 * Set radius reductions and determine new radius and center point
13241 * @private
13242 */
13243 setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
13244 var me = this;
13245 var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
13246 var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
13247 var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
13248 var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
13249
13250 radiusReductionLeft = numberOrZero(radiusReductionLeft);
13251 radiusReductionRight = numberOrZero(radiusReductionRight);
13252 radiusReductionTop = numberOrZero(radiusReductionTop);
13253 radiusReductionBottom = numberOrZero(radiusReductionBottom);
13254
13255 me.drawingArea = Math.min(
13256 Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
13257 Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
13258 me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
13259 },
13260 setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
13261 var me = this;
13262 var maxRight = me.width - rightMovement - me.drawingArea;
13263 var maxLeft = leftMovement + me.drawingArea;
13264 var maxTop = topMovement + me.drawingArea;
13265 var maxBottom = me.height - bottomMovement - me.drawingArea;
13266
13267 me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
13268 me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
13269 },
13270
13271 getIndexAngle: function(index) {
13272 var angleMultiplier = (Math.PI * 2) / getValueCount(this);
13273 var startAngle = this.chart.options && this.chart.options.startAngle ?
13274 this.chart.options.startAngle :
13275 0;
13276
13277 var startAngleRadians = startAngle * Math.PI * 2 / 360;
13278
13279 // Start from the top instead of right, so remove a quarter of the circle
13280 return index * angleMultiplier + startAngleRadians;
13281 },
13282 getDistanceFromCenterForValue: function(value) {
13283 var me = this;
13284
13285 if (value === null) {
13286 return 0; // null always in center
13287 }
13288
13289 // Take into account half font size + the yPadding of the top value
13290 var scalingFactor = me.drawingArea / (me.max - me.min);
13291 if (me.options.ticks.reverse) {
13292 return (me.max - value) * scalingFactor;
13293 }
13294 return (value - me.min) * scalingFactor;
13295 },
13296 getPointPosition: function(index, distanceFromCenter) {
13297 var me = this;
13298 var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
13299 return {
13300 x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
13301 y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
13302 };
13303 },
13304 getPointPositionForValue: function(index, value) {
13305 return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
13306 },
13307
13308 getBasePosition: function() {
13309 var me = this;
13310 var min = me.min;
13311 var max = me.max;
13312
13313 return me.getPointPositionForValue(0,
13314 me.beginAtZero ? 0 :
13315 min < 0 && max < 0 ? max :
13316 min > 0 && max > 0 ? min :
13317 0);
13318 },
13319
13320 draw: function() {
13321 var me = this;
13322 var opts = me.options;
13323 var gridLineOpts = opts.gridLines;
13324 var tickOpts = opts.ticks;
13325 var valueOrDefault = helpers.valueOrDefault;
13326
13327 if (opts.display) {
13328 var ctx = me.ctx;
13329 var startAngle = this.getIndexAngle(0);
13330
13331 // Tick Font
13332 var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13333 var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
13334 var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
13335 var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
13336
13337 helpers.each(me.ticks, function(label, index) {
13338 // Don't draw a centre value (if it is minimum)
13339 if (index > 0 || tickOpts.reverse) {
13340 var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13341
13342 // Draw circular lines around the scale
13343 if (gridLineOpts.display && index !== 0) {
13344 drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
13345 }
13346
13347 if (tickOpts.display) {
13348 var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
13349 ctx.font = tickLabelFont;
13350
13351 ctx.save();
13352 ctx.translate(me.xCenter, me.yCenter);
13353 ctx.rotate(startAngle);
13354
13355 if (tickOpts.showLabelBackdrop) {
13356 var labelWidth = ctx.measureText(label).width;
13357 ctx.fillStyle = tickOpts.backdropColor;
13358 ctx.fillRect(
13359 -labelWidth / 2 - tickOpts.backdropPaddingX,
13360 -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY,
13361 labelWidth + tickOpts.backdropPaddingX * 2,
13362 tickFontSize + tickOpts.backdropPaddingY * 2
13363 );
13364 }
13365
13366 ctx.textAlign = 'center';
13367 ctx.textBaseline = 'middle';
13368 ctx.fillStyle = tickFontColor;
13369 ctx.fillText(label, 0, -yCenterOffset);
13370 ctx.restore();
13371 }
13372 }
13373 });
13374
13375 if (opts.angleLines.display || opts.pointLabels.display) {
13376 drawPointLabels(me);
13377 }
13378 }
13379 }
13380 });
13381 Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
13382
13383 };
13384
13385 },{"25":25,"34":34,"45":45}],57:[function(require,module,exports){
13386 /* global window: false */
13387 'use strict';
13388
13389 var moment = require(1);
13390 moment = typeof moment === 'function' ? moment : window.moment;
13391
13392 var defaults = require(25);
13393 var helpers = require(45);
13394
13395 // Integer constants are from the ES6 spec.
13396 var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
13397 var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
13398
13399 var INTERVALS = {
13400 millisecond: {
13401 common: true,
13402 size: 1,
13403 steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
13404 },
13405 second: {
13406 common: true,
13407 size: 1000,
13408 steps: [1, 2, 5, 10, 30]
13409 },
13410 minute: {
13411 common: true,
13412 size: 60000,
13413 steps: [1, 2, 5, 10, 30]
13414 },
13415 hour: {
13416 common: true,
13417 size: 3600000,
13418 steps: [1, 2, 3, 6, 12]
13419 },
13420 day: {
13421 common: true,
13422 size: 86400000,
13423 steps: [1, 2, 5]
13424 },
13425 week: {
13426 common: false,
13427 size: 604800000,
13428 steps: [1, 2, 3, 4]
13429 },
13430 month: {
13431 common: true,
13432 size: 2.628e9,
13433 steps: [1, 2, 3]
13434 },
13435 quarter: {
13436 common: false,
13437 size: 7.884e9,
13438 steps: [1, 2, 3, 4]
13439 },
13440 year: {
13441 common: true,
13442 size: 3.154e10
13443 }
13444 };
13445
13446 var UNITS = Object.keys(INTERVALS);
13447
13448 function sorter(a, b) {
13449 return a - b;
13450 }
13451
13452 function arrayUnique(items) {
13453 var hash = {};
13454 var out = [];
13455 var i, ilen, item;
13456
13457 for (i = 0, ilen = items.length; i < ilen; ++i) {
13458 item = items[i];
13459 if (!hash[item]) {
13460 hash[item] = true;
13461 out.push(item);
13462 }
13463 }
13464
13465 return out;
13466 }
13467
13468 /**
13469 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
13470 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
13471 * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
13472 * extremity (left + width or top + height). Note that it would be more optimized to directly
13473 * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
13474 * to create the lookup table. The table ALWAYS contains at least two items: min and max.
13475 *
13476 * @param {Number[]} timestamps - timestamps sorted from lowest to highest.
13477 * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
13478 * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
13479 * If 'series', timestamps will be positioned at the same distance from each other. In this
13480 * case, only timestamps that break the time linearity are registered, meaning that in the
13481 * best case, all timestamps are linear, the table contains only min and max.
13482 */
13483 function buildLookupTable(timestamps, min, max, distribution) {
13484 if (distribution === 'linear' || !timestamps.length) {
13485 return [
13486 {time: min, pos: 0},
13487 {time: max, pos: 1}
13488 ];
13489 }
13490
13491 var table = [];
13492 var items = [min];
13493 var i, ilen, prev, curr, next;
13494
13495 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13496 curr = timestamps[i];
13497 if (curr > min && curr < max) {
13498 items.push(curr);
13499 }
13500 }
13501
13502 items.push(max);
13503
13504 for (i = 0, ilen = items.length; i < ilen; ++i) {
13505 next = items[i + 1];
13506 prev = items[i - 1];
13507 curr = items[i];
13508
13509 // only add points that breaks the scale linearity
13510 if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
13511 table.push({time: curr, pos: i / (ilen - 1)});
13512 }
13513 }
13514
13515 return table;
13516 }
13517
13518 // @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
13519 function lookup(table, key, value) {
13520 var lo = 0;
13521 var hi = table.length - 1;
13522 var mid, i0, i1;
13523
13524 while (lo >= 0 && lo <= hi) {
13525 mid = (lo + hi) >> 1;
13526 i0 = table[mid - 1] || null;
13527 i1 = table[mid];
13528
13529 if (!i0) {
13530 // given value is outside table (before first item)
13531 return {lo: null, hi: i1};
13532 } else if (i1[key] < value) {
13533 lo = mid + 1;
13534 } else if (i0[key] > value) {
13535 hi = mid - 1;
13536 } else {
13537 return {lo: i0, hi: i1};
13538 }
13539 }
13540
13541 // given value is outside table (after last item)
13542 return {lo: i1, hi: null};
13543 }
13544
13545 /**
13546 * Linearly interpolates the given source `value` using the table items `skey` values and
13547 * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
13548 * returns the position for a timestamp equal to 42. If value is out of bounds, values at
13549 * index [0, 1] or [n - 1, n] are used for the interpolation.
13550 */
13551 function interpolate(table, skey, sval, tkey) {
13552 var range = lookup(table, skey, sval);
13553
13554 // Note: the lookup table ALWAYS contains at least 2 items (min and max)
13555 var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
13556 var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
13557
13558 var span = next[skey] - prev[skey];
13559 var ratio = span ? (sval - prev[skey]) / span : 0;
13560 var offset = (next[tkey] - prev[tkey]) * ratio;
13561
13562 return prev[tkey] + offset;
13563 }
13564
13565 /**
13566 * Convert the given value to a moment object using the given time options.
13567 * @see http://momentjs.com/docs/#/parsing/
13568 */
13569 function momentify(value, options) {
13570 var parser = options.parser;
13571 var format = options.parser || options.format;
13572
13573 if (typeof parser === 'function') {
13574 return parser(value);
13575 }
13576
13577 if (typeof value === 'string' && typeof format === 'string') {
13578 return moment(value, format);
13579 }
13580
13581 if (!(value instanceof moment)) {
13582 value = moment(value);
13583 }
13584
13585 if (value.isValid()) {
13586 return value;
13587 }
13588
13589 // Labels are in an incompatible moment format and no `parser` has been provided.
13590 // The user might still use the deprecated `format` option to convert his inputs.
13591 if (typeof format === 'function') {
13592 return format(value);
13593 }
13594
13595 return value;
13596 }
13597
13598 function parse(input, scale) {
13599 if (helpers.isNullOrUndef(input)) {
13600 return null;
13601 }
13602
13603 var options = scale.options.time;
13604 var value = momentify(scale.getRightValue(input), options);
13605 if (!value.isValid()) {
13606 return null;
13607 }
13608
13609 if (options.round) {
13610 value.startOf(options.round);
13611 }
13612
13613 return value.valueOf();
13614 }
13615
13616 /**
13617 * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
13618 * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
13619 */
13620 function determineStepSize(min, max, unit, capacity) {
13621 var range = max - min;
13622 var interval = INTERVALS[unit];
13623 var milliseconds = interval.size;
13624 var steps = interval.steps;
13625 var i, ilen, factor;
13626
13627 if (!steps) {
13628 return Math.ceil(range / ((capacity || 1) * milliseconds));
13629 }
13630
13631 for (i = 0, ilen = steps.length; i < ilen; ++i) {
13632 factor = steps[i];
13633 if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
13634 break;
13635 }
13636 }
13637
13638 return factor;
13639 }
13640
13641 /**
13642 * Figures out what unit results in an appropriate number of auto-generated ticks
13643 */
13644 function determineUnitForAutoTicks(minUnit, min, max, capacity) {
13645 var ilen = UNITS.length;
13646 var i, interval, factor;
13647
13648 for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
13649 interval = INTERVALS[UNITS[i]];
13650 factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
13651
13652 if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
13653 return UNITS[i];
13654 }
13655 }
13656
13657 return UNITS[ilen - 1];
13658 }
13659
13660 /**
13661 * Figures out what unit to format a set of ticks with
13662 */
13663 function determineUnitForFormatting(ticks, minUnit, min, max) {
13664 var duration = moment.duration(moment(max).diff(moment(min)));
13665 var ilen = UNITS.length;
13666 var i, unit;
13667
13668 for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
13669 unit = UNITS[i];
13670 if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) {
13671 return unit;
13672 }
13673 }
13674
13675 return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
13676 }
13677
13678 function determineMajorUnit(unit) {
13679 for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
13680 if (INTERVALS[UNITS[i]].common) {
13681 return UNITS[i];
13682 }
13683 }
13684 }
13685
13686 /**
13687 * Generates a maximum of `capacity` timestamps between min and max, rounded to the
13688 * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
13689 * Important: this method can return ticks outside the min and max range, it's the
13690 * responsibility of the calling code to clamp values if needed.
13691 */
13692 function generate(min, max, capacity, options) {
13693 var timeOpts = options.time;
13694 var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
13695 var major = determineMajorUnit(minor);
13696 var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
13697 var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
13698 var majorTicksEnabled = options.ticks.major.enabled;
13699 var interval = INTERVALS[minor];
13700 var first = moment(min);
13701 var last = moment(max);
13702 var ticks = [];
13703 var time;
13704
13705 if (!stepSize) {
13706 stepSize = determineStepSize(min, max, minor, capacity);
13707 }
13708
13709 // For 'week' unit, handle the first day of week option
13710 if (weekday) {
13711 first = first.isoWeekday(weekday);
13712 last = last.isoWeekday(weekday);
13713 }
13714
13715 // Align first/last ticks on unit
13716 first = first.startOf(weekday ? 'day' : minor);
13717 last = last.startOf(weekday ? 'day' : minor);
13718
13719 // Make sure that the last tick include max
13720 if (last < max) {
13721 last.add(1, minor);
13722 }
13723
13724 time = moment(first);
13725
13726 if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
13727 // Align the first tick on the previous `minor` unit aligned on the `major` unit:
13728 // we first aligned time on the previous `major` unit then add the number of full
13729 // stepSize there is between first and the previous major time.
13730 time.startOf(major);
13731 time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
13732 }
13733
13734 for (; time < last; time.add(stepSize, minor)) {
13735 ticks.push(+time);
13736 }
13737
13738 ticks.push(+time);
13739
13740 return ticks;
13741 }
13742
13743 /**
13744 * Returns the right and left offsets from edges in the form of {left, right}.
13745 * Offsets are added when the `offset` option is true.
13746 */
13747 function computeOffsets(table, ticks, min, max, options) {
13748 var left = 0;
13749 var right = 0;
13750 var upper, lower;
13751
13752 if (options.offset && ticks.length) {
13753 if (!options.time.min) {
13754 upper = ticks.length > 1 ? ticks[1] : max;
13755 lower = ticks[0];
13756 left = (
13757 interpolate(table, 'time', upper, 'pos') -
13758 interpolate(table, 'time', lower, 'pos')
13759 ) / 2;
13760 }
13761 if (!options.time.max) {
13762 upper = ticks[ticks.length - 1];
13763 lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
13764 right = (
13765 interpolate(table, 'time', upper, 'pos') -
13766 interpolate(table, 'time', lower, 'pos')
13767 ) / 2;
13768 }
13769 }
13770
13771 return {left: left, right: right};
13772 }
13773
13774 function ticksFromTimestamps(values, majorUnit) {
13775 var ticks = [];
13776 var i, ilen, value, major;
13777
13778 for (i = 0, ilen = values.length; i < ilen; ++i) {
13779 value = values[i];
13780 major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
13781
13782 ticks.push({
13783 value: value,
13784 major: major
13785 });
13786 }
13787
13788 return ticks;
13789 }
13790
13791 module.exports = function(Chart) {
13792
13793 var defaultConfig = {
13794 position: 'bottom',
13795
13796 /**
13797 * Data distribution along the scale:
13798 * - 'linear': data are spread according to their time (distances can vary),
13799 * - 'series': data are spread at the same distance from each other.
13800 * @see https://github.com/chartjs/Chart.js/pull/4507
13801 * @since 2.7.0
13802 */
13803 distribution: 'linear',
13804
13805 /**
13806 * Scale boundary strategy (bypassed by min/max time options)
13807 * - `data`: make sure data are fully visible, ticks outside are removed
13808 * - `ticks`: make sure ticks are fully visible, data outside are truncated
13809 * @see https://github.com/chartjs/Chart.js/pull/4556
13810 * @since 2.7.0
13811 */
13812 bounds: 'data',
13813
13814 time: {
13815 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
13816 format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
13817 unit: false, // false == automatic or override with week, month, year, etc.
13818 round: false, // none, or override with week, month, year, etc.
13819 displayFormat: false, // DEPRECATED
13820 isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
13821 minUnit: 'millisecond',
13822
13823 // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
13824 displayFormats: {
13825 millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
13826 second: 'h:mm:ss a', // 11:20:01 AM
13827 minute: 'h:mm a', // 11:20 AM
13828 hour: 'hA', // 5PM
13829 day: 'MMM D', // Sep 4
13830 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
13831 month: 'MMM YYYY', // Sept 2015
13832 quarter: '[Q]Q - YYYY', // Q3
13833 year: 'YYYY' // 2015
13834 },
13835 },
13836 ticks: {
13837 autoSkip: false,
13838
13839 /**
13840 * Ticks generation input values:
13841 * - 'auto': generates "optimal" ticks based on scale size and time options.
13842 * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
13843 * - 'labels': generates ticks from user given `data.labels` values ONLY.
13844 * @see https://github.com/chartjs/Chart.js/pull/4507
13845 * @since 2.7.0
13846 */
13847 source: 'auto',
13848
13849 major: {
13850 enabled: false
13851 }
13852 }
13853 };
13854
13855 var TimeScale = Chart.Scale.extend({
13856 initialize: function() {
13857 if (!moment) {
13858 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');
13859 }
13860
13861 this.mergeTicksOptions();
13862
13863 Chart.Scale.prototype.initialize.call(this);
13864 },
13865
13866 update: function() {
13867 var me = this;
13868 var options = me.options;
13869
13870 // DEPRECATIONS: output a message only one time per update
13871 if (options.time && options.time.format) {
13872 console.warn('options.time.format is deprecated and replaced by options.time.parser.');
13873 }
13874
13875 return Chart.Scale.prototype.update.apply(me, arguments);
13876 },
13877
13878 /**
13879 * Allows data to be referenced via 't' attribute
13880 */
13881 getRightValue: function(rawValue) {
13882 if (rawValue && rawValue.t !== undefined) {
13883 rawValue = rawValue.t;
13884 }
13885 return Chart.Scale.prototype.getRightValue.call(this, rawValue);
13886 },
13887
13888 determineDataLimits: function() {
13889 var me = this;
13890 var chart = me.chart;
13891 var timeOpts = me.options.time;
13892 var min = MAX_INTEGER;
13893 var max = MIN_INTEGER;
13894 var timestamps = [];
13895 var datasets = [];
13896 var labels = [];
13897 var i, j, ilen, jlen, data, timestamp;
13898
13899 // Convert labels to timestamps
13900 for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) {
13901 labels.push(parse(chart.data.labels[i], me));
13902 }
13903
13904 // Convert data to timestamps
13905 for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
13906 if (chart.isDatasetVisible(i)) {
13907 data = chart.data.datasets[i].data;
13908
13909 // Let's consider that all data have the same format.
13910 if (helpers.isObject(data[0])) {
13911 datasets[i] = [];
13912
13913 for (j = 0, jlen = data.length; j < jlen; ++j) {
13914 timestamp = parse(data[j], me);
13915 timestamps.push(timestamp);
13916 datasets[i][j] = timestamp;
13917 }
13918 } else {
13919 timestamps.push.apply(timestamps, labels);
13920 datasets[i] = labels.slice(0);
13921 }
13922 } else {
13923 datasets[i] = [];
13924 }
13925 }
13926
13927 if (labels.length) {
13928 // Sort labels **after** data have been converted
13929 labels = arrayUnique(labels).sort(sorter);
13930 min = Math.min(min, labels[0]);
13931 max = Math.max(max, labels[labels.length - 1]);
13932 }
13933
13934 if (timestamps.length) {
13935 timestamps = arrayUnique(timestamps).sort(sorter);
13936 min = Math.min(min, timestamps[0]);
13937 max = Math.max(max, timestamps[timestamps.length - 1]);
13938 }
13939
13940 min = parse(timeOpts.min, me) || min;
13941 max = parse(timeOpts.max, me) || max;
13942
13943 // In case there is no valid min/max, let's use today limits
13944 min = min === MAX_INTEGER ? +moment().startOf('day') : min;
13945 max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max;
13946
13947 // Make sure that max is strictly higher than min (required by the lookup table)
13948 me.min = Math.min(min, max);
13949 me.max = Math.max(min + 1, max);
13950
13951 // PRIVATE
13952 me._horizontal = me.isHorizontal();
13953 me._table = [];
13954 me._timestamps = {
13955 data: timestamps,
13956 datasets: datasets,
13957 labels: labels
13958 };
13959 },
13960
13961 buildTicks: function() {
13962 var me = this;
13963 var min = me.min;
13964 var max = me.max;
13965 var options = me.options;
13966 var timeOpts = options.time;
13967 var timestamps = [];
13968 var ticks = [];
13969 var i, ilen, timestamp;
13970
13971 switch (options.ticks.source) {
13972 case 'data':
13973 timestamps = me._timestamps.data;
13974 break;
13975 case 'labels':
13976 timestamps = me._timestamps.labels;
13977 break;
13978 case 'auto':
13979 default:
13980 timestamps = generate(min, max, me.getLabelCapacity(min), options);
13981 }
13982
13983 if (options.bounds === 'ticks' && timestamps.length) {
13984 min = timestamps[0];
13985 max = timestamps[timestamps.length - 1];
13986 }
13987
13988 // Enforce limits with user min/max options
13989 min = parse(timeOpts.min, me) || min;
13990 max = parse(timeOpts.max, me) || max;
13991
13992 // Remove ticks outside the min/max range
13993 for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13994 timestamp = timestamps[i];
13995 if (timestamp >= min && timestamp <= max) {
13996 ticks.push(timestamp);
13997 }
13998 }
13999
14000 me.min = min;
14001 me.max = max;
14002
14003 // PRIVATE
14004 me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max);
14005 me._majorUnit = determineMajorUnit(me._unit);
14006 me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
14007 me._offsets = computeOffsets(me._table, ticks, min, max, options);
14008
14009 return ticksFromTimestamps(ticks, me._majorUnit);
14010 },
14011
14012 getLabelForIndex: function(index, datasetIndex) {
14013 var me = this;
14014 var data = me.chart.data;
14015 var timeOpts = me.options.time;
14016 var label = data.labels && index < data.labels.length ? data.labels[index] : '';
14017 var value = data.datasets[datasetIndex].data[index];
14018
14019 if (helpers.isObject(value)) {
14020 label = me.getRightValue(value);
14021 }
14022 if (timeOpts.tooltipFormat) {
14023 label = momentify(label, timeOpts).format(timeOpts.tooltipFormat);
14024 }
14025
14026 return label;
14027 },
14028
14029 /**
14030 * Function to format an individual tick mark
14031 * @private
14032 */
14033 tickFormatFunction: function(tick, index, ticks, formatOverride) {
14034 var me = this;
14035 var options = me.options;
14036 var time = tick.valueOf();
14037 var formats = options.time.displayFormats;
14038 var minorFormat = formats[me._unit];
14039 var majorUnit = me._majorUnit;
14040 var majorFormat = formats[majorUnit];
14041 var majorTime = tick.clone().startOf(majorUnit).valueOf();
14042 var majorTickOpts = options.ticks.major;
14043 var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
14044 var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat);
14045 var tickOpts = major ? majorTickOpts : options.ticks.minor;
14046 var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback);
14047
14048 return formatter ? formatter(label, index, ticks) : label;
14049 },
14050
14051 convertTicksToLabels: function(ticks) {
14052 var labels = [];
14053 var i, ilen;
14054
14055 for (i = 0, ilen = ticks.length; i < ilen; ++i) {
14056 labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
14057 }
14058
14059 return labels;
14060 },
14061
14062 /**
14063 * @private
14064 */
14065 getPixelForOffset: function(time) {
14066 var me = this;
14067 var size = me._horizontal ? me.width : me.height;
14068 var start = me._horizontal ? me.left : me.top;
14069 var pos = interpolate(me._table, 'time', time, 'pos');
14070
14071 return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
14072 },
14073
14074 getPixelForValue: function(value, index, datasetIndex) {
14075 var me = this;
14076 var time = null;
14077
14078 if (index !== undefined && datasetIndex !== undefined) {
14079 time = me._timestamps.datasets[datasetIndex][index];
14080 }
14081
14082 if (time === null) {
14083 time = parse(value, me);
14084 }
14085
14086 if (time !== null) {
14087 return me.getPixelForOffset(time);
14088 }
14089 },
14090
14091 getPixelForTick: function(index) {
14092 var ticks = this.getTicks();
14093 return index >= 0 && index < ticks.length ?
14094 this.getPixelForOffset(ticks[index].value) :
14095 null;
14096 },
14097
14098 getValueForPixel: function(pixel) {
14099 var me = this;
14100 var size = me._horizontal ? me.width : me.height;
14101 var start = me._horizontal ? me.left : me.top;
14102 var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
14103 var time = interpolate(me._table, 'pos', pos, 'time');
14104
14105 return moment(time);
14106 },
14107
14108 /**
14109 * Crude approximation of what the label width might be
14110 * @private
14111 */
14112 getLabelWidth: function(label) {
14113 var me = this;
14114 var ticksOpts = me.options.ticks;
14115 var tickLabelWidth = me.ctx.measureText(label).width;
14116 var angle = helpers.toRadians(ticksOpts.maxRotation);
14117 var cosRotation = Math.cos(angle);
14118 var sinRotation = Math.sin(angle);
14119 var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
14120
14121 return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
14122 },
14123
14124 /**
14125 * @private
14126 */
14127 getLabelCapacity: function(exampleTime) {
14128 var me = this;
14129
14130 var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
14131
14132 var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride);
14133 var tickLabelWidth = me.getLabelWidth(exampleLabel);
14134 var innerWidth = me.isHorizontal() ? me.width : me.height;
14135
14136 return Math.floor(innerWidth / tickLabelWidth);
14137 }
14138 });
14139
14140 Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
14141 };
14142
14143 },{"1":1,"25":25,"45":45}]},{},[7])(7)
14144 });
14145