diff --git a/resources/assets/js/plugins/chartjs/Chart.js b/resources/assets/js/plugins/chartjs/Chart.js index cd79b6df8..589124e06 100755 --- a/resources/assets/js/plugins/chartjs/Chart.js +++ b/resources/assets/js/plugins/chartjs/Chart.js @@ -1,3477 +1,12269 @@ /*! * Chart.js * http://chartjs.org/ - * Version: 1.0.2 + * Version: 2.5.0 * - * Copyright 2015 Nick Downie + * Copyright 2017 Nick Downie * Released under the MIT license - * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md + * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md */ +(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 lum2) { + return (lum1 + 0.05) / (lum2 + 0.05); + } + return (lum2 + 0.05) / (lum1 + 0.05); + }, + + level: function (color2) { + var contrastRatio = this.contrast(color2); + if (contrastRatio >= 7.1) { + return 'AAA'; + } + + return (contrastRatio >= 4.5) ? 'AA' : ''; + }, + + dark: function () { + // YIQ equation from http://24ways.org/2010/calculating-color-contrast + var rgb = this.values.rgb; + var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; + return yiq < 128; + }, + + light: function () { + return !this.dark(); + }, + + negate: function () { + var rgb = []; + for (var i = 0; i < 3; i++) { + rgb[i] = 255 - this.values.rgb[i]; + } + this.setValues('rgb', rgb); + return this; + }, + + lighten: function (ratio) { + var hsl = this.values.hsl; + hsl[2] += hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + darken: function (ratio) { + var hsl = this.values.hsl; + hsl[2] -= hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + saturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] += hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + desaturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] -= hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + whiten: function (ratio) { + var hwb = this.values.hwb; + hwb[1] += hwb[1] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + blacken: function (ratio) { + var hwb = this.values.hwb; + hwb[2] += hwb[2] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + greyscale: function () { + var rgb = this.values.rgb; + // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; + this.setValues('rgb', [val, val, val]); + return this; + }, + + clearer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha - (alpha * ratio)); + return this; + }, + + opaquer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha + (alpha * ratio)); + return this; + }, + + rotate: function (degrees) { + var hsl = this.values.hsl; + var hue = (hsl[0] + degrees) % 360; + hsl[0] = hue < 0 ? 360 + hue : hue; + this.setValues('hsl', hsl); + return this; + }, + + /** + * Ported from sass implementation in C + * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 + */ + mix: function (mixinColor, weight) { + var color1 = this; + var color2 = mixinColor; + var p = weight === undefined ? 0.5 : weight; + + var w = 2 * p - 1; + var a = color1.alpha() - color2.alpha(); + + var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + return this + .rgb( + w1 * color1.red() + w2 * color2.red(), + w1 * color1.green() + w2 * color2.green(), + w1 * color1.blue() + w2 * color2.blue() + ) + .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); + }, + + toJSON: function () { + return this.rgb(); + }, + + clone: function () { + // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, + // making the final build way to big to embed in Chart.js. So let's do it manually, + // assuming that values to clone are 1 dimension arrays containing only numbers, + // except 'alpha' which is a number. + var result = new Color(); + var source = this.values; + var target = result.values; + var value, type; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + value = source[prop]; + type = ({}).toString.call(value); + if (type === '[object Array]') { + target[prop] = value.slice(0); + } else if (type === '[object Number]') { + target[prop] = value; + } else { + console.error('unexpected color value:', value); + } + } + } + + return result; + } + }; + + Color.prototype.spaces = { + rgb: ['red', 'green', 'blue'], + hsl: ['hue', 'saturation', 'lightness'], + hsv: ['hue', 'saturation', 'value'], + hwb: ['hue', 'whiteness', 'blackness'], + cmyk: ['cyan', 'magenta', 'yellow', 'black'] + }; + + Color.prototype.maxes = { + rgb: [255, 255, 255], + hsl: [360, 100, 100], + hsv: [360, 100, 100], + hwb: [360, 100, 100], + cmyk: [100, 100, 100, 100] + }; + + Color.prototype.getValues = function (space) { + var values = this.values; + var vals = {}; + + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = values[space][i]; + } + + if (values.alpha !== 1) { + vals.a = values.alpha; + } + + // {r: 255, g: 255, b: 255, a: 0.4} + return vals; + }; + + Color.prototype.setValues = function (space, vals) { + var values = this.values; + var spaces = this.spaces; + var maxes = this.maxes; + var alpha = 1; + var i; + + if (space === 'alpha') { + alpha = vals; + } else if (vals.length) { + // [10, 10, 10] + values[space] = vals.slice(0, space.length); + alpha = vals[space.length]; + } else if (vals[space.charAt(0)] !== undefined) { + // {r: 10, g: 10, b: 10} + for (i = 0; i < space.length; i++) { + values[space][i] = vals[space.charAt(i)]; + } + + alpha = vals.a; + } else if (vals[spaces[space][0]] !== undefined) { + // {red: 10, green: 10, blue: 10} + var chans = spaces[space]; + + for (i = 0; i < space.length; i++) { + values[space][i] = vals[chans[i]]; + } + + alpha = vals.alpha; + } + + values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); + + if (space === 'alpha') { + return false; + } + + var capped; + + // cap values of the space prior converting all values + for (i = 0; i < space.length; i++) { + capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); + values[space][i] = Math.round(capped); + } + + // convert to all the other color spaces + for (var sname in spaces) { + if (sname !== space) { + values[sname] = convert[space][sname](values[space]); + } + } + + return true; + }; + + Color.prototype.setSpace = function (space, args) { + var vals = args[0]; + + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } + + // color.rgb(10, 10, 10) + if (typeof vals === 'number') { + vals = Array.prototype.slice.call(args); + } + + this.setValues(space, vals); + return this; + }; + + Color.prototype.setChannel = function (space, index, val) { + var svalues = this.values[space]; + if (val === undefined) { + // color.red() + return svalues[index]; + } else if (val === svalues[index]) { + // color.red(color.red()) + return this; + } + + // color.red(100) + svalues[index] = val; + this.setValues(space, svalues); + + return this; + }; + + if (typeof window !== 'undefined') { + window.Color = Color; + } + + module.exports = Color; + + },{"2":2,"5":5}],4:[function(require,module,exports){ + /* MIT license */ + + module.exports = { + rgb2hsl: rgb2hsl, + rgb2hsv: rgb2hsv, + rgb2hwb: rgb2hwb, + rgb2cmyk: rgb2cmyk, + rgb2keyword: rgb2keyword, + rgb2xyz: rgb2xyz, + rgb2lab: rgb2lab, + rgb2lch: rgb2lch, + + hsl2rgb: hsl2rgb, + hsl2hsv: hsl2hsv, + hsl2hwb: hsl2hwb, + hsl2cmyk: hsl2cmyk, + hsl2keyword: hsl2keyword, + + hsv2rgb: hsv2rgb, + hsv2hsl: hsv2hsl, + hsv2hwb: hsv2hwb, + hsv2cmyk: hsv2cmyk, + hsv2keyword: hsv2keyword, + + hwb2rgb: hwb2rgb, + hwb2hsl: hwb2hsl, + hwb2hsv: hwb2hsv, + hwb2cmyk: hwb2cmyk, + hwb2keyword: hwb2keyword, + + cmyk2rgb: cmyk2rgb, + cmyk2hsl: cmyk2hsl, + cmyk2hsv: cmyk2hsv, + cmyk2hwb: cmyk2hwb, + cmyk2keyword: cmyk2keyword, + + keyword2rgb: keyword2rgb, + keyword2hsl: keyword2hsl, + keyword2hsv: keyword2hsv, + keyword2hwb: keyword2hwb, + keyword2cmyk: keyword2cmyk, + keyword2lab: keyword2lab, + keyword2xyz: keyword2xyz, + + xyz2rgb: xyz2rgb, + xyz2lab: xyz2lab, + xyz2lch: xyz2lch, + + lab2xyz: lab2xyz, + lab2rgb: lab2rgb, + lab2lch: lab2lch, + + lch2lab: lch2lab, + lch2xyz: lch2xyz, + lch2rgb: lch2rgb + } + + + function rgb2hsl(rgb) { + var r = rgb[0]/255, + g = rgb[1]/255, + b = rgb[2]/255, + min = Math.min(r, g, b), + max = Math.max(r, g, b), + delta = max - min, + h, s, l; + + if (max == min) + h = 0; + else if (r == max) + h = (g - b) / delta; + else if (g == max) + h = 2 + (b - r) / delta; + else if (b == max) + h = 4 + (r - g)/ delta; + + h = Math.min(h * 60, 360); + + if (h < 0) + h += 360; + + l = (min + max) / 2; + + if (max == min) + s = 0; + else if (l <= 0.5) + s = delta / (max + min); + else + s = delta / (2 - max - min); + + return [h, s * 100, l * 100]; + } + + function rgb2hsv(rgb) { + var r = rgb[0], + g = rgb[1], + b = rgb[2], + min = Math.min(r, g, b), + max = Math.max(r, g, b), + delta = max - min, + h, s, v; + + if (max == 0) + s = 0; + else + s = (delta/max * 1000)/10; + + if (max == min) + h = 0; + else if (r == max) + h = (g - b) / delta; + else if (g == max) + h = 2 + (b - r) / delta; + else if (b == max) + h = 4 + (r - g) / delta; + + h = Math.min(h * 60, 360); + + if (h < 0) + h += 360; + + v = ((max / 255) * 1000) / 10; + + return [h, s, v]; + } + + function rgb2hwb(rgb) { + var r = rgb[0], + g = rgb[1], + b = rgb[2], + h = rgb2hsl(rgb)[0], + w = 1/255 * Math.min(r, Math.min(g, b)), + b = 1 - 1/255 * Math.max(r, Math.max(g, b)); + + return [h, w * 100, b * 100]; + } + + function rgb2cmyk(rgb) { + var r = rgb[0] / 255, + g = rgb[1] / 255, + b = rgb[2] / 255, + c, m, y, k; + + k = Math.min(1 - r, 1 - g, 1 - b); + c = (1 - r - k) / (1 - k) || 0; + m = (1 - g - k) / (1 - k) || 0; + y = (1 - b - k) / (1 - k) || 0; + return [c * 100, m * 100, y * 100, k * 100]; + } + + function rgb2keyword(rgb) { + return reverseKeywords[JSON.stringify(rgb)]; + } + + function rgb2xyz(rgb) { + var r = rgb[0] / 255, + g = rgb[1] / 255, + b = rgb[2] / 255; + + // assume sRGB + r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); + + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + return [x * 100, y *100, z * 100]; + } + + function rgb2lab(rgb) { + var xyz = rgb2xyz(rgb), + x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; + } + + function rgb2lch(args) { + return lab2lch(rgb2lab(args)); + } + + function hsl2rgb(hsl) { + var h = hsl[0] / 360, + s = hsl[1] / 100, + l = hsl[2] / 100, + t1, t2, t3, rgb, val; + + if (s == 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) + t2 = l * (1 + s); + else + t2 = l + s - l * s; + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * - (i - 1); + t3 < 0 && t3++; + t3 > 1 && t3--; + + if (6 * t3 < 1) + val = t1 + (t2 - t1) * 6 * t3; + else if (2 * t3 < 1) + val = t2; + else if (3 * t3 < 2) + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + else + val = t1; + + rgb[i] = val * 255; + } + + return rgb; + } + + function hsl2hsv(hsl) { + var h = hsl[0], + s = hsl[1] / 100, + l = hsl[2] / 100, + sv, v; + + if(l === 0) { + // no need to do calc on black + // also avoids divide by 0 error + return [0, 0, 0]; + } + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + v = (l + s) / 2; + sv = (2 * s) / (l + s); + return [h, sv * 100, v * 100]; + } + + function hsl2hwb(args) { + return rgb2hwb(hsl2rgb(args)); + } + + function hsl2cmyk(args) { + return rgb2cmyk(hsl2rgb(args)); + } + + function hsl2keyword(args) { + return rgb2keyword(hsl2rgb(args)); + } + + + function hsv2rgb(hsv) { + var h = hsv[0] / 60, + s = hsv[1] / 100, + v = hsv[2] / 100, + hi = Math.floor(h) % 6; + + var f = h - Math.floor(h), + p = 255 * v * (1 - s), + q = 255 * v * (1 - (s * f)), + t = 255 * v * (1 - (s * (1 - f))), + v = 255 * v; + + switch(hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } + } + + function hsv2hsl(hsv) { + var h = hsv[0], + s = hsv[1] / 100, + v = hsv[2] / 100, + sl, l; + + l = (2 - s) * v; + sl = s * v; + sl /= (l <= 1) ? l : 2 - l; + sl = sl || 0; + l /= 2; + return [h, sl * 100, l * 100]; + } + + function hsv2hwb(args) { + return rgb2hwb(hsv2rgb(args)) + } + + function hsv2cmyk(args) { + return rgb2cmyk(hsv2rgb(args)); + } + + function hsv2keyword(args) { + return rgb2keyword(hsv2rgb(args)); + } + +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb + function hwb2rgb(hwb) { + var h = hwb[0] / 360, + wh = hwb[1] / 100, + bl = hwb[2] / 100, + ratio = wh + bl, + i, v, f, n; + + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + if ((i & 0x01) != 0) { + f = 1 - f; + } + n = wh + f * (v - wh); // linear interpolation + + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; + } + + function hwb2hsl(args) { + return rgb2hsl(hwb2rgb(args)); + } + + function hwb2hsv(args) { + return rgb2hsv(hwb2rgb(args)); + } + + function hwb2cmyk(args) { + return rgb2cmyk(hwb2rgb(args)); + } + + function hwb2keyword(args) { + return rgb2keyword(hwb2rgb(args)); + } + + function cmyk2rgb(cmyk) { + var c = cmyk[0] / 100, + m = cmyk[1] / 100, + y = cmyk[2] / 100, + k = cmyk[3] / 100, + r, g, b; + + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); + return [r * 255, g * 255, b * 255]; + } + + function cmyk2hsl(args) { + return rgb2hsl(cmyk2rgb(args)); + } + + function cmyk2hsv(args) { + return rgb2hsv(cmyk2rgb(args)); + } + + function cmyk2hwb(args) { + return rgb2hwb(cmyk2rgb(args)); + } + + function cmyk2keyword(args) { + return rgb2keyword(cmyk2rgb(args)); + } + + + function xyz2rgb(xyz) { + var x = xyz[0] / 100, + y = xyz[1] / 100, + z = xyz[2] / 100, + r, g, b; + + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + // assume sRGB + r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r = (r * 12.92); + + g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g = (g * 12.92); + + b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b = (b * 12.92); + + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + + return [r * 255, g * 255, b * 255]; + } + + function xyz2lab(xyz) { + var x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; + } + + function xyz2lch(args) { + return lab2lch(xyz2lab(args)); + } + + function lab2xyz(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + x, y, z, y2; + + if (l <= 8) { + y = (l * 100) / 903.3; + y2 = (7.787 * (y / 100)) + (16 / 116); + } else { + y = 100 * Math.pow((l + 16) / 116, 3); + y2 = Math.pow(y / 100, 1/3); + } + + x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); + + z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); + + return [x, y, z]; + } + + function lab2lch(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + hr, h, c; + + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + if (h < 0) { + h += 360; + } + c = Math.sqrt(a * a + b * b); + return [l, c, h]; + } + + function lab2rgb(args) { + return xyz2rgb(lab2xyz(args)); + } + + function lch2lab(lch) { + var l = lch[0], + c = lch[1], + h = lch[2], + a, b, hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + return [l, a, b]; + } + + function lch2xyz(args) { + return lab2xyz(lch2lab(args)); + } + + function lch2rgb(args) { + return lab2rgb(lch2lab(args)); + } + + function keyword2rgb(keyword) { + return cssKeywords[keyword]; + } + + function keyword2hsl(args) { + return rgb2hsl(keyword2rgb(args)); + } + + function keyword2hsv(args) { + return rgb2hsv(keyword2rgb(args)); + } + + function keyword2hwb(args) { + return rgb2hwb(keyword2rgb(args)); + } + + function keyword2cmyk(args) { + return rgb2cmyk(keyword2rgb(args)); + } + + function keyword2lab(args) { + return rgb2lab(keyword2rgb(args)); + } + + function keyword2xyz(args) { + return rgb2xyz(keyword2rgb(args)); + } + + var cssKeywords = { + aliceblue: [240,248,255], + antiquewhite: [250,235,215], + aqua: [0,255,255], + aquamarine: [127,255,212], + azure: [240,255,255], + beige: [245,245,220], + bisque: [255,228,196], + black: [0,0,0], + blanchedalmond: [255,235,205], + blue: [0,0,255], + blueviolet: [138,43,226], + brown: [165,42,42], + burlywood: [222,184,135], + cadetblue: [95,158,160], + chartreuse: [127,255,0], + chocolate: [210,105,30], + coral: [255,127,80], + cornflowerblue: [100,149,237], + cornsilk: [255,248,220], + crimson: [220,20,60], + cyan: [0,255,255], + darkblue: [0,0,139], + darkcyan: [0,139,139], + darkgoldenrod: [184,134,11], + darkgray: [169,169,169], + darkgreen: [0,100,0], + darkgrey: [169,169,169], + darkkhaki: [189,183,107], + darkmagenta: [139,0,139], + darkolivegreen: [85,107,47], + darkorange: [255,140,0], + darkorchid: [153,50,204], + darkred: [139,0,0], + darksalmon: [233,150,122], + darkseagreen: [143,188,143], + darkslateblue: [72,61,139], + darkslategray: [47,79,79], + darkslategrey: [47,79,79], + darkturquoise: [0,206,209], + darkviolet: [148,0,211], + deeppink: [255,20,147], + deepskyblue: [0,191,255], + dimgray: [105,105,105], + dimgrey: [105,105,105], + dodgerblue: [30,144,255], + firebrick: [178,34,34], + floralwhite: [255,250,240], + forestgreen: [34,139,34], + fuchsia: [255,0,255], + gainsboro: [220,220,220], + ghostwhite: [248,248,255], + gold: [255,215,0], + goldenrod: [218,165,32], + gray: [128,128,128], + green: [0,128,0], + greenyellow: [173,255,47], + grey: [128,128,128], + honeydew: [240,255,240], + hotpink: [255,105,180], + indianred: [205,92,92], + indigo: [75,0,130], + ivory: [255,255,240], + khaki: [240,230,140], + lavender: [230,230,250], + lavenderblush: [255,240,245], + lawngreen: [124,252,0], + lemonchiffon: [255,250,205], + lightblue: [173,216,230], + lightcoral: [240,128,128], + lightcyan: [224,255,255], + lightgoldenrodyellow: [250,250,210], + lightgray: [211,211,211], + lightgreen: [144,238,144], + lightgrey: [211,211,211], + lightpink: [255,182,193], + lightsalmon: [255,160,122], + lightseagreen: [32,178,170], + lightskyblue: [135,206,250], + lightslategray: [119,136,153], + lightslategrey: [119,136,153], + lightsteelblue: [176,196,222], + lightyellow: [255,255,224], + lime: [0,255,0], + limegreen: [50,205,50], + linen: [250,240,230], + magenta: [255,0,255], + maroon: [128,0,0], + mediumaquamarine: [102,205,170], + mediumblue: [0,0,205], + mediumorchid: [186,85,211], + mediumpurple: [147,112,219], + mediumseagreen: [60,179,113], + mediumslateblue: [123,104,238], + mediumspringgreen: [0,250,154], + mediumturquoise: [72,209,204], + mediumvioletred: [199,21,133], + midnightblue: [25,25,112], + mintcream: [245,255,250], + mistyrose: [255,228,225], + moccasin: [255,228,181], + navajowhite: [255,222,173], + navy: [0,0,128], + oldlace: [253,245,230], + olive: [128,128,0], + olivedrab: [107,142,35], + orange: [255,165,0], + orangered: [255,69,0], + orchid: [218,112,214], + palegoldenrod: [238,232,170], + palegreen: [152,251,152], + paleturquoise: [175,238,238], + palevioletred: [219,112,147], + papayawhip: [255,239,213], + peachpuff: [255,218,185], + peru: [205,133,63], + pink: [255,192,203], + plum: [221,160,221], + powderblue: [176,224,230], + purple: [128,0,128], + rebeccapurple: [102, 51, 153], + red: [255,0,0], + rosybrown: [188,143,143], + royalblue: [65,105,225], + saddlebrown: [139,69,19], + salmon: [250,128,114], + sandybrown: [244,164,96], + seagreen: [46,139,87], + seashell: [255,245,238], + sienna: [160,82,45], + silver: [192,192,192], + skyblue: [135,206,235], + slateblue: [106,90,205], + slategray: [112,128,144], + slategrey: [112,128,144], + snow: [255,250,250], + springgreen: [0,255,127], + steelblue: [70,130,180], + tan: [210,180,140], + teal: [0,128,128], + thistle: [216,191,216], + tomato: [255,99,71], + turquoise: [64,224,208], + violet: [238,130,238], + wheat: [245,222,179], + white: [255,255,255], + whitesmoke: [245,245,245], + yellow: [255,255,0], + yellowgreen: [154,205,50] + }; + + var reverseKeywords = {}; + for (var key in cssKeywords) { + reverseKeywords[JSON.stringify(cssKeywords[key])] = key; + } + + },{}],5:[function(require,module,exports){ + var conversions = require(4); + + var convert = function() { + return new Converter(); + } + + for (var func in conversions) { + // export Raw versions + convert[func + "Raw"] = (function(func) { + // accept array or plain args + return function(arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + return conversions[func](arg); + } + })(func); + + var pair = /(\w+)2(\w+)/.exec(func), + from = pair[1], + to = pair[2]; + + // export rgb2hsl and ["rgb"]["hsl"] + convert[from] = convert[from] || {}; + + convert[from][to] = convert[func] = (function(func) { + return function(arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + + var val = conversions[func](arg); + if (typeof val == "string" || val === undefined) + return val; // keyword + + for (var i = 0; i < val.length; i++) + val[i] = Math.round(val[i]); + return val; + } + })(func); + } + + + /* Converter does lazy conversion and caching */ + var Converter = function() { + this.convs = {}; + }; + + /* Either get the values for a space or + set the values for a space, depending on args */ + Converter.prototype.routeSpace = function(space, args) { + var values = args[0]; + if (values === undefined) { + // color.rgb() + return this.getValues(space); + } + // color.rgb(10, 10, 10) + if (typeof values == "number") { + values = Array.prototype.slice.call(args); + } + + return this.setValues(space, values); + }; + + /* Set the values for a space, invalidating cache */ + Converter.prototype.setValues = function(space, values) { + this.space = space; + this.convs = {}; + this.convs[space] = values; + return this; + }; + + /* Get the values for a space. If there's already + a conversion for the space, fetch it, otherwise + compute it */ + Converter.prototype.getValues = function(space) { + var vals = this.convs[space]; + if (!vals) { + var fspace = this.space, + from = this.convs[fspace]; + vals = convert[fspace][space](from); + + this.convs[space] = vals; + } + return vals; + }; + + ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { + Converter.prototype[space] = function(vals) { + return this.routeSpace(space, arguments); + } + }); + + module.exports = convert; + },{"4":4}],6:[function(require,module,exports){ + module.exports = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] + }; + },{}],7:[function(require,module,exports){ + /** + * @namespace Chart + */ + var Chart = require(28)(); + + require(26)(Chart); + require(42)(Chart); + require(22)(Chart); + require(31)(Chart); + require(25)(Chart); + require(21)(Chart); + require(23)(Chart); + require(24)(Chart); + require(29)(Chart); + require(33)(Chart); + require(34)(Chart); + require(32)(Chart); + require(35)(Chart); + require(30)(Chart); + require(27)(Chart); + require(36)(Chart); + + require(37)(Chart); + require(38)(Chart); + require(39)(Chart); + require(40)(Chart); + + require(45)(Chart); + require(43)(Chart); + require(44)(Chart); + require(46)(Chart); + require(47)(Chart); + require(48)(Chart); + +// Controllers must be loaded after elements +// See Chart.core.datasetController.dataElementType + require(15)(Chart); + require(16)(Chart); + require(17)(Chart); + require(18)(Chart); + require(19)(Chart); + require(20)(Chart); + + require(8)(Chart); + require(9)(Chart); + require(10)(Chart); + require(11)(Chart); + require(12)(Chart); + require(13)(Chart); + require(14)(Chart); + + window.Chart = module.exports = Chart; + + },{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"8":8,"9":9}],8:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + Chart.Bar = function(context, config) { + config.type = 'bar'; + + return new Chart(context, config); + }; + + }; + + },{}],9:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + Chart.Bubble = function(context, config) { + config.type = 'bubble'; + return new Chart(context, config); + }; + + }; + + },{}],10:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + Chart.Doughnut = function(context, config) { + config.type = 'doughnut'; + + return new Chart(context, config); + }; + + }; + + },{}],11:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + Chart.Line = function(context, config) { + config.type = 'line'; -(function(){ + return new Chart(context, config); + }; - "use strict"; + }; - //Declare root variable - window in the browser, global on the server - var root = this, - previous = root.Chart; + },{}],12:[function(require,module,exports){ + 'use strict'; - //Occupy the global variable of Chart, and create a simple base class - var Chart = function(context){ - var chart = this; - this.canvas = context.canvas; + module.exports = function(Chart) { - this.ctx = context; + Chart.PolarArea = function(context, config) { + config.type = 'polarArea'; - //Variables global to the chart - var computeDimension = function(element,dimension) - { - if (element['offset'+dimension]) - { - return element['offset'+dimension]; - } - else - { - return document.defaultView.getComputedStyle(element).getPropertyValue(dimension); - } - } + return new Chart(context, config); + }; - var width = this.width = computeDimension(context.canvas,'Width'); - var height = this.height = computeDimension(context.canvas,'Height'); + }; - // Firefox requires this to work correctly - context.canvas.width = width; - context.canvas.height = height; + },{}],13:[function(require,module,exports){ + 'use strict'; - var width = this.width = context.canvas.width; - var height = this.height = context.canvas.height; - this.aspectRatio = this.width / this.height; - //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. - helpers.retinaScale(this); + module.exports = function(Chart) { - return this; - }; - //Globally expose the defaults to allow for user updating/changing - Chart.defaults = { - global: { - // Boolean - Whether to animate the chart - animation: true, + Chart.Radar = function(context, config) { + config.type = 'radar'; - // Number - Number of animation steps - animationSteps: 60, + return new Chart(context, config); + }; - // String - Animation easing effect - animationEasing: "easeOutQuart", + }; - // Boolean - If we should show the scale at all - showScale: true, + },{}],14:[function(require,module,exports){ + 'use strict'; - // Boolean - If we want to override with a hard coded scale - scaleOverride: false, + module.exports = function(Chart) { - // ** Required if scaleOverride is true ** - // Number - The number of steps in a hard coded scale - scaleSteps: null, - // Number - The value jump in the hard coded scale - scaleStepWidth: null, - // Number - The scale starting value - scaleStartValue: null, + var defaultConfig = { + hover: { + mode: 'single' + }, - // String - Colour of the scale line - scaleLineColor: "rgba(0,0,0,.1)", + scales: { + xAxes: [{ + type: 'linear', // scatter should not use a category axis + position: 'bottom', + id: 'x-axis-1' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-1' + }] + }, - // Number - Pixel width of the scale line - scaleLineWidth: 1, + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(tooltipItem) { + return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')'; + } + } + } + }; - // Boolean - Whether to show labels on the scale - scaleShowLabels: true, + // Register the default config for this type + Chart.defaults.scatter = defaultConfig; - // Interpolated JS string - can access value - scaleLabel: "<%=value%>", + // Scatter charts use line controllers + Chart.controllers.scatter = Chart.controllers.line; + + Chart.Scatter = function(context, config) { + config.type = 'scatter'; + return new Chart(context, config); + }; - // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there - scaleIntegersOnly: true, - - // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value - scaleBeginAtZero: false, - - // String - Scale label font declaration for the scale label - scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - - // Number - Scale label font size in pixels - scaleFontSize: 12, - - // String - Scale label font weight style - scaleFontStyle: "normal", - - // String - Scale label font colour - scaleFontColor: "#666", - - // Boolean - whether or not the chart should be responsive and resize when the browser does. - responsive: false, - - // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container - maintainAspectRatio: true, - - // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove - showTooltips: true, - - // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function - customTooltips: false, - - // Array - Array of string names to attach tooltip events - tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"], - - // String - Tooltip background colour - tooltipFillColor: "rgba(0,0,0,0.8)", - - // String - Tooltip label font declaration for the scale label - tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - - // Number - Tooltip label font size in pixels - tooltipFontSize: 14, - - // String - Tooltip font weight style - tooltipFontStyle: "normal", - - // String - Tooltip label font colour - tooltipFontColor: "#fff", - - // String - Tooltip title font declaration for the scale label - tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - - // Number - Tooltip title font size in pixels - tooltipTitleFontSize: 14, - - // String - Tooltip title font weight style - tooltipTitleFontStyle: "bold", - - // String - Tooltip title font colour - tooltipTitleFontColor: "#fff", - - // Number - pixel width of padding around tooltip text - tooltipYPadding: 6, - - // Number - pixel width of padding around tooltip text - tooltipXPadding: 6, - - // Number - Size of the caret on the tooltip - tooltipCaretSize: 8, - - // Number - Pixel radius of the tooltip border - tooltipCornerRadius: 6, - - // Number - Pixel offset from point x to tooltip edge - tooltipXOffset: 10, - - // String - Template string for single tooltips - tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>", - - // String - Template string for single tooltips - multiTooltipTemplate: "<%= value %>", - - // String - Colour behind the legend colour block - multiTooltipKeyBackground: '#fff', - - // Function - Will fire on animation progression. - onAnimationProgress: function(){}, - - // Function - Will fire on animation completion. - onAnimationComplete: function(){} - - } - }; - - //Create a dictionary of chart types, to allow for extension of existing types - Chart.types = {}; - - //Global Chart helpers object for utility methods and classes - var helpers = Chart.helpers = {}; - - //-- Basic js utility methods - var each = helpers.each = function(loopable,callback,self){ - var additionalArgs = Array.prototype.slice.call(arguments, 3); - // Check to see if null or undefined firstly. - if (loopable){ - if (loopable.length === +loopable.length){ - var i; - for (i=0; i= 0; i--) { - var currentItem = arrayToSearch[i]; - if (filterCallback(currentItem)){ - return currentItem; - } - } - }, - inherits = helpers.inherits = function(extensions){ - //Basic javascript inheritance based on the model created in Backbone.js - var parent = this; - var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); }; - - var Surrogate = function(){ this.constructor = ChartElement;}; - Surrogate.prototype = parent.prototype; - ChartElement.prototype = new Surrogate(); - - ChartElement.extend = inherits; - - if (extensions) extend(ChartElement.prototype, extensions); - - ChartElement.__super__ = parent.prototype; - - return ChartElement; - }, - noop = helpers.noop = function(){}, - uid = helpers.uid = (function(){ - var id=0; - return function(){ - return "chart-" + id++; - }; - })(), - warn = helpers.warn = function(str){ - //Method for warning of errors - if (window.console && typeof window.console.warn == "function") console.warn(str); - }, - amd = helpers.amd = (typeof define == 'function' && define.amd), - //-- Math methods - isNumber = helpers.isNumber = function(n){ - return !isNaN(parseFloat(n)) && isFinite(n); - }, - max = helpers.max = function(array){ - return Math.max.apply( Math, array ); - }, - min = helpers.min = function(array){ - return Math.min.apply( Math, array ); - }, - cap = helpers.cap = function(valueToCap,maxValue,minValue){ - if(isNumber(maxValue)) { - if( valueToCap > maxValue ) { - return maxValue; - } - } - else if(isNumber(minValue)){ - if ( valueToCap < minValue ){ - return minValue; - } - } - return valueToCap; - }, - getDecimalPlaces = helpers.getDecimalPlaces = function(num){ - if (num%1!==0 && isNumber(num)){ - return num.toString().split(".")[1].length; - } - else { - return 0; - } - }, - toRadians = helpers.radians = function(degrees){ - return degrees * (Math.PI/180); - }, - // Gets the angle from vertical upright to the point about a centre. - getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){ - var distanceFromXCenter = anglePoint.x - centrePoint.x, - distanceFromYCenter = anglePoint.y - centrePoint.y, - radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); - - - var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter); - - //If the segment is in the top left quadrant, we need to add another rotation to the angle - if (distanceFromXCenter < 0 && distanceFromYCenter < 0){ - angle += Math.PI*2; - } - - return { - angle: angle, - distance: radialDistanceFromCenter - }; - }, - aliasPixel = helpers.aliasPixel = function(pixelWidth){ - return (pixelWidth % 2 === 0) ? 0 : 0.5; - }, - splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){ - //Props to Rob Spencer at scaled innovation for his post on splining between points - //http://scaledinnovation.com/analytics/splines/aboutSplines.html - var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)), - d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)), - fa=t*d01/(d01+d12),// scaling factor for triangle Ta - fb=t*d12/(d01+d12); - return { - inner : { - x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x), - y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y) - }, - outer : { - x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x), - y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y) - } - }; - }, - calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){ - return Math.floor(Math.log(val) / Math.LN10); - }, - calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){ - - //Set a minimum step of two - a point at the top of the graph, and a point at the base - var minSteps = 2, - maxSteps = Math.floor(drawingSize/(textSize * 1.5)), - skipFitting = (minSteps >= maxSteps); - - var maxValue = max(valuesArray), - minValue = min(valuesArray); - - // We need some degree of seperation here to calculate the scales if all the values are the same - // Adding/minusing 0.5 will give us a range of 1. - if (maxValue === minValue){ - maxValue += 0.5; - // So we don't end up with a graph with a negative start value if we've said always start from zero - if (minValue >= 0.5 && !startFromZero){ - minValue -= 0.5; - } - else{ - // Make up a whole number above the values - maxValue += 0.5; - } - } - - var valueRange = Math.abs(maxValue - minValue), - rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange), - graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), - graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), - graphRange = graphMax - graphMin, - stepValue = Math.pow(10, rangeOrderOfMagnitude), - numberOfSteps = Math.round(graphRange / stepValue); - - //If we have more space on the graph we'll use it to give more definition to the data - while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) { - if(numberOfSteps > maxSteps){ - stepValue *=2; - numberOfSteps = Math.round(graphRange/stepValue); - // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps. - if (numberOfSteps % 1 !== 0){ - skipFitting = true; - } - } - //We can fit in double the amount of scale points on the scale - else{ - //If user has declared ints only, and the step value isn't a decimal - if (integersOnly && rangeOrderOfMagnitude >= 0){ - //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float - if(stepValue/2 % 1 === 0){ - stepValue /=2; - numberOfSteps = Math.round(graphRange/stepValue); - } - //If it would make it a float break out of the loop - else{ - break; - } - } - //If the scale doesn't have to be an int, make the scale more granular anyway. - else{ - stepValue /=2; - numberOfSteps = Math.round(graphRange/stepValue); - } - - } - } - - if (skipFitting){ - numberOfSteps = minSteps; - stepValue = graphRange / numberOfSteps; - } - - return { - steps : numberOfSteps, - stepValue : stepValue, - min : graphMin, - max : graphMin + (numberOfSteps * stepValue) - }; - - }, - /* jshint ignore:start */ - // Blows up jshint errors based on the new Function constructor - //Templating methods - //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ - template = helpers.template = function(templateString, valuesObject){ - - // If templateString is function rather than string-template - call the function for valuesObject - - if(templateString instanceof Function){ - return templateString(valuesObject); - } - - var cache = {}; - function tmpl(str, data){ - // Figure out if we're getting a template, or if we need to - // load the template - and be sure to cache the result. - var fn = !/\W/.test(str) ? - cache[str] = cache[str] : - - // Generate a reusable function that will serve as a template - // generator (and which will be cached). - new Function("obj", - "var p=[],print=function(){p.push.apply(p,arguments);};" + - - // Introduce the data as local variables using with(){} - "with(obj){p.push('" + - - // Convert the template into pure JavaScript - str - .replace(/[\r\t\n]/g, " ") - .split("<%").join("\t") - .replace(/((^|%>)[^\t]*)'/g, "$1\r") - .replace(/\t=(.*?)%>/g, "',$1,'") - .split("\t").join("');") - .split("%>").join("p.push('") - .split("\r").join("\\'") + - "');}return p.join('');" - ); - - // Provide some basic currying to the user - return data ? fn( data ) : fn; - } - return tmpl(templateString,valuesObject); - }, - /* jshint ignore:end */ - generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){ - var labelsArray = new Array(numberOfSteps); - if (labelTemplateString){ - each(labelsArray,function(val,index){ - labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))}); - }); - } - return labelsArray; - }, - //--Animation methods - //Easing functions adapted from Robert Penner's easing equations - //http://www.robertpenner.com/easing/ - easingEffects = helpers.easingEffects = { - linear: function (t) { - return t; - }, - easeInQuad: function (t) { - return t * t; - }, - easeOutQuad: function (t) { - return -1 * t * (t - 2); - }, - easeInOutQuad: function (t) { - if ((t /= 1 / 2) < 1) return 1 / 2 * t * t; - return -1 / 2 * ((--t) * (t - 2) - 1); - }, - easeInCubic: function (t) { - return t * t * t; - }, - easeOutCubic: function (t) { - return 1 * ((t = t / 1 - 1) * t * t + 1); - }, - easeInOutCubic: function (t) { - if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t; - return 1 / 2 * ((t -= 2) * t * t + 2); - }, - easeInQuart: function (t) { - return t * t * t * t; - }, - easeOutQuart: function (t) { - return -1 * ((t = t / 1 - 1) * t * t * t - 1); - }, - easeInOutQuart: function (t) { - if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t; - return -1 / 2 * ((t -= 2) * t * t * t - 2); - }, - easeInQuint: function (t) { - return 1 * (t /= 1) * t * t * t * t; - }, - easeOutQuint: function (t) { - return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); - }, - easeInOutQuint: function (t) { - if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t; - return 1 / 2 * ((t -= 2) * t * t * t * t + 2); - }, - easeInSine: function (t) { - return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; - }, - easeOutSine: function (t) { - return 1 * Math.sin(t / 1 * (Math.PI / 2)); - }, - easeInOutSine: function (t) { - return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); - }, - easeInExpo: function (t) { - return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); - }, - easeOutExpo: function (t) { - return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); - }, - easeInOutExpo: function (t) { - if (t === 0) return 0; - if (t === 1) return 1; - if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1)); - return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); - }, - easeInCirc: function (t) { - if (t >= 1) return t; - return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); - }, - easeOutCirc: function (t) { - return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); - }, - easeInOutCirc: function (t) { - if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1); - return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); - }, - easeInElastic: function (t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) return 0; - if ((t /= 1) == 1) return 1; - if (!p) p = 1 * 0.3; - if (a < Math.abs(1)) { - a = 1; - s = p / 4; - } else s = p / (2 * Math.PI) * Math.asin(1 / a); - return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); - }, - easeOutElastic: function (t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) return 0; - if ((t /= 1) == 1) return 1; - if (!p) p = 1 * 0.3; - if (a < Math.abs(1)) { - a = 1; - s = p / 4; - } else s = p / (2 * Math.PI) * Math.asin(1 / a); - return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; - }, - easeInOutElastic: function (t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) return 0; - if ((t /= 1 / 2) == 2) return 1; - if (!p) p = 1 * (0.3 * 1.5); - if (a < Math.abs(1)) { - a = 1; - s = p / 4; - } else s = p / (2 * Math.PI) * Math.asin(1 / a); - if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); - return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; - }, - easeInBack: function (t) { - var s = 1.70158; - return 1 * (t /= 1) * t * ((s + 1) * t - s); - }, - easeOutBack: function (t) { - var s = 1.70158; - return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); - }, - easeInOutBack: function (t) { - var s = 1.70158; - if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); - return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); - }, - easeInBounce: function (t) { - return 1 - easingEffects.easeOutBounce(1 - t); - }, - easeOutBounce: function (t) { - if ((t /= 1) < (1 / 2.75)) { - return 1 * (7.5625 * t * t); - } else if (t < (2 / 2.75)) { - return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); - } else if (t < (2.5 / 2.75)) { - return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); - } else { - return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); - } - }, - easeInOutBounce: function (t) { - if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5; - return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; - } - }, - //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ - requestAnimFrame = helpers.requestAnimFrame = (function(){ - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(callback, 1000 / 60); - }; - })(), - cancelAnimFrame = helpers.cancelAnimFrame = (function(){ - return window.cancelAnimationFrame || - window.webkitCancelAnimationFrame || - window.mozCancelAnimationFrame || - window.oCancelAnimationFrame || - window.msCancelAnimationFrame || - function(callback) { - return window.clearTimeout(callback, 1000 / 60); - }; - })(), - animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){ - - var currentStep = 0, - easingFunction = easingEffects[easingString] || easingEffects.linear; - - var animationFrame = function(){ - currentStep++; - var stepDecimal = currentStep/totalSteps; - var easeDecimal = easingFunction(stepDecimal); - - callback.call(chartInstance,easeDecimal,stepDecimal, currentStep); - onProgress.call(chartInstance,easeDecimal,stepDecimal); - if (currentStep < totalSteps){ - chartInstance.animationFrame = requestAnimFrame(animationFrame); - } else{ - onComplete.apply(chartInstance); - } - }; - requestAnimFrame(animationFrame); - }, - //-- DOM methods - getRelativePosition = helpers.getRelativePosition = function(evt){ - var mouseX, mouseY; - var e = evt.originalEvent || evt, - canvas = evt.currentTarget || evt.srcElement, - boundingRect = canvas.getBoundingClientRect(); - - if (e.touches){ - mouseX = e.touches[0].clientX - boundingRect.left; - mouseY = e.touches[0].clientY - boundingRect.top; - - } - else{ - mouseX = e.clientX - boundingRect.left; - mouseY = e.clientY - boundingRect.top; - } - - return { - x : mouseX, - y : mouseY - }; - - }, - addEvent = helpers.addEvent = function(node,eventType,method){ - if (node.addEventListener){ - node.addEventListener(eventType,method); - } else if (node.attachEvent){ - node.attachEvent("on"+eventType, method); - } else { - node["on"+eventType] = method; - } - }, - removeEvent = helpers.removeEvent = function(node, eventType, handler){ - if (node.removeEventListener){ - node.removeEventListener(eventType, handler, false); - } else if (node.detachEvent){ - node.detachEvent("on"+eventType,handler); - } else{ - node["on" + eventType] = noop; - } - }, - bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){ - // Create the events object if it's not already present - if (!chartInstance.events) chartInstance.events = {}; - - each(arrayOfEvents,function(eventName){ - chartInstance.events[eventName] = function(){ - handler.apply(chartInstance, arguments); - }; - addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]); - }); - }, - unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) { - each(arrayOfEvents, function(handler,eventName){ - removeEvent(chartInstance.chart.canvas, eventName, handler); - }); - }, - getMaximumWidth = helpers.getMaximumWidth = function(domNode){ - var container = domNode.parentNode; - // TODO = check cross browser stuff with this. - return container.clientWidth; - }, - getMaximumHeight = helpers.getMaximumHeight = function(domNode){ - var container = domNode.parentNode; - // TODO = check cross browser stuff with this. - return container.clientHeight; - }, - getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support - retinaScale = helpers.retinaScale = function(chart){ - var ctx = chart.ctx, - width = chart.canvas.width, - height = chart.canvas.height; - - if (window.devicePixelRatio) { - ctx.canvas.style.width = width + "px"; - ctx.canvas.style.height = height + "px"; - ctx.canvas.height = height * window.devicePixelRatio; - ctx.canvas.width = width * window.devicePixelRatio; - ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - } - }, - //-- Canvas methods - clear = helpers.clear = function(chart){ - chart.ctx.clearRect(0,0,chart.width,chart.height); - }, - fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){ - return fontStyle + " " + pixelSize+"px " + fontFamily; - }, - longestText = helpers.longestText = function(ctx,font,arrayOfStrings){ - ctx.font = font; - var longest = 0; - each(arrayOfStrings,function(string){ - var textWidth = ctx.measureText(string).width; - longest = (textWidth > longest) ? textWidth : longest; - }); - return longest; - }, - drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){ - ctx.beginPath(); - ctx.moveTo(x + radius, y); - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); - }; - - - //Store a reference to each instance - allowing us to globally resize chart instances on window resize. - //Destroy method on the chart will remove the instance of the chart from this reference. - Chart.instances = {}; - - Chart.Type = function(data,options,chart){ - this.options = options; - this.chart = chart; - this.id = uid(); - //Add the chart instance to the global namespace - Chart.instances[this.id] = this; - - // Initialize is always called when a chart type is created - // By default it is a no op, but it should be extended - if (options.responsive){ - this.resize(); - } - this.initialize.call(this,data); - }; - - //Core methods that'll be a part of every chart type - extend(Chart.Type.prototype,{ - initialize : function(){return this;}, - clear : function(){ - clear(this.chart); - return this; - }, - stop : function(){ - // Stops any current animation loop occuring - cancelAnimFrame(this.animationFrame); - return this; - }, - resize : function(callback){ - this.stop(); - var canvas = this.chart.canvas, - newWidth = getMaximumWidth(this.chart.canvas), - newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas); - - canvas.width = this.chart.width = newWidth; - canvas.height = this.chart.height = newHeight; - - retinaScale(this.chart); - - if (typeof callback === "function"){ - callback.apply(this, Array.prototype.slice.call(arguments, 1)); - } - return this; - }, - reflow : noop, - render : function(reflow){ - if (reflow){ - this.reflow(); - } - if (this.options.animation && !reflow){ - helpers.animationLoop( - this.draw, - this.options.animationSteps, - this.options.animationEasing, - this.options.onAnimationProgress, - this.options.onAnimationComplete, - this - ); - } - else{ - this.draw(); - this.options.onAnimationComplete.call(this); - } - return this; - }, - generateLegend : function(){ - return template(this.options.legendTemplate,this); - }, - destroy : function(){ - this.clear(); - unbindEvents(this, this.events); - var canvas = this.chart.canvas; - - // Reset canvas height/width attributes starts a fresh with the canvas context - canvas.width = this.chart.width; - canvas.height = this.chart.height; - - // < IE9 doesn't support removeProperty - if (canvas.style.removeProperty) { - canvas.style.removeProperty('width'); - canvas.style.removeProperty('height'); - } else { - canvas.style.removeAttribute('width'); - canvas.style.removeAttribute('height'); - } - - delete Chart.instances[this.id]; - }, - showTooltip : function(ChartElements, forceRedraw){ - // Only redraw the chart if we've actually changed what we're hovering on. - if (typeof this.activeElements === 'undefined') this.activeElements = []; - - var isChanged = (function(Elements){ - var changed = false; - - if (Elements.length !== this.activeElements.length){ - changed = true; - return changed; - } - - each(Elements, function(element, index){ - if (element !== this.activeElements[index]){ - changed = true; - } - }, this); - return changed; - }).call(this, ChartElements); - - if (!isChanged && !forceRedraw){ - return; - } - else{ - this.activeElements = ChartElements; - } - this.draw(); - if(this.options.customTooltips){ - this.options.customTooltips(false); - } - if (ChartElements.length > 0){ - // If we have multiple datasets, show a MultiTooltip for all of the data points at that index - if (this.datasets && this.datasets.length > 1) { - var dataArray, - dataIndex; - - for (var i = this.datasets.length - 1; i >= 0; i--) { - dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments; - dataIndex = indexOf(dataArray, ChartElements[0]); - if (dataIndex !== -1){ - break; - } - } - var tooltipLabels = [], - tooltipColors = [], - medianPosition = (function(index) { - - // Get all the points at that particular index - var Elements = [], - dataCollection, - xPositions = [], - yPositions = [], - xMax, - yMax, - xMin, - yMin; - helpers.each(this.datasets, function(dataset){ - dataCollection = dataset.points || dataset.bars || dataset.segments; - if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){ - Elements.push(dataCollection[dataIndex]); - } - }); - - helpers.each(Elements, function(element) { - xPositions.push(element.x); - yPositions.push(element.y); - - - //Include any colour information about the element - tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element)); - tooltipColors.push({ - fill: element._saved.fillColor || element.fillColor, - stroke: element._saved.strokeColor || element.strokeColor - }); - - }, this); - - yMin = min(yPositions); - yMax = max(yPositions); - - xMin = min(xPositions); - xMax = max(xPositions); - - return { - x: (xMin > this.chart.width/2) ? xMin : xMax, - y: (yMin + yMax)/2 - }; - }).call(this, dataIndex); - - new Chart.MultiTooltip({ - x: medianPosition.x, - y: medianPosition.y, - xPadding: this.options.tooltipXPadding, - yPadding: this.options.tooltipYPadding, - xOffset: this.options.tooltipXOffset, - fillColor: this.options.tooltipFillColor, - textColor: this.options.tooltipFontColor, - fontFamily: this.options.tooltipFontFamily, - fontStyle: this.options.tooltipFontStyle, - fontSize: this.options.tooltipFontSize, - titleTextColor: this.options.tooltipTitleFontColor, - titleFontFamily: this.options.tooltipTitleFontFamily, - titleFontStyle: this.options.tooltipTitleFontStyle, - titleFontSize: this.options.tooltipTitleFontSize, - cornerRadius: this.options.tooltipCornerRadius, - labels: tooltipLabels, - legendColors: tooltipColors, - legendColorBackground : this.options.multiTooltipKeyBackground, - title: ChartElements[0].label, - chart: this.chart, - ctx: this.chart.ctx, - custom: this.options.customTooltips - }).draw(); - - } else { - each(ChartElements, function(Element) { - var tooltipPosition = Element.tooltipPosition(); - new Chart.Tooltip({ - x: Math.round(tooltipPosition.x), - y: Math.round(tooltipPosition.y), - xPadding: this.options.tooltipXPadding, - yPadding: this.options.tooltipYPadding, - fillColor: this.options.tooltipFillColor, - textColor: this.options.tooltipFontColor, - fontFamily: this.options.tooltipFontFamily, - fontStyle: this.options.tooltipFontStyle, - fontSize: this.options.tooltipFontSize, - caretHeight: this.options.tooltipCaretSize, - cornerRadius: this.options.tooltipCornerRadius, - text: template(this.options.tooltipTemplate, Element), - chart: this.chart, - custom: this.options.customTooltips - }).draw(); - }, this); - } - } - return this; - }, - toBase64Image : function(){ - return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); - } - }); - - Chart.Type.extend = function(extensions){ - - var parent = this; - - var ChartType = function(){ - return parent.apply(this,arguments); - }; - - //Copy the prototype object of the this class - ChartType.prototype = clone(parent.prototype); - //Now overwrite some of the properties in the base class with the new extensions - extend(ChartType.prototype, extensions); - - ChartType.extend = Chart.Type.extend; - - if (extensions.name || parent.prototype.name){ - - var chartName = extensions.name || parent.prototype.name; - //Assign any potential default values of the new chart type - - //If none are defined, we'll use a clone of the chart type this is being extended from. - //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart - //doesn't define some defaults of their own. - - var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {}; - - Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults); - - Chart.types[chartName] = ChartType; - - //Register this new chart type in the Chart prototype - Chart.prototype[chartName] = function(data,options){ - var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {}); - return new ChartType(data,config,this); - }; - } else{ - warn("Name not provided for this chart, so it hasn't been registered"); - } - return parent; - }; - - Chart.Element = function(configuration){ - extend(this,configuration); - this.initialize.apply(this,arguments); - this.save(); - }; - extend(Chart.Element.prototype,{ - initialize : function(){}, - restore : function(props){ - if (!props){ - extend(this,this._saved); - } else { - each(props,function(key){ - this[key] = this._saved[key]; - },this); - } - return this; - }, - save : function(){ - this._saved = clone(this); - delete this._saved._saved; - return this; - }, - update : function(newProps){ - each(newProps,function(value,key){ - this._saved[key] = this[key]; - this[key] = value; - },this); - return this; - }, - transition : function(props,ease){ - each(props,function(value,key){ - this[key] = ((value - this._saved[key]) * ease) + this._saved[key]; - },this); - return this; - }, - tooltipPosition : function(){ - return { - x : this.x, - y : this.y - }; - }, - hasValue: function(){ - return isNumber(this.value); - } - }); - - Chart.Element.extend = inherits; - - - Chart.Point = Chart.Element.extend({ - display: true, - inRange: function(chartX,chartY){ - var hitDetectionRange = this.hitDetectionRadius + this.radius; - return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2)); - }, - draw : function(){ - if (this.display){ - var ctx = this.ctx; - ctx.beginPath(); - - ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2); - ctx.closePath(); - - ctx.strokeStyle = this.strokeColor; - ctx.lineWidth = this.strokeWidth; - - ctx.fillStyle = this.fillColor; - - ctx.fill(); - ctx.stroke(); - } - - - //Quick debug for bezier curve splining - //Highlights control points and the line between them. - //Handy for dev - stripped in the min version. - - // ctx.save(); - // ctx.fillStyle = "black"; - // ctx.strokeStyle = "black" - // ctx.beginPath(); - // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2); - // ctx.fill(); - - // ctx.beginPath(); - // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2); - // ctx.fill(); - - // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y); - // ctx.lineTo(this.x, this.y); - // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y); - // ctx.stroke(); - - // ctx.restore(); - - - - } - }); - - Chart.Arc = Chart.Element.extend({ - inRange : function(chartX,chartY){ - - var pointRelativePosition = helpers.getAngleFromPoint(this, { - x: chartX, - y: chartY - }); - - //Check if within the range of the open/close angle - var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle), - withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius); - - return (betweenAngles && withinRadius); - //Ensure within the outside of the arc centre, but inside arc outer - }, - tooltipPosition : function(){ - var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2), - rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius; - return { - x : this.x + (Math.cos(centreAngle) * rangeFromCentre), - y : this.y + (Math.sin(centreAngle) * rangeFromCentre) - }; - }, - draw : function(animationPercent){ - - var easingDecimal = animationPercent || 1; - - var ctx = this.ctx; - - ctx.beginPath(); - - ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle); - - ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true); - - ctx.closePath(); - ctx.strokeStyle = this.strokeColor; - ctx.lineWidth = this.strokeWidth; - - ctx.fillStyle = this.fillColor; - - ctx.fill(); - ctx.lineJoin = 'bevel'; - - if (this.showStroke){ - ctx.stroke(); - } - } - }); - - Chart.Rectangle = Chart.Element.extend({ - draw : function(){ - var ctx = this.ctx, - halfWidth = this.width/2, - leftX = this.x - halfWidth, - rightX = this.x + halfWidth, - top = this.base - (this.base - this.y), - halfStroke = this.strokeWidth / 2; - - // Canvas doesn't allow us to stroke inside the width so we can - // adjust the sizes to fit if we're setting a stroke on the line - if (this.showStroke){ - leftX += halfStroke; - rightX -= halfStroke; - top += halfStroke; - } - - ctx.beginPath(); - - ctx.fillStyle = this.fillColor; - ctx.strokeStyle = this.strokeColor; - ctx.lineWidth = this.strokeWidth; - - // It'd be nice to keep this class totally generic to any rectangle - // and simply specify which border to miss out. - ctx.moveTo(leftX, this.base); - ctx.lineTo(leftX, top); - ctx.lineTo(rightX, top); - ctx.lineTo(rightX, this.base); - ctx.fill(); - if (this.showStroke){ - ctx.stroke(); - } - }, - height : function(){ - return this.base - this.y; - }, - inRange : function(chartX,chartY){ - return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base); - } - }); - - Chart.Tooltip = Chart.Element.extend({ - draw : function(){ - - var ctx = this.chart.ctx; - - ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); - - this.xAlign = "center"; - this.yAlign = "above"; - - //Distance between the actual element.y position and the start of the tooltip caret - var caretPadding = this.caretPadding = 2; - - var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding, - tooltipRectHeight = this.fontSize + 2*this.yPadding, - tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding; - - if (this.x + tooltipWidth/2 >this.chart.width){ - this.xAlign = "left"; - } else if (this.x - tooltipWidth/2 < 0){ - this.xAlign = "right"; - } - - if (this.y - tooltipHeight < 0){ - this.yAlign = "below"; - } - - - var tooltipX = this.x - tooltipWidth/2, - tooltipY = this.y - tooltipHeight; - - ctx.fillStyle = this.fillColor; - - // Custom Tooltips - if(this.custom){ - this.custom(this); - } - else{ - switch(this.yAlign) - { - case "above": - //Draw a caret above the x/y - ctx.beginPath(); - ctx.moveTo(this.x,this.y - caretPadding); - ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight)); - ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight)); - ctx.closePath(); - ctx.fill(); - break; - case "below": - tooltipY = this.y + caretPadding + this.caretHeight; - //Draw a caret below the x/y - ctx.beginPath(); - ctx.moveTo(this.x, this.y + caretPadding); - ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight); - ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight); - ctx.closePath(); - ctx.fill(); - break; - } - - switch(this.xAlign) - { - case "left": - tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight); - break; - case "right": - tooltipX = this.x - (this.cornerRadius + this.caretHeight); - break; - } - - drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius); - - ctx.fill(); - - ctx.fillStyle = this.textColor; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2); - } - } - }); - - Chart.MultiTooltip = Chart.Element.extend({ - initialize : function(){ - this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); - - this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily); - - this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5; - - this.ctx.font = this.titleFont; - - var titleWidth = this.ctx.measureText(this.title).width, - //Label has a legend square as well so account for this. - labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3, - longestTextWidth = max([labelWidth,titleWidth]); - - this.width = longestTextWidth + (this.xPadding*2); - - - var halfHeight = this.height/2; - - //Check to ensure the height will fit on the canvas - if (this.y - halfHeight < 0 ){ - this.y = halfHeight; - } else if (this.y + halfHeight > this.chart.height){ - this.y = this.chart.height - halfHeight; - } - - //Decide whether to align left or right based on position on canvas - if (this.x > this.chart.width/2){ - this.x -= this.xOffset + this.width; - } else { - this.x += this.xOffset; - } - - - }, - getLineHeight : function(index){ - var baseLineHeight = this.y - (this.height/2) + this.yPadding, - afterTitleIndex = index-1; - - //If the index is zero, we're getting the title - if (index === 0){ - return baseLineHeight + this.titleFontSize/2; - } else{ - return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5; - } - - }, - draw : function(){ - // Custom Tooltips - if(this.custom){ - this.custom(this); - } - else{ - drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius); - var ctx = this.ctx; - ctx.fillStyle = this.fillColor; - ctx.fill(); - ctx.closePath(); - - ctx.textAlign = "left"; - ctx.textBaseline = "middle"; - ctx.fillStyle = this.titleTextColor; - ctx.font = this.titleFont; - - ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0)); - - ctx.font = this.font; - helpers.each(this.labels,function(label,index){ - ctx.fillStyle = this.textColor; - ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1)); - - //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas) - //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); - //Instead we'll make a white filled block to put the legendColour palette over. - - ctx.fillStyle = this.legendColorBackground; - ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); - - ctx.fillStyle = this.legendColors[index].fill; - ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); - - - },this); - } - } - }); - - Chart.Scale = Chart.Element.extend({ - initialize : function(){ - this.fit(); - }, - buildYLabels : function(){ - this.yLabels = []; - - var stepDecimalPlaces = getDecimalPlaces(this.stepValue); - - for (var i=0; i<=this.steps; i++){ - this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)})); - } - this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0; - }, - addXLabel : function(label){ - this.xLabels.push(label); - this.valuesCount++; - this.fit(); - }, - removeXLabel : function(){ - this.xLabels.shift(); - this.valuesCount--; - this.fit(); - }, - // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use - fit: function(){ - // First we need the width of the yLabels, assuming the xLabels aren't rotated - - // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation - this.startPoint = (this.display) ? this.fontSize : 0; - this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels - - // Apply padding settings to the start and end point. - this.startPoint += this.padding; - this.endPoint -= this.padding; - - // Cache the starting height, so can determine if we need to recalculate the scale yAxis - var cachedHeight = this.endPoint - this.startPoint, - cachedYLabelWidth; - - // Build the current yLabels so we have an idea of what size they'll be to start - /* - * This sets what is returned from calculateScaleRange as static properties of this class: - * - this.steps; - this.stepValue; - this.min; - this.max; - * - */ - this.calculateYRange(cachedHeight); - - // With these properties set we can now build the array of yLabels - // and also the width of the largest yLabel - this.buildYLabels(); - - this.calculateXLabelRotation(); - - while((cachedHeight > this.endPoint - this.startPoint)){ - cachedHeight = this.endPoint - this.startPoint; - cachedYLabelWidth = this.yLabelWidth; - - this.calculateYRange(cachedHeight); - this.buildYLabels(); - - // Only go through the xLabel loop again if the yLabel width has changed - if (cachedYLabelWidth < this.yLabelWidth){ - this.calculateXLabelRotation(); - } - } - - }, - calculateXLabelRotation : function(){ - //Get the width of each grid by calculating the difference - //between x offsets between 0 and 1. - - this.ctx.font = this.font; - - var firstWidth = this.ctx.measureText(this.xLabels[0]).width, - lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width, - firstRotated, - lastRotated; - - - this.xScalePaddingRight = lastWidth/2 + 3; - this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10; - - this.xLabelRotation = 0; - if (this.display){ - var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels), - cosRotation, - firstRotatedWidth; - this.xLabelWidth = originalLabelWidth; - //Allow 3 pixels x2 padding either side for label readability - var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6; - - //Max label rotate should be 90 - also act as a loop counter - while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){ - cosRotation = Math.cos(toRadians(this.xLabelRotation)); - - firstRotated = cosRotation * firstWidth; - lastRotated = cosRotation * lastWidth; - - // We're right aligning the text now. - if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){ - this.xScalePaddingLeft = firstRotated + this.fontSize / 2; - } - this.xScalePaddingRight = this.fontSize/2; - - - this.xLabelRotation++; - this.xLabelWidth = cosRotation * originalLabelWidth; - - } - if (this.xLabelRotation > 0){ - this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3; - } - } - else{ - this.xLabelWidth = 0; - this.xScalePaddingRight = this.padding; - this.xScalePaddingLeft = this.padding; - } - - }, - // Needs to be overidden in each Chart type - // Otherwise we need to pass all the data into the scale class - calculateYRange: noop, - drawingArea: function(){ - return this.startPoint - this.endPoint; - }, - calculateY : function(value){ - var scalingFactor = this.drawingArea() / (this.min - this.max); - return this.endPoint - (scalingFactor * (value - this.min)); - }, - calculateX : function(index){ - var isRotated = (this.xLabelRotation > 0), - // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding, - innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight), - valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1), - valueOffset = (valueWidth * index) + this.xScalePaddingLeft; - - if (this.offsetGridLines){ - valueOffset += (valueWidth/2); - } - - return Math.round(valueOffset); - }, - update : function(newProps){ - helpers.extend(this, newProps); - this.fit(); - }, - draw : function(){ - var ctx = this.ctx, - yLabelGap = (this.endPoint - this.startPoint) / this.steps, - xStart = Math.round(this.xScalePaddingLeft); - if (this.display){ - ctx.fillStyle = this.textColor; - ctx.font = this.font; - each(this.yLabels,function(labelString,index){ - var yLabelCenter = this.endPoint - (yLabelGap * index), - linePositionY = Math.round(yLabelCenter), - drawHorizontalLine = this.showHorizontalLines; - - ctx.textAlign = "right"; - ctx.textBaseline = "middle"; - if (this.showLabels){ - ctx.fillText(labelString,xStart - 10,yLabelCenter); - } - - // This is X axis, so draw it - if (index === 0 && !drawHorizontalLine){ - drawHorizontalLine = true; - } - - if (drawHorizontalLine){ - ctx.beginPath(); - } - - if (index > 0){ - // This is a grid line in the centre, so drop that - ctx.lineWidth = this.gridLineWidth; - ctx.strokeStyle = this.gridLineColor; - } else { - // This is the first line on the scale - ctx.lineWidth = this.lineWidth; - ctx.strokeStyle = this.lineColor; - } - - linePositionY += helpers.aliasPixel(ctx.lineWidth); - - if(drawHorizontalLine){ - ctx.moveTo(xStart, linePositionY); - ctx.lineTo(this.width, linePositionY); - ctx.stroke(); - ctx.closePath(); - } - - ctx.lineWidth = this.lineWidth; - ctx.strokeStyle = this.lineColor; - ctx.beginPath(); - ctx.moveTo(xStart - 5, linePositionY); - ctx.lineTo(xStart, linePositionY); - ctx.stroke(); - ctx.closePath(); - - },this); - - each(this.xLabels,function(label,index){ - var xPos = this.calculateX(index) + aliasPixel(this.lineWidth), - // Check to see if line/bar here and decide where to place the line - linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth), - isRotated = (this.xLabelRotation > 0), - drawVerticalLine = this.showVerticalLines; - - // This is Y axis, so draw it - if (index === 0 && !drawVerticalLine){ - drawVerticalLine = true; - } - - if (drawVerticalLine){ - ctx.beginPath(); - } - - if (index > 0){ - // This is a grid line in the centre, so drop that - ctx.lineWidth = this.gridLineWidth; - ctx.strokeStyle = this.gridLineColor; - } else { - // This is the first line on the scale - ctx.lineWidth = this.lineWidth; - ctx.strokeStyle = this.lineColor; - } - - if (drawVerticalLine){ - ctx.moveTo(linePos,this.endPoint); - ctx.lineTo(linePos,this.startPoint - 3); - ctx.stroke(); - ctx.closePath(); - } - - - ctx.lineWidth = this.lineWidth; - ctx.strokeStyle = this.lineColor; - - - // Small lines at the bottom of the base grid line - ctx.beginPath(); - ctx.moveTo(linePos,this.endPoint); - ctx.lineTo(linePos,this.endPoint + 5); - ctx.stroke(); - ctx.closePath(); - - ctx.save(); - ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8); - ctx.rotate(toRadians(this.xLabelRotation)*-1); - ctx.font = this.font; - ctx.textAlign = (isRotated) ? "right" : "center"; - ctx.textBaseline = (isRotated) ? "middle" : "top"; - ctx.fillText(label, 0, 0); - ctx.restore(); - },this); - - } - } - - }); - - Chart.RadialScale = Chart.Element.extend({ - initialize: function(){ - this.size = min([this.height, this.width]); - this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2); - }, - calculateCenterOffset: function(value){ - // Take into account half font size + the yPadding of the top value - var scalingFactor = this.drawingArea / (this.max - this.min); - - return (value - this.min) * scalingFactor; - }, - update : function(){ - if (!this.lineArc){ - this.setScaleSize(); - } else { - this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2); - } - this.buildYLabels(); - }, - buildYLabels: function(){ - this.yLabels = []; - - var stepDecimalPlaces = getDecimalPlaces(this.stepValue); - - for (var i=0; i<=this.steps; i++){ - this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)})); - } - }, - getCircumference : function(){ - return ((Math.PI*2) / this.valuesCount); - }, - setScaleSize: function(){ - /* - * Right, this is really confusing and there is a lot of maths going on here - * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 - * - * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif - * - * Solution: - * - * We assume the radius of the polygon is half the size of the canvas at first - * at each index we check if the text overlaps. - * - * Where it does, we store that angle and that index. - * - * After finding the largest index and angle we calculate how much we need to remove - * from the shape radius to move the point inwards by that x. - * - * We average the left and right distances to get the maximum shape radius that can fit in the box - * along with labels. - * - * Once we have that, we can find the centre point for the chart, by taking the x text protrusion - * on each side, removing that from the size, halving it and adding the left x protrusion width. - * - * This will mean we have a shape fitted to the canvas, as large as it can be with the labels - * and position it in the most space efficient manner - * - * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif - */ - - - // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. - // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points - var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]), - pointPosition, - i, - textWidth, - halfTextWidth, - furthestRight = this.width, - furthestRightIndex, - furthestRightAngle, - furthestLeft = 0, - furthestLeftIndex, - furthestLeftAngle, - xProtrusionLeft, - xProtrusionRight, - radiusReductionRight, - radiusReductionLeft, - maxWidthRadius; - this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily); - for (i=0;i furthestRight) { - furthestRight = pointPosition.x + halfTextWidth; - furthestRightIndex = i; - } - if (pointPosition.x - halfTextWidth < furthestLeft) { - furthestLeft = pointPosition.x - halfTextWidth; - furthestLeftIndex = i; - } - } - else if (i < this.valuesCount/2) { - // Less than half the values means we'll left align the text - if (pointPosition.x + textWidth > furthestRight) { - furthestRight = pointPosition.x + textWidth; - furthestRightIndex = i; - } - } - else if (i > this.valuesCount/2){ - // More than half the values means we'll right align the text - if (pointPosition.x - textWidth < furthestLeft) { - furthestLeft = pointPosition.x - textWidth; - furthestLeftIndex = i; - } - } - } - - xProtrusionLeft = furthestLeft; - - xProtrusionRight = Math.ceil(furthestRight - this.width); - - furthestRightAngle = this.getIndexAngle(furthestRightIndex); - - furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); - - radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2); - - radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2); - - // Ensure we actually need to reduce the size of the chart - radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0; - radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; - - this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2; - - //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) - this.setCenterPoint(radiusReductionLeft, radiusReductionRight); - - }, - setCenterPoint: function(leftMovement, rightMovement){ - - var maxRight = this.width - rightMovement - this.drawingArea, - maxLeft = leftMovement + this.drawingArea; - - this.xCenter = (maxLeft + maxRight)/2; - // Always vertically in the centre as the text height doesn't change - this.yCenter = (this.height/2); - }, - - getIndexAngle : function(index){ - var angleMultiplier = (Math.PI * 2) / this.valuesCount; - // Start from the top instead of right, so remove a quarter of the circle - - return index * angleMultiplier - (Math.PI/2); - }, - getPointPosition : function(index, distanceFromCenter){ - var thisAngle = this.getIndexAngle(index); - return { - x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, - y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter - }; - }, - draw: function(){ - if (this.display){ - var ctx = this.ctx; - each(this.yLabels, function(label, index){ - // Don't draw a centre value - if (index > 0){ - var yCenterOffset = index * (this.drawingArea/this.steps), - yHeight = this.yCenter - yCenterOffset, - pointPosition; - - // Draw circular lines around the scale - if (this.lineWidth > 0){ - ctx.strokeStyle = this.lineColor; - ctx.lineWidth = this.lineWidth; - - if(this.lineArc){ - ctx.beginPath(); - ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2); - ctx.closePath(); - ctx.stroke(); - } else{ - ctx.beginPath(); - for (var i=0;i= 0; i--) { - if (this.angleLineWidth > 0){ - var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max)); - ctx.beginPath(); - ctx.moveTo(this.xCenter, this.yCenter); - ctx.lineTo(outerPosition.x, outerPosition.y); - ctx.stroke(); - ctx.closePath(); - } - // Extra 3px out for some label spacing - var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5); - ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily); - ctx.fillStyle = this.pointLabelFontColor; - - var labelsCount = this.labels.length, - halfLabelsCount = this.labels.length/2, - quarterLabelsCount = halfLabelsCount/2, - upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), - exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); - if (i === 0){ - ctx.textAlign = 'center'; - } else if(i === halfLabelsCount){ - ctx.textAlign = 'center'; - } else if (i < halfLabelsCount){ - ctx.textAlign = 'left'; - } else { - ctx.textAlign = 'right'; - } - - // Set the correct text baseline based on outer positioning - if (exactQuarter){ - ctx.textBaseline = 'middle'; - } else if (upperHalf){ - ctx.textBaseline = 'bottom'; - } else { - ctx.textBaseline = 'top'; - } - - ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); - } - } - } - } - }); - - // Attach global event to resize each chart instance when the browser resizes - helpers.addEvent(window, "resize", (function(){ - // Basic debounce of resize function so it doesn't hurt performance when resizing browser. - var timeout; - return function(){ - clearTimeout(timeout); - timeout = setTimeout(function(){ - each(Chart.instances,function(instance){ - // If the responsive flag is set in the chart instance config - // Cascade the resize event down to the chart. - if (instance.options.responsive){ - instance.resize(instance.render, true); - } - }); - }, 50); - }; - })()); - - - if (amd) { - define(function(){ - return Chart; - }); - } else if (typeof module === 'object' && module.exports) { - module.exports = Chart; - } - - root.Chart = Chart; - - Chart.noConflict = function(){ - root.Chart = previous; - return Chart; - }; - -}).call(this); - -(function(){ - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - - var defaultConfig = { - //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value - scaleBeginAtZero : true, - - //Boolean - Whether grid lines are shown across the chart - scaleShowGridLines : true, - - //String - Colour of the grid lines - scaleGridLineColor : "rgba(0,0,0,.05)", - - //Number - Width of the grid lines - scaleGridLineWidth : 1, - - //Boolean - Whether to show horizontal lines (except X axis) - scaleShowHorizontalLines: true, - - //Boolean - Whether to show vertical lines (except Y axis) - scaleShowVerticalLines: true, - - //Boolean - If there is a stroke on each bar - barShowStroke : true, - - //Number - Pixel width of the bar stroke - barStrokeWidth : 2, - - //Number - Spacing between each of the X value sets - barValueSpacing : 5, - - //Number - Spacing between data sets within X values - barDatasetSpacing : 1, - - //String - A legend template - legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" - - }; - - - Chart.Type.extend({ - name: "Bar", - defaults : defaultConfig, - initialize: function(data){ - - //Expose options as a scope variable here so we can access it in the ScaleClass - var options = this.options; - - this.ScaleClass = Chart.Scale.extend({ - offsetGridLines : true, - calculateBarX : function(datasetCount, datasetIndex, barIndex){ - //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar - var xWidth = this.calculateBaseWidth(), - xAbsolute = this.calculateX(barIndex) - (xWidth/2), - barWidth = this.calculateBarWidth(datasetCount); - - return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2; - }, - calculateBaseWidth : function(){ - return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing); - }, - calculateBarWidth : function(datasetCount){ - //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset - var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing); - - return (baseWidth / datasetCount); - } - }); - - this.datasets = []; - - //Set up tooltip events on the chart - if (this.options.showTooltips){ - helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ - var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : []; - - this.eachBars(function(bar){ - bar.restore(['fillColor', 'strokeColor']); - }); - helpers.each(activeBars, function(activeBar){ - activeBar.fillColor = activeBar.highlightFill; - activeBar.strokeColor = activeBar.highlightStroke; - }); - this.showTooltip(activeBars); - }); - } - - //Declare the extension of the default point, to cater for the options passed in to the constructor - this.BarClass = Chart.Rectangle.extend({ - strokeWidth : this.options.barStrokeWidth, - showStroke : this.options.barShowStroke, - ctx : this.chart.ctx - }); - - //Iterate through each of the datasets, and build this into a property of the chart - helpers.each(data.datasets,function(dataset,datasetIndex){ - - var datasetObject = { - label : dataset.label || null, - fillColor : dataset.fillColor, - strokeColor : dataset.strokeColor, - bars : [] - }; - - this.datasets.push(datasetObject); - - helpers.each(dataset.data,function(dataPoint,index){ - //Add a new point for each piece of data, passing any required data to draw. - datasetObject.bars.push(new this.BarClass({ - value : dataPoint, - label : data.labels[index], - datasetLabel: dataset.label, - strokeColor : dataset.strokeColor, - fillColor : dataset.fillColor, - highlightFill : dataset.highlightFill || dataset.fillColor, - highlightStroke : dataset.highlightStroke || dataset.strokeColor - })); - },this); - - },this); - - this.buildScale(data.labels); - - this.BarClass.prototype.base = this.scale.endPoint; - - this.eachBars(function(bar, index, datasetIndex){ - helpers.extend(bar, { - width : this.scale.calculateBarWidth(this.datasets.length), - x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index), - y: this.scale.endPoint - }); - bar.save(); - }, this); - - this.render(); - }, - update : function(){ - this.scale.update(); - // Reset any highlight colours before updating. - helpers.each(this.activeElements, function(activeElement){ - activeElement.restore(['fillColor', 'strokeColor']); - }); - - this.eachBars(function(bar){ - bar.save(); - }); - this.render(); - }, - eachBars : function(callback){ - helpers.each(this.datasets,function(dataset, datasetIndex){ - helpers.each(dataset.bars, callback, this, datasetIndex); - },this); - }, - getBarsAtEvent : function(e){ - var barsArray = [], - eventPosition = helpers.getRelativePosition(e), - datasetIterator = function(dataset){ - barsArray.push(dataset.bars[barIndex]); - }, - barIndex; - - for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) { - for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) { - if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){ - helpers.each(this.datasets, datasetIterator); - return barsArray; - } - } - } - - return barsArray; - }, - buildScale : function(labels){ - var self = this; - - var dataTotal = function(){ - var values = []; - self.eachBars(function(bar){ - values.push(bar.value); - }); - return values; - }; - - var scaleOptions = { - templateString : this.options.scaleLabel, - height : this.chart.height, - width : this.chart.width, - ctx : this.chart.ctx, - textColor : this.options.scaleFontColor, - fontSize : this.options.scaleFontSize, - fontStyle : this.options.scaleFontStyle, - fontFamily : this.options.scaleFontFamily, - valuesCount : labels.length, - beginAtZero : this.options.scaleBeginAtZero, - integersOnly : this.options.scaleIntegersOnly, - calculateYRange: function(currentHeight){ - var updatedRanges = helpers.calculateScaleRange( - dataTotal(), - currentHeight, - this.fontSize, - this.beginAtZero, - this.integersOnly - ); - helpers.extend(this, updatedRanges); - }, - xLabels : labels, - font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), - lineWidth : this.options.scaleLineWidth, - lineColor : this.options.scaleLineColor, - showHorizontalLines : this.options.scaleShowHorizontalLines, - showVerticalLines : this.options.scaleShowVerticalLines, - gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, - gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", - padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0, - showLabels : this.options.scaleShowLabels, - display : this.options.showScale - }; - - if (this.options.scaleOverride){ - helpers.extend(scaleOptions, { - calculateYRange: helpers.noop, - steps: this.options.scaleSteps, - stepValue: this.options.scaleStepWidth, - min: this.options.scaleStartValue, - max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) - }); - } - - this.scale = new this.ScaleClass(scaleOptions); - }, - addData : function(valuesArray,label){ - //Map the values array for each of the datasets - helpers.each(valuesArray,function(value,datasetIndex){ - //Add a new point for each piece of data, passing any required data to draw. - this.datasets[datasetIndex].bars.push(new this.BarClass({ - value : value, - label : label, - x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1), - y: this.scale.endPoint, - width : this.scale.calculateBarWidth(this.datasets.length), - base : this.scale.endPoint, - strokeColor : this.datasets[datasetIndex].strokeColor, - fillColor : this.datasets[datasetIndex].fillColor - })); - },this); - - this.scale.addXLabel(label); - //Then re-render the chart. - this.update(); - }, - removeData : function(){ - this.scale.removeXLabel(); - //Then re-render the chart. - helpers.each(this.datasets,function(dataset){ - dataset.bars.shift(); - },this); - this.update(); - }, - reflow : function(){ - helpers.extend(this.BarClass.prototype,{ - y: this.scale.endPoint, - base : this.scale.endPoint - }); - var newScaleProps = helpers.extend({ - height : this.chart.height, - width : this.chart.width - }); - this.scale.update(newScaleProps); - }, - draw : function(ease){ - var easingDecimal = ease || 1; - this.clear(); - - var ctx = this.chart.ctx; - - this.scale.draw(easingDecimal); - - //Draw all the bars for each dataset - helpers.each(this.datasets,function(dataset,datasetIndex){ - helpers.each(dataset.bars,function(bar,index){ - if (bar.hasValue()){ - bar.base = this.scale.endPoint; - //Transition then draw - bar.transition({ - x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index), - y : this.scale.calculateY(bar.value), - width : this.scale.calculateBarWidth(this.datasets.length) - }, easingDecimal).draw(); - } - },this); - - },this); - } - }); - - -}).call(this); - -(function(){ - "use strict"; - - var root = this, - Chart = root.Chart, - //Cache a local reference to Chart.helpers - helpers = Chart.helpers; - - var defaultConfig = { - //Boolean - Whether we should show a stroke on each segment - segmentShowStroke : true, - - //String - The colour of each segment stroke - segmentStrokeColor : "#fff", - - //Number - The width of each segment stroke - segmentStrokeWidth : 2, - - //The percentage of the chart that we cut out of the middle. - percentageInnerCutout : 50, - - //Number - Amount of animation steps - animationSteps : 100, - - //String - Animation easing effect - animationEasing : "easeOutBounce", - - //Boolean - Whether we animate the rotation of the Doughnut - animateRotate : true, - - //Boolean - Whether we animate scaling the Doughnut from the centre - animateScale : false, - - //String - A legend template - legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
" - - }; - - - Chart.Type.extend({ - //Passing in a name registers this chart in the Chart namespace - name: "Doughnut", - //Providing a defaults will also register the deafults in the chart namespace - defaults : defaultConfig, - //Initialize is fired when the chart is initialized - Data is passed in as a parameter - //Config is automatically merged by the core of Chart.js, and is available at this.options - initialize: function(data){ - - //Declare segments as a static property to prevent inheriting across the Chart type prototype - this.segments = []; - this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; - - this.SegmentArc = Chart.Arc.extend({ - ctx : this.chart.ctx, - x : this.chart.width/2, - y : this.chart.height/2 - }); - - //Set up tooltip events on the chart - if (this.options.showTooltips){ - helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ - var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; - - helpers.each(this.segments,function(segment){ - segment.restore(["fillColor"]); - }); - helpers.each(activeSegments,function(activeSegment){ - activeSegment.fillColor = activeSegment.highlightColor; - }); - this.showTooltip(activeSegments); - }); - } - this.calculateTotal(data); - - helpers.each(data,function(datapoint, index){ - this.addData(datapoint, index, true); - },this); - - this.render(); - }, - getSegmentsAtEvent : function(e){ - var segmentsArray = []; - - var location = helpers.getRelativePosition(e); - - helpers.each(this.segments,function(segment){ - if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); - },this); - return segmentsArray; - }, - addData : function(segment, atIndex, silent){ - var index = atIndex || this.segments.length; - this.segments.splice(index, 0, new this.SegmentArc({ - value : segment.value, - outerRadius : (this.options.animateScale) ? 0 : this.outerRadius, - innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout, - fillColor : segment.color, - highlightColor : segment.highlight || segment.color, - showStroke : this.options.segmentShowStroke, - strokeWidth : this.options.segmentStrokeWidth, - strokeColor : this.options.segmentStrokeColor, - startAngle : Math.PI * 1.5, - circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value), - label : segment.label - })); - if (!silent){ - this.reflow(); - this.update(); - } - }, - calculateCircumference : function(value){ - return (Math.PI*2)*(Math.abs(value) / this.total); - }, - calculateTotal : function(data){ - this.total = 0; - helpers.each(data,function(segment){ - this.total += Math.abs(segment.value); - },this); - }, - update : function(){ - this.calculateTotal(this.segments); - - // Reset any highlight colours before updating. - helpers.each(this.activeElements, function(activeElement){ - activeElement.restore(['fillColor']); - }); - - helpers.each(this.segments,function(segment){ - segment.save(); - }); - this.render(); - }, - - removeData: function(atIndex){ - var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; - this.segments.splice(indexToDelete, 1); - this.reflow(); - this.update(); - }, - - reflow : function(){ - helpers.extend(this.SegmentArc.prototype,{ - x : this.chart.width/2, - y : this.chart.height/2 - }); - this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; - helpers.each(this.segments, function(segment){ - segment.update({ - outerRadius : this.outerRadius, - innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout - }); - }, this); - }, - draw : function(easeDecimal){ - var animDecimal = (easeDecimal) ? easeDecimal : 1; - this.clear(); - helpers.each(this.segments,function(segment,index){ - segment.transition({ - circumference : this.calculateCircumference(segment.value), - outerRadius : this.outerRadius, - innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout - },animDecimal); - - segment.endAngle = segment.startAngle + segment.circumference; - - segment.draw(); - if (index === 0){ - segment.startAngle = Math.PI * 1.5; - } - //Check to see if it's the last segment, if not get the next and update the start angle - if (index < this.segments.length-1){ - this.segments[index+1].startAngle = segment.endAngle; - } - },this); - - } - }); - - Chart.types.Doughnut.extend({ - name : "Pie", - defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0}) - }); - -}).call(this); -(function(){ - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - var defaultConfig = { - - ///Boolean - Whether grid lines are shown across the chart - scaleShowGridLines : true, - - //String - Colour of the grid lines - scaleGridLineColor : "rgba(0,0,0,.05)", - - //Number - Width of the grid lines - scaleGridLineWidth : 1, - - //Boolean - Whether to show horizontal lines (except X axis) - scaleShowHorizontalLines: true, - - //Boolean - Whether to show vertical lines (except Y axis) - scaleShowVerticalLines: true, - - //Boolean - Whether the line is curved between points - bezierCurve : true, - - //Number - Tension of the bezier curve between points - bezierCurveTension : 0.4, - - //Boolean - Whether to show a dot for each point - pointDot : true, - - //Number - Radius of each point dot in pixels - pointDotRadius : 4, - - //Number - Pixel width of point dot stroke - pointDotStrokeWidth : 1, - - //Number - amount extra to add to the radius to cater for hit detection outside the drawn point - pointHitDetectionRadius : 20, - - //Boolean - Whether to show a stroke for datasets - datasetStroke : true, - - //Number - Pixel width of dataset stroke - datasetStrokeWidth : 2, - - //Boolean - Whether to fill the dataset with a colour - datasetFill : true, - - //String - A legend template - legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" - - }; - - - Chart.Type.extend({ - name: "Line", - defaults : defaultConfig, - initialize: function(data){ - //Declare the extension of the default point, to cater for the options passed in to the constructor - this.PointClass = Chart.Point.extend({ - strokeWidth : this.options.pointDotStrokeWidth, - radius : this.options.pointDotRadius, - display: this.options.pointDot, - hitDetectionRadius : this.options.pointHitDetectionRadius, - ctx : this.chart.ctx, - inRange : function(mouseX){ - return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2)); - } - }); - - this.datasets = []; - - //Set up tooltip events on the chart - if (this.options.showTooltips){ - helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ - var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; - this.eachPoints(function(point){ - point.restore(['fillColor', 'strokeColor']); - }); - helpers.each(activePoints, function(activePoint){ - activePoint.fillColor = activePoint.highlightFill; - activePoint.strokeColor = activePoint.highlightStroke; - }); - this.showTooltip(activePoints); - }); - } - - //Iterate through each of the datasets, and build this into a property of the chart - helpers.each(data.datasets,function(dataset){ - - var datasetObject = { - label : dataset.label || null, - fillColor : dataset.fillColor, - strokeColor : dataset.strokeColor, - pointColor : dataset.pointColor, - pointStrokeColor : dataset.pointStrokeColor, - points : [] - }; - - this.datasets.push(datasetObject); - - - helpers.each(dataset.data,function(dataPoint,index){ - //Add a new point for each piece of data, passing any required data to draw. - datasetObject.points.push(new this.PointClass({ - value : dataPoint, - label : data.labels[index], - datasetLabel: dataset.label, - strokeColor : dataset.pointStrokeColor, - fillColor : dataset.pointColor, - highlightFill : dataset.pointHighlightFill || dataset.pointColor, - highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor - })); - },this); - - this.buildScale(data.labels); - - - this.eachPoints(function(point, index){ - helpers.extend(point, { - x: this.scale.calculateX(index), - y: this.scale.endPoint - }); - point.save(); - }, this); - - },this); - - - this.render(); - }, - update : function(){ - this.scale.update(); - // Reset any highlight colours before updating. - helpers.each(this.activeElements, function(activeElement){ - activeElement.restore(['fillColor', 'strokeColor']); - }); - this.eachPoints(function(point){ - point.save(); - }); - this.render(); - }, - eachPoints : function(callback){ - helpers.each(this.datasets,function(dataset){ - helpers.each(dataset.points,callback,this); - },this); - }, - getPointsAtEvent : function(e){ - var pointsArray = [], - eventPosition = helpers.getRelativePosition(e); - helpers.each(this.datasets,function(dataset){ - helpers.each(dataset.points,function(point){ - if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point); - }); - },this); - return pointsArray; - }, - buildScale : function(labels){ - var self = this; - - var dataTotal = function(){ - var values = []; - self.eachPoints(function(point){ - values.push(point.value); - }); - - return values; - }; - - var scaleOptions = { - templateString : this.options.scaleLabel, - height : this.chart.height, - width : this.chart.width, - ctx : this.chart.ctx, - textColor : this.options.scaleFontColor, - fontSize : this.options.scaleFontSize, - fontStyle : this.options.scaleFontStyle, - fontFamily : this.options.scaleFontFamily, - valuesCount : labels.length, - beginAtZero : this.options.scaleBeginAtZero, - integersOnly : this.options.scaleIntegersOnly, - calculateYRange : function(currentHeight){ - var updatedRanges = helpers.calculateScaleRange( - dataTotal(), - currentHeight, - this.fontSize, - this.beginAtZero, - this.integersOnly - ); - helpers.extend(this, updatedRanges); - }, - xLabels : labels, - font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), - lineWidth : this.options.scaleLineWidth, - lineColor : this.options.scaleLineColor, - showHorizontalLines : this.options.scaleShowHorizontalLines, - showVerticalLines : this.options.scaleShowVerticalLines, - gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, - gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", - padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth, - showLabels : this.options.scaleShowLabels, - display : this.options.showScale - }; - - if (this.options.scaleOverride){ - helpers.extend(scaleOptions, { - calculateYRange: helpers.noop, - steps: this.options.scaleSteps, - stepValue: this.options.scaleStepWidth, - min: this.options.scaleStartValue, - max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) - }); - } - - - this.scale = new Chart.Scale(scaleOptions); - }, - addData : function(valuesArray,label){ - //Map the values array for each of the datasets - - helpers.each(valuesArray,function(value,datasetIndex){ - //Add a new point for each piece of data, passing any required data to draw. - this.datasets[datasetIndex].points.push(new this.PointClass({ - value : value, - label : label, - x: this.scale.calculateX(this.scale.valuesCount+1), - y: this.scale.endPoint, - strokeColor : this.datasets[datasetIndex].pointStrokeColor, - fillColor : this.datasets[datasetIndex].pointColor - })); - },this); - - this.scale.addXLabel(label); - //Then re-render the chart. - this.update(); - }, - removeData : function(){ - this.scale.removeXLabel(); - //Then re-render the chart. - helpers.each(this.datasets,function(dataset){ - dataset.points.shift(); - },this); - this.update(); - }, - reflow : function(){ - var newScaleProps = helpers.extend({ - height : this.chart.height, - width : this.chart.width - }); - this.scale.update(newScaleProps); - }, - draw : function(ease){ - var easingDecimal = ease || 1; - this.clear(); - - var ctx = this.chart.ctx; - - // Some helper methods for getting the next/prev points - var hasValue = function(item){ - return item.value !== null; - }, - nextPoint = function(point, collection, index){ - return helpers.findNextWhere(collection, hasValue, index) || point; - }, - previousPoint = function(point, collection, index){ - return helpers.findPreviousWhere(collection, hasValue, index) || point; - }; - - this.scale.draw(easingDecimal); - - - helpers.each(this.datasets,function(dataset){ - var pointsWithValues = helpers.where(dataset.points, hasValue); - - //Transition each point first so that the line and point drawing isn't out of sync - //We can use this extra loop to calculate the control points of this dataset also in this loop - - helpers.each(dataset.points, function(point, index){ - if (point.hasValue()){ - point.transition({ - y : this.scale.calculateY(point.value), - x : this.scale.calculateX(index) - }, easingDecimal); - } - },this); - - - // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point - // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed - if (this.options.bezierCurve){ - helpers.each(pointsWithValues, function(point, index){ - var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0; - point.controlPoints = helpers.splineCurve( - previousPoint(point, pointsWithValues, index), - point, - nextPoint(point, pointsWithValues, index), - tension - ); - - // Prevent the bezier going outside of the bounds of the graph - - // Cap puter bezier handles to the upper/lower scale bounds - if (point.controlPoints.outer.y > this.scale.endPoint){ - point.controlPoints.outer.y = this.scale.endPoint; - } - else if (point.controlPoints.outer.y < this.scale.startPoint){ - point.controlPoints.outer.y = this.scale.startPoint; - } - - // Cap inner bezier handles to the upper/lower scale bounds - if (point.controlPoints.inner.y > this.scale.endPoint){ - point.controlPoints.inner.y = this.scale.endPoint; - } - else if (point.controlPoints.inner.y < this.scale.startPoint){ - point.controlPoints.inner.y = this.scale.startPoint; - } - },this); - } - - - //Draw the line between all the points - ctx.lineWidth = this.options.datasetStrokeWidth; - ctx.strokeStyle = dataset.strokeColor; - ctx.beginPath(); - - helpers.each(pointsWithValues, function(point, index){ - if (index === 0){ - ctx.moveTo(point.x, point.y); - } - else{ - if(this.options.bezierCurve){ - var previous = previousPoint(point, pointsWithValues, index); - - ctx.bezierCurveTo( - previous.controlPoints.outer.x, - previous.controlPoints.outer.y, - point.controlPoints.inner.x, - point.controlPoints.inner.y, - point.x, - point.y - ); - } - else{ - ctx.lineTo(point.x,point.y); - } - } - }, this); - - ctx.stroke(); - - if (this.options.datasetFill && pointsWithValues.length > 0){ - //Round off the line by going to the base of the chart, back to the start, then fill. - ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint); - ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint); - ctx.fillStyle = dataset.fillColor; - ctx.closePath(); - ctx.fill(); - } - - //Now draw the points over the line - //A little inefficient double looping, but better than the line - //lagging behind the point positions - helpers.each(pointsWithValues,function(point){ - point.draw(); - }); - },this); - } - }); - - -}).call(this); - -(function(){ - "use strict"; - - var root = this, - Chart = root.Chart, - //Cache a local reference to Chart.helpers - helpers = Chart.helpers; - - var defaultConfig = { - //Boolean - Show a backdrop to the scale label - scaleShowLabelBackdrop : true, - - //String - The colour of the label backdrop - scaleBackdropColor : "rgba(255,255,255,0.75)", - - // Boolean - Whether the scale should begin at zero - scaleBeginAtZero : true, - - //Number - The backdrop padding above & below the label in pixels - scaleBackdropPaddingY : 2, - - //Number - The backdrop padding to the side of the label in pixels - scaleBackdropPaddingX : 2, - - //Boolean - Show line for each value in the scale - scaleShowLine : true, - - //Boolean - Stroke a line around each segment in the chart - segmentShowStroke : true, - - //String - The colour of the stroke on each segement. - segmentStrokeColor : "#fff", - - //Number - The width of the stroke value in pixels - segmentStrokeWidth : 2, - - //Number - Amount of animation steps - animationSteps : 100, - - //String - Animation easing effect. - animationEasing : "easeOutBounce", - - //Boolean - Whether to animate the rotation of the chart - animateRotate : true, - - //Boolean - Whether to animate scaling the chart from the centre - animateScale : false, - - //String - A legend template - legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
" - }; - - - Chart.Type.extend({ - //Passing in a name registers this chart in the Chart namespace - name: "PolarArea", - //Providing a defaults will also register the deafults in the chart namespace - defaults : defaultConfig, - //Initialize is fired when the chart is initialized - Data is passed in as a parameter - //Config is automatically merged by the core of Chart.js, and is available at this.options - initialize: function(data){ - this.segments = []; - //Declare segment class as a chart instance specific class, so it can share props for this instance - this.SegmentArc = Chart.Arc.extend({ - showStroke : this.options.segmentShowStroke, - strokeWidth : this.options.segmentStrokeWidth, - strokeColor : this.options.segmentStrokeColor, - ctx : this.chart.ctx, - innerRadius : 0, - x : this.chart.width/2, - y : this.chart.height/2 - }); - this.scale = new Chart.RadialScale({ - display: this.options.showScale, - fontStyle: this.options.scaleFontStyle, - fontSize: this.options.scaleFontSize, - fontFamily: this.options.scaleFontFamily, - fontColor: this.options.scaleFontColor, - showLabels: this.options.scaleShowLabels, - showLabelBackdrop: this.options.scaleShowLabelBackdrop, - backdropColor: this.options.scaleBackdropColor, - backdropPaddingY : this.options.scaleBackdropPaddingY, - backdropPaddingX: this.options.scaleBackdropPaddingX, - lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, - lineColor: this.options.scaleLineColor, - lineArc: true, - width: this.chart.width, - height: this.chart.height, - xCenter: this.chart.width/2, - yCenter: this.chart.height/2, - ctx : this.chart.ctx, - templateString: this.options.scaleLabel, - valuesCount: data.length - }); - - this.updateScaleRange(data); - - this.scale.update(); - - helpers.each(data,function(segment,index){ - this.addData(segment,index,true); - },this); - - //Set up tooltip events on the chart - if (this.options.showTooltips){ - helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ - var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; - helpers.each(this.segments,function(segment){ - segment.restore(["fillColor"]); - }); - helpers.each(activeSegments,function(activeSegment){ - activeSegment.fillColor = activeSegment.highlightColor; - }); - this.showTooltip(activeSegments); - }); - } - - this.render(); - }, - getSegmentsAtEvent : function(e){ - var segmentsArray = []; - - var location = helpers.getRelativePosition(e); - - helpers.each(this.segments,function(segment){ - if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); - },this); - return segmentsArray; - }, - addData : function(segment, atIndex, silent){ - var index = atIndex || this.segments.length; - - this.segments.splice(index, 0, new this.SegmentArc({ - fillColor: segment.color, - highlightColor: segment.highlight || segment.color, - label: segment.label, - value: segment.value, - outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value), - circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(), - startAngle: Math.PI * 1.5 - })); - if (!silent){ - this.reflow(); - this.update(); - } - }, - removeData: function(atIndex){ - var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; - this.segments.splice(indexToDelete, 1); - this.reflow(); - this.update(); - }, - calculateTotal: function(data){ - this.total = 0; - helpers.each(data,function(segment){ - this.total += segment.value; - },this); - this.scale.valuesCount = this.segments.length; - }, - updateScaleRange: function(datapoints){ - var valuesArray = []; - helpers.each(datapoints,function(segment){ - valuesArray.push(segment.value); - }); - - var scaleSizes = (this.options.scaleOverride) ? - { - steps: this.options.scaleSteps, - stepValue: this.options.scaleStepWidth, - min: this.options.scaleStartValue, - max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) - } : - helpers.calculateScaleRange( - valuesArray, - helpers.min([this.chart.width, this.chart.height])/2, - this.options.scaleFontSize, - this.options.scaleBeginAtZero, - this.options.scaleIntegersOnly - ); - - helpers.extend( - this.scale, - scaleSizes, - { - size: helpers.min([this.chart.width, this.chart.height]), - xCenter: this.chart.width/2, - yCenter: this.chart.height/2 - } - ); - - }, - update : function(){ - this.calculateTotal(this.segments); - - helpers.each(this.segments,function(segment){ - segment.save(); - }); - - this.reflow(); - this.render(); - }, - reflow : function(){ - helpers.extend(this.SegmentArc.prototype,{ - x : this.chart.width/2, - y : this.chart.height/2 - }); - this.updateScaleRange(this.segments); - this.scale.update(); - - helpers.extend(this.scale,{ - xCenter: this.chart.width/2, - yCenter: this.chart.height/2 - }); - - helpers.each(this.segments, function(segment){ - segment.update({ - outerRadius : this.scale.calculateCenterOffset(segment.value) - }); - }, this); - - }, - draw : function(ease){ - var easingDecimal = ease || 1; - //Clear & draw the canvas - this.clear(); - helpers.each(this.segments,function(segment, index){ - segment.transition({ - circumference : this.scale.getCircumference(), - outerRadius : this.scale.calculateCenterOffset(segment.value) - },easingDecimal); - - segment.endAngle = segment.startAngle + segment.circumference; - - // If we've removed the first segment we need to set the first one to - // start at the top. - if (index === 0){ - segment.startAngle = Math.PI * 1.5; - } - - //Check to see if it's the last segment, if not get the next and update the start angle - if (index < this.segments.length - 1){ - this.segments[index+1].startAngle = segment.endAngle; - } - segment.draw(); - }, this); - this.scale.draw(); - } - }); - -}).call(this); -(function(){ - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - - - Chart.Type.extend({ - name: "Radar", - defaults:{ - //Boolean - Whether to show lines for each scale point - scaleShowLine : true, - - //Boolean - Whether we show the angle lines out of the radar - angleShowLineOut : true, - - //Boolean - Whether to show labels on the scale - scaleShowLabels : false, - - // Boolean - Whether the scale should begin at zero - scaleBeginAtZero : true, - - //String - Colour of the angle line - angleLineColor : "rgba(0,0,0,.1)", - - //Number - Pixel width of the angle line - angleLineWidth : 1, - - //String - Point label font declaration - pointLabelFontFamily : "'Arial'", - - //String - Point label font weight - pointLabelFontStyle : "normal", - - //Number - Point label font size in pixels - pointLabelFontSize : 10, - - //String - Point label font colour - pointLabelFontColor : "#666", - - //Boolean - Whether to show a dot for each point - pointDot : true, - - //Number - Radius of each point dot in pixels - pointDotRadius : 3, - - //Number - Pixel width of point dot stroke - pointDotStrokeWidth : 1, - - //Number - amount extra to add to the radius to cater for hit detection outside the drawn point - pointHitDetectionRadius : 20, - - //Boolean - Whether to show a stroke for datasets - datasetStroke : true, - - //Number - Pixel width of dataset stroke - datasetStrokeWidth : 2, - - //Boolean - Whether to fill the dataset with a colour - datasetFill : true, - - //String - A legend template - legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" - - }, - - initialize: function(data){ - this.PointClass = Chart.Point.extend({ - strokeWidth : this.options.pointDotStrokeWidth, - radius : this.options.pointDotRadius, - display: this.options.pointDot, - hitDetectionRadius : this.options.pointHitDetectionRadius, - ctx : this.chart.ctx - }); - - this.datasets = []; - - this.buildScale(data); - - //Set up tooltip events on the chart - if (this.options.showTooltips){ - helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ - var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; - - this.eachPoints(function(point){ - point.restore(['fillColor', 'strokeColor']); - }); - helpers.each(activePointsCollection, function(activePoint){ - activePoint.fillColor = activePoint.highlightFill; - activePoint.strokeColor = activePoint.highlightStroke; - }); - - this.showTooltip(activePointsCollection); - }); - } - - //Iterate through each of the datasets, and build this into a property of the chart - helpers.each(data.datasets,function(dataset){ - - var datasetObject = { - label: dataset.label || null, - fillColor : dataset.fillColor, - strokeColor : dataset.strokeColor, - pointColor : dataset.pointColor, - pointStrokeColor : dataset.pointStrokeColor, - points : [] - }; - - this.datasets.push(datasetObject); - - helpers.each(dataset.data,function(dataPoint,index){ - //Add a new point for each piece of data, passing any required data to draw. - var pointPosition; - if (!this.scale.animation){ - pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint)); - } - datasetObject.points.push(new this.PointClass({ - value : dataPoint, - label : data.labels[index], - datasetLabel: dataset.label, - x: (this.options.animation) ? this.scale.xCenter : pointPosition.x, - y: (this.options.animation) ? this.scale.yCenter : pointPosition.y, - strokeColor : dataset.pointStrokeColor, - fillColor : dataset.pointColor, - highlightFill : dataset.pointHighlightFill || dataset.pointColor, - highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor - })); - },this); - - },this); - - this.render(); - }, - eachPoints : function(callback){ - helpers.each(this.datasets,function(dataset){ - helpers.each(dataset.points,callback,this); - },this); - }, - - getPointsAtEvent : function(evt){ - var mousePosition = helpers.getRelativePosition(evt), - fromCenter = helpers.getAngleFromPoint({ - x: this.scale.xCenter, - y: this.scale.yCenter - }, mousePosition); - - var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount, - pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex), - activePointsCollection = []; - - // If we're at the top, make the pointIndex 0 to get the first of the array. - if (pointIndex >= this.scale.valuesCount || pointIndex < 0){ - pointIndex = 0; - } - - if (fromCenter.distance <= this.scale.drawingArea){ - helpers.each(this.datasets, function(dataset){ - activePointsCollection.push(dataset.points[pointIndex]); - }); - } - - return activePointsCollection; - }, - - buildScale : function(data){ - this.scale = new Chart.RadialScale({ - display: this.options.showScale, - fontStyle: this.options.scaleFontStyle, - fontSize: this.options.scaleFontSize, - fontFamily: this.options.scaleFontFamily, - fontColor: this.options.scaleFontColor, - showLabels: this.options.scaleShowLabels, - showLabelBackdrop: this.options.scaleShowLabelBackdrop, - backdropColor: this.options.scaleBackdropColor, - backdropPaddingY : this.options.scaleBackdropPaddingY, - backdropPaddingX: this.options.scaleBackdropPaddingX, - lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, - lineColor: this.options.scaleLineColor, - angleLineColor : this.options.angleLineColor, - angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0, - // Point labels at the edge of each line - pointLabelFontColor : this.options.pointLabelFontColor, - pointLabelFontSize : this.options.pointLabelFontSize, - pointLabelFontFamily : this.options.pointLabelFontFamily, - pointLabelFontStyle : this.options.pointLabelFontStyle, - height : this.chart.height, - width: this.chart.width, - xCenter: this.chart.width/2, - yCenter: this.chart.height/2, - ctx : this.chart.ctx, - templateString: this.options.scaleLabel, - labels: data.labels, - valuesCount: data.datasets[0].data.length - }); - - this.scale.setScaleSize(); - this.updateScaleRange(data.datasets); - this.scale.buildYLabels(); - }, - updateScaleRange: function(datasets){ - var valuesArray = (function(){ - var totalDataArray = []; - helpers.each(datasets,function(dataset){ - if (dataset.data){ - totalDataArray = totalDataArray.concat(dataset.data); - } - else { - helpers.each(dataset.points, function(point){ - totalDataArray.push(point.value); - }); - } - }); - return totalDataArray; - })(); - - - var scaleSizes = (this.options.scaleOverride) ? - { - steps: this.options.scaleSteps, - stepValue: this.options.scaleStepWidth, - min: this.options.scaleStartValue, - max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) - } : - helpers.calculateScaleRange( - valuesArray, - helpers.min([this.chart.width, this.chart.height])/2, - this.options.scaleFontSize, - this.options.scaleBeginAtZero, - this.options.scaleIntegersOnly - ); - - helpers.extend( - this.scale, - scaleSizes - ); - - }, - addData : function(valuesArray,label){ - //Map the values array for each of the datasets - this.scale.valuesCount++; - helpers.each(valuesArray,function(value,datasetIndex){ - var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value)); - this.datasets[datasetIndex].points.push(new this.PointClass({ - value : value, - label : label, - x: pointPosition.x, - y: pointPosition.y, - strokeColor : this.datasets[datasetIndex].pointStrokeColor, - fillColor : this.datasets[datasetIndex].pointColor - })); - },this); - - this.scale.labels.push(label); - - this.reflow(); - - this.update(); - }, - removeData : function(){ - this.scale.valuesCount--; - this.scale.labels.shift(); - helpers.each(this.datasets,function(dataset){ - dataset.points.shift(); - },this); - this.reflow(); - this.update(); - }, - update : function(){ - this.eachPoints(function(point){ - point.save(); - }); - this.reflow(); - this.render(); - }, - reflow: function(){ - helpers.extend(this.scale, { - width : this.chart.width, - height: this.chart.height, - size : helpers.min([this.chart.width, this.chart.height]), - xCenter: this.chart.width/2, - yCenter: this.chart.height/2 - }); - this.updateScaleRange(this.datasets); - this.scale.setScaleSize(); - this.scale.buildYLabels(); - }, - draw : function(ease){ - var easeDecimal = ease || 1, - ctx = this.chart.ctx; - this.clear(); - this.scale.draw(); - - helpers.each(this.datasets,function(dataset){ - - //Transition each point first so that the line and point drawing isn't out of sync - helpers.each(dataset.points,function(point,index){ - if (point.hasValue()){ - point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal); - } - },this); - - - - //Draw the line between all the points - ctx.lineWidth = this.options.datasetStrokeWidth; - ctx.strokeStyle = dataset.strokeColor; - ctx.beginPath(); - helpers.each(dataset.points,function(point,index){ - if (index === 0){ - ctx.moveTo(point.x,point.y); - } - else{ - ctx.lineTo(point.x,point.y); - } - },this); - ctx.closePath(); - ctx.stroke(); - - ctx.fillStyle = dataset.fillColor; - ctx.fill(); - - //Now draw the points over the line - //A little inefficient double looping, but better than the line - //lagging behind the point positions - helpers.each(dataset.points,function(point){ - if (point.hasValue()){ - point.draw(); - } - }); - - },this); - - } - - }); - - - - - -}).call(this); + }; + + },{}],15:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.bar = { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + + // Specific to Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }], + yAxes: [{ + type: 'linear' + }] + } + }; + + Chart.controllers.bar = Chart.DatasetController.extend({ + + dataElementType: Chart.elements.Rectangle, + + initialize: function(chart, datasetIndex) { + Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); + + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + + meta.stack = dataset.stack; + // Use this to indicate that this is a bar dataset. + meta.bar = true; + }, + + // Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible + getStackCount: function() { + var me = this; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + + var stacks = []; + helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { + var dsMeta = me.chart.getDatasetMeta(datasetIndex); + if (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) && + (yScale.options.stacked === false || + (yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || + (yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { + stacks.push(dsMeta.stack); + } + }, me); + + return stacks.length; + }, + + update: function(reset) { + var me = this; + helpers.each(me.getMeta().data, function(rectangle, index) { + me.updateElement(rectangle, index, reset); + }, me); + }, + + updateElement: function(rectangle, index, reset) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var scaleBase = yScale.getBasePixel(); + var rectangleElementOptions = me.chart.options.elements.rectangle; + var custom = rectangle.custom || {}; + var dataset = me.getDataset(); + + rectangle._xScale = xScale; + rectangle._yScale = yScale; + rectangle._datasetIndex = me.index; + rectangle._index = index; + + var ruler = me.getRuler(index); // The index argument for compatible + rectangle._model = { + x: me.calculateBarX(index, me.index, ruler), + y: reset ? scaleBase : me.calculateBarY(index, me.index), + + // Tooltip + label: me.chart.data.labels[index], + datasetLabel: dataset.label, + + // Appearance + horizontal: false, + base: reset ? scaleBase : me.calculateBarBase(me.index, index), + width: me.calculateBarWidth(ruler), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), + borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, + borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) + }; + + rectangle.pivot(); + }, + + calculateBarBase: function(datasetIndex, index) { + var me = this; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var base = yScale.getBaseValue(); + var original = base; + + if ((yScale.options.stacked === true) || + (yScale.options.stacked === undefined && meta.stack !== undefined)) { + var chart = me.chart; + var datasets = chart.data.datasets; + var value = Number(datasets[datasetIndex].data[index]); + + for (var i = 0; i < datasetIndex; i++) { + var currentDs = datasets[i]; + var currentDsMeta = chart.getDatasetMeta(i); + if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i) && + meta.stack === currentDsMeta.stack) { + var currentVal = Number(currentDs.data[index]); + base += value < 0 ? Math.min(currentVal, original) : Math.max(currentVal, original); + } + } + + return yScale.getPixelForValue(base); + } + + return yScale.getBasePixel(); + }, + + getRuler: function() { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var stackCount = me.getStackCount(); + + var tickWidth = xScale.width / xScale.ticks.length; + var categoryWidth = tickWidth * xScale.options.categoryPercentage; + var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2; + var fullBarWidth = categoryWidth / stackCount; + + var barWidth = fullBarWidth * xScale.options.barPercentage; + var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); + + return { + stackCount: stackCount, + tickWidth: tickWidth, + categoryWidth: categoryWidth, + categorySpacing: categorySpacing, + fullBarWidth: fullBarWidth, + barWidth: barWidth, + barSpacing: barSpacing + }; + }, + + calculateBarWidth: function(ruler) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + if (xScale.options.barThickness) { + return xScale.options.barThickness; + } + return ruler.barWidth; + }, + + // Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible + getStackIndex: function(datasetIndex) { + var me = this; + var meta = me.chart.getDatasetMeta(datasetIndex); + var yScale = me.getScaleForId(meta.yAxisID); + var dsMeta, j; + var stacks = [meta.stack]; + + for (j = 0; j < datasetIndex; ++j) { + dsMeta = this.chart.getDatasetMeta(j); + if (dsMeta.bar && this.chart.isDatasetVisible(j) && + (yScale.options.stacked === false || + (yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || + (yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { + stacks.push(dsMeta.stack); + } + } + + return stacks.length - 1; + }, + + calculateBarX: function(index, datasetIndex, ruler) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var stackIndex = me.getStackIndex(datasetIndex); + var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); + leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0; + + return leftTick + + (ruler.barWidth / 2) + + ruler.categorySpacing + + (ruler.barWidth * stackIndex) + + (ruler.barSpacing / 2) + + (ruler.barSpacing * stackIndex); + }, + + calculateBarY: function(index, datasetIndex) { + var me = this; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var value = Number(me.getDataset().data[index]); + + if (yScale.options.stacked || + (yScale.options.stacked === undefined && meta.stack !== undefined)) { + var base = yScale.getBaseValue(); + var sumPos = base, + sumNeg = base; + + for (var i = 0; i < datasetIndex; i++) { + var ds = me.chart.data.datasets[i]; + var dsMeta = me.chart.getDatasetMeta(i); + if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i) && + meta.stack === dsMeta.stack) { + var stackedVal = Number(ds.data[index]); + if (stackedVal < 0) { + sumNeg += stackedVal || 0; + } else { + sumPos += stackedVal || 0; + } + } + } + + if (value < 0) { + return yScale.getPixelForValue(sumNeg + value); + } + return yScale.getPixelForValue(sumPos + value); + } + + return yScale.getPixelForValue(value); + }, + + draw: function(ease) { + var me = this; + var easingDecimal = ease || 1; + var metaData = me.getMeta().data; + var dataset = me.getDataset(); + var i, len; + + Chart.canvasHelpers.clipArea(me.chart.chart.ctx, me.chart.chartArea); + for (i = 0, len = metaData.length; i < len; ++i) { + var d = dataset.data[i]; + if (d !== null && d !== undefined && !isNaN(d)) { + metaData[i].transition(easingDecimal).draw(); + } + } + Chart.canvasHelpers.unclipArea(me.chart.chart.ctx); + }, + + setHoverStyle: function(rectangle) { + var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + var index = rectangle._index; + + var custom = rectangle.custom || {}; + var model = rectangle._model; + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); + }, + + removeHoverStyle: function(rectangle) { + var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + var index = rectangle._index; + var custom = rectangle.custom || {}; + var model = rectangle._model; + var rectangleElementOptions = this.chart.options.elements.rectangle; + + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth); + } + + }); + + + // including horizontalBar in the bar file, instead of a file of its own + // it extends bar (like pie extends doughnut) + Chart.defaults.horizontalBar = { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + yAxes: [{ + position: 'left', + type: 'category', + + // Specific to Horizontal Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }] + }, + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + tooltips: { + callbacks: { + title: function(tooltipItems, data) { + // Pick first xLabel for now + var title = ''; + + if (tooltipItems.length > 0) { + if (tooltipItems[0].yLabel) { + title = tooltipItems[0].yLabel; + } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) { + title = data.labels[tooltipItems[0].index]; + } + } + + return title; + }, + label: function(tooltipItem, data) { + var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; + return datasetLabel + ': ' + tooltipItem.xLabel; + } + } + } + }; + + Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ + + // Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible + getStackCount: function() { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + + var stacks = []; + helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { + var dsMeta = me.chart.getDatasetMeta(datasetIndex); + if (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) && + (xScale.options.stacked === false || + (xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || + (xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { + stacks.push(dsMeta.stack); + } + }, me); + + return stacks.length; + }, + + updateElement: function(rectangle, index, reset) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var scaleBase = xScale.getBasePixel(); + var custom = rectangle.custom || {}; + var dataset = me.getDataset(); + var rectangleElementOptions = me.chart.options.elements.rectangle; + + rectangle._xScale = xScale; + rectangle._yScale = yScale; + rectangle._datasetIndex = me.index; + rectangle._index = index; + + var ruler = me.getRuler(index); // The index argument for compatible + rectangle._model = { + x: reset ? scaleBase : me.calculateBarX(index, me.index), + y: me.calculateBarY(index, me.index, ruler), + + // Tooltip + label: me.chart.data.labels[index], + datasetLabel: dataset.label, + + // Appearance + horizontal: true, + base: reset ? scaleBase : me.calculateBarBase(me.index, index), + height: me.calculateBarHeight(ruler), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), + borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, + borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) + }; + + rectangle.pivot(); + }, + + calculateBarBase: function(datasetIndex, index) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var base = xScale.getBaseValue(); + var originalBase = base; + + if (xScale.options.stacked || + (xScale.options.stacked === undefined && meta.stack !== undefined)) { + var chart = me.chart; + var datasets = chart.data.datasets; + var value = Number(datasets[datasetIndex].data[index]); + + for (var i = 0; i < datasetIndex; i++) { + var currentDs = datasets[i]; + var currentDsMeta = chart.getDatasetMeta(i); + if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i) && + meta.stack === currentDsMeta.stack) { + var currentVal = Number(currentDs.data[index]); + base += value < 0 ? Math.min(currentVal, originalBase) : Math.max(currentVal, originalBase); + } + } + + return xScale.getPixelForValue(base); + } + + return xScale.getBasePixel(); + }, + + getRuler: function() { + var me = this; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var stackCount = me.getStackCount(); + + var tickHeight = yScale.height / yScale.ticks.length; + var categoryHeight = tickHeight * yScale.options.categoryPercentage; + var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2; + var fullBarHeight = categoryHeight / stackCount; + + var barHeight = fullBarHeight * yScale.options.barPercentage; + var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage); + + return { + stackCount: stackCount, + tickHeight: tickHeight, + categoryHeight: categoryHeight, + categorySpacing: categorySpacing, + fullBarHeight: fullBarHeight, + barHeight: barHeight, + barSpacing: barSpacing + }; + }, + + calculateBarHeight: function(ruler) { + var me = this; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + if (yScale.options.barThickness) { + return yScale.options.barThickness; + } + return ruler.barHeight; + }, + + // Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible + getStackIndex: function(datasetIndex) { + var me = this; + var meta = me.chart.getDatasetMeta(datasetIndex); + var xScale = me.getScaleForId(meta.xAxisID); + var dsMeta, j; + var stacks = [meta.stack]; + + for (j = 0; j < datasetIndex; ++j) { + dsMeta = this.chart.getDatasetMeta(j); + if (dsMeta.bar && this.chart.isDatasetVisible(j) && + (xScale.options.stacked === false || + (xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || + (xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { + stacks.push(dsMeta.stack); + } + } + + return stacks.length - 1; + }, + + calculateBarX: function(index, datasetIndex) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var value = Number(me.getDataset().data[index]); + + if (xScale.options.stacked || + (xScale.options.stacked === undefined && meta.stack !== undefined)) { + var base = xScale.getBaseValue(); + var sumPos = base, + sumNeg = base; + + for (var i = 0; i < datasetIndex; i++) { + var ds = me.chart.data.datasets[i]; + var dsMeta = me.chart.getDatasetMeta(i); + if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i) && + meta.stack === dsMeta.stack) { + var stackedVal = Number(ds.data[index]); + if (stackedVal < 0) { + sumNeg += stackedVal || 0; + } else { + sumPos += stackedVal || 0; + } + } + } + + if (value < 0) { + return xScale.getPixelForValue(sumNeg + value); + } + return xScale.getPixelForValue(sumPos + value); + } + + return xScale.getPixelForValue(value); + }, + + calculateBarY: function(index, datasetIndex, ruler) { + var me = this; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var stackIndex = me.getStackIndex(datasetIndex); + var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); + topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0; + + return topTick + + (ruler.barHeight / 2) + + ruler.categorySpacing + + (ruler.barHeight * stackIndex) + + (ruler.barSpacing / 2) + + (ruler.barSpacing * stackIndex); + } + }); + }; + + },{}],16:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.bubble = { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + type: 'linear', // bubble should probably use a linear scale by default + position: 'bottom', + id: 'x-axis-0' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-0' + }] + }, + + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(tooltipItem, data) { + var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; + var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + return datasetLabel + ': (' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ', ' + dataPoint.r + ')'; + } + } + } + }; + + Chart.controllers.bubble = Chart.DatasetController.extend({ + + dataElementType: Chart.elements.Point, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, + + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + + var custom = point.custom || {}; + var dataset = me.getDataset(); + var data = dataset.data[index]; + var pointElementOptions = me.chart.options.elements.point; + var dsIndex = me.index; + + helpers.extend(point, { + // Utility + _xScale: xScale, + _yScale: yScale, + _datasetIndex: dsIndex, + _index: index, + + // Desired view properties + _model: { + x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex, me.chart.isCombo), + y: reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex), + // Appearance + radius: reset ? 0 : custom.radius ? custom.radius : me.getRadius(data), + + // Tooltip + hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) + } + }); + + // Trick to reset the styles of the point + Chart.DatasetController.prototype.removeHoverStyle.call(me, point, pointElementOptions); + + var model = point._model; + model.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y)); + + point.pivot(); + }, + + getRadius: function(value) { + return value.r || this.chart.options.elements.point.radius; + }, + + setHoverStyle: function(point) { + var me = this; + Chart.DatasetController.prototype.setHoverStyle.call(me, point); + + // Radius + var dataset = me.chart.data.datasets[point._datasetIndex]; + var index = point._index; + var custom = point.custom || {}; + var model = point._model; + model.radius = custom.hoverRadius ? custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, me.chart.options.elements.point.hoverRadius)) + me.getRadius(dataset.data[index]); + }, + + removeHoverStyle: function(point) { + var me = this; + Chart.DatasetController.prototype.removeHoverStyle.call(me, point, me.chart.options.elements.point); + + var dataVal = me.chart.data.datasets[point._datasetIndex].data[point._index]; + var custom = point.custom || {}; + var model = point._model; + + model.radius = custom.radius ? custom.radius : me.getRadius(dataVal); + } + }); + }; + + },{}],17:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + var helpers = Chart.helpers, + defaults = Chart.defaults; + + defaults.doughnut = { + animation: { + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + aspectRatio: 1, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var text = []; + text.push('
    '); + + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + + if (datasets.length) { + for (var i = 0; i < datasets[0].data.length; ++i) { + text.push('
  • '); + if (labels[i]) { + text.push(labels[i]); + } + text.push('
  • '); + } + } + + text.push('
'); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc && arc.custom || {}; + var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } + } + + chart.update(); + } + }, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: Math.PI * -0.5, + + // The total circumference of the chart. + circumference: Math.PI * 2.0, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } + }; + + defaults.pie = helpers.clone(defaults.doughnut); + helpers.extend(defaults.pie, { + cutoutPercentage: 0 + }); + + + Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ + + dataElementType: Chart.elements.Arc, + + linkScales: helpers.noop, + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function(datasetIndex) { + var ringIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } + + return ringIndex; + }, + + update: function(reset) { + var me = this; + var chart = me.chart, + chartArea = chart.chartArea, + opts = chart.options, + arcOpts = opts.elements.arc, + availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth, + availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth, + minSize = Math.min(availableWidth, availableHeight), + offset = { + x: 0, + y: 0 + }, + meta = me.getMeta(), + cutoutPercentage = opts.cutoutPercentage, + circumference = opts.circumference; + + // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc + if (circumference < Math.PI * 2.0) { + var startAngle = opts.rotation % (Math.PI * 2.0); + startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); + var endAngle = startAngle + circumference; + var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; + var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; + var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); + var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); + var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); + var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); + var cutout = cutoutPercentage / 100.0; + 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))}; + 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))}; + var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; + minSize = Math.min(availableWidth / size.width, availableHeight / size.height); + offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; + } + + chart.borderWidth = me.getMaxBorderWidth(meta.data); + chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); + chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + chart.offsetX = offset.x * chart.outerRadius; + chart.offsetY = offset.y * chart.outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); + + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart, + chartArea = chart.chartArea, + opts = chart.options, + animationOpts = opts.animation, + centerX = (chartArea.left + chartArea.right) / 2, + centerY = (chartArea.top + chartArea.bottom) / 2, + startAngle = opts.rotation, // non reset case handled later + endAngle = opts.rotation, // non reset case handled later + dataset = me.getDataset(), + circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)), + innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius, + outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius, + valueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + // Resets the visual styles + this.removeHoverStyle(arc); + + // Set correct angles if not resetting + if (!reset || !animationOpts.animateRotate) { + if (index === 0) { + model.startAngle = opts.rotation; + } else { + model.startAngle = me.getMeta().data[index - 1]._model.endAngle; + } + + model.endAngle = model.startAngle + model.circumference; + } + + arc.pivot(); + }, + + removeHoverStyle: function(arc) { + Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); + }, + + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; + + helpers.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); + + /* if (total === 0) { + total = NaN; + }*/ + + return total; + }, + + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return (Math.PI * 2.0) * (value / total); + } + return 0; + }, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(elements) { + var max = 0, + index = this.index, + length = elements.length, + borderWidth, + hoverWidth; + + for (var i = 0; i < length; i++) { + borderWidth = elements[i]._model ? elements[i]._model.borderWidth : 0; + hoverWidth = elements[i]._chart ? elements[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + return max; + } + }); + }; + + },{}],18:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.line = { + showLines: true, + spanGaps: false, + + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + id: 'x-axis-0' + }], + yAxes: [{ + type: 'linear', + id: 'y-axis-0' + }] + } + }; + + function lineEnabled(dataset, options) { + return helpers.getValueOrDefault(dataset.showLine, options.showLines); + } + + Chart.controllers.line = Chart.DatasetController.extend({ + + datasetElementType: Chart.elements.Line, + + dataElementType: Chart.elements.Point, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var lineElementOptions = options.elements.line; + var scale = me.getScaleForId(meta.yAxisID); + var i, ilen, custom; + var dataset = me.getDataset(); + var showLine = lineEnabled(dataset, options); + + // Update Line + if (showLine) { + custom = line.custom || {}; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } + + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = { + // Appearance + // The default behavior of lines is to break at null values, according + // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 + // This option gives lines the ability to span gaps + spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, + tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + steppedLine: custom.steppedLine ? custom.steppedLine : helpers.getValueOrDefault(dataset.steppedLine, lineElementOptions.stepped), + cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.getValueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode), + // Scale + scaleTop: scale.top, + scaleBottom: scale.bottom, + scaleZero: scale.getBasePixel() + }; + + line.pivot(); + } + + // Update Points + for (i=0, ilen=points.length; i'); + + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + + if (datasets.length) { + for (var i = 0; i < datasets[0].data.length; ++i) { + text.push('
  • '); + if (labels[i]) { + text.push(labels[i]); + } + text.push('
  • '); + } + } + + text.push(''); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc.custom || {}; + var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel; + } + } + } + }; + + Chart.controllers.polarArea = Chart.DatasetController.extend({ + + dataElementType: Chart.elements.Arc, + + linkScales: helpers.noop, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var meta = me.getMeta(); + var opts = chart.options; + var arcOpts = opts.elements.arc; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + + meta.count = me.countVisibleElements(); + + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; + var labels = chart.data.labels; + + var circumference = me.calculateCircumference(dataset.data[index]); + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // If there is NaN data before us, we need to calculate the starting angle correctly. + // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data + var visibleCount = 0; + var meta = me.getMeta(); + for (var i = 0; i < index; ++i) { + if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) { + ++visibleCount; + } + } + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = datasetStartAngle + (circumference * visibleCount); + var endAngle = startAngle + (arc.hidden ? 0 : circumference); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: getValueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + // Apply border and fill style + me.removeHoverStyle(arc); + + arc.pivot(); + }, + + removeHoverStyle: function(arc) { + Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); + }, + + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + calculateCircumference: function(value) { + var count = this.getMeta().count; + if (count > 0 && !isNaN(value)) { + return (2 * Math.PI) / count; + } + return 0; + } + }); + }; + + },{}],20:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.radar = { + aspectRatio: 1, + scale: { + type: 'radialLinear' + }, + elements: { + line: { + tension: 0 // no bezier in radar + } + } + }; + + Chart.controllers.radar = Chart.DatasetController.extend({ + + datasetElementType: Chart.elements.Line, + + dataElementType: Chart.elements.Point, + + linkScales: helpers.noop, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data; + var custom = line.custom || {}; + var dataset = me.getDataset(); + var lineElementOptions = me.chart.options.elements.line; + var scale = me.chart.scale; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } + + helpers.extend(meta.dataset, { + // Utility + _datasetIndex: me.index, + // Data + _children: points, + _loop: true, + // Model + _model: { + // Appearance + tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + + // Scale + scaleTop: scale.top, + scaleBottom: scale.bottom, + scaleZero: scale.getBasePosition() + } + }); + + meta.dataset.pivot(); + + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }, me); + + // Update bezier control points + me.updateBezierControlPoints(); + }, + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointElementOptions = me.chart.options.elements.point; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + + helpers.extend(point, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales + y: reset ? scale.yCenter : pointPosition.y, + + // Appearance + tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), + radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), + borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), + pointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), + + // Tooltip + hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) + } + }); + + point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); + }, + updateBezierControlPoints: function() { + var chartArea = this.chart.chartArea; + var meta = this.getMeta(); + + helpers.each(meta.data, function(point, index) { + var model = point._model; + var controlPoints = helpers.splineCurve( + helpers.previousItem(meta.data, index, true)._model, + model, + helpers.nextItem(meta.data, index, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); + model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); + + model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); + model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); + + // Now pivot the point for animation + point.pivot(); + }); + }, + + draw: function(ease) { + var meta = this.getMeta(); + var easingDecimal = ease || 1; + + // Transition Point Locations + helpers.each(meta.data, function(point) { + point.transition(easingDecimal); + }); + + // Transition and Draw the line + meta.dataset.transition(easingDecimal).draw(); + + // Draw the points + helpers.each(meta.data, function(point) { + point.draw(); + }); + }, + + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + + model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + }, + + removeHoverStyle: function(point) { + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + var pointElementOptions = this.chart.options.elements.point; + + model.radius = custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, pointElementOptions.radius); + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth); + } + }); + }; + + },{}],21:[function(require,module,exports){ + /* global window: false */ + 'use strict'; + + module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.global.animation = { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers.noop, + onComplete: helpers.noop + }; + + Chart.Animation = Chart.Element.extend({ + currentStep: null, // the current animation step + numSteps: 60, // default number of steps + easing: '', // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null // user specified callback to fire when the animation finishes + }); + + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + request: null, + + /** + * @function Chart.animationService.addAnimation + * @param chartInstance {ChartController} the chart to animate + * @param animationObject {IAnimation} the animation that we will animate + * @param duration {Number} length of animation in ms + * @param lazy {Boolean} if true, the chart is not marked as animating to enable more responsive interactions + */ + addAnimation: function(chartInstance, animationObject, duration, lazy) { + var me = this; + + if (!lazy) { + chartInstance.animating = true; + } + + for (var index = 0; index < me.animations.length; ++index) { + if (me.animations[index].chartInstance === chartInstance) { + // replacing an in progress animation + me.animations[index].animationObject = animationObject; + return; + } + } + + me.animations.push({ + chartInstance: chartInstance, + animationObject: animationObject + }); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (me.animations.length === 1) { + me.requestAnimationFrame(); + } + }, + // Cancel the animation for a given chart instance + cancelAnimation: function(chartInstance) { + var index = helpers.findIndex(this.animations, function(animationWrapper) { + return animationWrapper.chartInstance === chartInstance; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chartInstance.animating = false; + } + }, + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers.requestAnimFrame.call(window, function() { + me.request = null; + me.startDigest(); + }); + } + }, + startDigest: function() { + var me = this; + + var startTime = Date.now(); + var framesToDrop = 0; + + if (me.dropFrames > 1) { + framesToDrop = Math.floor(me.dropFrames); + me.dropFrames = me.dropFrames % 1; + } + + var i = 0; + while (i < me.animations.length) { + if (me.animations[i].animationObject.currentStep === null) { + me.animations[i].animationObject.currentStep = 0; + } + + me.animations[i].animationObject.currentStep += 1 + framesToDrop; + + if (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) { + me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps; + } + + me.animations[i].animationObject.render(me.animations[i].chartInstance, me.animations[i].animationObject); + if (me.animations[i].animationObject.onAnimationProgress && me.animations[i].animationObject.onAnimationProgress.call) { + me.animations[i].animationObject.onAnimationProgress.call(me.animations[i].chartInstance, me.animations[i]); + } + + if (me.animations[i].animationObject.currentStep === me.animations[i].animationObject.numSteps) { + if (me.animations[i].animationObject.onAnimationComplete && me.animations[i].animationObject.onAnimationComplete.call) { + me.animations[i].animationObject.onAnimationComplete.call(me.animations[i].chartInstance, me.animations[i]); + } + + // executed the last frame. Remove the animation. + me.animations[i].chartInstance.animating = false; + + me.animations.splice(i, 1); + } else { + ++i; + } + } + + var endTime = Date.now(); + var dropFrames = (endTime - startTime) / me.frameDuration; + + me.dropFrames += dropFrames; + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + } + }; + }; + + },{}],22:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + // Global Chart canvas helpers object for drawing items to canvas + var helpers = Chart.canvasHelpers = {}; + + helpers.drawPoint = function(ctx, pointStyle, radius, x, y) { + var type, edgeLength, xOffset, yOffset, height, size; + + if (typeof pointStyle === 'object') { + type = pointStyle.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + switch (pointStyle) { + // Default includes circle + default: + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.fill(); + break; + case 'triangle': + ctx.beginPath(); + edgeLength = 3 * radius / Math.sqrt(3); + height = edgeLength * Math.sqrt(3) / 2; + ctx.moveTo(x - edgeLength / 2, y + height / 3); + ctx.lineTo(x + edgeLength / 2, y + height / 3); + ctx.lineTo(x, y - 2 * height / 3); + ctx.closePath(); + ctx.fill(); + break; + case 'rect': + size = 1 / Math.SQRT2 * radius; + ctx.beginPath(); + ctx.fillRect(x - size, y - size, 2 * size, 2 * size); + ctx.strokeRect(x - size, y - size, 2 * size, 2 * size); + break; + case 'rectRounded': + var offset = radius / Math.SQRT2; + var leftX = x - offset; + var topY = y - offset; + var sideSize = Math.SQRT2 * radius; + Chart.helpers.drawRoundedRectangle(ctx, leftX, topY, sideSize, sideSize, radius / 2); + ctx.fill(); + break; + case 'rectRot': + size = 1 / Math.SQRT2 * radius; + ctx.beginPath(); + ctx.moveTo(x - size, y); + ctx.lineTo(x, y + size); + ctx.lineTo(x + size, y); + ctx.lineTo(x, y - size); + ctx.closePath(); + ctx.fill(); + break; + case 'cross': + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y - radius); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + case 'crossRot': + ctx.beginPath(); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x - xOffset, y + yOffset); + ctx.lineTo(x + xOffset, y - yOffset); + ctx.closePath(); + break; + case 'star': + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y - radius); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x - xOffset, y + yOffset); + ctx.lineTo(x + xOffset, y - yOffset); + ctx.closePath(); + break; + case 'line': + ctx.beginPath(); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + case 'dash': + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + } + + ctx.stroke(); + }; + + helpers.clipArea = function(ctx, clipArea) { + ctx.save(); + ctx.beginPath(); + ctx.rect(clipArea.left, clipArea.top, clipArea.right - clipArea.left, clipArea.bottom - clipArea.top); + ctx.clip(); + }; + + helpers.unclipArea = function(ctx) { + ctx.restore(); + }; + + }; + + },{}],23:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + var helpers = Chart.helpers; + var plugins = Chart.plugins; + var platform = Chart.platform; + + // Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; + + // Store a reference to each instance - allowing us to globally resize chart instances on window resize. + // Destroy method on the chart will remove the instance of the chart from this reference. + Chart.instances = {}; + + // Controllers available for dataset visualization eg. bar, line, slice, etc. + Chart.controllers = {}; + + /** + * Initializes the given config with global and chart default values. + */ + function initConfig(config) { + config = config || {}; + + // Do NOT use configMerge() for the data object because this method merges arrays + // and so would change references to labels and datasets, preventing data updates. + var data = config.data = config.data || {}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; + + config.options = helpers.configMerge( + Chart.defaults.global, + Chart.defaults[config.type], + config.options || {}); + + return config; + } + + /** + * Updates the config of the chart + * @param chart {Chart.Controller} chart to update the options for + */ + function updateConfig(chart) { + var newOptions = chart.options; + + // Update Scale(s) with options + if (newOptions.scale) { + chart.scale.options = newOptions.scale; + } else if (newOptions.scales) { + newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) { + chart.scales[scaleOptions.id].options = scaleOptions; + }); + } + + // Tooltip + chart.tooltip._options = newOptions.tooltips; + } + + /** + * @class Chart.Controller + * The main controller of a chart. + */ + Chart.Controller = function(item, config, instance) { + var me = this; + + config = initConfig(config); + + var context = platform.acquireContext(item, config); + var canvas = context && context.canvas; + var height = canvas && canvas.height; + var width = canvas && canvas.width; + + instance.ctx = context; + instance.canvas = canvas; + instance.config = config; + instance.width = width; + instance.height = height; + instance.aspectRatio = height? width / height : null; + + me.id = helpers.uid(); + me.chart = instance; + me.config = config; + me.options = config.options; + me._bufferedRender = false; + + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; + + Object.defineProperty(me, 'data', { + get: function() { + return me.config.data; + } + }); + + if (!context || !canvas) { + // The given item is not a compatible context2d element, let's return before finalizing + // the chart initialization but after setting basic chart / controller properties that + // can help to figure out that the chart is not valid (e.g chart.canvas !== null); + // https://github.com/chartjs/Chart.js/issues/2807 + console.error("Failed to create chart: can't acquire context from the given item"); + return me; + } + + me.initialize(); + me.update(); + + return me; + }; + + helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller.prototype */ { + initialize: function() { + var me = this; + + // Before init plugin notification + plugins.notify(me, 'beforeInit'); + + helpers.retinaScale(me.chart); + + me.bindEvents(); + + if (me.options.responsive) { + // Initial resize before chart draws (must be silent to preserve initial animations). + me.resize(true); + } + + // Make sure scales have IDs and are built before we build any controllers. + me.ensureScalesHaveIDs(); + me.buildScales(); + me.initToolTip(); + + // After init plugin notification + plugins.notify(me, 'afterInit'); + + return me; + }, + + clear: function() { + helpers.clear(this.chart); + return this; + }, + + stop: function() { + // Stops any current animation loop occurring + Chart.animationService.cancelAnimation(this); + return this; + }, + + resize: function(silent) { + var me = this; + var chart = me.chart; + var options = me.options; + var canvas = chart.canvas; + var aspectRatio = (options.maintainAspectRatio && chart.aspectRatio) || null; + + // the canvas render width and height will be casted to integers so make sure that + // the canvas display style uses the same integer values to avoid blurring effect. + var newWidth = Math.floor(helpers.getMaximumWidth(canvas)); + var newHeight = Math.floor(aspectRatio? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)); + + if (chart.width === newWidth && chart.height === newHeight) { + return; + } + + canvas.width = chart.width = newWidth; + canvas.height = chart.height = newHeight; + canvas.style.width = newWidth + 'px'; + canvas.style.height = newHeight + 'px'; + + helpers.retinaScale(chart); + + if (!silent) { + // Notify any plugins about the resize + var newSize = {width: newWidth, height: newHeight}; + plugins.notify(me, 'resize', [newSize]); + + // Notify of resize + if (me.options.onResize) { + me.options.onResize(me, newSize); + } + + me.stop(); + me.update(me.options.responsiveAnimationDuration); + } + }, + + ensureScalesHaveIDs: function() { + var options = this.options; + var scalesOptions = options.scales || {}; + var scaleOptions = options.scale; + + helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { + xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); + }); + + helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { + yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); + }); + + if (scaleOptions) { + scaleOptions.id = scaleOptions.id || 'scale'; + } + }, + + /** + * Builds a map of scale ID to scale object for future lookup. + */ + buildScales: function() { + var me = this; + var options = me.options; + var scales = me.scales = {}; + var items = []; + + if (options.scales) { + items = items.concat( + (options.scales.xAxes || []).map(function(xAxisOptions) { + return {options: xAxisOptions, dtype: 'category'}; + }), + (options.scales.yAxes || []).map(function(yAxisOptions) { + return {options: yAxisOptions, dtype: 'linear'}; + }) + ); + } + + if (options.scale) { + items.push({options: options.scale, dtype: 'radialLinear', isDefault: true}); + } + + helpers.each(items, function(item) { + var scaleOptions = item.options; + var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype); + var scaleClass = Chart.scaleService.getScaleConstructor(scaleType); + if (!scaleClass) { + return; + } + + var scale = new scaleClass({ + id: scaleOptions.id, + options: scaleOptions, + ctx: me.chart.ctx, + chart: me + }); + + scales[scale.id] = scale; + + // TODO(SB): I think we should be able to remove this custom case (options.scale) + // and consider it as a regular scale part of the "scales"" map only! This would + // make the logic easier and remove some useless? custom code. + if (item.isDefault) { + me.scale = scale; + } + }); + + Chart.scaleService.addScalesToLayout(this); + }, + + buildOrUpdateControllers: function() { + var me = this; + var types = []; + var newControllers = []; + + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + var meta = me.getDatasetMeta(datasetIndex); + if (!meta.type) { + meta.type = dataset.type || me.config.type; + } + + types.push(meta.type); + + if (meta.controller) { + meta.controller.updateIndex(datasetIndex); + } else { + meta.controller = new Chart.controllers[meta.type](me, datasetIndex); + newControllers.push(meta.controller); + } + }, me); + + if (types.length > 1) { + for (var i = 1; i < types.length; i++) { + if (types[i] !== types[i - 1]) { + me.isCombo = true; + break; + } + } + } + + return newControllers; + }, + + /** + * Reset the elements of all datasets + * @private + */ + resetElements: function() { + var me = this; + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.reset(); + }, me); + }, + + /** + * Resets the chart back to it's state before the initial animation + */ + reset: function() { + this.resetElements(); + this.tooltip.initialize(); + }, + + update: function(animationDuration, lazy) { + var me = this; + + updateConfig(me); + + if (plugins.notify(me, 'beforeUpdate') === false) { + return; + } + + // In case the entire data object changed + me.tooltip._data = me.data; + + // Make sure dataset controllers are updated and new controllers are reset + var newControllers = me.buildOrUpdateControllers(); + + // Make sure all dataset controllers have correct meta data counts + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); + }, me); + + me.updateLayout(); + + // Can only reset the new controllers after the scales have been updated + helpers.each(newControllers, function(controller) { + controller.reset(); + }); + + me.updateDatasets(); + + // Do this before render so that any plugins that need final scale updates can use it + plugins.notify(me, 'afterUpdate'); + + if (me._bufferedRender) { + me._bufferedRequest = { + lazy: lazy, + duration: animationDuration + }; + } else { + me.render(animationDuration, lazy); + } + }, + + /** + * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` + * hook, in which case, plugins will not be called on `afterLayout`. + * @private + */ + updateLayout: function() { + var me = this; + + if (plugins.notify(me, 'beforeLayout') === false) { + return; + } + + Chart.layoutService.update(this, this.chart.width, this.chart.height); + + /** + * Provided for backward compatibility, use `afterLayout` instead. + * @method IPlugin#afterScaleUpdate + * @deprecated since version 2.5.0 + * @todo remove at version 3 + */ + plugins.notify(me, 'afterScaleUpdate'); + plugins.notify(me, 'afterLayout'); + }, + + /** + * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` + * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. + * @private + */ + updateDatasets: function() { + var me = this; + + if (plugins.notify(me, 'beforeDatasetsUpdate') === false) { + return; + } + + for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.getDatasetMeta(i).controller.update(); + } + + plugins.notify(me, 'afterDatasetsUpdate'); + }, + + render: function(duration, lazy) { + var me = this; + + if (plugins.notify(me, 'beforeRender') === false) { + return; + } + + var animationOptions = me.options.animation; + var onComplete = function() { + plugins.notify(me, 'afterRender'); + var callback = animationOptions && animationOptions.onComplete; + if (callback && callback.call) { + callback.call(me); + } + }; + + if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { + var animation = new Chart.Animation(); + animation.numSteps = (duration || animationOptions.duration) / 16.66; // 60 fps + animation.easing = animationOptions.easing; + + // render function + animation.render = function(chartInstance, animationObject) { + var easingFunction = helpers.easingEffects[animationObject.easing]; + var stepDecimal = animationObject.currentStep / animationObject.numSteps; + var easeDecimal = easingFunction(stepDecimal); + + chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); + }; + + // user events + animation.onAnimationProgress = animationOptions.onProgress; + animation.onAnimationComplete = onComplete; + + Chart.animationService.addAnimation(me, animation, duration, lazy); + } else { + me.draw(); + onComplete(); + } + + return me; + }, + + draw: function(easingValue) { + var me = this; + + me.clear(); + + if (easingValue === undefined || easingValue === null) { + easingValue = 1; + } + + if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) { + return; + } + + // Draw all the scales + helpers.each(me.boxes, function(box) { + box.draw(me.chartArea); + }, me); + + if (me.scale) { + me.scale.draw(); + } + + me.drawDatasets(easingValue); + + // Finally draw the tooltip + me.tooltip.transition(easingValue).draw(); + + plugins.notify(me, 'afterDraw', [easingValue]); + }, + + /** + * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` + * hook, in which case, plugins will not be called on `afterDatasetsDraw`. + * @private + */ + drawDatasets: function(easingValue) { + var me = this; + + if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { + return; + } + + // Draw each dataset via its respective controller (reversed to support proper line stacking) + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + if (me.isDatasetVisible(datasetIndex)) { + me.getDatasetMeta(datasetIndex).controller.draw(easingValue); + } + }, me, true); + + plugins.notify(me, 'afterDatasetsDraw', [easingValue]); + }, + + // Get the single element that was clicked on + // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw + getElementAtEvent: function(e) { + return Chart.Interaction.modes.single(this, e); + }, + + getElementsAtEvent: function(e) { + return Chart.Interaction.modes.label(this, e, {intersect: true}); + }, + + getElementsAtXAxis: function(e) { + return Chart.Interaction.modes['x-axis'](this, e, {intersect: true}); + }, + + getElementsAtEventForMode: function(e, mode, options) { + var method = Chart.Interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options); + } + + return []; + }, + + getDatasetAtEvent: function(e) { + return Chart.Interaction.modes.dataset(this, e, {intersect: true}); + }, + + getDatasetMeta: function(datasetIndex) { + var me = this; + var dataset = me.data.datasets[datasetIndex]; + if (!dataset._meta) { + dataset._meta = {}; + } + + var meta = dataset._meta[me.id]; + if (!meta) { + meta = dataset._meta[me.id] = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, // See isDatasetVisible() comment + xAxisID: null, + yAxisID: null + }; + } + + return meta; + }, + + getVisibleDatasetCount: function() { + var count = 0; + for (var i = 0, ilen = this.data.datasets.length; i