vendor/assets/javascripts/chart2.js in chart-0.1.5.0.pre vs vendor/assets/javascripts/chart2.js in chart-0.1.5.1

- old
+ new

@@ -1,10397 +1,11557 @@ /*! * Chart.js * http://chartjs.org/ - * Version: 2.2.1 + * Version: 2.4.0 * * Copyright 2016 Nick Downie * Released under the MIT license * 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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ },{}],2:[function(require,module,exports){ -/* MIT license */ -var colorNames = require(6); + /* MIT license */ + var colorNames = require(6); -module.exports = { - getRgba: getRgba, - getHsla: getHsla, - getRgb: getRgb, - getHsl: getHsl, - getHwb: getHwb, - getAlpha: getAlpha, + module.exports = { + getRgba: getRgba, + getHsla: getHsla, + getRgb: getRgb, + getHsl: getHsl, + getHwb: getHwb, + getAlpha: getAlpha, - hexString: hexString, - rgbString: rgbString, - rgbaString: rgbaString, - percentString: percentString, - percentaString: percentaString, - hslString: hslString, - hslaString: hslaString, - hwbString: hwbString, - keyword: keyword -} + hexString: hexString, + rgbString: rgbString, + rgbaString: rgbaString, + percentString: percentString, + percentaString: percentaString, + hslString: hslString, + hslaString: hslaString, + hwbString: hwbString, + keyword: keyword + } -function getRgba(string) { - if (!string) { - return; - } - var abbr = /^#([a-fA-F0-9]{3})$/, - hex = /^#([a-fA-F0-9]{6})$/, - rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/, - per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/, - keyword = /(\w+)/; + function getRgba(string) { + if (!string) { + return; + } + var abbr = /^#([a-fA-F0-9]{3})$/, + hex = /^#([a-fA-F0-9]{6})$/, + rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/, + per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/, + keyword = /(\w+)/; - var rgb = [0, 0, 0], - a = 1, - match = string.match(abbr); - if (match) { - match = match[1]; - for (var i = 0; i < rgb.length; i++) { - rgb[i] = parseInt(match[i] + match[i], 16); - } - } - else if (match = string.match(hex)) { - match = match[1]; - for (var i = 0; i < rgb.length; i++) { - rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); - } - } - else if (match = string.match(rgba)) { - for (var i = 0; i < rgb.length; i++) { - rgb[i] = parseInt(match[i + 1]); - } - a = parseFloat(match[4]); - } - else if (match = string.match(per)) { - for (var i = 0; i < rgb.length; i++) { - rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); - } - a = parseFloat(match[4]); - } - else if (match = string.match(keyword)) { - if (match[1] == "transparent") { - return [0, 0, 0, 0]; - } - rgb = colorNames[match[1]]; - if (!rgb) { - return; - } - } + var rgb = [0, 0, 0], + a = 1, + match = string.match(abbr); + if (match) { + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i] + match[i], 16); + } + } + else if (match = string.match(hex)) { + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); + } + } + else if (match = string.match(rgba)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i + 1]); + } + a = parseFloat(match[4]); + } + else if (match = string.match(per)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); + } + a = parseFloat(match[4]); + } + else if (match = string.match(keyword)) { + if (match[1] == "transparent") { + return [0, 0, 0, 0]; + } + rgb = colorNames[match[1]]; + if (!rgb) { + return; + } + } - for (var i = 0; i < rgb.length; i++) { - rgb[i] = scale(rgb[i], 0, 255); - } - if (!a && a != 0) { - a = 1; - } - else { - a = scale(a, 0, 1); - } - rgb[3] = a; - return rgb; -} + for (var i = 0; i < rgb.length; i++) { + rgb[i] = scale(rgb[i], 0, 255); + } + if (!a && a != 0) { + a = 1; + } + else { + a = scale(a, 0, 1); + } + rgb[3] = a; + return rgb; + } -function getHsla(string) { - if (!string) { - return; - } - var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; - var match = string.match(hsl); - if (match) { - var alpha = parseFloat(match[4]); - var h = scale(parseInt(match[1]), 0, 360), - s = scale(parseFloat(match[2]), 0, 100), - l = scale(parseFloat(match[3]), 0, 100), - a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); - return [h, s, l, a]; - } -} + function getHsla(string) { + if (!string) { + return; + } + var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hsl); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + s = scale(parseFloat(match[2]), 0, 100), + l = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, s, l, a]; + } + } -function getHwb(string) { - if (!string) { - return; - } - var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; - var match = string.match(hwb); - if (match) { - var alpha = parseFloat(match[4]); - var h = scale(parseInt(match[1]), 0, 360), - w = scale(parseFloat(match[2]), 0, 100), - b = scale(parseFloat(match[3]), 0, 100), - a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); - return [h, w, b, a]; - } -} + function getHwb(string) { + if (!string) { + return; + } + var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hwb); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + w = scale(parseFloat(match[2]), 0, 100), + b = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, w, b, a]; + } + } -function getRgb(string) { - var rgba = getRgba(string); - return rgba && rgba.slice(0, 3); -} + function getRgb(string) { + var rgba = getRgba(string); + return rgba && rgba.slice(0, 3); + } -function getHsl(string) { - var hsla = getHsla(string); - return hsla && hsla.slice(0, 3); -} + function getHsl(string) { + var hsla = getHsla(string); + return hsla && hsla.slice(0, 3); + } -function getAlpha(string) { - var vals = getRgba(string); - if (vals) { - return vals[3]; - } - else if (vals = getHsla(string)) { - return vals[3]; - } - else if (vals = getHwb(string)) { - return vals[3]; - } -} + function getAlpha(string) { + var vals = getRgba(string); + if (vals) { + return vals[3]; + } + else if (vals = getHsla(string)) { + return vals[3]; + } + else if (vals = getHwb(string)) { + return vals[3]; + } + } // generators -function hexString(rgb) { - return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1]) - + hexDouble(rgb[2]); -} + function hexString(rgb) { + return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1]) + + hexDouble(rgb[2]); + } -function rgbString(rgba, alpha) { - if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { - return rgbaString(rgba, alpha); - } - return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; -} + function rgbString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return rgbaString(rgba, alpha); + } + return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; + } -function rgbaString(rgba, alpha) { - if (alpha === undefined) { - alpha = (rgba[3] !== undefined ? rgba[3] : 1); - } - return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] - + ", " + alpha + ")"; -} + function rgbaString(rgba, alpha) { + if (alpha === undefined) { + alpha = (rgba[3] !== undefined ? rgba[3] : 1); + } + return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + + ", " + alpha + ")"; + } -function percentString(rgba, alpha) { - if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { - return percentaString(rgba, alpha); - } - var r = Math.round(rgba[0]/255 * 100), - g = Math.round(rgba[1]/255 * 100), - b = Math.round(rgba[2]/255 * 100); + function percentString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return percentaString(rgba, alpha); + } + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); - return "rgb(" + r + "%, " + g + "%, " + b + "%)"; -} + return "rgb(" + r + "%, " + g + "%, " + b + "%)"; + } -function percentaString(rgba, alpha) { - var r = Math.round(rgba[0]/255 * 100), - g = Math.round(rgba[1]/255 * 100), - b = Math.round(rgba[2]/255 * 100); - return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; -} + function percentaString(rgba, alpha) { + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; + } -function hslString(hsla, alpha) { - if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { - return hslaString(hsla, alpha); - } - return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; -} + function hslString(hsla, alpha) { + if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { + return hslaString(hsla, alpha); + } + return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; + } -function hslaString(hsla, alpha) { - if (alpha === undefined) { - alpha = (hsla[3] !== undefined ? hsla[3] : 1); - } - return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " - + alpha + ")"; -} + function hslaString(hsla, alpha) { + if (alpha === undefined) { + alpha = (hsla[3] !== undefined ? hsla[3] : 1); + } + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + + alpha + ")"; + } // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax // (hwb have alpha optional & 1 is default value) -function hwbString(hwb, alpha) { - if (alpha === undefined) { - alpha = (hwb[3] !== undefined ? hwb[3] : 1); - } - return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" - + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; -} + function hwbString(hwb, alpha) { + if (alpha === undefined) { + alpha = (hwb[3] !== undefined ? hwb[3] : 1); + } + return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" + + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; + } -function keyword(rgb) { - return reverseNames[rgb.slice(0, 3)]; -} + function keyword(rgb) { + return reverseNames[rgb.slice(0, 3)]; + } // helpers -function scale(num, min, max) { - return Math.min(Math.max(min, num), max); -} + function scale(num, min, max) { + return Math.min(Math.max(min, num), max); + } -function hexDouble(num) { - var str = num.toString(16).toUpperCase(); - return (str.length < 2) ? "0" + str : str; -} + function hexDouble(num) { + var str = num.toString(16).toUpperCase(); + return (str.length < 2) ? "0" + str : str; + } //create a list of reverse color names -var reverseNames = {}; -for (var name in colorNames) { - reverseNames[colorNames[name]] = name; -} + var reverseNames = {}; + for (var name in colorNames) { + reverseNames[colorNames[name]] = name; + } },{"6":6}],3:[function(require,module,exports){ -/* MIT license */ -var convert = require(5); -var string = require(2); + /* MIT license */ + var convert = require(5); + var string = require(2); -var Color = function (obj) { - if (obj instanceof Color) { - return obj; - } - if (!(this instanceof Color)) { - return new Color(obj); - } + var Color = function (obj) { + if (obj instanceof Color) { + return obj; + } + if (!(this instanceof Color)) { + return new Color(obj); + } - this.values = { - rgb: [0, 0, 0], - hsl: [0, 0, 0], - hsv: [0, 0, 0], - hwb: [0, 0, 0], - cmyk: [0, 0, 0, 0], - alpha: 1 - }; + this.values = { + rgb: [0, 0, 0], + hsl: [0, 0, 0], + hsv: [0, 0, 0], + hwb: [0, 0, 0], + cmyk: [0, 0, 0, 0], + alpha: 1 + }; - // parse Color() argument - var vals; - if (typeof obj === 'string') { - vals = string.getRgba(obj); - if (vals) { - this.setValues('rgb', vals); - } else if (vals = string.getHsla(obj)) { - this.setValues('hsl', vals); - } else if (vals = string.getHwb(obj)) { - this.setValues('hwb', vals); - } else { - throw new Error('Unable to parse color from string "' + obj + '"'); - } - } else if (typeof obj === 'object') { - vals = obj; - if (vals.r !== undefined || vals.red !== undefined) { - this.setValues('rgb', vals); - } else if (vals.l !== undefined || vals.lightness !== undefined) { - this.setValues('hsl', vals); - } else if (vals.v !== undefined || vals.value !== undefined) { - this.setValues('hsv', vals); - } else if (vals.w !== undefined || vals.whiteness !== undefined) { - this.setValues('hwb', vals); - } else if (vals.c !== undefined || vals.cyan !== undefined) { - this.setValues('cmyk', vals); - } else { - throw new Error('Unable to parse color from object ' + JSON.stringify(obj)); - } - } -}; + // parse Color() argument + var vals; + if (typeof obj === 'string') { + vals = string.getRgba(obj); + if (vals) { + this.setValues('rgb', vals); + } else if (vals = string.getHsla(obj)) { + this.setValues('hsl', vals); + } else if (vals = string.getHwb(obj)) { + this.setValues('hwb', vals); + } else { + throw new Error('Unable to parse color from string "' + obj + '"'); + } + } else if (typeof obj === 'object') { + vals = obj; + if (vals.r !== undefined || vals.red !== undefined) { + this.setValues('rgb', vals); + } else if (vals.l !== undefined || vals.lightness !== undefined) { + this.setValues('hsl', vals); + } else if (vals.v !== undefined || vals.value !== undefined) { + this.setValues('hsv', vals); + } else if (vals.w !== undefined || vals.whiteness !== undefined) { + this.setValues('hwb', vals); + } else if (vals.c !== undefined || vals.cyan !== undefined) { + this.setValues('cmyk', vals); + } else { + throw new Error('Unable to parse color from object ' + JSON.stringify(obj)); + } + } + }; -Color.prototype = { - rgb: function () { - return this.setSpace('rgb', arguments); - }, - hsl: function () { - return this.setSpace('hsl', arguments); - }, - hsv: function () { - return this.setSpace('hsv', arguments); - }, - hwb: function () { - return this.setSpace('hwb', arguments); - }, - cmyk: function () { - return this.setSpace('cmyk', arguments); - }, + Color.prototype = { + rgb: function () { + return this.setSpace('rgb', arguments); + }, + hsl: function () { + return this.setSpace('hsl', arguments); + }, + hsv: function () { + return this.setSpace('hsv', arguments); + }, + hwb: function () { + return this.setSpace('hwb', arguments); + }, + cmyk: function () { + return this.setSpace('cmyk', arguments); + }, - rgbArray: function () { - return this.values.rgb; - }, - hslArray: function () { - return this.values.hsl; - }, - hsvArray: function () { - return this.values.hsv; - }, - hwbArray: function () { - var values = this.values; - if (values.alpha !== 1) { - return values.hwb.concat([values.alpha]); - } - return values.hwb; - }, - cmykArray: function () { - return this.values.cmyk; - }, - rgbaArray: function () { - var values = this.values; - return values.rgb.concat([values.alpha]); - }, - hslaArray: function () { - var values = this.values; - return values.hsl.concat([values.alpha]); - }, - alpha: function (val) { - if (val === undefined) { - return this.values.alpha; - } - this.setValues('alpha', val); - return this; - }, + rgbArray: function () { + return this.values.rgb; + }, + hslArray: function () { + return this.values.hsl; + }, + hsvArray: function () { + return this.values.hsv; + }, + hwbArray: function () { + var values = this.values; + if (values.alpha !== 1) { + return values.hwb.concat([values.alpha]); + } + return values.hwb; + }, + cmykArray: function () { + return this.values.cmyk; + }, + rgbaArray: function () { + var values = this.values; + return values.rgb.concat([values.alpha]); + }, + hslaArray: function () { + var values = this.values; + return values.hsl.concat([values.alpha]); + }, + alpha: function (val) { + if (val === undefined) { + return this.values.alpha; + } + this.setValues('alpha', val); + return this; + }, - red: function (val) { - return this.setChannel('rgb', 0, val); - }, - green: function (val) { - return this.setChannel('rgb', 1, val); - }, - blue: function (val) { - return this.setChannel('rgb', 2, val); - }, - hue: function (val) { - if (val) { - val %= 360; - val = val < 0 ? 360 + val : val; - } - return this.setChannel('hsl', 0, val); - }, - saturation: function (val) { - return this.setChannel('hsl', 1, val); - }, - lightness: function (val) { - return this.setChannel('hsl', 2, val); - }, - saturationv: function (val) { - return this.setChannel('hsv', 1, val); - }, - whiteness: function (val) { - return this.setChannel('hwb', 1, val); - }, - blackness: function (val) { - return this.setChannel('hwb', 2, val); - }, - value: function (val) { - return this.setChannel('hsv', 2, val); - }, - cyan: function (val) { - return this.setChannel('cmyk', 0, val); - }, - magenta: function (val) { - return this.setChannel('cmyk', 1, val); - }, - yellow: function (val) { - return this.setChannel('cmyk', 2, val); - }, - black: function (val) { - return this.setChannel('cmyk', 3, val); - }, + red: function (val) { + return this.setChannel('rgb', 0, val); + }, + green: function (val) { + return this.setChannel('rgb', 1, val); + }, + blue: function (val) { + return this.setChannel('rgb', 2, val); + }, + hue: function (val) { + if (val) { + val %= 360; + val = val < 0 ? 360 + val : val; + } + return this.setChannel('hsl', 0, val); + }, + saturation: function (val) { + return this.setChannel('hsl', 1, val); + }, + lightness: function (val) { + return this.setChannel('hsl', 2, val); + }, + saturationv: function (val) { + return this.setChannel('hsv', 1, val); + }, + whiteness: function (val) { + return this.setChannel('hwb', 1, val); + }, + blackness: function (val) { + return this.setChannel('hwb', 2, val); + }, + value: function (val) { + return this.setChannel('hsv', 2, val); + }, + cyan: function (val) { + return this.setChannel('cmyk', 0, val); + }, + magenta: function (val) { + return this.setChannel('cmyk', 1, val); + }, + yellow: function (val) { + return this.setChannel('cmyk', 2, val); + }, + black: function (val) { + return this.setChannel('cmyk', 3, val); + }, - hexString: function () { - return string.hexString(this.values.rgb); - }, - rgbString: function () { - return string.rgbString(this.values.rgb, this.values.alpha); - }, - rgbaString: function () { - return string.rgbaString(this.values.rgb, this.values.alpha); - }, - percentString: function () { - return string.percentString(this.values.rgb, this.values.alpha); - }, - hslString: function () { - return string.hslString(this.values.hsl, this.values.alpha); - }, - hslaString: function () { - return string.hslaString(this.values.hsl, this.values.alpha); - }, - hwbString: function () { - return string.hwbString(this.values.hwb, this.values.alpha); - }, - keyword: function () { - return string.keyword(this.values.rgb, this.values.alpha); - }, + hexString: function () { + return string.hexString(this.values.rgb); + }, + rgbString: function () { + return string.rgbString(this.values.rgb, this.values.alpha); + }, + rgbaString: function () { + return string.rgbaString(this.values.rgb, this.values.alpha); + }, + percentString: function () { + return string.percentString(this.values.rgb, this.values.alpha); + }, + hslString: function () { + return string.hslString(this.values.hsl, this.values.alpha); + }, + hslaString: function () { + return string.hslaString(this.values.hsl, this.values.alpha); + }, + hwbString: function () { + return string.hwbString(this.values.hwb, this.values.alpha); + }, + keyword: function () { + return string.keyword(this.values.rgb, this.values.alpha); + }, - rgbNumber: function () { - var rgb = this.values.rgb; - return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; - }, + rgbNumber: function () { + var rgb = this.values.rgb; + return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + }, - luminosity: function () { - // http://www.w3.org/TR/WCAG20/#relativeluminancedef - var rgb = this.values.rgb; - var lum = []; - for (var i = 0; i < rgb.length; i++) { - var chan = rgb[i] / 255; - lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); - } - return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; - }, + luminosity: function () { + // http://www.w3.org/TR/WCAG20/#relativeluminancedef + var rgb = this.values.rgb; + var lum = []; + for (var i = 0; i < rgb.length; i++) { + var chan = rgb[i] / 255; + lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); + } + return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; + }, - contrast: function (color2) { - // http://www.w3.org/TR/WCAG20/#contrast-ratiodef - var lum1 = this.luminosity(); - var lum2 = color2.luminosity(); - if (lum1 > lum2) { - return (lum1 + 0.05) / (lum2 + 0.05); - } - return (lum2 + 0.05) / (lum1 + 0.05); - }, + contrast: function (color2) { + // http://www.w3.org/TR/WCAG20/#contrast-ratiodef + var lum1 = this.luminosity(); + var lum2 = color2.luminosity(); + if (lum1 > 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'; - } + level: function (color2) { + var contrastRatio = this.contrast(color2); + if (contrastRatio >= 7.1) { + return 'AAA'; + } - return (contrastRatio >= 4.5) ? 'AA' : ''; - }, + 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; - }, + 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(); - }, + 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; - }, + 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; - }, + 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; - }, + 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; - }, + 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; - }, + 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; - }, + 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; - }, + 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; - }, + 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; - }, + 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; - }, + 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; - }, + 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; + /** + * 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 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; + 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)); - }, + 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(); - }, + 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; + 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); - } - } - } + 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; - } -}; + 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.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.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 = {}; + 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]; - } + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = values[space][i]; + } - if (values.alpha !== 1) { - vals.a = values.alpha; - } + if (values.alpha !== 1) { + vals.a = values.alpha; + } - // {r: 255, g: 255, b: 255, a: 0.4} - return vals; -}; + // {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; + 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)]; - } + 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]; + 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]]; - } + for (i = 0; i < space.length; i++) { + values[space][i] = vals[chans[i]]; + } - alpha = vals.alpha; - } + alpha = vals.alpha; + } - values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); + values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); - if (space === 'alpha') { - return false; - } + if (space === 'alpha') { + return false; + } - var capped; + 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); - } + // 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]); - } - } + // convert to all the other color spaces + for (var sname in spaces) { + if (sname !== space) { + values[sname] = convert[space][sname](values[space]); + } + } - return true; -}; + return true; + }; -Color.prototype.setSpace = function (space, args) { - var vals = args[0]; + Color.prototype.setSpace = function (space, args) { + var vals = args[0]; - if (vals === undefined) { - // color.rgb() - return this.getValues(space); - } + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } - // color.rgb(10, 10, 10) - if (typeof vals === 'number') { - vals = Array.prototype.slice.call(args); - } + // color.rgb(10, 10, 10) + if (typeof vals === 'number') { + vals = Array.prototype.slice.call(args); + } - this.setValues(space, vals); - return this; -}; + 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.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); + // color.red(100) + svalues[index] = val; + this.setValues(space, svalues); - return this; -}; + return this; + }; -if (typeof window !== 'undefined') { - window.Color = Color; -} + if (typeof window !== 'undefined') { + window.Color = Color; + } -module.exports = Color; + module.exports = Color; },{"2":2,"5":5}],4:[function(require,module,exports){ -/* MIT license */ + /* MIT license */ -module.exports = { - rgb2hsl: rgb2hsl, - rgb2hsv: rgb2hsv, - rgb2hwb: rgb2hwb, - rgb2cmyk: rgb2cmyk, - rgb2keyword: rgb2keyword, - rgb2xyz: rgb2xyz, - rgb2lab: rgb2lab, - rgb2lch: rgb2lch, + 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, + hsl2rgb: hsl2rgb, + hsl2hsv: hsl2hsv, + hsl2hwb: hsl2hwb, + hsl2cmyk: hsl2cmyk, + hsl2keyword: hsl2keyword, - hsv2rgb: hsv2rgb, - hsv2hsl: hsv2hsl, - hsv2hwb: hsv2hwb, - hsv2cmyk: hsv2cmyk, - hsv2keyword: hsv2keyword, + hsv2rgb: hsv2rgb, + hsv2hsl: hsv2hsl, + hsv2hwb: hsv2hwb, + hsv2cmyk: hsv2cmyk, + hsv2keyword: hsv2keyword, - hwb2rgb: hwb2rgb, - hwb2hsl: hwb2hsl, - hwb2hsv: hwb2hsv, - hwb2cmyk: hwb2cmyk, - hwb2keyword: hwb2keyword, + hwb2rgb: hwb2rgb, + hwb2hsl: hwb2hsl, + hwb2hsv: hwb2hsv, + hwb2cmyk: hwb2cmyk, + hwb2keyword: hwb2keyword, - cmyk2rgb: cmyk2rgb, - cmyk2hsl: cmyk2hsl, - cmyk2hsv: cmyk2hsv, - cmyk2hwb: cmyk2hwb, - cmyk2keyword: cmyk2keyword, + cmyk2rgb: cmyk2rgb, + cmyk2hsl: cmyk2hsl, + cmyk2hsv: cmyk2hsv, + cmyk2hwb: cmyk2hwb, + cmyk2keyword: cmyk2keyword, - keyword2rgb: keyword2rgb, - keyword2hsl: keyword2hsl, - keyword2hsv: keyword2hsv, - keyword2hwb: keyword2hwb, - keyword2cmyk: keyword2cmyk, - keyword2lab: keyword2lab, - keyword2xyz: keyword2xyz, + keyword2rgb: keyword2rgb, + keyword2hsl: keyword2hsl, + keyword2hsv: keyword2hsv, + keyword2hwb: keyword2hwb, + keyword2cmyk: keyword2cmyk, + keyword2lab: keyword2lab, + keyword2xyz: keyword2xyz, - xyz2rgb: xyz2rgb, - xyz2lab: xyz2lab, - xyz2lch: xyz2lch, + xyz2rgb: xyz2rgb, + xyz2lab: xyz2lab, + xyz2lch: xyz2lch, - lab2xyz: lab2xyz, - lab2rgb: lab2rgb, - lab2lch: lab2lch, + lab2xyz: lab2xyz, + lab2rgb: lab2rgb, + lab2lch: lab2lch, - lch2lab: lch2lab, - lch2xyz: lch2xyz, - lch2rgb: lch2rgb -} + 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; + 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; + 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); + h = Math.min(h * 60, 360); - if (h < 0) - h += 360; + if (h < 0) + h += 360; - l = (min + max) / 2; + l = (min + max) / 2; - if (max == min) - s = 0; - else if (l <= 0.5) - s = delta / (max + min); - else - s = delta / (2 - max - min); + 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]; -} + 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; + 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 == 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; + 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); + h = Math.min(h * 60, 360); - if (h < 0) - h += 360; + if (h < 0) + h += 360; - v = ((max / 255) * 1000) / 10; + v = ((max / 255) * 1000) / 10; - return [h, s, v]; -} + 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)); + 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]; -} + 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; + 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]; -} + 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 rgb2keyword(rgb) { + return reverseKeywords[JSON.stringify(rgb)]; + } -function rgb2xyz(rgb) { - var r = rgb[0] / 255, - g = rgb[1] / 255, - b = rgb[2] / 255; + 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); + // 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); + 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]; -} + 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; + 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 /= 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); + 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); + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); - return [l, a, b]; -} + return [l, a, b]; + } -function rgb2lch(args) { - return lab2lch(rgb2lab(args)); -} + 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; + 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 (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; + 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--; + 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; + 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; - } + rgb[i] = val * 255; + } - return rgb; -} + return rgb; + } -function hsl2hsv(hsl) { - var h = hsl[0], - s = hsl[1] / 100, - l = hsl[2] / 100, - sv, v; + 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]; - } + 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]; -} + 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 hsl2hwb(args) { + return rgb2hwb(hsl2rgb(args)); + } -function hsl2cmyk(args) { - return rgb2cmyk(hsl2rgb(args)); -} + function hsl2cmyk(args) { + return rgb2cmyk(hsl2rgb(args)); + } -function hsl2keyword(args) { - return rgb2keyword(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; + 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; + 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]; - } -} + 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; + 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]; -} + 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 hsv2hwb(args) { + return rgb2hwb(hsv2rgb(args)) + } -function hsv2cmyk(args) { - return rgb2cmyk(hsv2rgb(args)); -} + function hsv2cmyk(args) { + return rgb2cmyk(hsv2rgb(args)); + } -function hsv2keyword(args) { - return rgb2keyword(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; + 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; - } + // 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 + 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; - } + 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]; -} + return [r * 255, g * 255, b * 255]; + } -function hwb2hsl(args) { - return rgb2hsl(hwb2rgb(args)); -} + function hwb2hsl(args) { + return rgb2hsl(hwb2rgb(args)); + } -function hwb2hsv(args) { - return rgb2hsv(hwb2rgb(args)); -} + function hwb2hsv(args) { + return rgb2hsv(hwb2rgb(args)); + } -function hwb2cmyk(args) { - return rgb2cmyk(hwb2rgb(args)); -} + function hwb2cmyk(args) { + return rgb2cmyk(hwb2rgb(args)); + } -function hwb2keyword(args) { - return rgb2keyword(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; + 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]; -} + 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 cmyk2hsl(args) { + return rgb2hsl(cmyk2rgb(args)); + } -function cmyk2hsv(args) { - return rgb2hsv(cmyk2rgb(args)); -} + function cmyk2hsv(args) { + return rgb2hsv(cmyk2rgb(args)); + } -function cmyk2hwb(args) { - return rgb2hwb(cmyk2rgb(args)); -} + function cmyk2hwb(args) { + return rgb2hwb(cmyk2rgb(args)); + } -function cmyk2keyword(args) { - return rgb2keyword(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; + 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); + 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); + // 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); + 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); + 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); + 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]; -} + return [r * 255, g * 255, b * 255]; + } -function xyz2lab(xyz) { - var x = xyz[0], - y = xyz[1], - z = xyz[2], - l, a, b; + 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 /= 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); + 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); + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); - return [l, a, b]; -} + return [l, a, b]; + } -function xyz2lch(args) { - return lab2lch(xyz2lab(args)); -} + function xyz2lch(args) { + return lab2lch(xyz2lab(args)); + } -function lab2xyz(lab) { - var l = lab[0], - a = lab[1], - b = lab[2], - x, y, z, y2; + 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); - } + 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); + 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); + 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]; -} + return [x, y, z]; + } -function lab2lch(lab) { - var l = lab[0], - a = lab[1], - b = lab[2], - hr, h, c; + 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]; -} + 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 lab2rgb(args) { + return xyz2rgb(lab2xyz(args)); + } -function lch2lab(lch) { - var l = lch[0], - c = lch[1], - h = lch[2], - a, b, hr; + 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]; -} + 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 lch2xyz(args) { + return lab2xyz(lch2lab(args)); + } -function lch2rgb(args) { - return lab2rgb(lch2lab(args)); -} + function lch2rgb(args) { + return lab2rgb(lch2lab(args)); + } -function keyword2rgb(keyword) { - return cssKeywords[keyword]; -} + function keyword2rgb(keyword) { + return cssKeywords[keyword]; + } -function keyword2hsl(args) { - return rgb2hsl(keyword2rgb(args)); -} + function keyword2hsl(args) { + return rgb2hsl(keyword2rgb(args)); + } -function keyword2hsv(args) { - return rgb2hsv(keyword2rgb(args)); -} + function keyword2hsv(args) { + return rgb2hsv(keyword2rgb(args)); + } -function keyword2hwb(args) { - return rgb2hwb(keyword2rgb(args)); -} + function keyword2hwb(args) { + return rgb2hwb(keyword2rgb(args)); + } -function keyword2cmyk(args) { - return rgb2cmyk(keyword2rgb(args)); -} + function keyword2cmyk(args) { + return rgb2cmyk(keyword2rgb(args)); + } -function keyword2lab(args) { - return rgb2lab(keyword2rgb(args)); -} + function keyword2lab(args) { + return rgb2lab(keyword2rgb(args)); + } -function keyword2xyz(args) { - return rgb2xyz(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 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; -} + var reverseKeywords = {}; + for (var key in cssKeywords) { + reverseKeywords[JSON.stringify(cssKeywords[key])] = key; + } },{}],5:[function(require,module,exports){ -var conversions = require(4); + 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); + var convert = function() { + return new Converter(); } - })(func); - var pair = /(\w+)2(\w+)/.exec(func), - from = pair[1], - to = pair[2]; + 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); - // export rgb2hsl and ["rgb"]["hsl"] - convert[from] = convert[from] || {}; + var pair = /(\w+)2(\w+)/.exec(func), + from = pair[1], + to = pair[2]; - convert[from][to] = convert[func] = (function(func) { - return function(arg) { - if (typeof arg == "number") - arg = Array.prototype.slice.call(arguments); + // export rgb2hsl and ["rgb"]["hsl"] + convert[from] = convert[from] || {}; - var val = conversions[func](arg); - if (typeof val == "string" || val === undefined) - return val; // keyword + convert[from][to] = convert[func] = (function(func) { + return function(arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); - for (var i = 0; i < val.length; i++) - val[i] = Math.round(val[i]); - return val; + 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); } - })(func); -} -/* Converter does lazy conversion and caching */ -var Converter = function() { - this.convs = {}; -}; + /* 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); - } + /* 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); -}; + 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; -}; + /* 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); + /* 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; -}; + this.convs[space] = vals; + } + return vals; + }; -["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { - Converter.prototype[space] = function(vals) { - return this.routeSpace(space, arguments); - } -}); + ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { + Converter.prototype[space] = function(vals) { + return this.routeSpace(space, arguments); + } + }); -module.exports = convert; + 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] -}; + 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(27)(); + /** + * @namespace Chart + */ + var Chart = require(28)(); -require(26)(Chart); -require(22)(Chart); -require(25)(Chart); -require(21)(Chart); -require(23)(Chart); -require(24)(Chart); -require(28)(Chart); -require(32)(Chart); -require(30)(Chart); -require(31)(Chart); -require(33)(Chart); -require(29)(Chart); -require(34)(Chart); + require(26)(Chart); + require(22)(Chart); + require(25)(Chart); + require(21)(Chart); + require(23)(Chart); + require(24)(Chart); + require(29)(Chart); + require(33)(Chart); + require(31)(Chart); + require(34)(Chart); + require(32)(Chart); + require(35)(Chart); + require(30)(Chart); + require(27)(Chart); + require(36)(Chart); -require(35)(Chart); -require(36)(Chart); -require(37)(Chart); -require(38)(Chart); + require(37)(Chart); + require(38)(Chart); + require(39)(Chart); + require(40)(Chart); -require(41)(Chart); -require(39)(Chart); -require(40)(Chart); -require(42)(Chart); -require(43)(Chart); -require(44)(Chart); + require(43)(Chart); + require(41)(Chart); + require(42)(Chart); + require(44)(Chart); + require(45)(Chart); + require(46)(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(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); + 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; + 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,"41":41,"42":42,"43":43,"44":44,"8":8,"9":9}],8:[function(require,module,exports){ -"use strict"; +},{"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,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"8":8,"9":9}],8:[function(require,module,exports){ + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - Chart.Bar = function(context, config) { - config.type = 'bar'; + Chart.Bar = function(context, config) { + config.type = 'bar'; - return new Chart(context, config); - }; + return new Chart(context, config); + }; -}; + }; + },{}],9:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - Chart.Bubble = function(context, config) { - config.type = 'bubble'; - return new Chart(context, config); - }; + Chart.Bubble = function(context, config) { + config.type = 'bubble'; + return new Chart(context, config); + }; -}; + }; + },{}],10:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - Chart.Doughnut = function(context, config) { - config.type = 'doughnut'; + Chart.Doughnut = function(context, config) { + config.type = 'doughnut'; - return new Chart(context, config); - }; + return new Chart(context, config); + }; -}; + }; + },{}],11:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - Chart.Line = function(context, config) { - config.type = 'line'; + Chart.Line = function(context, config) { + config.type = 'line'; - return new Chart(context, config); - }; + return new Chart(context, config); + }; -}; + }; + },{}],12:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - Chart.PolarArea = function(context, config) { - config.type = 'polarArea'; + Chart.PolarArea = function(context, config) { + config.type = 'polarArea'; - return new Chart(context, config); - }; + return new Chart(context, config); + }; -}; + }; + },{}],13:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - Chart.Radar = function(context, config) { - config.options = Chart.helpers.configMerge({ aspectRatio: 1 }, config.options); - config.type = 'radar'; + Chart.Radar = function(context, config) { + config.type = 'radar'; - return new Chart(context, config); - }; + return new Chart(context, config); + }; -}; + }; },{}],14:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - var defaultConfig = { - hover: { - mode: 'single' - }, + var defaultConfig = { + hover: { + mode: 'single' + }, - 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" - }] - }, + 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' + }] + }, - 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 + ')'; - } - } - } - }; + 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 + ')'; + } + } + } + }; - // Register the default config for this type - Chart.defaults.scatter = defaultConfig; + // Register the default config for this type + Chart.defaults.scatter = defaultConfig; - // Scatter charts use line controllers - Chart.controllers.scatter = Chart.controllers.line; + // Scatter charts use line controllers + Chart.controllers.scatter = Chart.controllers.line; - Chart.Scatter = function(context, config) { - config.type = 'scatter'; - return new Chart(context, config); - }; + Chart.Scatter = function(context, config) { + config.type = 'scatter'; + return new Chart(context, config); + }; -}; + }; + },{}],15:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - var helpers = Chart.helpers; + var helpers = Chart.helpers; - Chart.defaults.bar = { - hover: { - mode: "label" - }, + Chart.defaults.bar = { + hover: { + mode: 'label' + }, - scales: { - xAxes: [{ - type: "category", + scales: { + xAxes: [{ + type: 'category', - // Specific to Bar Controller - categoryPercentage: 0.8, - barPercentage: 0.9, + // Specific to Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, - // grid line settings - gridLines: { - offsetGridLines: true - } - }], - yAxes: [{ - type: "linear" - }] - } - }; + // grid line settings + gridLines: { + offsetGridLines: true + } + }], + yAxes: [{ + type: 'linear' + }] + } + }; - Chart.controllers.bar = Chart.DatasetController.extend({ + Chart.controllers.bar = Chart.DatasetController.extend({ - dataElementType: Chart.elements.Rectangle, + dataElementType: Chart.elements.Rectangle, - initialize: function(chart, datasetIndex) { - Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); + initialize: function(chart, datasetIndex) { + Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); - // Use this to indicate that this is a bar dataset. - this.getMeta().bar = true; - }, + // Use this to indicate that this is a bar dataset. + this.getMeta().bar = true; + }, - // Get the number of datasets that display bars. We use this to correctly calculate the bar width - getBarCount: function() { - var me = this; - var barCount = 0; - helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { - var meta = me.chart.getDatasetMeta(datasetIndex); - if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) { - ++barCount; - } - }, me); - return barCount; - }, + // Get the number of datasets that display bars. We use this to correctly calculate the bar width + getBarCount: function() { + var me = this; + var barCount = 0; + helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { + var meta = me.chart.getDatasetMeta(datasetIndex); + if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) { + ++barCount; + } + }, me); + return barCount; + }, - update: function(reset) { - var me = this; - helpers.each(me.getMeta().data, function(rectangle, index) { - me.updateElement(rectangle, index, reset); - }, me); - }, + 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(); + 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(); - helpers.extend(rectangle, { - // Utility - _xScale: xScale, - _yScale: yScale, - _datasetIndex: me.index, - _index: index, + rectangle._xScale = xScale; + rectangle._yScale = yScale; + rectangle._datasetIndex = me.index; + rectangle._index = index; - // Desired view properties - _model: { - x: me.calculateBarX(index, me.index), - y: reset ? scaleBase : me.calculateBarY(index, me.index), + var ruler = me.getRuler(index); + 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, + // Tooltip + label: me.chart.data.labels[index], + datasetLabel: dataset.label, - // Appearance - base: reset ? scaleBase : me.calculateBarBase(me.index, index), - width: me.calculateBarWidth(index), - 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(); - }, + // Appearance + 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) + }; - calculateBarBase: function(datasetIndex, index) { - var me = this; - var meta = me.getMeta(); - var yScale = me.getScaleForId(meta.yAxisID); - var base = 0; + rectangle.pivot(); + }, - if (yScale.options.stacked) { - var chart = me.chart; - var datasets = chart.data.datasets; - var value = Number(datasets[datasetIndex].data[index]); + calculateBarBase: function(datasetIndex, index) { + var me = this; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var base = 0; - 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)) { - var currentVal = Number(currentDs.data[index]); - base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); - } - } + if (yScale.options.stacked) { + var chart = me.chart; + var datasets = chart.data.datasets; + var value = Number(datasets[datasetIndex].data[index]); - return yScale.getPixelForValue(base); - } + 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)) { + var currentVal = Number(currentDs.data[index]); + base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); + } + } - return yScale.getBasePixel(); - }, + return yScale.getPixelForValue(base); + } - getRuler: function(index) { - var me = this; - var meta = me.getMeta(); - var xScale = me.getScaleForId(meta.xAxisID); - var datasetCount = me.getBarCount(); + return yScale.getBasePixel(); + }, - var tickWidth; + getRuler: function(index) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var datasetCount = me.getBarCount(); - if (xScale.options.type === 'category') { - tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index); - } else { - // Average width - tickWidth = xScale.width / xScale.ticks.length; - } - var categoryWidth = tickWidth * xScale.options.categoryPercentage; - var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2; - var fullBarWidth = categoryWidth / datasetCount; + var tickWidth; - if (xScale.ticks.length !== me.chart.data.labels.length) { - var perc = xScale.ticks.length / me.chart.data.labels.length; - fullBarWidth = fullBarWidth * perc; - } + if (xScale.options.type === 'category') { + tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index); + } else { + // Average width + tickWidth = xScale.width / xScale.ticks.length; + } + var categoryWidth = tickWidth * xScale.options.categoryPercentage; + var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2; + var fullBarWidth = categoryWidth / datasetCount; - var barWidth = fullBarWidth * xScale.options.barPercentage; - var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); + if (xScale.ticks.length !== me.chart.data.labels.length) { + var perc = xScale.ticks.length / me.chart.data.labels.length; + fullBarWidth = fullBarWidth * perc; + } - return { - datasetCount: datasetCount, - tickWidth: tickWidth, - categoryWidth: categoryWidth, - categorySpacing: categorySpacing, - fullBarWidth: fullBarWidth, - barWidth: barWidth, - barSpacing: barSpacing - }; - }, + var barWidth = fullBarWidth * xScale.options.barPercentage; + var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); - calculateBarWidth: function(index) { - var xScale = this.getScaleForId(this.getMeta().xAxisID); - if (xScale.options.barThickness) { - return xScale.options.barThickness; - } - var ruler = this.getRuler(index); - return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth; - }, + return { + datasetCount: datasetCount, + tickWidth: tickWidth, + categoryWidth: categoryWidth, + categorySpacing: categorySpacing, + fullBarWidth: fullBarWidth, + barWidth: barWidth, + barSpacing: barSpacing + }; + }, - // Get bar index from the given dataset index accounting for the fact that not all bars are visible - getBarIndex: function(datasetIndex) { - var barIndex = 0; - var meta, j; + calculateBarWidth: function(ruler) { + var xScale = this.getScaleForId(this.getMeta().xAxisID); + if (xScale.options.barThickness) { + return xScale.options.barThickness; + } + return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth; + }, - for (j = 0; j < datasetIndex; ++j) { - meta = this.chart.getDatasetMeta(j); - if (meta.bar && this.chart.isDatasetVisible(j)) { - ++barIndex; - } - } + // Get bar index from the given dataset index accounting for the fact that not all bars are visible + getBarIndex: function(datasetIndex) { + var barIndex = 0; + var meta, j; - return barIndex; - }, + for (j = 0; j < datasetIndex; ++j) { + meta = this.chart.getDatasetMeta(j); + if (meta.bar && this.chart.isDatasetVisible(j)) { + ++barIndex; + } + } - calculateBarX: function(index, datasetIndex) { - var me = this; - var meta = me.getMeta(); - var xScale = me.getScaleForId(meta.xAxisID); - var barIndex = me.getBarIndex(datasetIndex); + return barIndex; + }, - var ruler = me.getRuler(index); - var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); - leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0; + calculateBarX: function(index, datasetIndex, ruler) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var barIndex = me.getBarIndex(datasetIndex); + var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); + leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0; - if (xScale.options.stacked) { - return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing; - } + if (xScale.options.stacked) { + return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing; + } - return leftTick + - (ruler.barWidth / 2) + - ruler.categorySpacing + - (ruler.barWidth * barIndex) + - (ruler.barSpacing / 2) + - (ruler.barSpacing * barIndex); - }, + return leftTick + + (ruler.barWidth / 2) + + ruler.categorySpacing + + (ruler.barWidth * barIndex) + + (ruler.barSpacing / 2) + + (ruler.barSpacing * barIndex); + }, - calculateBarY: function(index, datasetIndex) { - var me = this; - var meta = me.getMeta(); - var yScale = me.getScaleForId(meta.yAxisID); - var value = Number(me.getDataset().data[index]); + 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) { + if (yScale.options.stacked) { - var sumPos = 0, - sumNeg = 0; + var sumPos = 0, + sumNeg = 0; - 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)) { - var stackedVal = Number(ds.data[index]); - if (stackedVal < 0) { - sumNeg += stackedVal || 0; - } else { - sumPos += stackedVal || 0; - } - } - } + 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)) { + var stackedVal = Number(ds.data[index]); + if (stackedVal < 0) { + sumNeg += stackedVal || 0; + } else { + sumPos += stackedVal || 0; + } + } + } - if (value < 0) { - return yScale.getPixelForValue(sumNeg + value); - } else { - return yScale.getPixelForValue(sumPos + value); - } - } + if (value < 0) { + return yScale.getPixelForValue(sumNeg + value); + } + return yScale.getPixelForValue(sumPos + value); + } - return yScale.getPixelForValue(value); - }, + return yScale.getPixelForValue(value); + }, - draw: function(ease) { - var me = this; - var easingDecimal = ease || 1; - helpers.each(me.getMeta().data, function(rectangle, index) { - var d = me.getDataset().data[index]; - if (d !== null && d !== undefined && !isNaN(d)) { - rectangle.transition(easingDecimal).draw(); - } - }, me); - }, + draw: function(ease) { + var me = this; + var easingDecimal = ease || 1; + var metaData = me.getMeta().data; + var dataset = me.getDataset(); + var i, len; - setHoverStyle: function(rectangle) { - var dataset = this.chart.data.datasets[rectangle._datasetIndex]; - var index = rectangle._index; + 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(); + } + } + }, - 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); - }, + setHoverStyle: function(rectangle) { + var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + var index = rectangle._index; - 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; + 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); + }, - 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); - } + 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", + // 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' + }, - // Specific to Horizontal Bar Controller - categoryPercentage: 0.8, - barPercentage: 0.9, + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + yAxes: [{ + position: 'left', + type: 'category', - // grid line settings - gridLines: { - offsetGridLines: true - } - }] - }, - elements: { - rectangle: { - borderSkipped: 'left' - } - }, - tooltips: { - callbacks: { - title: function(tooltipItems, data) { - // Pick first xLabel for now - var title = ''; + // Specific to Horizontal Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, - 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]; - } - } + // grid line settings + gridLines: { + offsetGridLines: true + } + }] + }, + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + tooltips: { + callbacks: { + title: function(tooltipItems, data) { + // Pick first xLabel for now + var title = ''; - return title; - }, - label: function(tooltipItem, data) { - var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; - return datasetLabel + ': ' + tooltipItem.xLabel; - } - } - } - }; + 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]; + } + } - Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ - 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; + return title; + }, + label: function(tooltipItem, data) { + var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; + return datasetLabel + ': ' + tooltipItem.xLabel; + } + } + } + }; - helpers.extend(rectangle, { - // Utility - _xScale: xScale, - _yScale: yScale, - _datasetIndex: me.index, - _index: index, + Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ + 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; - // Desired view properties - _model: { - x: reset ? scaleBase : me.calculateBarX(index, me.index), - y: me.calculateBarY(index, me.index), + rectangle._xScale = xScale; + rectangle._yScale = yScale; + rectangle._datasetIndex = me.index; + rectangle._index = index; - // Tooltip - label: me.chart.data.labels[index], - datasetLabel: dataset.label, + var ruler = me.getRuler(index); + rectangle._model = { + x: reset ? scaleBase : me.calculateBarX(index, me.index), + y: me.calculateBarY(index, me.index, ruler), - // Appearance - base: reset ? scaleBase : me.calculateBarBase(me.index, index), - height: me.calculateBarHeight(index), - 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) - }, + // Tooltip + label: me.chart.data.labels[index], + datasetLabel: dataset.label, - draw: function () { - var ctx = this._chart.ctx; - var vm = this._view; + // Appearance + 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.draw = function() { + var ctx = this._chart.ctx; + var vm = this._view; - var halfHeight = vm.height / 2, - topY = vm.y - halfHeight, - bottomY = vm.y + halfHeight, - right = vm.base - (vm.base - vm.x), - halfStroke = vm.borderWidth / 2; + var halfHeight = vm.height / 2, + topY = vm.y - halfHeight, + bottomY = vm.y + halfHeight, + right = vm.base - (vm.base - vm.x), + halfStroke = vm.borderWidth / 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 (vm.borderWidth) { - topY += halfStroke; - bottomY -= halfStroke; - right += halfStroke; - } + // 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 (vm.borderWidth) { + topY += halfStroke; + bottomY -= halfStroke; + right += halfStroke; + } - ctx.beginPath(); + ctx.beginPath(); - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; - // Corner points, from bottom-left to bottom-right clockwise - // | 1 2 | - // | 0 3 | - var corners = [ - [vm.base, bottomY], - [vm.base, topY], - [right, topY], - [right, bottomY] - ]; + // Corner points, from bottom-left to bottom-right clockwise + // | 1 2 | + // | 0 3 | + var corners = [ + [vm.base, bottomY], + [vm.base, topY], + [right, topY], + [right, bottomY] + ]; - // Find first (starting) corner with fallback to 'bottom' - var borders = ['bottom', 'left', 'top', 'right']; - var startCorner = borders.indexOf(vm.borderSkipped, 0); - if (startCorner === -1) - startCorner = 0; + // Find first (starting) corner with fallback to 'bottom' + var borders = ['bottom', 'left', 'top', 'right']; + var startCorner = borders.indexOf(vm.borderSkipped, 0); + if (startCorner === -1) { + startCorner = 0; + } - function cornerAt(index) { - return corners[(startCorner + index) % 4]; - } + function cornerAt(cornerIndex) { + return corners[(startCorner + cornerIndex) % 4]; + } - // Draw rectangle from 'startCorner' - ctx.moveTo.apply(ctx, cornerAt(0)); - for (var i = 1; i < 4; i++) - ctx.lineTo.apply(ctx, cornerAt(i)); + // Draw rectangle from 'startCorner' + ctx.moveTo.apply(ctx, cornerAt(0)); + for (var i = 1; i < 4; i++) { + ctx.lineTo.apply(ctx, cornerAt(i)); + } - ctx.fill(); - if (vm.borderWidth) { - ctx.stroke(); - } - }, + ctx.fill(); + if (vm.borderWidth) { + ctx.stroke(); + } + }; - inRange: function (mouseX, mouseY) { - var vm = this._view; - var inRange = false; + rectangle.pivot(); + }, - if (vm) { - if (vm.x < vm.base) { - inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base); - } else { - inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x); - } - } + calculateBarBase: function(datasetIndex, index) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var base = 0; - return inRange; - } - }); + if (xScale.options.stacked) { + var chart = me.chart; + var datasets = chart.data.datasets; + var value = Number(datasets[datasetIndex].data[index]); - rectangle.pivot(); - }, + 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)) { + var currentVal = Number(currentDs.data[index]); + base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); + } + } - calculateBarBase: function (datasetIndex, index) { - var me = this; - var meta = me.getMeta(); - var xScale = me.getScaleForId(meta.xAxisID); - var base = 0; + return xScale.getPixelForValue(base); + } - if (xScale.options.stacked) { - var chart = me.chart; - var datasets = chart.data.datasets; - var value = Number(datasets[datasetIndex].data[index]); + return xScale.getBasePixel(); + }, - 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)) { - var currentVal = Number(currentDs.data[index]); - base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); - } - } + getRuler: function(index) { + var me = this; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var datasetCount = me.getBarCount(); - return xScale.getPixelForValue(base); - } + var tickHeight; + if (yScale.options.type === 'category') { + tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index); + } else { + // Average width + tickHeight = yScale.width / yScale.ticks.length; + } + var categoryHeight = tickHeight * yScale.options.categoryPercentage; + var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2; + var fullBarHeight = categoryHeight / datasetCount; - return xScale.getBasePixel(); - }, + if (yScale.ticks.length !== me.chart.data.labels.length) { + var perc = yScale.ticks.length / me.chart.data.labels.length; + fullBarHeight = fullBarHeight * perc; + } - getRuler: function (index) { - var me = this; - var meta = me.getMeta(); - var yScale = me.getScaleForId(meta.yAxisID); - var datasetCount = me.getBarCount(); + var barHeight = fullBarHeight * yScale.options.barPercentage; + var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage); - var tickHeight; - if (yScale.options.type === 'category') { - tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index); - } else { - // Average width - tickHeight = yScale.width / yScale.ticks.length; - } - var categoryHeight = tickHeight * yScale.options.categoryPercentage; - var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2; - var fullBarHeight = categoryHeight / datasetCount; + return { + datasetCount: datasetCount, + tickHeight: tickHeight, + categoryHeight: categoryHeight, + categorySpacing: categorySpacing, + fullBarHeight: fullBarHeight, + barHeight: barHeight, + barSpacing: barSpacing + }; + }, - if (yScale.ticks.length !== me.chart.data.labels.length) { - var perc = yScale.ticks.length / me.chart.data.labels.length; - fullBarHeight = fullBarHeight * perc; - } + calculateBarHeight: function(ruler) { + var me = this; + var yScale = me.getScaleForId(me.getMeta().yAxisID); + if (yScale.options.barThickness) { + return yScale.options.barThickness; + } + return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight; + }, - var barHeight = fullBarHeight * yScale.options.barPercentage; - var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage); + calculateBarX: function(index, datasetIndex) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var value = Number(me.getDataset().data[index]); - return { - datasetCount: datasetCount, - tickHeight: tickHeight, - categoryHeight: categoryHeight, - categorySpacing: categorySpacing, - fullBarHeight: fullBarHeight, - barHeight: barHeight, - barSpacing: barSpacing, - }; - }, + if (xScale.options.stacked) { - calculateBarHeight: function (index) { - var me = this; - var yScale = me.getScaleForId(me.getMeta().yAxisID); - if (yScale.options.barThickness) { - return yScale.options.barThickness; - } - var ruler = me.getRuler(index); - return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight; - }, + var sumPos = 0, + sumNeg = 0; - calculateBarX: function (index, datasetIndex) { - var me = this; - var meta = me.getMeta(); - var xScale = me.getScaleForId(meta.xAxisID); - var value = Number(me.getDataset().data[index]); + 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)) { + var stackedVal = Number(ds.data[index]); + if (stackedVal < 0) { + sumNeg += stackedVal || 0; + } else { + sumPos += stackedVal || 0; + } + } + } - if (xScale.options.stacked) { + if (value < 0) { + return xScale.getPixelForValue(sumNeg + value); + } + return xScale.getPixelForValue(sumPos + value); + } - var sumPos = 0, - sumNeg = 0; + return xScale.getPixelForValue(value); + }, - 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)) { - var stackedVal = Number(ds.data[index]); - if (stackedVal < 0) { - sumNeg += stackedVal || 0; - } else { - sumPos += stackedVal || 0; - } - } - } + calculateBarY: function(index, datasetIndex, ruler) { + var me = this; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var barIndex = me.getBarIndex(datasetIndex); + var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); + topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0; - if (value < 0) { - return xScale.getPixelForValue(sumNeg + value); - } else { - return xScale.getPixelForValue(sumPos + value); - } - } + if (yScale.options.stacked) { + return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing; + } - return xScale.getPixelForValue(value); - }, + return topTick + + (ruler.barHeight / 2) + + ruler.categorySpacing + + (ruler.barHeight * barIndex) + + (ruler.barSpacing / 2) + + (ruler.barSpacing * barIndex); + } + }); + }; - calculateBarY: function (index, datasetIndex) { - var me = this; - var meta = me.getMeta(); - var yScale = me.getScaleForId(meta.yAxisID); - var barIndex = me.getBarIndex(datasetIndex); +},{}],16:[function(require,module,exports){ + 'use strict'; - var ruler = me.getRuler(index); - var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); - topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0; + module.exports = function(Chart) { - if (yScale.options.stacked) { - return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing; - } + var helpers = Chart.helpers; - return topTick + - (ruler.barHeight / 2) + - ruler.categorySpacing + - (ruler.barHeight * barIndex) + - (ruler.barSpacing / 2) + - (ruler.barSpacing * barIndex); - } - }); -}; + Chart.defaults.bubble = { + hover: { + mode: 'single' + }, -},{}],16:[function(require,module,exports){ -"use strict"; + 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' + }] + }, -module.exports = function(Chart) { + 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 + ')'; + } + } + } + }; - var helpers = Chart.helpers; + Chart.controllers.bubble = Chart.DatasetController.extend({ - Chart.defaults.bubble = { - hover: { - mode: "single" - }, + dataElementType: Chart.elements.Point, - 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" - }] - }, + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; - 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 + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')'; - } - } - } - }; + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, - Chart.controllers.bubble = Chart.DatasetController.extend({ + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); - dataElementType: Chart.elements.Point, + var custom = point.custom || {}; + var dataset = me.getDataset(); + var data = dataset.data[index]; + var pointElementOptions = me.chart.options.elements.point; + var dsIndex = me.index; - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var points = meta.data; + helpers.extend(point, { + // Utility + _xScale: xScale, + _yScale: yScale, + _datasetIndex: dsIndex, + _index: index, - // Update Points - helpers.each(points, function(point, index) { - me.updateElement(point, index, reset); - }); - }, + // 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), - updateElement: function(point, index, reset) { - var me = this; - var meta = me.getMeta(); - var xScale = me.getScaleForId(meta.xAxisID); - var yScale = me.getScaleForId(meta.yAxisID); + // Tooltip + hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) + } + }); - var custom = point.custom || {}; - var dataset = me.getDataset(); - var data = dataset.data[index]; - var pointElementOptions = me.chart.options.elements.point; - var dsIndex = me.index; + // Trick to reset the styles of the point + Chart.DatasetController.prototype.removeHoverStyle.call(me, point, pointElementOptions); - helpers.extend(point, { - // Utility - _xScale: xScale, - _yScale: yScale, - _datasetIndex: dsIndex, - _index: index, + var model = point._model; + model.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y)); - // 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), + point.pivot(); + }, - // Tooltip - hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) - } - }); + getRadius: function(value) { + return value.r || this.chart.options.elements.point.radius; + }, - // Trick to reset the styles of the point - Chart.DatasetController.prototype.removeHoverStyle.call(me, point, pointElementOptions); + setHoverStyle: function(point) { + var me = this; + Chart.DatasetController.prototype.setHoverStyle.call(me, point); - var model = point._model; - model.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y)); + // 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]); + }, - point.pivot(); - }, + removeHoverStyle: function(point) { + var me = this; + Chart.DatasetController.prototype.removeHoverStyle.call(me, point, me.chart.options.elements.point); - getRadius: function(value) { - return value.r || this.chart.options.elements.point.radius; - }, + var dataVal = me.chart.data.datasets[point._datasetIndex].data[point._index]; + var custom = point.custom || {}; + var model = point._model; - setHoverStyle: function(point) { - var me = this; - Chart.DatasetController.prototype.setHoverStyle.call(me, point); + model.radius = custom.radius ? custom.radius : me.getRadius(dataVal); + } + }); + }; - // 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]); - }, +},{}],17:[function(require,module,exports){ + 'use strict'; - removeHoverStyle: function(point) { - var me = this; - Chart.DatasetController.prototype.removeHoverStyle.call(me, point, me.chart.options.elements.point); + module.exports = function(Chart) { - var dataVal = me.chart.data.datasets[point._datasetIndex].data[point._index]; - var custom = point.custom || {}; - var model = point._model; + var helpers = Chart.helpers, + defaults = Chart.defaults; - model.radius = custom.radius ? custom.radius : me.getRadius(dataVal); - } - }); -}; + 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('<ul class="' + chart.id + '-legend">'); -},{}],17:[function(require,module,exports){ -"use strict"; + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; -module.exports = function(Chart) { + if (datasets.length) { + for (var i = 0; i < datasets[0].data.length; ++i) { + text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); + if (labels[i]) { + text.push(labels[i]); + } + text.push('</li>'); + } + } - var helpers = Chart.helpers, - defaults = Chart.defaults; + text.push('</ul>'); + 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); - 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('<ul class="' + chart.id + '-legend">'); + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, - var data = chart.data; - var datasets = data.datasets; - var labels = data.labels; + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, - if (datasets.length) { - for (var i = 0; i < datasets[0].data.length; ++i) { - text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); - if (labels[i]) { - text.push(labels[i]); - } - text.push('</li>'); - } - } + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; - text.push('</ul>'); - 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); + 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; + } + } - return { - text: label, - fillStyle: fill, - strokeStyle: stroke, - lineWidth: bw, - hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + chart.update(); + } + }, - // Extra data used for toggling the correct item - index: i - }; - }); - } else { - return []; - } - } - }, + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, - onClick: function(e, legendItem) { - var index = legendItem.index; - var chart = this.chart; - var i, ilen, meta; + // The rotation of the chart, where the first data arc begins. + rotation: Math.PI * -0.5, - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - meta.data[index].hidden = !meta.data[index].hidden; - } + // The total circumference of the chart. + circumference: Math.PI * 2.0, - chart.update(); - } - }, + // 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]; - //The percentage of the chart that we cut out of the middle. - cutoutPercentage: 50, + 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; + } - //The rotation of the chart, where the first data arc begins. - rotation: Math.PI * -0.5, + return dataLabel; + } + } + } + }; - //The total circumference of the chart. - circumference: Math.PI * 2.0, + defaults.pie = helpers.clone(defaults.doughnut); + helpers.extend(defaults.pie, { + cutoutPercentage: 0 + }); - // Need to override these to give a nice default - tooltips: { - callbacks: { - title: function() { - return ''; - }, - label: function(tooltipItem, data) { - return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; - } - } - } - }; - 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, - Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ + linkScales: helpers.noop, - dataElementType: Chart.elements.Arc, + // 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; - linkScales: helpers.noop, + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } - // 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; + return ringIndex; + }, - for (var j = 0; j < datasetIndex; ++j) { - if (this.chart.isDatasetVisible(j)) { - ++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; - return ringIndex; - }, + // 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}; + } - 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; + chart.borderWidth = me.getMaxBorderWidth(meta.data); + chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); + chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + chart.offsetX = offset.x * chart.outerRadius; + chart.offsetY = offset.y * chart.outerRadius; - // 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); + meta.total = me.calculateTotal(); - chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); - chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0); - chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); - chart.offsetX = offset.x * chart.outerRadius; - chart.offsetY = offset.y * chart.outerRadius; + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); + me.innerRadius = me.outerRadius - chart.radiusLength; - meta.total = me.calculateTotal(); + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, - me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); - me.innerRadius = me.outerRadius - chart.radiusLength; + 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.each(meta.data, function(arc, index) { - me.updateElement(arc, index, reset); - }); - }, + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, - 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; + // 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]) + } + }); - helpers.extend(arc, { - // Utility - _datasetIndex: me.index, - _index: index, + var model = arc._model; + // Resets the visual styles + this.removeHoverStyle(arc); - // 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]) - } - }); + // 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; + } - var model = arc._model; - // Resets the visual styles - this.removeHoverStyle(arc); + model.endAngle = model.startAngle + model.circumference; + } - // 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; - } + arc.pivot(); + }, - model.endAngle = model.startAngle + model.circumference; - } + removeHoverStyle: function(arc) { + Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); + }, - arc.pivot(); - }, + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; - removeHoverStyle: function(arc) { - Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); - }, + helpers.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); - calculateTotal: function() { - var dataset = this.getDataset(); - var meta = this.getMeta(); - var total = 0; - var value; + /* if (total === 0) { + total = NaN; + }*/ - helpers.each(meta.data, function(element, index) { - value = dataset.data[index]; - if (!isNaN(value) && !element.hidden) { - total += Math.abs(value); - } - }); + return total; + }, - /*if (total === 0) { - total = NaN; - }*/ + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return (Math.PI * 2.0) * (value / total); + } + return 0; + }, - return total; - }, + // 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; - calculateCircumference: function(value) { - var total = this.getMeta().total; - if (total > 0 && !isNaN(value)) { - return (Math.PI * 2.0) * (value / total); - } else { - return 0; - } - }, + 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; - //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; + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + return max; + } + }); + }; - 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; +},{}],18:[function(require,module,exports){ + 'use strict'; - max = borderWidth > max ? borderWidth : max; - max = hoverWidth > max ? hoverWidth : max; + 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' + }] } - return max; + }; + + function lineEnabled(dataset, options) { + return helpers.getValueOrDefault(dataset.showLine, options.showLines); } - }); -}; -},{}],18:[function(require,module,exports){ -"use strict"; + Chart.controllers.line = Chart.DatasetController.extend({ -module.exports = function(Chart) { + datasetElementType: Chart.elements.Line, - var helpers = Chart.helpers; + dataElementType: Chart.elements.Point, - Chart.defaults.line = { - showLines: true, - spanGaps: false, + 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); - hover: { - mode: "label" - }, + // Update Line + if (showLine) { + custom = line.custom || {}; - scales: { - xAxes: [{ - type: "category", - id: 'x-axis-0' - }], - yAxes: [{ - type: "linear", - id: 'y-axis-0' - }] - } - }; + // 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; + } - function lineEnabled(dataset, options) { - return helpers.getValueOrDefault(dataset.showLine, options.showLines); - } + // 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() + }; - Chart.controllers.line = Chart.DatasetController.extend({ + line.pivot(); + } - datasetElementType: Chart.elements.Line, + // Update Points + for (i=0, ilen=points.length; i<ilen; ++i) { + me.updateElement(points[i], i, reset); + } - dataElementType: Chart.elements.Point, + if (showLine && line._model.tension !== 0) { + me.updateBezierControlPoints(); + } - addElementAndReset: function(index) { - var me = this; - var options = me.chart.options; - var meta = me.getMeta(); + // Now pivot the point for animation + for (i=0, ilen=points.length; i<ilen; ++i) { + points[i].pivot(); + } + }, - Chart.DatasetController.prototype.addElementAndReset.call(me, index); + getPointBackgroundColor: function(point, index) { + var backgroundColor = this.chart.options.elements.point.backgroundColor; + var dataset = this.getDataset(); + var custom = point.custom || {}; - // Make sure bezier control points are updated - if (lineEnabled(me.getDataset(), options) && meta.dataset._model.tension !== 0) { - me.updateBezierControlPoints(); - } - }, + if (custom.backgroundColor) { + backgroundColor = custom.backgroundColor; + } else if (dataset.pointBackgroundColor) { + backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); + } else if (dataset.backgroundColor) { + backgroundColor = dataset.backgroundColor; + } - 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); + return backgroundColor; + }, - // Update Line - if (showLine) { - custom = line.custom || {}; + getPointBorderColor: function(point, index) { + var borderColor = this.chart.options.elements.point.borderColor; + var dataset = this.getDataset(); + var custom = point.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; - } + if (custom.borderColor) { + borderColor = custom.borderColor; + } else if (dataset.pointBorderColor) { + borderColor = helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); + } else if (dataset.borderColor) { + borderColor = dataset.borderColor; + } - // 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 linse 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), - // Scale - scaleTop: scale.top, - scaleBottom: scale.bottom, - scaleZero: scale.getBasePixel() - }; + return borderColor; + }, - line.pivot(); - } + getPointBorderWidth: function(point, index) { + var borderWidth = this.chart.options.elements.point.borderWidth; + var dataset = this.getDataset(); + var custom = point.custom || {}; - // Update Points - for (i=0, ilen=points.length; i<ilen; ++i) { - me.updateElement(points[i], i, reset); - } + if (custom.borderWidth) { + borderWidth = custom.borderWidth; + } else if (dataset.pointBorderWidth) { + borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); + } else if (dataset.borderWidth) { + borderWidth = dataset.borderWidth; + } - if (showLine && line._model.tension !== 0) { - me.updateBezierControlPoints(); - } + return borderWidth; + }, - // Now pivot the point for animation - for (i=0, ilen=points.length; i<ilen; ++i) { - points[i].pivot(); - } - }, + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var dataset = me.getDataset(); + var datasetIndex = me.index; + var value = dataset.data[index]; + var yScale = me.getScaleForId(meta.yAxisID); + var xScale = me.getScaleForId(meta.xAxisID); + var pointOptions = me.chart.options.elements.point; + var x, y; + var labels = me.chart.data.labels || []; + var includeOffset = (labels.length === 1 || dataset.data.length === 1) || me.chart.isCombo; - getPointBackgroundColor: function(point, index) { - var backgroundColor = this.chart.options.elements.point.backgroundColor; - var dataset = this.getDataset(); - var custom = point.custom || {}; + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { + dataset.pointHitRadius = dataset.hitRadius; + } - if (custom.backgroundColor) { - backgroundColor = custom.backgroundColor; - } else if (dataset.pointBackgroundColor) { - backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); - } else if (dataset.backgroundColor) { - backgroundColor = dataset.backgroundColor; - } + x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex, includeOffset); + y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); - return backgroundColor; - }, + // Utility + point._xScale = xScale; + point._yScale = yScale; + point._datasetIndex = datasetIndex; + point._index = index; - getPointBorderColor: function(point, index) { - var borderColor = this.chart.options.elements.point.borderColor; - var dataset = this.getDataset(); - var custom = point.custom || {}; + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), + pointStyle: custom.pointStyle || helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), + backgroundColor: me.getPointBackgroundColor(point, index), + borderColor: me.getPointBorderColor(point, index), + borderWidth: me.getPointBorderWidth(point, index), + tension: meta.dataset._model ? meta.dataset._model.tension : 0, + steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, + // Tooltip + hitRadius: custom.hitRadius || helpers.getValueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) + }; + }, - if (custom.borderColor) { - borderColor = custom.borderColor; - } else if (dataset.pointBorderColor) { - borderColor = helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); - } else if (dataset.borderColor) { - borderColor = dataset.borderColor; - } + calculatePointY: function(value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta; - return borderColor; - }, + if (yScale.options.stacked) { + for (i = 0; i < datasetIndex; i++) { + ds = chart.data.datasets[i]; + dsMeta = chart.getDatasetMeta(i); + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { + var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; + } + } + } - getPointBorderWidth: function(point, index) { - var borderWidth = this.chart.options.elements.point.borderWidth; - var dataset = this.getDataset(); - var custom = point.custom || {}; + var rightValue = Number(yScale.getRightValue(value)); + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); + } + return yScale.getPixelForValue(sumPos + rightValue); + } - if (custom.borderWidth) { - borderWidth = custom.borderWidth; - } else if (dataset.pointBorderWidth) { - borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); - } else if (dataset.borderWidth) { - borderWidth = dataset.borderWidth; - } + return yScale.getPixelForValue(value); + }, - return borderWidth; - }, + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = (meta.data || []); + var i, ilen, point, model, controlPoints; - updateElement: function(point, index, reset) { - var me = this; - var meta = me.getMeta(); - var custom = point.custom || {}; - var dataset = me.getDataset(); - var datasetIndex = me.index; - var value = dataset.data[index]; - var yScale = me.getScaleForId(meta.yAxisID); - var xScale = me.getScaleForId(meta.xAxisID); - var pointOptions = me.chart.options.elements.point; - var x, y; + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } - // Compatibility: If the properties are defined with only the old name, use those values - if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { - dataset.pointRadius = dataset.radius; - } - if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { - dataset.pointHitRadius = dataset.hitRadius; - } + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } - x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex, me.chart.isCombo); - y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + if (meta.dataset._model.cubicInterpolationMode === 'monotone') { + helpers.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + point = points[i]; + model = point._model; + controlPoints = helpers.splineCurve( + helpers.previousItem(points, i)._model, + model, + helpers.nextItem(points, i)._model, + meta.dataset._model.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; + } + } - // Utility - point._xScale = xScale; - point._yScale = yScale; - point._datasetIndex = datasetIndex; - point._index = index; + if (me.chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); + model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); + } + } + }, - // Desired view properties - point._model = { - x: x, - y: y, - skip: custom.skip || isNaN(x) || isNaN(y), - // Appearance - radius: custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), - pointStyle: custom.pointStyle || helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), - backgroundColor: me.getPointBackgroundColor(point, index), - borderColor: me.getPointBorderColor(point, index), - borderWidth: me.getPointBorderWidth(point, index), - tension: meta.dataset._model ? meta.dataset._model.tension : 0, - steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, - // Tooltip - hitRadius: custom.hitRadius || helpers.getValueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) - }; - }, + draw: function(ease) { + var me = this; + var meta = me.getMeta(); + var points = meta.data || []; + var easingDecimal = ease || 1; + var i, ilen; - calculatePointY: function(value, index, datasetIndex) { - var me = this; - var chart = me.chart; - var meta = me.getMeta(); - var yScale = me.getScaleForId(meta.yAxisID); - var sumPos = 0; - var sumNeg = 0; - var i, ds, dsMeta; + // Transition Point Locations + for (i=0, ilen=points.length; i<ilen; ++i) { + points[i].transition(easingDecimal); + } - if (yScale.options.stacked) { - for (i = 0; i < datasetIndex; i++) { - ds = chart.data.datasets[i]; - dsMeta = chart.getDatasetMeta(i); - if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { - var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); - if (stackedRightValue < 0) { - sumNeg += stackedRightValue || 0; - } else { - sumPos += stackedRightValue || 0; - } - } - } + // Transition and Draw the line + if (lineEnabled(me.getDataset(), me.chart.options)) { + meta.dataset.transition(easingDecimal).draw(); + } - var rightValue = Number(yScale.getRightValue(value)); - if (rightValue < 0) { - return yScale.getPixelForValue(sumNeg + rightValue); - } else { - return yScale.getPixelForValue(sumPos + rightValue); - } - } + // Draw the points + for (i=0, ilen=points.length; i<ilen; ++i) { + points[i].draw(); + } + }, - return yScale.getPixelForValue(value); - }, + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var index = point._index; + var custom = point.custom || {}; + var model = point._model; - updateBezierControlPoints: function() { - var me = this; - var meta = me.getMeta(); - var area = me.chart.chartArea; + model.radius = custom.hoverRadius || helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + model.backgroundColor = custom.hoverBackgroundColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + }, - // only consider points that are drawn in case the spanGaps option is ued - var points = (meta.data || []).filter(function(pt) { return !pt._model.skip; }); - var i, ilen, point, model, controlPoints; + removeHoverStyle: function(point) { + var me = this; + var dataset = me.chart.data.datasets[point._datasetIndex]; + var index = point._index; + var custom = point.custom || {}; + var model = point._model; - var needToCap = me.chart.options.elements.line.capBezierPoints; - function capIfNecessary(pt, min, max) { - return needToCap ? Math.max(Math.min(pt, max), min) : pt; - } + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } - for (i=0, ilen=points.length; i<ilen; ++i) { - point = points[i]; - model = point._model; - controlPoints = helpers.splineCurve( - helpers.previousItem(points, i)._model, - model, - helpers.nextItem(points, i)._model, - meta.dataset._model.tension - ); + model.radius = custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius); + model.backgroundColor = me.getPointBackgroundColor(point, index); + model.borderColor = me.getPointBorderColor(point, index); + model.borderWidth = me.getPointBorderWidth(point, index); + } + }); + }; - model.controlPointPreviousX = capIfNecessary(controlPoints.previous.x, area.left, area.right); - model.controlPointPreviousY = capIfNecessary(controlPoints.previous.y, area.top, area.bottom); - model.controlPointNextX = capIfNecessary(controlPoints.next.x, area.left, area.right); - model.controlPointNextY = capIfNecessary(controlPoints.next.y, area.top, area.bottom); - } - }, +},{}],19:[function(require,module,exports){ + 'use strict'; - draw: function(ease) { - var me = this; - var meta = me.getMeta(); - var points = meta.data || []; - var easingDecimal = ease || 1; - var i, ilen; + module.exports = function(Chart) { - // Transition Point Locations - for (i=0, ilen=points.length; i<ilen; ++i) { - points[i].transition(easingDecimal); - } + var helpers = Chart.helpers; - // Transition and Draw the line - if (lineEnabled(me.getDataset(), me.chart.options)) { - meta.dataset.transition(easingDecimal).draw(); - } + Chart.defaults.polarArea = { - // Draw the points - for (i=0, ilen=points.length; i<ilen; ++i) { - points[i].draw(); - } - }, + scale: { + type: 'radialLinear', + lineArc: true, // so that lines are circular + ticks: { + beginAtZero: true + } + }, - setHoverStyle: function(point) { - // Point - var dataset = this.chart.data.datasets[point._datasetIndex]; - var index = point._index; - var custom = point.custom || {}; - var model = point._model; + // Boolean - Whether to animate the rotation of the chart + animation: { + animateRotate: true, + animateScale: true + }, - model.radius = custom.hoverRadius || helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); - model.backgroundColor = custom.hoverBackgroundColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); - model.borderColor = custom.hoverBorderColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); - model.borderWidth = custom.hoverBorderWidth || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); - }, + startAngle: -0.5 * Math.PI, + aspectRatio: 1, + legendCallback: function(chart) { + var text = []; + text.push('<ul class="' + chart.id + '-legend">'); - removeHoverStyle: function(point) { - var me = this; - var dataset = me.chart.data.datasets[point._datasetIndex]; - var index = point._index; - var custom = point.custom || {}; - var model = point._model; + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; - // Compatibility: If the properties are defined with only the old name, use those values - if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { - dataset.pointRadius = dataset.radius; - } + if (datasets.length) { + for (var i = 0; i < datasets[0].data.length; ++i) { + text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); + if (labels[i]) { + text.push(labels[i]); + } + text.push('</li>'); + } + } - model.radius = custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius); - model.backgroundColor = me.getPointBackgroundColor(point, index); - model.borderColor = me.getPointBorderColor(point, index); - model.borderWidth = me.getPointBorderWidth(point, index); - } - }); -}; + text.push('</ul>'); + 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); -},{}],19:[function(require,module,exports){ -"use strict"; + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, -module.exports = function(Chart) { + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, - var helpers = Chart.helpers; + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; - Chart.defaults.polarArea = { + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } - scale: { - type: "radialLinear", - lineArc: true, // so that lines are circular - ticks: { - beginAtZero: true - } - }, + chart.update(); + } + }, - //Boolean - Whether to animate the rotation of the chart - animation: { - animateRotate: true, - animateScale: true - }, + // 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; + } + } + } + }; - startAngle: -0.5 * Math.PI, - aspectRatio: 1, - legendCallback: function(chart) { - var text = []; - text.push('<ul class="' + chart.id + '-legend">'); + Chart.controllers.polarArea = Chart.DatasetController.extend({ - var data = chart.data; - var datasets = data.datasets; - var labels = data.labels; + dataElementType: Chart.elements.Arc, - if (datasets.length) { - for (var i = 0; i < datasets[0].data.length; ++i) { - text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '">'); - if (labels[i]) { - text.push(labels[i]); - } - text.push('</span></li>'); - } - } + linkScales: helpers.noop, - text.push('</ul>'); - 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); + 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(); - return { - text: label, - fillStyle: fill, - strokeStyle: stroke, - lineWidth: bw, - hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; - // Extra data used for toggling the correct item - index: i - }; - }); - } else { - return []; - } - } - }, + meta.count = me.countVisibleElements(); - onClick: function(e, legendItem) { - var index = legendItem.index; - var chart = this.chart; - var i, ilen, meta; + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - meta.data[index].hidden = !meta.data[index].hidden; - } + 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; - chart.update(); - } - }, + var circumference = me.calculateCircumference(dataset.data[index]); + var centerX = scale.xCenter; + var centerY = scale.yCenter; - // 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; - } - } - } - }; + // 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; + } + } - Chart.controllers.polarArea = Chart.DatasetController.extend({ + // 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); - dataElementType: Chart.elements.Arc, + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); - linkScales: helpers.noop, + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, - 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(); + // 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]) + } + }); - me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); - me.innerRadius = me.outerRadius - chart.radiusLength; + // Apply border and fill style + me.removeHoverStyle(arc); - meta.count = me.countVisibleElements(); + arc.pivot(); + }, - helpers.each(meta.data, function(arc, index) { - me.updateElement(arc, index, reset); - }); - }, + removeHoverStyle: function(arc) { + Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); + }, - 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; + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; - var circumference = me.calculateCircumference(dataset.data[index]); - var centerX = scale.xCenter; - var centerY = scale.yCenter; + helpers.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); - // 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; - } - } + return count; + }, - //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); + calculateCircumference: function(value) { + var count = this.getMeta().count; + if (count > 0 && !isNaN(value)) { + return (2 * Math.PI) / count; + } + return 0; + } + }); + }; - var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); +},{}],20:[function(require,module,exports){ + 'use strict'; - helpers.extend(arc, { - // Utility - _datasetIndex: me.index, - _index: index, - _scale: scale, + module.exports = function(Chart) { - // 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]) - } - }); + var helpers = Chart.helpers; - // Apply border and fill style - me.removeHoverStyle(arc); + Chart.defaults.radar = { + aspectRatio: 1, + scale: { + type: 'radialLinear' + }, + elements: { + line: { + tension: 0 // no bezier in radar + } + } + }; - arc.pivot(); - }, + Chart.controllers.radar = Chart.DatasetController.extend({ - removeHoverStyle: function(arc) { - Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); - }, + datasetElementType: Chart.elements.Line, - countVisibleElements: function() { - var dataset = this.getDataset(); - var meta = this.getMeta(); - var count = 0; + dataElementType: Chart.elements.Point, - helpers.each(meta.data, function(element, index) { - if (!isNaN(dataset.data[index]) && !element.hidden) { - count++; - } - }); + linkScales: helpers.noop, - return count; - }, + 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; - calculateCircumference: function(value) { - var count = this.getMeta().count; - if (count > 0 && !isNaN(value)) { - return (2 * Math.PI) / count; - } else { - return 0; - } - } - }); -}; + // 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; + } -},{}],20:[function(require,module,exports){ -"use strict"; + 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), -module.exports = function(Chart) { + // Scale + scaleTop: scale.top, + scaleBottom: scale.bottom, + scaleZero: scale.getBasePosition() + } + }); - var helpers = Chart.helpers; + meta.dataset.pivot(); - Chart.defaults.radar = { - scale: { - type: "radialLinear" - }, - elements: { - line: { - tension: 0 // no bezier in radar - } - } - }; + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }, me); - Chart.controllers.radar = Chart.DatasetController.extend({ + // 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]); - datasetElementType: Chart.elements.Line, + helpers.extend(point, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, - dataElementType: Chart.elements.Point, + // 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, - linkScales: helpers.noop, + // Appearance + tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, 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), - addElementAndReset: function(index) { - Chart.DatasetController.prototype.addElementAndReset.call(this, index); + // Tooltip + hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) + } + }); - // Make sure bezier control points are updated - this.updateBezierControlPoints(); - }, + 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(); - 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; + 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 + ); - // 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; - } + // 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); - 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), + 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); - // Scale - scaleTop: scale.top, - scaleBottom: scale.bottom, - scaleZero: scale.getBasePosition() - } - }); + // Now pivot the point for animation + point.pivot(); + }); + }, - meta.dataset.pivot(); + draw: function(ease) { + var meta = this.getMeta(); + var easingDecimal = ease || 1; - // Update Points - helpers.each(points, function(point, index) { - me.updateElement(point, index, reset); - }, me); + // Transition Point Locations + helpers.each(meta.data, function(point) { + point.transition(easingDecimal); + }); + // Transition and Draw the line + meta.dataset.transition(easingDecimal).draw(); - // 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]); + // Draw the points + helpers.each(meta.data, function(point) { + point.draw(); + }); + }, - helpers.extend(point, { - // Utility - _datasetIndex: me.index, - _index: index, - _scale: scale, + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; - // 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, + 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); + }, - // Appearance - tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, 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), + 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; - // Tooltip - hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) - } - }); + 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); + } + }); + }; - 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(); +},{}],21:[function(require,module,exports){ + /* global window: false */ + 'use strict'; - 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 - ); + module.exports = function(Chart) { - // 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); + var helpers = Chart.helpers; - 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); + Chart.defaults.global.animation = { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers.noop, + onComplete: helpers.noop + }; - // Now pivot the point for animation - point.pivot(); - }); - }, + 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 - draw: function(ease) { - var meta = this.getMeta(); - var easingDecimal = ease || 1; + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null // user specified callback to fire when the animation finishes + }); - // Transition Point Locations - helpers.each(meta.data, function(point) { - point.transition(easingDecimal); - }); + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + request: null, - // Transition and Draw the line - meta.dataset.transition(easingDecimal).draw(); + /** + * @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; - // Draw the points - helpers.each(meta.data, function(point) { - point.draw(); - }); - }, + if (!lazy) { + chartInstance.animating = true; + } - setHoverStyle: function(point) { - // Point - var dataset = this.chart.data.datasets[point._datasetIndex]; - var custom = point.custom || {}; - var index = point._index; - var model = point._model; + 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; + } + } - 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); - }, + me.animations.push({ + chartInstance: chartInstance, + animationObject: animationObject + }); - 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; + // 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; + }); - 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); - } - }); -}; + 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; -},{}],21:[function(require,module,exports){ -/*global window: false */ -"use strict"; + var startTime = Date.now(); + var framesToDrop = 0; -module.exports = function(Chart) { + if (me.dropFrames > 1) { + framesToDrop = Math.floor(me.dropFrames); + me.dropFrames = me.dropFrames % 1; + } - var helpers = Chart.helpers; + var i = 0; + while (i < me.animations.length) { + if (me.animations[i].animationObject.currentStep === null) { + me.animations[i].animationObject.currentStep = 0; + } - Chart.defaults.global.animation = { - duration: 1000, - easing: "easeOutQuart", - onProgress: helpers.noop, - onComplete: helpers.noop - }; + me.animations[i].animationObject.currentStep += 1 + framesToDrop; - 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 + if (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) { + me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps; + } - onAnimationProgress: null, // user specified callback to fire on each step of the animation - onAnimationComplete: null // user specified callback to fire when the animation finishes - }); + 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]); + } - Chart.animationService = { - frameDuration: 17, - animations: [], - dropFrames: 0, - request: null, - addAnimation: function(chartInstance, animationObject, duration, lazy) { - var me = this; + 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]); + } - if (!lazy) { - chartInstance.animating = true; - } + // executed the last frame. Remove the animation. + me.animations[i].chartInstance.animating = false; - 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.splice(i, 1); + } else { + ++i; + } + } - me.animations.push({ - chartInstance: chartInstance, - animationObject: animationObject - }); + var endTime = Date.now(); + var dropFrames = (endTime - startTime) / me.frameDuration; - // 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; - }); + me.dropFrames += dropFrames; - 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; + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + } + }; + }; - var startTime = Date.now(); - var framesToDrop = 0; +},{}],22:[function(require,module,exports){ + 'use strict'; - if (me.dropFrames > 1) { - framesToDrop = Math.floor(me.dropFrames); - me.dropFrames = me.dropFrames % 1; - } + module.exports = function(Chart) { + // Global Chart canvas helpers object for drawing items to canvas + var helpers = Chart.canvasHelpers = {}; - var i = 0; - while (i < me.animations.length) { - if (me.animations[i].animationObject.currentStep === null) { - me.animations[i].animationObject.currentStep = 0; - } + helpers.drawPoint = function(ctx, pointStyle, radius, x, y) { + var type, edgeLength, xOffset, yOffset, height, size; - me.animations[i].animationObject.currentStep += 1 + framesToDrop; + 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 (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) { - me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps; - } + if (isNaN(radius) || radius <= 0) { + return; + } - 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]); - } + 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 '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; + } - 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]); - } + ctx.stroke(); + }; + }; - // executed the last frame. Remove the animation. - me.animations[i].chartInstance.animating = false; +},{}],23:[function(require,module,exports){ + 'use strict'; - me.animations.splice(i, 1); - } else { - ++i; - } - } + module.exports = function(Chart) { - var endTime = Date.now(); - var dropFrames = (endTime - startTime) / me.frameDuration; + var helpers = Chart.helpers; - me.dropFrames += dropFrames; + // Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; - // Do we have more stuff to animate? - if (me.animations.length > 0) { - me.requestAnimationFrame(); - } - } - }; -}; -},{}],22:[function(require,module,exports){ -"use strict"; + // 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 = {}; -module.exports = function(Chart) { - // Global Chart canvas helpers object for drawing items to canvas - var helpers = Chart.canvasHelpers = {}; + // Controllers available for dataset visualization eg. bar, line, slice, etc. + Chart.controllers = {}; - helpers.drawPoint = function(ctx, pointStyle, radius, x, y) { - var type, edgeLength, xOffset, yOffset, height, size; + /** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * TODO(SB) Move this method in the upcoming core.platform class. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {Number} Size in pixels or undefined if unknown. + */ + function readUsedSize(element, property) { + var value = helpers.getStyle(element, property); + var matches = value && value.match(/(\d+)px/); + return matches? Number(matches[1]) : undefined; + } - 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; - } - } + /** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + * TODO(SB) Move this method in the upcoming core.platform class. + */ + function initCanvas(canvas, config) { + var style = canvas.style; - if (isNaN(radius) || radius <= 0) { - return; - } + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); - 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 '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; - } + // Chart.js modifies some canvas values that we want to restore on destroy + canvas._chartjs = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; - ctx.stroke(); - }; -}; -},{}],23:[function(require,module,exports){ -"use strict"; + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; -module.exports = function(Chart) { + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } - var helpers = Chart.helpers; - //Create a dictionary of chart types, to allow for extension of existing types - Chart.types = {}; + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); + if (displayWidth !== undefined) { + canvas.height = displayHeight; + } + } + } - //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 = {}; + return canvas; + } - // Controllers available for dataset visualization eg. bar, line, slice, etc. - Chart.controllers = {}; + /** + * Restores the canvas initial state, such as render/display sizes and style. + * TODO(SB) Move this method in the upcoming core.platform class. + */ + function releaseCanvas(canvas) { + if (!canvas._chartjs) { + return; + } - /** - * @class Chart.Controller - * The main controller of a chart. - */ - Chart.Controller = function(instance) { + var initial = canvas._chartjs.initial; + ['height', 'width'].forEach(function(prop) { + var value = initial[prop]; + if (value === undefined || value === null) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); - this.chart = instance; - this.config = instance.config; - this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {}); - this.id = helpers.uid(); + helpers.each(initial.style || {}, function(value, key) { + canvas.style[key] = value; + }); - Object.defineProperty(this, 'data', { - get: function() { - return this.config.data; - } - }); + // The canvas render size might have been changed (and thus the state stack discarded), + // we can't use save() and restore() to restore the initial state. So make sure that at + // least the canvas context is reset to the default state by setting the canvas width. + // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html + canvas.width = canvas.width; - //Add the chart instance to the global namespace - Chart.instances[this.id] = this; + delete canvas._chartjs; + } - if (this.options.responsive) { - // Silent resize before chart draws - this.resize(true); - } + /** + * TODO(SB) Move this method in the upcoming core.platform class. + */ + function acquireContext(item, config) { + if (typeof item === 'string') { + item = document.getElementById(item); + } else if (item.length) { + // Support for array based queries (such as jQuery) + item = item[0]; + } - this.initialize(); + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } - return this; - }; + if (item instanceof HTMLCanvasElement) { + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + var context = item.getContext && item.getContext('2d'); + if (context instanceof CanvasRenderingContext2D) { + initCanvas(item, config); + return context; + } + } - helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ { + return null; + } - initialize: function() { - var me = this; - // Before init plugin notification - Chart.plugins.notify('beforeInit', [me]); + /** + * Initializes the given config with global and chart default values. + */ + function initConfig(config) { + config = config || {}; - me.bindEvents(); + // 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 || []; - // Make sure controllers are built first so that each dataset is bound to an axis before the scales - // are built - me.ensureScalesHaveIDs(); - me.buildOrUpdateControllers(); - me.buildScales(); - me.updateLayout(); - me.resetElements(); - me.initToolTip(); - me.update(); + config.options = helpers.configMerge( + Chart.defaults.global, + Chart.defaults[config.type], + config.options || {}); - // After init plugin notification - Chart.plugins.notify('afterInit', [me]); + return config; + } - return me; - }, + /** + * @class Chart.Controller + * The main controller of a chart. + */ + Chart.Controller = function(item, config, instance) { + var me = this; - clear: function() { - helpers.clear(this.chart); - return this; - }, + config = initConfig(config); - stop: function() { - // Stops any current animation loop occuring - Chart.animationService.cancelAnimation(this); - return this; - }, + var context = acquireContext(item, config); + var canvas = context && context.canvas; + var height = canvas && canvas.height; + var width = canvas && canvas.width; - resize: function resize(silent) { - var me = this; - var chart = me.chart; - var canvas = chart.canvas; - var newWidth = helpers.getMaximumWidth(canvas); - var aspectRatio = chart.aspectRatio; - var newHeight = (me.options.maintainAspectRatio && isNaN(aspectRatio) === false && isFinite(aspectRatio) && aspectRatio !== 0) ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas); + instance.ctx = context; + instance.canvas = canvas; + instance.config = config; + instance.width = width; + instance.height = height; + instance.aspectRatio = height? width / height : null; - var sizeChanged = chart.width !== newWidth || chart.height !== newHeight; + me.id = helpers.uid(); + me.chart = instance; + me.config = config; + me.options = config.options; + me._bufferedRender = false; - if (!sizeChanged) { - return me; - } + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; - canvas.width = chart.width = newWidth; - canvas.height = chart.height = newHeight; + Object.defineProperty(me, 'data', { + get: function() { + return me.config.data; + } + }); - helpers.retinaScale(chart); + 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; + } - // Notify any plugins about the resize - var newSize = { width: newWidth, height: newHeight }; - Chart.plugins.notify('resize', [me, newSize]); + helpers.retinaScale(instance); - // Notify of resize - if (me.options.onResize) { - me.options.onResize(me, newSize); - } + // Responsiveness is currently based on the use of an iframe, however this method causes + // performance issues and could be troublesome when used with ad blockers. So make sure + // that the user is still able to create a chart without iframe when responsive is false. + // See https://github.com/chartjs/Chart.js/issues/2210 + if (me.options.responsive) { + helpers.addResizeListener(canvas.parentNode, function() { + me.resize(); + }); - if (!silent) { - me.stop(); - me.update(me.options.responsiveAnimationDuration); - } + // Initial resize before chart draws (must be silent to preserve initial animations). + me.resize(true); + } - return me; - }, + me.initialize(); - ensureScalesHaveIDs: function() { - var options = this.options; - var scalesOptions = options.scales || {}; - var scaleOptions = options.scale; + return me; + }; - helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { - xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); - }); + helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ { + initialize: function() { + var me = this; - helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { - yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); - }); + // Before init plugin notification + Chart.plugins.notify('beforeInit', [me]); - if (scaleOptions) { - scaleOptions.id = scaleOptions.id || 'scale'; - } - }, + me.bindEvents(); - /** - * 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 = []; + // Make sure controllers are built first so that each dataset is bound to an axis before the scales + // are built + me.ensureScalesHaveIDs(); + me.buildOrUpdateControllers(); + me.buildScales(); + me.updateLayout(); + me.resetElements(); + me.initToolTip(); + me.update(); - 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' }; })); - } + // After init plugin notification + Chart.plugins.notify('afterInit', [me]); - if (options.scale) { - items.push({ options: options.scale, dtype: 'radialLinear', isDefault: true }); - } + return me; + }, - 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; - } + clear: function() { + helpers.clear(this.chart); + return this; + }, - var scale = new scaleClass({ - id: scaleOptions.id, - options: scaleOptions, - ctx: me.chart.ctx, - chart: me - }); + stop: function() { + // Stops any current animation loop occurring + Chart.animationService.cancelAnimation(this); + return this; + }, - scales[scale.id] = scale; + 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; - // 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; - } - }); + // 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)); - Chart.scaleService.addScalesToLayout(this); - }, + if (chart.width === newWidth && chart.height === newHeight) { + return; + } - updateLayout: function() { - Chart.layoutService.update(this, this.chart.width, this.chart.height); - }, + canvas.width = chart.width = newWidth; + canvas.height = chart.height = newHeight; + canvas.style.width = newWidth + 'px'; + canvas.style.height = newHeight + 'px'; - buildOrUpdateControllers: function() { - var me = this; - var types = []; - var newControllers = []; + helpers.retinaScale(chart); - helpers.each(me.data.datasets, function(dataset, datasetIndex) { - var meta = me.getDatasetMeta(datasetIndex); - if (!meta.type) { - meta.type = dataset.type || me.config.type; - } + // Notify any plugins about the resize + var newSize = {width: newWidth, height: newHeight}; + Chart.plugins.notify('resize', [me, newSize]); - types.push(meta.type); + // Notify of resize + if (me.options.onResize) { + me.options.onResize(me, newSize); + } - if (meta.controller) { - meta.controller.updateIndex(datasetIndex); - } else { - meta.controller = new Chart.controllers[meta.type](me, datasetIndex); - newControllers.push(meta.controller); - } - }, me); + if (!silent) { + me.stop(); + me.update(me.options.responsiveAnimationDuration); + } + }, - if (types.length > 1) { - for (var i = 1; i < types.length; i++) { - if (types[i] !== types[i - 1]) { - me.isCombo = true; - break; - } - } - } + ensureScalesHaveIDs: function() { + var options = this.options; + var scalesOptions = options.scales || {}; + var scaleOptions = options.scale; - return newControllers; - }, + helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { + xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); + }); - resetElements: function() { - var me = this; - helpers.each(me.data.datasets, function(dataset, datasetIndex) { - me.getDatasetMeta(datasetIndex).controller.reset(); - }, me); - }, + helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { + yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); + }); - update: function update(animationDuration, lazy) { - var me = this; - Chart.plugins.notify('beforeUpdate', [me]); + if (scaleOptions) { + scaleOptions.id = scaleOptions.id || 'scale'; + } + }, - // In case the entire data object changed - me.tooltip._data = me.data; + /** + * 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 = []; - // Make sure dataset controllers are updated and new controllers are reset - var newControllers = me.buildOrUpdateControllers(); + 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'}; + }) + ); + } - // Make sure all dataset controllers have correct meta data counts - helpers.each(me.data.datasets, function(dataset, datasetIndex) { - me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); - }, me); + if (options.scale) { + items.push({options: options.scale, dtype: 'radialLinear', isDefault: true}); + } - Chart.layoutService.update(me, me.chart.width, me.chart.height); + 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; + } - // Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages - Chart.plugins.notify('afterScaleUpdate', [me]); + var scale = new scaleClass({ + id: scaleOptions.id, + options: scaleOptions, + ctx: me.chart.ctx, + chart: me + }); - // Can only reset the new controllers after the scales have been updated - helpers.each(newControllers, function(controller) { - controller.reset(); - }); + scales[scale.id] = scale; - me.updateDatasets(); + // 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; + } + }); - // Do this before render so that any plugins that need final scale updates can use it - Chart.plugins.notify('afterUpdate', [me]); + Chart.scaleService.addScalesToLayout(this); + }, - me.render(animationDuration, lazy); - }, + updateLayout: function() { + Chart.layoutService.update(this, this.chart.width, this.chart.height); + }, - /** - * @method beforeDatasetsUpdate - * @description Called before all datasets are updated. If a plugin returns false, - * the datasets update will be cancelled until another chart update is triggered. - * @param {Object} instance the chart instance being updated. - * @returns {Boolean} false to cancel the datasets update. - * @memberof Chart.PluginBase - * @since version 2.1.5 - * @instance - */ + buildOrUpdateControllers: function() { + var me = this; + var types = []; + var newControllers = []; - /** - * @method afterDatasetsUpdate - * @description Called after all datasets have been updated. Note that this - * extension will not be called if the datasets update has been cancelled. - * @param {Object} instance the chart instance being updated. - * @memberof Chart.PluginBase - * @since version 2.1.5 - * @instance - */ + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + var meta = me.getDatasetMeta(datasetIndex); + if (!meta.type) { + meta.type = dataset.type || me.config.type; + } - /** - * Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate - * extension, in which case no datasets will be updated and the afterDatasetsUpdate - * notification will be skipped. - * @protected - * @instance - */ - updateDatasets: function() { - var me = this; - var i, ilen; + types.push(meta.type); - if (Chart.plugins.notify('beforeDatasetsUpdate', [ me ])) { - for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { - me.getDatasetMeta(i).controller.update(); - } + if (meta.controller) { + meta.controller.updateIndex(datasetIndex); + } else { + meta.controller = new Chart.controllers[meta.type](me, datasetIndex); + newControllers.push(meta.controller); + } + }, me); - Chart.plugins.notify('afterDatasetsUpdate', [ me ]); - } - }, + if (types.length > 1) { + for (var i = 1; i < types.length; i++) { + if (types[i] !== types[i - 1]) { + me.isCombo = true; + break; + } + } + } - render: function render(duration, lazy) { - var me = this; - Chart.plugins.notify('beforeRender', [me]); + return newControllers; + }, - var animationOptions = me.options.animation; - 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; + /** + * Reset the elements of all datasets + * @method resetElements + * @private + */ + resetElements: function() { + var me = this; + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.reset(); + }, me); + }, - // render function - animation.render = function(chartInstance, animationObject) { - var easingFunction = helpers.easingEffects[animationObject.easing]; - var stepDecimal = animationObject.currentStep / animationObject.numSteps; - var easeDecimal = easingFunction(stepDecimal); + /** + * Resets the chart back to it's state before the initial animation + * @method reset + */ + reset: function() { + this.resetElements(); + this.tooltip.initialize(); + }, - chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); - }; + update: function(animationDuration, lazy) { + var me = this; + Chart.plugins.notify('beforeUpdate', [me]); - // user events - animation.onAnimationProgress = animationOptions.onProgress; - animation.onAnimationComplete = animationOptions.onComplete; + // In case the entire data object changed + me.tooltip._data = me.data; - Chart.animationService.addAnimation(me, animation, duration, lazy); - } else { - me.draw(); - if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) { - animationOptions.onComplete.call(me); - } - } - return me; - }, + // Make sure dataset controllers are updated and new controllers are reset + var newControllers = me.buildOrUpdateControllers(); - draw: function(ease) { - var me = this; - var easingDecimal = ease || 1; - me.clear(); + // Make sure all dataset controllers have correct meta data counts + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); + }, me); - Chart.plugins.notify('beforeDraw', [me, easingDecimal]); + Chart.layoutService.update(me, me.chart.width, me.chart.height); - // Draw all the scales - helpers.each(me.boxes, function(box) { - box.draw(me.chartArea); - }, me); - if (me.scale) { - me.scale.draw(); - } + // Apply changes to the datasets that require the scales to have been calculated i.e BorderColor changes + Chart.plugins.notify('afterScaleUpdate', [me]); - Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]); + // Can only reset the new controllers after the scales have been updated + helpers.each(newControllers, function(controller) { + controller.reset(); + }); - // 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(ease); - } - }, me, true); + me.updateDatasets(); - Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]); + // Do this before render so that any plugins that need final scale updates can use it + Chart.plugins.notify('afterUpdate', [me]); - // Finally draw the tooltip - me.tooltip.transition(easingDecimal).draw(); + if (me._bufferedRender) { + me._bufferedRequest = { + lazy: lazy, + duration: animationDuration + }; + } else { + me.render(animationDuration, lazy); + } + }, - Chart.plugins.notify('afterDraw', [me, easingDecimal]); - }, + /** + * @method beforeDatasetsUpdate + * @description Called before all datasets are updated. If a plugin returns false, + * the datasets update will be cancelled until another chart update is triggered. + * @param {Object} instance the chart instance being updated. + * @returns {Boolean} false to cancel the datasets update. + * @memberof Chart.PluginBase + * @since version 2.1.5 + * @instance + */ - // 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) { - var me = this; - var eventPosition = helpers.getRelativePosition(e, me.chart); - var elementsArray = []; + /** + * @method afterDatasetsUpdate + * @description Called after all datasets have been updated. Note that this + * extension will not be called if the datasets update has been cancelled. + * @param {Object} instance the chart instance being updated. + * @memberof Chart.PluginBase + * @since version 2.1.5 + * @instance + */ - helpers.each(me.data.datasets, function(dataset, datasetIndex) { - if (me.isDatasetVisible(datasetIndex)) { - var meta = me.getDatasetMeta(datasetIndex); - helpers.each(meta.data, function(element) { - if (element.inRange(eventPosition.x, eventPosition.y)) { - elementsArray.push(element); - return elementsArray; - } - }); - } - }); + /** + * Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate + * extension, in which case no datasets will be updated and the afterDatasetsUpdate + * notification will be skipped. + * @protected + * @instance + */ + updateDatasets: function() { + var me = this; + var i, ilen; - return elementsArray.slice(0, 1); - }, + if (Chart.plugins.notify('beforeDatasetsUpdate', [me])) { + for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.getDatasetMeta(i).controller.update(); + } - getElementsAtEvent: function(e) { - var me = this; - var eventPosition = helpers.getRelativePosition(e, me.chart); - var elementsArray = []; + Chart.plugins.notify('afterDatasetsUpdate', [me]); + } + }, - var found = (function() { - if (me.data.datasets) { - for (var i = 0; i < me.data.datasets.length; i++) { - var meta = me.getDatasetMeta(i); - if (me.isDatasetVisible(i)) { - for (var j = 0; j < meta.data.length; j++) { - if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) { - return meta.data[j]; - } - } - } - } - } - }).call(me); + render: function(duration, lazy) { + var me = this; + Chart.plugins.notify('beforeRender', [me]); - if (!found) { - return elementsArray; - } + var animationOptions = me.options.animation; + 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; - helpers.each(me.data.datasets, function(dataset, datasetIndex) { - if (me.isDatasetVisible(datasetIndex)) { - var meta = me.getDatasetMeta(datasetIndex), - element = meta.data[found._index]; - if(element && !element._view.skip){ - elementsArray.push(element); - } - } - }, me); + // render function + animation.render = function(chartInstance, animationObject) { + var easingFunction = helpers.easingEffects[animationObject.easing]; + var stepDecimal = animationObject.currentStep / animationObject.numSteps; + var easeDecimal = easingFunction(stepDecimal); - return elementsArray; - }, + chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); + }; - getElementsAtXAxis: function(e) { - var me = this; - var eventPosition = helpers.getRelativePosition(e, me.chart); - var elementsArray = []; + // user events + animation.onAnimationProgress = animationOptions.onProgress; + animation.onAnimationComplete = animationOptions.onComplete; - var found = (function() { - if (me.data.datasets) { - for (var i = 0; i < me.data.datasets.length; i++) { - var meta = me.getDatasetMeta(i); - if (me.isDatasetVisible(i)) { - for (var j = 0; j < meta.data.length; j++) { - if (meta.data[j].inLabelRange(eventPosition.x, eventPosition.y)) { - return meta.data[j]; - } - } - } - } - } - }).call(me); + Chart.animationService.addAnimation(me, animation, duration, lazy); + } else { + me.draw(); + if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) { + animationOptions.onComplete.call(me); + } + } + return me; + }, - if (!found) { - return elementsArray; - } + draw: function(ease) { + var me = this; + var easingDecimal = ease || 1; + me.clear(); - helpers.each(me.data.datasets, function(dataset, datasetIndex) { - if (me.isDatasetVisible(datasetIndex)) { - var meta = me.getDatasetMeta(datasetIndex); - var index = helpers.findIndex(meta.data, function (it) { - return found._model.x === it._model.x; - }); - if(index !== -1 && !meta.data[index]._view.skip) { - elementsArray.push(meta.data[index]); - } - } - }, me); + Chart.plugins.notify('beforeDraw', [me, easingDecimal]); - return elementsArray; - }, + // Draw all the scales + helpers.each(me.boxes, function(box) { + box.draw(me.chartArea); + }, me); + if (me.scale) { + me.scale.draw(); + } - getElementsAtEventForMode: function(e, mode) { - var me = this; - switch (mode) { - case 'single': - return me.getElementAtEvent(e); - case 'label': - return me.getElementsAtEvent(e); - case 'dataset': - return me.getDatasetAtEvent(e); - case 'x-axis': - return me.getElementsAtXAxis(e); - default: - return e; - } - }, + Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]); - getDatasetAtEvent: function(e) { - var elementsArray = this.getElementAtEvent(e); + // 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(ease); + } + }, me, true); - if (elementsArray.length > 0) { - elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data; - } + Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]); - return elementsArray; - }, + // Finally draw the tooltip + me.tooltip.transition(easingDecimal).draw(); - getDatasetMeta: function(datasetIndex) { - var me = this; - var dataset = me.data.datasets[datasetIndex]; - if (!dataset._meta) { - dataset._meta = {}; - } + Chart.plugins.notify('afterDraw', [me, easingDecimal]); + }, - 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 - }; - } + // 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); + }, - return meta; - }, + getElementsAtEvent: function(e) { + return Chart.Interaction.modes.label(this, e, {intersect: true}); + }, - getVisibleDatasetCount: function() { - var count = 0; - for (var i = 0, ilen = this.data.datasets.length; i<ilen; ++i) { - if (this.isDatasetVisible(i)) { - count++; - } - } - return count; - }, + getElementsAtXAxis: function(e) { + return Chart.Interaction.modes['x-axis'](this, e, {intersect: true}); + }, - isDatasetVisible: function(datasetIndex) { - var meta = this.getDatasetMeta(datasetIndex); + getElementsAtEventForMode: function(e, mode, options) { + var method = Chart.Interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options); + } - // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, - // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. - return typeof meta.hidden === 'boolean'? !meta.hidden : !this.data.datasets[datasetIndex].hidden; - }, + return []; + }, - generateLegend: function() { - return this.options.legendCallback(this); - }, + getDatasetAtEvent: function(e) { + return Chart.Interaction.modes.dataset(this, e); + }, - destroy: function() { - var me = this; - me.stop(); - me.clear(); - helpers.unbindEvents(me, me.events); - helpers.removeResizeListener(me.chart.canvas.parentNode); + getDatasetMeta: function(datasetIndex) { + var me = this; + var dataset = me.data.datasets[datasetIndex]; + if (!dataset._meta) { + dataset._meta = {}; + } - // Reset canvas height/width attributes - var canvas = me.chart.canvas; - canvas.width = me.chart.width; - canvas.height = me.chart.height; + 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 + }; + } - // if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here - if (me.chart.originalDevicePixelRatio !== undefined) { - me.chart.ctx.scale(1 / me.chart.originalDevicePixelRatio, 1 / me.chart.originalDevicePixelRatio); - } + return meta; + }, - // Reset to the old style since it may have been changed by the device pixel ratio changes - canvas.style.width = me.chart.originalCanvasStyleWidth; - canvas.style.height = me.chart.originalCanvasStyleHeight; + getVisibleDatasetCount: function() { + var count = 0; + for (var i = 0, ilen = this.data.datasets.length; i<ilen; ++i) { + if (this.isDatasetVisible(i)) { + count++; + } + } + return count; + }, - Chart.plugins.notify('destroy', [me]); + isDatasetVisible: function(datasetIndex) { + var meta = this.getDatasetMeta(datasetIndex); - delete Chart.instances[me.id]; - }, + // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, + // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. + return typeof meta.hidden === 'boolean'? !meta.hidden : !this.data.datasets[datasetIndex].hidden; + }, - toBase64Image: function() { - return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); - }, + generateLegend: function() { + return this.options.legendCallback(this); + }, - initToolTip: function() { - var me = this; - me.tooltip = new Chart.Tooltip({ - _chart: me.chart, - _chartInstance: me, - _data: me.data, - _options: me.options.tooltips - }, me); - }, + destroy: function() { + var me = this; + var canvas = me.chart.canvas; + var meta, i, ilen; - bindEvents: function() { - var me = this; - helpers.bindEvents(me, me.options.events, function(evt) { - me.eventHandler(evt); - }); - }, + me.stop(); - updateHoverStyle: function(elements, mode, enabled) { - var method = enabled? 'setHoverStyle' : 'removeHoverStyle'; - var element, i, ilen; + // dataset controllers need to cleanup associated data + for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + meta = me.getDatasetMeta(i); + if (meta.controller) { + meta.controller.destroy(); + meta.controller = null; + } + } - switch (mode) { - case 'single': - elements = [ elements[0] ]; - break; - case 'label': - case 'dataset': - case 'x-axis': - // elements = elements; - break; - default: - // unsupported mode - return; - } + if (canvas) { + helpers.unbindEvents(me, me.events); + helpers.removeResizeListener(canvas.parentNode); + helpers.clear(me.chart); + releaseCanvas(canvas); + me.chart.canvas = null; + me.chart.ctx = null; + } - for (i=0, ilen=elements.length; i<ilen; ++i) { - element = elements[i]; - if (element) { - this.getDatasetMeta(element._datasetIndex).controller[method](element); - } - } - }, + Chart.plugins.notify('destroy', [me]); - eventHandler: function eventHandler(e) { - var me = this; - var tooltip = me.tooltip; - var options = me.options || {}; - var hoverOptions = options.hover; - var tooltipsOptions = options.tooltips; + delete Chart.instances[me.id]; + }, - me.lastActive = me.lastActive || []; - me.lastTooltipActive = me.lastTooltipActive || []; + toBase64Image: function() { + return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); + }, - // Find Active Elements for hover and tooltips - if (e.type === 'mouseout') { - me.active = []; - me.tooltipActive = []; - } else { - me.active = me.getElementsAtEventForMode(e, hoverOptions.mode); - me.tooltipActive = me.getElementsAtEventForMode(e, tooltipsOptions.mode); - } + initToolTip: function() { + var me = this; + me.tooltip = new Chart.Tooltip({ + _chart: me.chart, + _chartInstance: me, + _data: me.data, + _options: me.options.tooltips + }, me); + me.tooltip.initialize(); + }, - // On Hover hook - if (hoverOptions.onHover) { - hoverOptions.onHover.call(me, me.active); - } + bindEvents: function() { + var me = this; + helpers.bindEvents(me, me.options.events, function(evt) { + me.eventHandler(evt); + }); + }, - if (e.type === 'mouseup' || e.type === 'click') { - if (options.onClick) { - options.onClick.call(me, e, me.active); - } - if (me.legend && me.legend.handleEvent) { - me.legend.handleEvent(e); - } - } + updateHoverStyle: function(elements, mode, enabled) { + var method = enabled? 'setHoverStyle' : 'removeHoverStyle'; + var element, i, ilen; - // Remove styling for last active (even if it may still be active) - if (me.lastActive.length) { - me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); - } + for (i=0, ilen=elements.length; i<ilen; ++i) { + element = elements[i]; + if (element) { + this.getDatasetMeta(element._datasetIndex).controller[method](element); + } + } + }, - // Built in hover styling - if (me.active.length && hoverOptions.mode) { - me.updateHoverStyle(me.active, hoverOptions.mode, true); - } + eventHandler: function(e) { + var me = this; + var legend = me.legend; + var tooltip = me.tooltip; + var hoverOptions = me.options.hover; - // Built in Tooltips - if (tooltipsOptions.enabled || tooltipsOptions.custom) { - tooltip.initialize(); - tooltip._active = me.tooltipActive; - tooltip.update(true); - } + // Buffer any update calls so that renders do not occur + me._bufferedRender = true; + me._bufferedRequest = null; - // Hover animations - tooltip.pivot(); + var changed = me.handleEvent(e); + changed |= legend && legend.handleEvent(e); + changed |= tooltip && tooltip.handleEvent(e); - if (!me.animating) { - // If entering, leaving, or changing elements, animate the change via pivot - if (!helpers.arrayEquals(me.active, me.lastActive) || - !helpers.arrayEquals(me.tooltipActive, me.lastTooltipActive)) { + var bufferedRequest = me._bufferedRequest; + if (bufferedRequest) { + // If we have an update that was triggered, we need to do a normal render + me.render(bufferedRequest.duration, bufferedRequest.lazy); + } else if (changed && !me.animating) { + // If entering, leaving, or changing elements, animate the change via pivot + me.stop(); - me.stop(); + // We only need to render at this point. Updating will cause scales to be + // recomputed generating flicker & using more memory than necessary. + me.render(hoverOptions.animationDuration, true); + } - if (tooltipsOptions.enabled || tooltipsOptions.custom) { - tooltip.update(true); - } + me._bufferedRender = false; + me._bufferedRequest = null; - // We only need to render at this point. Updating will cause scales to be - // recomputed generating flicker & using more memory than necessary. - me.render(hoverOptions.animationDuration, true); - } - } + return me; + }, - // Remember Last Actives - me.lastActive = me.active; - me.lastTooltipActive = me.tooltipActive; - return me; - } - }); -}; + /** + * Handle an event + * @private + * param e {Event} the event to handle + * @return {Boolean} true if the chart needs to re-render + */ + handleEvent: function(e) { + var me = this; + var options = me.options || {}; + var hoverOptions = options.hover; + var changed = false; + me.lastActive = me.lastActive || []; + + // Find Active Elements for hover and tooltips + if (e.type === 'mouseout') { + me.active = []; + } else { + me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); + } + + // On Hover hook + if (hoverOptions.onHover) { + hoverOptions.onHover.call(me, me.active); + } + + if (e.type === 'mouseup' || e.type === 'click') { + if (options.onClick) { + options.onClick.call(me, e, me.active); + } + } + + // Remove styling for last active (even if it may still be active) + if (me.lastActive.length) { + me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); + } + + // Built in hover styling + if (me.active.length && hoverOptions.mode) { + me.updateHoverStyle(me.active, hoverOptions.mode, true); + } + + changed = !helpers.arrayEquals(me.active, me.lastActive); + + // Remember Last Actives + me.lastActive = me.active; + + return changed; + } + }); + }; + },{}],24:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - var helpers = Chart.helpers; - var noop = helpers.noop; + var helpers = Chart.helpers; - // Base class for all dataset controllers (line, bar, etc) - Chart.DatasetController = function(chart, datasetIndex) { - this.initialize.call(this, chart, datasetIndex); - }; + var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; - helpers.extend(Chart.DatasetController.prototype, { + /** + * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', + * 'unshift') and notify the listener AFTER the array has been altered. Listeners are + * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. + */ + function listenArrayEvents(array, listener) { + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } - /** - * Element type used to generate a meta dataset (e.g. Chart.element.Line). - * @type {Chart.core.element} - */ - datasetElementType: null, + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); - /** - * Element type used to generate a meta data (e.g. Chart.element.Point). - * @type {Chart.core.element} - */ - dataElementType: null, + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; - initialize: function(chart, datasetIndex) { - var me = this; - me.chart = chart; - me.index = datasetIndex; - me.linkScales(); - me.addElements(); - }, + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); - updateIndex: function(datasetIndex) { - this.index = datasetIndex; - }, + helpers.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); - linkScales: function() { - var me = this; - var meta = me.getMeta(); - var dataset = me.getDataset(); + return res; + } + }); + }); + } - if (meta.xAxisID === null) { - meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; - } - if (meta.yAxisID === null) { - meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; - } - }, + /** + * Removes the given array event listener and cleanup extra attached properties (such as + * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + */ + function unlistenArrayEvents(array, listener) { + var stub = array._chartjs; + if (!stub) { + return; + } - getDataset: function() { - return this.chart.data.datasets[this.index]; - }, + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } - getMeta: function() { - return this.chart.getDatasetMeta(this.index); - }, + if (listeners.length > 0) { + return; + } - getScaleForId: function(scaleID) { - return this.chart.scales[scaleID]; - }, + arrayEvents.forEach(function(key) { + delete array[key]; + }); - reset: function() { - this.update(true); - }, + delete array._chartjs; + } - createMetaDataset: function() { - var me = this; - var type = me.datasetElementType; - return type && new type({ - _chart: me.chart.chart, - _datasetIndex: me.index - }); - }, + // Base class for all dataset controllers (line, bar, etc) + Chart.DatasetController = function(chart, datasetIndex) { + this.initialize(chart, datasetIndex); + }; - createMetaData: function(index) { - var me = this; - var type = me.dataElementType; - return type && new type({ - _chart: me.chart.chart, - _datasetIndex: me.index, - _index: index - }); - }, + helpers.extend(Chart.DatasetController.prototype, { - addElements: function() { - var me = this; - var meta = me.getMeta(); - var data = me.getDataset().data || []; - var metaData = meta.data; - var i, ilen; + /** + * Element type used to generate a meta dataset (e.g. Chart.element.Line). + * @type {Chart.core.element} + */ + datasetElementType: null, - for (i=0, ilen=data.length; i<ilen; ++i) { - metaData[i] = metaData[i] || me.createMetaData(meta, i); - } + /** + * Element type used to generate a meta data (e.g. Chart.element.Point). + * @type {Chart.core.element} + */ + dataElementType: null, - meta.dataset = meta.dataset || me.createMetaDataset(); - }, + initialize: function(chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + }, - addElementAndReset: function(index) { - var me = this; - var element = me.createMetaData(index); - me.getMeta().data.splice(index, 0, element); - me.updateElement(element, index, true); - }, + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, - buildOrUpdateElements: function() { - // Handle the number of data points changing - var meta = this.getMeta(), - md = meta.data, - numData = this.getDataset().data.length, - numMetaData = md.length; + linkScales: function() { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); - // Make sure that we handle number of datapoints changing - if (numData < numMetaData) { - // Remove excess bars for data points that have been removed - md.splice(numData, numMetaData - numData); - } else if (numData > numMetaData) { - // Add new elements - for (var index = numMetaData; index < numData; ++index) { - this.addElementAndReset(index); - } - } - }, + if (meta.xAxisID === null) { + meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; + } + if (meta.yAxisID === null) { + meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; + } + }, - update: noop, + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, - draw: function(ease) { - var easingDecimal = ease || 1; - helpers.each(this.getMeta().data, function(element) { - element.transition(easingDecimal).draw(); - }); - }, + getMeta: function() { + return this.chart.getDatasetMeta(this.index); + }, - removeHoverStyle: function(element, elementOpts) { - var dataset = this.chart.data.datasets[element._datasetIndex], - index = element._index, - custom = element.custom || {}, - valueOrDefault = helpers.getValueAtIndexOrDefault, - model = element._model; + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, - model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); - model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); - model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); - }, + reset: function() { + this.update(true); + }, - setHoverStyle: function(element) { - var dataset = this.chart.data.datasets[element._datasetIndex], - index = element._index, - custom = element.custom || {}, - valueOrDefault = helpers.getValueAtIndexOrDefault, - getHoverColor = helpers.getHoverColor, - model = element._model; + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, - model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); - model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); - model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); - } + createMetaDataset: function() { + var me = this; + var type = me.datasetElementType; + return type && new type({ + _chart: me.chart.chart, + _datasetIndex: me.index + }); + }, - }); + createMetaData: function(index) { + var me = this; + var type = me.dataElementType; + return type && new type({ + _chart: me.chart.chart, + _datasetIndex: me.index, + _index: index + }); + }, + addElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data || []; + var metaData = meta.data; + var i, ilen; - Chart.DatasetController.extend = helpers.inherits; -}; -},{}],25:[function(require,module,exports){ -"use strict"; + for (i=0, ilen=data.length; i<ilen; ++i) { + metaData[i] = metaData[i] || me.createMetaData(i); + } -module.exports = function(Chart) { + meta.dataset = meta.dataset || me.createMetaDataset(); + }, - var helpers = Chart.helpers; + addElementAndReset: function(index) { + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); + }, - Chart.elements = {}; + buildOrUpdateElements: function() { + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); - Chart.Element = function(configuration) { - helpers.extend(this, configuration); - this.initialize.apply(this, arguments); - }; + // In order to correctly handle data addition/deletion animation (an thus simulate + // real-time charts), we need to monitor these data modifications and synchronize + // the internal meta data accordingly. + if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); + } - helpers.extend(Chart.Element.prototype, { + listenArrayEvents(data, me); + me._data = data; + } - initialize: function() { - this.hidden = false; - }, + // Re-sync meta data in case the user replaced the data array or if we missed + // any updates and so make sure that we handle number of datapoints changing. + me.resyncElements(); + }, - pivot: function() { - var me = this; - if (!me._view) { - me._view = helpers.clone(me._model); - } - me._start = helpers.clone(me._view); - return me; - }, + update: helpers.noop, - transition: function(ease) { - var me = this; + draw: function(ease) { + var easingDecimal = ease || 1; + var i, len; + var metaData = this.getMeta().data; + for (i = 0, len = metaData.length; i < len; ++i) { + metaData[i].transition(easingDecimal).draw(); + } + }, - if (!me._view) { - me._view = helpers.clone(me._model); - } + removeHoverStyle: function(element, elementOpts) { + var dataset = this.chart.data.datasets[element._datasetIndex], + index = element._index, + custom = element.custom || {}, + valueOrDefault = helpers.getValueAtIndexOrDefault, + model = element._model; - // No animation -> No Transition - if (ease === 1) { - me._view = me._model; - me._start = null; - return me; - } + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); + }, - if (!me._start) { - me.pivot(); - } + setHoverStyle: function(element) { + var dataset = this.chart.data.datasets[element._datasetIndex], + index = element._index, + custom = element.custom || {}, + valueOrDefault = helpers.getValueAtIndexOrDefault, + getHoverColor = helpers.getHoverColor, + model = element._model; - helpers.each(me._model, function(value, key) { + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); + }, - if (key[0] === '_') { - // Only non-underscored properties - } + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; - // Init if doesn't exist - else if (!me._view.hasOwnProperty(key)) { - if (typeof value === 'number' && !isNaN(me._view[key])) { - me._view[key] = value * ease; - } else { - me._view[key] = value; - } - } + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, - // No unnecessary computations - else if (value === me._view[key]) { - // It's the same! Woohoo! - } + /** + * @private + */ + insertElements: function(start, count) { + for (var i=0; i<count; ++i) { + this.addElementAndReset(start + i); + } + }, - // Color transitions if possible - else if (typeof value === 'string') { - try { - var color = helpers.color(me._model[key]).mix(helpers.color(me._start[key]), ease); - me._view[key] = color.rgbString(); - } catch (err) { - me._view[key] = value; - } - } - // Number transitions - else if (typeof value === 'number') { - var startVal = me._start[key] !== undefined && isNaN(me._start[key]) === false ? me._start[key] : 0; - me._view[key] = ((me._model[key] - startVal) * ease) + startVal; - } - // Everything else - else { - me._view[key] = value; - } - }, me); + /** + * @private + */ + onDataPush: function() { + this.insertElements(this.getDataset().data.length-1, arguments.length); + }, - return me; - }, + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, - tooltipPosition: function() { - return { - x: this._model.x, - y: this._model.y - }; - }, + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, - hasValue: function() { - return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); - } - }); + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, - Chart.Element.extend = helpers.inherits; + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); + } + }); -}; + Chart.DatasetController.extend = helpers.inherits; + }; +},{}],25:[function(require,module,exports){ + 'use strict'; + + module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.elements = {}; + + Chart.Element = function(configuration) { + helpers.extend(this, configuration); + this.initialize.apply(this, arguments); + }; + + helpers.extend(Chart.Element.prototype, { + + initialize: function() { + this.hidden = false; + }, + + pivot: function() { + var me = this; + if (!me._view) { + me._view = helpers.clone(me._model); + } + me._start = helpers.clone(me._view); + return me; + }, + + transition: function(ease) { + var me = this; + + if (!me._view) { + me._view = helpers.clone(me._model); + } + + // No animation -> No Transition + if (ease === 1) { + me._view = me._model; + me._start = null; + return me; + } + + if (!me._start) { + me.pivot(); + } + + helpers.each(me._model, function(value, key) { + + if (key[0] === '_') { + // Only non-underscored properties + // Init if doesn't exist + } else if (!me._view.hasOwnProperty(key)) { + if (typeof value === 'number' && !isNaN(me._view[key])) { + me._view[key] = value * ease; + } else { + me._view[key] = value; + } + // No unnecessary computations + } else if (value === me._view[key]) { + // It's the same! Woohoo! + // Color transitions if possible + } else if (typeof value === 'string') { + try { + var color = helpers.color(me._model[key]).mix(helpers.color(me._start[key]), ease); + me._view[key] = color.rgbString(); + } catch (err) { + me._view[key] = value; + } + // Number transitions + } else if (typeof value === 'number') { + var startVal = me._start[key] !== undefined && isNaN(me._start[key]) === false ? me._start[key] : 0; + me._view[key] = ((me._model[key] - startVal) * ease) + startVal; + // Everything else + } else { + me._view[key] = value; + } + }, me); + + return me; + }, + + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + + hasValue: function() { + return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); + } + }); + + Chart.Element.extend = helpers.inherits; + + }; + },{}],26:[function(require,module,exports){ -/*global window: false */ -/*global document: false */ -"use strict"; + /* global window: false */ + /* global document: false */ + 'use strict'; -var color = require(3); + var color = require(3); -module.exports = function(Chart) { - //Global Chart helpers object for utility methods and classes - var helpers = Chart.helpers = {}; + module.exports = function(Chart) { + // Global Chart helpers object for utility methods and classes + var helpers = Chart.helpers = {}; - //-- Basic js utility methods - helpers.each = function(loopable, callback, self, reverse) { - // Check to see if null or undefined firstly. - var i, len; - if (helpers.isArray(loopable)) { - len = loopable.length; - if (reverse) { - for (i = len - 1; i >= 0; i--) { - callback.call(self, loopable[i], i); - } - } else { - for (i = 0; i < len; i++) { - callback.call(self, loopable[i], i); - } - } - } else if (typeof loopable === 'object') { - var keys = Object.keys(loopable); - len = keys.length; - for (i = 0; i < len; i++) { - callback.call(self, loopable[keys[i]], keys[i]); - } - } - }; - helpers.clone = function(obj) { - var objClone = {}; - helpers.each(obj, function(value, key) { - if (helpers.isArray(value)) { - objClone[key] = value.slice(0); - } else if (typeof value === 'object' && value !== null) { - objClone[key] = helpers.clone(value); - } else { - objClone[key] = value; - } - }); - return objClone; - }; - helpers.extend = function(base) { - var setFn = function(value, key) { base[key] = value; }; - for (var i = 1, ilen = arguments.length; i < ilen; i++) { - helpers.each(arguments[i], setFn); - } - return base; - }; - // Need a special merge function to chart configs since they are now grouped - helpers.configMerge = function(_base) { - var base = helpers.clone(_base); - helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { - helpers.each(extension, function(value, key) { - if (key === 'scales') { - // Scale config merging is complex. Add out own function here for that - base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value); + // -- Basic js utility methods + helpers.each = function(loopable, callback, self, reverse) { + // Check to see if null or undefined firstly. + var i, len; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + callback.call(self, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + callback.call(self, loopable[i], i); + } + } + } else if (typeof loopable === 'object') { + var keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + callback.call(self, loopable[keys[i]], keys[i]); + } + } + }; + helpers.clone = function(obj) { + var objClone = {}; + helpers.each(obj, function(value, key) { + if (helpers.isArray(value)) { + objClone[key] = value.slice(0); + } else if (typeof value === 'object' && value !== null) { + objClone[key] = helpers.clone(value); + } else { + objClone[key] = value; + } + }); + return objClone; + }; + helpers.extend = function(base) { + var setFn = function(value, key) { + base[key] = value; + }; + for (var i = 1, ilen = arguments.length; i < ilen; i++) { + helpers.each(arguments[i], setFn); + } + return base; + }; + // Need a special merge function to chart configs since they are now grouped + helpers.configMerge = function(_base) { + var base = helpers.clone(_base); + helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { + helpers.each(extension, function(value, key) { + var baseHasProperty = base.hasOwnProperty(key); + var baseVal = baseHasProperty ? base[key] : {}; - } else if (key === 'scale') { - // Used in polar area & radar charts since there is only one scale - base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value); - } else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) { - // In this case we have an array of objects replacing another array. Rather than doing a strict replace, - // merge. This allows easy scale option merging - var baseArray = base[key]; + if (key === 'scales') { + // Scale config merging is complex. Add our own function here for that + base[key] = helpers.scaleMerge(baseVal, value); + } else if (key === 'scale') { + // Used in polar area & radar charts since there is only one scale + base[key] = helpers.configMerge(baseVal, Chart.scaleService.getScaleDefaults(value.type), value); + } else if (baseHasProperty + && typeof baseVal === 'object' + && !helpers.isArray(baseVal) + && baseVal !== null + && typeof value === 'object' + && !helpers.isArray(value)) { + // If we are overwriting an object with an object, do a merge of the properties. + base[key] = helpers.configMerge(baseVal, value); + } else { + // can just overwrite the value in this case + base[key] = value; + } + }); + }); - helpers.each(value, function(valueObj, index) { + return base; + }; + helpers.scaleMerge = function(_base, extension) { + var base = helpers.clone(_base); - if (index < baseArray.length) { - if (typeof baseArray[index] === 'object' && baseArray[index] !== null && typeof valueObj === 'object' && valueObj !== null) { - // Two objects are coming together. Do a merge of them. - baseArray[index] = helpers.configMerge(baseArray[index], valueObj); - } else { - // Just overwrite in this case since there is nothing to merge - baseArray[index] = valueObj; - } - } else { - baseArray.push(valueObj); // nothing to merge - } - }); + helpers.each(extension, function(value, key) { + if (key === 'xAxes' || key === 'yAxes') { + // These properties are arrays of items + if (base.hasOwnProperty(key)) { + helpers.each(value, function(valueObj, index) { + var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); + var axisDefaults = Chart.scaleService.getScaleDefaults(axisType); + if (index >= base[key].length || !base[key][index].type) { + base[key].push(helpers.configMerge(axisDefaults, valueObj)); + } else if (valueObj.type && valueObj.type !== base[key][index].type) { + // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults + base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj); + } else { + // Type is the same + base[key][index] = helpers.configMerge(base[key][index], valueObj); + } + }); + } else { + base[key] = []; + helpers.each(value, function(valueObj) { + var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); + base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj)); + }); + } + } else if (base.hasOwnProperty(key) && typeof base[key] === 'object' && base[key] !== null && typeof value === 'object') { + // If we are overwriting an object with an object, do a merge of the properties. + base[key] = helpers.configMerge(base[key], value); - } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") { - // If we are overwriting an object with an object, do a merge of the properties. - base[key] = helpers.configMerge(base[key], value); + } else { + // can just overwrite the value in this case + base[key] = value; + } + }); - } else { - // can just overwrite the value in this case - base[key] = value; - } - }); - }); + return base; + }; + helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { + if (value === undefined || value === null) { + return defaultValue; + } - return base; - }; - helpers.scaleMerge = function(_base, extension) { - var base = helpers.clone(_base); + if (helpers.isArray(value)) { + return index < value.length ? value[index] : defaultValue; + } - helpers.each(extension, function(value, key) { - if (key === 'xAxes' || key === 'yAxes') { - // These properties are arrays of items - if (base.hasOwnProperty(key)) { - helpers.each(value, function(valueObj, index) { - var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); - var axisDefaults = Chart.scaleService.getScaleDefaults(axisType); - if (index >= base[key].length || !base[key][index].type) { - base[key].push(helpers.configMerge(axisDefaults, valueObj)); - } else if (valueObj.type && valueObj.type !== base[key][index].type) { - // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults - base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj); - } else { - // Type is the same - base[key][index] = helpers.configMerge(base[key][index], valueObj); - } - }); - } else { - base[key] = []; - helpers.each(value, function(valueObj) { - var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); - base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj)); - }); - } - } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") { - // If we are overwriting an object with an object, do a merge of the properties. - base[key] = helpers.configMerge(base[key], value); + return value; + }; + helpers.getValueOrDefault = function(value, defaultValue) { + return value === undefined ? defaultValue : value; + }; + helpers.indexOf = Array.prototype.indexOf? + function(array, item) { + return array.indexOf(item); + }: + function(array, item) { + for (var i = 0, ilen = array.length; i < ilen; ++i) { + if (array[i] === item) { + return i; + } + } + return -1; + }; + helpers.where = function(collection, filterCallback) { + if (helpers.isArray(collection) && Array.prototype.filter) { + return collection.filter(filterCallback); + } + var filtered = []; - } else { - // can just overwrite the value in this case - base[key] = value; - } - }); + helpers.each(collection, function(item) { + if (filterCallback(item)) { + filtered.push(item); + } + }); - return base; - }; - helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { - if (value === undefined || value === null) { - return defaultValue; - } + return filtered; + }; + helpers.findIndex = Array.prototype.findIndex? + function(array, callback, scope) { + return array.findIndex(callback, scope); + } : + function(array, callback, scope) { + scope = scope === undefined? array : scope; + for (var i = 0, ilen = array.length; i < ilen; ++i) { + if (callback.call(scope, array[i], i, array)) { + return i; + } + } + return -1; + }; + helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to start of the array + if (startIndex === undefined || startIndex === null) { + startIndex = -1; + } + for (var i = startIndex + 1; i < arrayToSearch.length; i++) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to end of the array + if (startIndex === undefined || startIndex === null) { + startIndex = arrayToSearch.length; + } + for (var i = startIndex - 1; i >= 0; i--) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + helpers.inherits = function(extensions) { + // Basic javascript inheritance based on the model created in Backbone.js + var me = this; + var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; - if (helpers.isArray(value)) { - return index < value.length ? value[index] : defaultValue; - } + var Surrogate = function() { + this.constructor = ChartElement; + }; + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); - return value; - }; - helpers.getValueOrDefault = function(value, defaultValue) { - return value === undefined ? defaultValue : value; - }; - helpers.indexOf = Array.prototype.indexOf? - function(array, item) { return array.indexOf(item); } : - function(array, item) { - for (var i = 0, ilen = array.length; i < ilen; ++i) { - if (array[i] === item) { - return i; - } - } - return -1; - }; - helpers.where = function(collection, filterCallback) { - if (helpers.isArray(collection) && Array.prototype.filter) { - return collection.filter(filterCallback); - } else { - var filtered = []; + ChartElement.extend = helpers.inherits; - helpers.each(collection, function(item) { - if (filterCallback(item)) { - filtered.push(item); - } - }); + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } - return filtered; - } - }; - helpers.findIndex = Array.prototype.findIndex? - function(array, callback, scope) { return array.findIndex(callback, scope); } : - function(array, callback, scope) { - scope = scope === undefined? array : scope; - for (var i = 0, ilen = array.length; i < ilen; ++i) { - if (callback.call(scope, array[i], i, array)) { - return i; - } - } - return -1; - }; - helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { - // Default to start of the array - if (startIndex === undefined || startIndex === null) { - startIndex = -1; - } - for (var i = startIndex + 1; i < arrayToSearch.length; i++) { - var currentItem = arrayToSearch[i]; - if (filterCallback(currentItem)) { - return currentItem; - } - } - }; - helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { - // Default to end of the array - if (startIndex === undefined || startIndex === null) { - startIndex = arrayToSearch.length; - } - for (var i = startIndex - 1; i >= 0; i--) { - var currentItem = arrayToSearch[i]; - if (filterCallback(currentItem)) { - return currentItem; - } - } - }; - 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); - }; + ChartElement.__super__ = me.prototype; - var Surrogate = function() { - this.constructor = ChartElement; - }; - Surrogate.prototype = parent.prototype; - ChartElement.prototype = new Surrogate(); + return ChartElement; + }; + helpers.noop = function() {}; + helpers.uid = (function() { + var id = 0; + return function() { + return id++; + }; + }()); + // -- Math methods + helpers.isNumber = function(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + helpers.almostEquals = function(x, y, epsilon) { + return Math.abs(x - y) < epsilon; + }; + helpers.max = function(array) { + return array.reduce(function(max, value) { + if (!isNaN(value)) { + return Math.max(max, value); + } + return max; + }, Number.NEGATIVE_INFINITY); + }; + helpers.min = function(array) { + return array.reduce(function(min, value) { + if (!isNaN(value)) { + return Math.min(min, value); + } + return min; + }, Number.POSITIVE_INFINITY); + }; + helpers.sign = Math.sign? + function(x) { + return Math.sign(x); + } : + function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + }; + helpers.log10 = Math.log10? + function(x) { + return Math.log10(x); + } : + function(x) { + return Math.log(x) / Math.LN10; + }; + helpers.toRadians = function(degrees) { + return degrees * (Math.PI / 180); + }; + helpers.toDegrees = function(radians) { + return radians * (180 / Math.PI); + }; + // Gets the angle from vertical upright to the point about a centre. + helpers.getAngleFromPoint = function(centrePoint, anglePoint) { + var distanceFromXCenter = anglePoint.x - centrePoint.x, + distanceFromYCenter = anglePoint.y - centrePoint.y, + radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); - ChartElement.extend = helpers.inherits; + var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); - if (extensions) { - helpers.extend(ChartElement.prototype, extensions); - } + if (angle < (-0.5 * Math.PI)) { + angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] + } - ChartElement.__super__ = parent.prototype; + return { + angle: angle, + distance: radialDistanceFromCenter + }; + }; + helpers.distanceBetweenPoints = function(pt1, pt2) { + return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); + }; + helpers.aliasPixel = function(pixelWidth) { + return (pixelWidth % 2 === 0) ? 0 : 0.5; + }; + 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 - return ChartElement; - }; - helpers.noop = function() {}; - helpers.uid = (function() { - var id = 0; - return function() { - return id++; - }; - })(); - //-- Math methods - helpers.isNumber = function(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - }; - helpers.almostEquals = function(x, y, epsilon) { - return Math.abs(x - y) < epsilon; - }; - helpers.max = function(array) { - return array.reduce(function(max, value) { - if (!isNaN(value)) { - return Math.max(max, value); - } else { - return max; - } - }, Number.NEGATIVE_INFINITY); - }; - helpers.min = function(array) { - return array.reduce(function(min, value) { - if (!isNaN(value)) { - return Math.min(min, value); - } else { - return min; - } - }, Number.POSITIVE_INFINITY); - }; - helpers.sign = Math.sign? - function(x) { return Math.sign(x); } : - function(x) { - x = +x; // convert to a number - if (x === 0 || isNaN(x)) { - return x; - } - return x > 0 ? 1 : -1; - }; - helpers.log10 = Math.log10? - function(x) { return Math.log10(x); } : - function(x) { - return Math.log(x) / Math.LN10; - }; - helpers.toRadians = function(degrees) { - return degrees * (Math.PI / 180); - }; - helpers.toDegrees = function(radians) { - return radians * (180 / Math.PI); - }; - // Gets the angle from vertical upright to the point about a centre. - helpers.getAngleFromPoint = function(centrePoint, anglePoint) { - var distanceFromXCenter = anglePoint.x - centrePoint.x, - distanceFromYCenter = anglePoint.y - centrePoint.y, - radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + // This function must also respect "skipped" points - var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); + var previous = firstPoint.skip ? middlePoint : firstPoint, + current = middlePoint, + next = afterPoint.skip ? middlePoint : afterPoint; - if (angle < (-0.5 * Math.PI)) { - angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] - } + var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); + var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); - return { - angle: angle, - distance: radialDistanceFromCenter - }; - }; - helpers.aliasPixel = function(pixelWidth) { - return (pixelWidth % 2 === 0) ? 0 : 0.5; - }; - 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 s01 = d01 / (d01 + d12); + var s12 = d12 / (d01 + d12); - // This function must also respect "skipped" points + // If all points are the same, s01 & s02 will be inf + s01 = isNaN(s01) ? 0 : s01; + s12 = isNaN(s12) ? 0 : s12; - var previous = firstPoint.skip ? middlePoint : firstPoint, - current = middlePoint, - next = afterPoint.skip ? middlePoint : afterPoint; + var fa = t * s01; // scaling factor for triangle Ta + var fb = t * s12; - var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); - var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); + return { + previous: { + x: current.x - fa * (next.x - previous.x), + y: current.y - fa * (next.y - previous.y) + }, + next: { + x: current.x + fb * (next.x - previous.x), + y: current.y + fb * (next.y - previous.y) + } + }; + }; + helpers.EPSILON = Number.EPSILON || 1e-14; + helpers.splineCurveMonotone = function(points) { + // This function calculates Bézier control points in a similar way than |splineCurve|, + // but preserves monotonicity of the provided data and ensures no local extremums are added + // between the dataset discrete points due to the interpolation. + // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation - var s01 = d01 / (d01 + d12); - var s12 = d12 / (d01 + d12); + var pointsWithTangents = (points || []).map(function(point) { + return { + model: point._model, + deltaK: 0, + mK: 0 + }; + }); - // If all points are the same, s01 & s02 will be inf - s01 = isNaN(s01) ? 0 : s01; - s12 = isNaN(s12) ? 0 : s12; + // Calculate slopes (deltaK) and initialize tangents (mK) + var pointsLen = pointsWithTangents.length; + var i, pointBefore, pointCurrent, pointAfter; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } - var fa = t * s01; // scaling factor for triangle Ta - var fb = t * s12; + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointAfter && !pointAfter.model.skip) { + pointCurrent.deltaK = (pointAfter.model.y - pointCurrent.model.y) / (pointAfter.model.x - pointCurrent.model.x); + } - return { - previous: { - x: current.x - fa * (next.x - previous.x), - y: current.y - fa * (next.y - previous.y) - }, - next: { - x: current.x + fb * (next.x - previous.x), - y: current.y + fb * (next.y - previous.y) - } - }; - }; - helpers.nextItem = function(collection, index, loop) { - if (loop) { - return index >= collection.length - 1 ? collection[0] : collection[index + 1]; - } + if (!pointBefore || pointBefore.model.skip) { + pointCurrent.mK = pointCurrent.deltaK; + } else if (!pointAfter || pointAfter.model.skip) { + pointCurrent.mK = pointBefore.deltaK; + } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { + pointCurrent.mK = 0; + } else { + pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; + } + } - return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; - }; - helpers.previousItem = function(collection, index, loop) { - if (loop) { - return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; - } - return index <= 0 ? collection[0] : collection[index - 1]; - }; - // Implementation of the nice number algorithm used in determining where axis labels will go - helpers.niceNum = function(range, round) { - var exponent = Math.floor(helpers.log10(range)); - var fraction = range / Math.pow(10, exponent); - var niceFraction; + // Adjust tangents to ensure monotonic properties + var alphaK, betaK, tauK, squaredMagnitude; + for (i = 0; i < pointsLen - 1; ++i) { + pointCurrent = pointsWithTangents[i]; + pointAfter = pointsWithTangents[i + 1]; + if (pointCurrent.model.skip || pointAfter.model.skip) { + continue; + } - if (round) { - if (fraction < 1.5) { - niceFraction = 1; - } else if (fraction < 3) { - niceFraction = 2; - } else if (fraction < 7) { - niceFraction = 5; - } else { - niceFraction = 10; - } - } else { - if (fraction <= 1.0) { - niceFraction = 1; - } else if (fraction <= 2) { - niceFraction = 2; - } else if (fraction <= 5) { - niceFraction = 5; - } else { - niceFraction = 10; - } - } + if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { + pointCurrent.mK = pointAfter.mK = 0; + continue; + } - return niceFraction * Math.pow(10, exponent); - }; - //Easing functions adapted from Robert Penner's easing equations - //http://www.robertpenner.com/easing/ - var 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/ - helpers.requestAnimFrame = (function() { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(callback, 1000 / 60); - }; - })(); - helpers.cancelAnimFrame = (function() { - return window.cancelAnimationFrame || - window.webkitCancelAnimationFrame || - window.mozCancelAnimationFrame || - window.oCancelAnimationFrame || - window.msCancelAnimationFrame || - function(callback) { - return window.clearTimeout(callback, 1000 / 60); - }; - })(); - //-- DOM methods - helpers.getRelativePosition = function(evt, chart) { - var mouseX, mouseY; - var e = evt.originalEvent || evt, - canvas = evt.currentTarget || evt.srcElement, - boundingRect = canvas.getBoundingClientRect(); + alphaK = pointCurrent.mK / pointCurrent.deltaK; + betaK = pointAfter.mK / pointCurrent.deltaK; + squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); + if (squaredMagnitude <= 9) { + continue; + } - var touches = e.touches; - if (touches && touches.length > 0) { - mouseX = touches[0].clientX; - mouseY = touches[0].clientY; + tauK = 3 / Math.sqrt(squaredMagnitude); + pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; + pointAfter.mK = betaK * tauK * pointCurrent.deltaK; + } - } else { - mouseX = e.clientX; - mouseY = e.clientY; - } + // Compute control points + var deltaX; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } - // Scale mouse coordinates into canvas coordinates - // by following the pattern laid out by 'jerryj' in the comments of - // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ - var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); - var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); - var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); - var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); - var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; - var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointBefore && !pointBefore.model.skip) { + deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; + pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; + pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; + } + if (pointAfter && !pointAfter.model.skip) { + deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; + pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; + pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; + } + } + }; + helpers.nextItem = function(collection, index, loop) { + if (loop) { + return index >= collection.length - 1 ? collection[0] : collection[index + 1]; + } + return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; + }; + helpers.previousItem = function(collection, index, loop) { + if (loop) { + return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; + } + return index <= 0 ? collection[0] : collection[index - 1]; + }; + // Implementation of the nice number algorithm used in determining where axis labels will go + helpers.niceNum = function(range, round) { + var exponent = Math.floor(helpers.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; - // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However - // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here - mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); - mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } - return { - x: mouseX, - y: mouseY - }; + return niceFraction * Math.pow(10, exponent); + }; + // Easing functions adapted from Robert Penner's easing equations + // http://www.robertpenner.com/easing/ + var 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); + } + 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/ + helpers.requestAnimFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, 1000 / 60); + }; + }()); + helpers.cancelAnimFrame = (function() { + return window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.mozCancelAnimationFrame || + window.oCancelAnimationFrame || + window.msCancelAnimationFrame || + function(callback) { + return window.clearTimeout(callback, 1000 / 60); + }; + }()); + // -- DOM methods + helpers.getRelativePosition = function(evt, chart) { + var mouseX, mouseY; + var e = evt.originalEvent || evt, + canvas = evt.currentTarget || evt.srcElement, + boundingRect = canvas.getBoundingClientRect(); - }; - 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; - } - }; - 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] = helpers.noop; - } - }; - helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) { - // Create the events object if it's not already present - var events = chartInstance.events = chartInstance.events || {}; + var touches = e.touches; + if (touches && touches.length > 0) { + mouseX = touches[0].clientX; + mouseY = touches[0].clientY; - helpers.each(arrayOfEvents, function(eventName) { - events[eventName] = function() { - handler.apply(chartInstance, arguments); - }; - helpers.addEvent(chartInstance.chart.canvas, eventName, events[eventName]); - }); - }; - helpers.unbindEvents = function(chartInstance, arrayOfEvents) { - var canvas = chartInstance.chart.canvas; - helpers.each(arrayOfEvents, function(handler, eventName) { - helpers.removeEvent(canvas, eventName, handler); - }); - }; + } else { + mouseX = e.clientX; + mouseY = e.clientY; + } - // Private helper function to convert max-width/max-height values that may be percentages into a number - function parseMaxStyle(styleValue, node, parentProperty) { - var valueInPixels; - if (typeof(styleValue) === 'string') { - valueInPixels = parseInt(styleValue, 10); + // Scale mouse coordinates into canvas coordinates + // by following the pattern laid out by 'jerryj' in the comments of + // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ + var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); + var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); + var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); + var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); + var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; + var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; - if (styleValue.indexOf('%') != -1) { - // percentage * size in dimension - valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; - } - } else { - valueInPixels = styleValue; - } + // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However + // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here + mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); + mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); - return valueInPixels; - } + return { + x: mouseX, + y: mouseY + }; - /** - * Returns if the given value contains an effective constraint. - * @private - */ - function isConstrainedValue(value) { - return value !== undefined && value !== null && value !== 'none'; - } + }; + 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; + } + }; + 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] = helpers.noop; + } + }; + helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) { + // Create the events object if it's not already present + var events = chartInstance.events = chartInstance.events || {}; - // Private helper to get a constraint dimension - // @param domNode : the node to check the constraint on - // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) - // @param percentageProperty : property of parent to use when calculating width as a percentage - // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser - function getConstraintDimension(domNode, maxStyle, percentageProperty) { - var view = document.defaultView; - var parentNode = domNode.parentNode; - var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; - var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; - var hasCNode = isConstrainedValue(constrainedNode); - var hasCContainer = isConstrainedValue(constrainedContainer); - var infinity = Number.POSITIVE_INFINITY; + helpers.each(arrayOfEvents, function(eventName) { + events[eventName] = function() { + handler.apply(chartInstance, arguments); + }; + helpers.addEvent(chartInstance.chart.canvas, eventName, events[eventName]); + }); + }; + helpers.unbindEvents = function(chartInstance, arrayOfEvents) { + var canvas = chartInstance.chart.canvas; + helpers.each(arrayOfEvents, function(handler, eventName) { + helpers.removeEvent(canvas, eventName, handler); + }); + }; - if (hasCNode || hasCContainer) { - return Math.min( - hasCNode? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, - hasCContainer? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); - } + // Private helper function to convert max-width/max-height values that may be percentages into a number + function parseMaxStyle(styleValue, node, parentProperty) { + var valueInPixels; + if (typeof(styleValue) === 'string') { + valueInPixels = parseInt(styleValue, 10); - return 'none'; - } - // returns Number or undefined if no constraint - helpers.getConstraintWidth = function(domNode) { - return getConstraintDimension(domNode, 'max-width', 'clientWidth'); - }; - // returns Number or undefined if no constraint - helpers.getConstraintHeight = function(domNode) { - return getConstraintDimension(domNode, 'max-height', 'clientHeight'); - }; - helpers.getMaximumWidth = function(domNode) { - var container = domNode.parentNode; - var padding = parseInt(helpers.getStyle(container, 'padding-left')) + parseInt(helpers.getStyle(container, 'padding-right')); - var w = container.clientWidth - padding; - var cw = helpers.getConstraintWidth(domNode); - return isNaN(cw)? w : Math.min(w, cw); - }; - helpers.getMaximumHeight = function(domNode) { - var container = domNode.parentNode; - var padding = parseInt(helpers.getStyle(container, 'padding-top')) + parseInt(helpers.getStyle(container, 'padding-bottom')); - var h = container.clientHeight - padding; - var ch = helpers.getConstraintHeight(domNode); - return isNaN(ch)? h : Math.min(h, ch); - }; - helpers.getStyle = function(el, property) { - return el.currentStyle ? - el.currentStyle[property] : - document.defaultView.getComputedStyle(el, null).getPropertyValue(property); - }; - helpers.retinaScale = function(chart) { - var ctx = chart.ctx; - var canvas = chart.canvas; - var width = canvas.width; - var height = canvas.height; - var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1; + if (styleValue.indexOf('%') !== -1) { + // percentage * size in dimension + valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; + } + } else { + valueInPixels = styleValue; + } - if (pixelRatio !== 1) { - canvas.height = height * pixelRatio; - canvas.width = width * pixelRatio; - ctx.scale(pixelRatio, pixelRatio); + return valueInPixels; + } - // Store the device pixel ratio so that we can go backwards in `destroy`. - // The devicePixelRatio changes with zoom, so there are no guarantees that it is the same - // when destroy is called - chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio; - } + /** + * Returns if the given value contains an effective constraint. + * @private + */ + function isConstrainedValue(value) { + return value !== undefined && value !== null && value !== 'none'; + } - canvas.style.width = width + 'px'; - canvas.style.height = height + 'px'; - }; - //-- Canvas methods - helpers.clear = function(chart) { - chart.ctx.clearRect(0, 0, chart.width, chart.height); - }; - helpers.fontString = function(pixelSize, fontStyle, fontFamily) { - return fontStyle + " " + pixelSize + "px " + fontFamily; - }; - helpers.longestText = function(ctx, font, arrayOfThings, cache) { - cache = cache || {}; - var data = cache.data = cache.data || {}; - var gc = cache.garbageCollect = cache.garbageCollect || []; + // Private helper to get a constraint dimension + // @param domNode : the node to check the constraint on + // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) + // @param percentageProperty : property of parent to use when calculating width as a percentage + // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser + function getConstraintDimension(domNode, maxStyle, percentageProperty) { + var view = document.defaultView; + var parentNode = domNode.parentNode; + var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; + var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; + var hasCNode = isConstrainedValue(constrainedNode); + var hasCContainer = isConstrainedValue(constrainedContainer); + var infinity = Number.POSITIVE_INFINITY; - if (cache.font !== font) { - data = cache.data = {}; - gc = cache.garbageCollect = []; - cache.font = font; - } + if (hasCNode || hasCContainer) { + return Math.min( + hasCNode? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, + hasCContainer? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); + } - ctx.font = font; - var longest = 0; - helpers.each(arrayOfThings, function(thing) { - // Undefined strings and arrays should not be measured - if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { - longest = helpers.measureText(ctx, data, gc, longest, thing); - } else if (helpers.isArray(thing)) { - // if it is an array lets measure each element - // to do maybe simplify this function a bit so we can do this more recursively? - helpers.each(thing, function(nestedThing) { - // Undefined strings and arrays should not be measured - if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { - longest = helpers.measureText(ctx, data, gc, longest, nestedThing); - } - }); - } - }); + return 'none'; + } + // returns Number or undefined if no constraint + helpers.getConstraintWidth = function(domNode) { + return getConstraintDimension(domNode, 'max-width', 'clientWidth'); + }; + // returns Number or undefined if no constraint + helpers.getConstraintHeight = function(domNode) { + return getConstraintDimension(domNode, 'max-height', 'clientHeight'); + }; + helpers.getMaximumWidth = function(domNode) { + var container = domNode.parentNode; + var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10); + var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10); + var w = container.clientWidth - paddingLeft - paddingRight; + var cw = helpers.getConstraintWidth(domNode); + return isNaN(cw)? w : Math.min(w, cw); + }; + helpers.getMaximumHeight = function(domNode) { + var container = domNode.parentNode; + var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10); + var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10); + var h = container.clientHeight - paddingTop - paddingBottom; + var ch = helpers.getConstraintHeight(domNode); + return isNaN(ch)? h : Math.min(h, ch); + }; + helpers.getStyle = function(el, property) { + return el.currentStyle ? + el.currentStyle[property] : + document.defaultView.getComputedStyle(el, null).getPropertyValue(property); + }; + helpers.retinaScale = function(chart) { + var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1; + if (pixelRatio === 1) { + return; + } - var gcLen = gc.length / 2; - if (gcLen > arrayOfThings.length) { - for (var i = 0; i < gcLen; i++) { - delete data[gc[i]]; - } - gc.splice(0, gcLen); - } - return longest; - }; - helpers.measureText = function (ctx, data, gc, longest, string) { - var textWidth = data[string]; - if (!textWidth) { - textWidth = data[string] = ctx.measureText(string).width; - gc.push(string); - } - if (textWidth > longest) { - longest = textWidth; - } - return longest; - }; - helpers.numberOfLabelLines = function(arrayOfThings) { - var numberOfLines = 1; - helpers.each(arrayOfThings, function(thing) { - if (helpers.isArray(thing)) { - if (thing.length > numberOfLines) { - numberOfLines = thing.length; - } - } - }); - return numberOfLines; - }; - 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(); - }; - helpers.color = function(c) { - if (!color) { - console.log('Color.js not found!'); - return c; - } + var canvas = chart.canvas; + var height = chart.height; + var width = chart.width; - /* global CanvasGradient */ - if (c instanceof CanvasGradient) { - return color(Chart.defaults.global.defaultColor); - } + canvas.height = height * pixelRatio; + canvas.width = width * pixelRatio; + chart.ctx.scale(pixelRatio, pixelRatio); - return color(c); - }; - helpers.addResizeListener = function(node, callback) { - // Hide an iframe before the node - var hiddenIframe = document.createElement('iframe'); - var hiddenIframeClass = 'chartjs-hidden-iframe'; + // If no style has been set on the canvas, the render size is used as display size, + // making the chart visually bigger, so let's enforce it to the "correct" values. + // See https://github.com/chartjs/Chart.js/issues/3575 + canvas.style.height = height + 'px'; + canvas.style.width = width + 'px'; + }; + // -- Canvas methods + helpers.clear = function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }; + helpers.fontString = function(pixelSize, fontStyle, fontFamily) { + return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; + }; + helpers.longestText = function(ctx, font, arrayOfThings, cache) { + cache = cache || {}; + var data = cache.data = cache.data || {}; + var gc = cache.garbageCollect = cache.garbageCollect || []; - if (hiddenIframe.classlist) { - // can use classlist - hiddenIframe.classlist.add(hiddenIframeClass); - } else { - hiddenIframe.setAttribute('class', hiddenIframeClass); - } + if (cache.font !== font) { + data = cache.data = {}; + gc = cache.garbageCollect = []; + cache.font = font; + } - // Set the style - var style = hiddenIframe.style; - style.width = '100%'; - style.display = 'block'; - style.border = 0; - style.height = 0; - style.margin = 0; - style.position = 'absolute'; - style.left = 0; - style.right = 0; - style.top = 0; - style.bottom = 0; + ctx.font = font; + var longest = 0; + helpers.each(arrayOfThings, function(thing) { + // Undefined strings and arrays should not be measured + if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { + longest = helpers.measureText(ctx, data, gc, longest, thing); + } else if (helpers.isArray(thing)) { + // if it is an array lets measure each element + // to do maybe simplify this function a bit so we can do this more recursively? + helpers.each(thing, function(nestedThing) { + // Undefined strings and arrays should not be measured + if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { + longest = helpers.measureText(ctx, data, gc, longest, nestedThing); + } + }); + } + }); - // Insert the iframe so that contentWindow is available - node.insertBefore(hiddenIframe, node.firstChild); + var gcLen = gc.length / 2; + if (gcLen > arrayOfThings.length) { + for (var i = 0; i < gcLen; i++) { + delete data[gc[i]]; + } + gc.splice(0, gcLen); + } + return longest; + }; + helpers.measureText = function(ctx, data, gc, longest, string) { + var textWidth = data[string]; + if (!textWidth) { + textWidth = data[string] = ctx.measureText(string).width; + gc.push(string); + } + if (textWidth > longest) { + longest = textWidth; + } + return longest; + }; + helpers.numberOfLabelLines = function(arrayOfThings) { + var numberOfLines = 1; + helpers.each(arrayOfThings, function(thing) { + if (helpers.isArray(thing)) { + if (thing.length > numberOfLines) { + numberOfLines = thing.length; + } + } + }); + return numberOfLines; + }; + 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(); + }; + helpers.color = function(c) { + if (!color) { + console.error('Color.js not found!'); + return c; + } - (hiddenIframe.contentWindow || hiddenIframe).onresize = function() { - if (callback) { - callback(); - } - }; - }; - helpers.removeResizeListener = function(node) { - var hiddenIframe = node.querySelector('.chartjs-hidden-iframe'); + /* global CanvasGradient */ + if (c instanceof CanvasGradient) { + return color(Chart.defaults.global.defaultColor); + } - // Remove the resize detect iframe - if (hiddenIframe) { - hiddenIframe.parentNode.removeChild(hiddenIframe); - } - }; - helpers.isArray = Array.isArray? - function(obj) { return Array.isArray(obj); } : - function(obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; - }; - //! @see http://stackoverflow.com/a/14853974 - helpers.arrayEquals = function(a0, a1) { - var i, ilen, v0, v1; + return color(c); + }; + helpers.addResizeListener = function(node, callback) { + var iframe = document.createElement('iframe'); + iframe.className = 'chartjs-hidden-iframe'; + iframe.style.cssText = + 'display:block;'+ + 'overflow:hidden;'+ + 'border:0;'+ + 'margin:0;'+ + 'top:0;'+ + 'left:0;'+ + 'bottom:0;'+ + 'right:0;'+ + 'height:100%;'+ + 'width:100%;'+ + 'position:absolute;'+ + 'pointer-events:none;'+ + 'z-index:-1;'; - if (!a0 || !a1 || a0.length != a1.length) { - return false; - } + // Prevent the iframe to gain focus on tab. + // https://github.com/chartjs/Chart.js/issues/3090 + iframe.tabIndex = -1; - for (i = 0, ilen=a0.length; i < ilen; ++i) { - v0 = a0[i]; - v1 = a1[i]; + // Let's keep track of this added iframe and thus avoid DOM query when removing it. + var stub = node._chartjs = { + resizer: iframe, + ticking: false + }; - if (v0 instanceof Array && v1 instanceof Array) { - if (!helpers.arrayEquals(v0, v1)) { - return false; - } - } else if (v0 != v1) { - // NOTE: two different object instances will never be equal: {x:20} != {x:20} - return false; - } - } + // Throttle the callback notification until the next animation frame. + var notify = function() { + if (!stub.ticking) { + stub.ticking = true; + helpers.requestAnimFrame.call(window, function() { + if (stub.resizer) { + stub.ticking = false; + return callback(); + } + }); + } + }; - return true; - }; - helpers.callCallback = function(fn, args, _tArg) { - if (fn && typeof fn.call === 'function') { - fn.apply(_tArg, args); - } - }; - helpers.getHoverColor = function(color) { - /* global CanvasPattern */ - return (color instanceof CanvasPattern) ? - color : - helpers.color(color).saturate(0.5).darken(0.1).rgbString(); - }; -}; + // If the iframe is re-attached to the DOM, the resize listener is removed because the + // content is reloaded, so make sure to install the handler after the iframe is loaded. + // https://github.com/chartjs/Chart.js/issues/3521 + helpers.addEvent(iframe, 'load', function() { + helpers.addEvent(iframe.contentWindow || iframe, 'resize', notify); + // The iframe size might have changed while loading, which can also + // happen if the size has been changed while detached from the DOM. + notify(); + }); + + node.insertBefore(iframe, node.firstChild); + }; + helpers.removeResizeListener = function(node) { + if (!node || !node._chartjs) { + return; + } + + var iframe = node._chartjs.resizer; + if (iframe) { + iframe.parentNode.removeChild(iframe); + node._chartjs.resizer = null; + } + + delete node._chartjs; + }; + helpers.isArray = Array.isArray? + function(obj) { + return Array.isArray(obj); + } : + function(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; + // ! @see http://stackoverflow.com/a/14853974 + helpers.arrayEquals = function(a0, a1) { + var i, ilen, v0, v1; + + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen=a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { + return false; + } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + + return true; + }; + helpers.callCallback = function(fn, args, _tArg) { + if (fn && typeof fn.call === 'function') { + fn.apply(_tArg, args); + } + }; + helpers.getHoverColor = function(colorValue) { + /* global CanvasPattern */ + return (colorValue instanceof CanvasPattern) ? + colorValue : + helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString(); + }; + }; + },{"3":3}],27:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function() { + module.exports = function(Chart) { + var helpers = Chart.helpers; - //Occupy the global variable of Chart, and create a simple base class - var Chart = function(context, config) { - var me = this; - var helpers = Chart.helpers; - me.config = config || { - data: { - datasets: [] - } - }; + /** + * Helper function to traverse all of the visible elements in the chart + * @param chart {chart} the chart + * @param handler {Function} the callback to execute for each visible item + */ + function parseVisibleItems(chart, handler) { + var datasets = chart.data.datasets; + var meta, i, j, ilen, jlen; - // Support a jQuery'd canvas element - if (context.length && context[0].getContext) { - context = context[0]; - } + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (!chart.isDatasetVisible(i)) { + continue; + } - // Support a canvas domnode - if (context.getContext) { - context = context.getContext("2d"); - } + meta = chart.getDatasetMeta(i); + for (j = 0, jlen = meta.data.length; j < jlen; ++j) { + var element = meta.data[j]; + if (!element._view.skip) { + handler(element); + } + } + } + } - me.ctx = context; - me.canvas = context.canvas; + /** + * Helper function to get the items that intersect the event position + * @param items {ChartElement[]} elements to filter + * @param position {Point} the point to be nearest to + * @return {ChartElement[]} the nearest items + */ + function getIntersectItems(chart, position) { + var elements = []; - context.canvas.style.display = context.canvas.style.display || 'block'; + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); - // Figure out what the size of the chart will be. - // If the canvas has a specified width and height, we use those else - // we look to see if the canvas node has a CSS width and height. - // If there is still no height, fill the parent container - me.width = context.canvas.width || parseInt(helpers.getStyle(context.canvas, 'width'), 10) || helpers.getMaximumWidth(context.canvas); - me.height = context.canvas.height || parseInt(helpers.getStyle(context.canvas, 'height'), 10) || helpers.getMaximumHeight(context.canvas); + return elements; + } - me.aspectRatio = me.width / me.height; + /** + * Helper function to get the items nearest to the event position considering all visible items in teh chart + * @param chart {Chart} the chart to look at elements from + * @param position {Point} the point to be nearest to + * @param intersect {Boolean} if true, only consider items that intersect the position + * @param distanceMetric {Function} Optional function to provide the distance between + * @return {ChartElement[]} the nearest items + */ + function getNearestItems(chart, position, intersect, distanceMetric) { + var minDistance = Number.POSITIVE_INFINITY; + var nearestItems = []; - if (isNaN(me.aspectRatio) || isFinite(me.aspectRatio) === false) { - // If the canvas has no size, try and figure out what the aspect ratio will be. - // Some charts prefer square canvases (pie, radar, etc). If that is specified, use that - // else use the canvas default ratio of 2 - me.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2; - } + if (!distanceMetric) { + distanceMetric = helpers.distanceBetweenPoints; + } - // Store the original style of the element so we can set it back - me.originalCanvasStyleWidth = context.canvas.style.width; - me.originalCanvasStyleHeight = context.canvas.style.height; + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } - // High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. - helpers.retinaScale(me); - me.controller = new Chart.Controller(me); + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); - // Always bind this so that if the responsive state changes we still work - helpers.addResizeListener(context.canvas.parentNode, function() { - if (me.controller && me.controller.config.options.responsive) { - me.controller.resize(); - } - }); + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + }); - return me.controller ? me.controller : me; + return nearestItems; + } - }; + function indexMode(chart, e, options) { + var position = helpers.getRelativePosition(e, chart.chart); + var distanceMetric = function(pt1, pt2) { + return Math.abs(pt1.x - pt2.x); + }; + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + var elements = []; - //Globally expose the defaults to allow for user updating/changing - Chart.defaults = { - global: { - responsive: true, - responsiveAnimationDuration: 0, - maintainAspectRatio: true, - events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"], - hover: { - onHover: null, - mode: 'single', - animationDuration: 400 - }, - onClick: null, - defaultColor: 'rgba(0,0,0,0.1)', - defaultFontColor: '#666', - defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - defaultFontSize: 12, - defaultFontStyle: 'normal', - showLines: true, + if (!items.length) { + return []; + } - // Element defaults defined in element extensions - elements: {}, + chart.data.datasets.forEach(function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex), + element = meta.data[items[0]._index]; - // Legend callback string - legendCallback: function(chart) { - var text = []; - text.push('<ul class="' + chart.id + '-legend">'); - for (var i = 0; i < chart.data.datasets.length; i++) { - text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>'); - if (chart.data.datasets[i].label) { - text.push(chart.data.datasets[i].label); - } - text.push('</li>'); - } - text.push('</ul>'); + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); + } + } + }); - return text.join(""); - } - } - }; + return elements; + } - Chart.Chart = Chart; + /** + * @interface IInteractionOptions + */ + /** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ - return Chart; + /** + * @namespace Chart.Interaction + * Contains interaction related functions + */ + Chart.Interaction = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = helpers.getRelativePosition(e, chart.chart); + var elements = []; -}; + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + return elements; + } + }); -},{}],28:[function(require,module,exports){ -"use strict"; + return elements.slice(0, 1); + }, -module.exports = function(Chart) { + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + */ + label: indexMode, - var helpers = Chart.helpers; + /** + * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item + * @function Chart.Interaction.modes.index + * @since v2.4.0 + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + index: indexMode, - // The layout service is very self explanatory. It's responsible for the layout within a chart. - // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need - // It is this service's responsibility of carrying out that layout. - Chart.layoutService = { - defaults: {}, + /** + * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect is false, we find the nearest item and return the items in that dataset + * @function Chart.Interaction.modes.dataset + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + dataset: function(chart, e, options) { + var position = helpers.getRelativePosition(e, chart.chart); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false); - // Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins. - addBox: function(chartInstance, box) { - if (!chartInstance.boxes) { - chartInstance.boxes = []; - } - chartInstance.boxes.push(box); - }, + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; + } - removeBox: function(chartInstance, box) { - if (!chartInstance.boxes) { - return; - } - chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1); - }, + return items; + }, - // The most important function - update: function(chartInstance, width, height) { + /** + * @function Chart.Interaction.modes.x-axis + * @deprecated since version 2.4.0. Use index mode and intersect == true + */ + 'x-axis': function(chart, e) { + return indexMode(chart, e, true); + }, - if (!chartInstance) { - return; - } + /** + * Point mode returns all elements that hit test based on the event position + * of the event + * @function Chart.Interaction.modes.intersect + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + point: function(chart, e) { + var position = helpers.getRelativePosition(e, chart.chart); + return getIntersectItems(chart, position); + }, - var xPadding = 0; - var yPadding = 0; + /** + * nearest mode returns the element closest to the point + * @function Chart.Interaction.modes.intersect + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + nearest: function(chart, e, options) { + var position = helpers.getRelativePosition(e, chart.chart); + var nearestItems = getNearestItems(chart, position, options.intersect); - var leftBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "left"; - }); - var rightBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "right"; - }); - var topBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "top"; - }); - var bottomBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "bottom"; - }); + // We have multiple items at the same distance from the event. Now sort by smallest + if (nearestItems.length > 1) { + nearestItems.sort(function(a, b) { + var sizeA = a.getArea(); + var sizeB = b.getArea(); + var ret = sizeA - sizeB; - // Boxes that overlay the chartarea such as the radialLinear scale - var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "chartArea"; - }); + if (ret === 0) { + // if equal sort by dataset index + ret = a._datasetIndex - b._datasetIndex; + } - // Ensure that full width boxes are at the very top / bottom - topBoxes.sort(function(a, b) { - return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0); - }); - bottomBoxes.sort(function(a, b) { - return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0); - }); + return ret; + }); + } - // Essentially we now have any number of boxes on each of the 4 sides. - // Our canvas looks like the following. - // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and - // B1 is the bottom axis - // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays - // These locations are single-box locations only, when trying to register a chartArea location that is already taken, - // an error will be thrown. - // - // |----------------------------------------------------| - // | T1 (Full Width) | - // |----------------------------------------------------| - // | | | T2 | | - // | |----|-------------------------------------|----| - // | | | C1 | | C2 | | - // | | |----| |----| | - // | | | | | - // | L1 | L2 | ChartArea (C0) | R1 | - // | | | | | - // | | |----| |----| | - // | | | C3 | | C4 | | - // | |----|-------------------------------------|----| - // | | | B1 | | - // |----------------------------------------------------| - // | B2 (Full Width) | - // |----------------------------------------------------| - // - // What we do to find the best sizing, we do the following - // 1. Determine the minimum size of the chart area. - // 2. Split the remaining width equally between each vertical axis - // 3. Split the remaining height equally between each horizontal axis - // 4. Give each layout the maximum size it can be. The layout will return it's minimum size - // 5. Adjust the sizes of each axis based on it's minimum reported size. - // 6. Refit each axis - // 7. Position each axis in the final location - // 8. Tell the chart the final location of the chart area - // 9. Tell any axes that overlay the chart area the positions of the chart area + // Return only 1 item + return nearestItems.slice(0, 1); + }, - // Step 1 - var chartWidth = width - (2 * xPadding); - var chartHeight = height - (2 * yPadding); - var chartAreaWidth = chartWidth / 2; // min 50% - var chartAreaHeight = chartHeight / 2; // min 50% + /** + * x mode returns the elements that hit-test at the current x coordinate + * @function Chart.Interaction.modes.x + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + x: function(chart, e, options) { + var position = helpers.getRelativePosition(e, chart.chart); + var items = []; + var intersectsItem = false; - // Step 2 - var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } - // Step 3 - var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); - // Step 4 - var maxChartAreaWidth = chartWidth; - var maxChartAreaHeight = chartHeight; - var minBoxSizes = []; + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + }, - helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); + /** + * y mode returns the elements that hit-test at the current y coordinate + * @function Chart.Interaction.modes.y + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + y: function(chart, e, options) { + var position = helpers.getRelativePosition(e, chart.chart); + var items = []; + var intersectsItem = false; - function getMinimumBoxSize(box) { - var minSize; - var isHorizontal = box.isHorizontal(); + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } - if (isHorizontal) { - minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); - maxChartAreaHeight -= minSize.height; - } else { - minSize = box.update(verticalBoxWidth, chartAreaHeight); - maxChartAreaWidth -= minSize.width; - } + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); - minBoxSizes.push({ - horizontal: isHorizontal, - minSize: minSize, - box: box - }); - } + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + } + } + }; + }; - // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could - // be if the axes are drawn at their minimum sizes. +},{}],28:[function(require,module,exports){ + 'use strict'; - // Steps 5 & 6 - var totalLeftBoxesWidth = xPadding; - var totalRightBoxesWidth = xPadding; - var totalTopBoxesHeight = yPadding; - var totalBottomBoxesHeight = yPadding; + module.exports = function() { - // Update, and calculate the left and right margins for the horizontal boxes - helpers.each(leftBoxes.concat(rightBoxes), fitBox); + // Occupy the global variable of Chart, and create a simple base class + var Chart = function(item, config) { + this.controller = new Chart.Controller(item, config, this); + return this.controller; + }; - helpers.each(leftBoxes, function(box) { - totalLeftBoxesWidth += box.width; - }); + // Globally expose the defaults to allow for user updating/changing + Chart.defaults = { + global: { + responsive: true, + responsiveAnimationDuration: 0, + maintainAspectRatio: true, + events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], + hover: { + onHover: null, + mode: 'nearest', + intersect: true, + animationDuration: 400 + }, + onClick: null, + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + showLines: true, - helpers.each(rightBoxes, function(box) { - totalRightBoxesWidth += box.width; - }); + // Element defaults defined in element extensions + elements: {}, - // Set the Left and Right margins for the horizontal boxes - helpers.each(topBoxes.concat(bottomBoxes), fitBox); + // Legend callback string + legendCallback: function(chart) { + var text = []; + text.push('<ul class="' + chart.id + '-legend">'); + for (var i = 0; i < chart.data.datasets.length; i++) { + text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>'); + if (chart.data.datasets[i].label) { + text.push(chart.data.datasets[i].label); + } + text.push('</li>'); + } + text.push('</ul>'); - // Function to fit a box - function fitBox(box) { - var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { - return minBoxSize.box === box; - }); + return text.join(''); + } + } + }; - if (minBoxSize) { - if (box.isHorizontal()) { - var scaleMargin = { - left: totalLeftBoxesWidth, - right: totalRightBoxesWidth, - top: 0, - bottom: 0 - }; + Chart.Chart = Chart; - // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends - // on the margin. Sometimes they need to increase in size slightly - box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); - } else { - box.update(minBoxSize.minSize.width, maxChartAreaHeight); - } - } - } + return Chart; + }; - // Figure out how much margin is on the top and bottom of the vertical boxes - helpers.each(topBoxes, function(box) { - totalTopBoxesHeight += box.height; - }); +},{}],29:[function(require,module,exports){ + 'use strict'; - helpers.each(bottomBoxes, function(box) { - totalBottomBoxesHeight += box.height; - }); + module.exports = function(Chart) { - // Let the left layout know the final margin - helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); + var helpers = Chart.helpers; - function finalFitVerticalBox(box) { - var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { - return minBoxSize.box === box; - }); + // The layout service is very self explanatory. It's responsible for the layout within a chart. + // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need + // It is this service's responsibility of carrying out that layout. + Chart.layoutService = { + defaults: {}, - var scaleMargin = { - left: 0, - right: 0, - top: totalTopBoxesHeight, - bottom: totalBottomBoxesHeight - }; + // Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins. + addBox: function(chartInstance, box) { + if (!chartInstance.boxes) { + chartInstance.boxes = []; + } + chartInstance.boxes.push(box); + }, - if (minBoxSize) { - box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); - } - } + removeBox: function(chartInstance, box) { + if (!chartInstance.boxes) { + return; + } + chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1); + }, - // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) - totalLeftBoxesWidth = xPadding; - totalRightBoxesWidth = xPadding; - totalTopBoxesHeight = yPadding; - totalBottomBoxesHeight = yPadding; + // The most important function + update: function(chartInstance, width, height) { - helpers.each(leftBoxes, function(box) { - totalLeftBoxesWidth += box.width; - }); + if (!chartInstance) { + return; + } - helpers.each(rightBoxes, function(box) { - totalRightBoxesWidth += box.width; - }); + var layoutOptions = chartInstance.options.layout; + var padding = layoutOptions ? layoutOptions.padding : null; - helpers.each(topBoxes, function(box) { - totalTopBoxesHeight += box.height; - }); - helpers.each(bottomBoxes, function(box) { - totalBottomBoxesHeight += box.height; - }); + var leftPadding = 0; + var rightPadding = 0; + var topPadding = 0; + var bottomPadding = 0; - // Figure out if our chart area changed. This would occur if the dataset layout label rotation - // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do - // without calling `fit` again - var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; - var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; + if (!isNaN(padding)) { + // options.layout.padding is a number. assign to all + leftPadding = padding; + rightPadding = padding; + topPadding = padding; + bottomPadding = padding; + } else { + leftPadding = padding.left || 0; + rightPadding = padding.right || 0; + topPadding = padding.top || 0; + bottomPadding = padding.bottom || 0; + } - if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { - helpers.each(leftBoxes, function(box) { - box.height = newMaxChartAreaHeight; - }); + var leftBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === 'left'; + }); + var rightBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === 'right'; + }); + var topBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === 'top'; + }); + var bottomBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === 'bottom'; + }); - helpers.each(rightBoxes, function(box) { - box.height = newMaxChartAreaHeight; - }); + // Boxes that overlay the chartarea such as the radialLinear scale + var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === 'chartArea'; + }); - helpers.each(topBoxes, function(box) { - if (!box.options.fullWidth) { - box.width = newMaxChartAreaWidth; - } - }); + // Ensure that full width boxes are at the very top / bottom + topBoxes.sort(function(a, b) { + return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0); + }); + bottomBoxes.sort(function(a, b) { + return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0); + }); - helpers.each(bottomBoxes, function(box) { - if (!box.options.fullWidth) { - box.width = newMaxChartAreaWidth; - } - }); + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + // What we do to find the best sizing, we do the following + // 1. Determine the minimum size of the chart area. + // 2. Split the remaining width equally between each vertical axis + // 3. Split the remaining height equally between each horizontal axis + // 4. Give each layout the maximum size it can be. The layout will return it's minimum size + // 5. Adjust the sizes of each axis based on it's minimum reported size. + // 6. Refit each axis + // 7. Position each axis in the final location + // 8. Tell the chart the final location of the chart area + // 9. Tell any axes that overlay the chart area the positions of the chart area - maxChartAreaHeight = newMaxChartAreaHeight; - maxChartAreaWidth = newMaxChartAreaWidth; - } + // Step 1 + var chartWidth = width - leftPadding - rightPadding; + var chartHeight = height - topPadding - bottomPadding; + var chartAreaWidth = chartWidth / 2; // min 50% + var chartAreaHeight = chartHeight / 2; // min 50% - // Step 7 - Position the boxes - var left = xPadding; - var top = yPadding; + // Step 2 + var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); - helpers.each(leftBoxes.concat(topBoxes), placeBox); + // Step 3 + var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); - // Account for chart width and height - left += maxChartAreaWidth; - top += maxChartAreaHeight; + // Step 4 + var maxChartAreaWidth = chartWidth; + var maxChartAreaHeight = chartHeight; + var minBoxSizes = []; - helpers.each(rightBoxes, placeBox); - helpers.each(bottomBoxes, placeBox); + function getMinimumBoxSize(box) { + var minSize; + var isHorizontal = box.isHorizontal(); - function placeBox(box) { - if (box.isHorizontal()) { - box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth; - box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth; - box.top = top; - box.bottom = top + box.height; + if (isHorizontal) { + minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); + maxChartAreaHeight -= minSize.height; + } else { + minSize = box.update(verticalBoxWidth, chartAreaHeight); + maxChartAreaWidth -= minSize.width; + } - // Move to next point - top = box.bottom; + minBoxSizes.push({ + horizontal: isHorizontal, + minSize: minSize, + box: box + }); + } - } else { + helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); - box.left = left; - box.right = left + box.width; - box.top = totalTopBoxesHeight; - box.bottom = totalTopBoxesHeight + maxChartAreaHeight; + // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. - // Move to next point - left = box.right; - } - } + // Steps 5 & 6 + var totalLeftBoxesWidth = leftPadding; + var totalRightBoxesWidth = rightPadding; + var totalTopBoxesHeight = topPadding; + var totalBottomBoxesHeight = bottomPadding; - // Step 8 - chartInstance.chartArea = { - left: totalLeftBoxesWidth, - top: totalTopBoxesHeight, - right: totalLeftBoxesWidth + maxChartAreaWidth, - bottom: totalTopBoxesHeight + maxChartAreaHeight - }; + // Function to fit a box + function fitBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) { + return minBox.box === box; + }); - // Step 9 - helpers.each(chartAreaBoxes, function(box) { - box.left = chartInstance.chartArea.left; - box.top = chartInstance.chartArea.top; - box.right = chartInstance.chartArea.right; - box.bottom = chartInstance.chartArea.bottom; + if (minBoxSize) { + if (box.isHorizontal()) { + var scaleMargin = { + left: totalLeftBoxesWidth, + right: totalRightBoxesWidth, + top: 0, + bottom: 0 + }; - box.update(maxChartAreaWidth, maxChartAreaHeight); - }); - } - }; -}; + // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends + // on the margin. Sometimes they need to increase in size slightly + box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); + } else { + box.update(minBoxSize.minSize.width, maxChartAreaHeight); + } + } + } -},{}],29:[function(require,module,exports){ -"use strict"; + // Update, and calculate the left and right margins for the horizontal boxes + helpers.each(leftBoxes.concat(rightBoxes), fitBox); -module.exports = function(Chart) { + helpers.each(leftBoxes, function(box) { + totalLeftBoxesWidth += box.width; + }); - var helpers = Chart.helpers; - var noop = helpers.noop; + helpers.each(rightBoxes, function(box) { + totalRightBoxesWidth += box.width; + }); - Chart.defaults.global.legend = { + // Set the Left and Right margins for the horizontal boxes + helpers.each(topBoxes.concat(bottomBoxes), fitBox); - display: true, - position: 'top', - fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) - reverse: false, + // Figure out how much margin is on the top and bottom of the vertical boxes + helpers.each(topBoxes, function(box) { + totalTopBoxesHeight += box.height; + }); - // a callback that will handle - onClick: function(e, legendItem) { - var index = legendItem.datasetIndex; - var ci = this.chart; - var meta = ci.getDatasetMeta(index); + helpers.each(bottomBoxes, function(box) { + totalBottomBoxesHeight += box.height; + }); - // See controller.isDatasetVisible comment - meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null; + function finalFitVerticalBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) { + return minSize.box === box; + }); - // We hid a dataset ... rerender the chart - ci.update(); - }, + var scaleMargin = { + left: 0, + right: 0, + top: totalTopBoxesHeight, + bottom: totalBottomBoxesHeight + }; - labels: { - boxWidth: 40, - padding: 10, - // Generates labels shown in the legend - // Valid properties to return: - // text : text to display - // fillStyle : fill of coloured box - // strokeStyle: stroke of coloured box - // hidden : if this legend item refers to a hidden item - // lineCap : cap style for line - // lineDash - // lineDashOffset : - // lineJoin : - // lineWidth : - generateLabels: function(chart) { - var data = chart.data; - return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { - return { - text: dataset.label, - fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), - hidden: !chart.isDatasetVisible(i), - lineCap: dataset.borderCapStyle, - lineDash: dataset.borderDash, - lineDashOffset: dataset.borderDashOffset, - lineJoin: dataset.borderJoinStyle, - lineWidth: dataset.borderWidth, - strokeStyle: dataset.borderColor, - pointStyle: dataset.pointStyle, + if (minBoxSize) { + box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); + } + } - // Below is extra data used for toggling the datasets - datasetIndex: i - }; - }, this) : []; - } - } - }; + // Let the left layout know the final margin + helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); - Chart.Legend = Chart.Element.extend({ + // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) + totalLeftBoxesWidth = leftPadding; + totalRightBoxesWidth = rightPadding; + totalTopBoxesHeight = topPadding; + totalBottomBoxesHeight = bottomPadding; - initialize: function(config) { - helpers.extend(this, config); + helpers.each(leftBoxes, function(box) { + totalLeftBoxesWidth += box.width; + }); - // Contains hit boxes for each dataset (in dataset order) - this.legendHitBoxes = []; + helpers.each(rightBoxes, function(box) { + totalRightBoxesWidth += box.width; + }); - // Are we in doughnut mode which has a different data type - this.doughnutMode = false; - }, + helpers.each(topBoxes, function(box) { + totalTopBoxesHeight += box.height; + }); + helpers.each(bottomBoxes, function(box) { + totalBottomBoxesHeight += box.height; + }); - // These methods are ordered by lifecyle. Utilities then follow. - // Any function defined here is inherited by all legend types. - // Any function can be extended by the legend type + // Figure out if our chart area changed. This would occur if the dataset layout label rotation + // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do + // without calling `fit` again + var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; + var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; - beforeUpdate: noop, - update: function(maxWidth, maxHeight, margins) { - var me = this; + if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { + helpers.each(leftBoxes, function(box) { + box.height = newMaxChartAreaHeight; + }); - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); + helpers.each(rightBoxes, function(box) { + box.height = newMaxChartAreaHeight; + }); - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me.margins = margins; + helpers.each(topBoxes, function(box) { + if (!box.options.fullWidth) { + box.width = newMaxChartAreaWidth; + } + }); - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); - // Labels - me.beforeBuildLabels(); - me.buildLabels(); - me.afterBuildLabels(); + helpers.each(bottomBoxes, function(box) { + if (!box.options.fullWidth) { + box.width = newMaxChartAreaWidth; + } + }); - // Fit - me.beforeFit(); - me.fit(); - me.afterFit(); - // - me.afterUpdate(); + maxChartAreaHeight = newMaxChartAreaHeight; + maxChartAreaWidth = newMaxChartAreaWidth; + } - return me.minSize; - }, - afterUpdate: noop, + // Step 7 - Position the boxes + var left = leftPadding; + var top = topPadding; - // + function placeBox(box) { + if (box.isHorizontal()) { + box.left = box.options.fullWidth ? leftPadding : totalLeftBoxesWidth; + box.right = box.options.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth; + box.top = top; + box.bottom = top + box.height; - beforeSetDimensions: noop, - setDimensions: function() { - var me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; + // Move to next point + top = box.bottom; - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } + } else { - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; + box.left = left; + box.right = left + box.width; + box.top = totalTopBoxesHeight; + box.bottom = totalTopBoxesHeight + maxChartAreaHeight; - // Reset minSize - me.minSize = { - width: 0, - height: 0 - }; - }, - afterSetDimensions: noop, + // Move to next point + left = box.right; + } + } - // + helpers.each(leftBoxes.concat(topBoxes), placeBox); - beforeBuildLabels: noop, - buildLabels: function() { - var me = this; - me.legendItems = me.options.labels.generateLabels.call(me, me.chart); - if(me.options.reverse){ - me.legendItems.reverse(); - } - }, - afterBuildLabels: noop, + // Account for chart width and height + left += maxChartAreaWidth; + top += maxChartAreaHeight; - // + helpers.each(rightBoxes, placeBox); + helpers.each(bottomBoxes, placeBox); - beforeFit: noop, - fit: function() { - var me = this; - var opts = me.options; - var labelOpts = opts.labels; - var display = opts.display; + // Step 8 + chartInstance.chartArea = { + left: totalLeftBoxesWidth, + top: totalTopBoxesHeight, + right: totalLeftBoxesWidth + maxChartAreaWidth, + bottom: totalTopBoxesHeight + maxChartAreaHeight + }; - var ctx = me.ctx; + // Step 9 + helpers.each(chartAreaBoxes, function(box) { + box.left = chartInstance.chartArea.left; + box.top = chartInstance.chartArea.top; + box.right = chartInstance.chartArea.right; + box.bottom = chartInstance.chartArea.bottom; - var globalDefault = Chart.defaults.global, - itemOrDefault = helpers.getValueOrDefault, - fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize), - fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), - fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily), - labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + box.update(maxChartAreaWidth, maxChartAreaHeight); + }); + } + }; + }; - // Reset hit boxes - var hitboxes = me.legendHitBoxes = []; +},{}],30:[function(require,module,exports){ + 'use strict'; - var minSize = me.minSize; - var isHorizontal = me.isHorizontal(); + module.exports = function(Chart) { - if (isHorizontal) { - minSize.width = me.maxWidth; // fill all the width - minSize.height = display ? 10 : 0; - } else { - minSize.width = display ? 10 : 0; - minSize.height = me.maxHeight; // fill all the height - } + var helpers = Chart.helpers; + var noop = helpers.noop; - // Increase sizes here - if (display) { - ctx.font = labelFont; + Chart.defaults.global.legend = { - if (isHorizontal) { - // Labels + display: true, + position: 'top', + fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) + reverse: false, - // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one - var lineWidths = me.lineWidths = [0]; - var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; + // a callback that will handle + onClick: function(e, legendItem) { + var index = legendItem.datasetIndex; + var ci = this.chart; + var meta = ci.getDatasetMeta(index); - ctx.textAlign = "left"; - ctx.textBaseline = 'top'; + // See controller.isDatasetVisible comment + meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null; - helpers.each(me.legendItems, function(legendItem, i) { - var boxWidth = labelOpts.usePointStyle ? - fontSize * Math.sqrt(2) : - labelOpts.boxWidth; + // We hid a dataset ... rerender the chart + ci.update(); + }, - var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; - if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { - totalHeight += fontSize + (labelOpts.padding); - lineWidths[lineWidths.length] = me.left; - } + onHover: null, - // Store the hitbox width and height here. Final position will be updated in `draw` - hitboxes[i] = { - left: 0, - top: 0, - width: width, - height: fontSize - }; + labels: { + boxWidth: 40, + padding: 10, + // Generates labels shown in the legend + // Valid properties to return: + // text : text to display + // fillStyle : fill of coloured box + // strokeStyle: stroke of coloured box + // hidden : if this legend item refers to a hidden item + // lineCap : cap style for line + // lineDash + // lineDashOffset : + // lineJoin : + // lineWidth : + generateLabels: function(chart) { + var data = chart.data; + return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { + return { + text: dataset.label, + fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), + hidden: !chart.isDatasetVisible(i), + lineCap: dataset.borderCapStyle, + lineDash: dataset.borderDash, + lineDashOffset: dataset.borderDashOffset, + lineJoin: dataset.borderJoinStyle, + lineWidth: dataset.borderWidth, + strokeStyle: dataset.borderColor, + pointStyle: dataset.pointStyle, - lineWidths[lineWidths.length - 1] += width + labelOpts.padding; - }); + // Below is extra data used for toggling the datasets + datasetIndex: i + }; + }, this) : []; + } + } + }; - minSize.height += totalHeight; + /** + * Helper function to get the box width based on the usePointStyle option + * @param labelopts {Object} the label options on the legend + * @param fontSize {Number} the label font size + * @return {Number} width of the color box area + */ + function getBoxWidth(labelOpts, fontSize) { + return labelOpts.usePointStyle ? + fontSize * Math.SQRT2 : + labelOpts.boxWidth; + } - } else { - var vPadding = labelOpts.padding; - var columnWidths = me.columnWidths = []; - var totalWidth = labelOpts.padding; - var currentColWidth = 0; - var currentColHeight = 0; - var itemHeight = fontSize + vPadding; + Chart.Legend = Chart.Element.extend({ - helpers.each(me.legendItems, function(legendItem, i) { - // If usePointStyle is set, multiple boxWidth by 2 since it represents - // the radius and not truly the width - var boxWidth = labelOpts.usePointStyle ? 2 * labelOpts.boxWidth : labelOpts.boxWidth; + initialize: function(config) { + helpers.extend(this, config); - var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + // Contains hit boxes for each dataset (in dataset order) + this.legendHitBoxes = []; - // If too tall, go to new column - if (currentColHeight + itemHeight > minSize.height) { - totalWidth += currentColWidth + labelOpts.padding; - columnWidths.push(currentColWidth); // previous column width + // Are we in doughnut mode which has a different data type + this.doughnutMode = false; + }, - currentColWidth = 0; - currentColHeight = 0; - } + // These methods are ordered by lifecycle. Utilities then follow. + // Any function defined here is inherited by all legend types. + // Any function can be extended by the legend type - // Get max width - currentColWidth = Math.max(currentColWidth, itemWidth); - currentColHeight += itemHeight; + beforeUpdate: noop, + update: function(maxWidth, maxHeight, margins) { + var me = this; - // Store the hitbox width and height here. Final position will be updated in `draw` - hitboxes[i] = { - left: 0, - top: 0, - width: itemWidth, - height: fontSize - }; - }); + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); - totalWidth += currentColWidth; - columnWidths.push(currentColWidth); - minSize.width += totalWidth; - } - } + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; - me.width = minSize.width; - me.height = minSize.height; - }, - afterFit: noop, + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); - // Shared Methods - isHorizontal: function() { - return this.options.position === "top" || this.options.position === "bottom"; - }, + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); - // Actualy draw the legend on the canvas - draw: function() { - var me = this; - var opts = me.options; - var labelOpts = opts.labels; - var globalDefault = Chart.defaults.global, - lineDefault = globalDefault.elements.line, - legendWidth = me.width, - lineWidths = me.lineWidths; + return me.minSize; + }, + afterUpdate: noop, - if (opts.display) { - var ctx = me.ctx, - cursor, - itemOrDefault = helpers.getValueOrDefault, - fontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor), - fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize), - fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), - fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily), - labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + // - // Canvas setup - ctx.textAlign = "left"; - ctx.textBaseline = 'top'; - ctx.lineWidth = 0.5; - ctx.strokeStyle = fontColor; // for strikethrough effect - ctx.fillStyle = fontColor; // render in correct colour - ctx.font = labelFont; + beforeSetDimensions: noop, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; - var boxWidth = labelOpts.boxWidth, - hitboxes = me.legendHitBoxes; + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } - // current position - var drawLegendBox = function(x, y, legendItem) { - if (isNaN(boxWidth) || boxWidth <= 0) { - return; - } + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; - // Set the ctx for the box - ctx.save(); + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop, - ctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor); - ctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); - ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); - ctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); - ctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth); - ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); + // - if (ctx.setLineDash) { - // IE 9 and 10 do not support line dash - ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash)); - } + beforeBuildLabels: noop, + buildLabels: function() { + var me = this; + me.legendItems = me.options.labels.generateLabels.call(me, me.chart); + if (me.options.reverse) { + me.legendItems.reverse(); + } + }, + afterBuildLabels: noop, - if (opts.labels && opts.labels.usePointStyle) { - // Recalulate x and y for drawPoint() because its expecting - // x and y to be center of figure (instead of top left) - var radius = fontSize * Math.SQRT2 / 2; - var offSet = radius / Math.SQRT2; - var centerX = x + offSet; - var centerY = y + offSet; + // - // Draw pointStyle as legend symbol - Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); - } - else { - // Draw box as legend symbol - ctx.strokeRect(x, y, boxWidth, fontSize); - ctx.fillRect(x, y, boxWidth, fontSize); - } + beforeFit: noop, + fit: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var display = opts.display; - ctx.restore(); - }; - var fillText = function(x, y, legendItem, textWidth) { - ctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y); + var ctx = me.ctx; - if (legendItem.hidden) { - // Strikethrough the text if hidden - ctx.beginPath(); - ctx.lineWidth = 2; - ctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2)); - ctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2)); - ctx.stroke(); - } - }; + var globalDefault = Chart.defaults.global, + itemOrDefault = helpers.getValueOrDefault, + fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize), + fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), + fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily), + labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); - // Horizontal - var isHorizontal = me.isHorizontal(); - if (isHorizontal) { - cursor = { - x: me.left + ((legendWidth - lineWidths[0]) / 2), - y: me.top + labelOpts.padding, - line: 0 - }; - } else { - cursor = { - x: me.left + labelOpts.padding, - y: me.top + labelOpts.padding, - line: 0 - }; - } + // Reset hit boxes + var hitboxes = me.legendHitBoxes = []; - var itemHeight = fontSize + labelOpts.padding; - helpers.each(me.legendItems, function(legendItem, i) { - var textWidth = ctx.measureText(legendItem.text).width, - width = labelOpts.usePointStyle ? - fontSize + (fontSize / 2) + textWidth : - boxWidth + (fontSize / 2) + textWidth, - x = cursor.x, - y = cursor.y; + var minSize = me.minSize; + var isHorizontal = me.isHorizontal(); - if (isHorizontal) { - if (x + width >= legendWidth) { - y = cursor.y += itemHeight; - cursor.line++; - x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); - } - } else { - if (y + itemHeight > me.bottom) { - x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; - y = cursor.y = me.top; - cursor.line++; - } - } + if (isHorizontal) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = display ? 10 : 0; + } else { + minSize.width = display ? 10 : 0; + minSize.height = me.maxHeight; // fill all the height + } - drawLegendBox(x, y, legendItem); + // Increase sizes here + if (display) { + ctx.font = labelFont; - hitboxes[i].left = x; - hitboxes[i].top = y; + if (isHorizontal) { + // Labels - // Fill the actual label - fillText(x, y, legendItem, textWidth); + // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one + var lineWidths = me.lineWidths = [0]; + var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; - if (isHorizontal) { - cursor.x += width + (labelOpts.padding); - } else { - cursor.y += itemHeight; - } + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; - }); - } - }, + helpers.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; - // Handle an event - handleEvent: function(e) { - var me = this; - var position = helpers.getRelativePosition(e, me.chart.chart), - x = position.x, - y = position.y, - opts = me.options; + if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { + totalHeight += fontSize + (labelOpts.padding); + lineWidths[lineWidths.length] = me.left; + } - if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { - // See if we are touching one of the dataset boxes - var lh = me.legendHitBoxes; - for (var i = 0; i < lh.length; ++i) { - var hitBox = lh[i]; + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: width, + height: fontSize + }; - if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { - // Touching an element - if (opts.onClick) { - opts.onClick.call(me, e, me.legendItems[i]); - } - break; - } - } - } - } - }); + lineWidths[lineWidths.length - 1] += width + labelOpts.padding; + }); - // Register the legend plugin - Chart.plugins.register({ - beforeInit: function(chartInstance) { - var opts = chartInstance.options; - var legendOpts = opts.legend; + minSize.height += totalHeight; - if (legendOpts) { - chartInstance.legend = new Chart.Legend({ - ctx: chartInstance.chart.ctx, - options: legendOpts, - chart: chartInstance - }); + } else { + var vPadding = labelOpts.padding; + var columnWidths = me.columnWidths = []; + var totalWidth = labelOpts.padding; + var currentColWidth = 0; + var currentColHeight = 0; + var itemHeight = fontSize + vPadding; - Chart.layoutService.addBox(chartInstance, chartInstance.legend); - } - } - }); -}; + helpers.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; -},{}],30:[function(require,module,exports){ -"use strict"; + // If too tall, go to new column + if (currentColHeight + itemHeight > minSize.height) { + totalWidth += currentColWidth + labelOpts.padding; + columnWidths.push(currentColWidth); // previous column width -module.exports = function(Chart) { + currentColWidth = 0; + currentColHeight = 0; + } - var noop = Chart.helpers.noop; + // Get max width + currentColWidth = Math.max(currentColWidth, itemWidth); + currentColHeight += itemHeight; - /** - * The plugin service singleton - * @namespace Chart.plugins - * @since 2.1.0 - */ - Chart.plugins = { - _plugins: [], + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: itemWidth, + height: fontSize + }; + }); - /** - * Registers the given plugin(s) if not already registered. - * @param {Array|Object} plugins plugin instance(s). - */ - register: function(plugins) { - var p = this._plugins; - ([]).concat(plugins).forEach(function(plugin) { - if (p.indexOf(plugin) === -1) { - p.push(plugin); - } - }); - }, + totalWidth += currentColWidth; + columnWidths.push(currentColWidth); + minSize.width += totalWidth; + } + } - /** - * Unregisters the given plugin(s) only if registered. - * @param {Array|Object} plugins plugin instance(s). - */ - unregister: function(plugins) { - var p = this._plugins; - ([]).concat(plugins).forEach(function(plugin) { - var idx = p.indexOf(plugin); - if (idx !== -1) { - p.splice(idx, 1); - } - }); - }, + me.width = minSize.width; + me.height = minSize.height; + }, + afterFit: noop, - /** - * Remove all registered p^lugins. - * @since 2.1.5 - */ - clear: function() { - this._plugins = []; - }, + // Shared Methods + isHorizontal: function() { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, - /** - * Returns the number of registered plugins? - * @returns {Number} - * @since 2.1.5 - */ - count: function() { - return this._plugins.length; - }, + // Actually draw the legend on the canvas + draw: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var globalDefault = Chart.defaults.global, + lineDefault = globalDefault.elements.line, + legendWidth = me.width, + lineWidths = me.lineWidths; - /** - * Returns all registered plugin intances. - * @returns {Array} array of plugin objects. - * @since 2.1.5 - */ - getAll: function() { - return this._plugins; - }, + if (opts.display) { + var ctx = me.ctx, + cursor, + itemOrDefault = helpers.getValueOrDefault, + fontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor), + fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize), + fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), + fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily), + labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); - /** - * Calls registered plugins on the specified extension, with the given args. This - * method immediately returns as soon as a plugin explicitly returns false. The - * returned value can be used, for instance, to interrupt the current action. - * @param {String} extension the name of the plugin method to call (e.g. 'beforeUpdate'). - * @param {Array} [args] extra arguments to apply to the extension call. - * @returns {Boolean} false if any of the plugins return false, else returns true. - */ - notify: function(extension, args) { - var plugins = this._plugins; - var ilen = plugins.length; - var i, plugin; + // Canvas setup + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + ctx.lineWidth = 0.5; + ctx.strokeStyle = fontColor; // for strikethrough effect + ctx.fillStyle = fontColor; // render in correct colour + ctx.font = labelFont; - for (i=0; i<ilen; ++i) { - plugin = plugins[i]; - if (typeof plugin[extension] === 'function') { - if (plugin[extension].apply(plugin, args || []) === false) { - return false; - } - } - } + var boxWidth = getBoxWidth(labelOpts, fontSize), + hitboxes = me.legendHitBoxes; - return true; - } - }; + // current position + var drawLegendBox = function(x, y, legendItem) { + if (isNaN(boxWidth) || boxWidth <= 0) { + return; + } - /** - * Plugin extension methods. - * @interface Chart.PluginBase - * @since 2.1.0 - */ - Chart.PluginBase = Chart.Element.extend({ - // Called at start of chart init - beforeInit: noop, + // Set the ctx for the box + ctx.save(); - // Called at end of chart init - afterInit: noop, + ctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor); + ctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); + ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); + ctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); + ctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth); + ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); + var isLineWidthZero = (itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); - // Called at start of update - beforeUpdate: noop, + if (ctx.setLineDash) { + // IE 9 and 10 do not support line dash + ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash)); + } - // Called at end of update - afterUpdate: noop, + if (opts.labels && opts.labels.usePointStyle) { + // Recalculate x and y for drawPoint() because its expecting + // x and y to be center of figure (instead of top left) + var radius = fontSize * Math.SQRT2 / 2; + var offSet = radius / Math.SQRT2; + var centerX = x + offSet; + var centerY = y + offSet; - // Called at start of draw - beforeDraw: noop, + // Draw pointStyle as legend symbol + Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); + } else { + // Draw box as legend symbol + if (!isLineWidthZero) { + ctx.strokeRect(x, y, boxWidth, fontSize); + } + ctx.fillRect(x, y, boxWidth, fontSize); + } - // Called at end of draw - afterDraw: noop, + ctx.restore(); + }; + var fillText = function(x, y, legendItem, textWidth) { + ctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y); - // Called during destroy - destroy: noop - }); + if (legendItem.hidden) { + // Strikethrough the text if hidden + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2)); + ctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2)); + ctx.stroke(); + } + }; - /** - * Provided for backward compatibility, use Chart.plugins instead - * @namespace Chart.pluginService - * @deprecated since version 2.1.5 - * @todo remove me at version 3 - */ - Chart.pluginService = Chart.plugins; -}; + // Horizontal + var isHorizontal = me.isHorizontal(); + if (isHorizontal) { + cursor = { + x: me.left + ((legendWidth - lineWidths[0]) / 2), + y: me.top + labelOpts.padding, + line: 0 + }; + } else { + cursor = { + x: me.left + labelOpts.padding, + y: me.top + labelOpts.padding, + line: 0 + }; + } + var itemHeight = fontSize + labelOpts.padding; + helpers.each(me.legendItems, function(legendItem, i) { + var textWidth = ctx.measureText(legendItem.text).width, + width = boxWidth + (fontSize / 2) + textWidth, + x = cursor.x, + y = cursor.y; + + if (isHorizontal) { + if (x + width >= legendWidth) { + y = cursor.y += itemHeight; + cursor.line++; + x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); + } + } else if (y + itemHeight > me.bottom) { + x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; + y = cursor.y = me.top; + cursor.line++; + } + + drawLegendBox(x, y, legendItem); + + hitboxes[i].left = x; + hitboxes[i].top = y; + + // Fill the actual label + fillText(x, y, legendItem, textWidth); + + if (isHorizontal) { + cursor.x += width + (labelOpts.padding); + } else { + cursor.y += itemHeight; + } + + }); + } + }, + + /** + * Handle an event + * @private + * @param e {Event} the event to handle + * @return {Boolean} true if a change occured + */ + handleEvent: function(e) { + var me = this; + var opts = me.options; + var type = e.type === 'mouseup' ? 'click' : e.type; + var changed = false; + + if (type === 'mousemove') { + if (!opts.onHover) { + return; + } + } else if (type === 'click') { + if (!opts.onClick) { + return; + } + } else { + return; + } + + var position = helpers.getRelativePosition(e, me.chart.chart), + x = position.x, + y = position.y; + + if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { + // See if we are touching one of the dataset boxes + var lh = me.legendHitBoxes; + for (var i = 0; i < lh.length; ++i) { + var hitBox = lh[i]; + + if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { + // Touching an element + if (type === 'click') { + opts.onClick.call(me, e, me.legendItems[i]); + changed = true; + break; + } else if (type === 'mousemove') { + opts.onHover.call(me, e, me.legendItems[i]); + changed = true; + break; + } + } + } + } + + return changed; + } + }); + + // Register the legend plugin + Chart.plugins.register({ + beforeInit: function(chartInstance) { + var opts = chartInstance.options; + var legendOpts = opts.legend; + + if (legendOpts) { + chartInstance.legend = new Chart.Legend({ + ctx: chartInstance.chart.ctx, + options: legendOpts, + chart: chartInstance + }); + + Chart.layoutService.addBox(chartInstance, chartInstance.legend); + } + } + }); + }; + },{}],31:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - var helpers = Chart.helpers; + var noop = Chart.helpers.noop; - Chart.defaults.scale = { - display: true, - position: "left", + /** + * The plugin service singleton + * @namespace Chart.plugins + * @since 2.1.0 + */ + Chart.plugins = { + _plugins: [], - // grid line settings - gridLines: { - display: true, - color: "rgba(0, 0, 0, 0.1)", - lineWidth: 1, - drawBorder: true, - drawOnChartArea: true, - drawTicks: true, - tickMarkLength: 10, - zeroLineWidth: 1, - zeroLineColor: "rgba(0,0,0,0.25)", - offsetGridLines: false - }, + /** + * Registers the given plugin(s) if not already registered. + * @param {Array|Object} plugins plugin instance(s). + */ + register: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + if (p.indexOf(plugin) === -1) { + p.push(plugin); + } + }); + }, - // scale label - scaleLabel: { - // actual label - labelString: '', + /** + * Unregisters the given plugin(s) only if registered. + * @param {Array|Object} plugins plugin instance(s). + */ + unregister: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + var idx = p.indexOf(plugin); + if (idx !== -1) { + p.splice(idx, 1); + } + }); + }, - // display property - display: false - }, + /** + * Remove all registered plugins. + * @since 2.1.5 + */ + clear: function() { + this._plugins = []; + }, - // label settings - ticks: { - beginAtZero: false, - minRotation: 0, - maxRotation: 50, - mirror: false, - padding: 10, - reverse: false, - display: true, - autoSkip: true, - autoSkipPadding: 0, - labelOffset: 0, - // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. - callback: function(value) { - return helpers.isArray(value) ? value : '' + value; - } - } - }; + /** + * Returns the number of registered plugins? + * @returns {Number} + * @since 2.1.5 + */ + count: function() { + return this._plugins.length; + }, - Chart.Scale = Chart.Element.extend({ + /** + * Returns all registered plugin instances. + * @returns {Array} array of plugin objects. + * @since 2.1.5 + */ + getAll: function() { + return this._plugins; + }, - // These methods are ordered by lifecyle. Utilities then follow. - // Any function defined here is inherited by all scale types. - // Any function can be extended by the scale type + /** + * Calls registered plugins on the specified extension, with the given args. This + * method immediately returns as soon as a plugin explicitly returns false. The + * returned value can be used, for instance, to interrupt the current action. + * @param {String} extension the name of the plugin method to call (e.g. 'beforeUpdate'). + * @param {Array} [args] extra arguments to apply to the extension call. + * @returns {Boolean} false if any of the plugins return false, else returns true. + */ + notify: function(extension, args) { + var plugins = this._plugins; + var ilen = plugins.length; + var i, plugin; - beforeUpdate: function() { - helpers.callCallback(this.options.beforeUpdate, [this]); - }, - update: function(maxWidth, maxHeight, margins) { - var me = this; + for (i=0; i<ilen; ++i) { + plugin = plugins[i]; + if (typeof plugin[extension] === 'function') { + if (plugin[extension].apply(plugin, args || []) === false) { + return false; + } + } + } - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); + return true; + } + }; - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me.margins = helpers.extend({ - left: 0, - right: 0, - top: 0, - bottom: 0 - }, margins); + /** + * Plugin extension methods. + * @interface Chart.PluginBase + * @since 2.1.0 + */ + Chart.PluginBase = Chart.Element.extend({ + // Called at start of chart init + beforeInit: noop, - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); + // Called at end of chart init + afterInit: noop, - // Data min/max - me.beforeDataLimits(); - me.determineDataLimits(); - me.afterDataLimits(); + // Called at start of update + beforeUpdate: noop, - // Ticks - me.beforeBuildTicks(); - me.buildTicks(); - me.afterBuildTicks(); + // Called at end of update + afterUpdate: noop, - me.beforeTickToLabelConversion(); - me.convertTicksToLabels(); - me.afterTickToLabelConversion(); + // Called at start of draw + beforeDraw: noop, - // Tick Rotation - me.beforeCalculateTickRotation(); - me.calculateTickRotation(); - me.afterCalculateTickRotation(); - // Fit - me.beforeFit(); - me.fit(); - me.afterFit(); - // - me.afterUpdate(); + // Called at end of draw + afterDraw: noop, - return me.minSize; + // Called during destroy + destroy: noop + }); - }, - afterUpdate: function() { - helpers.callCallback(this.options.afterUpdate, [this]); - }, + /** + * Provided for backward compatibility, use Chart.plugins instead + * @namespace Chart.pluginService + * @deprecated since version 2.1.5 + * @todo remove me at version 3 + */ + Chart.pluginService = Chart.plugins; + }; - // +},{}],32:[function(require,module,exports){ + 'use strict'; - beforeSetDimensions: function() { - helpers.callCallback(this.options.beforeSetDimensions, [this]); - }, - setDimensions: function() { - var me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; + module.exports = function(Chart) { - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } + var helpers = Chart.helpers; - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; - }, - afterSetDimensions: function() { - helpers.callCallback(this.options.afterSetDimensions, [this]); - }, + Chart.defaults.scale = { + display: true, + position: 'left', - // Data limits - beforeDataLimits: function() { - helpers.callCallback(this.options.beforeDataLimits, [this]); - }, - determineDataLimits: helpers.noop, - afterDataLimits: function() { - helpers.callCallback(this.options.afterDataLimits, [this]); - }, + // grid line settings + gridLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1, + drawBorder: true, + drawOnChartArea: true, + drawTicks: true, + tickMarkLength: 10, + zeroLineWidth: 1, + zeroLineColor: 'rgba(0,0,0,0.25)', + offsetGridLines: false, + borderDash: [], + borderDashOffset: 0.0 + }, - // - beforeBuildTicks: function() { - helpers.callCallback(this.options.beforeBuildTicks, [this]); - }, - buildTicks: helpers.noop, - afterBuildTicks: function() { - helpers.callCallback(this.options.afterBuildTicks, [this]); - }, + // scale label + scaleLabel: { + // actual label + labelString: '', - beforeTickToLabelConversion: function() { - helpers.callCallback(this.options.beforeTickToLabelConversion, [this]); - }, - convertTicksToLabels: function() { - var me = this; - // Convert ticks to strings - me.ticks = me.ticks.map(function(numericalTick, index, ticks) { - if (me.options.ticks.userCallback) { - return me.options.ticks.userCallback(numericalTick, index, ticks); - } - return me.options.ticks.callback(numericalTick, index, ticks); - }, - me); - }, - afterTickToLabelConversion: function() { - helpers.callCallback(this.options.afterTickToLabelConversion, [this]); - }, + // display property + display: false + }, - // + // label settings + ticks: { + beginAtZero: false, + minRotation: 0, + maxRotation: 50, + mirror: false, + padding: 10, + reverse: false, + display: true, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0, + // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. + callback: Chart.Ticks.formatters.values + } + }; - beforeCalculateTickRotation: function() { - helpers.callCallback(this.options.beforeCalculateTickRotation, [this]); - }, - calculateTickRotation: function() { - var me = this; - var context = me.ctx; - var globalDefaults = Chart.defaults.global; - var optionTicks = me.options.ticks; + Chart.Scale = Chart.Element.extend({ - //Get the width of each grid by calculating the difference - //between x offsets between 0 and 1. - var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize); - var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle); - var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily); - var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); - context.font = tickLabelFont; + // These methods are ordered by lifecycle. Utilities then follow. + // Any function defined here is inherited by all scale types. + // Any function can be extended by the scale type - var firstWidth = context.measureText(me.ticks[0]).width; - var lastWidth = context.measureText(me.ticks[me.ticks.length - 1]).width; - var firstRotated; + beforeUpdate: function() { + helpers.callCallback(this.options.beforeUpdate, [this]); + }, + update: function(maxWidth, maxHeight, margins) { + var me = this; - me.labelRotation = optionTicks.minRotation || 0; - me.paddingRight = 0; - me.paddingLeft = 0; + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); - if (me.options.display) { - if (me.isHorizontal()) { - me.paddingRight = lastWidth / 2 + 3; - me.paddingLeft = firstWidth / 2 + 3; + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = helpers.extend({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, margins); - if (!me.longestTextCache) { - me.longestTextCache = {}; - } - var originalLabelWidth = helpers.longestText(context, tickLabelFont, me.ticks, me.longestTextCache); - var labelWidth = originalLabelWidth; - var cosRotation; - var sinRotation; + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); - // Allow 3 pixels x2 padding either side for label readability - // only the index matters for a dataset scale, but we want a consistent interface between scales - var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; + // Data min/max + me.beforeDataLimits(); + me.determineDataLimits(); + me.afterDataLimits(); - //Max label rotation can be set or default to 90 - also act as a loop counter - while (labelWidth > tickWidth && me.labelRotation < optionTicks.maxRotation) { - cosRotation = Math.cos(helpers.toRadians(me.labelRotation)); - sinRotation = Math.sin(helpers.toRadians(me.labelRotation)); + // Ticks + me.beforeBuildTicks(); + me.buildTicks(); + me.afterBuildTicks(); - firstRotated = cosRotation * firstWidth; + me.beforeTickToLabelConversion(); + me.convertTicksToLabels(); + me.afterTickToLabelConversion(); - // We're right aligning the text now. - if (firstRotated + tickFontSize / 2 > me.yLabelWidth) { - me.paddingLeft = firstRotated + tickFontSize / 2; - } + // Tick Rotation + me.beforeCalculateTickRotation(); + me.calculateTickRotation(); + me.afterCalculateTickRotation(); + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); - me.paddingRight = tickFontSize / 2; + return me.minSize; - if (sinRotation * originalLabelWidth > me.maxHeight) { - // go back one step - me.labelRotation--; - break; - } + }, + afterUpdate: function() { + helpers.callCallback(this.options.afterUpdate, [this]); + }, - me.labelRotation++; - labelWidth = cosRotation * originalLabelWidth; - } - } - } + // - if (me.margins) { - me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); - me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); - } - }, - afterCalculateTickRotation: function() { - helpers.callCallback(this.options.afterCalculateTickRotation, [this]); - }, + beforeSetDimensions: function() { + helpers.callCallback(this.options.beforeSetDimensions, [this]); + }, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; - // + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } - beforeFit: function() { - helpers.callCallback(this.options.beforeFit, [this]); - }, - fit: function() { - var me = this; - // Reset - var minSize = me.minSize = { - width: 0, - height: 0 - }; + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + }, + afterSetDimensions: function() { + helpers.callCallback(this.options.afterSetDimensions, [this]); + }, - var opts = me.options; - var globalDefaults = Chart.defaults.global; - var tickOpts = opts.ticks; - var scaleLabelOpts = opts.scaleLabel; - var display = opts.display; - var isHorizontal = me.isHorizontal(); + // Data limits + beforeDataLimits: function() { + helpers.callCallback(this.options.beforeDataLimits, [this]); + }, + determineDataLimits: helpers.noop, + afterDataLimits: function() { + helpers.callCallback(this.options.afterDataLimits, [this]); + }, - var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); - var tickFontStyle = helpers.getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); - var tickFontFamily = helpers.getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); - var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + // + beforeBuildTicks: function() { + helpers.callCallback(this.options.beforeBuildTicks, [this]); + }, + buildTicks: helpers.noop, + afterBuildTicks: function() { + helpers.callCallback(this.options.afterBuildTicks, [this]); + }, - var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize); + beforeTickToLabelConversion: function() { + helpers.callCallback(this.options.beforeTickToLabelConversion, [this]); + }, + convertTicksToLabels: function() { + var me = this; + // Convert ticks to strings + var tickOpts = me.options.ticks; + me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback); + }, + afterTickToLabelConversion: function() { + helpers.callCallback(this.options.afterTickToLabelConversion, [this]); + }, - var tickMarkLength = opts.gridLines.tickMarkLength; + // - // Width - if (isHorizontal) { - // subtract the margins to line up with the chartArea if we are a full width scale - minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; - } else { - minSize.width = display ? tickMarkLength : 0; - } + beforeCalculateTickRotation: function() { + helpers.callCallback(this.options.beforeCalculateTickRotation, [this]); + }, + calculateTickRotation: function() { + var me = this; + var context = me.ctx; + var globalDefaults = Chart.defaults.global; + var optionTicks = me.options.ticks; - // height - if (isHorizontal) { - minSize.height = display ? tickMarkLength : 0; - } else { - minSize.height = me.maxHeight; // fill all the height - } + // Get the width of each grid by calculating the difference + // between x offsets between 0 and 1. + var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize); + var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle); + var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + context.font = tickLabelFont; - // Are we showing a title for the scale? - if (scaleLabelOpts.display && display) { - if (isHorizontal) { - minSize.height += (scaleLabelFontSize * 1.5); - } else { - minSize.width += (scaleLabelFontSize * 1.5); - } - } + var firstWidth = context.measureText(me.ticks[0]).width; + var lastWidth = context.measureText(me.ticks[me.ticks.length - 1]).width; + var firstRotated; - if (tickOpts.display && display) { - // Don't bother fitting the ticks if we are not showing them - if (!me.longestTextCache) { - me.longestTextCache = {}; - } + me.labelRotation = optionTicks.minRotation || 0; + me.paddingRight = 0; + me.paddingLeft = 0; - var largestTextWidth = helpers.longestText(me.ctx, tickLabelFont, me.ticks, me.longestTextCache); - var tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks); - var lineSpace = tickFontSize * 0.5; + if (me.options.display) { + if (me.isHorizontal()) { + me.paddingRight = lastWidth / 2 + 3; + me.paddingLeft = firstWidth / 2 + 3; - if (isHorizontal) { - // A horizontal axis is more constrained by the height. - me.longestLabelWidth = largestTextWidth; + if (!me.longestTextCache) { + me.longestTextCache = {}; + } + var originalLabelWidth = helpers.longestText(context, tickLabelFont, me.ticks, me.longestTextCache); + var labelWidth = originalLabelWidth; + var cosRotation; + var sinRotation; - // TODO - improve this calculation - var labelHeight = (Math.sin(helpers.toRadians(me.labelRotation)) * me.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines); + // Allow 3 pixels x2 padding either side for label readability + // only the index matters for a dataset scale, but we want a consistent interface between scales + var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; - minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight); - me.ctx.font = tickLabelFont; + // Max label rotation can be set or default to 90 - also act as a loop counter + while (labelWidth > tickWidth && me.labelRotation < optionTicks.maxRotation) { + cosRotation = Math.cos(helpers.toRadians(me.labelRotation)); + sinRotation = Math.sin(helpers.toRadians(me.labelRotation)); - var firstLabelWidth = me.ctx.measureText(me.ticks[0]).width; - var lastLabelWidth = me.ctx.measureText(me.ticks[me.ticks.length - 1]).width; + firstRotated = cosRotation * firstWidth; - // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated - // by the font height - var cosRotation = Math.cos(helpers.toRadians(me.labelRotation)); - var sinRotation = Math.sin(helpers.toRadians(me.labelRotation)); - me.paddingLeft = me.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges - me.paddingRight = me.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated - } else { - // A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first - var maxLabelWidth = me.maxWidth - minSize.width; + // We're right aligning the text now. + if (firstRotated + tickFontSize / 2 > me.yLabelWidth) { + me.paddingLeft = firstRotated + tickFontSize / 2; + } - // Account for padding - var mirror = tickOpts.mirror; - if (!mirror) { - largestTextWidth += me.options.ticks.padding; - } else { - // If mirrored text is on the inside so don't expand - largestTextWidth = 0; - } + me.paddingRight = tickFontSize / 2; - if (largestTextWidth < maxLabelWidth) { - // We don't need all the room - minSize.width += largestTextWidth; - } else { - // Expand to max size - minSize.width = me.maxWidth; - } + if (sinRotation * originalLabelWidth > me.maxHeight) { + // go back one step + me.labelRotation--; + break; + } - me.paddingTop = tickFontSize / 2; - me.paddingBottom = tickFontSize / 2; - } - } + me.labelRotation++; + labelWidth = cosRotation * originalLabelWidth; + } + } + } - if (me.margins) { - me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); - me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); - me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); - me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); - } + if (me.margins) { + me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); + me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); + } + }, + afterCalculateTickRotation: function() { + helpers.callCallback(this.options.afterCalculateTickRotation, [this]); + }, - me.width = minSize.width; - me.height = minSize.height; + // - }, - afterFit: function() { - helpers.callCallback(this.options.afterFit, [this]); - }, + beforeFit: function() { + helpers.callCallback(this.options.beforeFit, [this]); + }, + fit: function() { + var me = this; + // Reset + var minSize = me.minSize = { + width: 0, + height: 0 + }; - // Shared Methods - isHorizontal: function() { - return this.options.position === "top" || this.options.position === "bottom"; - }, - isFullWidth: function() { - return (this.options.fullWidth); - }, + var opts = me.options; + var globalDefaults = Chart.defaults.global; + var tickOpts = opts.ticks; + var scaleLabelOpts = opts.scaleLabel; + var gridLineOpts = opts.gridLines; + var display = opts.display; + var isHorizontal = me.isHorizontal(); - // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not - getRightValue: function(rawValue) { - // Null and undefined values first - if (rawValue === null || typeof(rawValue) === 'undefined') { - return NaN; - } - // isNaN(object) returns true, so make sure NaN is checking for a number - if (typeof(rawValue) === 'number' && isNaN(rawValue)) { - return NaN; - } - // If it is in fact an object, dive in one more level - if (typeof(rawValue) === "object") { - if ((rawValue instanceof Date) || (rawValue.isValid)) { - return rawValue; - } else { - return this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y); - } - } + var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + var tickFontStyle = helpers.getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); + var tickFontFamily = helpers.getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); - // Value is good, return it - return rawValue; - }, + var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize); - // Used to get the value to display in the tooltip for the data at the given index - // function getLabelForIndex(index, datasetIndex) - getLabelForIndex: helpers.noop, + var tickMarkLength = opts.gridLines.tickMarkLength; - // Used to get data value locations. Value can either be an index or a numerical value - getPixelForValue: helpers.noop, + // Width + if (isHorizontal) { + // subtract the margins to line up with the chartArea if we are a full width scale + minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; + } else { + minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0; + } - // Used to get the data value from a given pixel. This is the inverse of getPixelForValue - getValueForPixel: helpers.noop, + // height + if (isHorizontal) { + minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0; + } else { + minSize.height = me.maxHeight; // fill all the height + } - // Used for tick location, should - getPixelForTick: function(index, includeOffset) { - var me = this; - if (me.isHorizontal()) { - var innerWidth = me.width - (me.paddingLeft + me.paddingRight); - var tickWidth = innerWidth / Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); - var pixel = (tickWidth * index) + me.paddingLeft; + // Are we showing a title for the scale? + if (scaleLabelOpts.display && display) { + if (isHorizontal) { + minSize.height += (scaleLabelFontSize * 1.5); + } else { + minSize.width += (scaleLabelFontSize * 1.5); + } + } - if (includeOffset) { - pixel += tickWidth / 2; - } + if (tickOpts.display && display) { + // Don't bother fitting the ticks if we are not showing them + if (!me.longestTextCache) { + me.longestTextCache = {}; + } - var finalVal = me.left + Math.round(pixel); - finalVal += me.isFullWidth() ? me.margins.left : 0; - return finalVal; - } else { - var innerHeight = me.height - (me.paddingTop + me.paddingBottom); - return me.top + (index * (innerHeight / (me.ticks.length - 1))); - } - }, + var largestTextWidth = helpers.longestText(me.ctx, tickLabelFont, me.ticks, me.longestTextCache); + var tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks); + var lineSpace = tickFontSize * 0.5; - // Utility for getting the pixel location of a percentage of scale - getPixelForDecimal: function(decimal /*, includeOffset*/ ) { - var me = this; - if (me.isHorizontal()) { - var innerWidth = me.width - (me.paddingLeft + me.paddingRight); - var valueOffset = (innerWidth * decimal) + me.paddingLeft; + if (isHorizontal) { + // A horizontal axis is more constrained by the height. + me.longestLabelWidth = largestTextWidth; - var finalVal = me.left + Math.round(valueOffset); - finalVal += me.isFullWidth() ? me.margins.left : 0; - return finalVal; - } else { - return me.top + (decimal * me.height); - } - }, + // TODO - improve this calculation + var labelHeight = (Math.sin(helpers.toRadians(me.labelRotation)) * me.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines); - getBasePixel: function() { - var me = this; - var min = me.min; - var max = me.max; + minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight); + me.ctx.font = tickLabelFont; - return me.getPixelForValue( - me.beginAtZero? 0: - min < 0 && max < 0? max : - min > 0 && max > 0? min : - 0); - }, + var firstLabelWidth = me.ctx.measureText(me.ticks[0]).width; + var lastLabelWidth = me.ctx.measureText(me.ticks[me.ticks.length - 1]).width; - // Actualy draw the scale on the canvas - // @param {rectangle} chartArea : the area of the chart to draw full grid lines on - draw: function(chartArea) { - var me = this; - var options = me.options; - if (!options.display) { - return; - } + // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated + // by the font height + var cosRotation = Math.cos(helpers.toRadians(me.labelRotation)); + var sinRotation = Math.sin(helpers.toRadians(me.labelRotation)); + me.paddingLeft = me.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges + me.paddingRight = me.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated + } else { + // A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first + var maxLabelWidth = me.maxWidth - minSize.width; - var context = me.ctx; - var globalDefaults = Chart.defaults.global; - var optionTicks = options.ticks; - var gridLines = options.gridLines; - var scaleLabel = options.scaleLabel; + // Account for padding + var mirror = tickOpts.mirror; + if (!mirror) { + largestTextWidth += me.options.ticks.padding; + } else { + // If mirrored text is on the inside so don't expand + largestTextWidth = 0; + } - var isRotated = me.labelRotation !== 0; - var skipRatio; - var useAutoskipper = optionTicks.autoSkip; - var isHorizontal = me.isHorizontal(); + if (largestTextWidth < maxLabelWidth) { + // We don't need all the room + minSize.width += largestTextWidth; + } else { + // Expand to max size + minSize.width = me.maxWidth; + } - // figure out the maximum number of gridlines to show - var maxTicks; - if (optionTicks.maxTicksLimit) { - maxTicks = optionTicks.maxTicksLimit; - } + me.paddingTop = tickFontSize / 2; + me.paddingBottom = tickFontSize / 2; + } + } - var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); - var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize); - var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle); - var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily); - var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); - var tl = gridLines.tickMarkLength; + if (me.margins) { + me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); + me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); + me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); + me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); + } - var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); - var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabel.fontSize, globalDefaults.defaultFontSize); - var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabel.fontStyle, globalDefaults.defaultFontStyle); - var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabel.fontFamily, globalDefaults.defaultFontFamily); - var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily); + me.width = minSize.width; + me.height = minSize.height; - var labelRotationRadians = helpers.toRadians(me.labelRotation); - var cosRotation = Math.cos(labelRotationRadians); - var longestRotatedLabel = me.longestLabelWidth * cosRotation; + }, + afterFit: function() { + helpers.callCallback(this.options.afterFit, [this]); + }, - // Make sure we draw text in the correct color and font - context.fillStyle = tickFontColor; + // Shared Methods + isHorizontal: function() { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, + isFullWidth: function() { + return (this.options.fullWidth); + }, - var itemsToDraw = []; + // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not + getRightValue: function(rawValue) { + // Null and undefined values first + if (rawValue === null || typeof(rawValue) === 'undefined') { + return NaN; + } + // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values + if (typeof(rawValue) === 'number' && !isFinite(rawValue)) { + return NaN; + } + // If it is in fact an object, dive in one more level + if (typeof(rawValue) === 'object') { + if ((rawValue instanceof Date) || (rawValue.isValid)) { + return rawValue; + } + return this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y); + } - if (isHorizontal) { - skipRatio = false; + // Value is good, return it + return rawValue; + }, - // Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation - // See #2584 - if (isRotated) { - longestRotatedLabel /= 2; - } + // Used to get the value to display in the tooltip for the data at the given index + // function getLabelForIndex(index, datasetIndex) + getLabelForIndex: helpers.noop, - if ((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length > (me.width - (me.paddingLeft + me.paddingRight))) { - skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length) / (me.width - (me.paddingLeft + me.paddingRight))); - } + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: helpers.noop, - // if they defined a max number of optionTicks, - // increase skipRatio until that number is met - if (maxTicks && me.ticks.length > maxTicks) { - while (!skipRatio || me.ticks.length / (skipRatio || 1) > maxTicks) { - if (!skipRatio) { - skipRatio = 1; - } - skipRatio += 1; - } - } + // Used to get the data value from a given pixel. This is the inverse of getPixelForValue + getValueForPixel: helpers.noop, - if (!useAutoskipper) { - skipRatio = false; - } - } + // Used for tick location, should + getPixelForTick: function(index, includeOffset) { + var me = this; + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var tickWidth = innerWidth / Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); + var pixel = (tickWidth * index) + me.paddingLeft; + if (includeOffset) { + pixel += tickWidth / 2; + } - var xTickStart = options.position === "right" ? me.left : me.right - tl; - var xTickEnd = options.position === "right" ? me.left + tl : me.right; - var yTickStart = options.position === "bottom" ? me.top : me.bottom - tl; - var yTickEnd = options.position === "bottom" ? me.top + tl : me.bottom; + var finalVal = me.left + Math.round(pixel); + finalVal += me.isFullWidth() ? me.margins.left : 0; + return finalVal; + } + var innerHeight = me.height - (me.paddingTop + me.paddingBottom); + return me.top + (index * (innerHeight / (me.ticks.length - 1))); + }, - helpers.each(me.ticks, function(label, index) { - // If the callback returned a null or undefined value, do not draw this line - if (label === undefined || label === null) { - return; - } + // Utility for getting the pixel location of a percentage of scale + getPixelForDecimal: function(decimal /* , includeOffset*/) { + var me = this; + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var valueOffset = (innerWidth * decimal) + me.paddingLeft; - var isLastTick = me.ticks.length === index + 1; + var finalVal = me.left + Math.round(valueOffset); + finalVal += me.isFullWidth() ? me.margins.left : 0; + return finalVal; + } + return me.top + (decimal * me.height); + }, - // Since we always show the last tick,we need may need to hide the last shown one before - var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= me.ticks.length); - if (shouldSkip && !isLastTick || (label === undefined || label === null)) { - return; - } + getBasePixel: function() { + var me = this; + var min = me.min; + var max = me.max; - var lineWidth, lineColor; - if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) { - // Draw the first index specially - lineWidth = gridLines.zeroLineWidth; - lineColor = gridLines.zeroLineColor; - } else { - lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, index); - lineColor = helpers.getValueAtIndexOrDefault(gridLines.color, index); - } + return me.getPixelForValue( + me.beginAtZero? 0: + min < 0 && max < 0? max : + min > 0 && max > 0? min : + 0); + }, - // Common properties - var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; - var textAlign, textBaseline = 'middle'; + // Actually draw the scale on the canvas + // @param {rectangle} chartArea : the area of the chart to draw full grid lines on + draw: function(chartArea) { + var me = this; + var options = me.options; + if (!options.display) { + return; + } - if (isHorizontal) { - if (!isRotated) { - textBaseline = options.position === 'top' ? 'bottom' : 'top'; - } + var context = me.ctx; + var globalDefaults = Chart.defaults.global; + var optionTicks = options.ticks; + var gridLines = options.gridLines; + var scaleLabel = options.scaleLabel; - textAlign = isRotated ? 'right' : 'center'; + var isRotated = me.labelRotation !== 0; + var skipRatio; + var useAutoskipper = optionTicks.autoSkip; + var isHorizontal = me.isHorizontal(); - var xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines - labelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) - labelY = (isRotated) ? me.top + 12 : options.position === 'top' ? me.bottom - tl : me.top + tl; + // figure out the maximum number of gridlines to show + var maxTicks; + if (optionTicks.maxTicksLimit) { + maxTicks = optionTicks.maxTicksLimit; + } - tx1 = tx2 = x1 = x2 = xLineValue; - ty1 = yTickStart; - ty2 = yTickEnd; - y1 = chartArea.top; - y2 = chartArea.bottom; - } else { - if (options.position === 'left') { - if (optionTicks.mirror) { - labelX = me.right + optionTicks.padding; - textAlign = 'left'; - } else { - labelX = me.right - optionTicks.padding; - textAlign = 'right'; - } - } else { - // right side - if (optionTicks.mirror) { - labelX = me.left - optionTicks.padding; - textAlign = 'right'; - } else { - labelX = me.left + optionTicks.padding; - textAlign = 'left'; - } - } + var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); + var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize); + var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle); + var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + var tl = gridLines.tickMarkLength; + var borderDash = helpers.getValueOrDefault(gridLines.borderDash, globalDefaults.borderDash); + var borderDashOffset = helpers.getValueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset); - var yLineValue = me.getPixelForTick(index); // xvalues for grid lines - yLineValue += helpers.aliasPixel(lineWidth); - labelY = me.getPixelForTick(index, gridLines.offsetGridLines); + var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); + var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabel.fontSize, globalDefaults.defaultFontSize); + var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabel.fontStyle, globalDefaults.defaultFontStyle); + var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabel.fontFamily, globalDefaults.defaultFontFamily); + var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily); - tx1 = xTickStart; - tx2 = xTickEnd; - x1 = chartArea.left; - x2 = chartArea.right; - ty1 = ty2 = y1 = y2 = yLineValue; - } + var labelRotationRadians = helpers.toRadians(me.labelRotation); + var cosRotation = Math.cos(labelRotationRadians); + var longestRotatedLabel = me.longestLabelWidth * cosRotation; - itemsToDraw.push({ - tx1: tx1, - ty1: ty1, - tx2: tx2, - ty2: ty2, - x1: x1, - y1: y1, - x2: x2, - y2: y2, - labelX: labelX, - labelY: labelY, - glWidth: lineWidth, - glColor: lineColor, - rotation: -1 * labelRotationRadians, - label: label, - textBaseline: textBaseline, - textAlign: textAlign - }); - }); + // Make sure we draw text in the correct color and font + context.fillStyle = tickFontColor; - // Draw all of the tick labels, tick marks, and grid lines at the correct places - helpers.each(itemsToDraw, function(itemToDraw) { - if (gridLines.display) { - context.lineWidth = itemToDraw.glWidth; - context.strokeStyle = itemToDraw.glColor; + var itemsToDraw = []; - context.beginPath(); + if (isHorizontal) { + skipRatio = false; - if (gridLines.drawTicks) { - context.moveTo(itemToDraw.tx1, itemToDraw.ty1); - context.lineTo(itemToDraw.tx2, itemToDraw.ty2); - } + // Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation + // See #2584 + if (isRotated) { + longestRotatedLabel /= 2; + } - if (gridLines.drawOnChartArea) { - context.moveTo(itemToDraw.x1, itemToDraw.y1); - context.lineTo(itemToDraw.x2, itemToDraw.y2); - } + if ((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length > (me.width - (me.paddingLeft + me.paddingRight))) { + skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length) / (me.width - (me.paddingLeft + me.paddingRight))); + } - context.stroke(); - } + // if they defined a max number of optionTicks, + // increase skipRatio until that number is met + if (maxTicks && me.ticks.length > maxTicks) { + while (!skipRatio || me.ticks.length / (skipRatio || 1) > maxTicks) { + if (!skipRatio) { + skipRatio = 1; + } + skipRatio += 1; + } + } - if (optionTicks.display) { - context.save(); - context.translate(itemToDraw.labelX, itemToDraw.labelY); - context.rotate(itemToDraw.rotation); - context.font = tickLabelFont; - context.textBaseline = itemToDraw.textBaseline; - context.textAlign = itemToDraw.textAlign; + if (!useAutoskipper) { + skipRatio = false; + } + } - var label = itemToDraw.label; - if (helpers.isArray(label)) { - for (var i = 0, y = 0; i < label.length; ++i) { - // We just make sure the multiline element is a string here.. - context.fillText('' + label[i], 0, y); - // apply same lineSpacing as calculated @ L#320 - y += (tickFontSize * 1.5); - } - } else { - context.fillText(label, 0, 0); - } - context.restore(); - } - }); - if (scaleLabel.display) { - // Draw the scale label - var scaleLabelX; - var scaleLabelY; - var rotation = 0; + var xTickStart = options.position === 'right' ? me.left : me.right - tl; + var xTickEnd = options.position === 'right' ? me.left + tl : me.right; + var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl; + var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom; - if (isHorizontal) { - scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width - scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFontSize / 2) : me.top + (scaleLabelFontSize / 2); - } else { - var isLeft = options.position === 'left'; - scaleLabelX = isLeft ? me.left + (scaleLabelFontSize / 2) : me.right - (scaleLabelFontSize / 2); - scaleLabelY = me.top + ((me.bottom - me.top) / 2); - rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; - } + helpers.each(me.ticks, function(label, index) { + // If the callback returned a null or undefined value, do not draw this line + if (label === undefined || label === null) { + return; + } - context.save(); - context.translate(scaleLabelX, scaleLabelY); - context.rotate(rotation); - context.textAlign = 'center'; - context.textBaseline = 'middle'; - context.fillStyle = scaleLabelFontColor; // render in correct colour - context.font = scaleLabelFont; - context.fillText(scaleLabel.labelString, 0, 0); - context.restore(); - } + var isLastTick = me.ticks.length === index + 1; - if (gridLines.drawBorder) { - // Draw the line at the edge of the axis - context.lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, 0); - context.strokeStyle = helpers.getValueAtIndexOrDefault(gridLines.color, 0); - var x1 = me.left, - x2 = me.right, - y1 = me.top, - y2 = me.bottom; + // Since we always show the last tick,we need may need to hide the last shown one before + var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= me.ticks.length); + if (shouldSkip && !isLastTick || (label === undefined || label === null)) { + return; + } - var aliasPixel = helpers.aliasPixel(context.lineWidth); - if (isHorizontal) { - y1 = y2 = options.position === 'top' ? me.bottom : me.top; - y1 += aliasPixel; - y2 += aliasPixel; - } else { - x1 = x2 = options.position === 'left' ? me.right : me.left; - x1 += aliasPixel; - x2 += aliasPixel; - } + var lineWidth, lineColor; + if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) { + // Draw the first index specially + lineWidth = gridLines.zeroLineWidth; + lineColor = gridLines.zeroLineColor; + } else { + lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, index); + lineColor = helpers.getValueAtIndexOrDefault(gridLines.color, index); + } - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.stroke(); - } - } - }); -}; + // Common properties + var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; + var textAlign = 'middle'; + var textBaseline = 'middle'; -},{}],32:[function(require,module,exports){ -"use strict"; + if (isHorizontal) { + if (!isRotated) { + textBaseline = options.position === 'top' ? 'bottom' : 'top'; + } -module.exports = function(Chart) { + textAlign = isRotated ? 'right' : 'center'; - var helpers = Chart.helpers; + var xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines + labelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) + labelY = (isRotated) ? me.top + 12 : options.position === 'top' ? me.bottom - tl : me.top + tl; - Chart.scaleService = { - // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then - // use the new chart options to grab the correct scale - constructors: {}, - // Use a registration function so that we can move to an ES6 map when we no longer need to support - // old browsers + tx1 = tx2 = x1 = x2 = xLineValue; + ty1 = yTickStart; + ty2 = yTickEnd; + y1 = chartArea.top; + y2 = chartArea.bottom; + } else { + if (options.position === 'left') { + if (optionTicks.mirror) { + labelX = me.right + optionTicks.padding; + textAlign = 'left'; + } else { + labelX = me.right - optionTicks.padding; + textAlign = 'right'; + } + // right side + } else if (optionTicks.mirror) { + labelX = me.left - optionTicks.padding; + textAlign = 'right'; + } else { + labelX = me.left + optionTicks.padding; + textAlign = 'left'; + } - // Scale config defaults - defaults: {}, - registerScaleType: function(type, scaleConstructor, defaults) { - this.constructors[type] = scaleConstructor; - this.defaults[type] = helpers.clone(defaults); - }, - getScaleConstructor: function(type) { - return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; - }, - getScaleDefaults: function(type) { - // Return the scale defaults merged with the global settings so that we always use the latest ones - return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {}; - }, - updateScaleDefaults: function(type, additions) { - var defaults = this.defaults; - if (defaults.hasOwnProperty(type)) { - defaults[type] = helpers.extend(defaults[type], additions); - } - }, - addScalesToLayout: function(chartInstance) { - // Adds each scale to the chart.boxes array to be sized accordingly - helpers.each(chartInstance.scales, function(scale) { - Chart.layoutService.addBox(chartInstance, scale); - }); - } - }; -}; + var yLineValue = me.getPixelForTick(index); // xvalues for grid lines + yLineValue += helpers.aliasPixel(lineWidth); + labelY = me.getPixelForTick(index, gridLines.offsetGridLines); + + tx1 = xTickStart; + tx2 = xTickEnd; + x1 = chartArea.left; + x2 = chartArea.right; + ty1 = ty2 = y1 = y2 = yLineValue; + } + + itemsToDraw.push({ + tx1: tx1, + ty1: ty1, + tx2: tx2, + ty2: ty2, + x1: x1, + y1: y1, + x2: x2, + y2: y2, + labelX: labelX, + labelY: labelY, + glWidth: lineWidth, + glColor: lineColor, + glBorderDash: borderDash, + glBorderDashOffset: borderDashOffset, + rotation: -1 * labelRotationRadians, + label: label, + textBaseline: textBaseline, + textAlign: textAlign + }); + }); + + // Draw all of the tick labels, tick marks, and grid lines at the correct places + helpers.each(itemsToDraw, function(itemToDraw) { + if (gridLines.display) { + context.save(); + context.lineWidth = itemToDraw.glWidth; + context.strokeStyle = itemToDraw.glColor; + if (context.setLineDash) { + context.setLineDash(itemToDraw.glBorderDash); + context.lineDashOffset = itemToDraw.glBorderDashOffset; + } + + context.beginPath(); + + if (gridLines.drawTicks) { + context.moveTo(itemToDraw.tx1, itemToDraw.ty1); + context.lineTo(itemToDraw.tx2, itemToDraw.ty2); + } + + if (gridLines.drawOnChartArea) { + context.moveTo(itemToDraw.x1, itemToDraw.y1); + context.lineTo(itemToDraw.x2, itemToDraw.y2); + } + + context.stroke(); + context.restore(); + } + + if (optionTicks.display) { + context.save(); + context.translate(itemToDraw.labelX, itemToDraw.labelY); + context.rotate(itemToDraw.rotation); + context.font = tickLabelFont; + context.textBaseline = itemToDraw.textBaseline; + context.textAlign = itemToDraw.textAlign; + + var label = itemToDraw.label; + if (helpers.isArray(label)) { + for (var i = 0, y = -(label.length - 1)*tickFontSize*0.75; i < label.length; ++i) { + // We just make sure the multiline element is a string here.. + context.fillText('' + label[i], 0, y); + // apply same lineSpacing as calculated @ L#320 + y += (tickFontSize * 1.5); + } + } else { + context.fillText(label, 0, 0); + } + context.restore(); + } + }); + + if (scaleLabel.display) { + // Draw the scale label + var scaleLabelX; + var scaleLabelY; + var rotation = 0; + + if (isHorizontal) { + scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width + scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFontSize / 2) : me.top + (scaleLabelFontSize / 2); + } else { + var isLeft = options.position === 'left'; + scaleLabelX = isLeft ? me.left + (scaleLabelFontSize / 2) : me.right - (scaleLabelFontSize / 2); + scaleLabelY = me.top + ((me.bottom - me.top) / 2); + rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; + } + + context.save(); + context.translate(scaleLabelX, scaleLabelY); + context.rotate(rotation); + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.fillStyle = scaleLabelFontColor; // render in correct colour + context.font = scaleLabelFont; + context.fillText(scaleLabel.labelString, 0, 0); + context.restore(); + } + + if (gridLines.drawBorder) { + // Draw the line at the edge of the axis + context.lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, 0); + context.strokeStyle = helpers.getValueAtIndexOrDefault(gridLines.color, 0); + var x1 = me.left, + x2 = me.right, + y1 = me.top, + y2 = me.bottom; + + var aliasPixel = helpers.aliasPixel(context.lineWidth); + if (isHorizontal) { + y1 = y2 = options.position === 'top' ? me.bottom : me.top; + y1 += aliasPixel; + y2 += aliasPixel; + } else { + x1 = x2 = options.position === 'left' ? me.right : me.left; + x1 += aliasPixel; + x2 += aliasPixel; + } + + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + } + } + }); + }; + },{}],33:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - var helpers = Chart.helpers; + var helpers = Chart.helpers; - Chart.defaults.global.title = { - display: false, - position: 'top', - fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) + Chart.scaleService = { + // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then + // use the new chart options to grab the correct scale + constructors: {}, + // Use a registration function so that we can move to an ES6 map when we no longer need to support + // old browsers - fontStyle: 'bold', - padding: 10, + // Scale config defaults + defaults: {}, + registerScaleType: function(type, scaleConstructor, defaults) { + this.constructors[type] = scaleConstructor; + this.defaults[type] = helpers.clone(defaults); + }, + getScaleConstructor: function(type) { + return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; + }, + getScaleDefaults: function(type) { + // Return the scale defaults merged with the global settings so that we always use the latest ones + return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {}; + }, + updateScaleDefaults: function(type, additions) { + var defaults = this.defaults; + if (defaults.hasOwnProperty(type)) { + defaults[type] = helpers.extend(defaults[type], additions); + } + }, + addScalesToLayout: function(chartInstance) { + // Adds each scale to the chart.boxes array to be sized accordingly + helpers.each(chartInstance.scales, function(scale) { + Chart.layoutService.addBox(chartInstance, scale); + }); + } + }; + }; - // actual title - text: '' - }; +},{}],34:[function(require,module,exports){ + 'use strict'; - var noop = helpers.noop; - Chart.Title = Chart.Element.extend({ + module.exports = function(Chart) { - initialize: function(config) { - var me = this; - helpers.extend(me, config); - me.options = helpers.configMerge(Chart.defaults.global.title, config.options); + var helpers = Chart.helpers; - // Contains hit boxes for each dataset (in dataset order) - me.legendHitBoxes = []; - }, + /** + * Namespace to hold static tick generation functions + * @namespace Chart.Ticks + */ + Chart.Ticks = { + /** + * Namespace to hold generators for different types of ticks + * @namespace Chart.Ticks.generators + */ + generators: { + /** + * Interface for the options provided to the numeric tick generator + * @interface INumericTickGenerationOptions + */ + /** + * The maximum number of ticks to display + * @name INumericTickGenerationOptions#maxTicks + * @type Number + */ + /** + * The distance between each tick. + * @name INumericTickGenerationOptions#stepSize + * @type Number + * @optional + */ + /** + * Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum + * @name INumericTickGenerationOptions#min + * @type Number + * @optional + */ + /** + * The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum + * @name INumericTickGenerationOptions#max + * @type Number + * @optional + */ - // These methods are ordered by lifecyle. Utilities then follow. + /** + * Generate a set of linear ticks + * @method Chart.Ticks.generators.linear + * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks + * @param dataRange {IRange} the range of the data + * @returns {Array<Number>} array of tick values + */ + linear: function(generationOptions, dataRange) { + var ticks = []; + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. - beforeUpdate: function () { - var chartOpts = this.chart.options; - if (chartOpts && chartOpts.title) { - this.options = helpers.configMerge(Chart.defaults.global.title, chartOpts.title); - } - }, - update: function(maxWidth, maxHeight, margins) { - var me = this; + var spacing; + if (generationOptions.stepSize && generationOptions.stepSize > 0) { + spacing = generationOptions.stepSize; + } else { + var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); + spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true); + } + var niceMin = Math.floor(dataRange.min / spacing) * spacing; + var niceMax = Math.ceil(dataRange.max / spacing) * spacing; - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); + // If min, max and stepSize is set and they make an evenly spaced scale use it. + if (generationOptions.min && generationOptions.max && generationOptions.stepSize) { + var minMaxDeltaDivisibleByStepSize = ((generationOptions.max - generationOptions.min) % generationOptions.stepSize) === 0; + if (minMaxDeltaDivisibleByStepSize) { + niceMin = generationOptions.min; + niceMax = generationOptions.max; + } + } - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me.margins = margins; + var numSpaces = (niceMax - niceMin) / spacing; + // If very close to our rounded value, use it. + if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { + numSpaces = Math.round(numSpaces); + } else { + numSpaces = Math.ceil(numSpaces); + } - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); - // Labels - me.beforeBuildLabels(); - me.buildLabels(); - me.afterBuildLabels(); + // Put the values into the ticks array + ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin); + for (var j = 1; j < numSpaces; ++j) { + ticks.push(niceMin + (j * spacing)); + } + ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax); - // Fit - me.beforeFit(); - me.fit(); - me.afterFit(); - // - me.afterUpdate(); + return ticks; + }, - return me.minSize; + /** + * Generate a set of logarithmic ticks + * @method Chart.Ticks.generators.logarithmic + * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks + * @param dataRange {IRange} the range of the data + * @returns {Array<Number>} array of tick values + */ + logarithmic: function(generationOptions, dataRange) { + var ticks = []; + var getValueOrDefault = helpers.getValueOrDefault; - }, - afterUpdate: noop, + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + var tickVal = getValueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min)))); - // + while (tickVal < dataRange.max) { + ticks.push(tickVal); - beforeSetDimensions: noop, - setDimensions: function() { - var me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; + var exp; + var significand; - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } + if (tickVal === 0) { + exp = Math.floor(helpers.log10(dataRange.minNotZero)); + significand = Math.round(dataRange.minNotZero / Math.pow(10, exp)); + } else { + exp = Math.floor(helpers.log10(tickVal)); + significand = Math.floor(tickVal / Math.pow(10, exp)) + 1; + } - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; + if (significand === 10) { + significand = 1; + ++exp; + } - // Reset minSize - me.minSize = { - width: 0, - height: 0 - }; - }, - afterSetDimensions: noop, + tickVal = significand * Math.pow(10, exp); + } - // + var lastTick = getValueOrDefault(generationOptions.max, tickVal); + ticks.push(lastTick); - beforeBuildLabels: noop, - buildLabels: noop, - afterBuildLabels: noop, + return ticks; + } + }, - // + /** + * Namespace to hold formatters for different types of ticks + * @namespace Chart.Ticks.formatters + */ + formatters: { + /** + * Formatter for value labels + * @method Chart.Ticks.formatters.values + * @param value the value to display + * @return {String|Array} the label to display + */ + values: function(value) { + return helpers.isArray(value) ? value : '' + value; + }, - beforeFit: noop, - fit: function() { - var me = this, - valueOrDefault = helpers.getValueOrDefault, - opts = me.options, - globalDefaults = Chart.defaults.global, - display = opts.display, - fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), - minSize = me.minSize; + /** + * Formatter for linear numeric ticks + * @method Chart.Ticks.formatters.linear + * @param tickValue {Number} the value to be formatted + * @param index {Number} the position of the tickValue parameter in the ticks array + * @param ticks {Array<Number>} the list of ticks being converted + * @return {String} string representation of the tickValue parameter + */ + linear: function(tickValue, index, ticks) { + // If we have lots of ticks, don't use the ones + var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; - if (me.isHorizontal()) { - minSize.width = me.maxWidth; // fill all the width - minSize.height = display ? fontSize + (opts.padding * 2) : 0; - } else { - minSize.width = display ? fontSize + (opts.padding * 2) : 0; - minSize.height = me.maxHeight; // fill all the height - } + // If we have a number like 2.5 as the delta, figure out how many decimal places we need + if (Math.abs(delta) > 1) { + if (tickValue !== Math.floor(tickValue)) { + // not an integer + delta = tickValue - Math.floor(tickValue); + } + } - me.width = minSize.width; - me.height = minSize.height; + var logDelta = helpers.log10(Math.abs(delta)); + var tickString = ''; - }, - afterFit: noop, + if (tickValue !== 0) { + var numDecimal = -1 * Math.floor(logDelta); + numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places + tickString = tickValue.toFixed(numDecimal); + } else { + tickString = '0'; // never show decimal places for 0 + } - // Shared Methods - isHorizontal: function() { - var pos = this.options.position; - return pos === "top" || pos === "bottom"; - }, + return tickString; + }, - // Actualy draw the title block on the canvas - draw: function() { - var me = this, - ctx = me.ctx, - valueOrDefault = helpers.getValueOrDefault, - opts = me.options, - globalDefaults = Chart.defaults.global; + logarithmic: function(tickValue, index, ticks) { + var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); - if (opts.display) { - var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), - fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle), - fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily), - titleFont = helpers.fontString(fontSize, fontStyle, fontFamily), - rotation = 0, - titleX, - titleY, - top = me.top, - left = me.left, - bottom = me.bottom, - right = me.right; + if (tickValue === 0) { + return '0'; + } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { + return tickValue.toExponential(); + } + return ''; + } + } + }; + }; - ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour - ctx.font = titleFont; +},{}],35:[function(require,module,exports){ + 'use strict'; - // Horizontal - if (me.isHorizontal()) { - titleX = left + ((right - left) / 2); // midpoint of the width - titleY = top + ((bottom - top) / 2); // midpoint of the height - } else { - titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2); - titleY = top + ((bottom - top) / 2); - rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); - } + module.exports = function(Chart) { - ctx.save(); - ctx.translate(titleX, titleY); - ctx.rotate(rotation); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(opts.text, 0, 0); - ctx.restore(); - } - } - }); + var helpers = Chart.helpers; - // Register the title plugin - Chart.plugins.register({ - beforeInit: function(chartInstance) { - var opts = chartInstance.options; - var titleOpts = opts.title; + Chart.defaults.global.title = { + display: false, + position: 'top', + fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) - if (titleOpts) { - chartInstance.titleBlock = new Chart.Title({ - ctx: chartInstance.chart.ctx, - options: titleOpts, - chart: chartInstance - }); + fontStyle: 'bold', + padding: 10, - Chart.layoutService.addBox(chartInstance, chartInstance.titleBlock); - } - } - }); -}; + // actual title + text: '' + }; -},{}],34:[function(require,module,exports){ -"use strict"; + var noop = helpers.noop; + Chart.Title = Chart.Element.extend({ -module.exports = function(Chart) { + initialize: function(config) { + var me = this; + helpers.extend(me, config); + me.options = helpers.configMerge(Chart.defaults.global.title, config.options); - var helpers = Chart.helpers; + // Contains hit boxes for each dataset (in dataset order) + me.legendHitBoxes = []; + }, - Chart.defaults.global.tooltips = { - enabled: true, - custom: null, - mode: 'single', - backgroundColor: "rgba(0,0,0,0.8)", - titleFontStyle: "bold", - titleSpacing: 2, - titleMarginBottom: 6, - titleFontColor: "#fff", - titleAlign: "left", - bodySpacing: 2, - bodyFontColor: "#fff", - bodyAlign: "left", - footerFontStyle: "bold", - footerSpacing: 2, - footerMarginTop: 6, - footerFontColor: "#fff", - footerAlign: "left", - yPadding: 6, - xPadding: 6, - yAlign : 'center', - xAlign : 'center', - caretSize: 5, - cornerRadius: 6, - multiKeyBackground: '#fff', - callbacks: { - // Args are: (tooltipItems, data) - beforeTitle: helpers.noop, - title: function(tooltipItems, data) { - // Pick first xLabel for now - var title = ''; - var labels = data.labels; - var labelCount = labels ? labels.length : 0; + // These methods are ordered by lifecycle. Utilities then follow. - if (tooltipItems.length > 0) { - var item = tooltipItems[0]; + beforeUpdate: function() { + var chartOpts = this.chart.options; + if (chartOpts && chartOpts.title) { + this.options = helpers.configMerge(Chart.defaults.global.title, chartOpts.title); + } + }, + update: function(maxWidth, maxHeight, margins) { + var me = this; - if (item.xLabel) { - title = item.xLabel; - } else if (labelCount > 0 && item.index < labelCount) { - title = labels[item.index]; - } - } + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); - return title; - }, - afterTitle: helpers.noop, + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; - // Args are: (tooltipItems, data) - beforeBody: helpers.noop, + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); - // Args are: (tooltipItem, data) - beforeLabel: helpers.noop, - label: function(tooltipItem, data) { - var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; - return datasetLabel + ': ' + tooltipItem.yLabel; - }, - labelColor: function(tooltipItem, chartInstance) { - var meta = chartInstance.getDatasetMeta(tooltipItem.datasetIndex); - var activeElement = meta.data[tooltipItem.index]; - var view = activeElement._view; - return { - borderColor: view.borderColor, - backgroundColor: view.backgroundColor - }; - }, - afterLabel: helpers.noop, + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); - // Args are: (tooltipItems, data) - afterBody: helpers.noop, + return me.minSize; - // Args are: (tooltipItems, data) - beforeFooter: helpers.noop, - footer: helpers.noop, - afterFooter: helpers.noop - } - }; + }, + afterUpdate: noop, - // Helper to push or concat based on if the 2nd parameter is an array or not - function pushOrConcat(base, toPush) { - if (toPush) { - if (helpers.isArray(toPush)) { - //base = base.concat(toPush); - Array.prototype.push.apply(base, toPush); - } else { - base.push(toPush); - } - } + // - return base; - } + beforeSetDimensions: noop, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; - function getAveragePosition(elements) { - if (!elements.length) { - return false; - } + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } - var i, len; - var xPositions = []; - var yPositions = []; + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; - for (i = 0, len = elements.length; i < len; ++i) { - var el = elements[i]; - if (el && el.hasValue()){ - var pos = el.tooltipPosition(); - xPositions.push(pos.x); - yPositions.push(pos.y); - } - } + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop, - var x = 0, - y = 0; - for (i = 0; i < xPositions.length; ++i) { - if (xPositions[ i ]) { - x += xPositions[i]; - y += yPositions[i]; - } - } + // - return { - x: Math.round(x / xPositions.length), - y: Math.round(y / xPositions.length) - }; - } + beforeBuildLabels: noop, + buildLabels: noop, + afterBuildLabels: noop, - // Private helper to create a tooltip iteam model - // @param element : the chart element (point, arc, bar) to create the tooltip item for - // @return : new tooltip item - function createTooltipItem(element) { - var xScale = element._xScale; - var yScale = element._yScale || element._scale; // handle radar || polarArea charts - var index = element._index, - datasetIndex = element._datasetIndex; + // - return { - xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', - yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', - index: index, - datasetIndex: datasetIndex - }; - } + beforeFit: noop, + fit: function() { + var me = this, + valueOrDefault = helpers.getValueOrDefault, + opts = me.options, + globalDefaults = Chart.defaults.global, + display = opts.display, + fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), + minSize = me.minSize; - Chart.Tooltip = Chart.Element.extend({ - initialize: function() { - var me = this; - var globalDefaults = Chart.defaults.global; - var tooltipOpts = me._options; - var getValueOrDefault = helpers.getValueOrDefault; + if (me.isHorizontal()) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = display ? fontSize + (opts.padding * 2) : 0; + } else { + minSize.width = display ? fontSize + (opts.padding * 2) : 0; + minSize.height = me.maxHeight; // fill all the height + } - helpers.extend(me, { - _model: { - // Positioning - xPadding: tooltipOpts.xPadding, - yPadding: tooltipOpts.yPadding, - xAlign : tooltipOpts.xAlign, - yAlign : tooltipOpts.yAlign, + me.width = minSize.width; + me.height = minSize.height; - // Body - bodyFontColor: tooltipOpts.bodyFontColor, - _bodyFontFamily: getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), - _bodyFontStyle: getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), - _bodyAlign: tooltipOpts.bodyAlign, - bodyFontSize: getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), - bodySpacing: tooltipOpts.bodySpacing, + }, + afterFit: noop, - // Title - titleFontColor: tooltipOpts.titleFontColor, - _titleFontFamily: getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), - _titleFontStyle: getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), - titleFontSize: getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), - _titleAlign: tooltipOpts.titleAlign, - titleSpacing: tooltipOpts.titleSpacing, - titleMarginBottom: tooltipOpts.titleMarginBottom, + // Shared Methods + isHorizontal: function() { + var pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + }, - // Footer - footerFontColor: tooltipOpts.footerFontColor, - _footerFontFamily: getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), - _footerFontStyle: getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), - footerFontSize: getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), - _footerAlign: tooltipOpts.footerAlign, - footerSpacing: tooltipOpts.footerSpacing, - footerMarginTop: tooltipOpts.footerMarginTop, + // Actually draw the title block on the canvas + draw: function() { + var me = this, + ctx = me.ctx, + valueOrDefault = helpers.getValueOrDefault, + opts = me.options, + globalDefaults = Chart.defaults.global; - // Appearance - caretSize: tooltipOpts.caretSize, - cornerRadius: tooltipOpts.cornerRadius, - backgroundColor: tooltipOpts.backgroundColor, - opacity: 0, - legendColorBackground: tooltipOpts.multiKeyBackground - } - }); - }, + if (opts.display) { + var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), + fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle), + fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily), + titleFont = helpers.fontString(fontSize, fontStyle, fontFamily), + rotation = 0, + titleX, + titleY, + top = me.top, + left = me.left, + bottom = me.bottom, + right = me.right, + maxWidth; - // Get the title - // Args are: (tooltipItem, data) - getTitle: function() { - var me = this; - var opts = me._options; - var callbacks = opts.callbacks; + ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour + ctx.font = titleFont; - var beforeTitle = callbacks.beforeTitle.apply(me, arguments), - title = callbacks.title.apply(me, arguments), - afterTitle = callbacks.afterTitle.apply(me, arguments); + // Horizontal + if (me.isHorizontal()) { + titleX = left + ((right - left) / 2); // midpoint of the width + titleY = top + ((bottom - top) / 2); // midpoint of the height + maxWidth = right - left; + } else { + titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2); + titleY = top + ((bottom - top) / 2); + maxWidth = bottom - top; + rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); + } - var lines = []; - lines = pushOrConcat(lines, beforeTitle); - lines = pushOrConcat(lines, title); - lines = pushOrConcat(lines, afterTitle); + ctx.save(); + ctx.translate(titleX, titleY); + ctx.rotate(rotation); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(opts.text, 0, 0, maxWidth); + ctx.restore(); + } + } + }); - return lines; - }, + // Register the title plugin + Chart.plugins.register({ + beforeInit: function(chartInstance) { + var opts = chartInstance.options; + var titleOpts = opts.title; - // Args are: (tooltipItem, data) - getBeforeBody: function() { - var lines = this._options.callbacks.beforeBody.apply(this, arguments); - return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; - }, + if (titleOpts) { + chartInstance.titleBlock = new Chart.Title({ + ctx: chartInstance.chart.ctx, + options: titleOpts, + chart: chartInstance + }); - // Args are: (tooltipItem, data) - getBody: function(tooltipItems, data) { - var me = this; - var callbacks = me._options.callbacks; - var bodyItems = []; + Chart.layoutService.addBox(chartInstance, chartInstance.titleBlock); + } + } + }); + }; - helpers.each(tooltipItems, function(tooltipItem) { - var bodyItem = { - before: [], - lines: [], - after: [] - }; - pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data)); - pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); - pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data)); +},{}],36:[function(require,module,exports){ + 'use strict'; - bodyItems.push(bodyItem); - }); + module.exports = function(Chart) { - return bodyItems; - }, + var helpers = Chart.helpers; - // Args are: (tooltipItem, data) - getAfterBody: function() { - var lines = this._options.callbacks.afterBody.apply(this, arguments); - return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; - }, + /** + * Helper method to merge the opacity into a color + */ + function mergeOpacity(colorString, opacity) { + var color = helpers.color(colorString); + return color.alpha(opacity * color.alpha()).rgbaString(); + } - // Get the footer and beforeFooter and afterFooter lines - // Args are: (tooltipItem, data) - getFooter: function() { - var me = this; - var callbacks = me._options.callbacks; + Chart.defaults.global.tooltips = { + enabled: true, + custom: null, + mode: 'nearest', + position: 'average', + intersect: true, + backgroundColor: 'rgba(0,0,0,0.8)', + titleFontStyle: 'bold', + titleSpacing: 2, + titleMarginBottom: 6, + titleFontColor: '#fff', + titleAlign: 'left', + bodySpacing: 2, + bodyFontColor: '#fff', + bodyAlign: 'left', + footerFontStyle: 'bold', + footerSpacing: 2, + footerMarginTop: 6, + footerFontColor: '#fff', + footerAlign: 'left', + yPadding: 6, + xPadding: 6, + caretSize: 5, + cornerRadius: 6, + multiKeyBackground: '#fff', + displayColors: true, + callbacks: { + // Args are: (tooltipItems, data) + beforeTitle: helpers.noop, + title: function(tooltipItems, data) { + // Pick first xLabel for now + var title = ''; + var labels = data.labels; + var labelCount = labels ? labels.length : 0; - var beforeFooter = callbacks.beforeFooter.apply(me, arguments); - var footer = callbacks.footer.apply(me, arguments); - var afterFooter = callbacks.afterFooter.apply(me, arguments); + if (tooltipItems.length > 0) { + var item = tooltipItems[0]; - var lines = []; - lines = pushOrConcat(lines, beforeFooter); - lines = pushOrConcat(lines, footer); - lines = pushOrConcat(lines, afterFooter); + if (item.xLabel) { + title = item.xLabel; + } else if (labelCount > 0 && item.index < labelCount) { + title = labels[item.index]; + } + } - return lines; - }, + return title; + }, + afterTitle: helpers.noop, - update: function(changed) { - var me = this; - var opts = me._options; - var model = me._model; - var active = me._active; + // Args are: (tooltipItems, data) + beforeBody: helpers.noop, - var data = me._data; - var chartInstance = me._chartInstance; + // Args are: (tooltipItem, data) + beforeLabel: helpers.noop, + label: function(tooltipItem, data) { + var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; + return datasetLabel + ': ' + tooltipItem.yLabel; + }, + labelColor: function(tooltipItem, chartInstance) { + var meta = chartInstance.getDatasetMeta(tooltipItem.datasetIndex); + var activeElement = meta.data[tooltipItem.index]; + var view = activeElement._view; + return { + borderColor: view.borderColor, + backgroundColor: view.backgroundColor + }; + }, + afterLabel: helpers.noop, - var i, len; + // Args are: (tooltipItems, data) + afterBody: helpers.noop, - if (active.length) { - model.opacity = 1; + // Args are: (tooltipItems, data) + beforeFooter: helpers.noop, + footer: helpers.noop, + afterFooter: helpers.noop + } + }; - var labelColors = [], - tooltipPosition = getAveragePosition(active); + // Helper to push or concat based on if the 2nd parameter is an array or not + function pushOrConcat(base, toPush) { + if (toPush) { + if (helpers.isArray(toPush)) { + // base = base.concat(toPush); + Array.prototype.push.apply(base, toPush); + } else { + base.push(toPush); + } + } - var tooltipItems = []; - for (i = 0, len = active.length; i < len; ++i) { - tooltipItems.push(createTooltipItem(active[i])); - } + return base; + } - // If the user provided a sorting function, use it to modify the tooltip items - if (opts.itemSort) { - tooltipItems = tooltipItems.sort(opts.itemSort); - } + // Private helper to create a tooltip item model + // @param element : the chart element (point, arc, bar) to create the tooltip item for + // @return : new tooltip item + function createTooltipItem(element) { + var xScale = element._xScale; + var yScale = element._yScale || element._scale; // handle radar || polarArea charts + var index = element._index, + datasetIndex = element._datasetIndex; - // If there is more than one item, show color items - if (active.length > 1) { - helpers.each(tooltipItems, function(tooltipItem) { - labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance)); - }); - } + return { + xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', + yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', + index: index, + datasetIndex: datasetIndex, + x: element._model.x, + y: element._model.y + }; + } - // Build the Text Lines - helpers.extend(model, { - title: me.getTitle(tooltipItems, data), - beforeBody: me.getBeforeBody(tooltipItems, data), - body: me.getBody(tooltipItems, data), - afterBody: me.getAfterBody(tooltipItems, data), - footer: me.getFooter(tooltipItems, data), - x: Math.round(tooltipPosition.x), - y: Math.round(tooltipPosition.y), - caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2), - labelColors: labelColors - }); + /** + * Helper to get the reset model for the tooltip + * @param tooltipOpts {Object} the tooltip options + */ + function getBaseModel(tooltipOpts) { + var globalDefaults = Chart.defaults.global; + var getValueOrDefault = helpers.getValueOrDefault; - // We need to determine alignment of - var tooltipSize = me.getTooltipSize(model); - me.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas + return { + // Positioning + xPadding: tooltipOpts.xPadding, + yPadding: tooltipOpts.yPadding, + xAlign: tooltipOpts.xAlign, + yAlign: tooltipOpts.yAlign, - helpers.extend(model, me.getBackgroundPoint(model, tooltipSize)); - } else { - me._model.opacity = 0; - } + // Body + bodyFontColor: tooltipOpts.bodyFontColor, + _bodyFontFamily: getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), + _bodyFontStyle: getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), + _bodyAlign: tooltipOpts.bodyAlign, + bodyFontSize: getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), + bodySpacing: tooltipOpts.bodySpacing, - if (changed && opts.custom) { - opts.custom.call(me, model); - } + // Title + titleFontColor: tooltipOpts.titleFontColor, + _titleFontFamily: getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), + _titleFontStyle: getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), + titleFontSize: getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), + _titleAlign: tooltipOpts.titleAlign, + titleSpacing: tooltipOpts.titleSpacing, + titleMarginBottom: tooltipOpts.titleMarginBottom, - return me; - }, - getTooltipSize: function(vm) { - var ctx = this._chart.ctx; + // Footer + footerFontColor: tooltipOpts.footerFontColor, + _footerFontFamily: getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), + _footerFontStyle: getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), + footerFontSize: getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), + _footerAlign: tooltipOpts.footerAlign, + footerSpacing: tooltipOpts.footerSpacing, + footerMarginTop: tooltipOpts.footerMarginTop, - var size = { - height: vm.yPadding * 2, // Tooltip Padding - width: 0 - }; + // Appearance + caretSize: tooltipOpts.caretSize, + cornerRadius: tooltipOpts.cornerRadius, + backgroundColor: tooltipOpts.backgroundColor, + opacity: 0, + legendColorBackground: tooltipOpts.multiKeyBackground, + displayColors: tooltipOpts.displayColors + }; + } - // Count of all lines in the body - var body = vm.body; - var combinedBodyLength = body.reduce(function(count, bodyItem) { - return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; - }, 0); - combinedBodyLength += vm.beforeBody.length + vm.afterBody.length; + /** + * Get the size of the tooltip + */ + function getTooltipSize(tooltip, model) { + var ctx = tooltip._chart.ctx; - var titleLineCount = vm.title.length; - var footerLineCount = vm.footer.length; - var titleFontSize = vm.titleFontSize, - bodyFontSize = vm.bodyFontSize, - footerFontSize = vm.footerFontSize; + var height = model.yPadding * 2; // Tooltip Padding + var width = 0; - size.height += titleLineCount * titleFontSize; // Title Lines - size.height += (titleLineCount - 1) * vm.titleSpacing; // Title Line Spacing - size.height += titleLineCount ? vm.titleMarginBottom : 0; // Title's bottom Margin - size.height += combinedBodyLength * bodyFontSize; // Body Lines - size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing - size.height += footerLineCount ? vm.footerMarginTop : 0; // Footer Margin - size.height += footerLineCount * (footerFontSize); // Footer Lines - size.height += footerLineCount ? (footerLineCount - 1) * vm.footerSpacing : 0; // Footer Line Spacing + // Count of all lines in the body + var body = model.body; + var combinedBodyLength = body.reduce(function(count, bodyItem) { + return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; + }, 0); + combinedBodyLength += model.beforeBody.length + model.afterBody.length; - // Title width - var widthPadding = 0; - var maxLineWidth = function(line) { - size.width = Math.max(size.width, ctx.measureText(line).width + widthPadding); - }; + var titleLineCount = model.title.length; + var footerLineCount = model.footer.length; + var titleFontSize = model.titleFontSize, + bodyFontSize = model.bodyFontSize, + footerFontSize = model.footerFontSize; - ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); - helpers.each(vm.title, maxLineWidth); + height += titleLineCount * titleFontSize; // Title Lines + height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing + height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin + height += combinedBodyLength * bodyFontSize; // Body Lines + height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing + height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin + height += footerLineCount * (footerFontSize); // Footer Lines + height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing - // Body width - ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); - helpers.each(vm.beforeBody.concat(vm.afterBody), maxLineWidth); + // Title width + var widthPadding = 0; + var maxLineWidth = function(line) { + width = Math.max(width, ctx.measureText(line).width + widthPadding); + }; - // Body lines may include some extra width due to the color box - widthPadding = body.length > 1 ? (bodyFontSize + 2) : 0; - helpers.each(body, function(bodyItem) { - helpers.each(bodyItem.before, maxLineWidth); - helpers.each(bodyItem.lines, maxLineWidth); - helpers.each(bodyItem.after, maxLineWidth); - }); + ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); + helpers.each(model.title, maxLineWidth); - // Reset back to 0 - widthPadding = 0; + // Body width + ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); + helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth); - // Footer width - ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); - helpers.each(vm.footer, maxLineWidth); + // Body lines may include some extra width due to the color box + widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; + helpers.each(body, function(bodyItem) { + helpers.each(bodyItem.before, maxLineWidth); + helpers.each(bodyItem.lines, maxLineWidth); + helpers.each(bodyItem.after, maxLineWidth); + }); - // Add padding - size.width += 2 * vm.xPadding; + // Reset back to 0 + widthPadding = 0; - return size; - }, - determineAlignment: function(size) { - var me = this; - var model = me._model; - var chart = me._chart; - var chartArea = me._chartInstance.chartArea; + // Footer width + ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); + helpers.each(model.footer, maxLineWidth); - if (model.y < size.height) { - model.yAlign = 'top'; - } else if (model.y > (chart.height - size.height)) { - model.yAlign = 'bottom'; - } + // Add padding + width += 2 * model.xPadding; - var lf, rf; // functions to determine left, right alignment - var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart - var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges - var midX = (chartArea.left + chartArea.right) / 2; - var midY = (chartArea.top + chartArea.bottom) / 2; + return { + width: width, + height: height + }; + } - if (model.yAlign === 'center') { - lf = function(x) { - return x <= midX; - }; - rf = function(x) { - return x > midX; - }; - } else { - lf = function(x) { - return x <= (size.width / 2); - }; - rf = function(x) { - return x >= (chart.width - (size.width / 2)); - }; - } + /** + * Helper to get the alignment of a tooltip given the size + */ + function determineAlignment(tooltip, size) { + var model = tooltip._model; + var chart = tooltip._chart; + var chartArea = tooltip._chartInstance.chartArea; + var xAlign = 'center'; + var yAlign = 'center'; - olf = function(x) { - return x + size.width > chart.width; - }; - orf = function(x) { - return x - size.width < 0; - }; - yf = function(y) { - return y <= midY ? 'top' : 'bottom'; - }; + if (model.y < size.height) { + yAlign = 'top'; + } else if (model.y > (chart.height - size.height)) { + yAlign = 'bottom'; + } - if (lf(model.x)) { - model.xAlign = 'left'; + var lf, rf; // functions to determine left, right alignment + var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart + var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges + var midX = (chartArea.left + chartArea.right) / 2; + var midY = (chartArea.top + chartArea.bottom) / 2; - // Is tooltip too wide and goes over the right side of the chart.? - if (olf(model.x)) { - model.xAlign = 'center'; - model.yAlign = yf(model.y); - } - } else if (rf(model.x)) { - model.xAlign = 'right'; + if (yAlign === 'center') { + lf = function(x) { + return x <= midX; + }; + rf = function(x) { + return x > midX; + }; + } else { + lf = function(x) { + return x <= (size.width / 2); + }; + rf = function(x) { + return x >= (chart.width - (size.width / 2)); + }; + } - // Is tooltip too wide and goes outside left edge of canvas? - if (orf(model.x)) { - model.xAlign = 'center'; - model.yAlign = yf(model.y); - } - } - }, - getBackgroundPoint: function(vm, size) { - // Background Position - var pt = { - x: vm.x, - y: vm.y - }; + olf = function(x) { + return x + size.width > chart.width; + }; + orf = function(x) { + return x - size.width < 0; + }; + yf = function(y) { + return y <= midY ? 'top' : 'bottom'; + }; - var caretSize = vm.caretSize, - caretPadding = vm.caretPadding, - cornerRadius = vm.cornerRadius, - xAlign = vm.xAlign, - yAlign = vm.yAlign, - paddingAndSize = caretSize + caretPadding, - radiusAndPadding = cornerRadius + caretPadding; + if (lf(model.x)) { + xAlign = 'left'; - if (xAlign === 'right') { - pt.x -= size.width; - } else if (xAlign === 'center') { - pt.x -= (size.width / 2); - } + // Is tooltip too wide and goes over the right side of the chart.? + if (olf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } else if (rf(model.x)) { + xAlign = 'right'; - if (yAlign === 'top') { - pt.y += paddingAndSize; - } else if (yAlign === 'bottom') { - pt.y -= size.height + paddingAndSize; - } else { - pt.y -= (size.height / 2); - } + // Is tooltip too wide and goes outside left edge of canvas? + if (orf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } - if (yAlign === 'center') { - if (xAlign === 'left') { - pt.x += paddingAndSize; - } else if (xAlign === 'right') { - pt.x -= paddingAndSize; - } - } else { - if (xAlign === 'left') { - pt.x -= radiusAndPadding; - } else if (xAlign === 'right') { - pt.x += radiusAndPadding; - } - } + var opts = tooltip._options; + return { + xAlign: opts.xAlign ? opts.xAlign : xAlign, + yAlign: opts.yAlign ? opts.yAlign : yAlign + }; + } - return pt; - }, - drawCaret: function(tooltipPoint, size, opacity) { - var vm = this._view; - var ctx = this._chart.ctx; - var x1, x2, x3; - var y1, y2, y3; - var caretSize = vm.caretSize; - var cornerRadius = vm.cornerRadius; - var xAlign = vm.xAlign, - yAlign = vm.yAlign; - var ptX = tooltipPoint.x, - ptY = tooltipPoint.y; - var width = size.width, - height = size.height; + /** + * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment + */ + function getBackgroundPoint(vm, size, alignment) { + // Background Position + var x = vm.x; + var y = vm.y; - if (yAlign === 'center') { - // Left or right side - if (xAlign === 'left') { - x1 = ptX; - x2 = x1 - caretSize; - x3 = x1; - } else { - x1 = ptX + width; - x2 = x1 + caretSize; - x3 = x1; - } + var caretSize = vm.caretSize, + caretPadding = vm.caretPadding, + cornerRadius = vm.cornerRadius, + xAlign = alignment.xAlign, + yAlign = alignment.yAlign, + paddingAndSize = caretSize + caretPadding, + radiusAndPadding = cornerRadius + caretPadding; - y2 = ptY + (height / 2); - y1 = y2 - caretSize; - y3 = y2 + caretSize; - } else { - if (xAlign === 'left') { - x1 = ptX + cornerRadius; - x2 = x1 + caretSize; - x3 = x2 + caretSize; - } else if (xAlign === 'right') { - x1 = ptX + width - cornerRadius; - x2 = x1 - caretSize; - x3 = x2 - caretSize; - } else { - x2 = ptX + (width / 2); - x1 = x2 - caretSize; - x3 = x2 + caretSize; - } + if (xAlign === 'right') { + x -= size.width; + } else if (xAlign === 'center') { + x -= (size.width / 2); + } - if (yAlign === 'top') { - y1 = ptY; - y2 = y1 - caretSize; - y3 = y1; - } else { - y1 = ptY + height; - y2 = y1 + caretSize; - y3 = y1; - } - } + if (yAlign === 'top') { + y += paddingAndSize; + } else if (yAlign === 'bottom') { + y -= size.height + paddingAndSize; + } else { + y -= (size.height / 2); + } - var bgColor = helpers.color(vm.backgroundColor); - ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString(); - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.lineTo(x3, y3); - ctx.closePath(); - ctx.fill(); - }, - drawTitle: function(pt, vm, ctx, opacity) { - var title = vm.title; + if (yAlign === 'center') { + if (xAlign === 'left') { + x += paddingAndSize; + } else if (xAlign === 'right') { + x -= paddingAndSize; + } + } else if (xAlign === 'left') { + x -= radiusAndPadding; + } else if (xAlign === 'right') { + x += radiusAndPadding; + } - if (title.length) { - ctx.textAlign = vm._titleAlign; - ctx.textBaseline = "top"; + return { + x: x, + y: y + }; + } - var titleFontSize = vm.titleFontSize, - titleSpacing = vm.titleSpacing; + Chart.Tooltip = Chart.Element.extend({ + initialize: function() { + this._model = getBaseModel(this._options); + }, - var titleFontColor = helpers.color(vm.titleFontColor); - ctx.fillStyle = titleFontColor.alpha(opacity * titleFontColor.alpha()).rgbString(); - ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + // Get the title + // Args are: (tooltipItem, data) + getTitle: function() { + var me = this; + var opts = me._options; + var callbacks = opts.callbacks; - var i, len; - for (i = 0, len = title.length; i < len; ++i) { - ctx.fillText(title[i], pt.x, pt.y); - pt.y += titleFontSize + titleSpacing; // Line Height and spacing + var beforeTitle = callbacks.beforeTitle.apply(me, arguments), + title = callbacks.title.apply(me, arguments), + afterTitle = callbacks.afterTitle.apply(me, arguments); - if (i + 1 === title.length) { - pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing - } - } - } - }, - drawBody: function(pt, vm, ctx, opacity) { - var bodyFontSize = vm.bodyFontSize; - var bodySpacing = vm.bodySpacing; - var body = vm.body; + var lines = []; + lines = pushOrConcat(lines, beforeTitle); + lines = pushOrConcat(lines, title); + lines = pushOrConcat(lines, afterTitle); - ctx.textAlign = vm._bodyAlign; - ctx.textBaseline = "top"; + return lines; + }, - var bodyFontColor = helpers.color(vm.bodyFontColor); - var textColor = bodyFontColor.alpha(opacity * bodyFontColor.alpha()).rgbString(); - ctx.fillStyle = textColor; - ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + // Args are: (tooltipItem, data) + getBeforeBody: function() { + var lines = this._options.callbacks.beforeBody.apply(this, arguments); + return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; + }, - // Before Body - var xLinePadding = 0; - var fillLineOfText = function(line) { - ctx.fillText(line, pt.x + xLinePadding, pt.y); - pt.y += bodyFontSize + bodySpacing; - }; + // Args are: (tooltipItem, data) + getBody: function(tooltipItems, data) { + var me = this; + var callbacks = me._options.callbacks; + var bodyItems = []; - // Before body lines - helpers.each(vm.beforeBody, fillLineOfText); + helpers.each(tooltipItems, function(tooltipItem) { + var bodyItem = { + before: [], + lines: [], + after: [] + }; + pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data)); + pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); + pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data)); - var drawColorBoxes = body.length > 1; - xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; + bodyItems.push(bodyItem); + }); - // Draw body lines now - helpers.each(body, function(bodyItem, i) { - helpers.each(bodyItem.before, fillLineOfText); + return bodyItems; + }, - helpers.each(bodyItem.lines, function(line) { - // Draw Legend-like boxes if needed - if (drawColorBoxes) { - // Fill a white rect so that colours merge nicely if the opacity is < 1 - ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString(); - ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); + // Args are: (tooltipItem, data) + getAfterBody: function() { + var lines = this._options.callbacks.afterBody.apply(this, arguments); + return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; + }, - // Border - ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString(); - ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); + // Get the footer and beforeFooter and afterFooter lines + // Args are: (tooltipItem, data) + getFooter: function() { + var me = this; + var callbacks = me._options.callbacks; - // Inner square - ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString(); - ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); + var beforeFooter = callbacks.beforeFooter.apply(me, arguments); + var footer = callbacks.footer.apply(me, arguments); + var afterFooter = callbacks.afterFooter.apply(me, arguments); - ctx.fillStyle = textColor; - } + var lines = []; + lines = pushOrConcat(lines, beforeFooter); + lines = pushOrConcat(lines, footer); + lines = pushOrConcat(lines, afterFooter); - fillLineOfText(line); - }); + return lines; + }, - helpers.each(bodyItem.after, fillLineOfText); - }); + update: function(changed) { + var me = this; + var opts = me._options; - // Reset back to 0 for after body - xLinePadding = 0; + // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition + // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time + // which breaks any animations. + var existingModel = me._model; + var model = me._model = getBaseModel(opts); + var active = me._active; - // After body lines - helpers.each(vm.afterBody, fillLineOfText); - pt.y -= bodySpacing; // Remove last body spacing - }, - drawFooter: function(pt, vm, ctx, opacity) { - var footer = vm.footer; + var data = me._data; + var chartInstance = me._chartInstance; - if (footer.length) { - pt.y += vm.footerMarginTop; + // In the case where active.length === 0 we need to keep these at existing values for good animations + var alignment = { + xAlign: existingModel.xAlign, + yAlign: existingModel.yAlign + }; + var backgroundPoint = { + x: existingModel.x, + y: existingModel.y + }; + var tooltipSize = { + width: existingModel.width, + height: existingModel.height + }; + var tooltipPosition = { + x: existingModel.caretX, + y: existingModel.caretY + }; - ctx.textAlign = vm._footerAlign; - ctx.textBaseline = "top"; + var i, len; - var footerFontColor = helpers.color(vm.footerFontColor); - ctx.fillStyle = footerFontColor.alpha(opacity * footerFontColor.alpha()).rgbString(); - ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); + if (active.length) { + model.opacity = 1; - helpers.each(footer, function(line) { - ctx.fillText(line, pt.x, pt.y); - pt.y += vm.footerFontSize + vm.footerSpacing; - }); - } - }, - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; + var labelColors = []; + tooltipPosition = Chart.Tooltip.positioners[opts.position](active, me._eventPosition); - if (vm.opacity === 0) { - return; - } + var tooltipItems = []; + for (i = 0, len = active.length; i < len; ++i) { + tooltipItems.push(createTooltipItem(active[i])); + } - var tooltipSize = this.getTooltipSize(vm); - var pt = { - x: vm.x, - y: vm.y - }; + // If the user provided a filter function, use it to modify the tooltip items + if (opts.filter) { + tooltipItems = tooltipItems.filter(function(a) { + return opts.filter(a, data); + }); + } - // IE11/Edge does not like very small opacities, so snap to 0 - var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; + // If the user provided a sorting function, use it to modify the tooltip items + if (opts.itemSort) { + tooltipItems = tooltipItems.sort(function(a, b) { + return opts.itemSort(a, b, data); + }); + } - if (this._options.enabled) { - // Draw Background - var bgColor = helpers.color(vm.backgroundColor); - ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString(); - helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius); - ctx.fill(); + // Determine colors for boxes + helpers.each(tooltipItems, function(tooltipItem) { + labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance)); + }); - // Draw Caret - this.drawCaret(pt, tooltipSize, opacity); + // Build the Text Lines + model.title = me.getTitle(tooltipItems, data); + model.beforeBody = me.getBeforeBody(tooltipItems, data); + model.body = me.getBody(tooltipItems, data); + model.afterBody = me.getAfterBody(tooltipItems, data); + model.footer = me.getFooter(tooltipItems, data); - // Draw Title, Body, and Footer - pt.x += vm.xPadding; - pt.y += vm.yPadding; + // Initial positioning and colors + model.x = Math.round(tooltipPosition.x); + model.y = Math.round(tooltipPosition.y); + model.caretPadding = helpers.getValueOrDefault(tooltipPosition.padding, 2); + model.labelColors = labelColors; - // Titles - this.drawTitle(pt, vm, ctx, opacity); + // data points + model.dataPoints = tooltipItems; - // Body - this.drawBody(pt, vm, ctx, opacity); + // We need to determine alignment of the tooltip + tooltipSize = getTooltipSize(this, model); + alignment = determineAlignment(this, tooltipSize); + // Final Size and Position + backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment); + } else { + model.opacity = 0; + } - // Footer - this.drawFooter(pt, vm, ctx, opacity); - } - } - }); -}; + model.xAlign = alignment.xAlign; + model.yAlign = alignment.yAlign; + model.x = backgroundPoint.x; + model.y = backgroundPoint.y; + model.width = tooltipSize.width; + model.height = tooltipSize.height; -},{}],35:[function(require,module,exports){ -"use strict"; + // Point where the caret on the tooltip points to + model.caretX = tooltipPosition.x; + model.caretY = tooltipPosition.y; -module.exports = function(Chart) { + me._model = model; - var helpers = Chart.helpers, - globalOpts = Chart.defaults.global; + if (changed && opts.custom) { + opts.custom.call(me, model); + } - globalOpts.elements.arc = { - backgroundColor: globalOpts.defaultColor, - borderColor: "#fff", - borderWidth: 2 - }; + return me; + }, + drawCaret: function(tooltipPoint, size, opacity) { + var vm = this._view; + var ctx = this._chart.ctx; + var x1, x2, x3; + var y1, y2, y3; + var caretSize = vm.caretSize; + var cornerRadius = vm.cornerRadius; + var xAlign = vm.xAlign, + yAlign = vm.yAlign; + var ptX = tooltipPoint.x, + ptY = tooltipPoint.y; + var width = size.width, + height = size.height; - Chart.elements.Arc = Chart.Element.extend({ - inLabelRange: function(mouseX) { - var vm = this._view; + if (yAlign === 'center') { + // Left or right side + if (xAlign === 'left') { + x1 = ptX; + x2 = x1 - caretSize; + x3 = x1; + } else { + x1 = ptX + width; + x2 = x1 + caretSize; + x3 = x1; + } - if (vm) { - return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); - } else { - return false; - } - }, - inRange: function(chartX, chartY) { - var vm = this._view; + y2 = ptY + (height / 2); + y1 = y2 - caretSize; + y3 = y2 + caretSize; + } else { + if (xAlign === 'left') { + x1 = ptX + cornerRadius; + x2 = x1 + caretSize; + x3 = x2 + caretSize; + } else if (xAlign === 'right') { + x1 = ptX + width - cornerRadius; + x2 = x1 - caretSize; + x3 = x2 - caretSize; + } else { + x2 = ptX + (width / 2); + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } - if (vm) { - var pointRelativePosition = helpers.getAngleFromPoint(vm, { - x: chartX, - y: chartY - }), - angle = pointRelativePosition.angle, - distance = pointRelativePosition.distance; + if (yAlign === 'top') { + y1 = ptY; + y2 = y1 - caretSize; + y3 = y1; + } else { + y1 = ptY + height; + y2 = y1 + caretSize; + y3 = y1; + } + } - //Sanitise angle range - var startAngle = vm.startAngle; - var endAngle = vm.endAngle; - while (endAngle < startAngle) { - endAngle += 2.0 * Math.PI; - } - while (angle > endAngle) { - angle -= 2.0 * Math.PI; - } - while (angle < startAngle) { - angle += 2.0 * Math.PI; - } + ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x3, y3); + ctx.closePath(); + ctx.fill(); + }, + drawTitle: function(pt, vm, ctx, opacity) { + var title = vm.title; - //Check if within the range of the open/close angle - var betweenAngles = (angle >= startAngle && angle <= endAngle), - withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + if (title.length) { + ctx.textAlign = vm._titleAlign; + ctx.textBaseline = 'top'; - return (betweenAngles && withinRadius); - } else { - return false; - } - }, - tooltipPosition: function() { - var vm = this._view; + var titleFontSize = vm.titleFontSize, + titleSpacing = vm.titleSpacing; - var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), - rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; - return { - x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), - y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) - }; - }, - draw: function() { + ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); + ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); - var ctx = this._chart.ctx, - vm = this._view, - sA = vm.startAngle, - eA = vm.endAngle; + var i, len; + for (i = 0, len = title.length; i < len; ++i) { + ctx.fillText(title[i], pt.x, pt.y); + pt.y += titleFontSize + titleSpacing; // Line Height and spacing - ctx.beginPath(); + if (i + 1 === title.length) { + pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing + } + } + } + }, + drawBody: function(pt, vm, ctx, opacity) { + var bodyFontSize = vm.bodyFontSize; + var bodySpacing = vm.bodySpacing; + var body = vm.body; - ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); - ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); + ctx.textAlign = vm._bodyAlign; + ctx.textBaseline = 'top'; - ctx.closePath(); - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; + var textColor = mergeOpacity(vm.bodyFontColor, opacity); + ctx.fillStyle = textColor; + ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); - ctx.fillStyle = vm.backgroundColor; + // Before Body + var xLinePadding = 0; + var fillLineOfText = function(line) { + ctx.fillText(line, pt.x + xLinePadding, pt.y); + pt.y += bodyFontSize + bodySpacing; + }; - ctx.fill(); - ctx.lineJoin = 'bevel'; + // Before body lines + helpers.each(vm.beforeBody, fillLineOfText); - if (vm.borderWidth) { - ctx.stroke(); - } - } - }); -}; + var drawColorBoxes = vm.displayColors; + xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; -},{}],36:[function(require,module,exports){ -"use strict"; + // Draw body lines now + helpers.each(body, function(bodyItem, i) { + helpers.each(bodyItem.before, fillLineOfText); -module.exports = function(Chart) { + helpers.each(bodyItem.lines, function(line) { + // Draw Legend-like boxes if needed + if (drawColorBoxes) { + // Fill a white rect so that colours merge nicely if the opacity is < 1 + ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); + ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); - var helpers = Chart.helpers; - var globalDefaults = Chart.defaults.global; + // Border + ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); + ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); - Chart.defaults.global.elements.line = { - tension: 0.4, - backgroundColor: globalDefaults.defaultColor, - borderWidth: 3, - borderColor: globalDefaults.defaultColor, - borderCapStyle: 'butt', - borderDash: [], - borderDashOffset: 0.0, - borderJoinStyle: 'miter', - capBezierPoints: true, - fill: true // do we fill in the area between the line and its base axis - }; + // Inner square + ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); + ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); - Chart.elements.Line = Chart.Element.extend({ - draw: function() { - var me = this; - var vm = me._view; - var spanGaps = vm.spanGaps; - var scaleZero = vm.scaleZero; - var loop = me._loop; + ctx.fillStyle = textColor; + } - var ctx = me._chart.ctx; - ctx.save(); + fillLineOfText(line); + }); - // Helper function to draw a line to a point - function lineToPoint(previousPoint, point) { - var vm = point._view; - if (point._view.steppedLine === true) { - ctx.lineTo(point._view.x, previousPoint._view.y); - ctx.lineTo(point._view.x, point._view.y); - } else if (point._view.tension === 0) { - ctx.lineTo(vm.x, vm.y); - } else { - ctx.bezierCurveTo( - previousPoint._view.controlPointNextX, - previousPoint._view.controlPointNextY, - vm.controlPointPreviousX, - vm.controlPointPreviousY, - vm.x, - vm.y - ); - } - } + helpers.each(bodyItem.after, fillLineOfText); + }); - var points = me._children.slice(); // clone array - var lastDrawnIndex = -1; + // Reset back to 0 for after body + xLinePadding = 0; - // If we are looping, adding the first point again - if (loop && points.length) { - points.push(points[0]); - } + // After body lines + helpers.each(vm.afterBody, fillLineOfText); + pt.y -= bodySpacing; // Remove last body spacing + }, + drawFooter: function(pt, vm, ctx, opacity) { + var footer = vm.footer; - var index, current, previous, currentVM; + if (footer.length) { + pt.y += vm.footerMarginTop; - // Fill Line - if (points.length && vm.fill) { - ctx.beginPath(); + ctx.textAlign = vm._footerAlign; + ctx.textBaseline = 'top'; - for (index = 0; index < points.length; ++index) { - current = points[index]; - previous = helpers.previousItem(points, index); - currentVM = current._view; + ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); + ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); - // First point moves to it's starting position no matter what - if (index === 0) { - if (loop) { - ctx.moveTo(scaleZero.x, scaleZero.y); - } else { - ctx.moveTo(currentVM.x, scaleZero); - } + helpers.each(footer, function(line) { + ctx.fillText(line, pt.x, pt.y); + pt.y += vm.footerFontSize + vm.footerSpacing; + }); + } + }, + drawBackground: function(pt, vm, ctx, tooltipSize, opacity) { + ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); + helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius); + ctx.fill(); + }, + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; - if (!currentVM.skip) { - lastDrawnIndex = index; - ctx.lineTo(currentVM.x, currentVM.y); - } - } else { - previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; + if (vm.opacity === 0) { + return; + } - if (currentVM.skip) { - // Only do this if this is the first point that is skipped - if (!spanGaps && lastDrawnIndex === (index - 1)) { - if (loop) { - ctx.lineTo(scaleZero.x, scaleZero.y); - } else { - ctx.lineTo(previous._view.x, scaleZero); - } - } - } else { - if (lastDrawnIndex !== (index - 1)) { - // There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case. - // If the first data point is NaN, then there is no real gap to skip - if (spanGaps && lastDrawnIndex !== -1) { - // We are spanning the gap, so simple draw a line to this point - lineToPoint(previous, current); - } else { - if (loop) { - ctx.lineTo(currentVM.x, currentVM.y); - } else { - ctx.lineTo(currentVM.x, scaleZero); - ctx.lineTo(currentVM.x, currentVM.y); - } - } - } else { - // Line to next point - lineToPoint(previous, current); - } - lastDrawnIndex = index; - } - } - } + var tooltipSize = { + width: vm.width, + height: vm.height + }; + var pt = { + x: vm.x, + y: vm.y + }; - if (!loop) { - ctx.lineTo(points[lastDrawnIndex]._view.x, scaleZero); - } + // IE11/Edge does not like very small opacities, so snap to 0 + var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; - ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor; - ctx.closePath(); - ctx.fill(); - } + if (this._options.enabled) { + // Draw Background + this.drawBackground(pt, vm, ctx, tooltipSize, opacity); - // Stroke Line Options - var globalOptionLineElements = globalDefaults.elements.line; - ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; + // Draw Caret + this.drawCaret(pt, tooltipSize, opacity); - // IE 9 and 10 do not support line dash - if (ctx.setLineDash) { - ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); - } + // Draw Title, Body, and Footer + pt.x += vm.xPadding; + pt.y += vm.yPadding; - ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; - ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; - ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; - ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + // Titles + this.drawTitle(pt, vm, ctx, opacity); - // Stroke Line - ctx.beginPath(); - lastDrawnIndex = -1; + // Body + this.drawBody(pt, vm, ctx, opacity); - for (index = 0; index < points.length; ++index) { - current = points[index]; - previous = helpers.previousItem(points, index); - currentVM = current._view; + // Footer + this.drawFooter(pt, vm, ctx, opacity); + } + }, - // First point moves to it's starting position no matter what - if (index === 0) { - if (currentVM.skip) { + /** + * Handle an event + * @private + * @param e {Event} the event to handle + * @returns {Boolean} true if the tooltip changed + */ + handleEvent: function(e) { + var me = this; + var options = me._options; + var changed = false; - } else { - ctx.moveTo(currentVM.x, currentVM.y); - lastDrawnIndex = index; - } - } else { - previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; + me._lastActive = me._lastActive || []; - if (!currentVM.skip) { - if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { - // There was a gap and this is the first point after the gap - ctx.moveTo(currentVM.x, currentVM.y); - } else { - // Line to next point - lineToPoint(previous, current); - } - lastDrawnIndex = index; - } - } - } + // Find Active Elements for tooltips + if (e.type === 'mouseout') { + me._active = []; + } else { + me._active = me._chartInstance.getElementsAtEventForMode(e, options.mode, options); + } - ctx.stroke(); - ctx.restore(); - } - }); -}; + // Remember Last Actives + changed = !helpers.arrayEquals(me._active, me._lastActive); + me._lastActive = me._active; + + if (options.enabled || options.custom) { + me._eventPosition = helpers.getRelativePosition(e, me._chart); + + var model = me._model; + me.update(true); + me.pivot(); + + // See if our tooltip position changed + changed |= (model.x !== me._model.x) || (model.y !== me._model.y); + } + + return changed; + } + }); + + /** + * @namespace Chart.Tooltip.positioners + */ + Chart.Tooltip.positioners = { + /** + * Average mode places the tooltip at the average position of the elements shown + * @function Chart.Tooltip.positioners.average + * @param elements {ChartElement[]} the elements being displayed in the tooltip + * @returns {Point} tooltip position + */ + average: function(elements) { + if (!elements.length) { + return false; + } + + var i, len; + var x = 0; + var y = 0; + var count = 0; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var pos = el.tooltipPosition(); + x += pos.x; + y += pos.y; + ++count; + } + } + + return { + x: Math.round(x / count), + y: Math.round(y / count) + }; + }, + + /** + * Gets the tooltip position nearest of the item nearest to the event position + * @function Chart.Tooltip.positioners.nearest + * @param elements {Chart.Element[]} the tooltip elements + * @param eventPosition {Point} the position of the event in canvas coordinates + * @returns {Point} the tooltip position + */ + nearest: function(elements, eventPosition) { + var x = eventPosition.x; + var y = eventPosition.y; + + var nearestElement; + var minDistance = Number.POSITIVE_INFINITY; + var i, len; + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var center = el.getCenterPoint(); + var d = helpers.distanceBetweenPoints(eventPosition, center); + + if (d < minDistance) { + minDistance = d; + nearestElement = el; + } + } + } + + if (nearestElement) { + var tp = nearestElement.tooltipPosition(); + x = tp.x; + y = tp.y; + } + + return { + x: x, + y: y + }; + } + }; + }; + },{}],37:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - var helpers = Chart.helpers, - globalOpts = Chart.defaults.global, - defaultColor = globalOpts.defaultColor; + var helpers = Chart.helpers, + globalOpts = Chart.defaults.global; - globalOpts.elements.point = { - radius: 3, - pointStyle: 'circle', - backgroundColor: defaultColor, - borderWidth: 1, - borderColor: defaultColor, - // Hover - hitRadius: 1, - hoverRadius: 4, - hoverBorderWidth: 1 - }; + globalOpts.elements.arc = { + backgroundColor: globalOpts.defaultColor, + borderColor: '#fff', + borderWidth: 2 + }; - Chart.elements.Point = Chart.Element.extend({ - inRange: function(mouseX, mouseY) { - var vm = this._view; - return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; - }, - inLabelRange: function(mouseX) { - var vm = this._view; - return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; - }, - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y, - padding: vm.radius + vm.borderWidth - }; - }, - draw: function() { - var vm = this._view; - var ctx = this._chart.ctx; - var pointStyle = vm.pointStyle; - var radius = vm.radius; - var x = vm.x; - var y = vm.y; + Chart.elements.Arc = Chart.Element.extend({ + inLabelRange: function(mouseX) { + var vm = this._view; - if (vm.skip) { - return; - } + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } + return false; + }, + inRange: function(chartX, chartY) { + var vm = this._view; - ctx.strokeStyle = vm.borderColor || defaultColor; - ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth); - ctx.fillStyle = vm.backgroundColor || defaultColor; + if (vm) { + var pointRelativePosition = helpers.getAngleFromPoint(vm, { + x: chartX, + y: chartY + }), + angle = pointRelativePosition.angle, + distance = pointRelativePosition.distance; - Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y); - } - }); -}; + // Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += 2.0 * Math.PI; + } + while (angle > endAngle) { + angle -= 2.0 * Math.PI; + } + while (angle < startAngle) { + angle += 2.0 * Math.PI; + } + // Check if within the range of the open/close angle + var betweenAngles = (angle >= startAngle && angle <= endAngle), + withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } + return false; + }, + getCenterPoint: function() { + var vm = this._view; + var halfAngle = (vm.startAngle + vm.endAngle) / 2; + var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; + return { + x: vm.x + Math.cos(halfAngle) * halfRadius, + y: vm.y + Math.sin(halfAngle) * halfRadius + }; + }, + getArea: function() { + var vm = this._view; + return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); + }, + tooltipPosition: function() { + var vm = this._view; + + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), + rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + draw: function() { + + var ctx = this._chart.ctx, + vm = this._view, + sA = vm.startAngle, + eA = vm.endAngle; + + ctx.beginPath(); + + ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); + ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); + + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + + ctx.fillStyle = vm.backgroundColor; + + ctx.fill(); + ctx.lineJoin = 'bevel'; + + if (vm.borderWidth) { + ctx.stroke(); + } + } + }); + }; + },{}],38:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - var globalOpts = Chart.defaults.global; + var helpers = Chart.helpers; + var globalDefaults = Chart.defaults.global; - globalOpts.elements.rectangle = { - backgroundColor: globalOpts.defaultColor, - borderWidth: 0, - borderColor: globalOpts.defaultColor, - borderSkipped: 'bottom' - }; + Chart.defaults.global.elements.line = { + tension: 0.4, + backgroundColor: globalDefaults.defaultColor, + borderWidth: 3, + borderColor: globalDefaults.defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + capBezierPoints: true, + fill: true, // do we fill in the area between the line and its base axis + }; - Chart.elements.Rectangle = Chart.Element.extend({ - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; + Chart.elements.Line = Chart.Element.extend({ + draw: function() { + var me = this; + var vm = me._view; + var spanGaps = vm.spanGaps; + var fillPoint = vm.scaleZero; + var loop = me._loop; - var halfWidth = vm.width / 2, - leftX = vm.x - halfWidth, - rightX = vm.x + halfWidth, - top = vm.base - (vm.base - vm.y), - halfStroke = vm.borderWidth / 2; + // Handle different fill modes for cartesian lines + if (!loop) { + if (vm.fill === 'top') { + fillPoint = vm.scaleTop; + } else if (vm.fill === 'bottom') { + fillPoint = vm.scaleBottom; + } + } - // 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 (vm.borderWidth) { - leftX += halfStroke; - rightX -= halfStroke; - top += halfStroke; - } + var ctx = me._chart.ctx; + ctx.save(); - ctx.beginPath(); - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; + // Helper function to draw a line to a point + function lineToPoint(previousPoint, point) { + var pointVM = point._view; + if (point._view.steppedLine === true) { + ctx.lineTo(pointVM.x, previousPoint._view.y); + ctx.lineTo(pointVM.x, pointVM.y); + } else if (point._view.tension === 0) { + ctx.lineTo(pointVM.x, pointVM.y); + } else { + ctx.bezierCurveTo( + previousPoint._view.controlPointNextX, + previousPoint._view.controlPointNextY, + pointVM.controlPointPreviousX, + pointVM.controlPointPreviousY, + pointVM.x, + pointVM.y + ); + } + } - // Corner points, from bottom-left to bottom-right clockwise - // | 1 2 | - // | 0 3 | - var corners = [ - [leftX, vm.base], - [leftX, top], - [rightX, top], - [rightX, vm.base] - ]; + var points = me._children.slice(); // clone array + var lastDrawnIndex = -1; - // Find first (starting) corner with fallback to 'bottom' - var borders = ['bottom', 'left', 'top', 'right']; - var startCorner = borders.indexOf(vm.borderSkipped, 0); - if (startCorner === -1) - startCorner = 0; + // If we are looping, adding the first point again + if (loop && points.length) { + points.push(points[0]); + } - function cornerAt(index) { - return corners[(startCorner + index) % 4]; - } + var index, current, previous, currentVM; - // Draw rectangle from 'startCorner' - ctx.moveTo.apply(ctx, cornerAt(0)); - for (var i = 1; i < 4; i++) - ctx.lineTo.apply(ctx, cornerAt(i)); + // Fill Line + if (points.length && vm.fill) { + ctx.beginPath(); - ctx.fill(); - if (vm.borderWidth) { - ctx.stroke(); - } - }, - height: function() { - var vm = this._view; - return vm.base - vm.y; - }, - inRange: function(mouseX, mouseY) { - var vm = this._view; - return vm ? - (vm.y < vm.base ? - (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base) : - (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y)) : - false; - }, - inLabelRange: function(mouseX) { - var vm = this._view; - return vm ? (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) : false; - }, - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y - }; - } - }); + for (index = 0; index < points.length; ++index) { + current = points[index]; + previous = helpers.previousItem(points, index); + currentVM = current._view; -}; -},{}],39:[function(require,module,exports){ -"use strict"; + // First point moves to it's starting position no matter what + if (index === 0) { + if (loop) { + ctx.moveTo(fillPoint.x, fillPoint.y); + } else { + ctx.moveTo(currentVM.x, fillPoint); + } -module.exports = function(Chart) { + if (!currentVM.skip) { + lastDrawnIndex = index; + ctx.lineTo(currentVM.x, currentVM.y); + } + } else { + previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; - var helpers = Chart.helpers; - // Default config for a category scale - var defaultConfig = { - position: "bottom" - }; + if (currentVM.skip) { + // Only do this if this is the first point that is skipped + if (!spanGaps && lastDrawnIndex === (index - 1)) { + if (loop) { + ctx.lineTo(fillPoint.x, fillPoint.y); + } else { + ctx.lineTo(previous._view.x, fillPoint); + } + } + } else { + if (lastDrawnIndex !== (index - 1)) { + // There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case. + // If the first data point is NaN, then there is no real gap to skip + if (spanGaps && lastDrawnIndex !== -1) { + // We are spanning the gap, so simple draw a line to this point + lineToPoint(previous, current); + } else if (loop) { + ctx.lineTo(currentVM.x, currentVM.y); + } else { + ctx.lineTo(currentVM.x, fillPoint); + ctx.lineTo(currentVM.x, currentVM.y); + } + } else { + // Line to next point + lineToPoint(previous, current); + } + lastDrawnIndex = index; + } + } + } - var DatasetScale = Chart.Scale.extend({ - /** - * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use tose - * else fall back to data.labels - * @private - */ - getLabels: function() { - var data = this.chart.data; - return (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; - }, - // Implement this so that - determineDataLimits: function() { - var me = this; - var labels = me.getLabels(); - me.minIndex = 0; - me.maxIndex = labels.length - 1; - var findIndex; + if (!loop && lastDrawnIndex !== -1) { + ctx.lineTo(points[lastDrawnIndex]._view.x, fillPoint); + } - if (me.options.ticks.min !== undefined) { - // user specified min value - findIndex = helpers.indexOf(labels, me.options.ticks.min); - me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; - } + ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor; + ctx.closePath(); + ctx.fill(); + } - if (me.options.ticks.max !== undefined) { - // user specified max value - findIndex = helpers.indexOf(labels, me.options.ticks.max); - me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; - } + // Stroke Line Options + var globalOptionLineElements = globalDefaults.elements.line; + ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; - me.min = labels[me.minIndex]; - me.max = labels[me.maxIndex]; - }, + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); + } - buildTicks: function() { - var me = this; - var labels = me.getLabels(); - // If we are viewing some subset of labels, slice the original array - me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); - }, + ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; - getLabelForIndex: function(index) { - return this.ticks[index]; - }, + // Stroke Line + ctx.beginPath(); + lastDrawnIndex = -1; - // Used to get data value locations. Value can either be an index or a numerical value - getPixelForValue: function(value, index, datasetIndex, includeOffset) { - var me = this; - // 1 is added because we need the length but we have the indexes - var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); + for (index = 0; index < points.length; ++index) { + current = points[index]; + previous = helpers.previousItem(points, index); + currentVM = current._view; - if (value !== undefined) { - var labels = me.getLabels(); - var idx = labels.indexOf(value); - index = idx !== -1 ? idx : index; - } + // First point moves to it's starting position no matter what + if (index === 0) { + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = index; + } + } else { + previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; - if (me.isHorizontal()) { - var innerWidth = me.width - (me.paddingLeft + me.paddingRight); - var valueWidth = innerWidth / offsetAmt; - var widthOffset = (valueWidth * (index - me.minIndex)) + me.paddingLeft; + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap + ctx.moveTo(currentVM.x, currentVM.y); + } else { + // Line to next point + lineToPoint(previous, current); + } + lastDrawnIndex = index; + } + } + } - if (me.options.gridLines.offsetGridLines && includeOffset) { - widthOffset += (valueWidth / 2); - } + ctx.stroke(); + ctx.restore(); + } + }); + }; - return me.left + Math.round(widthOffset); - } else { - var innerHeight = me.height - (me.paddingTop + me.paddingBottom); - var valueHeight = innerHeight / offsetAmt; - var heightOffset = (valueHeight * (index - me.minIndex)) + me.paddingTop; +},{}],39:[function(require,module,exports){ + 'use strict'; - if (me.options.gridLines.offsetGridLines && includeOffset) { - heightOffset += (valueHeight / 2); - } + module.exports = function(Chart) { - return me.top + Math.round(heightOffset); - } - }, - getPixelForTick: function(index, includeOffset) { - return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset); - }, - getValueForPixel: function(pixel) { - var me = this; - var value; - var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); - var horz = me.isHorizontal(); - var innerDimension = horz ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); - var valueDimension = innerDimension / offsetAmt; + var helpers = Chart.helpers, + globalOpts = Chart.defaults.global, + defaultColor = globalOpts.defaultColor; - pixel -= horz ? me.left : me.top; + globalOpts.elements.point = { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor, + borderWidth: 1, + borderColor: defaultColor, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + }; - if (me.options.gridLines.offsetGridLines) { - pixel -= (valueDimension / 2); - } - pixel -= horz ? me.paddingLeft : me.paddingTop; + function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; + } - if (pixel <= 0) { - value = 0; - } else { - value = Math.round(pixel / valueDimension); - } + function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; + } - return value; - }, - getBasePixel: function() { - return this.bottom; - } - }); + Chart.elements.Point = Chart.Element.extend({ + inRange: function(mouseX, mouseY) { + var vm = this._view; + return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; + }, - Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig); + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, -}; -},{}],40:[function(require,module,exports){ -"use strict"; + getCenterPoint: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + }, + getArea: function() { + return Math.PI * Math.pow(this._view.radius, 2); + }, + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + draw: function() { + var vm = this._view; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; -module.exports = function(Chart) { + if (vm.skip) { + return; + } - var helpers = Chart.helpers; + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; - var defaultConfig = { - position: "left", - ticks: { - callback: function(tickValue, index, ticks) { - // If we have lots of ticks, don't use the ones - var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; + Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y); + } + }); + }; - // If we have a number like 2.5 as the delta, figure out how many decimal places we need - if (Math.abs(delta) > 1) { - if (tickValue !== Math.floor(tickValue)) { - // not an integer - delta = tickValue - Math.floor(tickValue); - } - } +},{}],40:[function(require,module,exports){ + 'use strict'; - var logDelta = helpers.log10(Math.abs(delta)); - var tickString = ''; + module.exports = function(Chart) { - if (tickValue !== 0) { - var numDecimal = -1 * Math.floor(logDelta); - numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places - tickString = tickValue.toFixed(numDecimal); - } else { - tickString = '0'; // never show decimal places for 0 - } + var globalOpts = Chart.defaults.global; - return tickString; - } - } - }; + globalOpts.elements.rectangle = { + backgroundColor: globalOpts.defaultColor, + borderWidth: 0, + borderColor: globalOpts.defaultColor, + borderSkipped: 'bottom' + }; - var LinearScale = Chart.LinearScaleBase.extend({ - determineDataLimits: function() { - var me = this; - var opts = me.options; - var chart = me.chart; - var data = chart.data; - var datasets = data.datasets; - var isHorizontal = me.isHorizontal(); + function isVertical(bar) { + return bar._view.width !== undefined; + } - function IDMatches(meta) { - return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; - } + /** + * Helper function to get the bounds of the bar regardless of the orientation + * @private + * @param bar {Chart.Element.Rectangle} the bar + * @return {Bounds} bounds of the bar + */ + function getBarBounds(bar) { + var vm = bar._view; + var x1, x2, y1, y2; - // First Calculate the range - me.min = null; - me.max = null; + if (isVertical(bar)) { + // vertical + var halfWidth = vm.width / 2; + x1 = vm.x - halfWidth; + x2 = vm.x + halfWidth; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + // horizontal bar + var halfHeight = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - halfHeight; + y2 = vm.y + halfHeight; + } - if (opts.stacked) { - var valuesPerType = {}; - var hasPositiveValues = false; - var hasNegativeValues = false; + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; + } - helpers.each(datasets, function(dataset, datasetIndex) { - var meta = chart.getDatasetMeta(datasetIndex); - if (valuesPerType[meta.type] === undefined) { - valuesPerType[meta.type] = { - positiveValues: [], - negativeValues: [] - }; - } + Chart.elements.Rectangle = Chart.Element.extend({ + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; - // Store these per type - var positiveValues = valuesPerType[meta.type].positiveValues; - var negativeValues = valuesPerType[meta.type].negativeValues; + var halfWidth = vm.width / 2, + leftX = vm.x - halfWidth, + rightX = vm.x + halfWidth, + top = vm.base - (vm.base - vm.y), + halfStroke = vm.borderWidth / 2; - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } + // 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 (vm.borderWidth) { + leftX += halfStroke; + rightX -= halfStroke; + top += halfStroke; + } - positiveValues[index] = positiveValues[index] || 0; - negativeValues[index] = negativeValues[index] || 0; + ctx.beginPath(); + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; - if (opts.relativePoints) { - positiveValues[index] = 100; - } else { - if (value < 0) { - hasNegativeValues = true; - negativeValues[index] += value; - } else { - hasPositiveValues = true; - positiveValues[index] += value; - } - } - }); - } - }); + // Corner points, from bottom-left to bottom-right clockwise + // | 1 2 | + // | 0 3 | + var corners = [ + [leftX, vm.base], + [leftX, top], + [rightX, top], + [rightX, vm.base] + ]; - helpers.each(valuesPerType, function(valuesForType) { - var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); - var minVal = helpers.min(values); - var maxVal = helpers.max(values); - me.min = me.min === null ? minVal : Math.min(me.min, minVal); - me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); - }); + // Find first (starting) corner with fallback to 'bottom' + var borders = ['bottom', 'left', 'top', 'right']; + var startCorner = borders.indexOf(vm.borderSkipped, 0); + if (startCorner === -1) { + startCorner = 0; + } - } else { - helpers.each(datasets, function(dataset, datasetIndex) { - var meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } + function cornerAt(index) { + return corners[(startCorner + index) % 4]; + } - if (me.min === null) { - me.min = value; - } else if (value < me.min) { - me.min = value; - } + // Draw rectangle from 'startCorner' + var corner = cornerAt(0); + ctx.moveTo(corner[0], corner[1]); - if (me.max === null) { - me.max = value; - } else if (value > me.max) { - me.max = value; - } - }); - } - }); - } + for (var i = 1; i < 4; i++) { + corner = cornerAt(i); + ctx.lineTo(corner[0], corner[1]); + } - // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero - this.handleTickRangeOptions(); - }, - getTickLimit: function() { - var maxTicks; - var me = this; - var tickOpts = me.options.ticks; + ctx.fill(); + if (vm.borderWidth) { + ctx.stroke(); + } + }, + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + inRange: function(mouseX, mouseY) { + var inRange = false; - if (me.isHorizontal()) { - maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); - } else { - // The factor of 2 used to scale the font size has been experimentally determined. - var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize); - maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); - } + if (this._view) { + var bounds = getBarBounds(this); + inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; + } - return maxTicks; - }, - // Called after the ticks are built. We need - handleDirectionalChanges: function() { - if (!this.isHorizontal()) { - // We are in a vertical orientation. The top value is the highest. So reverse the array - this.ticks.reverse(); - } - }, - getLabelForIndex: function(index, datasetIndex) { - return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); - }, - // Utils - getPixelForValue: function(value) { - // This must be called after fit has been run so that - // this.left, this.top, this.right, and this.bottom have been defined - var me = this; - var paddingLeft = me.paddingLeft; - var paddingBottom = me.paddingBottom; - var start = me.start; + return inRange; + }, + inLabelRange: function(mouseX, mouseY) { + var me = this; + if (!me._view) { + return false; + } - var rightValue = +me.getRightValue(value); - var pixel; - var innerDimension; - var range = me.end - start; + var inRange = false; + var bounds = getBarBounds(me); - if (me.isHorizontal()) { - innerDimension = me.width - (paddingLeft + me.paddingRight); - pixel = me.left + (innerDimension / range * (rightValue - start)); - return Math.round(pixel + paddingLeft); - } else { - innerDimension = me.height - (me.paddingTop + paddingBottom); - pixel = (me.bottom - paddingBottom) - (innerDimension / range * (rightValue - start)); - return Math.round(pixel); - } - }, - getValueForPixel: function(pixel) { - var me = this; - var isHorizontal = me.isHorizontal(); - var paddingLeft = me.paddingLeft; - var paddingBottom = me.paddingBottom; - var innerDimension = isHorizontal ? me.width - (paddingLeft + me.paddingRight) : me.height - (me.paddingTop + paddingBottom); - var offset = (isHorizontal ? pixel - me.left - paddingLeft : me.bottom - paddingBottom - pixel) / innerDimension; - return me.start + ((me.end - me.start) * offset); - }, - getPixelForTick: function(index) { - return this.getPixelForValue(this.ticksAsNumbers[index]); - } - }); - Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig); + if (isVertical(me)) { + inRange = mouseX >= bounds.left && mouseX <= bounds.right; + } else { + inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; + } -}; + return inRange; + }, + inXRange: function(mouseX) { + var bounds = getBarBounds(this); + return mouseX >= bounds.left && mouseX <= bounds.right; + }, + inYRange: function(mouseY) { + var bounds = getBarBounds(this); + return mouseY >= bounds.top && mouseY <= bounds.bottom; + }, + getCenterPoint: function() { + var vm = this._view; + var x, y; + if (isVertical(this)) { + x = vm.x; + y = (vm.y + vm.base) / 2; + } else { + x = (vm.x + vm.base) / 2; + y = vm.y; + } + + return {x: x, y: y}; + }, + getArea: function() { + var vm = this._view; + return vm.width * Math.abs(vm.y - vm.base); + }, + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } + }); + + }; + },{}],41:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - var helpers = Chart.helpers, - noop = helpers.noop; + var helpers = Chart.helpers; + // Default config for a category scale + var defaultConfig = { + position: 'bottom' + }; - Chart.LinearScaleBase = Chart.Scale.extend({ - handleTickRangeOptions: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; + var DatasetScale = Chart.Scale.extend({ + /** + * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those + * else fall back to data.labels + * @private + */ + getLabels: function() { + var data = this.chart.data; + return (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; + }, + // Implement this so that + determineDataLimits: function() { + var me = this; + var labels = me.getLabels(); + me.minIndex = 0; + me.maxIndex = labels.length - 1; + var findIndex; - // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, - // do nothing since that would make the chart weird. If the user really wants a weird chart - // axis, they can manually override it - if (tickOpts.beginAtZero) { - var minSign = helpers.sign(me.min); - var maxSign = helpers.sign(me.max); + if (me.options.ticks.min !== undefined) { + // user specified min value + findIndex = helpers.indexOf(labels, me.options.ticks.min); + me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; + } - if (minSign < 0 && maxSign < 0) { - // move the top up to 0 - me.max = 0; - } else if (minSign > 0 && maxSign > 0) { - // move the botttom down to 0 - me.min = 0; - } - } + if (me.options.ticks.max !== undefined) { + // user specified max value + findIndex = helpers.indexOf(labels, me.options.ticks.max); + me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; + } - if (tickOpts.min !== undefined) { - me.min = tickOpts.min; - } else if (tickOpts.suggestedMin !== undefined) { - me.min = Math.min(me.min, tickOpts.suggestedMin); - } + me.min = labels[me.minIndex]; + me.max = labels[me.maxIndex]; + }, - if (tickOpts.max !== undefined) { - me.max = tickOpts.max; - } else if (tickOpts.suggestedMax !== undefined) { - me.max = Math.max(me.max, tickOpts.suggestedMax); - } + buildTicks: function() { + var me = this; + var labels = me.getLabels(); + // If we are viewing some subset of labels, slice the original array + me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); + }, - if (me.min === me.max) { - me.max++; + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var data = me.chart.data; + var isHorizontal = me.isHorizontal(); - if (!tickOpts.beginAtZero) { - me.min--; - } - } - }, - getTickLimit: noop, - handleDirectionalChanges: noop, + if ((data.xLabels && isHorizontal) || (data.yLabels && !isHorizontal)) { + return me.getRightValue(data.datasets[datasetIndex].data[index]); + } + return me.ticks[index]; + }, - buildTicks: function() { - var me = this; - var opts = me.options; - var ticks = me.ticks = []; - var tickOpts = opts.ticks; - var getValueOrDefault = helpers.getValueOrDefault; + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: function(value, index, datasetIndex, includeOffset) { + var me = this; + // 1 is added because we need the length but we have the indexes + var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); - // Figure out what the max number of ticks we can support it is based on the size of - // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 - // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on - // the graph + if (value !== undefined && isNaN(index)) { + var labels = me.getLabels(); + var idx = labels.indexOf(value); + index = idx !== -1 ? idx : index; + } - var maxTicks = me.getTickLimit(); + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var valueWidth = innerWidth / offsetAmt; + var widthOffset = (valueWidth * (index - me.minIndex)) + me.paddingLeft; - // Make sure we always have at least 2 ticks - maxTicks = Math.max(2, maxTicks); + if (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) { + widthOffset += (valueWidth / 2); + } - // To get a "nice" value for the tick spacing, we will use the appropriately named - // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks - // for details. + return me.left + Math.round(widthOffset); + } + var innerHeight = me.height - (me.paddingTop + me.paddingBottom); + var valueHeight = innerHeight / offsetAmt; + var heightOffset = (valueHeight * (index - me.minIndex)) + me.paddingTop; - var spacing; - var fixedStepSizeSet = (tickOpts.fixedStepSize && tickOpts.fixedStepSize > 0) || (tickOpts.stepSize && tickOpts.stepSize > 0); - if (fixedStepSizeSet) { - spacing = getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize); - } else { - var niceRange = helpers.niceNum(me.max - me.min, false); - spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); - } - var niceMin = Math.floor(me.min / spacing) * spacing; - var niceMax = Math.ceil(me.max / spacing) * spacing; - var numSpaces = (niceMax - niceMin) / spacing; + if (me.options.gridLines.offsetGridLines && includeOffset) { + heightOffset += (valueHeight / 2); + } - // If very close to our rounded value, use it. - if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { - numSpaces = Math.round(numSpaces); - } else { - numSpaces = Math.ceil(numSpaces); - } + return me.top + Math.round(heightOffset); + }, + getPixelForTick: function(index, includeOffset) { + return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset); + }, + getValueForPixel: function(pixel) { + var me = this; + var value; + var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1); + var horz = me.isHorizontal(); + var innerDimension = horz ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); + var valueDimension = innerDimension / offsetAmt; - // Put the values into the ticks array - ticks.push(tickOpts.min !== undefined ? tickOpts.min : niceMin); - for (var j = 1; j < numSpaces; ++j) { - ticks.push(niceMin + (j * spacing)); - } - ticks.push(tickOpts.max !== undefined ? tickOpts.max : niceMax); + pixel -= horz ? me.left : me.top; - me.handleDirectionalChanges(); + if (me.options.gridLines.offsetGridLines) { + pixel -= (valueDimension / 2); + } + pixel -= horz ? me.paddingLeft : me.paddingTop; - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - me.max = helpers.max(ticks); - me.min = helpers.min(ticks); + if (pixel <= 0) { + value = 0; + } else { + value = Math.round(pixel / valueDimension); + } - if (tickOpts.reverse) { - ticks.reverse(); + return value; + }, + getBasePixel: function() { + return this.bottom; + } + }); - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - }, - convertTicksToLabels: function() { - var me = this; - me.ticksAsNumbers = me.ticks.slice(); - me.zeroLineIndex = me.ticks.indexOf(0); + Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig); - Chart.Scale.prototype.convertTicksToLabels.call(me); - }, - }); -}; + }; + },{}],42:[function(require,module,exports){ -"use strict"; + 'use strict'; -module.exports = function(Chart) { + module.exports = function(Chart) { - var helpers = Chart.helpers; + var helpers = Chart.helpers; - var defaultConfig = { - position: "left", + var defaultConfig = { + position: 'left', + ticks: { + callback: Chart.Ticks.formatters.linear + } + }; - // label settings - ticks: { - callback: function(value, index, arr) { - var remain = value / (Math.pow(10, Math.floor(helpers.log10(value)))); + var LinearScale = Chart.LinearScaleBase.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var isHorizontal = me.isHorizontal(); - if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) { - return value.toExponential(); - } else { - return ''; - } - } - } - }; + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } - var LogarithmicScale = Chart.Scale.extend({ - determineDataLimits: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - var chart = me.chart; - var data = chart.data; - var datasets = data.datasets; - var getValueOrDefault = helpers.getValueOrDefault; - var isHorizontal = me.isHorizontal(); - function IDMatches(meta) { - return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; - } + // First Calculate the range + me.min = null; + me.max = null; - // Calculate Range - me.min = null; - me.max = null; + if (opts.stacked) { + var valuesPerType = {}; - if (opts.stacked) { - var valuesPerType = {}; + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (valuesPerType[meta.type] === undefined) { + valuesPerType[meta.type] = { + positiveValues: [], + negativeValues: [] + }; + } - helpers.each(datasets, function(dataset, datasetIndex) { - var meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - if (valuesPerType[meta.type] === undefined) { - valuesPerType[meta.type] = []; - } + // Store these per type + var positiveValues = valuesPerType[meta.type].positiveValues; + var negativeValues = valuesPerType[meta.type].negativeValues; - helpers.each(dataset.data, function(rawValue, index) { - var values = valuesPerType[meta.type]; - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } - values[index] = values[index] || 0; + positiveValues[index] = positiveValues[index] || 0; + negativeValues[index] = negativeValues[index] || 0; - if (opts.relativePoints) { - values[index] = 100; - } else { - // Don't need to split positive and negative since the log scale can't handle a 0 crossing - values[index] += value; - } - }); - } - }); + if (opts.relativePoints) { + positiveValues[index] = 100; + } else if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + }); + } + }); - helpers.each(valuesPerType, function(valuesForType) { - var minVal = helpers.min(valuesForType); - var maxVal = helpers.max(valuesForType); - me.min = me.min === null ? minVal : Math.min(me.min, minVal); - me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); - }); + helpers.each(valuesPerType, function(valuesForType) { + var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); + var minVal = helpers.min(values); + var maxVal = helpers.max(values); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + }); - } else { - helpers.each(datasets, function(dataset, datasetIndex) { - var meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } + } else { + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } - if (me.min === null) { - me.min = value; - } else if (value < me.min) { - me.min = value; - } + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } - if (me.max === null) { - me.max = value; - } else if (value > me.max) { - me.max = value; - } - }); - } - }); - } + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } + }); + } + }); + } - me.min = getValueOrDefault(tickOpts.min, me.min); - me.max = getValueOrDefault(tickOpts.max, me.max); + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + this.handleTickRangeOptions(); + }, + getTickLimit: function() { + var maxTicks; + var me = this; + var tickOpts = me.options.ticks; - if (me.min === me.max) { - if (me.min !== 0 && me.min !== null) { - me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); - me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); - } else { - me.min = 1; - me.max = 10; - } - } - }, - buildTicks: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - var getValueOrDefault = helpers.getValueOrDefault; + if (me.isHorizontal()) { + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); + } else { + // The factor of 2 used to scale the font size has been experimentally determined. + var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize); + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); + } - // Reset the ticks array. Later on, we will draw a grid line at these positions - // The array simply contains the numerical value of the spots where ticks will be - var ticks = me.ticks = []; + return maxTicks; + }, + // Called after the ticks are built. We need + handleDirectionalChanges: function() { + if (!this.isHorizontal()) { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + }, + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + // Utils + getPixelForValue: function(value) { + // This must be called after fit has been run so that + // this.left, this.top, this.right, and this.bottom have been defined + var me = this; + var paddingLeft = me.paddingLeft; + var paddingBottom = me.paddingBottom; + var start = me.start; - // Figure out what the max number of ticks we can support it is based on the size of - // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 - // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on - // the graph + var rightValue = +me.getRightValue(value); + var pixel; + var innerDimension; + var range = me.end - start; - var tickVal = getValueOrDefault(tickOpts.min, Math.pow(10, Math.floor(helpers.log10(me.min)))); + if (me.isHorizontal()) { + innerDimension = me.width - (paddingLeft + me.paddingRight); + pixel = me.left + (innerDimension / range * (rightValue - start)); + return Math.round(pixel + paddingLeft); + } + innerDimension = me.height - (me.paddingTop + paddingBottom); + pixel = (me.bottom - paddingBottom) - (innerDimension / range * (rightValue - start)); + return Math.round(pixel); + }, + getValueForPixel: function(pixel) { + var me = this; + var isHorizontal = me.isHorizontal(); + var paddingLeft = me.paddingLeft; + var paddingBottom = me.paddingBottom; + var innerDimension = isHorizontal ? me.width - (paddingLeft + me.paddingRight) : me.height - (me.paddingTop + paddingBottom); + var offset = (isHorizontal ? pixel - me.left - paddingLeft : me.bottom - paddingBottom - pixel) / innerDimension; + return me.start + ((me.end - me.start) * offset); + }, + getPixelForTick: function(index) { + return this.getPixelForValue(this.ticksAsNumbers[index]); + } + }); + Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig); - while (tickVal < me.max) { - ticks.push(tickVal); + }; - var exp = Math.floor(helpers.log10(tickVal)); - var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1; +},{}],43:[function(require,module,exports){ + 'use strict'; - if (significand === 10) { - significand = 1; - ++exp; - } + module.exports = function(Chart) { - tickVal = significand * Math.pow(10, exp); - } + var helpers = Chart.helpers, + noop = helpers.noop; - var lastTick = getValueOrDefault(tickOpts.max, tickVal); - ticks.push(lastTick); + Chart.LinearScaleBase = Chart.Scale.extend({ + handleTickRangeOptions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; - if (!me.isHorizontal()) { - // We are in a vertical orientation. The top value is the highest. So reverse the array - ticks.reverse(); - } + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (tickOpts.beginAtZero) { + var minSign = helpers.sign(me.min); + var maxSign = helpers.sign(me.max); - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - me.max = helpers.max(ticks); - me.min = helpers.min(ticks); + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + me.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the bottom down to 0 + me.min = 0; + } + } - if (tickOpts.reverse) { - ticks.reverse(); + if (tickOpts.min !== undefined) { + me.min = tickOpts.min; + } else if (tickOpts.suggestedMin !== undefined) { + me.min = Math.min(me.min, tickOpts.suggestedMin); + } - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - }, - convertTicksToLabels: function() { - this.tickValues = this.ticks.slice(); + if (tickOpts.max !== undefined) { + me.max = tickOpts.max; + } else if (tickOpts.suggestedMax !== undefined) { + me.max = Math.max(me.max, tickOpts.suggestedMax); + } - Chart.Scale.prototype.convertTicksToLabels.call(this); - }, - // Get the correct tooltip label - getLabelForIndex: function(index, datasetIndex) { - return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); - }, - getPixelForTick: function(index) { - return this.getPixelForValue(this.tickValues[index]); - }, - getPixelForValue: function(value) { - var me = this; - var innerDimension; - var pixel; + if (me.min === me.max) { + me.max++; - var start = me.start; - var newVal = +me.getRightValue(value); - var range = helpers.log10(me.end) - helpers.log10(start); - var paddingTop = me.paddingTop; - var paddingBottom = me.paddingBottom; - var paddingLeft = me.paddingLeft; + if (!tickOpts.beginAtZero) { + me.min--; + } + } + }, + getTickLimit: noop, + handleDirectionalChanges: noop, - if (me.isHorizontal()) { + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; - if (newVal === 0) { - pixel = me.left + paddingLeft; - } else { - innerDimension = me.width - (paddingLeft + me.paddingRight); - pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); - pixel += paddingLeft; - } - } else { - // Bottom - top since pixels increase downard on a screen - if (newVal === 0) { - pixel = me.top + paddingTop; - } else { - innerDimension = me.height - (paddingTop + paddingBottom); - pixel = (me.bottom - paddingBottom) - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); - } - } + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph. Make sure we always have at least 2 ticks + var maxTicks = me.getTickLimit(); + maxTicks = Math.max(2, maxTicks); - return pixel; - }, - getValueForPixel: function(pixel) { - var me = this; - var range = helpers.log10(me.end) - helpers.log10(me.start); - var value, innerDimension; + var numericGeneratorOptions = { + maxTicks: maxTicks, + min: tickOpts.min, + max: tickOpts.max, + stepSize: helpers.getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) + }; + var ticks = me.ticks = Chart.Ticks.generators.linear(numericGeneratorOptions, me); - if (me.isHorizontal()) { - innerDimension = me.width - (me.paddingLeft + me.paddingRight); - value = me.start * Math.pow(10, (pixel - me.left - me.paddingLeft) * range / innerDimension); - } else { - innerDimension = me.height - (me.paddingTop + me.paddingBottom); - value = Math.pow(10, (me.bottom - me.paddingBottom - pixel) * range / innerDimension) / me.start; - } + me.handleDirectionalChanges(); - return value; - } - }); - Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig); + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); -}; -},{}],43:[function(require,module,exports){ -"use strict"; + if (tickOpts.reverse) { + ticks.reverse(); -module.exports = function(Chart) { + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + }, + convertTicksToLabels: function() { + var me = this; + me.ticksAsNumbers = me.ticks.slice(); + me.zeroLineIndex = me.ticks.indexOf(0); - var helpers = Chart.helpers; - var globalDefaults = Chart.defaults.global; + Chart.Scale.prototype.convertTicksToLabels.call(me); + } + }); + }; - var defaultConfig = { - display: true, +},{}],44:[function(require,module,exports){ + 'use strict'; - //Boolean - Whether to animate scaling the chart from the centre - animate: true, - lineArc: false, - position: "chartArea", + module.exports = function(Chart) { - angleLines: { - display: true, - color: "rgba(0, 0, 0, 0.1)", - lineWidth: 1 - }, + var helpers = Chart.helpers; - // label settings - ticks: { - //Boolean - Show a backdrop to the scale label - showLabelBackdrop: true, + var defaultConfig = { + position: 'left', - //String - The colour of the label backdrop - backdropColor: "rgba(255,255,255,0.75)", + // label settings + ticks: { + callback: Chart.Ticks.formatters.logarithmic + } + }; - //Number - The backdrop padding above & below the label in pixels - backdropPaddingY: 2, + var LogarithmicScale = Chart.Scale.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var getValueOrDefault = helpers.getValueOrDefault; + var isHorizontal = me.isHorizontal(); + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } - //Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2 - }, + // Calculate Range + me.min = null; + me.max = null; + me.minNotZero = null; - pointLabels: { - //Number - Point label font size in pixels - fontSize: 10, + if (opts.stacked) { + var valuesPerType = {}; - //Function - Used to convert point labels - callback: function(label) { - return label; - } - } - }; + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + if (valuesPerType[meta.type] === undefined) { + valuesPerType[meta.type] = []; + } - var LinearRadialScale = Chart.LinearScaleBase.extend({ - getValueCount: function() { - return this.chart.data.labels.length; - }, - setDimensions: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - // Set the unconstrained dimension before label rotation - me.width = me.maxWidth; - me.height = me.maxHeight; - me.xCenter = Math.round(me.width / 2); - me.yCenter = Math.round(me.height / 2); + helpers.each(dataset.data, function(rawValue, index) { + var values = valuesPerType[meta.type]; + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } - var minSize = helpers.min([me.height, me.width]); - var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); - me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); - }, - determineDataLimits: function() { - var me = this; - var chart = me.chart; - me.min = null; - me.max = null; + values[index] = values[index] || 0; + if (opts.relativePoints) { + values[index] = 100; + } else { + // Don't need to split positive and negative since the log scale can't handle a 0 crossing + values[index] += value; + } + }); + } + }); - helpers.each(chart.data.datasets, function(dataset, datasetIndex) { - if (chart.isDatasetVisible(datasetIndex)) { - var meta = chart.getDatasetMeta(datasetIndex); + helpers.each(valuesPerType, function(valuesForType) { + var minVal = helpers.min(valuesForType); + var maxVal = helpers.max(valuesForType); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + }); - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } + } else { + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } - if (me.min === null) { - me.min = value; - } else if (value < me.min) { - me.min = value; - } + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } - if (me.max === null) { - me.max = value; - } else if (value > me.max) { - me.max = value; - } - }); - } - }); + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } - // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero - me.handleTickRangeOptions(); - }, - getTickLimit: function() { - var tickOpts = this.options.ticks; - var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); - return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); - }, - convertTicksToLabels: function() { - var me = this; - Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); + if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { + me.minNotZero = value; + } + }); + } + }); + } - // Point labels - me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); - }, - getLabelForIndex: function(index, datasetIndex) { - return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); - }, - fit: 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 - */ + me.min = getValueOrDefault(tickOpts.min, me.min); + me.max = getValueOrDefault(tickOpts.max, me.max); - var pointLabels = this.options.pointLabels; - var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize); - var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle); - var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily); - var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); + if (me.min === me.max) { + if (me.min !== 0 && me.min !== null) { + me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); + me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); + } else { + me.min = 1; + me.max = 10; + } + } + }, + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; - // 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 = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]), - pointPosition, - i, - textWidth, - halfTextWidth, - furthestRight = this.width, - furthestRightIndex, - furthestRightAngle, - furthestLeft = 0, - furthestLeftIndex, - furthestLeftAngle, - xProtrusionLeft, - xProtrusionRight, - radiusReductionRight, - radiusReductionLeft; - this.ctx.font = pointLabeFont; + var generationOptions = { + min: tickOpts.min, + max: tickOpts.max + }; + var ticks = me.ticks = Chart.Ticks.generators.logarithmic(generationOptions, me); - for (i = 0; i < this.getValueCount(); i++) { - // 5px to space the text slightly out - similar to what we do in the draw function. - pointPosition = this.getPointPosition(i, largestPossibleRadius); - textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5; + if (!me.isHorizontal()) { + // We are in a vertical orientation. The top value is the highest. So reverse the array + ticks.reverse(); + } - // Add quarter circle to make degree 0 mean top of circle - var angleRadians = this.getIndexAngle(i) + (Math.PI / 2); - var angle = (angleRadians * 360 / (2 * Math.PI)) % 360; + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); - if (angle === 0 || angle === 180) { - // At angle 0 and 180, we're at exactly the top/bottom - // of the radar chart, so text will be aligned centrally, so we'll half it and compare - // w/left and right text sizes - halfTextWidth = textWidth / 2; - if (pointPosition.x + halfTextWidth > furthestRight) { - furthestRight = pointPosition.x + halfTextWidth; - furthestRightIndex = i; - } - if (pointPosition.x - halfTextWidth < furthestLeft) { - furthestLeft = pointPosition.x - halfTextWidth; - furthestLeftIndex = i; - } - } else if (angle < 180) { - // Less than half the values means we'll left align the text - if (pointPosition.x + textWidth > furthestRight) { - furthestRight = pointPosition.x + textWidth; - furthestRightIndex = i; - } - } else { - // More than half the values means we'll right align the text - if (pointPosition.x - textWidth < furthestLeft) { - furthestLeft = pointPosition.x - textWidth; - furthestLeftIndex = i; - } - } - } + if (tickOpts.reverse) { + ticks.reverse(); - xProtrusionLeft = furthestLeft; - xProtrusionRight = Math.ceil(furthestRight - this.width); + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + }, + convertTicksToLabels: function() { + this.tickValues = this.ticks.slice(); - furthestRightAngle = this.getIndexAngle(furthestRightIndex); - furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); + Chart.Scale.prototype.convertTicksToLabels.call(this); + }, + // Get the correct tooltip label + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + getPixelForTick: function(index) { + return this.getPixelForValue(this.tickValues[index]); + }, + getPixelForValue: function(value) { + var me = this; + var innerDimension; + var pixel; - radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); - radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); + var start = me.start; + var newVal = +me.getRightValue(value); + var range; + var paddingTop = me.paddingTop; + var paddingBottom = me.paddingBottom; + var paddingLeft = me.paddingLeft; + var opts = me.options; + var tickOpts = opts.ticks; - // Ensure we actually need to reduce the size of the chart - radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; - radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; + if (me.isHorizontal()) { + range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0 + if (newVal === 0) { + pixel = me.left + paddingLeft; + } else { + innerDimension = me.width - (paddingLeft + me.paddingRight); + pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); + pixel += paddingLeft; + } + } else { + // Bottom - top since pixels increase downward on a screen + innerDimension = me.height - (paddingTop + paddingBottom); + if (start === 0 && !tickOpts.reverse) { + range = helpers.log10(me.end) - helpers.log10(me.minNotZero); + if (newVal === start) { + pixel = me.bottom - paddingBottom; + } else if (newVal === me.minNotZero) { + pixel = me.bottom - paddingBottom - innerDimension * 0.02; + } else { + pixel = me.bottom - paddingBottom - innerDimension * 0.02 - (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero))); + } + } else if (me.end === 0 && tickOpts.reverse) { + range = helpers.log10(me.start) - helpers.log10(me.minNotZero); + if (newVal === me.end) { + pixel = me.top + paddingTop; + } else if (newVal === me.minNotZero) { + pixel = me.top + paddingTop + innerDimension * 0.02; + } else { + pixel = me.top + paddingTop + innerDimension * 0.02 + (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero))); + } + } else { + range = helpers.log10(me.end) - helpers.log10(start); + innerDimension = me.height - (paddingTop + paddingBottom); + pixel = (me.bottom - paddingBottom) - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); + } + } + return pixel; + }, + getValueForPixel: function(pixel) { + var me = this; + var range = helpers.log10(me.end) - helpers.log10(me.start); + var value, innerDimension; - this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2); - this.setCenterPoint(radiusReductionLeft, radiusReductionRight); - }, - setCenterPoint: function(leftMovement, rightMovement) { - var me = this; - var maxRight = me.width - rightMovement - me.drawingArea, - maxLeft = leftMovement + me.drawingArea; + if (me.isHorizontal()) { + innerDimension = me.width - (me.paddingLeft + me.paddingRight); + value = me.start * Math.pow(10, (pixel - me.left - me.paddingLeft) * range / innerDimension); + } else { // todo: if start === 0 + innerDimension = me.height - (me.paddingTop + me.paddingBottom); + value = Math.pow(10, (me.bottom - me.paddingBottom - pixel) * range / innerDimension) / me.start; + } + return value; + } + }); + Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig); - me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); - // Always vertically in the centre as the text height doesn't change - me.yCenter = Math.round((me.height / 2) + me.top); - }, + }; - getIndexAngle: function(index) { - var angleMultiplier = (Math.PI * 2) / this.getValueCount(); - var startAngle = this.chart.options && this.chart.options.startAngle ? - this.chart.options.startAngle : - 0; +},{}],45:[function(require,module,exports){ + 'use strict'; - var startAngleRadians = startAngle * Math.PI * 2 / 360; + module.exports = function(Chart) { - // Start from the top instead of right, so remove a quarter of the circle - return index * angleMultiplier - (Math.PI / 2) + startAngleRadians; - }, - getDistanceFromCenterForValue: function(value) { - var me = this; + var helpers = Chart.helpers; + var globalDefaults = Chart.defaults.global; - if (value === null) { - return 0; // null always in center - } + var defaultConfig = { + display: true, - // Take into account half font size + the yPadding of the top value - var scalingFactor = me.drawingArea / (me.max - me.min); - if (me.options.reverse) { - return (me.max - value) * scalingFactor; - } else { - return (value - me.min) * scalingFactor; - } - }, - getPointPosition: function(index, distanceFromCenter) { - var me = this; - var thisAngle = me.getIndexAngle(index); - return { - x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, - y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter - }; - }, - getPointPositionForValue: function(index, value) { - return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); - }, + // Boolean - Whether to animate scaling the chart from the centre + animate: true, + lineArc: false, + position: 'chartArea', - getBasePosition: function() { - var me = this; - var min = me.min; - var max = me.max; + angleLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1 + }, - return me.getPointPositionForValue(0, - me.beginAtZero? 0: - min < 0 && max < 0? max : - min > 0 && max > 0? min : - 0); - }, + // label settings + ticks: { + // Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, - draw: function() { - var me = this; - var opts = me.options; - var gridLineOpts = opts.gridLines; - var tickOpts = opts.ticks; - var angleLineOpts = opts.angleLines; - var pointLabelOpts = opts.pointLabels; - var getValueOrDefault = helpers.getValueOrDefault; + // String - The colour of the label backdrop + backdropColor: 'rgba(255,255,255,0.75)', - if (opts.display) { - var ctx = me.ctx; + // Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, - // Tick Font - var tickFontSize = getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); - var tickFontStyle = getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); - var tickFontFamily = getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); - var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + // Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, - helpers.each(me.ticks, function(label, index) { - // Don't draw a centre value (if it is minimum) - if (index > 0 || opts.reverse) { - var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); - var yHeight = me.yCenter - yCenterOffset; + callback: Chart.Ticks.formatters.linear + }, - // Draw circular lines around the scale - if (gridLineOpts.display && index !== 0) { - ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1); - ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); + pointLabels: { + // Number - Point label font size in pixels + fontSize: 10, - if (opts.lineArc) { - // Draw circular arcs between the points - ctx.beginPath(); - ctx.arc(me.xCenter, me.yCenter, yCenterOffset, 0, Math.PI * 2); - ctx.closePath(); - ctx.stroke(); - } else { - // Draw straight lines connecting each index - ctx.beginPath(); - for (var i = 0; i < me.getValueCount(); i++) { - var pointPosition = me.getPointPosition(i, yCenterOffset); - if (i === 0) { - ctx.moveTo(pointPosition.x, pointPosition.y); - } else { - ctx.lineTo(pointPosition.x, pointPosition.y); - } - } - ctx.closePath(); - ctx.stroke(); - } - } + // Function - Used to convert point labels + callback: function(label) { + return label; + } + } + }; - if (tickOpts.display) { - var tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); - ctx.font = tickLabelFont; + var LinearRadialScale = Chart.LinearScaleBase.extend({ + getValueCount: function() { + return this.chart.data.labels.length; + }, + setDimensions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + // Set the unconstrained dimension before label rotation + me.width = me.maxWidth; + me.height = me.maxHeight; + me.xCenter = Math.round(me.width / 2); + me.yCenter = Math.round(me.height / 2); - if (tickOpts.showLabelBackdrop) { - var labelWidth = ctx.measureText(label).width; - ctx.fillStyle = tickOpts.backdropColor; - ctx.fillRect( - me.xCenter - labelWidth / 2 - tickOpts.backdropPaddingX, - yHeight - tickFontSize / 2 - tickOpts.backdropPaddingY, - labelWidth + tickOpts.backdropPaddingX * 2, - tickFontSize + tickOpts.backdropPaddingY * 2 - ); - } + var minSize = helpers.min([me.height, me.width]); + var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); + }, + determineDataLimits: function() { + var me = this; + var chart = me.chart; + me.min = null; + me.max = null; - ctx.textAlign = 'center'; - ctx.textBaseline = "middle"; - ctx.fillStyle = tickFontColor; - ctx.fillText(label, me.xCenter, yHeight); - } - } - }); - if (!opts.lineArc) { - ctx.lineWidth = angleLineOpts.lineWidth; - ctx.strokeStyle = angleLineOpts.color; + helpers.each(chart.data.datasets, function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); - var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max); + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } - // Point Label Font - var pointLabelFontSize = getValueOrDefault(pointLabelOpts.fontSize, globalDefaults.defaultFontSize); - var pointLabeFontStyle = getValueOrDefault(pointLabelOpts.fontStyle, globalDefaults.defaultFontStyle); - var pointLabeFontFamily = getValueOrDefault(pointLabelOpts.fontFamily, globalDefaults.defaultFontFamily); - var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } - for (var i = me.getValueCount() - 1; i >= 0; i--) { - if (angleLineOpts.display) { - var outerPosition = me.getPointPosition(i, outerDistance); - ctx.beginPath(); - ctx.moveTo(me.xCenter, me.yCenter); - ctx.lineTo(outerPosition.x, outerPosition.y); - ctx.stroke(); - ctx.closePath(); - } - // Extra 3px out for some label spacing - var pointLabelPosition = me.getPointPosition(i, outerDistance + 5); + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } + }); + } + }); - // Keep this in loop since we may support array properties here - var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor); - ctx.font = pointLabeFont; - ctx.fillStyle = pointLabelFontColor; + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + me.handleTickRangeOptions(); + }, + getTickLimit: function() { + var tickOpts = this.options.ticks; + var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); + }, + convertTicksToLabels: function() { + var me = this; + Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); - var pointLabels = me.pointLabels; + // Point labels + me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); + }, + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + fit: 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 + */ - // Add quarter circle to make degree 0 mean top of circle - var angleRadians = this.getIndexAngle(i) + (Math.PI / 2); - var angle = (angleRadians * 360 / (2 * Math.PI)) % 360; + var pointLabels = this.options.pointLabels; + var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize); + var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle); + var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily); + var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); - if (angle === 0 || angle === 180) { - ctx.textAlign = 'center'; - } else if (angle < 180) { - ctx.textAlign = 'left'; - } else { - ctx.textAlign = 'right'; - } + // 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 = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]), + pointPosition, + i, + textWidth, + halfTextWidth, + furthestRight = this.width, + furthestRightIndex, + furthestRightAngle, + furthestLeft = 0, + furthestLeftIndex, + furthestLeftAngle, + xProtrusionLeft, + xProtrusionRight, + radiusReductionRight, + radiusReductionLeft; + this.ctx.font = pointLabeFont; - // Set the correct text baseline based on outer positioning - if (angle === 90 || angle === 270) { - ctx.textBaseline = 'middle'; - } else if (angle > 270 || angle < 90) { - ctx.textBaseline = 'bottom'; - } else { - ctx.textBaseline = 'top'; - } + for (i = 0; i < this.getValueCount(); i++) { + // 5px to space the text slightly out - similar to what we do in the draw function. + pointPosition = this.getPointPosition(i, largestPossibleRadius); + textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5; - ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y); - } - } - } - } - }); - Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig); + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = this.getIndexAngle(i) + (Math.PI / 2); + var angle = (angleRadians * 360 / (2 * Math.PI)) % 360; -}; + if (angle === 0 || angle === 180) { + // At angle 0 and 180, we're at exactly the top/bottom + // of the radar chart, so text will be aligned centrally, so we'll half it and compare + // w/left and right text sizes + halfTextWidth = textWidth / 2; + if (pointPosition.x + halfTextWidth > furthestRight) { + furthestRight = pointPosition.x + halfTextWidth; + furthestRightIndex = i; + } + if (pointPosition.x - halfTextWidth < furthestLeft) { + furthestLeft = pointPosition.x - halfTextWidth; + furthestLeftIndex = i; + } + } else if (angle < 180) { + // Less than half the values means we'll left align the text + if (pointPosition.x + textWidth > furthestRight) { + furthestRight = pointPosition.x + textWidth; + furthestRightIndex = i; + } + // More than half the values means we'll right align the text + } else if (pointPosition.x - textWidth < furthestLeft) { + furthestLeft = pointPosition.x - textWidth; + furthestLeftIndex = i; + } + } -},{}],44:[function(require,module,exports){ -/*global window: false */ -"use strict"; + xProtrusionLeft = furthestLeft; + xProtrusionRight = Math.ceil(furthestRight - this.width); -var moment = require(1); -moment = typeof(moment) === 'function' ? moment : window.moment; + furthestRightAngle = this.getIndexAngle(furthestRightIndex); + furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); -module.exports = function(Chart) { + radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); + radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); - var helpers = Chart.helpers; - var time = { - units: [{ - name: 'millisecond', - steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] - }, { - name: 'second', - steps: [1, 2, 5, 10, 30] - }, { - name: 'minute', - steps: [1, 2, 5, 10, 30] - }, { - name: 'hour', - steps: [1, 2, 3, 6, 12] - }, { - name: 'day', - steps: [1, 2, 5] - }, { - name: 'week', - maxStep: 4 - }, { - name: 'month', - maxStep: 3 - }, { - name: 'quarter', - maxStep: 4 - }, { - name: 'year', - maxStep: false - }] - }; + // Ensure we actually need to reduce the size of the chart + radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; + radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; - var defaultConfig = { - position: "bottom", + this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2); + this.setCenterPoint(radiusReductionLeft, radiusReductionRight); + }, + setCenterPoint: function(leftMovement, rightMovement) { + var me = this; + var maxRight = me.width - rightMovement - me.drawingArea, + maxLeft = leftMovement + me.drawingArea; - time: { - parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment - format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ - unit: false, // false == automatic or override with week, month, year, etc. - round: false, // none, or override with week, month, year, etc. - displayFormat: false, // DEPRECATED - isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ + me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); + // Always vertically in the centre as the text height doesn't change + me.yCenter = Math.round((me.height / 2) + me.top); + }, - // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ - displayFormats: { - 'millisecond': 'h:mm:ss.SSS a', // 11:20:01.123 AM, - 'second': 'h:mm:ss a', // 11:20:01 AM - 'minute': 'h:mm:ss a', // 11:20:01 AM - 'hour': 'MMM D, hA', // Sept 4, 5PM - 'day': 'll', // Sep 4 2015 - 'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ? - 'month': 'MMM YYYY', // Sept 2015 - 'quarter': '[Q]Q - YYYY', // Q3 - 'year': 'YYYY' // 2015 - } - }, - ticks: { - autoSkip: false - } - }; + getIndexAngle: function(index) { + var angleMultiplier = (Math.PI * 2) / this.getValueCount(); + var startAngle = this.chart.options && this.chart.options.startAngle ? + this.chart.options.startAngle : + 0; - var TimeScale = Chart.Scale.extend({ - initialize: function() { - if (!moment) { - throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); - } + var startAngleRadians = startAngle * Math.PI * 2 / 360; - Chart.Scale.prototype.initialize.call(this); - }, - getLabelMoment: function(datasetIndex, index) { - if (typeof this.labelMoments[datasetIndex] != 'undefined') { - return this.labelMoments[datasetIndex][index]; - } + // Start from the top instead of right, so remove a quarter of the circle + return index * angleMultiplier - (Math.PI / 2) + startAngleRadians; + }, + getDistanceFromCenterForValue: function(value) { + var me = this; - return null; - }, - getMomentStartOf: function(tick) { - var me = this; - if (me.options.time.unit === 'week' && me.options.time.isoWeekday !== false) { - return tick.clone().startOf('isoWeek').isoWeekday(me.options.time.isoWeekday); - } else { - return tick.clone().startOf(me.tickUnit); - } - }, - determineDataLimits: function() { - var me = this; - me.labelMoments = []; + if (value === null) { + return 0; // null always in center + } - // Only parse these once. If the dataset does not have data as x,y pairs, we will use - // these - var scaleLabelMoments = []; - if (me.chart.data.labels && me.chart.data.labels.length > 0) { - helpers.each(me.chart.data.labels, function(label) { - var labelMoment = me.parseTime(label); + // Take into account half font size + the yPadding of the top value + var scalingFactor = me.drawingArea / (me.max - me.min); + if (me.options.reverse) { + return (me.max - value) * scalingFactor; + } + return (value - me.min) * scalingFactor; + }, + getPointPosition: function(index, distanceFromCenter) { + var me = this; + var thisAngle = me.getIndexAngle(index); + return { + x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, + y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter + }; + }, + getPointPositionForValue: function(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + }, - if (labelMoment.isValid()) { - if (me.options.time.round) { - labelMoment.startOf(me.options.time.round); - } - scaleLabelMoments.push(labelMoment); - } - }, me); + getBasePosition: function() { + var me = this; + var min = me.min; + var max = me.max; - me.firstTick = moment.min.call(me, scaleLabelMoments); - me.lastTick = moment.max.call(me, scaleLabelMoments); - } else { - me.firstTick = null; - me.lastTick = null; - } + return me.getPointPositionForValue(0, + me.beginAtZero? 0: + min < 0 && max < 0? max : + min > 0 && max > 0? min : + 0); + }, - helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { - var momentsForDataset = []; - var datasetVisible = me.chart.isDatasetVisible(datasetIndex); + draw: function() { + var me = this; + var opts = me.options; + var gridLineOpts = opts.gridLines; + var tickOpts = opts.ticks; + var angleLineOpts = opts.angleLines; + var pointLabelOpts = opts.pointLabels; + var getValueOrDefault = helpers.getValueOrDefault; - if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) { - helpers.each(dataset.data, function(value) { - var labelMoment = me.parseTime(me.getRightValue(value)); + if (opts.display) { + var ctx = me.ctx; - if (labelMoment.isValid()) { - if (me.options.time.round) { - labelMoment.startOf(me.options.time.round); - } - momentsForDataset.push(labelMoment); + // Tick Font + var tickFontSize = getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + var tickFontStyle = getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); + var tickFontFamily = getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); - if (datasetVisible) { - // May have gone outside the scale ranges, make sure we keep the first and last ticks updated - me.firstTick = me.firstTick !== null ? moment.min(me.firstTick, labelMoment) : labelMoment; - me.lastTick = me.lastTick !== null ? moment.max(me.lastTick, labelMoment) : labelMoment; - } - } - }, me); - } else { - // We have no labels. Use the ones from the scale - momentsForDataset = scaleLabelMoments; - } + helpers.each(me.ticks, function(label, index) { + // Don't draw a centre value (if it is minimum) + if (index > 0 || opts.reverse) { + var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); + var yHeight = me.yCenter - yCenterOffset; - me.labelMoments.push(momentsForDataset); - }, me); + // Draw circular lines around the scale + if (gridLineOpts.display && index !== 0) { + ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1); + ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); - // Set these after we've done all the data - if (me.options.time.min) { - me.firstTick = me.parseTime(me.options.time.min); - } + if (opts.lineArc) { + // Draw circular arcs between the points + ctx.beginPath(); + ctx.arc(me.xCenter, me.yCenter, yCenterOffset, 0, Math.PI * 2); + ctx.closePath(); + ctx.stroke(); + } else { + // Draw straight lines connecting each index + ctx.beginPath(); + for (var i = 0; i < me.getValueCount(); i++) { + var pointPosition = me.getPointPosition(i, yCenterOffset); + if (i === 0) { + ctx.moveTo(pointPosition.x, pointPosition.y); + } else { + ctx.lineTo(pointPosition.x, pointPosition.y); + } + } + ctx.closePath(); + ctx.stroke(); + } + } - if (me.options.time.max) { - me.lastTick = me.parseTime(me.options.time.max); - } + if (tickOpts.display) { + var tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); + ctx.font = tickLabelFont; - // We will modify these, so clone for later - me.firstTick = (me.firstTick || moment()).clone(); - me.lastTick = (me.lastTick || moment()).clone(); - }, - buildTicks: function() { - var me = this; + if (tickOpts.showLabelBackdrop) { + var labelWidth = ctx.measureText(label).width; + ctx.fillStyle = tickOpts.backdropColor; + ctx.fillRect( + me.xCenter - labelWidth / 2 - tickOpts.backdropPaddingX, + yHeight - tickFontSize / 2 - tickOpts.backdropPaddingY, + labelWidth + tickOpts.backdropPaddingX * 2, + tickFontSize + tickOpts.backdropPaddingY * 2 + ); + } - me.ctx.save(); - var tickFontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); - var tickFontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); - var tickFontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); - var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); - me.ctx.font = tickLabelFont; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = tickFontColor; + ctx.fillText(label, me.xCenter, yHeight); + } + } + }); - me.ticks = []; - me.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step - me.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc) + if (!opts.lineArc) { + ctx.lineWidth = angleLineOpts.lineWidth; + ctx.strokeStyle = angleLineOpts.color; - // Set unit override if applicable - if (me.options.time.unit) { - me.tickUnit = me.options.time.unit || 'day'; - me.displayFormat = me.options.time.displayFormats[me.tickUnit]; - me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); - me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, 1); - } else { - // Determine the smallest needed unit of the time - var innerWidth = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); + var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max); - // Crude approximation of what the label length might be - var tempFirstLabel = me.tickFormatFunction(me.firstTick, 0, []); - var tickLabelWidth = me.ctx.measureText(tempFirstLabel).width; - var cosRotation = Math.cos(helpers.toRadians(me.options.ticks.maxRotation)); - var sinRotation = Math.sin(helpers.toRadians(me.options.ticks.maxRotation)); - tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); - var labelCapacity = innerWidth / (tickLabelWidth); + // Point Label Font + var pointLabelFontSize = getValueOrDefault(pointLabelOpts.fontSize, globalDefaults.defaultFontSize); + var pointLabeFontStyle = getValueOrDefault(pointLabelOpts.fontStyle, globalDefaults.defaultFontStyle); + var pointLabeFontFamily = getValueOrDefault(pointLabelOpts.fontFamily, globalDefaults.defaultFontFamily); + var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); - // Start as small as possible - me.tickUnit = 'millisecond'; - me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); - me.displayFormat = me.options.time.displayFormats[me.tickUnit]; + for (var i = me.getValueCount() - 1; i >= 0; i--) { + if (angleLineOpts.display) { + var outerPosition = me.getPointPosition(i, outerDistance); + ctx.beginPath(); + ctx.moveTo(me.xCenter, me.yCenter); + ctx.lineTo(outerPosition.x, outerPosition.y); + ctx.stroke(); + ctx.closePath(); + } + // Extra 3px out for some label spacing + var pointLabelPosition = me.getPointPosition(i, outerDistance + 5); - var unitDefinitionIndex = 0; - var unitDefinition = time.units[unitDefinitionIndex]; + // Keep this in loop since we may support array properties here + var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor); + ctx.font = pointLabeFont; + ctx.fillStyle = pointLabelFontColor; - // While we aren't ideal and we don't have units left - while (unitDefinitionIndex < time.units.length) { - // Can we scale this unit. If `false` we can scale infinitely - me.unitScale = 1; + var pointLabels = me.pointLabels; - if (helpers.isArray(unitDefinition.steps) && Math.ceil(me.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) { - // Use one of the prefedined steps - for (var idx = 0; idx < unitDefinition.steps.length; ++idx) { - if (unitDefinition.steps[idx] >= Math.ceil(me.scaleSizeInUnits / labelCapacity)) { - me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, unitDefinition.steps[idx]); - break; - } - } + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = this.getIndexAngle(i) + (Math.PI / 2); + var angle = (angleRadians * 360 / (2 * Math.PI)) % 360; - break; - } else if ((unitDefinition.maxStep === false) || (Math.ceil(me.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) { - // We have a max step. Scale this unit - me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, Math.ceil(me.scaleSizeInUnits / labelCapacity)); - break; - } else { - // Move to the next unit up - ++unitDefinitionIndex; - unitDefinition = time.units[unitDefinitionIndex]; + if (angle === 0 || angle === 180) { + ctx.textAlign = 'center'; + } else if (angle < 180) { + ctx.textAlign = 'left'; + } else { + ctx.textAlign = 'right'; + } - me.tickUnit = unitDefinition.name; - var leadingUnitBuffer = me.firstTick.diff(me.getMomentStartOf(me.firstTick), me.tickUnit, true); - var trailingUnitBuffer = me.getMomentStartOf(me.lastTick.clone().add(1, me.tickUnit)).diff(me.lastTick, me.tickUnit, true); - me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true) + leadingUnitBuffer + trailingUnitBuffer; - me.displayFormat = me.options.time.displayFormats[unitDefinition.name]; - } - } - } + // Set the correct text baseline based on outer positioning + if (angle === 90 || angle === 270) { + ctx.textBaseline = 'middle'; + } else if (angle > 270 || angle < 90) { + ctx.textBaseline = 'bottom'; + } else { + ctx.textBaseline = 'top'; + } - var roundedStart; + ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y); + } + } + } + } + }); + Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig); - // Only round the first tick if we have no hard minimum - if (!me.options.time.min) { - me.firstTick = me.getMomentStartOf(me.firstTick); - roundedStart = me.firstTick; - } else { - roundedStart = me.getMomentStartOf(me.firstTick); - } + }; - // Only round the last tick if we have no hard maximum - if (!me.options.time.max) { - var roundedEnd = me.getMomentStartOf(me.lastTick); - var delta = roundedEnd.diff(me.lastTick, me.tickUnit, true); - if (delta < 0) { - // Do not use end of because we need me to be in the next time unit - me.lastTick = me.getMomentStartOf(me.lastTick.add(1, me.tickUnit)); - } else if (delta >= 0) { - me.lastTick = roundedEnd; - } +},{}],46:[function(require,module,exports){ + /* global window: false */ + 'use strict'; - me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); - } + var moment = require(1); + moment = typeof(moment) === 'function' ? moment : window.moment; - me.smallestLabelSeparation = me.width; + module.exports = function(Chart) { - helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { - for (var i = 1; i < me.labelMoments[datasetIndex].length; i++) { - me.smallestLabelSeparation = Math.min(me.smallestLabelSeparation, me.labelMoments[datasetIndex][i].diff(me.labelMoments[datasetIndex][i - 1], me.tickUnit, true)); - } - }, me); + var helpers = Chart.helpers; + var time = { + units: [{ + name: 'millisecond', + steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] + }, { + name: 'second', + steps: [1, 2, 5, 10, 30] + }, { + name: 'minute', + steps: [1, 2, 5, 10, 30] + }, { + name: 'hour', + steps: [1, 2, 3, 6, 12] + }, { + name: 'day', + steps: [1, 2, 5] + }, { + name: 'week', + maxStep: 4 + }, { + name: 'month', + maxStep: 3 + }, { + name: 'quarter', + maxStep: 4 + }, { + name: 'year', + maxStep: false + }] + }; - // Tick displayFormat override - if (me.options.time.displayFormat) { - me.displayFormat = me.options.time.displayFormat; - } + var defaultConfig = { + position: 'bottom', - // first tick. will have been rounded correctly if options.time.min is not specified - me.ticks.push(me.firstTick.clone()); + time: { + parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment + format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + displayFormat: false, // DEPRECATED + isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ + minUnit: 'millisecond', - // For every unit in between the first and last moment, create a moment and add it to the ticks tick - for (var i = 1; i <= me.scaleSizeInUnits; ++i) { - var newTick = roundedStart.clone().add(i, me.tickUnit); + // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ + displayFormats: { + millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, + second: 'h:mm:ss a', // 11:20:01 AM + minute: 'h:mm:ss a', // 11:20:01 AM + hour: 'MMM D, hA', // Sept 4, 5PM + day: 'll', // Sep 4 2015 + week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? + month: 'MMM YYYY', // Sept 2015 + quarter: '[Q]Q - YYYY', // Q3 + year: 'YYYY' // 2015 + } + }, + ticks: { + autoSkip: false + } + }; - // Are we greater than the max time - if (me.options.time.max && newTick.diff(me.lastTick, me.tickUnit, true) >= 0) { - break; - } + var TimeScale = Chart.Scale.extend({ + initialize: function() { + if (!moment) { + throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); + } - if (i % me.unitScale === 0) { - me.ticks.push(newTick); - } - } + Chart.Scale.prototype.initialize.call(this); + }, + getLabelMoment: function(datasetIndex, index) { + if (datasetIndex === null || index === null) { + return null; + } - // Always show the right tick - var diff = me.ticks[me.ticks.length - 1].diff(me.lastTick, me.tickUnit); - if (diff !== 0 || me.scaleSizeInUnits === 0) { - // this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart - // but the last tick was not rounded. - if (me.options.time.max) { - me.ticks.push(me.lastTick.clone()); - me.scaleSizeInUnits = me.lastTick.diff(me.ticks[0], me.tickUnit, true); - } else { - me.ticks.push(me.lastTick.clone()); - me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); - } - } + if (typeof this.labelMoments[datasetIndex] !== 'undefined') { + return this.labelMoments[datasetIndex][index]; + } - me.ctx.restore(); - }, - // Get tooltip label - getLabelForIndex: function(index, datasetIndex) { - var me = this; - var label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : ''; + return null; + }, + getLabelDiff: function(datasetIndex, index) { + var me = this; + if (datasetIndex === null || index === null) { + return null; + } - if (typeof me.chart.data.datasets[datasetIndex].data[0] === 'object') { - label = me.getRightValue(me.chart.data.datasets[datasetIndex].data[index]); - } + if (me.labelDiffs === undefined) { + me.buildLabelDiffs(); + } - // Format nicely - if (me.options.time.tooltipFormat) { - label = me.parseTime(label).format(me.options.time.tooltipFormat); - } + if (typeof me.labelDiffs[datasetIndex] !== 'undefined') { + return me.labelDiffs[datasetIndex][index]; + } - return label; - }, - // Function to format an individual tick mark - tickFormatFunction: function(tick, index, ticks) { - var formattedTick = tick.format(this.displayFormat); - var tickOpts = this.options.ticks; - var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); + return null; + }, + getMomentStartOf: function(tick) { + var me = this; + if (me.options.time.unit === 'week' && me.options.time.isoWeekday !== false) { + return tick.clone().startOf('isoWeek').isoWeekday(me.options.time.isoWeekday); + } + return tick.clone().startOf(me.tickUnit); + }, + determineDataLimits: function() { + var me = this; + me.labelMoments = []; - if (callback) { - return callback(formattedTick, index, ticks); - } else { - return formattedTick; - } - }, - convertTicksToLabels: function() { - var me = this; - me.tickMoments = me.ticks; - me.ticks = me.ticks.map(me.tickFormatFunction, me); - }, - getPixelForValue: function(value, index, datasetIndex) { - var me = this; - if (!value || !value.isValid) { - // not already a moment object - value = moment(me.getRightValue(value)); - } - var labelMoment = value && value.isValid && value.isValid() ? value : me.getLabelMoment(datasetIndex, index); + // Only parse these once. If the dataset does not have data as x,y pairs, we will use + // these + var scaleLabelMoments = []; + if (me.chart.data.labels && me.chart.data.labels.length > 0) { + helpers.each(me.chart.data.labels, function(label) { + var labelMoment = me.parseTime(label); - if (labelMoment) { - var offset = labelMoment.diff(me.firstTick, me.tickUnit, true); + if (labelMoment.isValid()) { + if (me.options.time.round) { + labelMoment.startOf(me.options.time.round); + } + scaleLabelMoments.push(labelMoment); + } + }, me); - var decimal = offset !== 0 ? offset / me.scaleSizeInUnits : offset; + me.firstTick = moment.min.call(me, scaleLabelMoments); + me.lastTick = moment.max.call(me, scaleLabelMoments); + } else { + me.firstTick = null; + me.lastTick = null; + } - if (me.isHorizontal()) { - var innerWidth = me.width - (me.paddingLeft + me.paddingRight); - var valueOffset = (innerWidth * decimal) + me.paddingLeft; + helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { + var momentsForDataset = []; + var datasetVisible = me.chart.isDatasetVisible(datasetIndex); - return me.left + Math.round(valueOffset); - } else { - var innerHeight = me.height - (me.paddingTop + me.paddingBottom); - var heightOffset = (innerHeight * decimal) + me.paddingTop; + if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) { + helpers.each(dataset.data, function(value) { + var labelMoment = me.parseTime(me.getRightValue(value)); - return me.top + Math.round(heightOffset); - } - } - }, - getPixelForTick: function(index) { - return this.getPixelForValue(this.tickMoments[index], null, null); - }, - getValueForPixel: function(pixel) { - var me = this; - var innerDimension = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); - var offset = (pixel - (me.isHorizontal() ? me.left + me.paddingLeft : me.top + me.paddingTop)) / innerDimension; - offset *= me.scaleSizeInUnits; - return me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds'); - }, - parseTime: function(label) { - var me = this; - if (typeof me.options.time.parser === 'string') { - return moment(label, me.options.time.parser); - } - if (typeof me.options.time.parser === 'function') { - return me.options.time.parser(label); - } - // Date objects - if (typeof label.getMonth === 'function' || typeof label === 'number') { - return moment(label); - } - // Moment support - if (label.isValid && label.isValid()) { - return label; - } - // Custom parsing (return an instance of moment) - if (typeof me.options.time.format !== 'string' && me.options.time.format.call) { - console.warn("options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale"); - return me.options.time.format(label); - } - // Moment format parsing - return moment(label, me.options.time.format); - } - }); - Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig); + if (labelMoment.isValid()) { + if (me.options.time.round) { + labelMoment.startOf(me.options.time.round); + } + momentsForDataset.push(labelMoment); -}; + if (datasetVisible) { + // May have gone outside the scale ranges, make sure we keep the first and last ticks updated + me.firstTick = me.firstTick !== null ? moment.min(me.firstTick, labelMoment) : labelMoment; + me.lastTick = me.lastTick !== null ? moment.max(me.lastTick, labelMoment) : labelMoment; + } + } + }, me); + } else { + // We have no labels. Use the ones from the scale + momentsForDataset = scaleLabelMoments; + } + me.labelMoments.push(momentsForDataset); + }, me); + + // Set these after we've done all the data + if (me.options.time.min) { + me.firstTick = me.parseTime(me.options.time.min); + } + + if (me.options.time.max) { + me.lastTick = me.parseTime(me.options.time.max); + } + + // We will modify these, so clone for later + me.firstTick = (me.firstTick || moment()).clone(); + me.lastTick = (me.lastTick || moment()).clone(); + }, + buildLabelDiffs: function() { + var me = this; + me.labelDiffs = []; + var scaleLabelDiffs = []; + // Parse common labels once + if (me.chart.data.labels && me.chart.data.labels.length > 0) { + helpers.each(me.chart.data.labels, function(label) { + var labelMoment = me.parseTime(label); + + if (labelMoment.isValid()) { + if (me.options.time.round) { + labelMoment.startOf(me.options.time.round); + } + scaleLabelDiffs.push(labelMoment.diff(me.firstTick, me.tickUnit, true)); + } + }, me); + } + + helpers.each(me.chart.data.datasets, function(dataset) { + var diffsForDataset = []; + + if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) { + helpers.each(dataset.data, function(value) { + var labelMoment = me.parseTime(me.getRightValue(value)); + + if (labelMoment.isValid()) { + if (me.options.time.round) { + labelMoment.startOf(me.options.time.round); + } + diffsForDataset.push(labelMoment.diff(me.firstTick, me.tickUnit, true)); + } + }, me); + } else { + // We have no labels. Use common ones + diffsForDataset = scaleLabelDiffs; + } + + me.labelDiffs.push(diffsForDataset); + }, me); + }, + buildTicks: function() { + var me = this; + + me.ctx.save(); + var tickFontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); + var tickFontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); + var tickFontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + me.ctx.font = tickLabelFont; + + me.ticks = []; + me.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step + me.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc) + + // Set unit override if applicable + if (me.options.time.unit) { + me.tickUnit = me.options.time.unit || 'day'; + me.displayFormat = me.options.time.displayFormats[me.tickUnit]; + me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); + me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, 1); + } else { + // Determine the smallest needed unit of the time + var innerWidth = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); + + // Crude approximation of what the label length might be + var tempFirstLabel = me.tickFormatFunction(me.firstTick, 0, []); + var tickLabelWidth = me.ctx.measureText(tempFirstLabel).width; + var cosRotation = Math.cos(helpers.toRadians(me.options.ticks.maxRotation)); + var sinRotation = Math.sin(helpers.toRadians(me.options.ticks.maxRotation)); + tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); + var labelCapacity = innerWidth / (tickLabelWidth); + + // Start as small as possible + me.tickUnit = me.options.time.minUnit; + me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); + me.displayFormat = me.options.time.displayFormats[me.tickUnit]; + + var unitDefinitionIndex = 0; + var unitDefinition = time.units[unitDefinitionIndex]; + + // While we aren't ideal and we don't have units left + while (unitDefinitionIndex < time.units.length) { + // Can we scale this unit. If `false` we can scale infinitely + me.unitScale = 1; + + if (helpers.isArray(unitDefinition.steps) && Math.ceil(me.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) { + // Use one of the predefined steps + for (var idx = 0; idx < unitDefinition.steps.length; ++idx) { + if (unitDefinition.steps[idx] >= Math.ceil(me.scaleSizeInUnits / labelCapacity)) { + me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, unitDefinition.steps[idx]); + break; + } + } + + break; + } else if ((unitDefinition.maxStep === false) || (Math.ceil(me.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) { + // We have a max step. Scale this unit + me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, Math.ceil(me.scaleSizeInUnits / labelCapacity)); + break; + } else { + // Move to the next unit up + ++unitDefinitionIndex; + unitDefinition = time.units[unitDefinitionIndex]; + + me.tickUnit = unitDefinition.name; + var leadingUnitBuffer = me.firstTick.diff(me.getMomentStartOf(me.firstTick), me.tickUnit, true); + var trailingUnitBuffer = me.getMomentStartOf(me.lastTick.clone().add(1, me.tickUnit)).diff(me.lastTick, me.tickUnit, true); + me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true) + leadingUnitBuffer + trailingUnitBuffer; + me.displayFormat = me.options.time.displayFormats[unitDefinition.name]; + } + } + } + + var roundedStart; + + // Only round the first tick if we have no hard minimum + if (!me.options.time.min) { + me.firstTick = me.getMomentStartOf(me.firstTick); + roundedStart = me.firstTick; + } else { + roundedStart = me.getMomentStartOf(me.firstTick); + } + + // Only round the last tick if we have no hard maximum + if (!me.options.time.max) { + var roundedEnd = me.getMomentStartOf(me.lastTick); + var delta = roundedEnd.diff(me.lastTick, me.tickUnit, true); + if (delta < 0) { + // Do not use end of because we need me to be in the next time unit + me.lastTick = me.getMomentStartOf(me.lastTick.add(1, me.tickUnit)); + } else if (delta >= 0) { + me.lastTick = roundedEnd; + } + + me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); + } + + // Tick displayFormat override + if (me.options.time.displayFormat) { + me.displayFormat = me.options.time.displayFormat; + } + + // first tick. will have been rounded correctly if options.time.min is not specified + me.ticks.push(me.firstTick.clone()); + + // For every unit in between the first and last moment, create a moment and add it to the ticks tick + for (var i = 1; i <= me.scaleSizeInUnits; ++i) { + var newTick = roundedStart.clone().add(i, me.tickUnit); + + // Are we greater than the max time + if (me.options.time.max && newTick.diff(me.lastTick, me.tickUnit, true) >= 0) { + break; + } + + if (i % me.unitScale === 0) { + me.ticks.push(newTick); + } + } + + // Always show the right tick + var diff = me.ticks[me.ticks.length - 1].diff(me.lastTick, me.tickUnit); + if (diff !== 0 || me.scaleSizeInUnits === 0) { + // this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart + // but the last tick was not rounded. + if (me.options.time.max) { + me.ticks.push(me.lastTick.clone()); + me.scaleSizeInUnits = me.lastTick.diff(me.ticks[0], me.tickUnit, true); + } else { + me.ticks.push(me.lastTick.clone()); + me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true); + } + } + + me.ctx.restore(); + + // Invalidate label diffs cache + me.labelDiffs = undefined; + }, + // Get tooltip label + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : ''; + + if (typeof me.chart.data.datasets[datasetIndex].data[0] === 'object') { + label = me.getRightValue(me.chart.data.datasets[datasetIndex].data[index]); + } + + // Format nicely + if (me.options.time.tooltipFormat) { + label = me.parseTime(label).format(me.options.time.tooltipFormat); + } + + return label; + }, + // Function to format an individual tick mark + tickFormatFunction: function(tick, index, ticks) { + var formattedTick = tick.format(this.displayFormat); + var tickOpts = this.options.ticks; + var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); + + if (callback) { + return callback(formattedTick, index, ticks); + } + return formattedTick; + }, + convertTicksToLabels: function() { + var me = this; + me.tickMoments = me.ticks; + me.ticks = me.ticks.map(me.tickFormatFunction, me); + }, + getPixelForValue: function(value, index, datasetIndex) { + var me = this; + var offset = null; + if (index !== undefined && datasetIndex !== undefined) { + offset = me.getLabelDiff(datasetIndex, index); + } + + if (offset === null) { + if (!value || !value.isValid) { + // not already a moment object + value = me.parseTime(me.getRightValue(value)); + } + if (value && value.isValid && value.isValid()) { + offset = value.diff(me.firstTick, me.tickUnit, true); + } + } + + if (offset !== null) { + var decimal = offset !== 0 ? offset / me.scaleSizeInUnits : offset; + + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var valueOffset = (innerWidth * decimal) + me.paddingLeft; + + return me.left + Math.round(valueOffset); + } + var innerHeight = me.height - (me.paddingTop + me.paddingBottom); + var heightOffset = (innerHeight * decimal) + me.paddingTop; + + return me.top + Math.round(heightOffset); + } + }, + getPixelForTick: function(index) { + return this.getPixelForValue(this.tickMoments[index], null, null); + }, + getValueForPixel: function(pixel) { + var me = this; + var innerDimension = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom); + var offset = (pixel - (me.isHorizontal() ? me.left + me.paddingLeft : me.top + me.paddingTop)) / innerDimension; + offset *= me.scaleSizeInUnits; + return me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds'); + }, + parseTime: function(label) { + var me = this; + if (typeof me.options.time.parser === 'string') { + return moment(label, me.options.time.parser); + } + if (typeof me.options.time.parser === 'function') { + return me.options.time.parser(label); + } + // Date objects + if (typeof label.getMonth === 'function' || typeof label === 'number') { + return moment(label); + } + // Moment support + if (label.isValid && label.isValid()) { + return label; + } + // Custom parsing (return an instance of moment) + if (typeof me.options.time.format !== 'string' && me.options.time.format.call) { + console.warn('options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale'); + return me.options.time.format(label); + } + // Moment format parsing + return moment(label, me.options.time.format); + } + }); + Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig); + + }; + },{"1":1}]},{},[7])(7) -}); +}); \ No newline at end of file